// @ts-check import { WebComponent, html } from 'web-component-base' /** * Composition: a component within a component within a component. * * The three classes below nest three levels deep, all rendering to light DOM. * The point of interest is what happens to the inner subtrees when an *outer* * component re-renders for a reason unrelated to them. * * A parent's vnode never describes what a nested component rendered for * itself — `` is empty in the row's template even * though the counter fills itself with a button and a count. The in-place * reconciler therefore treats a nested custom element as opaque: it patches the * element's props (so data still flows down) but leaves the element's own * children alone. Reconciling them would trim away the child's rendered content * on every ancestor re-render and leave it blank until it next re-rendered on * its own. */ /** Level 3 (leaf): owns its own count entirely — no prop feeds it. */ class TickCounter extends WebComponent { static props = { label: 'tick' } #count = 0 get template() { return html` ` } #bump() { this.#count++ this.render() } } customElements.define('tick-counter', TickCounter) /** Level 2 (middle): a labelled row that hosts a leaf counter. */ class CounterRow extends WebComponent { static props = { name: 'row' } get template() { return html`
${this.props.name}
` } } customElements.define('counter-row', CounterRow) /** Level 1 (root): a board that re-renders on an unrelated "rename". */ class CounterBoard extends WebComponent { static props = { title: 'Board A' } get template() { return html`

${this.props.title}

` } #renamed = 0 #rename() { this.props.title = `Board ${String.fromCharCode(66 + (++this.#renamed % 25))}` } } customElements.define('counter-board', CounterBoard)