Skip to content

Commit c9edaae

Browse files
authored
Merge pull request #256 from marchbox/add-to-polyfill
Enable manual polyfill
2 parents 2c69cc0 + b4915fa commit c9edaae

File tree

8 files changed

+562
-31
lines changed

8 files changed

+562
-31
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ lib-cov
2424
coverage
2525
*.lcov
2626
playwright-report
27+
test-results
2728

2829
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
2930
.grunt

index.html

+198-1
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,20 @@
4747
bottom: anchor(--my-anchor-style-tag start);
4848
right: anchor(--my-anchor-style-tag left);
4949
}
50+
51+
#anchor-manual .anchor {
52+
inline-size: fit-content;
53+
margin: 3rem auto;
54+
}
5055
</style>
5156
<script type="module">
5257
import polyfill from '/src/index-fn.ts';
5358

59+
const SUPPORTS_ANCHOR_POSITIONING = CSS.supports('anchor-name: --a');
60+
5461
const btn = document.getElementById('apply-polyfill');
5562

56-
if (!('anchorName' in document.documentElement.style)) {
63+
if (!SUPPORTS_ANCHOR_POSITIONING) {
5764
btn.addEventListener('click', () =>
5865
polyfill().then((rules) => {
5966
btn.innerText = 'Polyfill Applied';
@@ -80,6 +87,116 @@
8087
updateAnchor.removeAttribute('data-large');
8188
}
8289
});
90+
91+
function prepareManualPolyfill() {
92+
// anchor style element
93+
const anchorStyleEl = document.createElement('style');
94+
anchorStyleEl.id = 'my-style-manual-anchor';
95+
anchorStyleEl.textContent = [
96+
'#my-anchor-manual {',
97+
'anchor-name: --my-anchor-manual;',
98+
'}',
99+
].join('');
100+
101+
// style element
102+
const styleEl = document.createElement('style');
103+
styleEl.id = 'my-style-manual-style-el';
104+
styleEl.textContent = [
105+
'#my-target-manual-style-el {',
106+
'position: absolute;',
107+
'bottom: anchor(--my-anchor-manual top);',
108+
'right: anchor(--my-anchor-manual left);',
109+
'}',
110+
].join('');
111+
112+
// link element
113+
const linkEl = document.createElement('link');
114+
linkEl.id = 'my-style-manual-link-el';
115+
linkEl.rel = 'stylesheet';
116+
linkEl.href = '/anchor-manual.css';
117+
118+
document.head.append(anchorStyleEl, styleEl, linkEl);
119+
120+
// inline style
121+
document
122+
.getElementById('my-target-manual-inline-style')
123+
?.setAttribute(
124+
'style',
125+
[
126+
'position: absolute',
127+
'top: anchor(--my-anchor-manual bottom)',
128+
'left: anchor(--my-anchor-manual right)',
129+
].join(';'),
130+
);
131+
}
132+
133+
// These event listeners are for E2E testing only
134+
document
135+
.getElementById('prepare-manual-polyfill')
136+
?.addEventListener('click', () => prepareManualPolyfill(), {
137+
once: true,
138+
});
139+
document
140+
.getElementById('apply-polyfill-manually-set1')
141+
?.addEventListener('click', () => {
142+
polyfill({
143+
elements: [
144+
document.getElementById('my-style-manual-anchor'),
145+
document.getElementById('my-style-manual-style-el'),
146+
],
147+
excludeInlineStyles: true,
148+
});
149+
});
150+
document
151+
.getElementById('apply-polyfill-manually-set2')
152+
?.addEventListener('click', () => {
153+
polyfill({
154+
elements: [
155+
document.getElementById('my-style-manual-anchor'),
156+
document.getElementById('my-style-manual-link-el'),
157+
document.getElementById('my-target-manual-inline-style'),
158+
],
159+
excludeInlineStyles: true,
160+
});
161+
});
162+
document
163+
.getElementById('apply-polyfill-manually-set3')
164+
?.addEventListener('click', () => {
165+
polyfill({
166+
elements: [
167+
document.getElementById('my-style-manual-anchor'),
168+
document.getElementById('my-style-manual-style-el'),
169+
],
170+
});
171+
});
172+
173+
const manualBtn = document.getElementById('apply-polyfill-manually');
174+
if (SUPPORTS_ANCHOR_POSITIONING) {
175+
manualBtn.innerText = 'Load Anchor Positioning CSS';
176+
}
177+
manualBtn.addEventListener('click', () => {
178+
prepareManualPolyfill();
179+
180+
if (!SUPPORTS_ANCHOR_POSITIONING) {
181+
polyfill({
182+
elements: [
183+
document.getElementById('my-style-manual-anchor'),
184+
document.getElementById('my-style-manual-link-el'),
185+
document.getElementById('my-style-manual-style-el'),
186+
document.getElementById('my-target-manual-inline-style'),
187+
],
188+
}).then((rules) => {
189+
manualBtn.innerText = 'Polyfill Applied';
190+
console.log(rules);
191+
});
192+
} else {
193+
manualBtn.innerText = 'Anchor Positioning CSS applied';
194+
console.log(
195+
'anchor-positioning is supported in this browser; polyfill skipped.',
196+
);
197+
}
198+
manualBtn.setAttribute('disabled', '');
199+
});
83200
</script>
84201
<script src="https://unpkg.com/[email protected]/components/prism-core.min.js"></script>
85202
<script src="https://unpkg.com/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
@@ -1003,6 +1120,86 @@ <h2>
10031120
top: anchor(--my-anchor-media-query top);
10041121
right: anchor(--my-anchor-media-query right);
10051122
}</code></pre>
1123+
</section>
1124+
<section id="anchor-manual" class="demo-item" style="position: relative">
1125+
<h2>
1126+
<a href="#manual" aria-hidden="true">🔗</a>
1127+
Manually apply polyfill to specific styles
1128+
</h2>
1129+
<button id="apply-polyfill-manually">Polyfill these elements</button>
1130+
<div id="anchor-manual-test-buttons" hidden>
1131+
<!-- These buttons are for E2E testing only -->
1132+
<button id="prepare-manual-polyfill">Prepare</button>
1133+
<button id="apply-polyfill-manually-set1">Polyfill target 1</button>
1134+
<button id="apply-polyfill-manually-set2">
1135+
Polyfill target 2 and 3
1136+
</button>
1137+
<button id="apply-polyfill-manually-set3">
1138+
Polyfill target 1 and 3
1139+
</button>
1140+
</div>
1141+
<div class="demo-elements">
1142+
<div id="my-anchor-manual" class="anchor">Anchor</div>
1143+
<div id="my-target-manual-style-el" class="target">
1144+
Target 1 (with <code>&lt;style&gt;</code>)
1145+
</div>
1146+
<div id="my-target-manual-link-el" class="target">
1147+
Target 2 (with <code>&lt;link&gt;</code>)
1148+
</div>
1149+
<div id="my-target-manual-inline-style" class="target">
1150+
Target 3 (with inline style)
1151+
</div>
1152+
</div>
1153+
<p class="note">
1154+
With polyfill applied: Target 1, 2, and 3 are positioned at Anchor’s
1155+
top-left, top-right, and bottom-right corners respectively.
1156+
</p>
1157+
<pre><code class="language-html" data-dependencies="css,js">&lt;style id="my-style-manual-anchor"&gt;
1158+
#my-anchor-manual {
1159+
anchor-name: --my-anchor-manual;
1160+
}
1161+
&lt;/style&gt;
1162+
&lt;style id="my-style-manual-style-el"&gt;
1163+
#my-target-manual-style-el {
1164+
position: absolute;
1165+
bottom: anchor(--my-anchor-manual top);
1166+
right: anchor(--my-anchor-manual left);
1167+
}
1168+
&lt;/style&gt;
1169+
&lt;link rel="stylesheet" href="/anchor-manual.css" id="my-style-manual-link-el" /&gt;
1170+
&lt;!--
1171+
CSS inside the anchor-manual.css file:
1172+
1173+
#my-target-manual-link-el {
1174+
position: absolute;
1175+
bottom: anchor(--my-anchor-manual top);
1176+
left: anchor(--my-anchor-manual right);
1177+
}
1178+
--&gt;
1179+
1180+
&lt;div id="my-anchor-manual" class="anchor"&gt;...&lt;/div&gt;
1181+
&lt;div id="my-target-manual-style-el" class="target"&gt;...&lt;/div&gt;
1182+
&lt;div id="my-target-manual-link-el" class="target"&gt;...&lt;/div&gt;
1183+
&lt;div id="my-target-manual-inline-style" class="target"
1184+
style="position: absolute;
1185+
top: anchor(--my-anchor-manual bottom);
1186+
left: anchor(--my-anchor-manual right);"
1187+
&gt;...&lt;/div&gt;
1188+
1189+
&lt;script&gt;
1190+
polyfill({
1191+
elements: [
1192+
// The &lt;style&gt; element for anchor
1193+
document.getElementById('my-style-manual-anchor'),
1194+
// The &lt;style&gt; element
1195+
document.getElementById('my-style-manual-style-el'),
1196+
// The &lt;link&gt; element
1197+
document.getElementById('my-style-manual-link-el'),
1198+
// The target element with inline styles
1199+
document.getElementById('my-target-manual-inline-style'),
1200+
],
1201+
});
1202+
&lt;/script&gt;</code></pre>
10061203
</section>
10071204
<section id="sponsor">
10081205
<h2>Sponsor OddBird's OSS Work</h2>

public/anchor-manual.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#my-target-manual-link-el {
2+
position: absolute;
3+
bottom: anchor(--my-anchor-manual top);
4+
left: anchor(--my-anchor-manual right);
5+
}

src/@types/global.d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
export {};
22

33
declare global {
4+
interface AnchorPositioningPolyfillOptions {
5+
// Whether to use `requestAnimationFrame()` when updating target elements’
6+
// positions
7+
useAnimationFrame?: boolean;
8+
9+
// An array of explicitly targeted elements to polyfill
10+
elements?: HTMLElement[];
11+
12+
// Whether to exclude elements with eligible inline styles. When not defined
13+
// or set to `false`, the polyfill will be applied to all elements that have
14+
// eligible inline styles, regardless of whether the `elements` option is
15+
// defined. When set to `true`, elements with eligible inline styles listed
16+
// in the `elements` option will still be polyfilled, but no other elements
17+
// in the document will be implicitly polyfilled.
18+
excludeInlineStyles?: boolean;
19+
}
20+
421
interface Window {
522
UPDATE_ANCHOR_ON_ANIMATION_FRAME?: boolean;
23+
ANCHOR_POSITIONING_POLYFILL_OPTIONS?: AnchorPositioningPolyfillOptions;
624
CHECK_LAYOUT_DELAY?: boolean;
725
}
826
}

src/fetch.ts

+42-25
Original file line numberDiff line numberDiff line change
@@ -51,45 +51,62 @@ async function fetchLinkedStylesheets(
5151
return results.filter((loaded) => loaded !== null);
5252
}
5353

54+
const ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY = '[style*="anchor"]';
5455
// Searches for all elements with inline style attributes that include `anchor`.
5556
// For each element found, adds a new 'data-has-inline-styles' attribute with a
5657
// random UUID value, and then formats the styles in the same manner as CSS from
5758
// style tags.
58-
function fetchInlineStyles() {
59-
const elementsWithInlineAnchorStyles: NodeListOf<HTMLElement> =
60-
document.querySelectorAll('[style*="anchor"]');
59+
function fetchInlineStyles(elements?: HTMLElement[]) {
60+
const elementsWithInlineAnchorStyles: HTMLElement[] = elements
61+
? elements.filter(
62+
(el) =>
63+
el instanceof HTMLElement &&
64+
el.matches(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY),
65+
)
66+
: Array.from(
67+
document.querySelectorAll(ELEMENTS_WITH_INLINE_ANCHOR_STYLES_QUERY),
68+
);
6169
const inlineStyles: Partial<StyleData>[] = [];
6270

63-
elementsWithInlineAnchorStyles.forEach((el) => {
64-
const selector = nanoid(12);
65-
const dataAttribute = 'data-has-inline-styles';
66-
el.setAttribute(dataAttribute, selector);
67-
const styles = el.getAttribute('style');
68-
const css = `[${dataAttribute}="${selector}"] { ${styles} }`;
69-
inlineStyles.push({ el, css });
70-
});
71+
elementsWithInlineAnchorStyles
72+
.filter((el) => el instanceof HTMLElement)
73+
.forEach((el) => {
74+
const selector = nanoid(12);
75+
const dataAttribute = 'data-has-inline-styles';
76+
el.setAttribute(dataAttribute, selector);
77+
const styles = el.getAttribute('style');
78+
const css = `[${dataAttribute}="${selector}"] { ${styles} }`;
79+
inlineStyles.push({ el, css });
80+
});
7181

7282
return inlineStyles;
7383
}
7484

75-
export async function fetchCSS(): Promise<StyleData[]> {
76-
const elements: NodeListOf<HTMLElement> =
77-
document.querySelectorAll('link, style');
85+
export async function fetchCSS(
86+
elements?: HTMLElement[],
87+
excludeInlineStyles?: boolean,
88+
): Promise<StyleData[]> {
89+
const targetElements: HTMLElement[] =
90+
elements ?? Array.from(document.querySelectorAll('link, style'));
7891
const sources: Partial<StyleData>[] = [];
7992

80-
elements.forEach((el) => {
81-
if (el.tagName.toLowerCase() === 'link') {
82-
const url = getStylesheetUrl(el as HTMLLinkElement);
83-
if (url) {
84-
sources.push({ el, url });
93+
targetElements
94+
.filter((el) => el instanceof HTMLElement)
95+
.forEach((el) => {
96+
if (el.tagName.toLowerCase() === 'link') {
97+
const url = getStylesheetUrl(el as HTMLLinkElement);
98+
if (url) {
99+
sources.push({ el, url });
100+
}
85101
}
86-
}
87-
if (el.tagName.toLowerCase() === 'style') {
88-
sources.push({ el, css: el.innerHTML });
89-
}
90-
});
102+
if (el.tagName.toLowerCase() === 'style') {
103+
sources.push({ el, css: el.innerHTML });
104+
}
105+
});
106+
107+
const elementsForInlines = excludeInlineStyles ? (elements ?? []) : undefined;
91108

92-
const inlines = fetchInlineStyles();
109+
const inlines = fetchInlineStyles(elementsForInlines);
93110

94111
return await fetchLinkedStylesheets([...sources, ...inlines]);
95112
}

0 commit comments

Comments
 (0)