Skip to content

Add closest, classList, and dataset to the DOM polyfill - #658

Open
airhorns wants to merge 10 commits into
Shopify:mainfrom
airhorns:dataset-classlist-closest-v2
Open

airhorns wants to merge 10 commits into
Shopify:mainfrom
airhorns:dataset-classlist-closest-v2

Conversation

@airhorns

@airhorns airhorns commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What changed

Supersedes #619, carrying @developit's six commits unchanged (authorship intact) and rebasing them onto current main (post-#620/#625/#653). His PR description covers the core design: closest(), classList, and dataset composed from the polyfill's existing selector, class-name, and attribute primitives, with no new or widened hooks.

On top of that, four commits fix issues found while running this branch under a production embedder (each with the failure it prevents):

  1. dataset keys now enumerate — the proxy gained has/ownKeys/getOwnPropertyDescriptor traps, so Object.keys(el.dataset), spread, in, and for...in see the data attributes instead of always coming back empty.
  2. Inherited Object.prototype members stay visible — the traps only claim actual data-* names, so `${el.dataset}`, .toString, and hasOwnProperty work instead of throwing on string coercion.
  3. Names that don't round-trip camel-casing are hiddendata-fooBar is excluded from ownKeys per the HTML spec's dataset name rules, which also prevents data-fooBar + data-foo-bar from producing duplicate keys and a permanent proxy-invariant TypeError.
  4. matches()/closest() route through the recursive selector matcher — descendant combinators (article span) now check all ancestors instead of only the immediate parent, and the dead flat matcher is deleted.
  5. Object.defineProperty(el.dataset, …) routes through the named setter — a data descriptor writes the attribute (WebIDL [[DefineOwnProperty]] semantics) and accessor descriptors are rejected, instead of the default trap defining a non-configurable property on the proxy target and poisoning every later enumeration.
  6. Object.preventExtensions/freeze/seal throw like a real DOMStringMap — the proxy refuses [[PreventExtensions]] as WebIDL requires of legacy platform objects, instead of letting the target become non-extensible and turning every later enumeration into a proxy-invariant TypeError.

Tests

The original coverage plus 8 new tests: dataset enumeration as own properties, inherited-member visibility, defineProperty routing and accessor rejection, preventExtensions refusal, non-round-tripping name hiding, and matches/closest descendant-combinator traversal.

Validated with:

  • pnpm exec vitest run (263 tests)
  • pnpm type-check
  • pnpm lint
  • pnpm --filter @remote-dom/polyfill build

🤖 Generated with Claude Code

developit and others added 10 commits September 1, 2026 22:24
Implement closest, classList, and dataset by composing the existing selector, class-name, and attribute primitives. Keep remote synchronization on the unchanged hooks and cover traversal and mutation behavior.
Preserve remote-dom's flat element model instead of changing createElement and the HTML prototype hierarchy. Dataset remains a direct wrapper around data attribute operations.
Store dataset in a symbol-backed field so repeated access returns the same live object. Use a compact lazy initializer while preserving camel-case attribute normalization.
Cache a proxied DOMTokenList and route numeric properties to live tokens while rejecting indexed writes. Keep non-index properties and methods on the token-list object.
Place one proxy behind DOMTokenList.prototype so numeric lookups read live tokens and numeric assignments are ignored without allocating a proxy per element.
Store the owner and live tokens through the existing OWNER_ELEMENT and VALUE symbols. This follows the polyfill's private-state convention and avoids string-named implementation properties.
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.

2 participants