Shadow DOM Spec — Slot-Assignment, Event Retargeting, Composited DOM Access #100

Closed
opened 2026-06-19 12:09:49 +00:00 by Artur · 0 comments
Owner

Issue #100 — Shadow DOM Spec — Slot-Assignment, Event Retargeting, Composited DOM Access

Problembeschreibung

Shadow DOM existiert als Basis-Implementierung (src/custom-elements/shim.ts + Issue #13), aber ist nicht spec-konform:

  • attachShadow({ mode: 'open' }) existiert als stub
  • Shadow Root hat keine korrekte Slot-Zuweisung (Slot-Assignment-Algorithmus fehlt)
  • slotchange-Event fehlt
  • Event Retargeting (Events aus Shadow DOM werden zum Host umgeleitet) fehlt
  • composed Events (die den Shadow Boundary durchdringen) fehlen
  • CustomElementRegistry.define() existiert, aber ohne Lifecycle-Callbacks
  • Kein Declarative Shadow DOM (<template shadowrootmode="open">)

Warum das blockiert:

  • Amazon.de: Nutzt Shadow DOM für Product-Cards + AUI Components
  • Web Components: Jede Lit/Polymer/Stencil-Komponente braucht korrektes Shadow DOM
  • Modern Frameworks: Qwik, Svelte, Angular nutzen Shadow DOM für Style-Isolation
  • YouTube: Nutzt Shadow DOM für Custom Video Controls

Architektur-Analyse

Aktueller Stand (src/custom-elements/shim.ts)

class CustomElementRegistry {
    define(name, constructor, options) {
        // Registriert Custom Element
        // installAttachShadow auf HTMLElement.prototype
    }
}

installAttachShadow(htmlProto) {
    // attachShadow mode:'open' → erzeugt ShadowRoot
    // attachShadow mode:'closed' → erzeugt ShadowRoot (sollte undefined sein)
}

Spec-konformer Shadow DOM

Element.attachShadow(init) → ShadowRoot
  - mode: 'open' | 'closed'
  - delegatesFocus: boolean
  - slotAssignment: 'manual' | 'named'

ShadowRoot:
  - mode: 'open' | 'closed'
  - host: Element
  - innerHTML: string
  - adoptedStyleSheets: StyleSheet[]
  - getSelection(), elementFromPoint(), ...

<slot name="...">:
  - assignedNodes(options?)
  - assignedElements(options?)
  - name
  - slotchange Event

Event Retargeting:
  - Event.path retargeted zum Host
  - composed: true Events durchdringen Boundary

Lösungsansätze

Option A — Full Shadow DOM Spec (Empfohlen)

Eigene ShadowDOM-Implementierung nach Spec:

src/shadow-dom/
├── shadow-root.ts          (ShadowRoot-Klasse + Implementation)
├── slot-assignment.ts      (Slot-Assignment-Algorithmus: named → fallback)
├── slot-change.ts           (slotchange-Event-Dispatch)
├── event-retargeting.ts    (Event-Retargeting + composed Path)
└── declarative-shadow.ts   (<template shadowrootmode> Parser)

Slot-Assignment Algorithmus (nach Spec):

1. Für jedes <slot> im Shadow Tree:
   a. Finde assigned nodes aus Light DOM nach name
   b. fallback content = Kinder des <slot> wenn nichts assigned
   
2. Bei DOM-Änderungen:
   a. Neu-Zuweisung aller Slots
   b. slotchange-Event für geänderte Slots

Event Retargeting:

class ShadowEventRetargeter {
    adjustTarget(event, shadowRoot) {
        // Event.target = Host (oder nächstgelegener Slot wenn in Slot)
        // Event.composedPath() = [target, ..., host, ..., window]
        // Für composed:false Events → stop bei Shadow Boundary
    }
}

Vorteile:

  • 100% nach DOM Spec
  • Web Components funktionieren (Lit, Stencil)
  • Style-Isolation korrekt
  • Event-Flow spec-konform (Capturing → Target → Bubbling auch durch Shadow Boundaries)

Nachteile:

  • ~1.500 Zeilen Code
  • Integration mit Happy DOMs Element-Klassen (muss deren Prototype patchen)

Option B — Happy DOM Shadow DOM verbessern

Happy DOM hat Basis-Shadow-DOM. Forken/Patchen:

  • Slot-Assignment hinzufügen
  • Event-Retargeting hinzufügen
  • slotchange-Event

Vorteile: Happy DOM hat die Basis-Infrastruktur (Event-Dispatch, DOM-Traversal)
Nachteile: Tiefe Happy DOM Interna (PropertySymbols) müssen verstanden werden

Entscheidung

Option A — Eigene Implementation. Happy DOMs Shadow DOM ist zu rudimentär. Die Event-Retargeting-Logik muss auf unserer EventDispatcher-Infrastruktur aufbauen, nicht auf Happy DOMs.

Akzeptanzkriterien

  • attachShadow({ mode: 'open' }) → ShadowRoot (zugreifbar via element.shadowRoot)
  • attachShadow({ mode: 'closed' }) → ShadowRoot (element.shadowRoot = null)
  • ShadowRoot.host → das Host-Element
  • <slot> assignedNodes() retourniert korrekte Light-DOM-Kinder
  • <slot name="..."> filtert nach slot-Attribut
  • Fallback-Content im Slot wird angezeigt wenn nichts assigned
  • slotchange-Event bei Slot-Zuweisungs-Änderungen
  • Event aus Shadow DOM → Event.target = Host
  • composed: true Events durchdringen Shadow Boundary
  • event.composedPath() retourniert korrekten Pfad inkl. Host
  • Style-Scoping: CSS innerhalb Shadow Root leaked nicht nach außen
  • adoptedStyleSheets auf ShadowRoot
  • Declarative Shadow DOM: <template shadowrootmode="open">

Betroffene Dateien

Datei Änderung Status
src/custom-elements/shim.ts Erweitern Ändern
src/shadow-dom/shadow-root.ts Neu Neu
src/shadow-dom/slot-assignment.ts Neu Neu
src/shadow-dom/slot-change.ts Neu Neu
src/shadow-dom/event-retargeting.ts Neu Neu
src/shadow-dom/declarative-shadow.ts Neu Neu
src/interaction/dispatch.ts Event-Retargeting Integration Ändern
src/css/style-engine.ts Shadow-Style-Scoping Ändern
tests/unit/shadow-dom.test.ts Neu Neu
tests/integration/web-components.test.ts Neu Neu

Testplan

Unit (30+)

  • open vs closed mode
  • Slot-Zuweisung (named/unamed/fallback)
  • slotchange-Event
  • Event-Retargeting (diverse Bubbling-Szenarien)
  • composed vs non-composed Events
  • composedPath()
  • adoptedStyleSheets
  • Mehrere Shadow Roots

Integration (15+)

  • Lit-Element: Hello World
  • Amazon AUI Card Component
  • YouTube-ähnliche Video Controls
  • Form-Associated Custom Elements
  • Style-Scoping (Border leak Test)

E2E (3)

  • Lit SPA mit Shadow DOM Routing
  • Stencil Component Library
  • Amazon.de Product Card
# Issue #100 — Shadow DOM Spec — Slot-Assignment, Event Retargeting, Composited DOM Access ## Problembeschreibung Shadow DOM existiert als Basis-Implementierung (`src/custom-elements/shim.ts` + Issue #13), aber ist **nicht spec-konform**: - `attachShadow({ mode: 'open' })` existiert als stub - Shadow Root hat keine korrekte Slot-Zuweisung (Slot-Assignment-Algorithmus fehlt) - `slotchange`-Event fehlt - Event Retargeting (Events aus Shadow DOM werden zum Host umgeleitet) fehlt - `composed` Events (die den Shadow Boundary durchdringen) fehlen - `CustomElementRegistry.define()` existiert, aber ohne Lifecycle-Callbacks - Kein Declarative Shadow DOM (`<template shadowrootmode="open">`) **Warum das blockiert:** - **Amazon.de**: Nutzt Shadow DOM für Product-Cards + AUI Components - **Web Components**: Jede Lit/Polymer/Stencil-Komponente braucht korrektes Shadow DOM - **Modern Frameworks**: Qwik, Svelte, Angular nutzen Shadow DOM für Style-Isolation - **YouTube**: Nutzt Shadow DOM für Custom Video Controls ## Architektur-Analyse ### Aktueller Stand (`src/custom-elements/shim.ts`) ```ts class CustomElementRegistry { define(name, constructor, options) { // Registriert Custom Element // installAttachShadow auf HTMLElement.prototype } } installAttachShadow(htmlProto) { // attachShadow mode:'open' → erzeugt ShadowRoot // attachShadow mode:'closed' → erzeugt ShadowRoot (sollte undefined sein) } ``` ### Spec-konformer Shadow DOM ``` Element.attachShadow(init) → ShadowRoot - mode: 'open' | 'closed' - delegatesFocus: boolean - slotAssignment: 'manual' | 'named' ShadowRoot: - mode: 'open' | 'closed' - host: Element - innerHTML: string - adoptedStyleSheets: StyleSheet[] - getSelection(), elementFromPoint(), ... <slot name="...">: - assignedNodes(options?) - assignedElements(options?) - name - slotchange Event Event Retargeting: - Event.path retargeted zum Host - composed: true Events durchdringen Boundary ``` ## Lösungsansätze ### Option A — Full Shadow DOM Spec (Empfohlen) Eigene ShadowDOM-Implementierung nach Spec: ``` src/shadow-dom/ ├── shadow-root.ts (ShadowRoot-Klasse + Implementation) ├── slot-assignment.ts (Slot-Assignment-Algorithmus: named → fallback) ├── slot-change.ts (slotchange-Event-Dispatch) ├── event-retargeting.ts (Event-Retargeting + composed Path) └── declarative-shadow.ts (<template shadowrootmode> Parser) ``` **Slot-Assignment Algorithmus (nach Spec):** ``` 1. Für jedes <slot> im Shadow Tree: a. Finde assigned nodes aus Light DOM nach name b. fallback content = Kinder des <slot> wenn nichts assigned 2. Bei DOM-Änderungen: a. Neu-Zuweisung aller Slots b. slotchange-Event für geänderte Slots ``` **Event Retargeting:** ```ts class ShadowEventRetargeter { adjustTarget(event, shadowRoot) { // Event.target = Host (oder nächstgelegener Slot wenn in Slot) // Event.composedPath() = [target, ..., host, ..., window] // Für composed:false Events → stop bei Shadow Boundary } } ``` **Vorteile:** - 100% nach DOM Spec - Web Components funktionieren (Lit, Stencil) - Style-Isolation korrekt - Event-Flow spec-konform (Capturing → Target → Bubbling auch durch Shadow Boundaries) **Nachteile:** - ~1.500 Zeilen Code - Integration mit Happy DOMs Element-Klassen (muss deren Prototype patchen) ### Option B — Happy DOM Shadow DOM verbessern Happy DOM hat Basis-Shadow-DOM. Forken/Patchen: - Slot-Assignment hinzufügen - Event-Retargeting hinzufügen - slotchange-Event **Vorteile:** Happy DOM hat die Basis-Infrastruktur (Event-Dispatch, DOM-Traversal) **Nachteile:** Tiefe Happy DOM Interna (PropertySymbols) müssen verstanden werden ## Entscheidung **Option A** — Eigene Implementation. Happy DOMs Shadow DOM ist zu rudimentär. Die Event-Retargeting-Logik muss auf unserer EventDispatcher-Infrastruktur aufbauen, nicht auf Happy DOMs. ## Akzeptanzkriterien - [ ] `attachShadow({ mode: 'open' })` → ShadowRoot (zugreifbar via element.shadowRoot) - [ ] `attachShadow({ mode: 'closed' })` → ShadowRoot (element.shadowRoot = null) - [ ] ShadowRoot.host → das Host-Element - [ ] `<slot>` assignedNodes() retourniert korrekte Light-DOM-Kinder - [ ] `<slot name="...">` filtert nach slot-Attribut - [ ] Fallback-Content im Slot wird angezeigt wenn nichts assigned - [ ] `slotchange`-Event bei Slot-Zuweisungs-Änderungen - [ ] Event aus Shadow DOM → Event.target = Host - [ ] `composed: true` Events durchdringen Shadow Boundary - [ ] `event.composedPath()` retourniert korrekten Pfad inkl. Host - [ ] Style-Scoping: CSS innerhalb Shadow Root leaked nicht nach außen - [ ] `adoptedStyleSheets` auf ShadowRoot - [ ] Declarative Shadow DOM: `<template shadowrootmode="open">` ## Betroffene Dateien | Datei | Änderung | Status | |-------|----------|--------| | `src/custom-elements/shim.ts` | Erweitern | Ändern | | `src/shadow-dom/shadow-root.ts` | Neu | Neu | | `src/shadow-dom/slot-assignment.ts` | Neu | Neu | | `src/shadow-dom/slot-change.ts` | Neu | Neu | | `src/shadow-dom/event-retargeting.ts` | Neu | Neu | | `src/shadow-dom/declarative-shadow.ts` | Neu | Neu | | `src/interaction/dispatch.ts` | Event-Retargeting Integration | Ändern | | `src/css/style-engine.ts` | Shadow-Style-Scoping | Ändern | | `tests/unit/shadow-dom.test.ts` | Neu | Neu | | `tests/integration/web-components.test.ts` | Neu | Neu | ## Testplan ### Unit (30+) - open vs closed mode - Slot-Zuweisung (named/unamed/fallback) - slotchange-Event - Event-Retargeting (diverse Bubbling-Szenarien) - composed vs non-composed Events - composedPath() - adoptedStyleSheets - Mehrere Shadow Roots ### Integration (15+) - Lit-Element: Hello World - Amazon AUI Card Component - YouTube-ähnliche Video Controls - Form-Associated Custom Elements - Style-Scoping (Border leak Test) ### E2E (3) - Lit SPA mit Shadow DOM Routing - Stencil Component Library - Amazon.de Product Card
Artur closed this issue 2026-06-19 14:16:10 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
glow-all/true-headless-browser#100
No description provided.