Skip to content

fix: support position-anchor on a custom-element host#415

Open
jpzwarte wants to merge 5 commits into
oddbird:mainfrom
jpzwarte:fix/408-position-anchor-on-host-element
Open

fix: support position-anchor on a custom-element host#415
jpzwarte wants to merge 5 commits into
oddbird:mainfrom
jpzwarte:fix/408-position-anchor-on-host-element

Conversation

@jpzwarte

@jpzwarte jpzwarte commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Make position-anchor work when set on a custom-element host whose anchor() functions live in the shadow root's :host rule — including multiple such hosts on a page, and hosts that share a single constructed stylesheet.

Positioning a shadow host

  • Resolve :host / :host(...) target rules to the shadow host element, which querySelectorAll cannot return (hostMatchesSelector).
  • Extend the anchor search to the target's own tree when a shadow host sets position-anchor in its own inline style, so the anchor in the outer (light DOM) tree is found — without leaking shadow-scoped names across the boundary.

Concurrent, per-host polyfill runs

Each shadow root that adopts an anchor stylesheet schedules its own polyfill() run (shadow.ts). Those runs are async and were interleaving over shared state, so with two <position-anchor-on-host> hosts only the last one positioned correctly. The runs are now safe to overlap — with each other and with a user-initiated polyfill() — without any queue or coordinator:

  • Reentrant anchor resolutionparseCSS snapshots the anchorNames/anchorScopes registries before its first await and passes them to getAnchorEl, so a concurrent run's resetStores() can no longer wipe the registries a run is resolving against mid-flight.
  • Stable data-has-inline-styles ids — reused instead of re-minted each run (and no longer stripped on cleanup), so a concurrent run's anchor selector can't go stale.
  • Non-clobbering inline writes — when rewriting an element's inline style, any --anchor-* mappings another run added are preserved (re-read at write time).

Shared constructed stylesheets

A constructed stylesheet adopted by multiple shadow roots ("construct once, adopt everywhere") is no longer mutated in place — each adopting root receives a polyfill-owned copy with its own rewritten custom properties, so all hosts resolve, not just the last.

Demo & tests

The <position-anchor-on-host> demo now uses one shared stylesheet across two linked hosts, and the e2e test asserts that every host is positioned above its own anchor — not just the last. Verified across many real-browser (Firefox) runs.

Closes #408

Make `position-anchor` work when set on a custom-element host whose
`anchor()` functions live in the shadow root's `:host` rule.

This introduces CSS tree-scoping into the polyfill:
- Tag every `StyleData`/`Selector` with the tree (document or shadow
  root) it was authored in, threaded through fetch, transform and
  `getSelectors`.
- Resolve `:host` / `:host(...)` target rules to the shadow host, which
  `querySelectorAll` cannot return (`hostMatchesSelector`).
- Extend the anchor search to the target's own tree when a shadow host
  sets `position-anchor` in its own inline style, so the anchor in the
  outer (light DOM) tree is found — without leaking shadow-scoped names
  across the boundary.

Adds the `<position-anchor-on-host>` demo and an e2e test covering it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-polyfill ready!

Name Link
🔨 Latest commit 71dc8b1
🔍 Latest deploy log https://app.netlify.com/projects/anchor-polyfill/deploys/6a3410bc7e3ddf0008327fa0
😎 Deploy Preview https://deploy-preview-415--anchor-polyfill.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-position-wpt canceled.

Name Link
🔨 Latest commit 39c5e58
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a33a96d203c3a0008cd0f05

@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for anchor-position-wpt canceled.

Name Link
🔨 Latest commit 71dc8b1
🔍 Latest deploy log https://app.netlify.com/projects/anchor-position-wpt/deploys/6a3410bcde4f210008e3ad3a

Comment thread src/dom.ts
const match = /^:host\(\s*(.+?)\s*\)$/.exec(trimmed);
if (match) {
try {
return host.matches(match[1]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted this line to the original one; What's in between :host(...) can be passed to matches() just fine afaik. I don't see a need to parse the CSS here (i tested the .blue, .red manually against matches())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] position-anchor pointing from the :host of a custom element to another element is broken

2 participants