From c2a3f25371005499b470b4aaf37aa13be6ca8d40 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Tue, 8 Oct 2024 15:52:03 -0700 Subject: [PATCH 01/28] allow polyfilling a single style element --- src/polyfill.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/polyfill.ts b/src/polyfill.ts index abb0835..05b4678 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -26,6 +26,7 @@ import { type SizingProperty, } from './syntax.js'; import { transformCSS } from './transform.js'; +import type { StyleData } from './utils.js'; const platformWithCache = { ...platform, _c: new Map() }; @@ -443,13 +444,14 @@ async function position(rules: AnchorPositions, useAnimationFrame = false) { } } -export async function polyfill(animationFrame?: boolean) { +export async function polyfill(animationFrame?: boolean, el?: HTMLStyleElement) { const useAnimationFrame = animationFrame === undefined ? Boolean(window.UPDATE_ANCHOR_ON_ANIMATION_FRAME) : animationFrame; + // fetch CSS from stylesheet and inline style - let styleData = await fetchCSS(); + let styleData = el ? [{el, css: el.textContent ?? ''}] : await fetchCSS(); // pre parse CSS styles that we need to cascade const cascadeCausedChanges = await cascadeCSS(styleData); From 8eeb7a86d3e5fe18d977357e6f2c1ccfb9d5f617 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Tue, 8 Oct 2024 15:53:43 -0700 Subject: [PATCH 02/28] remove unnecessary import --- src/polyfill.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/polyfill.ts b/src/polyfill.ts index 05b4678..0ef60ad 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -26,7 +26,6 @@ import { type SizingProperty, } from './syntax.js'; import { transformCSS } from './transform.js'; -import type { StyleData } from './utils.js'; const platformWithCache = { ...platform, _c: new Map() }; From fe5389c0e22ba188e033b34d44e06cfff3542c41 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Wed, 9 Oct 2024 11:30:26 -0700 Subject: [PATCH 03/28] make polyfill options more flexible and allow a list of target elements --- src/fetch.ts | 24 ++++++++++++++++-------- src/polyfill.ts | 29 ++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/fetch.ts b/src/fetch.ts index 7d414e8..ca39cbc 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -31,13 +31,19 @@ async function fetchLinkedStylesheets( ); } +const ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY = '[style*="anchor"]'; // Searches for all elements with inline style attributes that include `anchor`. // For each element found, adds a new 'data-has-inline-styles' attribute with a // random UUID value, and then formats the styles in the same manner as CSS from // style tags. -function fetchInlineStyles() { - const elementsWithInlineAnchorStyles: NodeListOf = - document.querySelectorAll('[style*="anchor"]'); +function fetchInlineStyles(elements: HTMLElement[] = []) { + const elementsWithInlineAnchorStyles: + | NodeListOf + | HTMLElement[] = elements.length + ? elements.filter((el) => + el.matches(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY), + ) + : document.querySelectorAll(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY); const inlineStyles: Partial[] = []; elementsWithInlineAnchorStyles.forEach((el) => { @@ -52,12 +58,14 @@ function fetchInlineStyles() { return inlineStyles; } -export async function fetchCSS(): Promise { - const elements: NodeListOf = - document.querySelectorAll('link, style'); +export async function fetchCSS( + elements: HTMLElement[] = [], +): Promise { + const targetElements: NodeListOf | HTMLElement[] = + elements.length ? elements : document.querySelectorAll('link, style'); const sources: Partial[] = []; - elements.forEach((el) => { + targetElements.forEach((el) => { if (el.tagName.toLowerCase() === 'link') { const url = getStylesheetUrl(el as HTMLLinkElement); if (url) { @@ -69,7 +77,7 @@ export async function fetchCSS(): Promise { } }); - const inlines = fetchInlineStyles(); + const inlines = fetchInlineStyles(elements); return await fetchLinkedStylesheets([...sources, ...inlines]); } diff --git a/src/polyfill.ts b/src/polyfill.ts index 0ef60ad..a1476ef 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -443,14 +443,33 @@ async function position(rules: AnchorPositions, useAnimationFrame = false) { } } -export async function polyfill(animationFrame?: boolean, el?: HTMLStyleElement) { +export interface PolyfillOptions { + useAnimationFrame?: boolean; + elements?: HTMLElement[]; +} + +function getPolyfillOptions( + useAnimationFrameOrOption: boolean | PolyfillOptions = {}, +) { + const options = + typeof useAnimationFrameOrOption === 'boolean' + ? { useAnimationFrame: useAnimationFrameOrOption } + : useAnimationFrameOrOption; const useAnimationFrame = - animationFrame === undefined + options.useAnimationFrame === undefined ? Boolean(window.UPDATE_ANCHOR_ON_ANIMATION_FRAME) - : animationFrame; + : options.useAnimationFrame; + + return Object.assign(options, { useAnimationFrame }); +} + +export async function polyfill( + useAnimationFrameOrOption: boolean | PolyfillOptions = {}, +) { + const options = getPolyfillOptions(useAnimationFrameOrOption); // fetch CSS from stylesheet and inline style - let styleData = el ? [{el, css: el.textContent ?? ''}] : await fetchCSS(); + let styleData = await fetchCSS(options.elements); // pre parse CSS styles that we need to cascade const cascadeCausedChanges = await cascadeCSS(styleData); @@ -465,7 +484,7 @@ export async function polyfill(animationFrame?: boolean, el?: HTMLStyleElement) await transformCSS(styleData, inlineStyles, true); // calculate position values - await position(rules, useAnimationFrame); + await position(rules, options.useAnimationFrame); } return rules; From a58268f6f73c31de33136d27e85930cd66ba7f1a Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Wed, 9 Oct 2024 11:51:56 -0700 Subject: [PATCH 04/28] simplify element list type --- src/fetch.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/fetch.ts b/src/fetch.ts index ca39cbc..e3f0640 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -37,13 +37,13 @@ const ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY = '[style*="anchor"]'; // random UUID value, and then formats the styles in the same manner as CSS from // style tags. function fetchInlineStyles(elements: HTMLElement[] = []) { - const elementsWithInlineAnchorStyles: - | NodeListOf - | HTMLElement[] = elements.length + const elementsWithInlineAnchorStyles: HTMLElement[] = elements.length ? elements.filter((el) => el.matches(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY), ) - : document.querySelectorAll(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY); + : Array.from( + document.querySelectorAll(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY), + ); const inlineStyles: Partial[] = []; elementsWithInlineAnchorStyles.forEach((el) => { @@ -61,8 +61,9 @@ function fetchInlineStyles(elements: HTMLElement[] = []) { export async function fetchCSS( elements: HTMLElement[] = [], ): Promise { - const targetElements: NodeListOf | HTMLElement[] = - elements.length ? elements : document.querySelectorAll('link, style'); + const targetElements: HTMLElement[] = elements.length + ? elements + : Array.from(document.querySelectorAll('link, style')); const sources: Partial[] = []; targetElements.forEach((el) => { From c0ceb709bb4604156e03b3ae13103de8c0936fc7 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Wed, 9 Oct 2024 14:53:05 -0700 Subject: [PATCH 05/28] add demo for imperative polyfill --- index.html | 162 +++++++++++++++++++++++++++++++++++ public/anchor-imperative.css | 5 ++ 2 files changed, 167 insertions(+) create mode 100644 public/anchor-imperative.css diff --git a/index.html b/index.html index ae31978..7ba8694 100644 --- a/index.html +++ b/index.html @@ -45,6 +45,11 @@ bottom: anchor(--my-anchor-style-tag start); right: anchor(--my-anchor-style-tag left); } + + #my-anchor-imperative { + inline-size: fit-content; + margin-inline: auto; + } @@ -1001,6 +1089,80 @@

top: anchor(--my-anchor-media-query top); right: anchor(--my-anchor-media-query right); } + +
+

+ + Imperatively apply polyfill +

+ +
+
Anchor
+
+ Target 1 (with <style>) +
+ +
+ Target 3 (with inline style) +
+
+

+ With polyfill applied: Target 1, 2, and 3 are positioned at Anchor’s + top-left, top-right, and bottom-right corners respectively. +

+
<style id="my-style-imperative-anchor">
+  #my-anchor-imperative {
+    anchor-name: --my-anchor-imperative;
+  }
+</style>
+<style id="my-style-imperative-style-el">
+  #my-target-imperative-style-el {
+    position: absolute;
+    bottom: anchor(--my-anchor-imperative top);
+    right: anchor(--my-anchor-imperative left);
+  }
+</style>
+<link rel="stylesheet" href="/anchor-imperative.css" id="my-style-imperative-link-el">
+<!--
+CSS inside the anchor-imperative.css file:
+
+#my-target-imperative-link-el {
+  position: absolute;
+  bottom: anchor(--my-anchor-imperative top);
+  left: anchor(--my-anchor-imperative right);
+}
+-->
+
+<div id="my-anchor-imperative" class="anchor">...</div>
+<div id="my-target-imperative-style-el" class="target">...</div>
+<div id="my-target-imperative-link-el" class="target">...</div>
+<div id="my-target-imperative-inline-style" class="target"
+  style="position: absolute;
+    top: anchor(--my-anchor-imperative bottom);
+    left: anchor(--my-anchor-imperative right);"
+>...</div>
+
+<script>
+  polyfill({
+    useAnimationFrame: true,
+    elements: [
+      // The <style> element for anchor
+      document.getElementById('my-style-imperative-anchor'),
+      // The <style> element
+      document.getElementById('my-style-imperative-style-el'),
+      // The <link> element
+      document.getElementById('my-style-imperative-link-el'),
+      // The target element with inline styles
+      document.getElementById('my-target-imperative-inline-style'),
+    ],
+  });
+</script>