Skip to content
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ value of `window.ANCHOR_POSITIONING_POLYFILL_OPTIONS`.
window.ANCHOR_POSITIONING_POLYFILL_OPTIONS = {
elements: undefined,
excludeInlineStyles: false,
roots: [document],
useAnimationFrame: false,
};
import("https://unpkg.com/@oddbird/css-anchor-positioning");
Expand All @@ -89,6 +90,7 @@ an argument.
polyfill({
elements: undefined,
excludeInlineStyles: false,
roots: [document],
useAnimationFrame: false,
});
}
Expand All @@ -115,6 +117,15 @@ defined. When set to `true`, elements with eligible inline styles listed in the
`elements` option will still be polyfilled, but no other elements in the
document will be implicitly polyfilled.

### roots

type: `(Document | HTMLElement)[]`, default: `[document]`

By default the polyfill applies to `document`, but you can configure one or more
shadow roots the polyfill should apply to using this option. See the
[shadow DOM examples](https://anchor-positioning.oddbird.net/shadow-dom) to learn
more.

### useAnimationFrame

type: `boolean`, default: `false`
Expand Down Expand Up @@ -150,7 +161,7 @@ following features:
`align-items` properties
- `position-visibility` property
- dynamically added/removed anchors or targets
- anchors or targets in the shadow-dom
- anchors and targets in separate shadow roots (see https://github.com/oddbird/css-anchor-positioning/issues/191)
- anchors or targets in constructed stylesheets
(https://github.com/oddbird/css-anchor-positioning/issues/228)
- anchor functions assigned to `inset-*` properties or `inset` shorthand
Expand Down
10 changes: 10 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,16 @@ <h2>
color: var(--brand-orange);
}</code></pre>
</section>
<section id="shadow-dom">
<h2>
<a href="#shadow-com" aria-hidden="true">🔗</a>
Positioning within the shadow DOM
</h2>
<p class="note">
The polyfill can position targets and anchors inside a shadow root. See
<a href="shadow-dom.html">shadow DOM examples</a>.
</p>
</section>
<section id="sponsor">
<h2>Sponsor OddBird’s OSS Work</h2>
<p>
Expand Down
9 changes: 9 additions & 0 deletions public/anchor-shadow-root.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#shadow-anchor-positioning {
anchor-name: --shadow-anchor-positioning;
}

#shadow-target-positioning {
position: absolute;
right: anchor(--shadow-anchor-positioning right, 50px);
top: anchor(--shadow-anchor-positioning bottom);
}
214 changes: 214 additions & 0 deletions shadow-dom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CSS Anchor Positioning Polyfill</title>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/themes/prism.css"
/>
<script
src="https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js"
crossorigin="anonymous"
defer
></script>
<!-- TypeKit Fonts -->
<script src="https://use.typekit.net/slx1xnq.js"></script>
<script>
try {
Typekit.load({ async: true });
} catch (e) {}
</script>
<link rel="stylesheet" href="/demo.css" />
<style>
anchor-web-component {
width: 100%;
}
</style>
<script type="module">
import polyfill from '/src/index-fn.ts';

const SUPPORTS_ANCHOR_POSITIONING =
'anchorName' in document.documentElement.style;

const btn = document.getElementById('apply-polyfill');

if (!SUPPORTS_ANCHOR_POSITIONING) {
btn.addEventListener('click', () => {
document
.querySelector('anchor-web-component')
.applyPolyfill()
.then(() => {
btn.innerText = 'Polyfill Applied';
btn.setAttribute('disabled', '');
});
});
} else {
btn.innerText = 'No Polyfill Needed';
btn.setAttribute('disabled', '');
console.log(
'anchor-positioning is supported in this browser; polyfill skipped.',
);
}

class AnchorWebComponent extends HTMLElement {
/* This would typically be run in connectedCallback() */
applyPolyfill() {
return polyfill({ roots: [this.shadowRoot] });
}
}
customElements.define('anchor-web-component', AnchorWebComponent);
</script>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<script src="https://unpkg.com/[email protected]/components/prism-core.min.js"></script>
<script src="https://unpkg.com/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
<script
defer
data-domain="anchor-positioning.oddbird.net"
src="https://plausible.io/js/script.hash.outbound-links.js"
></script>
</head>
<body>
<header>
<h1><a href="/">CSS Anchor Positioning Polyfill</a></h1>
<nav>
<span> See also: </span>
<a
href="https://github.com/web-platform-tests/wpt/tree/master/css/css-anchor-position"
target="_blank"
rel="noopener noreferrer"
>WPT examples</a
>
<!-- <a
href="https://anchor-position-wpt.netlify.app/"
target="_blank"
rel="noopener noreferrer"
>WPT results</a
> -->
<a
href="https://drafts.csswg.org/css-anchor-position/"
target="_blank"
rel="noopener noreferrer"
>Draft Spec</a
>
</nav>
<button id="apply-polyfill" data-button="apply-polyfill" type="button">
Apply Polyfill
</button>
</header>
<section>
<h2>Anchoring Elements in the shadow DOM</h2>
<p>
<strong>Note: </strong>We strive to keep the polyfill up-to-date with
ongoing changes to the spec, and we welcome
<a
href="https://github.com/oddbird/css-anchor-positioning"
target="_blank"
rel="noopener noreferrer"
>code contributions</a
>
and <a href="#sponsor">financial support</a> to make that happen.
</p>
</section>
<section id="shadow-root" class="demo-item">
<h2>
<a href="#shadow-root" aria-hidden="true">🔗</a>
Works if anchor and target are both inside the same shadow root
</h2>
<div class="demo-elements">
<anchor-web-component>
<template shadowrootmode="open">
<link rel="stylesheet" href="/demo.css" />
<link rel="stylesheet" href="/anchor-shadow-root.css" />
<div style="position: relative">
<div id="shadow-target-positioning" class="target">Target</div>
<div id="shadow-anchor-positioning" class="anchor">Anchor</div>
</div>
</template>
</anchor-web-component>
</div>
<div class="note">
<p>
With polyfill applied: Target and Anchor’s right edges line up.
Target’s top edge lines up with the bottom edge of the Anchor.
</p>
<p>
<strong>Note: </strong> this will not work across shadow root
boundaries, and will not work with constructed stylesheets.
</p>
</div>

<pre><code class="language-html"
>&lt;anchor-web-component&gt;
&lt;template shadowrootmode="open"&gt;
&lt;style&gt;
#my-anchor-positioning {
anchor-name: --my-anchor-positioning;
}

#my-target-positioning {
position: absolute;
top: anchor(--my-anchor-positioning bottom);
right: anchor(--my-anchor-positioning right, 50px);
}
&lt;/style&gt;
&lt;div style="position: relative"&gt;
&lt;div id="my-target-positioning"&gt;Target&lt;/div&gt;
&lt;div id="my-anchor-positioning"&gt;Anchor&lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;/anchor-web-component&gt;
&lt;script&gt;
class AnchorDemo extends HTMLElement {
connectedCallback() {
window.ANCHOR_POSITIONING_POLYFILL({ roots: [this.shadowRoot] });
}
}
customElements.define("anchor-web-component", AnchorDemo);
&lt;/script&gt;
</code></pre>
</section>
<section id="sponsor">
<h2>Sponsor OddBird’s OSS Work</h2>
<p>
At OddBird, we love contributing to the languages & tools developers
rely on. We’re currently working on polyfills for new Popover & Anchor
Positioning functionality, as well as CSS specifications for functions,
mixins, and responsive typography. Help us keep this work sustainable
and centered on your needs as a developer! We display sponsor logos and
avatars on our
<a
href="https://www.oddbird.net/polyfill/#open-source-sponsors"
target="_blank"
rel="noopener noreferrer"
>website</a
>.
</p>
<a
href="https://github.com/sponsors/oddbird"
target="_blank"
rel="noopener noreferrer"
>Sponsor OddBird’s OSS Work</a
>
</section>
<footer>
<p>
Spec proposal by
<a
href="http://xanthir.com/contact/"
target="_blank"
rel="noopener noreferrer"
>Tab Atkins-Bittner</a
>. Polyfill and demo by
<a
href="https://www.oddbird.net/"
target="_blank"
rel="noopener noreferrer"
>OddBird</a
>.
</p>
</footer>
</body>
</html>
21 changes: 16 additions & 5 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { platform, type VirtualElement } from '@floating-ui/dom';
import { nanoid } from 'nanoid/non-secure';

import { SHIFTED_PROPERTIES } from './cascade.js';
import { type AnchorPositioningRoot } from './polyfill.js';

/**
* Representation of a CSS selector that allows getting the element part and
Expand Down Expand Up @@ -140,18 +141,19 @@ function getContainerScrollPosition(element: HTMLElement) {
* Like `document.querySelectorAll`, but if the selector has a pseudo-element it
* will return a wrapper for the rest of the polyfill to use.
*/
export function getElementsBySelector(selector: Selector) {
export function getElementsBySelector(
selector: Selector,
options: { roots: AnchorPositioningRoot[] },
) {
const { elementPart, pseudoElementPart } = selector;
const result: (HTMLElement | PseudoElement)[] = [];
const isBefore = pseudoElementPart === '::before';
const isAfter = pseudoElementPart === '::after';

// Current we only support `::before` and `::after` pseudo-elements.
// Currently we only support `::before` and `::after` pseudo-elements.
if (pseudoElementPart && !(isBefore || isAfter)) return result;

const elements = Array.from(
document.querySelectorAll<HTMLElement>(elementPart),
);
const elements = querySelectorAllRoots(options.roots, elementPart);

if (!pseudoElementPart) {
result.push(...elements);
Expand Down Expand Up @@ -255,3 +257,12 @@ export const getOffsetParent = async (el: HTMLElement) => {
}
return offsetParent as HTMLElement;
};

export const querySelectorAllRoots = (
roots: AnchorPositioningRoot[],
selector: string,
): HTMLElement[] => {
return roots.flatMap(
(e) => [...e.querySelectorAll(selector)] as HTMLElement[],
);
};
11 changes: 7 additions & 4 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { nanoid } from 'nanoid/non-secure';

import { querySelectorAllRoots } from './dom.js';
import { type NormalizedAnchorPositioningPolyfillOptions } from './polyfill.js';
import { type StyleData } from './utils.js';

const INVALID_MIME_TYPE_ERROR = 'InvalidMimeType';
Expand All @@ -25,7 +27,7 @@
if (!data.url) {
return data as StyleData;
}
// TODO: Add MutationObserver to watch for disabled links being enabled

Check warning on line 30 in src/fetch.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO: Add MutationObserver to watch for...'
// https://github.com/oddbird/css-anchor-positioning/issues/246
if ((data.el as HTMLLinkElement | undefined)?.disabled) {
// Do not fetch or parse disabled stylesheets
Expand Down Expand Up @@ -96,11 +98,10 @@
}

export async function fetchCSS(
elements?: HTMLElement[],
excludeInlineStyles?: boolean,
options: NormalizedAnchorPositioningPolyfillOptions,
): Promise<StyleData[]> {
const targetElements: HTMLElement[] =
elements ?? Array.from(document.querySelectorAll('link, style'));
options.elements ?? querySelectorAllRoots(options.roots, 'link, style');
const sources: Partial<StyleData>[] = [];

targetElements
Expand All @@ -117,7 +118,9 @@
}
});

const elementsForInlines = excludeInlineStyles ? (elements ?? []) : undefined;
const elementsForInlines = options.excludeInlineStyles
? (options.elements ?? [])
: undefined;

const inlines = fetchInlineStyles(elementsForInlines);

Expand Down
Loading
Loading