@context-action/lit-ui 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,26 @@
1
1
  # Change Log
2
2
 
3
+ ## [0.1.1] (2026-10-23)
4
+
5
+ ### Fixed
6
+
7
+ - `hydrateLit(host.shadowRoot, host)` now finds `lit-part` markers anywhere
8
+ under the shadow root instead of assuming they are direct siblings of the
9
+ first node. Declarative shadow roots with `static styles` are adopted instead
10
+ of appending a second client render.
11
+ - If a client render already appended a duplicate tree before `hydrateLit` runs,
12
+ the server root part is isolated, the client tree is removed, and the server
13
+ tree is re-adopted. Final item counts and server node identity are preserved.
14
+
15
+ ### Tests
16
+
17
+ - Added static-styles + three-item DSD hydration regression coverage in jsdom
18
+ and Chromium, including the late `hydrateLit` after first-update scenario.
19
+
3
20
  ## [0.1.0] (2026-10-23)
4
21
 
22
+ Published to npm on 2026-10-23. `latest` and `next` resolve to `0.1.0`.
23
+
5
24
  ### Added
6
25
 
7
26
  - Initial public release of the Lit UI primitives and integrations.
package/README.md CHANGED
@@ -80,7 +80,14 @@ pnpm --filter @context-action/lit-ui test:browser # Chromium DSD hydration contr
80
80
 
81
81
  ## Status
82
82
 
83
- Public package, initial version `0.1.0`. Requires Node.js `>=24.11.0`.
83
+ Public package. `0.1.1` is the current patch release candidate for the
84
+ static-styles hydration fix; `0.1.0` remains the last published version until
85
+ the candidate/promotion workflows complete.
86
+
87
+ - https://www.npmjs.com/package/@context-action/lit-ui
88
+ - Release plan: [`releases/LIT_PUBLIC_0.1.1.md`](../../releases/LIT_PUBLIC_0.1.1.md)
89
+
90
+ Requires Node.js `>=24.11.0`.
84
91
 
85
92
  ## Architecture
86
93
 
package/dist/hydrate.cjs CHANGED
@@ -17,7 +17,16 @@ let _lit_labs_ssr_client = require("@lit-labs/ssr-client");
17
17
  const COMMENT_NODE = 8;
18
18
  const DOCUMENT_FRAGMENT_NODE = 11;
19
19
  function hasLitPartMarker(root) {
20
- for (let node = root.firstChild; node; node = node.nextSibling) if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
20
+ const document = root.ownerDocument ?? root.host?.ownerDocument ?? null;
21
+ if (document && typeof document.createTreeWalker === "function") {
22
+ const walker = document.createTreeWalker(root, 128);
23
+ while (walker.nextNode()) if (walker.currentNode.data.startsWith("lit-part")) return true;
24
+ return false;
25
+ }
26
+ for (let node = root.firstChild; node; node = node.nextSibling) {
27
+ if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
28
+ if (node.nodeType === 1 && hasLitPartMarker(node)) return true;
29
+ }
21
30
  return false;
22
31
  }
23
32
  function findServerHost(root, element) {
@@ -37,6 +46,67 @@ function isHydrationSupportInstalled() {
37
46
  return false;
38
47
  }
39
48
  }
49
+ function partStartNode(part) {
50
+ return part?._$startNode ?? part?._$AA ?? null;
51
+ }
52
+ function isServerRootPart(part) {
53
+ const start = partStartNode(part);
54
+ return start?.nodeType === COMMENT_NODE && start.data.startsWith("lit-part");
55
+ }
56
+ /**
57
+ * When a client render appended a separate tree beside the server DSD tree,
58
+ * keep only the server root part (plus preceding static style tags), remove the
59
+ * client anchors/children, and re-adopt the surviving server tree.
60
+ */
61
+ function isolateServerTree(shadow) {
62
+ const children = Array.from(shadow.childNodes);
63
+ const rootIndex = children.findIndex((node) => node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part"));
64
+ if (rootIndex < 0) return false;
65
+ let beginIndex = rootIndex;
66
+ while (beginIndex > 0) {
67
+ const previous = children[beginIndex - 1];
68
+ if (previous.nodeType === 1 && previous.localName === "style") {
69
+ beginIndex -= 1;
70
+ continue;
71
+ }
72
+ break;
73
+ }
74
+ let depth = 0;
75
+ let endIndex = -1;
76
+ for (let index = rootIndex; index < children.length; index += 1) {
77
+ const node = children[index];
78
+ if (node.nodeType !== COMMENT_NODE) continue;
79
+ const data = node.data;
80
+ if (data.startsWith("lit-part")) depth += 1;
81
+ else if (data.startsWith("/lit-part")) {
82
+ depth -= 1;
83
+ if (depth === 0) {
84
+ endIndex = index;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ if (endIndex < 0) return false;
90
+ for (let index = children.length - 1; index > endIndex; index -= 1) children[index].parentNode?.removeChild(children[index]);
91
+ for (let index = 0; index < beginIndex; index += 1) children[index].parentNode?.removeChild(children[index]);
92
+ return true;
93
+ }
94
+ function adoptServerShadow(host, shadow) {
95
+ host._$needsHydration = false;
96
+ host._$AG = false;
97
+ if (isolateServerTree(shadow)) {
98
+ delete shadow._$litPart$;
99
+ try {
100
+ const rendered = host.render();
101
+ (0, _lit_labs_ssr_client.hydrate)(rendered, shadow);
102
+ host.requestUpdate();
103
+ return;
104
+ } catch {}
105
+ }
106
+ shadow.replaceChildren();
107
+ delete shadow._$litPart$;
108
+ host.requestUpdate();
109
+ }
40
110
  function hydrateHost(host) {
41
111
  const shadow = host.shadowRoot;
42
112
  if (!shadow) {
@@ -48,7 +118,12 @@ function hydrateHost(host) {
48
118
  configurable: true,
49
119
  value: () => shadow
50
120
  });
51
- if (shadow._$litPart$ !== void 0) {
121
+ const livePart = shadow._$litPart$;
122
+ if (livePart !== void 0) {
123
+ if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
124
+ adoptServerShadow(host, shadow);
125
+ return;
126
+ }
52
127
  host.requestUpdate();
53
128
  return;
54
129
  }
@@ -107,7 +182,8 @@ function hydrateLit(root, element) {
107
182
  }
108
183
  if (root.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in root && root.host === element) {
109
184
  const shadow = root;
110
- if (shadow._$litPart$ === void 0) {
185
+ const livePart = shadow._$litPart$;
186
+ if (livePart === void 0) {
111
187
  Object.defineProperty(element, "createRenderRoot", {
112
188
  configurable: true,
113
189
  value: () => shadow
@@ -116,6 +192,9 @@ function hydrateLit(root, element) {
116
192
  const rendered = element.render();
117
193
  (0, _lit_labs_ssr_client.hydrate)(rendered, shadow);
118
194
  } else shadow.replaceChildren();
195
+ } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
196
+ adoptServerShadow(element, shadow);
197
+ return;
119
198
  }
120
199
  element._$needsHydration = false;
121
200
  element._$AG = false;
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.cjs","names":["LitElement"],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hydrate.cjs","names":["LitElement"],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n const document = root.ownerDocument\n ?? (root as ShadowRoot).host?.ownerDocument\n ?? null;\n if (document && typeof document.createTreeWalker === 'function') {\n const walker = document.createTreeWalker(root, 128 /* NodeFilter.SHOW_COMMENT */);\n while (walker.nextNode()) {\n if ((walker.currentNode as Comment).data.startsWith('lit-part')) {\n return true;\n }\n }\n return false;\n }\n\n // Defensive fallback for environments without TreeWalker.\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n if (node.nodeType === 1 && hasLitPartMarker(node as Element)) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\ninterface LitPartLike {\n _$startNode?: Node | null;\n _$endNode?: Node | null;\n _$AA?: Node | null;\n _$AB?: Node | null;\n}\n\nfunction partStartNode(part: LitPartLike | undefined): Node | null {\n return part?._$startNode ?? part?._$AA ?? null;\n}\n\nfunction partEndNode(part: LitPartLike | undefined): Node | null {\n return part?._$endNode ?? part?._$AB ?? null;\n}\n\nfunction isServerRootPart(part: LitPartLike | undefined): boolean {\n const start = partStartNode(part);\n return (\n start?.nodeType === COMMENT_NODE &&\n (start as Comment).data.startsWith('lit-part')\n );\n}\n\n/**\n * When a client render appended a separate tree beside the server DSD tree,\n * keep only the server root part (plus preceding static style tags), remove the\n * client anchors/children, and re-adopt the surviving server tree.\n */\nfunction isolateServerTree(shadow: ShadowRoot): boolean {\n const children = Array.from(shadow.childNodes);\n const rootIndex = children.findIndex(\n node => node.nodeType === COMMENT_NODE && (node as Comment).data.startsWith('lit-part'),\n );\n if (rootIndex < 0) return false;\n\n let beginIndex = rootIndex;\n while (beginIndex > 0) {\n const previous = children[beginIndex - 1]!;\n if (previous.nodeType === 1 && (previous as Element).localName === 'style') {\n beginIndex -= 1;\n continue;\n }\n break;\n }\n\n let depth = 0;\n let endIndex = -1;\n for (let index = rootIndex; index < children.length; index += 1) {\n const node = children[index]!;\n if (node.nodeType !== COMMENT_NODE) continue;\n const data = (node as Comment).data;\n if (data.startsWith('lit-part')) {\n depth += 1;\n } else if (data.startsWith('/lit-part')) {\n depth -= 1;\n if (depth === 0) {\n endIndex = index;\n break;\n }\n }\n }\n if (endIndex < 0) return false;\n\n for (let index = children.length - 1; index > endIndex; index -= 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n for (let index = 0; index < beginIndex; index += 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n return true;\n}\n\nfunction adoptServerShadow(host: LitElement, shadow: ShadowRoot): void {\n // Clear Lit hydration support flags so the following requestUpdate does not\n // adopt the same tree a second time.\n (host as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (host as unknown as { _$AG?: boolean })._$AG = false;\n\n if (isolateServerTree(shadow)) {\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n try {\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n return;\n } catch {\n // Fall through to a clean client render when the server tree cannot be\n // adopted (for example after an incompatible template change).\n }\n }\n\n shadow.replaceChildren();\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n host.requestUpdate();\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart !== undefined) {\n if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(host, shadow);\n return;\n }\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(element, shadow);\n return;\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.d.cts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAiKgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
1
+ {"version":3,"file":"hydrate.d.cts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAsRgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.d.ts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAiKgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
1
+ {"version":3,"file":"hydrate.d.ts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAsRgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
package/dist/hydrate.js CHANGED
@@ -5,7 +5,16 @@ import { hydrate } from "@lit-labs/ssr-client";
5
5
  const COMMENT_NODE = 8;
6
6
  const DOCUMENT_FRAGMENT_NODE = 11;
7
7
  function hasLitPartMarker(root) {
8
- for (let node = root.firstChild; node; node = node.nextSibling) if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
8
+ const document = root.ownerDocument ?? root.host?.ownerDocument ?? null;
9
+ if (document && typeof document.createTreeWalker === "function") {
10
+ const walker = document.createTreeWalker(root, 128);
11
+ while (walker.nextNode()) if (walker.currentNode.data.startsWith("lit-part")) return true;
12
+ return false;
13
+ }
14
+ for (let node = root.firstChild; node; node = node.nextSibling) {
15
+ if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
16
+ if (node.nodeType === 1 && hasLitPartMarker(node)) return true;
17
+ }
9
18
  return false;
10
19
  }
11
20
  function findServerHost(root, element) {
@@ -25,6 +34,67 @@ function isHydrationSupportInstalled() {
25
34
  return false;
26
35
  }
27
36
  }
37
+ function partStartNode(part) {
38
+ return part?._$startNode ?? part?._$AA ?? null;
39
+ }
40
+ function isServerRootPart(part) {
41
+ const start = partStartNode(part);
42
+ return start?.nodeType === COMMENT_NODE && start.data.startsWith("lit-part");
43
+ }
44
+ /**
45
+ * When a client render appended a separate tree beside the server DSD tree,
46
+ * keep only the server root part (plus preceding static style tags), remove the
47
+ * client anchors/children, and re-adopt the surviving server tree.
48
+ */
49
+ function isolateServerTree(shadow) {
50
+ const children = Array.from(shadow.childNodes);
51
+ const rootIndex = children.findIndex((node) => node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part"));
52
+ if (rootIndex < 0) return false;
53
+ let beginIndex = rootIndex;
54
+ while (beginIndex > 0) {
55
+ const previous = children[beginIndex - 1];
56
+ if (previous.nodeType === 1 && previous.localName === "style") {
57
+ beginIndex -= 1;
58
+ continue;
59
+ }
60
+ break;
61
+ }
62
+ let depth = 0;
63
+ let endIndex = -1;
64
+ for (let index = rootIndex; index < children.length; index += 1) {
65
+ const node = children[index];
66
+ if (node.nodeType !== COMMENT_NODE) continue;
67
+ const data = node.data;
68
+ if (data.startsWith("lit-part")) depth += 1;
69
+ else if (data.startsWith("/lit-part")) {
70
+ depth -= 1;
71
+ if (depth === 0) {
72
+ endIndex = index;
73
+ break;
74
+ }
75
+ }
76
+ }
77
+ if (endIndex < 0) return false;
78
+ for (let index = children.length - 1; index > endIndex; index -= 1) children[index].parentNode?.removeChild(children[index]);
79
+ for (let index = 0; index < beginIndex; index += 1) children[index].parentNode?.removeChild(children[index]);
80
+ return true;
81
+ }
82
+ function adoptServerShadow(host, shadow) {
83
+ host._$needsHydration = false;
84
+ host._$AG = false;
85
+ if (isolateServerTree(shadow)) {
86
+ delete shadow._$litPart$;
87
+ try {
88
+ const rendered = host.render();
89
+ hydrate(rendered, shadow);
90
+ host.requestUpdate();
91
+ return;
92
+ } catch {}
93
+ }
94
+ shadow.replaceChildren();
95
+ delete shadow._$litPart$;
96
+ host.requestUpdate();
97
+ }
28
98
  function hydrateHost(host) {
29
99
  const shadow = host.shadowRoot;
30
100
  if (!shadow) {
@@ -36,7 +106,12 @@ function hydrateHost(host) {
36
106
  configurable: true,
37
107
  value: () => shadow
38
108
  });
39
- if (shadow._$litPart$ !== void 0) {
109
+ const livePart = shadow._$litPart$;
110
+ if (livePart !== void 0) {
111
+ if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
112
+ adoptServerShadow(host, shadow);
113
+ return;
114
+ }
40
115
  host.requestUpdate();
41
116
  return;
42
117
  }
@@ -95,7 +170,8 @@ function hydrateLit(root, element) {
95
170
  }
96
171
  if (root.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in root && root.host === element) {
97
172
  const shadow = root;
98
- if (shadow._$litPart$ === void 0) {
173
+ const livePart = shadow._$litPart$;
174
+ if (livePart === void 0) {
99
175
  Object.defineProperty(element, "createRenderRoot", {
100
176
  configurable: true,
101
177
  value: () => shadow
@@ -104,6 +180,9 @@ function hydrateLit(root, element) {
104
180
  const rendered = element.render();
105
181
  hydrate(rendered, shadow);
106
182
  } else shadow.replaceChildren();
183
+ } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
184
+ adoptServerShadow(element, shadow);
185
+ return;
107
186
  }
108
187
  element._$needsHydration = false;
109
188
  element._$AG = false;
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.js","names":[],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hydrate.js","names":[],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n const document = root.ownerDocument\n ?? (root as ShadowRoot).host?.ownerDocument\n ?? null;\n if (document && typeof document.createTreeWalker === 'function') {\n const walker = document.createTreeWalker(root, 128 /* NodeFilter.SHOW_COMMENT */);\n while (walker.nextNode()) {\n if ((walker.currentNode as Comment).data.startsWith('lit-part')) {\n return true;\n }\n }\n return false;\n }\n\n // Defensive fallback for environments without TreeWalker.\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n if (node.nodeType === 1 && hasLitPartMarker(node as Element)) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\ninterface LitPartLike {\n _$startNode?: Node | null;\n _$endNode?: Node | null;\n _$AA?: Node | null;\n _$AB?: Node | null;\n}\n\nfunction partStartNode(part: LitPartLike | undefined): Node | null {\n return part?._$startNode ?? part?._$AA ?? null;\n}\n\nfunction partEndNode(part: LitPartLike | undefined): Node | null {\n return part?._$endNode ?? part?._$AB ?? null;\n}\n\nfunction isServerRootPart(part: LitPartLike | undefined): boolean {\n const start = partStartNode(part);\n return (\n start?.nodeType === COMMENT_NODE &&\n (start as Comment).data.startsWith('lit-part')\n );\n}\n\n/**\n * When a client render appended a separate tree beside the server DSD tree,\n * keep only the server root part (plus preceding static style tags), remove the\n * client anchors/children, and re-adopt the surviving server tree.\n */\nfunction isolateServerTree(shadow: ShadowRoot): boolean {\n const children = Array.from(shadow.childNodes);\n const rootIndex = children.findIndex(\n node => node.nodeType === COMMENT_NODE && (node as Comment).data.startsWith('lit-part'),\n );\n if (rootIndex < 0) return false;\n\n let beginIndex = rootIndex;\n while (beginIndex > 0) {\n const previous = children[beginIndex - 1]!;\n if (previous.nodeType === 1 && (previous as Element).localName === 'style') {\n beginIndex -= 1;\n continue;\n }\n break;\n }\n\n let depth = 0;\n let endIndex = -1;\n for (let index = rootIndex; index < children.length; index += 1) {\n const node = children[index]!;\n if (node.nodeType !== COMMENT_NODE) continue;\n const data = (node as Comment).data;\n if (data.startsWith('lit-part')) {\n depth += 1;\n } else if (data.startsWith('/lit-part')) {\n depth -= 1;\n if (depth === 0) {\n endIndex = index;\n break;\n }\n }\n }\n if (endIndex < 0) return false;\n\n for (let index = children.length - 1; index > endIndex; index -= 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n for (let index = 0; index < beginIndex; index += 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n return true;\n}\n\nfunction adoptServerShadow(host: LitElement, shadow: ShadowRoot): void {\n // Clear Lit hydration support flags so the following requestUpdate does not\n // adopt the same tree a second time.\n (host as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (host as unknown as { _$AG?: boolean })._$AG = false;\n\n if (isolateServerTree(shadow)) {\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n try {\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n return;\n } catch {\n // Fall through to a clean client render when the server tree cannot be\n // adopted (for example after an incompatible template change).\n }\n }\n\n shadow.replaceChildren();\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n host.requestUpdate();\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart !== undefined) {\n if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(host, shadow);\n return;\n }\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(element, shadow);\n return;\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
@@ -84,10 +84,37 @@ hydrateLit(root: Element | ShadowRoot, element: LitElement): void
84
84
  - support가 없으면 수동으로 기존 shadow root를 hydrate합니다.
85
85
  - 기존 `_$litPart$`가 있으면 재hydrate하지 않습니다.
86
86
  3. element 자신의 shadow root를 직접 넘기는 경우
87
- - 기존 server tree를 manual hydrate하고 hydration support flag를 해제해 double hydration을 막습니다.
87
+ - shadow root 전체에서 `lit-part` comment marker를 찾습니다. `static styles`로
88
+ 첫 자식이 `<style>`이어도 그 뒤/하위의 marker를 인식합니다.
89
+ - 기존 server tree를 manual hydrate하고 hydration support flag를 해제해
90
+ double hydration을 막습니다.
91
+ - 첫 client update가 이미 별도 tree를 append한 경우 server root part만
92
+ 분리하고 client tree를 제거한 뒤 server tree를 다시 채택합니다.
88
93
  4. `root`가 비어 있는 경우
89
94
  - element를 mount하고 일반 client render를 수행합니다.
90
95
 
96
+ ### Static styles와 marker 탐색
97
+
98
+ `hasLitPartMarker`는 direct sibling만 보지 않고 `TreeWalker`로 root 아래의
99
+ comment node를 순회합니다. 따라서 다음 markup에서도 hydration 경로를 탑니다.
100
+
101
+ ```html
102
+ <template shadowrootmode="open">
103
+ <style>...</style>
104
+ <!--lit-part ...-->
105
+ <div class="tl-item">...</div>
106
+ <!--/lit-part-->
107
+ </template>
108
+ ```
109
+
110
+ ### 이미 중복된 client tree 복구
111
+
112
+ `hydrateLit` 호출 전에 LitElement의 첫 update가 실행되어 server tree 옆에
113
+ client tree가 append된 경우, `_$litPart$`가 server root part인지 확인합니다.
114
+ 별도 client render라면 server root marker 범위만 남기고 client anchor/children을
115
+ 제거한 뒤 `hydrate()`로 다시 채택합니다. 서버 tree를 분리할 수 없으면 shadow
116
+ root를 비우고 client render로 fallback합니다.
117
+
91
118
  ### Import 순서
92
119
 
93
120
  ```ts
@@ -5,6 +5,26 @@
5
5
  [`releases/LIT_PUBLIC_0.1.0.md`](../../../releases/LIT_PUBLIC_0.1.0.md)와
6
6
  [`releases/lit-public-2026-10.json`](../../../releases/lit-public-2026-10.json)입니다.
7
7
 
8
+ ## 0. 게시 상태
9
+
10
+ 두 패키지는 2026-10-23 npm에 게시되었습니다.
11
+
12
+ | 패키지 | 버전 | latest | next |
13
+ | --- | --- | --- | --- |
14
+ | `@context-action/lit` | `0.1.0` | `0.1.0` | `0.1.0` |
15
+ | `@context-action/lit-ui` | `0.1.0` | `0.1.0` | `0.1.0` |
16
+
17
+ - Candidate: `publish-lit-public-candidate.yml` — 성공
18
+ - Promotion: `promote-lit-public.yml` — 성공
19
+ - Registry consumer smoke — 25 assertions 통과
20
+
21
+ ### 0.1.1 patch candidate
22
+
23
+ `@context-action/lit-ui@0.1.1`은 static styles DSD hydration과 중복 client tree
24
+ 복구 patch입니다. `@context-action/lit@0.1.0`은 재사용하며, candidate/promotion
25
+ workflow는 기존 lit artifact의 attested commit과 새 lit-ui commit을 함께
26
+ 검증합니다. 배포 완료 후 이 절의 상태를 갱신합니다.
27
+
8
28
  ## 1. 패키지 경계
9
29
 
10
30
  ```text
@@ -78,17 +98,15 @@ Candidate는 다음을 fail-closed로 수행합니다.
78
98
  6. `@context-action/lit` → `@context-action/lit-ui` 순서로 `next` 게시
79
99
  7. provenance, published consumer smoke, registry evidence
80
100
 
81
- ### 첫 배포 bootstrap
82
-
83
- 두 패키지는 아직 npm에 없으므로 첫 배포는 반드시:
101
+ ### 첫 배포 bootstrap (완료)
84
102
 
85
- ```text
86
- publish_auth = token
87
- NPM_TOKEN in npm-stable environment
88
- ```
103
+ 0.1.0은 두 패키지가 npm에 없던 상태였으므로 `publish_auth=token`과 `npm-stable`의
104
+ `NPM_TOKEN`으로 bootstrap했습니다.
89
105
 
90
- OIDC/trusted publishing은 초기 package version이 npm에 존재한 후 별도로 검토합니다.
91
- Candidate workflow는 OIDC 선택 시 package 존재 여부를 확인하고 없으면 fail-closed로 중단합니다.
106
+ 이제 두 package version이 registry에 존재합니다. npm trusted publisher가 이
107
+ workflow/environment에 구성된 경우 후속 릴리스는 `publish_auth=oidc`를 사용할 수
108
+ 있습니다. Candidate workflow는 OIDC 선택 시 package 존재 여부를 확인하고 없으면
109
+ fail-closed로 중단합니다. `NPM_TOKEN`은 bootstrap/recovery 경로로 유지합니다.
92
110
 
93
111
  ### Promotion
94
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-action/lit-ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@context-action/core": "^1.2.6",
60
- "@context-action/lit": "^0.1.0"
60
+ "@context-action/lit": "^0.1.1"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@lit-labs/ssr": "^4.1.0",