Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM ubuntu:22.04
50 changes: 50 additions & 0 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# .devcontainer

This container is not strictly necessary for regular development. It was added while debugging an issue with the browser tests that happened on GitHub Actions. Use it if you need it.

[This getting-started guide](https://code.visualstudio.com/docs/devcontainers/tutorial) (you can stop after installing the extension) takes you through using a dev container for the first time.

Once you've installed the extension you may see a popup asking you if you want to reopen the current directory in a devcontainer. Click yes.

If you missed it, look for the icon in the bottom left of the status bar that looks like kind of like `>` and `<` next to each other and click that.

Choose Reopen in container from the menu.

## First-time setup

The first time will be a bit slow as you download the base image.

Open a terminal in VS Code once it reopens the project in a new devcontainer-powered window.
The dev container is running Ubuntu as the root user.

To install additional tools via `apt`, first run `apt update`.

```sh
apt update
```

Next, install `curl`.

```sh
apt install curl
```

Now follow the instructions on Nodejs.org for [installing Node on Linux using `nvm`](https://nodejs.org/en/download).

You'll need some browsers and dependencies for Playwright:

```sh
npx playwright install --with-deps
```

Finally, install dependencies:

```sh
npm clean-install
```

Optional: confirm tests pass inside the container.

```sh
NODE_OPTIONS=--no-experimental-strip-types npm run test:ci
```
7 changes: 7 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "Ubuntu 22.04",
"build": {
"dockerfile": "Dockerfile"
},
"forwardPorts": [3000]
}
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[],
);
};
Loading
Loading