From 6d46ba5d9194cb0d7d841764280a7da13b86f4ed Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 May 2024 14:23:43 -0400 Subject: [PATCH 01/17] Add demo of position-anchor --- .stylelintrc.json | 6 +++- index.html | 65 ++++++++++++++++++++++++++++++++++++++ public/position-anchor.css | 22 +++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 public/position-anchor.css diff --git a/.stylelintrc.json b/.stylelintrc.json index bece359..2eb6e78 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -14,7 +14,11 @@ "property-no-unknown": [ true, { - "ignoreProperties": ["anchor-name", "position-fallback"] + "ignoreProperties": [ + "anchor-name", + "position-anchor", + "position-fallback" + ] } ] } diff --git a/index.html b/index.html index 890339a..af823fb 100644 --- a/index.html +++ b/index.html @@ -24,6 +24,7 @@ + @@ -309,6 +310,70 @@

bottom: anchor(top); } +
+

+ + Positioning with anchor() [ + position anchor property] +

+
+
Anchor A
+
+ Target A +
+
Anchor B
+
+ Target B +
+
+

+ With polyfill applied: Targets are positioned at the top right corner of + the Anchor. +

+
<div id="my-position-anchor-a" class="anchor">Anchor A</div>
+<div id="my-position-target-a" class="target" anchor="my-implicit-anchor">
+  Target A
+</div>
+<div id="my-position-anchor-b" class="anchor">Anchor B</div>
+<div id="my-position-target-b" class="target" anchor="my-implicit-anchor">
+  Target B
+</div>
+
+#position-anchor .target {
+  position: absolute;
+  bottom: anchor(top);
+  left: anchor(right);
+}
+
+#my-position-target-a {
+  position-anchor: --my-position-anchor-a;
+}
+
+#my-position-target-b {
+  position-anchor: --my-position-anchor-b;
+}
+
+#my-position-anchor-a {
+  anchor-name: --my-position-anchor-a;
+  margin-bottom: 3em;
+}
+
+#my-position-anchor-b {
+  anchor-name: --my-position-anchor-b;
+}
+
+
+

diff --git a/public/position-anchor.css b/public/position-anchor.css new file mode 100644 index 0000000..b8e3f27 --- /dev/null +++ b/public/position-anchor.css @@ -0,0 +1,22 @@ +#position-anchor .target { + position: absolute; + bottom: anchor(top); + left: anchor(right); +} + +#my-position-target-a { + position-anchor: --my-position-anchor-a; +} + +#my-position-target-b { + position-anchor: --my-position-anchor-b; +} + +#my-position-anchor-a { + anchor-name: --my-position-anchor-a; + margin-bottom: 3em; +} + +#my-position-anchor-b { + anchor-name: --my-position-anchor-b; +} From 29bb583aa52a8a6b7a67813e5eab227962ec8713 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 21 May 2024 11:42:55 -0400 Subject: [PATCH 02/17] Remove implicit anchor from position-anchor demo --- index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.html b/index.html index af823fb..2ab31f4 100644 --- a/index.html +++ b/index.html @@ -321,7 +321,6 @@

Target A
@@ -329,7 +328,6 @@

Target B
From 21cad6e5a46c411ccdf1d7446825defa972367df Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 23 May 2024 08:53:10 -0400 Subject: [PATCH 03/17] Add position-anchor --- src/parse.ts | 41 +++++++++++++- tests/unit/parse.test.ts | 114 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 9f7ae1b..5ad2e84 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -100,6 +100,7 @@ const ANCHOR_SIDES: AnchorSideKeyword[] = [ 'center', ]; +const POSITION_ANCHOR_DATASET_KEY = 'polyfillPositionAnchorName'; export type AnchorSide = AnchorSideKeyword | number; export type AnchorSize = @@ -221,6 +222,10 @@ function isIdentifier(node: csstree.CssNode): node is csstree.Identifier { return Boolean(node.type === 'Identifier' && node.name); } +function getDeclarationValue(node: DeclarationWithValue) { + return (node.value.children.first as csstree.Identifier).name; +} + function isPercentage(node: csstree.CssNode): node is csstree.Percentage { return Boolean(node.type === 'Percentage' && node.value); } @@ -249,6 +254,12 @@ export function isBoxAlignmentProp( return BOX_ALIGNMENT_PROPS.includes(property as BoxAlignmentProperty); } +function isPositionAnchorDeclaration( + node: csstree.CssNode, +): node is DeclarationWithValue { + return node.type === 'Declaration' && node.property === 'position-anchor'; +} + function parseAnchorFn( node: csstree.FunctionNode, replaceCss?: boolean, @@ -338,7 +349,7 @@ function getAnchorNameData(node: csstree.CssNode, rule?: csstree.Raw) { node.value.children.first && rule?.value ) { - const name = (node.value.children.first as csstree.Identifier).name; + const name = getDeclarationValue(node); return { name, selector: rule.value }; } return {}; @@ -394,12 +405,25 @@ function getAnchorFunctionData( return {}; } +function getPositionAnchorData(node: csstree.CssNode, rule?: csstree.Raw) { + if (isPositionAnchorDeclaration(node) && rule?.value) { + const targets = document.querySelectorAll(rule.value); + const name = getDeclarationValue(node); + + for (const targetEl of targets) { + (targetEl as HTMLElement).dataset[POSITION_ANCHOR_DATASET_KEY] = name; + } + return { name, selector: rule.value }; + } + return {}; +} + function getPositionFallbackDeclaration( node: csstree.Declaration, rule?: csstree.Raw, ) { if (isFallbackDeclaration(node) && node.value.children.first && rule?.value) { - const name = (node.value.children.first as csstree.Identifier).name; + const name = getDeclarationValue(node); return { name, selector: rule.value }; } return {}; @@ -452,6 +476,8 @@ async function getAnchorEl( return await validatedForPositioning(targetEl, [ `#${CSS.escape(anchorAttr)}`, ]); + } else if (targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]) { + anchorName = targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]; } } const anchorSelectors = anchorName ? anchorNames[anchorName] ?? [] : []; @@ -571,6 +597,17 @@ export async function parseCSS(styleData: StyleData[]) { } } + // Parse `position-anchor` data + const { name: positionAnchorName, selector: positionAnchorSelector } = + getPositionAnchorData(node, rule); + if (positionAnchorName && positionAnchorSelector) { + if (anchorNames[positionAnchorName]) { + anchorNames[positionAnchorName].push(positionAnchorSelector); + } else { + anchorNames[positionAnchorName] = [positionAnchorSelector]; + } + } + // Parse `anchor()` function const { prop, diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index 29048c6..caec1d8 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -163,6 +163,120 @@ describe('parseCSS', () => { expect(rules).toEqual(expected); }); + it('parses `position-anchor` on different selector', async () => { + document.body.innerHTML = ` +
+
+
+
+
`; + const css = ` + #my-target-1 { + top: anchor(bottom); + } + #my-target-2 { + bottom: anchor(top); + } + .my-targets { + position: absolute; + position-anchor: --my-anchor; + } + #my-anchor { + anchor-name: --my-anchor; + } + `; + document.head.innerHTML = ``; + const { rules } = await parseCSS([{ css }] as StyleData[]); + const expected = { + '#my-target-1': { + declarations: { + top: [ + { + anchorName: undefined, + anchorEl: document.getElementById('my-anchor'), + targetEl: document.getElementById('my-target-1'), + anchorSide: 'bottom', + fallbackValue: '0px', + uuid: expect.any(String), + }, + ], + }, + }, + '#my-target-2': { + declarations: { + bottom: [ + { + anchorName: undefined, + anchorEl: document.getElementById('my-anchor'), + targetEl: document.getElementById('my-target-2'), + anchorSide: 'top', + fallbackValue: '0px', + uuid: expect.any(String), + }, + ], + }, + }, + }; + expect(rules).toEqual(expected); + }); + + it('parses `position-anchor` declared multiple times', async () => { + document.body.innerHTML = ` +
+
+
+
+
`; + const css = ` + #my-target-1 { + top: anchor(bottom); + position-anchor: --my-anchor; + position: absolute; + } + #my-target-2 { + bottom: anchor(top); + position-anchor: --my-anchor; + position: absolute; + } + #my-anchor { + anchor-name: --my-anchor; + } + `; + document.head.innerHTML = ``; + const { rules } = await parseCSS([{ css }] as StyleData[]); + const expected = { + '#my-target-1': { + declarations: { + top: [ + { + anchorName: undefined, + anchorEl: document.getElementById('my-anchor'), + targetEl: document.getElementById('my-target-1'), + anchorSide: 'bottom', + fallbackValue: '0px', + uuid: expect.any(String), + }, + ], + }, + }, + '#my-target-2': { + declarations: { + bottom: [ + { + anchorName: undefined, + anchorEl: document.getElementById('my-anchor'), + targetEl: document.getElementById('my-target-2'), + anchorSide: 'top', + fallbackValue: '0px', + uuid: expect.any(String), + }, + ], + }, + }, + }; + expect(rules).toEqual(expected); + }); + it('handles duplicate anchor-names', async () => { document.body.innerHTML = '
'; From eb326b84ed226af2c7c8bfb901963f99cebd92a6 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 23 May 2024 08:55:18 -0400 Subject: [PATCH 04/17] Lint --- index.html | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index 2ab31f4..ab16016 100644 --- a/index.html +++ b/index.html @@ -318,19 +318,9 @@

Anchor A
-
- Target A -
+
Target A
Anchor B
-
- Target B -
+
Target B

With polyfill applied: Targets are positioned at the top right corner of From 61636a08999d2f15ffa1726f919cb0a348e6fd83 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 23 May 2024 09:26:43 -0400 Subject: [PATCH 05/17] Handle position-anchor first to reflect order from spec --- src/parse.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index 5ad2e84..b96150e 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -470,14 +470,14 @@ async function getAnchorEl( const customPropName = anchorObj.customPropName; if (targetEl && !anchorName) { const anchorAttr = targetEl.getAttribute('anchor'); - if (customPropName) { + if (targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]) { + anchorName = targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]; + } else if (customPropName) { anchorName = getCSSPropertyValue(targetEl, customPropName); } else if (anchorAttr) { return await validatedForPositioning(targetEl, [ `#${CSS.escape(anchorAttr)}`, ]); - } else if (targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]) { - anchorName = targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]; } } const anchorSelectors = anchorName ? anchorNames[anchorName] ?? [] : []; From c1f35e156e2f4a40e81daf64a1f05557c5730c79 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 24 May 2024 16:42:51 -0400 Subject: [PATCH 06/17] nits --- index.html | 19 ++++++------------- src/parse.ts | 11 ++++++----- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index ab16016..f04649b 100644 --- a/index.html +++ b/index.html @@ -314,7 +314,7 @@

Positioning with anchor() [ - position anchor property] + position-anchor property]

Anchor A
@@ -324,20 +324,16 @@

With polyfill applied: Targets are positioned at the top right corner of - the Anchor. + their respective Anchors.

<div id="my-position-anchor-a" class="anchor">Anchor A</div>
-<div id="my-position-target-a" class="target" anchor="my-implicit-anchor">
-  Target A
-</div>
+<div id="my-position-target-a" class="target">Target A</div>
 <div id="my-position-anchor-b" class="anchor">Anchor B</div>
-<div id="my-position-target-b" class="target" anchor="my-implicit-anchor">
-  Target B
-</div>
+<div id="my-position-target-b" class="target">Target B</div>
 
 #position-anchor .target {
+>.target {
   position: absolute;
   bottom: anchor(top);
   left: anchor(right);
@@ -353,15 +349,12 @@ 

#my-position-anchor-a { anchor-name: --my-position-anchor-a; - margin-bottom: 3em; } #my-position-anchor-b { anchor-name: --my-position-anchor-b; -} -

+}

-

diff --git a/src/parse.ts b/src/parse.ts index b96150e..025d343 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -100,7 +100,6 @@ const ANCHOR_SIDES: AnchorSideKeyword[] = [ 'center', ]; -const POSITION_ANCHOR_DATASET_KEY = 'polyfillPositionAnchorName'; export type AnchorSide = AnchorSideKeyword | number; export type AnchorSize = @@ -174,6 +173,8 @@ type Fallbacks = Record< } >; +const POSITION_ANCHOR_DATASET_KEY = 'polyfillPositionAnchorName'; + function isDeclaration(node: csstree.CssNode): node is DeclarationWithValue { return node.type === 'Declaration'; } @@ -222,10 +223,6 @@ function isIdentifier(node: csstree.CssNode): node is csstree.Identifier { return Boolean(node.type === 'Identifier' && node.name); } -function getDeclarationValue(node: DeclarationWithValue) { - return (node.value.children.first as csstree.Identifier).name; -} - function isPercentage(node: csstree.CssNode): node is csstree.Percentage { return Boolean(node.type === 'Percentage' && node.value); } @@ -260,6 +257,10 @@ function isPositionAnchorDeclaration( return node.type === 'Declaration' && node.property === 'position-anchor'; } +function getDeclarationValue(node: DeclarationWithValue) { + return (node.value.children.first as csstree.Identifier).name; +} + function parseAnchorFn( node: csstree.FunctionNode, replaceCss?: boolean, From d5500e0b9d21dd0a2849d3042deedd07f233e886 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 7 Jun 2024 16:30:33 -0400 Subject: [PATCH 07/17] Move position-anchor to cascade --- index.html | 14 +++++----- public/position-anchor.css | 11 +++----- src/cascade.ts | 53 ++++++++++++++++++++++++++++++++++++++ src/parse.ts | 22 +++++++--------- src/polyfill.ts | 10 +++++-- src/transform.ts | 8 +++++- 6 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 src/cascade.ts diff --git a/index.html b/index.html index f04649b..904384b 100644 --- a/index.html +++ b/index.html @@ -333,20 +333,18 @@

<div id="my-position-target-b" class="target">Target B</div> .target { +> +#my-position-target-b { + position-anchor: --my-position-anchor-b; +} + +.target { position: absolute; bottom: anchor(top); left: anchor(right); -} - -#my-position-target-a { position-anchor: --my-position-anchor-a; } -#my-position-target-b { - position-anchor: --my-position-anchor-b; -} - #my-position-anchor-a { anchor-name: --my-position-anchor-a; } diff --git a/public/position-anchor.css b/public/position-anchor.css index b8e3f27..9968617 100644 --- a/public/position-anchor.css +++ b/public/position-anchor.css @@ -1,17 +1,14 @@ +#position-anchor #my-position-target-b { + position-anchor: --my-position-anchor-b; +} + #position-anchor .target { position: absolute; bottom: anchor(top); left: anchor(right); -} - -#my-position-target-a { position-anchor: --my-position-anchor-a; } -#my-position-target-b { - position-anchor: --my-position-anchor-b; -} - #my-position-anchor-a { anchor-name: --my-position-anchor-a; margin-bottom: 3em; diff --git a/src/cascade.ts b/src/cascade.ts new file mode 100644 index 0000000..8777fdd --- /dev/null +++ b/src/cascade.ts @@ -0,0 +1,53 @@ +import * as csstree from 'css-tree'; + +import { StyleData } from './fetch.js'; +import { DeclarationWithValue, getAST, getDeclarationValue } from './parse.js'; + +function isPositionAnchorDeclaration( + node: csstree.CssNode, +): node is DeclarationWithValue { + return node.type === 'Declaration' && node.property === 'position-anchor'; +} + +// Move `position-anchor` declaration to cascadable `--position-anchor` +// property. +function shiftPositionAnchorData(node: csstree.CssNode, block?: csstree.Block) { + if (isPositionAnchorDeclaration(node) && block) { + const newProp = { + type: 'Declaration', + important: false, + property: '--position-anchor', + value: { + type: 'Raw', + value: getDeclarationValue(node), + }, + } as const; + + block.children.append(block.children.createItem(newProp)); + return { updated: true }; + } + return {}; +} + +export async function cascadeCSS(styleData: StyleData[]) { + const changedStyles = []; + for (const styleObj of styleData) { + let changed = false; + const ast = getAST(styleObj.css); + csstree.walk(ast, function (node) { + const block = this.rule?.block; + + const { updated } = shiftPositionAnchorData(node, block); + if (updated) { + changed = true; + } + }); + if (changed) { + // Update CSS + styleObj.css = csstree.generate(ast); + styleObj.changed = true; + changedStyles.push(styleObj); + } + } + return { changedStyles }; +} diff --git a/src/parse.ts b/src/parse.ts index 025d343..3ebfe53 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -4,7 +4,7 @@ import { nanoid } from 'nanoid/non-secure'; import { StyleData } from './fetch.js'; import { validatedForPositioning } from './validate.js'; -interface DeclarationWithValue extends csstree.Declaration { +export interface DeclarationWithValue extends csstree.Declaration { value: csstree.Value; } @@ -173,8 +173,6 @@ type Fallbacks = Record< } >; -const POSITION_ANCHOR_DATASET_KEY = 'polyfillPositionAnchorName'; - function isDeclaration(node: csstree.CssNode): node is DeclarationWithValue { return node.type === 'Declaration'; } @@ -257,7 +255,7 @@ function isPositionAnchorDeclaration( return node.type === 'Declaration' && node.property === 'position-anchor'; } -function getDeclarationValue(node: DeclarationWithValue) { +export function getDeclarationValue(node: DeclarationWithValue) { return (node.value.children.first as csstree.Identifier).name; } @@ -408,12 +406,7 @@ function getAnchorFunctionData( function getPositionAnchorData(node: csstree.CssNode, rule?: csstree.Raw) { if (isPositionAnchorDeclaration(node) && rule?.value) { - const targets = document.querySelectorAll(rule.value); const name = getDeclarationValue(node); - - for (const targetEl of targets) { - (targetEl as HTMLElement).dataset[POSITION_ANCHOR_DATASET_KEY] = name; - } return { name, selector: rule.value }; } return {}; @@ -471,8 +464,13 @@ async function getAnchorEl( const customPropName = anchorObj.customPropName; if (targetEl && !anchorName) { const anchorAttr = targetEl.getAttribute('anchor'); - if (targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]) { - anchorName = targetEl.dataset[POSITION_ANCHOR_DATASET_KEY]; + const positionAnchorProperty = getCSSPropertyValue( + targetEl, + '--position-anchor', + ); + + if (positionAnchorProperty) { + anchorName = positionAnchorProperty; } else if (customPropName) { anchorName = getCSSPropertyValue(targetEl, customPropName); } else if (anchorAttr) { @@ -485,7 +483,7 @@ async function getAnchorEl( return await validatedForPositioning(targetEl, anchorSelectors); } -function getAST(cssText: string) { +export function getAST(cssText: string) { const ast = csstree.parse(cssText, { parseAtrulePrelude: false, parseRulePrelude: false, diff --git a/src/polyfill.ts b/src/polyfill.ts index 930bfdd..a095b48 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -6,6 +6,7 @@ import { type Rect, } from '@floating-ui/dom'; +import { cascadeCSS } from './cascade.js'; import { fetchCSS } from './fetch.js'; import { AnchorFunction, @@ -412,14 +413,19 @@ export async function polyfill(animationFrame?: boolean) { ? Boolean(window.UPDATE_ANCHOR_ON_ANIMATION_FRAME) : animationFrame; // fetch CSS from stylesheet and inline style - const styleData = await fetchCSS(); + let styleData = await fetchCSS(); + // pre parse CSS styles that we need to cascade + const { changedStyles } = await cascadeCSS(styleData); + if (Object.values(changedStyles).length) { + styleData = await transformCSS(styleData); + } // parse CSS const { rules, inlineStyles } = await parseCSS(styleData); if (Object.values(rules).length) { // update source code - await transformCSS(styleData, inlineStyles); + await transformCSS(styleData, inlineStyles, true); // calculate position values await position(rules, useAnimationFrame); diff --git a/src/transform.ts b/src/transform.ts index 1900535..81785c0 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -3,8 +3,11 @@ import { type StyleData } from './fetch.js'; export async function transformCSS( styleData: StyleData[], inlineStyles?: Map>, + cleanup = false, ) { + const updatedStyleData: StyleData[] = []; for (const { el, css, changed } of styleData) { + const updatedObject: StyleData = { el, css, changed: false }; if (changed) { if (el.tagName.toLowerCase() === 'style') { // Handle inline stylesheets @@ -23,6 +26,7 @@ export async function transformCSS( // Wait for new stylesheet to be loaded await promise; URL.revokeObjectURL(url); + updatedObject.el = link; } else if (el.hasAttribute('data-has-inline-styles')) { // Handle inline styles const attr = el.getAttribute('data-has-inline-styles'); @@ -43,8 +47,10 @@ export async function transformCSS( } } // Remove no-longer-needed data-attribute - if (el.hasAttribute('data-has-inline-styles')) { + if (cleanup && el.hasAttribute('data-has-inline-styles')) { el.removeAttribute('data-has-inline-styles'); } + updatedStyleData.push(updatedObject); } + return updatedStyleData; } From ffa0e0e67876359705f17e9c0f9a298f0d9d9c44 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 7 Jun 2024 16:44:01 -0400 Subject: [PATCH 08/17] Move shared items to utils --- src/cascade.ts | 8 ++++++-- src/fetch.ts | 7 +------ src/parse.ts | 25 ++++++------------------- src/transform.ts | 2 +- src/utils.ts | 26 ++++++++++++++++++++++++++ tests/unit/parse.test.ts | 2 +- 6 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 src/utils.ts diff --git a/src/cascade.ts b/src/cascade.ts index 8777fdd..095b0fd 100644 --- a/src/cascade.ts +++ b/src/cascade.ts @@ -1,7 +1,11 @@ import * as csstree from 'css-tree'; -import { StyleData } from './fetch.js'; -import { DeclarationWithValue, getAST, getDeclarationValue } from './parse.js'; +import { + type DeclarationWithValue, + getAST, + getDeclarationValue, + type StyleData, +} from './utils.js'; function isPositionAnchorDeclaration( node: csstree.CssNode, diff --git a/src/fetch.ts b/src/fetch.ts index c54a938..9fcd436 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,11 +1,6 @@ import { nanoid } from 'nanoid/non-secure'; -export interface StyleData { - el: HTMLElement; - css: string; - url?: URL; - changed?: boolean; -} +import { StyleData } from './utils.js'; export function isStyleLink(link: HTMLLinkElement): link is HTMLLinkElement { return Boolean( diff --git a/src/parse.ts b/src/parse.ts index 3ebfe53..8b26101 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,13 +1,14 @@ import * as csstree from 'css-tree'; import { nanoid } from 'nanoid/non-secure'; -import { StyleData } from './fetch.js'; +import { + DeclarationWithValue, + getAST, + getDeclarationValue, + StyleData, +} from './utils.js'; import { validatedForPositioning } from './validate.js'; -export interface DeclarationWithValue extends csstree.Declaration { - value: csstree.Value; -} - interface AtRuleRaw extends csstree.Atrule { prelude: csstree.Raw | null; } @@ -255,10 +256,6 @@ function isPositionAnchorDeclaration( return node.type === 'Declaration' && node.property === 'position-anchor'; } -export function getDeclarationValue(node: DeclarationWithValue) { - return (node.value.children.first as csstree.Identifier).name; -} - function parseAnchorFn( node: csstree.FunctionNode, replaceCss?: boolean, @@ -483,16 +480,6 @@ async function getAnchorEl( return await validatedForPositioning(targetEl, anchorSelectors); } -export function getAST(cssText: string) { - const ast = csstree.parse(cssText, { - parseAtrulePrelude: false, - parseRulePrelude: false, - parseCustomProperty: true, - }); - - return ast; -} - export async function parseCSS(styleData: StyleData[]) { const anchorFunctions: AnchorFunctionDeclarations = {}; const fallbackTargets: FallbackTargets = {}; diff --git a/src/transform.ts b/src/transform.ts index 81785c0..3a7ebdc 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,4 +1,4 @@ -import { type StyleData } from './fetch.js'; +import { type StyleData } from './utils.js'; export async function transformCSS( styleData: StyleData[], diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..32e884c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,26 @@ +import * as csstree from 'css-tree'; + +export interface DeclarationWithValue extends csstree.Declaration { + value: csstree.Value; +} + +export function getAST(cssText: string) { + const ast = csstree.parse(cssText, { + parseAtrulePrelude: false, + parseRulePrelude: false, + parseCustomProperty: true, + }); + + return ast; +} + +export function getDeclarationValue(node: DeclarationWithValue) { + return (node.value.children.first as csstree.Identifier).name; +} + +export interface StyleData { + el: HTMLElement; + css: string; + url?: URL; + changed?: boolean; +} diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index caec1d8..12c5e12 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -1,5 +1,5 @@ -import { type StyleData } from '../../src/fetch.js'; import { AnchorPositions, parseCSS } from '../../src/parse.js'; +import { type StyleData } from '../../src/utils.js'; import { getSampleCSS, sampleBaseCSS } from './../helpers.js'; describe('parseCSS', () => { From a65e0710bbc12769b004d9a2ddf1e9dd6a43929a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 10 Jun 2024 13:05:47 -0400 Subject: [PATCH 09/17] Fix test --- tests/unit/transform.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/transform.test.ts b/tests/unit/transform.test.ts index a00ec38..d6afc72 100644 --- a/tests/unit/transform.test.ts +++ b/tests/unit/transform.test.ts @@ -37,7 +37,7 @@ describe('transformCSS', () => { ]; const inlineStyles = new Map(); inlineStyles.set(div, { '--foo': '--bar' }); - const promise = transformCSS(styleData, inlineStyles); + const promise = transformCSS(styleData, inlineStyles, true); link = document.querySelector('link') as HTMLLinkElement; link.dispatchEvent(new Event('load')); await promise; From 899534144d30b9e9127d36b46acdde0a126164d8 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 10 Jun 2024 13:06:13 -0400 Subject: [PATCH 10/17] Generate CSS in spec mode --- src/cascade.ts | 3 ++- src/parse.ts | 19 ++++++++++--------- src/utils.ts | 12 +++++++++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/cascade.ts b/src/cascade.ts index 095b0fd..8f317b5 100644 --- a/src/cascade.ts +++ b/src/cascade.ts @@ -2,6 +2,7 @@ import * as csstree from 'css-tree'; import { type DeclarationWithValue, + generateCSS, getAST, getDeclarationValue, type StyleData, @@ -48,7 +49,7 @@ export async function cascadeCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; changedStyles.push(styleObj); } diff --git a/src/parse.ts b/src/parse.ts index 8b26101..32049f0 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -3,6 +3,7 @@ import { nanoid } from 'nanoid/non-secure'; import { DeclarationWithValue, + generateCSS, getAST, getDeclarationValue, StyleData, @@ -270,7 +271,7 @@ function parseAnchorFn( const args: csstree.CssNode[] = []; node.children.toArray().forEach((child) => { if (foundComma) { - fallbackValue = `${fallbackValue}${csstree.generate(child)}`; + fallbackValue = `${fallbackValue}${generateCSS(child)}`; return; } if (child.type === 'Operator' && child.value === ',') { @@ -380,7 +381,7 @@ function getAnchorFunctionData( ) { if ((isAnchorFunction(node) || isAnchorSizeFunction(node)) && declaration) { if (declaration.property.startsWith('--')) { - const original = csstree.generate(declaration.value); + const original = generateCSS(declaration.value); const data = parseAnchorFn(node, true); // Store the original anchor function so that we can restore it later customPropOriginals[data.uuid] = original; @@ -438,7 +439,7 @@ function getPositionFallbackRules(node: csstree.Atrule) { const tryBlock: TryBlock = { uuid: `${name}-try-${nanoid(12)}`, declarations: Object.fromEntries( - declarations.map((d) => [d.property, csstree.generate(d.value)]), + declarations.map((d) => [d.property, generateCSS(d.value)]), ), }; tryBlocks.push(tryBlock); @@ -559,7 +560,7 @@ export async function parseCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; } } @@ -617,7 +618,7 @@ export async function parseCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; } } @@ -693,7 +694,7 @@ export async function parseCSS(styleData: StyleData[]) { // now being re-assigned to another custom property... const uuid = `${child.name}-anchor-${nanoid(12)}`; // Store the original declaration so that we can restore it later - const original = csstree.generate(declaration.value); + const original = generateCSS(declaration.value); customPropOriginals[uuid] = original; // Store a mapping of the new property to the original property // name, as well as the unique uuid(s) temporarily used to replace @@ -717,7 +718,7 @@ export async function parseCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; } } @@ -874,7 +875,7 @@ export async function parseCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; } } @@ -928,7 +929,7 @@ export async function parseCSS(styleData: StyleData[]) { }); if (changed) { // Update CSS - styleObj.css = csstree.generate(ast); + styleObj.css = generateCSS(ast); styleObj.changed = true; } } diff --git a/src/utils.ts b/src/utils.ts index 32e884c..390c13b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,10 +10,20 @@ export function getAST(cssText: string) { parseRulePrelude: false, parseCustomProperty: true, }); - return ast; } +export function generateCSS(ast: csstree.CssNode) { + const css = csstree.generate(ast, { + // Default `safe` adds extra (potentially breaking0 spaces for compatibility + // with old browsers. + mode: 'spec', + // Pending types updated in + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69772 + } as csstree.GenerateOptions); + return css; +} + export function getDeclarationValue(node: DeclarationWithValue) { return (node.value.children.first as csstree.Identifier).name; } From 255e4cef189348e11670a64381a654df4a7e1297 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 10 Jun 2024 13:38:13 -0400 Subject: [PATCH 11/17] Test cascade --- tests/unit/cascade.test.ts | 16 ++++++++++++++++ tests/unit/parse.test.ts | 3 +++ 2 files changed, 19 insertions(+) create mode 100644 tests/unit/cascade.test.ts diff --git a/tests/unit/cascade.test.ts b/tests/unit/cascade.test.ts new file mode 100644 index 0000000..c68cfb2 --- /dev/null +++ b/tests/unit/cascade.test.ts @@ -0,0 +1,16 @@ +import { cascadeCSS } from '../../src/cascade.js'; +import { StyleData } from '../../src/utils.js'; +import { getSampleCSS } from './../helpers.js'; + +describe('cascadeCSS', () => { + it('moves position-anchor to custom property', async () => { + const srcCSS = getSampleCSS('position-anchor'); + const { changedStyles } = await cascadeCSS([ + { css: srcCSS }, + ] as StyleData[]); + expect(changedStyles[0].changed).toBe(true); + const { css } = changedStyles[0]; + expect(css).toContain('--position-anchor:--my-position-anchor-b'); + expect(css).toContain('--position-anchor:--my-position-anchor-a'); + }); +}); diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index 12c5e12..ecf582f 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -180,6 +180,7 @@ describe('parseCSS', () => { .my-targets { position: absolute; position-anchor: --my-anchor; + --position-anchor: --my-anchor; } #my-anchor { anchor-name: --my-anchor; @@ -230,12 +231,14 @@ describe('parseCSS', () => { const css = ` #my-target-1 { top: anchor(bottom); + --position-anchor: --my-anchor; position-anchor: --my-anchor; position: absolute; } #my-target-2 { bottom: anchor(top); position-anchor: --my-anchor; + --position-anchor: --my-anchor; position: absolute; } #my-anchor { From c6ae11a013cb6b4a14169309691eecba3f3be0a2 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 21 Jun 2024 14:18:40 -0400 Subject: [PATCH 12/17] update comment --- src/utils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 390c13b..ab0ad91 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,12 +15,10 @@ export function getAST(cssText: string) { export function generateCSS(ast: csstree.CssNode) { const css = csstree.generate(ast, { - // Default `safe` adds extra (potentially breaking0 spaces for compatibility + // Default `safe` adds extra (potentially breaking) spaces for compatibility // with old browsers. mode: 'spec', - // Pending types updated in - // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69772 - } as csstree.GenerateOptions); + }); return css; } From f8ee2615e7f522beff8c4c82e91700ba4d1b31ca Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 21 Jun 2024 14:51:08 -0400 Subject: [PATCH 13/17] consistent type imports --- .eslintrc.cjs | 4 ++++ index.html | 3 +-- src/fetch.ts | 2 +- src/parse.ts | 4 ++-- src/polyfill.ts | 12 ++++++------ src/utils.ts | 6 ++---- tests/e2e/polyfill.test.ts | 2 +- tests/e2e/validate.test.ts | 2 +- tests/unit/cascade.test.ts | 2 +- tests/unit/parse.test.ts | 2 +- tests/unit/polyfill.test.ts | 4 ++-- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index dc8310c..ff070c9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -29,5 +29,9 @@ module.exports = { 'simple-import-sort/imports': 1, 'no-console': 1, 'no-warning-comments': [1, { terms: ['todo', 'fixme', '@@@'] }], + '@typescript-eslint/consistent-type-imports': [ + 1, + { fixStyle: 'inline-type-imports' }, + ], }, }; diff --git a/index.html b/index.html index 69d8b40..6cc22b7 100644 --- a/index.html +++ b/index.html @@ -334,8 +334,7 @@

<div id="my-position-target-b" class="target">Target B</div> -#my-position-target-b { +>#my-position-target-b { position-anchor: --my-position-anchor-b; } diff --git a/src/fetch.ts b/src/fetch.ts index 9fcd436..7d414e8 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,6 +1,6 @@ import { nanoid } from 'nanoid/non-secure'; -import { StyleData } from './utils.js'; +import { type StyleData } from './utils.js'; export function isStyleLink(link: HTMLLinkElement): link is HTMLLinkElement { return Boolean( diff --git a/src/parse.ts b/src/parse.ts index 90f2fee..514694f 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -2,11 +2,11 @@ import * as csstree from 'css-tree'; import { nanoid } from 'nanoid/non-secure'; import { - DeclarationWithValue, + type DeclarationWithValue, generateCSS, getAST, getDeclarationValue, - StyleData, + type StyleData, } from './utils.js'; import { validatedForPositioning } from './validate.js'; diff --git a/src/polyfill.ts b/src/polyfill.ts index a095b48..efe1f62 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -1,7 +1,7 @@ import { autoUpdate, detectOverflow, - MiddlewareState, + type MiddlewareState, platform, type Rect, } from '@floating-ui/dom'; @@ -9,18 +9,18 @@ import { import { cascadeCSS } from './cascade.js'; import { fetchCSS } from './fetch.js'; import { - AnchorFunction, - AnchorFunctionDeclaration, + type AnchorFunction, + type AnchorFunctionDeclaration, type AnchorPositions, type AnchorSide, type AnchorSize, getCSSPropertyValue, - InsetProperty, + type InsetProperty, isInsetProp, isSizingProp, parseCSS, - SizingProperty, - TryBlock, + type SizingProperty, + type TryBlock, } from './parse.js'; import { transformCSS } from './transform.js'; diff --git a/src/utils.ts b/src/utils.ts index ab0ad91..29527c4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,21 +5,19 @@ export interface DeclarationWithValue extends csstree.Declaration { } export function getAST(cssText: string) { - const ast = csstree.parse(cssText, { + return csstree.parse(cssText, { parseAtrulePrelude: false, parseRulePrelude: false, parseCustomProperty: true, }); - return ast; } export function generateCSS(ast: csstree.CssNode) { - const css = csstree.generate(ast, { + return csstree.generate(ast, { // Default `safe` adds extra (potentially breaking) spaces for compatibility // with old browsers. mode: 'spec', }); - return css; } export function getDeclarationValue(node: DeclarationWithValue) { diff --git a/tests/e2e/polyfill.test.ts b/tests/e2e/polyfill.test.ts index cfa7ff6..0be561e 100644 --- a/tests/e2e/polyfill.test.ts +++ b/tests/e2e/polyfill.test.ts @@ -1,4 +1,4 @@ -import { expect, Locator, type Page, test } from '@playwright/test'; +import { expect, type Locator, type Page, test } from '@playwright/test'; test.beforeEach(async ({ page }) => { // Listen for all console logs diff --git a/tests/e2e/validate.test.ts b/tests/e2e/validate.test.ts index aaa7d9b..efb5de5 100644 --- a/tests/e2e/validate.test.ts +++ b/tests/e2e/validate.test.ts @@ -1,4 +1,4 @@ -import { Browser, expect, type Page, test } from '@playwright/test'; +import { type Browser, expect, type Page, test } from '@playwright/test'; import { isValidAnchorElement, diff --git a/tests/unit/cascade.test.ts b/tests/unit/cascade.test.ts index c68cfb2..96c2574 100644 --- a/tests/unit/cascade.test.ts +++ b/tests/unit/cascade.test.ts @@ -1,5 +1,5 @@ import { cascadeCSS } from '../../src/cascade.js'; -import { StyleData } from '../../src/utils.js'; +import { type StyleData } from '../../src/utils.js'; import { getSampleCSS } from './../helpers.js'; describe('cascadeCSS', () => { diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index ee4b53d..a37b0dd 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -1,4 +1,4 @@ -import { AnchorPositions, parseCSS } from '../../src/parse.js'; +import { type AnchorPositions, parseCSS } from '../../src/parse.js'; import { type StyleData } from '../../src/utils.js'; import { getSampleCSS, sampleBaseCSS } from './../helpers.js'; diff --git a/tests/unit/polyfill.test.ts b/tests/unit/polyfill.test.ts index 0d65f02..d465eca 100644 --- a/tests/unit/polyfill.test.ts +++ b/tests/unit/polyfill.test.ts @@ -1,9 +1,9 @@ -import { AnchorSide, AnchorSize } from '../../src/parse.js'; +import { type AnchorSide, type AnchorSize } from '../../src/parse.js'; import { getAxis, getAxisProperty, getPixelValue, - GetPixelValueOpts, + type GetPixelValueOpts, resolveLogicalSideKeyword, resolveLogicalSizeKeyword, } from '../../src/polyfill.js'; From ac3370d30679488d8363507f17ef0d6a7b4c2f07 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 21 Jun 2024 15:13:22 -0400 Subject: [PATCH 14/17] remove duplicate fn --- src/cascade.ts | 8 +------- src/parse.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/cascade.ts b/src/cascade.ts index 8f317b5..8edd72d 100644 --- a/src/cascade.ts +++ b/src/cascade.ts @@ -1,19 +1,13 @@ import * as csstree from 'css-tree'; +import { isPositionAnchorDeclaration } from './parse.js'; import { - type DeclarationWithValue, generateCSS, getAST, getDeclarationValue, type StyleData, } from './utils.js'; -function isPositionAnchorDeclaration( - node: csstree.CssNode, -): node is DeclarationWithValue { - return node.type === 'Declaration' && node.property === 'position-anchor'; -} - // Move `position-anchor` declaration to cascadable `--position-anchor` // property. function shiftPositionAnchorData(node: csstree.CssNode, block?: csstree.Block) { diff --git a/src/parse.ts b/src/parse.ts index 514694f..dd7625d 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -251,7 +251,7 @@ export function isBoxAlignmentProp( return BOX_ALIGNMENT_PROPS.includes(property as BoxAlignmentProperty); } -function isPositionAnchorDeclaration( +export function isPositionAnchorDeclaration( node: csstree.CssNode, ): node is DeclarationWithValue { return node.type === 'Declaration' && node.property === 'position-anchor'; From fd93c655fe1aa9badf6d4ec486f2069685628c5c Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 21 Jun 2024 15:16:42 -0400 Subject: [PATCH 15/17] use generateCSS fn --- src/parse.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index dd7625d..bad853e 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -911,9 +911,10 @@ export async function parseCSS(styleData: StyleData[]) { property: `${this.declaration.property}-${propUuid}`, value: { type: 'Raw', - value: csstree - .generate(this.declaration.value) - .replace(`var(${child.name})`, `var(${value})`), + value: generateCSS(this.declaration.value).replace( + `var(${child.name})`, + `var(${value})`, + ), }, }); changed = true; From 7298e90e3d8ec4684fafee3537cafb3db1504a22 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 24 Jun 2024 14:39:45 -0400 Subject: [PATCH 16/17] Address review --- index.html | 7 +++++-- src/cascade.ts | 30 +++++++++++++++--------------- src/parse.ts | 22 ++-------------------- src/polyfill.ts | 4 ++-- src/utils.ts | 4 +++- tests/unit/cascade.test.ts | 17 +++++++++-------- tests/unit/parse.test.ts | 8 ++++---- tests/unit/setup.ts | 3 +++ 8 files changed, 43 insertions(+), 52 deletions(-) diff --git a/index.html b/index.html index 6cc22b7..5fc4e18 100644 --- a/index.html +++ b/index.html @@ -334,7 +334,11 @@

<div id="my-position-target-b" class="target">Target B</div> #my-position-target-b { +>#my-position-target-a { + position-anchor: --my-position-anchor-a; +} + +#my-position-target-b { position-anchor: --my-position-anchor-b; } @@ -342,7 +346,6 @@

position: absolute; bottom: anchor(top); left: anchor(right); - position-anchor: --my-position-anchor-a; } #my-position-anchor-a { diff --git a/src/cascade.ts b/src/cascade.ts index 8edd72d..8dbd456 100644 --- a/src/cascade.ts +++ b/src/cascade.ts @@ -5,6 +5,7 @@ import { generateCSS, getAST, getDeclarationValue, + POSITION_ANCHOR_PROPERTY, type StyleData, } from './utils.js'; @@ -12,41 +13,40 @@ import { // property. function shiftPositionAnchorData(node: csstree.CssNode, block?: csstree.Block) { if (isPositionAnchorDeclaration(node) && block) { - const newProp = { + block.children.appendData({ type: 'Declaration', important: false, - property: '--position-anchor', + property: POSITION_ANCHOR_PROPERTY, value: { type: 'Raw', value: getDeclarationValue(node), }, - } as const; - - block.children.append(block.children.createItem(newProp)); + }); return { updated: true }; } return {}; } export async function cascadeCSS(styleData: StyleData[]) { - const changedStyles = []; for (const styleObj of styleData) { let changed = false; const ast = getAST(styleObj.css); - csstree.walk(ast, function (node) { - const block = this.rule?.block; - - const { updated } = shiftPositionAnchorData(node, block); - if (updated) { - changed = true; - } + csstree.walk(ast, { + visit: 'Declaration', + enter(node) { + const block = this.rule?.block; + const { updated } = shiftPositionAnchorData(node, block); + if (updated) { + changed = true; + } + }, }); + if (changed) { // Update CSS styleObj.css = generateCSS(ast); styleObj.changed = true; - changedStyles.push(styleObj); } } - return { changedStyles }; + return styleData.some((styleObj) => styleObj.changed === true); } diff --git a/src/parse.ts b/src/parse.ts index bad853e..ea104e8 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -6,6 +6,7 @@ import { generateCSS, getAST, getDeclarationValue, + POSITION_ANCHOR_PROPERTY, type StyleData, } from './utils.js'; import { validatedForPositioning } from './validate.js'; @@ -404,14 +405,6 @@ function getAnchorFunctionData( return {}; } -function getPositionAnchorData(node: csstree.CssNode, rule?: csstree.Raw) { - if (isPositionAnchorDeclaration(node) && rule?.value) { - const name = getDeclarationValue(node); - return { name, selector: rule.value }; - } - return {}; -} - function getPositionFallbackDeclaration( node: csstree.Declaration, rule?: csstree.Raw, @@ -466,7 +459,7 @@ async function getAnchorEl( const anchorAttr = targetEl.getAttribute('anchor'); const positionAnchorProperty = getCSSPropertyValue( targetEl, - '--position-anchor', + POSITION_ANCHOR_PROPERTY, ); if (positionAnchorProperty) { @@ -587,17 +580,6 @@ export async function parseCSS(styleData: StyleData[]) { }, ); - // Parse `position-anchor` data - const { name: positionAnchorName, selector: positionAnchorSelector } = - getPositionAnchorData(node, rule); - if (positionAnchorName && positionAnchorSelector) { - if (anchorNames[positionAnchorName]) { - anchorNames[positionAnchorName].push(positionAnchorSelector); - } else { - anchorNames[positionAnchorName] = [positionAnchorSelector]; - } - } - // Parse `anchor()` function const { prop, diff --git a/src/polyfill.ts b/src/polyfill.ts index efe1f62..f942e75 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -416,8 +416,8 @@ export async function polyfill(animationFrame?: boolean) { let styleData = await fetchCSS(); // pre parse CSS styles that we need to cascade - const { changedStyles } = await cascadeCSS(styleData); - if (Object.values(changedStyles).length) { + const cascadeCausedChanges = await cascadeCSS(styleData); + if (cascadeCausedChanges) { styleData = await transformCSS(styleData); } // parse CSS diff --git a/src/utils.ts b/src/utils.ts index 29527c4..01f8c42 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import * as csstree from 'css-tree'; - +import { nanoid } from 'nanoid/non-secure'; export interface DeclarationWithValue extends csstree.Declaration { value: csstree.Value; } @@ -30,3 +30,5 @@ export interface StyleData { url?: URL; changed?: boolean; } + +export const POSITION_ANCHOR_PROPERTY = `--position-anchor-${nanoid(12)}`; diff --git a/tests/unit/cascade.test.ts b/tests/unit/cascade.test.ts index 96c2574..679092d 100644 --- a/tests/unit/cascade.test.ts +++ b/tests/unit/cascade.test.ts @@ -1,16 +1,17 @@ import { cascadeCSS } from '../../src/cascade.js'; -import { type StyleData } from '../../src/utils.js'; +import { POSITION_ANCHOR_PROPERTY, type StyleData } from '../../src/utils.js'; import { getSampleCSS } from './../helpers.js'; describe('cascadeCSS', () => { it('moves position-anchor to custom property', async () => { const srcCSS = getSampleCSS('position-anchor'); - const { changedStyles } = await cascadeCSS([ - { css: srcCSS }, - ] as StyleData[]); - expect(changedStyles[0].changed).toBe(true); - const { css } = changedStyles[0]; - expect(css).toContain('--position-anchor:--my-position-anchor-b'); - expect(css).toContain('--position-anchor:--my-position-anchor-a'); + const styleData: StyleData[] = [ + { css: srcCSS, el: document.createElement('div') }, + ]; + const cascadeCausedChanges = await cascadeCSS(styleData); + expect(cascadeCausedChanges).toBe(true); + const { css } = styleData[0]; + expect(css).toContain(`${POSITION_ANCHOR_PROPERTY}:--my-position-anchor-b`); + expect(css).toContain(`${POSITION_ANCHOR_PROPERTY}:--my-position-anchor-a`); }); }); diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index a37b0dd..9f8ed79 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -1,5 +1,5 @@ import { type AnchorPositions, parseCSS } from '../../src/parse.js'; -import { type StyleData } from '../../src/utils.js'; +import { POSITION_ANCHOR_PROPERTY, type StyleData } from '../../src/utils.js'; import { getSampleCSS, sampleBaseCSS } from './../helpers.js'; describe('parseCSS', () => { @@ -180,7 +180,7 @@ describe('parseCSS', () => { .my-targets { position: absolute; position-anchor: --my-anchor; - --position-anchor: --my-anchor; + ${POSITION_ANCHOR_PROPERTY}: --my-anchor; } #my-anchor { anchor-name: --my-anchor; @@ -231,14 +231,14 @@ describe('parseCSS', () => { const css = ` #my-target-1 { top: anchor(bottom); - --position-anchor: --my-anchor; + ${POSITION_ANCHOR_PROPERTY}: --my-anchor; position-anchor: --my-anchor; position: absolute; } #my-target-2 { bottom: anchor(top); position-anchor: --my-anchor; - --position-anchor: --my-anchor; + ${POSITION_ANCHOR_PROPERTY}: --my-anchor; position: absolute; } #my-anchor { diff --git a/tests/unit/setup.ts b/tests/unit/setup.ts index 393bced..addd06c 100644 --- a/tests/unit/setup.ts +++ b/tests/unit/setup.ts @@ -11,3 +11,6 @@ beforeAll(() => { afterEach(() => { fetchMock.reset(); }); +vi.mock('nanoid/non-secure', () => { + return { nanoid: () => '1234' }; +}); From e205a2221d28f9a8b416596c9ceec310237ac8fe Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Mon, 24 Jun 2024 16:39:16 -0400 Subject: [PATCH 17/17] review --- src/utils.ts | 1 + tests/unit/setup.ts | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 01f8c42..39053b4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import * as csstree from 'css-tree'; import { nanoid } from 'nanoid/non-secure'; + export interface DeclarationWithValue extends csstree.Declaration { value: csstree.Value; } diff --git a/tests/unit/setup.ts b/tests/unit/setup.ts index addd06c..393bced 100644 --- a/tests/unit/setup.ts +++ b/tests/unit/setup.ts @@ -11,6 +11,3 @@ beforeAll(() => { afterEach(() => { fetchMock.reset(); }); -vi.mock('nanoid/non-secure', () => { - return { nanoid: () => '1234' }; -});