@takazudo/zfb-runtime 2.22.1 → 3.0.0

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.
@@ -160,25 +160,38 @@ export function swapBodyElement(newElement, oldElement) {
160
160
  else {
161
161
  newTarget.replaceWith(el);
162
162
  }
163
- // Persist-props hybrid path (port-spec §12.3.1 hybrid case / §12.3.2): the
164
- // persisted island survived the lift with its live component instance, but
165
- // the incoming page carries different props. Refresh data-props on the
166
- // surviving node and flag it with ISLAND_REMOUNT_ATTR. That flag is the
167
- // cross-package "needs-remount" signal consumed by @takazudo/zfb's
168
- // mountNewIslands (clearMountedForRemount): it can't be an in-memory queue
169
- // because the islands runtime's mounted-map lives in a different package, so
170
- // the flagged DOM node itself is the queue. mountNewIslands unmounts the
171
- // stale instance and re-mounts against this node with the new data-props.
172
- if (newTarget.matches("[data-zfb-island]") &&
173
- shouldCopyProps(el) &&
174
- !isSameProps(el, newTarget)) {
175
- el.setAttribute(ISLAND_REMOUNT_ATTR, "");
176
- // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).
177
- const np = newTarget.getAttribute("data-props");
178
- if (np !== null)
179
- el.setAttribute("data-props", np);
180
- else
181
- el.removeAttribute("data-props");
163
+ // Compare effective identity and props on the lifted wrapper. The surviving
164
+ // node carries incoming metadata before the runtime recreates its root.
165
+ const islandAttrs = [
166
+ "data-zfb-island",
167
+ "data-zfb-island-skip-ssr",
168
+ "data-zfb-transport",
169
+ "data-zfb-protocol",
170
+ "data-zfb-build",
171
+ ];
172
+ const isIsland = islandAttrs
173
+ .slice(0, 2)
174
+ .some((attr) => el.hasAttribute(attr) || newTarget.hasAttribute(attr));
175
+ if (isIsland) {
176
+ const identityChanged = islandAttrs.some((attr) => el.getAttribute(attr) !== newTarget.getAttribute(attr));
177
+ const copyProps = shouldCopyProps(el);
178
+ if (identityChanged || (copyProps && !isSameProps(el, newTarget))) {
179
+ el.setAttribute(ISLAND_REMOUNT_ATTR, "");
180
+ for (const attr of islandAttrs) {
181
+ const value = newTarget.getAttribute(attr);
182
+ if (value === null)
183
+ el.removeAttribute(attr);
184
+ else
185
+ el.setAttribute(attr, value);
186
+ }
187
+ if (copyProps) {
188
+ const props = newTarget.getAttribute("data-props");
189
+ if (props === null)
190
+ el.removeAttribute("data-props");
191
+ else
192
+ el.setAttribute("data-props", props);
193
+ }
194
+ }
182
195
  }
183
196
  }
184
197
  // This will upgrade any Declarative Shadow DOM in the new body.
@@ -1 +1 @@
1
- {"version":3,"file":"swap-functions.js","sourceRoot":"","sources":["../../src/client-router/swap-functions.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n/// <reference lib=\"dom.iterable\" />\n// Ported verbatim from Astro transitions/swap-functions.ts. Mechanical renames\n// applied per W1B §13.4 (data-astro-* → data-zfb-*, dataset.astro* → dataset.zfb*,\n// astro-island element-name branch → data-zfb-island marker per W1B §12.3 island-lifecycle\n// contract). vueScopedStyleId is intentionally KEPT verbatim per W2A §2.8 (the W3B\n// issue body's \"DELETE\" instruction is overridden by W2A's reclassification).\n\nexport type SavedFocus = {\n activeElement: HTMLElement | null;\n start?: number | null;\n end?: number | null;\n};\n\nconst PERSIST_ATTR = \"data-zfb-transition-persist\";\n\n// Cross-package island attributes must use the same literal strings here and in\n// @takazudo/zfb's runtime: PERSIST_ATTR is read by both packages;\n// ISLAND_REMOUNT_ATTR below is written here and consumed there; that runtime's\n// public ISLAND_MOUNTED_ATTR (\"data-zfb-island-mounted\") is preserved on lifted\n// islands and cleared/re-written there during a props-changed remount.\n//\n// ISLAND_REMOUNT_ATTR is written onto a persisted island whose props changed\n// across the swap. @takazudo/zfb's mountNewIslands consumes it — the island\n// mounted-map lives in that separate package, so a DOM attribute is the only\n// channel the two packages share. Namespaced under data-zfb-* like every other\n// zfb marker so it can't collide with a bare attribute a consumer sets on the\n// same island root. Replaces the opaque `ssr` attribute the original Astro port\n// set here but nothing consumed — zfb islands are marker divs, not <astro-island>\n// custom elements. See client-router/port-spec.md §12.3 / #1389.\nconst ISLAND_REMOUNT_ATTR = \"data-zfb-island-remount\";\n\nconst NON_OVERRIDABLE_ZFB_ATTRS = [\"data-zfb-transition\", \"data-zfb-transition-fallback\"];\n\n// Consumers extend the preserve-set via <meta name=\"zfb-preserve-html-attrs\"\n// content=\"data-theme data-sidebar-hidden …\">, emitted by\n// <ClientRouter preserveHtmlAttrs={[…]} />. Without it, a runtime <html>\n// attribute set from a persisted island is wiped on every swap. See\n// client-router/port-spec.md deviation #11 and Takazudo/zudo-front-builder#1103.\nconst PRESERVE_ATTRS_META_NAME = \"zfb-preserve-html-attrs\";\n\nconst knownVueScopedStyles = new Map<string, HTMLStyleElement>();\n\nconst scriptsAlreadyRan = new Set<string>();\nexport function detectScriptExecuted(script: HTMLScriptElement) {\n const key = script.src ? new URL(script.src, location.href).href : script.textContent!;\n if (scriptsAlreadyRan.has(key)) return true;\n scriptsAlreadyRan.add(key);\n return false;\n}\n\n/*\n * \tMark new scripts that should not execute\n */\nexport function deselectScripts(doc: Document) {\n for (const s2 of doc.scripts) {\n if (\n // Check if the script should be rerun regardless of it being the same\n !s2.hasAttribute(\"data-zfb-rerun\") &&\n // Check if the script has already been executed\n detectScriptExecuted(s2)\n ) {\n // the old script is in the new document and doesn't have the rerun attribute\n // we mark it as executed to prevent re-execution\n s2.dataset[\"zfbExec\"] = \"\";\n }\n }\n}\n\n/*\n * Read the consumer-configured preserve-list from the live document's\n * <meta name=\"zfb-preserve-html-attrs\"> tag. The list is a site-wide, static\n * contract (the tag is rendered on every page, so it is present on both the\n * current and incoming documents); reading the live document is sufficient.\n * Returns [] when the tag is absent.\n */\nfunction consumerPreservedAttrs(): string[] {\n const content = document\n .querySelector(`meta[name=\"${PRESERVE_ATTRS_META_NAME}\"]`)\n ?.getAttribute(\"content\");\n // Attribute names are ASCII-case-insensitive and the DOM exposes them\n // lowercased, so normalize the list to match (NON_OVERRIDABLE_ZFB_ATTRS is\n // already lowercase). Without this, a mixed-case entry would silently never\n // match the live attribute name.\n return content ? content.toLowerCase().split(/\\s+/).filter(Boolean) : [];\n}\n\nconst attrValueSelector = (attr: string, value: string): string =>\n value === \"\" ? `[${attr}=\"\"]` : `[${attr}=${CSS.escape(value)}]`;\n\nconst querySelectorWithAttrValue = (\n root: ParentNode,\n selectorPrefix: string,\n attr: string,\n value: string,\n): Element | null => {\n const escapedMatch = root.querySelector(`${selectorPrefix}${attrValueSelector(attr, value)}`);\n if (escapedMatch) return escapedMatch;\n return (\n Array.from(root.querySelectorAll(`${selectorPrefix}[${attr}]`)).find(\n (el) => el.getAttribute(attr) === value,\n ) ?? null\n );\n};\n\n/*\n * swap attributes of the html element\n * delete all attributes from the current document\n * insert all attributes from doc\n * reinsert all original attributes whose name is in the preserve-set:\n * NON_OVERRIDABLE_ZFB_ATTRS (transition-internal) ∪ the consumer preserve-list.\n * Preserved attributes are re-applied last, so a runtime value on the current\n * root wins over the incoming document's default (e.g. a localStorage-driven\n * data-theme). See client-router/port-spec.md deviation #11 / #1103.\n */\nexport function swapRootAttributes(newDoc: Document) {\n const currentRoot = document.documentElement;\n const preserve = new Set([...NON_OVERRIDABLE_ZFB_ATTRS, ...consumerPreservedAttrs()]);\n const preservedAttributes = [...currentRoot.attributes].filter(\n ({ name }) => (currentRoot.removeAttribute(name), preserve.has(name)),\n );\n [...newDoc.documentElement.attributes, ...preservedAttributes].forEach(({ name, value }) =>\n currentRoot.setAttribute(name, value),\n );\n}\n\n/*\n * make the old head look like the new one\n */\nexport function swapHeadElements(doc: Document) {\n for (const el of Array.from(document.head.children)) {\n const newEl = persistedHeadElement(el as HTMLElement, doc);\n // If the element exists in the document already, remove it\n // from the new document and leave the current node alone\n if (newEl) {\n newEl.remove();\n } else {\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n // In DEV mode, keep updated Vue scoped styles for later reuse\n const viteDevId = vueScopedStyleId(el);\n viteDevId && knownVueScopedStyles.set(viteDevId, el);\n }\n // If the element does not exist in the new document, remove the element from current the head.\n el.remove();\n }\n }\n\n // Everything left in the new head is new, append it all.\n if ((import.meta as any).env?.DEV) {\n // In DEV mode, replace known Vue scoped styles with the versions we remembered\n [...doc.head.children].forEach((child) => {\n document.head.append(knownVueScopedStyles.get((child as any).dataset?.viteDevId) || child);\n });\n } else {\n document.head.append(...doc.head.children);\n }\n}\n\nexport function swapBodyElement(newElement: Element, oldElement: Element) {\n // Lift persist elements to <html> before the body swap so they stay in the DOM\n // throughout replaceWith(). This prevents Safari from losing WebGL context on\n // <canvas> elements due to brief DOM detachment. Uses moveBefore() where available\n // (Chrome 133+) for zero-detachment atomic moves.\n const persistPairs: { old: Element; newTarget: Element }[] = [];\n const docEl = oldElement.ownerDocument.documentElement;\n\n // moveBefore() is not yet in TypeScript's DOM lib, feature-detect and wrap.\n const moveBefore: ((parent: Node, node: Node, child: Node | null) => void) | null =\n typeof (docEl as any).moveBefore === \"function\"\n ? (parent, node, child) => (parent as any).moveBefore(node, child)\n : null;\n\n for (const el of oldElement.querySelectorAll(`[${PERSIST_ATTR}]`)) {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newElement, \"\", PERSIST_ATTR, id) : null;\n if (!newEl) continue; // no matching target — leave in old body to be discarded\n persistPairs.push({ old: el, newTarget: newEl });\n if (moveBefore) {\n moveBefore(docEl, el, null);\n } else {\n docEl.appendChild(el);\n }\n }\n\n // this will reset scroll Position\n oldElement.replaceWith(newElement);\n\n // Move persist elements into the new body at the position of their targets\n for (const { old: el, newTarget } of persistPairs) {\n if (moveBefore) {\n moveBefore(newTarget.parentNode!, el, newTarget);\n newTarget.remove();\n } else {\n newTarget.replaceWith(el);\n }\n // Persist-props hybrid path (port-spec §12.3.1 hybrid case / §12.3.2): the\n // persisted island survived the lift with its live component instance, but\n // the incoming page carries different props. Refresh data-props on the\n // surviving node and flag it with ISLAND_REMOUNT_ATTR. That flag is the\n // cross-package \"needs-remount\" signal consumed by @takazudo/zfb's\n // mountNewIslands (clearMountedForRemount): it can't be an in-memory queue\n // because the islands runtime's mounted-map lives in a different package, so\n // the flagged DOM node itself is the queue. mountNewIslands unmounts the\n // stale instance and re-mounts against this node with the new data-props.\n if (\n newTarget.matches(\"[data-zfb-island]\") &&\n shouldCopyProps(el as HTMLElement) &&\n !isSameProps(el, newTarget)\n ) {\n el.setAttribute(ISLAND_REMOUNT_ATTR, \"\");\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n const np = newTarget.getAttribute(\"data-props\");\n if (np !== null) el.setAttribute(\"data-props\", np);\n else el.removeAttribute(\"data-props\");\n }\n }\n\n // This will upgrade any Declarative Shadow DOM in the new body.\n attachShadowRoots(newElement);\n}\n\n/**\n * Attach Shadow DOM roots for templates with the declarative `shadowrootmode` attribute.\n * @see https://web.dev/articles/declarative-shadow-dom#polyfill\n * @param root DOM subtree to attach shadow roots within.\n */\nfunction attachShadowRoots(root: Element | ShadowRoot) {\n root.querySelectorAll<HTMLTemplateElement>(\"template[shadowrootmode]\").forEach((template) => {\n const mode = template.getAttribute(\"shadowrootmode\");\n const parent = template.parentNode;\n if ((mode === \"closed\" || mode === \"open\") && parent instanceof HTMLElement) {\n // Skip if shadow root already exists (e.g., from transition-persisted elements)\n if (parent.shadowRoot) {\n template.remove();\n return;\n }\n const shadowRoot = parent.attachShadow({ mode });\n shadowRoot.appendChild(template.content);\n template.remove();\n attachShadowRoots(shadowRoot);\n }\n });\n}\n\nexport const saveFocus = (): (() => void) => {\n const activeElement = document.activeElement as HTMLElement;\n // The element that currently has the focus is part of a DOM tree\n // that will survive the transition to the new document.\n // Save the element and the cursor position\n if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n const start = activeElement.selectionStart;\n const end = activeElement.selectionEnd;\n return () => restoreFocus({ activeElement, start, end });\n }\n return () => restoreFocus({ activeElement });\n } else {\n return () => restoreFocus({ activeElement: null });\n }\n};\n\nexport const restoreFocus = ({ activeElement, start, end }: SavedFocus) => {\n if (activeElement) {\n activeElement.focus();\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n if (typeof start === \"number\") activeElement.selectionStart = start;\n if (typeof end === \"number\") activeElement.selectionEnd = end;\n }\n }\n};\n\nexport const vueScopedStyleId = (el: HTMLStyleElement): string => {\n const viteDevId = el.dataset[\"viteDevId\"] || \"\";\n\n const url = new URL(viteDevId, location.href);\n return url.searchParams.get(\"vue\") !== null &&\n url.searchParams.get(\"type\") === \"style\" &&\n url.searchParams.has(\"scoped\")\n ? viteDevId\n : \"\";\n};\n\n// Check for a head element that should persist and returns it,\n// either because it has the data attribute or because replacing it would cause avoidable FOUC.\nconst persistedHeadElement = (el: HTMLElement, newDoc: Document): Element | null => {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newDoc.head, \"\", PERSIST_ATTR, id) : null;\n if (newEl) {\n return newEl;\n }\n if (el.matches(\"link[rel=stylesheet]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=stylesheet]\", \"href\", href)\n : null;\n }\n // In dev mode, Vite injects <style data-vite-dev-id=\"...\"> elements whose\n // textContent may later be transformed (especially Vue's `:deep()` → `[data-v-xxx]`).\n // Match these by their stable dev ID so the already-transformed style is preserved\n // across ClientRouter soft navigations instead of being replaced by the raw version.\n // There are other ids that can't be preserved and need a refresh, like Uno's /__uno.css,\n // which keeps the same id, but with different contents.\n // To avoid enumerating all exceptions, we only apply the auto-persist logic to elements\n // that look like Vue's dev styles.\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n const viteDevId = vueScopedStyleId(el);\n if (viteDevId) {\n return querySelectorWithAttrValue(newDoc.head, \"style\", \"data-vite-dev-id\", viteDevId);\n }\n }\n // Preserve inline <style> elements with identical content across navigations.\n // This prevents unnecessary removal and re-insertion of styles (e.g. @font-face\n // declarations from <Font>), which would cause the browser to re-evaluate them\n // and trigger a flash of unstyled text (FOUT).\n if (el.tagName === \"STYLE\" && el.textContent) {\n const styles = newDoc.head.querySelectorAll(\"style\");\n for (const s of styles) {\n if (s.textContent === el.textContent) {\n return s;\n }\n }\n }\n // Preserve font preload links across navigations to avoid re-fetching cached fonts.\n if (el.matches(\"link[rel=preload][as=font]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=preload][as=font]\", \"href\", href)\n : null;\n }\n return null;\n};\n\nconst shouldCopyProps = (el: HTMLElement): boolean => {\n const persistProps = el.dataset[\"zfbTransitionPersistProps\"];\n return persistProps == null || persistProps === \"false\";\n};\n\nconst isSameProps = (oldEl: Element, newEl: Element) => {\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n return oldEl.getAttribute(\"data-props\") === newEl.getAttribute(\"data-props\");\n};\n\nexport const swapFunctions = {\n deselectScripts,\n swapRootAttributes,\n swapHeadElements,\n swapBodyElement,\n saveFocus,\n};\n\nexport const swap = (doc: Document) => {\n deselectScripts(doc);\n swapRootAttributes(doc);\n swapHeadElements(doc);\n const restoreFocusFunction = saveFocus();\n swapBodyElement(doc.body, document.body);\n restoreFocusFunction();\n};\n"]}
1
+ {"version":3,"file":"swap-functions.js","sourceRoot":"","sources":["../../src/client-router/swap-functions.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,oCAAoC;AACpC,+EAA+E;AAC/E,mFAAmF;AACnF,2FAA2F;AAC3F,mFAAmF;AACnF,8EAA8E;AAQ9E,MAAM,YAAY,GAAG,6BAA6B,CAAC;AAEnD,gFAAgF;AAChF,kEAAkE;AAClE,+EAA+E;AAC/E,gFAAgF;AAChF,uEAAuE;AACvE,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,kFAAkF;AAClF,iEAAiE;AACjE,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAEtD,MAAM,yBAAyB,GAAG,CAAC,qBAAqB,EAAE,8BAA8B,CAAC,CAAC;AAE1F,6EAA6E;AAC7E,0DAA0D;AAC1D,yEAAyE;AACzE,oEAAoE;AACpE,iFAAiF;AACjF,MAAM,wBAAwB,GAAG,yBAAyB,CAAC;AAE3D,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEjE,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAC;AAC5C,MAAM,UAAU,oBAAoB,CAAC,MAAyB;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAY,CAAC;IACvF,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7B;QACE,sEAAsE;QACtE,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC;YAClC,gDAAgD;YAChD,oBAAoB,CAAC,EAAE,CAAC,EACxB,CAAC;YACD,6EAA6E;YAC7E,iDAAiD;YACjD,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB;IAC7B,MAAM,OAAO,GAAG,QAAQ;SACrB,aAAa,CAAC,cAAc,wBAAwB,IAAI,CAAC;QAC1D,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC5B,sEAAsE;IACtE,2EAA2E;IAC3E,4EAA4E;IAC5E,iCAAiC;IACjC,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAChE,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;AAEnE,MAAM,0BAA0B,GAAG,CACjC,IAAgB,EAChB,cAAsB,EACtB,IAAY,EACZ,KAAa,EACG,EAAE;IAClB,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,cAAc,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9F,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,OAAO,CACL,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,cAAc,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAClE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,KAAK,CACxC,IAAI,IAAI,CACV,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAgB;IACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,yBAAyB,EAAE,GAAG,sBAAsB,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,mBAAmB,GAAG,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAC5D,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CACtE,CAAC;IACF,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC,UAAU,EAAE,GAAG,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CACzF,WAAW,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CACtC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAa;IAC5C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,oBAAoB,CAAC,EAAiB,EAAE,GAAG,CAAC,CAAC;QAC3D,2DAA2D;QAC3D,yDAAyD;QACzD,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,IAAK,MAAM,CAAC,IAAY,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY,gBAAgB,EAAE,CAAC;gBACpE,8DAA8D;gBAC9D,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;gBACvC,SAAS,IAAI,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YACvD,CAAC;YACD,+FAA+F;YAC/F,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,IAAK,MAAM,CAAC,IAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QAClC,+EAA+E;QAC/E,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACvC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,GAAG,CAAE,KAAa,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAmB,EAAE,UAAmB;IACtE,+EAA+E;IAC/E,8EAA8E;IAC9E,mFAAmF;IACnF,kDAAkD;IAClD,MAAM,YAAY,GAA2C,EAAE,CAAC;IAChE,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,eAAe,CAAC;IAEvD,4EAA4E;IAC5E,MAAM,UAAU,GACd,OAAQ,KAAa,CAAC,UAAU,KAAK,UAAU;QAC7C,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAE,MAAc,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC;QAClE,CAAC,CAAC,IAAI,CAAC;IAEX,KAAK,MAAM,EAAE,IAAI,UAAU,CAAC,gBAAgB,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAC,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChG,IAAI,CAAC,KAAK;YAAE,SAAS,CAAC,yDAAyD;QAC/E,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAEnC,2EAA2E;IAC3E,KAAK,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,YAAY,EAAE,CAAC;QAClD,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,SAAS,CAAC,UAAW,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;YACjD,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,4EAA4E;QAC5E,wEAAwE;QACxE,MAAM,WAAW,GAAG;YAClB,iBAAiB;YACjB,0BAA0B;YAC1B,oBAAoB;YACpB,mBAAmB;YACnB,gBAAgB;SACjB,CAAC;QACF,MAAM,QAAQ,GAAG,WAAW;aACzB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,eAAe,GAAG,WAAW,CAAC,IAAI,CACtC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CACjE,CAAC;YACF,MAAM,SAAS,GAAG,eAAe,CAAC,EAAiB,CAAC,CAAC;YACrD,IAAI,eAAe,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;gBAClE,EAAE,CAAC,YAAY,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;gBACzC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;oBAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;oBAC3C,IAAI,KAAK,KAAK,IAAI;wBAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;;wBACxC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACpC,CAAC;gBACD,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;oBACnD,IAAI,KAAK,KAAK,IAAI;wBAAE,EAAE,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;;wBAChD,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;gBAC5C,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,iBAAiB,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAA0B;IACnD,IAAI,CAAC,gBAAgB,CAAsB,0BAA0B,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1F,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,CAAC,IAAI,MAAM,YAAY,WAAW,EAAE,CAAC;YAC5E,gFAAgF;YAChF,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBACtB,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACzC,QAAQ,CAAC,MAAM,EAAE,CAAC;YAClB,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,GAAiB,EAAE;IAC1C,MAAM,aAAa,GAAG,QAAQ,CAAC,aAA4B,CAAC;IAC5D,iEAAiE;IACjE,wDAAwD;IACxD,2CAA2C;IAC3C,IAAI,aAAa,EAAE,OAAO,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAChD,IAAI,aAAa,YAAY,gBAAgB,IAAI,aAAa,YAAY,mBAAmB,EAAE,CAAC;YAC9F,MAAM,KAAK,GAAG,aAAa,CAAC,cAAc,CAAC;YAC3C,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC;YACvC,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAc,EAAE,EAAE;IACxE,IAAI,aAAa,EAAE,CAAC;QAClB,aAAa,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,aAAa,YAAY,gBAAgB,IAAI,aAAa,YAAY,mBAAmB,EAAE,CAAC;YAC9F,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,aAAa,CAAC,cAAc,GAAG,KAAK,CAAC;YACpE,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,aAAa,CAAC,YAAY,GAAG,GAAG,CAAC;QAChE,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAoB,EAAU,EAAE;IAC/D,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAEhD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI;QACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO;QACxC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC9B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,EAAE,CAAC;AACT,CAAC,CAAC;AAEF,+DAA+D;AAC/D,+FAA+F;AAC/F,MAAM,oBAAoB,GAAG,CAAC,EAAe,EAAE,MAAgB,EAAkB,EAAE;IACjF,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjG,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,IAAI,KAAK,IAAI;YAClB,CAAC,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,IAAI,CAAC;YAC/E,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IACD,0EAA0E;IAC1E,sFAAsF;IACtF,mFAAmF;IACnF,qFAAqF;IACrF,yFAAyF;IACzF,wDAAwD;IACxD,wFAAwF;IACxF,mCAAmC;IACnC,IAAK,MAAM,CAAC,IAAY,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY,gBAAgB,EAAE,CAAC;QACpE,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,0BAA0B,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,gFAAgF;IAChF,+EAA+E;IAC/E,+CAA+C;IAC/C,IAAI,EAAE,CAAC,OAAO,KAAK,OAAO,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACrD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC;gBACrC,OAAO,CAAC,CAAC;YACX,CAAC;QACH,CAAC;IACH,CAAC;IACD,oFAAoF;IACpF,IAAI,EAAE,CAAC,OAAO,CAAC,4BAA4B,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,IAAI,KAAK,IAAI;YAClB,CAAC,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,EAAE,4BAA4B,EAAE,MAAM,EAAE,IAAI,CAAC;YACrF,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,EAAe,EAAW,EAAE;IACnD,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7D,OAAO,YAAY,IAAI,IAAI,IAAI,YAAY,KAAK,OAAO,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,KAAc,EAAE,KAAc,EAAE,EAAE;IACrD,wGAAwG;IACxG,OAAO,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,KAAK,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;AAC/E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,eAAe;IACf,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;IACf,SAAS;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAa,EAAE,EAAE;IACpC,eAAe,CAAC,GAAG,CAAC,CAAC;IACrB,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxB,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,oBAAoB,GAAG,SAAS,EAAE,CAAC;IACzC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzC,oBAAoB,EAAE,CAAC;AACzB,CAAC,CAAC","sourcesContent":["/// <reference lib=\"dom\" />\n/// <reference lib=\"dom.iterable\" />\n// Ported verbatim from Astro transitions/swap-functions.ts. Mechanical renames\n// applied per W1B §13.4 (data-astro-* → data-zfb-*, dataset.astro* → dataset.zfb*,\n// astro-island element-name branch → data-zfb-island marker per W1B §12.3 island-lifecycle\n// contract). vueScopedStyleId is intentionally KEPT verbatim per W2A §2.8 (the W3B\n// issue body's \"DELETE\" instruction is overridden by W2A's reclassification).\n\nexport type SavedFocus = {\n activeElement: HTMLElement | null;\n start?: number | null;\n end?: number | null;\n};\n\nconst PERSIST_ATTR = \"data-zfb-transition-persist\";\n\n// Cross-package island attributes must use the same literal strings here and in\n// @takazudo/zfb's runtime: PERSIST_ATTR is read by both packages;\n// ISLAND_REMOUNT_ATTR below is written here and consumed there; that runtime's\n// public ISLAND_MOUNTED_ATTR (\"data-zfb-island-mounted\") is preserved on lifted\n// islands and cleared/re-written there during a props-changed remount.\n//\n// ISLAND_REMOUNT_ATTR is written onto a persisted island whose props changed\n// across the swap. @takazudo/zfb's mountNewIslands consumes it — the island\n// mounted-map lives in that separate package, so a DOM attribute is the only\n// channel the two packages share. Namespaced under data-zfb-* like every other\n// zfb marker so it can't collide with a bare attribute a consumer sets on the\n// same island root. Replaces the opaque `ssr` attribute the original Astro port\n// set here but nothing consumed — zfb islands are marker divs, not <astro-island>\n// custom elements. See client-router/port-spec.md §12.3 / #1389.\nconst ISLAND_REMOUNT_ATTR = \"data-zfb-island-remount\";\n\nconst NON_OVERRIDABLE_ZFB_ATTRS = [\"data-zfb-transition\", \"data-zfb-transition-fallback\"];\n\n// Consumers extend the preserve-set via <meta name=\"zfb-preserve-html-attrs\"\n// content=\"data-theme data-sidebar-hidden …\">, emitted by\n// <ClientRouter preserveHtmlAttrs={[…]} />. Without it, a runtime <html>\n// attribute set from a persisted island is wiped on every swap. See\n// client-router/port-spec.md deviation #11 and Takazudo/zudo-front-builder#1103.\nconst PRESERVE_ATTRS_META_NAME = \"zfb-preserve-html-attrs\";\n\nconst knownVueScopedStyles = new Map<string, HTMLStyleElement>();\n\nconst scriptsAlreadyRan = new Set<string>();\nexport function detectScriptExecuted(script: HTMLScriptElement) {\n const key = script.src ? new URL(script.src, location.href).href : script.textContent!;\n if (scriptsAlreadyRan.has(key)) return true;\n scriptsAlreadyRan.add(key);\n return false;\n}\n\n/*\n * \tMark new scripts that should not execute\n */\nexport function deselectScripts(doc: Document) {\n for (const s2 of doc.scripts) {\n if (\n // Check if the script should be rerun regardless of it being the same\n !s2.hasAttribute(\"data-zfb-rerun\") &&\n // Check if the script has already been executed\n detectScriptExecuted(s2)\n ) {\n // the old script is in the new document and doesn't have the rerun attribute\n // we mark it as executed to prevent re-execution\n s2.dataset[\"zfbExec\"] = \"\";\n }\n }\n}\n\n/*\n * Read the consumer-configured preserve-list from the live document's\n * <meta name=\"zfb-preserve-html-attrs\"> tag. The list is a site-wide, static\n * contract (the tag is rendered on every page, so it is present on both the\n * current and incoming documents); reading the live document is sufficient.\n * Returns [] when the tag is absent.\n */\nfunction consumerPreservedAttrs(): string[] {\n const content = document\n .querySelector(`meta[name=\"${PRESERVE_ATTRS_META_NAME}\"]`)\n ?.getAttribute(\"content\");\n // Attribute names are ASCII-case-insensitive and the DOM exposes them\n // lowercased, so normalize the list to match (NON_OVERRIDABLE_ZFB_ATTRS is\n // already lowercase). Without this, a mixed-case entry would silently never\n // match the live attribute name.\n return content ? content.toLowerCase().split(/\\s+/).filter(Boolean) : [];\n}\n\nconst attrValueSelector = (attr: string, value: string): string =>\n value === \"\" ? `[${attr}=\"\"]` : `[${attr}=${CSS.escape(value)}]`;\n\nconst querySelectorWithAttrValue = (\n root: ParentNode,\n selectorPrefix: string,\n attr: string,\n value: string,\n): Element | null => {\n const escapedMatch = root.querySelector(`${selectorPrefix}${attrValueSelector(attr, value)}`);\n if (escapedMatch) return escapedMatch;\n return (\n Array.from(root.querySelectorAll(`${selectorPrefix}[${attr}]`)).find(\n (el) => el.getAttribute(attr) === value,\n ) ?? null\n );\n};\n\n/*\n * swap attributes of the html element\n * delete all attributes from the current document\n * insert all attributes from doc\n * reinsert all original attributes whose name is in the preserve-set:\n * NON_OVERRIDABLE_ZFB_ATTRS (transition-internal) ∪ the consumer preserve-list.\n * Preserved attributes are re-applied last, so a runtime value on the current\n * root wins over the incoming document's default (e.g. a localStorage-driven\n * data-theme). See client-router/port-spec.md deviation #11 / #1103.\n */\nexport function swapRootAttributes(newDoc: Document) {\n const currentRoot = document.documentElement;\n const preserve = new Set([...NON_OVERRIDABLE_ZFB_ATTRS, ...consumerPreservedAttrs()]);\n const preservedAttributes = [...currentRoot.attributes].filter(\n ({ name }) => (currentRoot.removeAttribute(name), preserve.has(name)),\n );\n [...newDoc.documentElement.attributes, ...preservedAttributes].forEach(({ name, value }) =>\n currentRoot.setAttribute(name, value),\n );\n}\n\n/*\n * make the old head look like the new one\n */\nexport function swapHeadElements(doc: Document) {\n for (const el of Array.from(document.head.children)) {\n const newEl = persistedHeadElement(el as HTMLElement, doc);\n // If the element exists in the document already, remove it\n // from the new document and leave the current node alone\n if (newEl) {\n newEl.remove();\n } else {\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n // In DEV mode, keep updated Vue scoped styles for later reuse\n const viteDevId = vueScopedStyleId(el);\n viteDevId && knownVueScopedStyles.set(viteDevId, el);\n }\n // If the element does not exist in the new document, remove the element from current the head.\n el.remove();\n }\n }\n\n // Everything left in the new head is new, append it all.\n if ((import.meta as any).env?.DEV) {\n // In DEV mode, replace known Vue scoped styles with the versions we remembered\n [...doc.head.children].forEach((child) => {\n document.head.append(knownVueScopedStyles.get((child as any).dataset?.viteDevId) || child);\n });\n } else {\n document.head.append(...doc.head.children);\n }\n}\n\nexport function swapBodyElement(newElement: Element, oldElement: Element) {\n // Lift persist elements to <html> before the body swap so they stay in the DOM\n // throughout replaceWith(). This prevents Safari from losing WebGL context on\n // <canvas> elements due to brief DOM detachment. Uses moveBefore() where available\n // (Chrome 133+) for zero-detachment atomic moves.\n const persistPairs: { old: Element; newTarget: Element }[] = [];\n const docEl = oldElement.ownerDocument.documentElement;\n\n // moveBefore() is not yet in TypeScript's DOM lib, feature-detect and wrap.\n const moveBefore: ((parent: Node, node: Node, child: Node | null) => void) | null =\n typeof (docEl as any).moveBefore === \"function\"\n ? (parent, node, child) => (parent as any).moveBefore(node, child)\n : null;\n\n for (const el of oldElement.querySelectorAll(`[${PERSIST_ATTR}]`)) {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newElement, \"\", PERSIST_ATTR, id) : null;\n if (!newEl) continue; // no matching target — leave in old body to be discarded\n persistPairs.push({ old: el, newTarget: newEl });\n if (moveBefore) {\n moveBefore(docEl, el, null);\n } else {\n docEl.appendChild(el);\n }\n }\n\n // this will reset scroll Position\n oldElement.replaceWith(newElement);\n\n // Move persist elements into the new body at the position of their targets\n for (const { old: el, newTarget } of persistPairs) {\n if (moveBefore) {\n moveBefore(newTarget.parentNode!, el, newTarget);\n newTarget.remove();\n } else {\n newTarget.replaceWith(el);\n }\n // Compare effective identity and props on the lifted wrapper. The surviving\n // node carries incoming metadata before the runtime recreates its root.\n const islandAttrs = [\n \"data-zfb-island\",\n \"data-zfb-island-skip-ssr\",\n \"data-zfb-transport\",\n \"data-zfb-protocol\",\n \"data-zfb-build\",\n ];\n const isIsland = islandAttrs\n .slice(0, 2)\n .some((attr) => el.hasAttribute(attr) || newTarget.hasAttribute(attr));\n if (isIsland) {\n const identityChanged = islandAttrs.some(\n (attr) => el.getAttribute(attr) !== newTarget.getAttribute(attr),\n );\n const copyProps = shouldCopyProps(el as HTMLElement);\n if (identityChanged || (copyProps && !isSameProps(el, newTarget))) {\n el.setAttribute(ISLAND_REMOUNT_ATTR, \"\");\n for (const attr of islandAttrs) {\n const value = newTarget.getAttribute(attr);\n if (value === null) el.removeAttribute(attr);\n else el.setAttribute(attr, value);\n }\n if (copyProps) {\n const props = newTarget.getAttribute(\"data-props\");\n if (props === null) el.removeAttribute(\"data-props\");\n else el.setAttribute(\"data-props\", props);\n }\n }\n }\n }\n\n // This will upgrade any Declarative Shadow DOM in the new body.\n attachShadowRoots(newElement);\n}\n\n/**\n * Attach Shadow DOM roots for templates with the declarative `shadowrootmode` attribute.\n * @see https://web.dev/articles/declarative-shadow-dom#polyfill\n * @param root DOM subtree to attach shadow roots within.\n */\nfunction attachShadowRoots(root: Element | ShadowRoot) {\n root.querySelectorAll<HTMLTemplateElement>(\"template[shadowrootmode]\").forEach((template) => {\n const mode = template.getAttribute(\"shadowrootmode\");\n const parent = template.parentNode;\n if ((mode === \"closed\" || mode === \"open\") && parent instanceof HTMLElement) {\n // Skip if shadow root already exists (e.g., from transition-persisted elements)\n if (parent.shadowRoot) {\n template.remove();\n return;\n }\n const shadowRoot = parent.attachShadow({ mode });\n shadowRoot.appendChild(template.content);\n template.remove();\n attachShadowRoots(shadowRoot);\n }\n });\n}\n\nexport const saveFocus = (): (() => void) => {\n const activeElement = document.activeElement as HTMLElement;\n // The element that currently has the focus is part of a DOM tree\n // that will survive the transition to the new document.\n // Save the element and the cursor position\n if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n const start = activeElement.selectionStart;\n const end = activeElement.selectionEnd;\n return () => restoreFocus({ activeElement, start, end });\n }\n return () => restoreFocus({ activeElement });\n } else {\n return () => restoreFocus({ activeElement: null });\n }\n};\n\nexport const restoreFocus = ({ activeElement, start, end }: SavedFocus) => {\n if (activeElement) {\n activeElement.focus();\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n if (typeof start === \"number\") activeElement.selectionStart = start;\n if (typeof end === \"number\") activeElement.selectionEnd = end;\n }\n }\n};\n\nexport const vueScopedStyleId = (el: HTMLStyleElement): string => {\n const viteDevId = el.dataset[\"viteDevId\"] || \"\";\n\n const url = new URL(viteDevId, location.href);\n return url.searchParams.get(\"vue\") !== null &&\n url.searchParams.get(\"type\") === \"style\" &&\n url.searchParams.has(\"scoped\")\n ? viteDevId\n : \"\";\n};\n\n// Check for a head element that should persist and returns it,\n// either because it has the data attribute or because replacing it would cause avoidable FOUC.\nconst persistedHeadElement = (el: HTMLElement, newDoc: Document): Element | null => {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newDoc.head, \"\", PERSIST_ATTR, id) : null;\n if (newEl) {\n return newEl;\n }\n if (el.matches(\"link[rel=stylesheet]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=stylesheet]\", \"href\", href)\n : null;\n }\n // In dev mode, Vite injects <style data-vite-dev-id=\"...\"> elements whose\n // textContent may later be transformed (especially Vue's `:deep()` → `[data-v-xxx]`).\n // Match these by their stable dev ID so the already-transformed style is preserved\n // across ClientRouter soft navigations instead of being replaced by the raw version.\n // There are other ids that can't be preserved and need a refresh, like Uno's /__uno.css,\n // which keeps the same id, but with different contents.\n // To avoid enumerating all exceptions, we only apply the auto-persist logic to elements\n // that look like Vue's dev styles.\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n const viteDevId = vueScopedStyleId(el);\n if (viteDevId) {\n return querySelectorWithAttrValue(newDoc.head, \"style\", \"data-vite-dev-id\", viteDevId);\n }\n }\n // Preserve inline <style> elements with identical content across navigations.\n // This prevents unnecessary removal and re-insertion of styles (e.g. @font-face\n // declarations from <Font>), which would cause the browser to re-evaluate them\n // and trigger a flash of unstyled text (FOUT).\n if (el.tagName === \"STYLE\" && el.textContent) {\n const styles = newDoc.head.querySelectorAll(\"style\");\n for (const s of styles) {\n if (s.textContent === el.textContent) {\n return s;\n }\n }\n }\n // Preserve font preload links across navigations to avoid re-fetching cached fonts.\n if (el.matches(\"link[rel=preload][as=font]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=preload][as=font]\", \"href\", href)\n : null;\n }\n return null;\n};\n\nconst shouldCopyProps = (el: HTMLElement): boolean => {\n const persistProps = el.dataset[\"zfbTransitionPersistProps\"];\n return persistProps == null || persistProps === \"false\";\n};\n\nconst isSameProps = (oldEl: Element, newEl: Element) => {\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n return oldEl.getAttribute(\"data-props\") === newEl.getAttribute(\"data-props\");\n};\n\nexport const swapFunctions = {\n deselectScripts,\n swapRootAttributes,\n swapHeadElements,\n swapBodyElement,\n saveFocus,\n};\n\nexport const swap = (doc: Document) => {\n deselectScripts(doc);\n swapRootAttributes(doc);\n swapHeadElements(doc);\n const restoreFocusFunction = saveFocus();\n swapBodyElement(doc.body, document.body);\n restoreFocusFunction();\n};\n"]}
@@ -1,3 +1,4 @@
1
+ import type { Description } from "@takazudo/zfb/zudo-react";
1
2
  export interface ClientRouterProps {
2
3
  /** Fallback animation strategy when native View Transitions are not supported. */
3
4
  fallback?: "none" | "animate" | "swap";
@@ -44,16 +45,8 @@ export interface ClientRouterProps {
44
45
  */
45
46
  traverseRefetch?: boolean;
46
47
  }
47
- /**
48
- * Public element shape for each node returned by `<ClientRouter />`.
49
- * Structural type — intentionally matches the Preact/React VNode object shape
50
- * so consumers do not type-infer through the internal representation.
51
- */
52
- export type ClientRouterElement = {
53
- readonly type: string;
54
- readonly props: Readonly<Record<string, unknown>>;
55
- readonly key: unknown;
56
- };
48
+ /** Head descriptions returned by ClientRouter. */
49
+ export type ClientRouterElement = Description;
57
50
  /**
58
51
  * `<ClientRouter />` — SPA soft-swap navigation with View Transition animations.
59
52
  *
@@ -11,67 +11,24 @@
11
11
  // click/form intercepts live in `client-router/router.ts` and are registered
12
12
  // via `init()`. No inline <script> dangerouslySetInnerHTML is emitted.
13
13
  //
14
- // Component/activation split (#2437): this module is the pure component —
15
- // it imports ONLY `react/jsx-runtime` and `./client-router/prefetch.js`
16
- // (`prefetch.ts` has zero module-scope side effects), never
17
- // `./client-router/router.js`, and performs no top-level `document`/`window`
18
- // access. Importing it — including via the root barrel `@takazudo/zfb-runtime`
19
- // — registers no listeners and writes no history. `./client-router.ts` is the
20
- // activation shim: it re-exports this module's surface and additionally runs
21
- // `init()` as a side effect on import, which `import "@takazudo/zfb-runtime/client-router"`
22
- // (auto-injected by the islands bundler) relies on for byte-compatible activation.
23
- //
24
- // Framework-agnostic element minting (no JSX syntax):
25
- // `@takazudo/zfb-runtime` does not depend on a framework runtime. Head nodes
26
- // are minted by calling `jsx` from `react/jsx-runtime` directly — NOT JSX
27
- // syntax, so this stays a plain `.ts` file with no tsconfig JSX changes. The
28
- // engine alias-rewrites `react/jsx-runtime` → `preact/jsx-runtime` in Preact
29
- // mode (bundler.rs ~2886) and resolves it natively in React mode, so the same
30
- // call mints a real element for whichever framework the project configured.
31
- // The previous approach (a hand-rolled `{ type, props, key, constructor:
32
- // undefined }` object literal — the Preact diff-path sentinel) only worked for
33
- // Preact: React's renderer rejects such an object as a child with React error
34
- // #31 ("Objects are not valid as a React child"), because a real React element
35
- // carries `$$typeof: Symbol.for("react.element")` a literal cannot fake. Same
36
- // migration as `Island` in @takazudo/zfb.
37
- //
38
- // The component renders three base sibling elements to <head> (plus optional
39
- // metas emitted conditionally — see `prefetchDisabled` and `preserveHtmlAttrs`):
40
- // 1. A <style> tag with the `.zfb-route-announcer` ARIA helper class.
41
- // 2. <meta name="zfb-view-transitions-enabled" content="true" />
42
- // 3. <meta name="zfb-view-transitions-fallback" content={fallback} />
43
- //
44
- // The route-announcer <div> is injected into <body> by `announce()` in
45
- // `client-router/router.ts` on every navigation.
46
- import { jsx } from "react/jsx-runtime";
14
+ // Component/activation split (#2437): this pure module imports the owned
15
+ // JSX factory and prefetch helpers without activating the client router.
16
+ // The router activation shim remains at the client-router subpath.
17
+ import { jsx } from "@takazudo/zfb/zudo-react/jsx-runtime";
47
18
  import { init as prefetchInit } from "./client-router/prefetch.js";
48
19
  // Module-level guard: prefetch bootstrap is triggered at most once per module
49
20
  // lifetime even if <ClientRouter prefetchAll /> is mounted multiple times (#276).
50
21
  let prefetchBootstrapped = false;
51
- /**
52
- * Mint a head element through the per-project JSX runtime.
53
- *
54
- * Calls `jsx` from `react/jsx-runtime` (alias-rewritten to
55
- * `preact/jsx-runtime` in Preact mode by the engine, native in React mode)
56
- * so the returned value is a real element for whichever framework the
57
- * project configured — NOT a hand-rolled `{ type, props, key }` literal,
58
- * which only Preact accepts and which makes React throw error #31. A stable
59
- * `key` is passed because `ClientRouter()` returns these nodes in a plain
60
- * array (React warns about keyless list children otherwise). Mirrors the
61
- * `Island` migration in `@takazudo/zfb`.
62
- */
22
+ /** Mint a keyed owned head description without JSX syntax in this .ts file. */
63
23
  function makeVNode(type, props, key) {
64
- // `jsx`'s `type` param is typed `ElementType` (string-literal intrinsic
65
- // tags or component types), which rejects an arbitrary runtime `string`.
66
- // The tag is dynamic here, so cast to the factory's own first-param type —
67
- // robust whether the engine aliases `jsx` to react or preact at build time.
24
+ // The static tag choices below are passed through this string parameter.
68
25
  return jsx(type, props, key);
69
26
  }
70
27
  // CSS for the route-announcer element. Ported verbatim from Astro's
71
28
  // `<style is:global>` block in ClientRouter.astro (lines 11–23), renaming
72
29
  // `.astro-route-announcer` → `.zfb-route-announcer` per W1B §5.
73
30
  // This is a global (non-scoped) <style> because the announcer <div> is
74
- // appended to document.body at runtime, outside any Preact-controlled subtree.
31
+ // appended to document.body at runtime, outside the rendered head.
75
32
  const announcerCss = `
76
33
  .zfb-route-announcer {
77
34
  position: absolute;
@@ -112,7 +69,7 @@ export function ClientRouter({ fallback = "animate", prefetchAll: prefetchAllPro
112
69
  }
113
70
  const nodes = [
114
71
  // Global styles for the ARIA route-announcer div injected into <body>.
115
- makeVNode("style", { dangerouslySetInnerHTML: { __html: announcerCss } }, "zfb-vt-style"),
72
+ makeVNode("style", { rawHtml: announcerCss }, "zfb-vt-style"),
116
73
  // Opt-in meta tag: router checks for this to decide whether to intercept navigations.
117
74
  makeVNode("meta", { name: "zfb-view-transitions-enabled", content: "true" }, "zfb-vt-enabled"),
118
75
  // Fallback strategy meta tag: read by getFallback() in router.ts.
@@ -1 +1 @@
1
- {"version":3,"file":"client-router-component.js","sourceRoot":"","sources":["../src/client-router-component.ts"],"names":[],"mappings":"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","sourcesContent":["// `@takazudo/zfb-runtime` — `<ClientRouter />` component (pure module).\n//\n// Ported from Astro's `ClientRouter.astro` (155 lines).\n// Source: packages/astro/components/ClientRouter.astro\n// Issue: zudolab/zudo-doc#1519 (W3D), parent epic zudolab/zudo-doc#1510.\n//\n// Named-cause deviation — inline script split (W1B §13.6):\n// Astro's <script> block is processed by Vite at build time as a bundled\n// module with virtual-module imports (`astro:transitions/client`). The zfb\n// port emits meta tags + global styles from the JSX component, while the\n// click/form intercepts live in `client-router/router.ts` and are registered\n// via `init()`. No inline <script> dangerouslySetInnerHTML is emitted.\n//\n// Component/activation split (#2437): this module is the pure component —\n// it imports ONLY `react/jsx-runtime` and `./client-router/prefetch.js`\n// (`prefetch.ts` has zero module-scope side effects), never\n// `./client-router/router.js`, and performs no top-level `document`/`window`\n// access. Importing it — including via the root barrel `@takazudo/zfb-runtime`\n// — registers no listeners and writes no history. `./client-router.ts` is the\n// activation shim: it re-exports this module's surface and additionally runs\n// `init()` as a side effect on import, which `import \"@takazudo/zfb-runtime/client-router\"`\n// (auto-injected by the islands bundler) relies on for byte-compatible activation.\n//\n// Framework-agnostic element minting (no JSX syntax):\n// `@takazudo/zfb-runtime` does not depend on a framework runtime. Head nodes\n// are minted by calling `jsx` from `react/jsx-runtime` directly — NOT JSX\n// syntax, so this stays a plain `.ts` file with no tsconfig JSX changes. The\n// engine alias-rewrites `react/jsx-runtime` → `preact/jsx-runtime` in Preact\n// mode (bundler.rs ~2886) and resolves it natively in React mode, so the same\n// call mints a real element for whichever framework the project configured.\n// The previous approach (a hand-rolled `{ type, props, key, constructor:\n// undefined }` object literal — the Preact diff-path sentinel) only worked for\n// Preact: React's renderer rejects such an object as a child with React error\n// #31 (\"Objects are not valid as a React child\"), because a real React element\n// carries `$$typeof: Symbol.for(\"react.element\")` a literal cannot fake. Same\n// migration as `Island` in @takazudo/zfb.\n//\n// The component renders three base sibling elements to <head> (plus optional\n// metas emitted conditionally — see `prefetchDisabled` and `preserveHtmlAttrs`):\n// 1. A <style> tag with the `.zfb-route-announcer` ARIA helper class.\n// 2. <meta name=\"zfb-view-transitions-enabled\" content=\"true\" />\n// 3. <meta name=\"zfb-view-transitions-fallback\" content={fallback} />\n//\n// The route-announcer <div> is injected into <body> by `announce()` in\n// `client-router/router.ts` on every navigation.\n\nimport { jsx } from \"react/jsx-runtime\";\n\nimport { init as prefetchInit } from \"./client-router/prefetch.js\";\n\n// Module-level guard: prefetch bootstrap is triggered at most once per module\n// lifetime even if <ClientRouter prefetchAll /> is mounted multiple times (#276).\nlet prefetchBootstrapped = false;\n\nexport interface ClientRouterProps {\n /** Fallback animation strategy when native View Transitions are not supported. */\n fallback?: \"none\" | \"animate\" | \"swap\";\n /**\n * When true, opts every same-origin link into the default prefetch strategy\n * (hover) by calling prefetchInit({ prefetchAll: true }) once on the client.\n */\n prefetchAll?: boolean;\n /**\n * Extra `<html>` attribute names to preserve across SPA swaps. By default the\n * client router copies the incoming server-rendered document's `<html>`\n * attributes onto the live root, dropping any current attribute that isn't\n * internal to the transition machinery — so a *runtime* attribute a consumer\n * sets from a persisted island (e.g. `data-theme` or `data-sidebar-hidden`\n * driven from `localStorage`) is lost on every navigation. List those names\n * here and the router re-applies their current value after each swap. Emitted\n * as a `<meta name=\"zfb-preserve-html-attrs\">` tag that `swapRootAttributes`\n * reads at swap time.\n *\n * Mount with the **same list on every page** that participates in SPA\n * navigation: the preserve-list is read from the *current* (outgoing) page's\n * meta at swap time, so a page that omits an entry drops that attribute when\n * navigating away from it. Names are matched case-insensitively (DOM attribute\n * names are lowercased). For dynamic/computed cases, mutate\n * `event.newDocument.documentElement` in a `zfb:before-swap` listener instead.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1103\n */\n preserveHtmlAttrs?: string[];\n /**\n * Opt this page OUT of the same-page traverse fast-path (default `false` —\n * fast-path ON). By default a Back/Forward traversal between two history\n * entries sharing the same `pathname + search` is served instantly from the\n * live DOM — no re-fetch, no re-swap, so island/client state is preserved.\n *\n * Set this on a **per-request SSR page** (`prerender = false`) whose\n * server-rendered content can legitimately differ between two visits to the\n * same URL: with the fast-path skipped such a traverse would pin the stale\n * first-render content. Emitted as\n * `<meta name=\"zfb-traverse-refetch\" content=\"true\">`, which the router reads\n * on the current (target) page to force the fetch back on. Mount with the\n * **same value on every page** that participates in SPA navigation, mirroring\n * the `preserveHtmlAttrs` guidance.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1376\n */\n traverseRefetch?: boolean;\n}\n\n/**\n * Public element shape for each node returned by `<ClientRouter />`.\n * Structural type — intentionally matches the Preact/React VNode object shape\n * so consumers do not type-infer through the internal representation.\n */\nexport type ClientRouterElement = {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly key: unknown;\n};\n\n/**\n * Mint a head element through the per-project JSX runtime.\n *\n * Calls `jsx` from `react/jsx-runtime` (alias-rewritten to\n * `preact/jsx-runtime` in Preact mode by the engine, native in React mode)\n * so the returned value is a real element for whichever framework the\n * project configured — NOT a hand-rolled `{ type, props, key }` literal,\n * which only Preact accepts and which makes React throw error #31. A stable\n * `key` is passed because `ClientRouter()` returns these nodes in a plain\n * array (React warns about keyless list children otherwise). Mirrors the\n * `Island` migration in `@takazudo/zfb`.\n */\nfunction makeVNode(type: string, props: Record<string, unknown>, key: string): ClientRouterElement {\n // `jsx`'s `type` param is typed `ElementType` (string-literal intrinsic\n // tags or component types), which rejects an arbitrary runtime `string`.\n // The tag is dynamic here, so cast to the factory's own first-param type —\n // robust whether the engine aliases `jsx` to react or preact at build time.\n return jsx(type as Parameters<typeof jsx>[0], props, key) as unknown as ClientRouterElement;\n}\n\n// CSS for the route-announcer element. Ported verbatim from Astro's\n// `<style is:global>` block in ClientRouter.astro (lines 11–23), renaming\n// `.astro-route-announcer` → `.zfb-route-announcer` per W1B §5.\n// This is a global (non-scoped) <style> because the announcer <div> is\n// appended to document.body at runtime, outside any Preact-controlled subtree.\nconst announcerCss = `\n.zfb-route-announcer {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tclip: rect(0 0 0 0);\n\tclip-path: inset(50%);\n\toverflow: hidden;\n\twhite-space: nowrap;\n\twidth: 1px;\n\theight: 1px;\n}\n`;\n\n/**\n * `<ClientRouter />` — SPA soft-swap navigation with View Transition animations.\n *\n * Mount once in your page `<head>`. Emits the opt-in meta tags and the global\n * `.zfb-route-announcer` stylesheet that the route-announcer ARIA div needs.\n * Importing this module performs no side effects — click/form-submit\n * intercepts are wired only when `./client-router.js` (the activation shim,\n * imported directly or via the `@takazudo/zfb-runtime/client-router` subpath)\n * is evaluated.\n *\n * @example\n * ```tsx\n * import { ClientRouter } from \"@takazudo/zfb-runtime\";\n * // In your page <head>:\n * <ClientRouter fallback=\"animate\" />\n * ```\n */\nexport function ClientRouter({\n fallback = \"animate\",\n prefetchAll: prefetchAllProp = false,\n preserveHtmlAttrs = [],\n traverseRefetch = false,\n}: ClientRouterProps = {}): readonly ClientRouterElement[] {\n // Bootstrap prefetch exactly once on the client when prefetchAll is true.\n // The initialized flag inside prefetchInit() provides a second safety layer\n // in case of concurrent hydration or manual callers (#276).\n if (typeof document !== \"undefined\" && prefetchAllProp && !prefetchBootstrapped) {\n prefetchBootstrapped = true;\n prefetchInit({ prefetchAll: true });\n }\n\n const nodes: ClientRouterElement[] = [\n // Global styles for the ARIA route-announcer div injected into <body>.\n makeVNode(\"style\", { dangerouslySetInnerHTML: { __html: announcerCss } }, \"zfb-vt-style\"),\n // Opt-in meta tag: router checks for this to decide whether to intercept navigations.\n makeVNode(\"meta\", { name: \"zfb-view-transitions-enabled\", content: \"true\" }, \"zfb-vt-enabled\"),\n // Fallback strategy meta tag: read by getFallback() in router.ts.\n makeVNode(\n \"meta\",\n { name: \"zfb-view-transitions-fallback\", content: fallback },\n \"zfb-vt-fallback\",\n ),\n ];\n\n // Prefetch-disabled meta tag (#277): emitted when the bundler set\n // `globalThis.__zfb.prefetchDisabled = true` (from `zfb.config.ts`\n // `prefetch: { disabled: true }`). The sibling prefetch-core module reads\n // `document.querySelector('meta[name=\"zfb-prefetch-disabled\"][content=\"true\"]')`\n // at `init()` time and short-circuits if found.\n //\n // The flag is site-wide and static — set once at bundle-emit time, never\n // per-page. This meta tag appears on every page that mounts `<ClientRouter />`\n // or not at all.\n //\n // Pin the contract verbatim — the attribute names and content value are\n // shared with the sibling prefetch-core sub-issue (#276).\n if ((globalThis as { __zfb?: { prefetchDisabled?: boolean } }).__zfb?.prefetchDisabled === true) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-prefetch-disabled\", content: \"true\" },\n \"zfb-prefetch-disabled\",\n ),\n );\n }\n\n // Consumer-extensible <html> attribute preserve-list (#1103). swapRootAttributes\n // reads this meta and re-applies the listed attributes' runtime values after each\n // swap, so persisted-island state on <html> (data-theme, data-sidebar-hidden, …)\n // survives navigation. Emitted only when non-empty, so non-opt-in output stays\n // byte-identical. Same conditional-emission shape as the prefetch-disabled meta.\n const preserveList = preserveHtmlAttrs.filter(Boolean);\n if (preserveList.length > 0) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-preserve-html-attrs\", content: preserveList.join(\" \") },\n \"zfb-preserve-html-attrs\",\n ),\n );\n }\n\n // Traverse-refetch opt-out meta (#1376). A same-page Back/Forward traversal\n // skips the re-fetch/re-swap by default (the router's traverse fast-path);\n // this meta opts a per-request SSR page (`prerender = false`) back INTO the\n // fetch, since skipping it would pin stale server-rendered content. Emitted\n // only when opted in, so non-opt-in output stays byte-identical. Same\n // conditional-emission shape as the prefetch-disabled / preserve-html-attrs\n // metas; the router reads meta[name=\"zfb-traverse-refetch\"][content=\"true\"].\n if (traverseRefetch) {\n nodes.push(\n makeVNode(\"meta\", { name: \"zfb-traverse-refetch\", content: \"true\" }, \"zfb-traverse-refetch\"),\n );\n }\n\n return nodes;\n}\n"]}
1
+ {"version":3,"file":"client-router-component.js","sourceRoot":"","sources":["../src/client-router-component.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,wDAAwD;AACxD,uDAAuD;AACvD,yEAAyE;AACzE,EAAE;AACF,2DAA2D;AAC3D,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,+EAA+E;AAC/E,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,mEAAmE;AAEnE,OAAO,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AAG3D,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEnE,8EAA8E;AAC9E,kFAAkF;AAClF,IAAI,oBAAoB,GAAG,KAAK,CAAC;AAoDjC,+EAA+E;AAC/E,SAAS,SAAS,CAAC,IAAY,EAAE,KAA8B,EAAE,GAAW;IAC1E,yEAAyE;IACzE,OAAO,GAAG,CAAC,IAAiC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,oEAAoE;AACpE,0EAA0E;AAC1E,gEAAgE;AAChE,uEAAuE;AACvE,mEAAmE;AACnE,MAAM,YAAY,GAAG;;;;;;;;;;;;CAYpB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,QAAQ,GAAG,SAAS,EACpB,WAAW,EAAE,eAAe,GAAG,KAAK,EACpC,iBAAiB,GAAG,EAAE,EACtB,eAAe,GAAG,KAAK,MACF,EAAE;IACvB,0EAA0E;IAC1E,4EAA4E;IAC5E,4DAA4D;IAC5D,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,eAAe,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAChF,oBAAoB,GAAG,IAAI,CAAC;QAC5B,YAAY,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,KAAK,GAA0B;QACnC,uEAAuE;QACvE,SAAS,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,cAAc,CAAC;QAC7D,sFAAsF;QACtF,SAAS,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,gBAAgB,CAAC;QAC9F,kEAAkE;QAClE,SAAS,CACP,MAAM,EACN,EAAE,IAAI,EAAE,+BAA+B,EAAE,OAAO,EAAE,QAAQ,EAAE,EAC5D,iBAAiB,CAClB;KACF,CAAC;IAEF,kEAAkE;IAClE,mEAAmE;IACnE,0EAA0E;IAC1E,iFAAiF;IACjF,gDAAgD;IAChD,EAAE;IACF,yEAAyE;IACzE,+EAA+E;IAC/E,iBAAiB;IACjB,EAAE;IACF,wEAAwE;IACxE,0DAA0D;IAC1D,IAAK,UAAyD,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAChG,KAAK,CAAC,IAAI,CACR,SAAS,CACP,MAAM,EACN,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,EAAE,EAClD,uBAAuB,CACxB,CACF,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,kFAAkF;IAClF,iFAAiF;IACjF,+EAA+E;IAC/E,iFAAiF;IACjF,MAAM,YAAY,GAAG,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CACR,SAAS,CACP,MAAM,EACN,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EACpE,yBAAyB,CAC1B,CACF,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,sEAAsE;IACtE,4EAA4E;IAC5E,6EAA6E;IAC7E,IAAI,eAAe,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAC7F,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// `@takazudo/zfb-runtime` — `<ClientRouter />` component (pure module).\n//\n// Ported from Astro's `ClientRouter.astro` (155 lines).\n// Source: packages/astro/components/ClientRouter.astro\n// Issue: zudolab/zudo-doc#1519 (W3D), parent epic zudolab/zudo-doc#1510.\n//\n// Named-cause deviation — inline script split (W1B §13.6):\n// Astro's <script> block is processed by Vite at build time as a bundled\n// module with virtual-module imports (`astro:transitions/client`). The zfb\n// port emits meta tags + global styles from the JSX component, while the\n// click/form intercepts live in `client-router/router.ts` and are registered\n// via `init()`. No inline <script> dangerouslySetInnerHTML is emitted.\n//\n// Component/activation split (#2437): this pure module imports the owned\n// JSX factory and prefetch helpers without activating the client router.\n// The router activation shim remains at the client-router subpath.\n\nimport { jsx } from \"@takazudo/zfb/zudo-react/jsx-runtime\";\nimport type { Description } from \"@takazudo/zfb/zudo-react\";\n\nimport { init as prefetchInit } from \"./client-router/prefetch.js\";\n\n// Module-level guard: prefetch bootstrap is triggered at most once per module\n// lifetime even if <ClientRouter prefetchAll /> is mounted multiple times (#276).\nlet prefetchBootstrapped = false;\n\nexport interface ClientRouterProps {\n /** Fallback animation strategy when native View Transitions are not supported. */\n fallback?: \"none\" | \"animate\" | \"swap\";\n /**\n * When true, opts every same-origin link into the default prefetch strategy\n * (hover) by calling prefetchInit({ prefetchAll: true }) once on the client.\n */\n prefetchAll?: boolean;\n /**\n * Extra `<html>` attribute names to preserve across SPA swaps. By default the\n * client router copies the incoming server-rendered document's `<html>`\n * attributes onto the live root, dropping any current attribute that isn't\n * internal to the transition machinery — so a *runtime* attribute a consumer\n * sets from a persisted island (e.g. `data-theme` or `data-sidebar-hidden`\n * driven from `localStorage`) is lost on every navigation. List those names\n * here and the router re-applies their current value after each swap. Emitted\n * as a `<meta name=\"zfb-preserve-html-attrs\">` tag that `swapRootAttributes`\n * reads at swap time.\n *\n * Mount with the **same list on every page** that participates in SPA\n * navigation: the preserve-list is read from the *current* (outgoing) page's\n * meta at swap time, so a page that omits an entry drops that attribute when\n * navigating away from it. Names are matched case-insensitively (DOM attribute\n * names are lowercased). For dynamic/computed cases, mutate\n * `event.newDocument.documentElement` in a `zfb:before-swap` listener instead.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1103\n */\n preserveHtmlAttrs?: string[];\n /**\n * Opt this page OUT of the same-page traverse fast-path (default `false` —\n * fast-path ON). By default a Back/Forward traversal between two history\n * entries sharing the same `pathname + search` is served instantly from the\n * live DOM — no re-fetch, no re-swap, so island/client state is preserved.\n *\n * Set this on a **per-request SSR page** (`prerender = false`) whose\n * server-rendered content can legitimately differ between two visits to the\n * same URL: with the fast-path skipped such a traverse would pin the stale\n * first-render content. Emitted as\n * `<meta name=\"zfb-traverse-refetch\" content=\"true\">`, which the router reads\n * on the current (target) page to force the fetch back on. Mount with the\n * **same value on every page** that participates in SPA navigation, mirroring\n * the `preserveHtmlAttrs` guidance.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1376\n */\n traverseRefetch?: boolean;\n}\n\n/** Head descriptions returned by ClientRouter. */\nexport type ClientRouterElement = Description;\n\n/** Mint a keyed owned head description without JSX syntax in this .ts file. */\nfunction makeVNode(type: string, props: Record<string, unknown>, key: string): ClientRouterElement {\n // The static tag choices below are passed through this string parameter.\n return jsx(type as Parameters<typeof jsx>[0], props, key);\n}\n\n// CSS for the route-announcer element. Ported verbatim from Astro's\n// `<style is:global>` block in ClientRouter.astro (lines 11–23), renaming\n// `.astro-route-announcer` → `.zfb-route-announcer` per W1B §5.\n// This is a global (non-scoped) <style> because the announcer <div> is\n// appended to document.body at runtime, outside the rendered head.\nconst announcerCss = `\n.zfb-route-announcer {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tclip: rect(0 0 0 0);\n\tclip-path: inset(50%);\n\toverflow: hidden;\n\twhite-space: nowrap;\n\twidth: 1px;\n\theight: 1px;\n}\n`;\n\n/**\n * `<ClientRouter />` — SPA soft-swap navigation with View Transition animations.\n *\n * Mount once in your page `<head>`. Emits the opt-in meta tags and the global\n * `.zfb-route-announcer` stylesheet that the route-announcer ARIA div needs.\n * Importing this module performs no side effects — click/form-submit\n * intercepts are wired only when `./client-router.js` (the activation shim,\n * imported directly or via the `@takazudo/zfb-runtime/client-router` subpath)\n * is evaluated.\n *\n * @example\n * ```tsx\n * import { ClientRouter } from \"@takazudo/zfb-runtime\";\n * // In your page <head>:\n * <ClientRouter fallback=\"animate\" />\n * ```\n */\nexport function ClientRouter({\n fallback = \"animate\",\n prefetchAll: prefetchAllProp = false,\n preserveHtmlAttrs = [],\n traverseRefetch = false,\n}: ClientRouterProps = {}): readonly ClientRouterElement[] {\n // Bootstrap prefetch exactly once on the client when prefetchAll is true.\n // The initialized flag inside prefetchInit() provides a second safety layer\n // in case of concurrent hydration or manual callers (#276).\n if (typeof document !== \"undefined\" && prefetchAllProp && !prefetchBootstrapped) {\n prefetchBootstrapped = true;\n prefetchInit({ prefetchAll: true });\n }\n\n const nodes: ClientRouterElement[] = [\n // Global styles for the ARIA route-announcer div injected into <body>.\n makeVNode(\"style\", { rawHtml: announcerCss }, \"zfb-vt-style\"),\n // Opt-in meta tag: router checks for this to decide whether to intercept navigations.\n makeVNode(\"meta\", { name: \"zfb-view-transitions-enabled\", content: \"true\" }, \"zfb-vt-enabled\"),\n // Fallback strategy meta tag: read by getFallback() in router.ts.\n makeVNode(\n \"meta\",\n { name: \"zfb-view-transitions-fallback\", content: fallback },\n \"zfb-vt-fallback\",\n ),\n ];\n\n // Prefetch-disabled meta tag (#277): emitted when the bundler set\n // `globalThis.__zfb.prefetchDisabled = true` (from `zfb.config.ts`\n // `prefetch: { disabled: true }`). The sibling prefetch-core module reads\n // `document.querySelector('meta[name=\"zfb-prefetch-disabled\"][content=\"true\"]')`\n // at `init()` time and short-circuits if found.\n //\n // The flag is site-wide and static — set once at bundle-emit time, never\n // per-page. This meta tag appears on every page that mounts `<ClientRouter />`\n // or not at all.\n //\n // Pin the contract verbatim — the attribute names and content value are\n // shared with the sibling prefetch-core sub-issue (#276).\n if ((globalThis as { __zfb?: { prefetchDisabled?: boolean } }).__zfb?.prefetchDisabled === true) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-prefetch-disabled\", content: \"true\" },\n \"zfb-prefetch-disabled\",\n ),\n );\n }\n\n // Consumer-extensible <html> attribute preserve-list (#1103). swapRootAttributes\n // reads this meta and re-applies the listed attributes' runtime values after each\n // swap, so persisted-island state on <html> (data-theme, data-sidebar-hidden, …)\n // survives navigation. Emitted only when non-empty, so non-opt-in output stays\n // byte-identical. Same conditional-emission shape as the prefetch-disabled meta.\n const preserveList = preserveHtmlAttrs.filter(Boolean);\n if (preserveList.length > 0) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-preserve-html-attrs\", content: preserveList.join(\" \") },\n \"zfb-preserve-html-attrs\",\n ),\n );\n }\n\n // Traverse-refetch opt-out meta (#1376). A same-page Back/Forward traversal\n // skips the re-fetch/re-swap by default (the router's traverse fast-path);\n // this meta opts a per-request SSR page (`prerender = false`) back INTO the\n // fetch, since skipping it would pin stale server-rendered content. Emitted\n // only when opted in, so non-opt-in output stays byte-identical. Same\n // conditional-emission shape as the prefetch-disabled / preserve-html-attrs\n // metas; the router reads meta[name=\"zfb-traverse-refetch\"][content=\"true\"].\n if (traverseRefetch) {\n nodes.push(\n makeVNode(\"meta\", { name: \"zfb-traverse-refetch\", content: \"true\" }, \"zfb-traverse-refetch\"),\n );\n }\n\n return nodes;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export type { CreatePageRouterOptions, PageDefinition, PageHeading, PageModule, PageRouter, } from "./router.js";
2
- export type { FrameworkAdapter } from "./framework.js";
3
2
  export type { ContentSnapshot, EntrySnapshot } from "./snapshot.js";
4
3
  export { ViewTransitions, type ViewTransitionsElement } from "./view-transitions.js";
5
4
  export { ClientRouter, type ClientRouterProps } from "./client-router-component.js";
package/dist/index.js CHANGED
@@ -18,8 +18,7 @@
18
18
  // const router = createPageRouter({
19
19
  // pages: [...],
20
20
  // contentSnapshot: __ZFB_CONTENT_SNAPSHOT__, // embedded by the bundler
21
- // framework: { renderToString: render }, // preact-render-to-string etc.
22
- // });
21
+ //// });
23
22
  //
24
23
  // export default { fetch: router };
25
24
  //
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,qEAAqE;AACrE,2EAA2E;AAC3E,mFAAmF;AACnF,sEAAsE;AACtE,0EAA0E;AAC1E,qEAAqE;AACrE,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,oBAAoB;AACpB,4EAA4E;AAC5E,iFAAiF;AACjF,QAAQ;AACR,EAAE;AACF,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AAWxE,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AAErF,2FAA2F;AAC3F,2CAA2C;AAC3C,EAAE;AACF,mEAAmE;AACnE,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,8EAA8E;AAC9E,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EACL,QAAQ,EACR,uBAAuB,EACvB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,kCAAkC;AAClC,8EAA8E;AAC9E,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM7E,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,gCAAgC,EAChC,yBAAyB,EACzB,kCAAkC,EAClC,2BAA2B,GAC5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC","sourcesContent":["// Public entry point for `@takazudo/zfb-runtime`.\n//\n// This is the **client-safe** barrel: everything re-exported here must be\n// bundleable for `--platform=browser` without pulling a server-only\n// dependency. In particular `createPageRouter` (and the Hono server router\n// it builds) is intentionally NOT re-exported here — it lives at the\n// server-only subpath `@takazudo/zfb-runtime/server`. Re-exporting it from\n// this barrel makes any island that does `import ... from \"@takazudo/zfb-runtime\"`\n// drag `hono` into the browser graph, which esbuild must then resolve\n// (issue #1298). The page-router *types* below are `export type` only, so\n// esbuild strips them and no runtime edge to `./router.js` survives.\n//\n// Worker bundles produced by T3 import the server subpath and invoke\n// `createPageRouter` once, at module top, to obtain the fetch handler:\n//\n// import { createPageRouter } from \"@takazudo/zfb-runtime/server\";\n//\n// const router = createPageRouter({\n// pages: [...],\n// contentSnapshot: __ZFB_CONTENT_SNAPSHOT__, // embedded by the bundler\n// framework: { renderToString: render }, // preact-render-to-string etc.\n// });\n//\n// export default { fetch: router };\n//\n// The README documents the bundle shape T6 (embedded V8 host) consumes.\n\nexport type {\n CreatePageRouterOptions,\n PageDefinition,\n PageHeading,\n PageModule,\n PageRouter,\n} from \"./router.js\";\nexport type { FrameworkAdapter } from \"./framework.js\";\nexport type { ContentSnapshot, EntrySnapshot } from \"./snapshot.js\";\nexport { ViewTransitions, type ViewTransitionsElement } from \"./view-transitions.js\";\n\n// Client-router public surface (W3D — mirrors @takazudo/zfb-runtime/client-router barrel).\n// See W1B §2 for the full public API spec.\n//\n// ClientRouter is imported from the pure component module, NOT the\n// `./client-router.js` activation shim (#2437): the shim runs `init()` as a\n// module-scope side effect, which would make `import { ClientRouter } from\n// \"@takazudo/zfb-runtime\"` silently activate the router. The pure module\n// performs zero side effects, so the root barrel stays side-effect-free.\n// `import \"@takazudo/zfb-runtime/client-router\"` (the shim's subpath) remains\n// the byte-compatible activation entry point — see client-router.ts.\nexport { ClientRouter, type ClientRouterProps } from \"./client-router-component.js\";\nexport {\n navigate,\n supportsViewTransitions,\n transitionEnabledOnThisPage,\n syncHistoryEntry,\n} from \"./client-router/router.js\";\n\n// Prefetch public surface (#276).\n// `init` from prefetch.ts is re-exported as `prefetchInit` to avoid collision\n// with the router's `init`.\nexport { prefetch, init as prefetchInit } from \"./client-router/prefetch.js\";\nexport type {\n PrefetchStrategy,\n PrefetchInitOptions,\n PrefetchOptions,\n} from \"./client-router/prefetch.js\";\nexport {\n TRANSITION_BEFORE_PREPARATION,\n TRANSITION_AFTER_PREPARATION,\n TRANSITION_BEFORE_SWAP,\n TRANSITION_AFTER_SWAP,\n TRANSITION_PAGE_LOAD,\n TRANSITION_NAVIGATION_ABORTED,\n TransitionBeforePreparationEvent,\n TransitionBeforeSwapEvent,\n isTransitionBeforePreparationEvent,\n isTransitionBeforeSwapEvent,\n} from \"./client-router/events.js\";\nexport { swapFunctions, swap } from \"./client-router/swap-functions.js\";\nexport type {\n Direction,\n Fallback,\n NavigationTypeString,\n Options,\n SyncHistoryEntryOptions,\n} from \"./client-router/types.js\";\n\n// Plugin lifecycle types (#255). The runtime package re-exports the\n// `@takazudo/zfb/plugins` surface so consumers writing plugins from a\n// project that only depends on `@takazudo/zfb-runtime` can still\n// import the canonical types (e.g. `import type { ZfbPlugin } from\n// \"@takazudo/zfb-runtime\"`).\nexport type {\n ZfbPlugin,\n ZfbPluginLogger,\n ZfbBuildHookContext,\n ZfbDevMiddlewareContext,\n ZfbDevMiddlewareHandler,\n ZfbDevMiddlewareRequest,\n ZfbDevMiddlewareResponse,\n ZfbRouteEntry,\n ZfbRouteManifest,\n ZfbSetupContext,\n ZfbVirtualModuleLoader,\n} from \"@takazudo/zfb/plugins\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,qEAAqE;AACrE,2EAA2E;AAC3E,mFAAmF;AACnF,sEAAsE;AACtE,0EAA0E;AAC1E,qEAAqE;AACrE,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,oBAAoB;AACpB,4EAA4E;AAC5E,UAAU;AACV,EAAE;AACF,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AAUxE,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AAErF,2FAA2F;AAC3F,2CAA2C;AAC3C,EAAE;AACF,mEAAmE;AACnE,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,8EAA8E;AAC9E,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EACL,QAAQ,EACR,uBAAuB,EACvB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,kCAAkC;AAClC,8EAA8E;AAC9E,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM7E,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,gCAAgC,EAChC,yBAAyB,EACzB,kCAAkC,EAClC,2BAA2B,GAC5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC","sourcesContent":["// Public entry point for `@takazudo/zfb-runtime`.\n//\n// This is the **client-safe** barrel: everything re-exported here must be\n// bundleable for `--platform=browser` without pulling a server-only\n// dependency. In particular `createPageRouter` (and the Hono server router\n// it builds) is intentionally NOT re-exported here — it lives at the\n// server-only subpath `@takazudo/zfb-runtime/server`. Re-exporting it from\n// this barrel makes any island that does `import ... from \"@takazudo/zfb-runtime\"`\n// drag `hono` into the browser graph, which esbuild must then resolve\n// (issue #1298). The page-router *types* below are `export type` only, so\n// esbuild strips them and no runtime edge to `./router.js` survives.\n//\n// Worker bundles produced by T3 import the server subpath and invoke\n// `createPageRouter` once, at module top, to obtain the fetch handler:\n//\n// import { createPageRouter } from \"@takazudo/zfb-runtime/server\";\n//\n// const router = createPageRouter({\n// pages: [...],\n// contentSnapshot: __ZFB_CONTENT_SNAPSHOT__, // embedded by the bundler\n//// });\n//\n// export default { fetch: router };\n//\n// The README documents the bundle shape T6 (embedded V8 host) consumes.\n\nexport type {\n CreatePageRouterOptions,\n PageDefinition,\n PageHeading,\n PageModule,\n PageRouter,\n} from \"./router.js\";\nexport type { ContentSnapshot, EntrySnapshot } from \"./snapshot.js\";\nexport { ViewTransitions, type ViewTransitionsElement } from \"./view-transitions.js\";\n\n// Client-router public surface (W3D — mirrors @takazudo/zfb-runtime/client-router barrel).\n// See W1B §2 for the full public API spec.\n//\n// ClientRouter is imported from the pure component module, NOT the\n// `./client-router.js` activation shim (#2437): the shim runs `init()` as a\n// module-scope side effect, which would make `import { ClientRouter } from\n// \"@takazudo/zfb-runtime\"` silently activate the router. The pure module\n// performs zero side effects, so the root barrel stays side-effect-free.\n// `import \"@takazudo/zfb-runtime/client-router\"` (the shim's subpath) remains\n// the byte-compatible activation entry point — see client-router.ts.\nexport { ClientRouter, type ClientRouterProps } from \"./client-router-component.js\";\nexport {\n navigate,\n supportsViewTransitions,\n transitionEnabledOnThisPage,\n syncHistoryEntry,\n} from \"./client-router/router.js\";\n\n// Prefetch public surface (#276).\n// `init` from prefetch.ts is re-exported as `prefetchInit` to avoid collision\n// with the router's `init`.\nexport { prefetch, init as prefetchInit } from \"./client-router/prefetch.js\";\nexport type {\n PrefetchStrategy,\n PrefetchInitOptions,\n PrefetchOptions,\n} from \"./client-router/prefetch.js\";\nexport {\n TRANSITION_BEFORE_PREPARATION,\n TRANSITION_AFTER_PREPARATION,\n TRANSITION_BEFORE_SWAP,\n TRANSITION_AFTER_SWAP,\n TRANSITION_PAGE_LOAD,\n TRANSITION_NAVIGATION_ABORTED,\n TransitionBeforePreparationEvent,\n TransitionBeforeSwapEvent,\n isTransitionBeforePreparationEvent,\n isTransitionBeforeSwapEvent,\n} from \"./client-router/events.js\";\nexport { swapFunctions, swap } from \"./client-router/swap-functions.js\";\nexport type {\n Direction,\n Fallback,\n NavigationTypeString,\n Options,\n SyncHistoryEntryOptions,\n} from \"./client-router/types.js\";\n\n// Plugin lifecycle types (#255). The runtime package re-exports the\n// `@takazudo/zfb/plugins` surface so consumers writing plugins from a\n// project that only depends on `@takazudo/zfb-runtime` can still\n// import the canonical types (e.g. `import type { ZfbPlugin } from\n// \"@takazudo/zfb-runtime\"`).\nexport type {\n ZfbPlugin,\n ZfbPluginLogger,\n ZfbBuildHookContext,\n ZfbDevMiddlewareContext,\n ZfbDevMiddlewareHandler,\n ZfbDevMiddlewareRequest,\n ZfbDevMiddlewareResponse,\n ZfbRouteEntry,\n ZfbRouteManifest,\n ZfbSetupContext,\n ZfbVirtualModuleLoader,\n} from \"@takazudo/zfb/plugins\";\n"]}
package/dist/router.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { FrameworkAdapter } from "./framework.js";
2
1
  import type { ContentSnapshot } from "./snapshot.js";
3
2
  /**
4
3
  * Heading metadata emitted by the MDX `headings` export (T4). Cross-ref:
@@ -16,7 +15,7 @@ export interface PageHeading {
16
15
  * - `default`: the JSX page component. Called with the props returned by
17
16
  * `getStaticProps` (if exported) or the `props` from the matching
18
17
  * `paths()` entry (for dynamic routes). The return value is fed straight
19
- * to the framework adapter's `renderToString`. A dynamic route with no
18
+ * to the owned server renderer's `renderToString`. A dynamic route with no
20
19
  * `paths()` export is called with `{ params: urlParams }` instead of
21
20
  * `{}` — see `createPageRouter`'s JSDoc for the full componentInput
22
21
  * derivation table.
@@ -76,8 +75,6 @@ export interface CreatePageRouterOptions {
76
75
  * `getCollection(...)` resolve synchronously from memory.
77
76
  */
78
77
  readonly contentSnapshot: ContentSnapshot;
79
- /** Framework adapter pinning the SSR call. */
80
- readonly framework: FrameworkAdapter;
81
78
  /**
82
79
  * When `true`, the 500 body for render errors includes the full JS stack
83
80
  * trace. When `false`, only the message + route are included. When
@@ -93,7 +90,7 @@ export interface CreatePageRouterOptions {
93
90
  /**
94
91
  * Fetch-handler shape returned by [`createPageRouter`]. Shaped as a plain
95
92
  * function (not a Hono `app`) so the consumer's contract is exactly
96
- * "Worker-style fetch handler" with no leaked framework types.
93
+ * "Worker-style fetch handler" with no leaked renderer types.
97
94
  */
98
95
  export type PageRouter = (request: Request) => Promise<Response>;
99
96
  /**
@@ -110,7 +107,7 @@ export type PageRouter = (request: Request) => Promise<Response>;
110
107
  * under `pages/api/` — actually reaches its handler instead of being
111
108
  * 404'd by the inner router. See the comment at the `app.all` call
112
109
  * site below. The handler imports the page module, calls
113
- * `framework.renderToString(module.default(componentInput))`, and
110
+ * `renderToString(module.default(componentInput))`, and
114
111
  * returns the string in a `Response` with the appropriate
115
112
  * `Content-Type`. (`componentInput` is the page's props object — see
116
113
  * the derivation table below; it is never the incoming `Request`.)
package/dist/router.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // user pages/ + content/ + layouts/ + components/
7
7
  // → esbuild bundle (T3) // single ESM file
8
8
  // → embedded V8 host (T6) // same WinterCG surface as CF Workers
9
- // → createPageRouter({ pages, contentSnapshot, framework })
9
+ // → createPageRouter({ pages, contentSnapshot })
10
10
  // → (request) => Promise<Response>
11
11
  //
12
12
  // The bundle's Worker entry point shape is documented in the package
@@ -39,6 +39,7 @@
39
39
  // that hand-build `pages` and therefore bypass the scanner.
40
40
  import { Hono } from "hono";
41
41
  import { setContentSnapshot } from "@takazudo/zfb/content";
42
+ import { renderToString } from "@takazudo/zfb/zudo-react/server";
42
43
  // ---------------------------------------------------------------------------
43
44
  // Implementation
44
45
  // ---------------------------------------------------------------------------
@@ -113,7 +114,7 @@ async function getOrEvalPaths(memo, routeKey, pathsFn) {
113
114
  * under `pages/api/` — actually reaches its handler instead of being
114
115
  * 404'd by the inner router. See the comment at the `app.all` call
115
116
  * site below. The handler imports the page module, calls
116
- * `framework.renderToString(module.default(componentInput))`, and
117
+ * `renderToString(module.default(componentInput))`, and
117
118
  * returns the string in a `Response` with the appropriate
118
119
  * `Content-Type`. (`componentInput` is the page's props object — see
119
120
  * the derivation table below; it is never the incoming `Request`.)
@@ -317,7 +318,7 @@ export function createPageRouter(opts) {
317
318
  if (!match) {
318
319
  // The URL params do not correspond to any paths() entry —
319
320
  // this is the dev-mode equivalent of a build-time miss.
320
- // Hono's `c.notFound()` returns the framework's default 404,
321
+ // Hono's `c.notFound()` returns Hono's default 404,
321
322
  // which is cleaner than fabricating an empty-props render.
322
323
  return c.notFound();
323
324
  }
@@ -377,7 +378,7 @@ export function createPageRouter(opts) {
377
378
  // directly (e.g. `pages/api/*.tsx` handlers that use Web Fetch
378
379
  // primitives instead of returning JSX) is responsible for its own
379
380
  // status, headers, and body — return it as-is rather than running
380
- // it through the framework SSR path. A `return` from inside a
381
+ // it through the owned SSR renderer. A `return` from inside a
381
382
  // `try` does not trigger the `catch`, so this passes through
382
383
  // correctly without special-casing.
383
384
  if (result instanceof Response) {
@@ -385,11 +386,11 @@ export function createPageRouter(opts) {
385
386
  }
386
387
  // Non-HTML routes (e.g. `sitemap.xml.tsx`, `feed.xml.tsx`) commonly
387
388
  // return their body as a pre-serialised `string` instead of a
388
- // VNode. Routing those through `framework.renderToString` would
389
+ // VNode. Routing those through `renderToString` would
389
390
  // HTML-escape the angle brackets and ampersands, producing
390
391
  // garbage XML. Pass strings through verbatim; only wrap actual
391
392
  // VNodes.
392
- const html = typeof result === "string" ? result : opts.framework.renderToString(result);
393
+ const html = typeof result === "string" ? result : renderToString(result);
393
394
  const contentType = mod.contentType ?? DEFAULT_CONTENT_TYPE;
394
395
  return c.body(ensureHtml5Doctype(html, contentType), 200, { "Content-Type": contentType });
395
396
  }