@streetui/dom 1.6.0 → 1.7.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.
- package/dist/index.cjs +121 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -3
- package/dist/index.d.ts +92 -3
- package/dist/index.js +112 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,16 +26,24 @@ __export(index_exports, {
|
|
|
26
26
|
ServerDOMAdapter: () => ServerDOMAdapter,
|
|
27
27
|
ServerElement: () => ServerElement,
|
|
28
28
|
ServerFragment: () => ServerFragment,
|
|
29
|
+
ServerRawHTML: () => ServerRawHTML,
|
|
29
30
|
ServerStyle: () => ServerStyle,
|
|
30
31
|
ServerText: () => ServerText,
|
|
31
32
|
browserDOMAdapter: () => browserDOMAdapter,
|
|
33
|
+
containFocus: () => containFocus,
|
|
32
34
|
escapeHtmlAttr: () => escapeHtmlAttr,
|
|
33
35
|
escapeHtmlText: () => escapeHtmlText,
|
|
34
36
|
focusById: () => focusById,
|
|
35
37
|
focusFirst: () => focusFirst,
|
|
38
|
+
focusInitial: () => focusInitial,
|
|
39
|
+
getFocusable: () => getFocusable,
|
|
40
|
+
onEscape: () => onEscape,
|
|
41
|
+
restoreFocus: () => restoreFocus,
|
|
42
|
+
saveFocus: () => saveFocus,
|
|
36
43
|
serializeChildren: () => serializeChildren,
|
|
37
44
|
serializeServerNode: () => serializeServerNode,
|
|
38
|
-
serverDOMAdapter: () => serverDOMAdapter
|
|
45
|
+
serverDOMAdapter: () => serverDOMAdapter,
|
|
46
|
+
trapFocus: () => trapFocus
|
|
39
47
|
});
|
|
40
48
|
module.exports = __toCommonJS(index_exports);
|
|
41
49
|
|
|
@@ -104,6 +112,18 @@ var BrowserDOMAdapter = class {
|
|
|
104
112
|
focus(element) {
|
|
105
113
|
element.focus?.();
|
|
106
114
|
}
|
|
115
|
+
body() {
|
|
116
|
+
return document.body ?? null;
|
|
117
|
+
}
|
|
118
|
+
activeElement() {
|
|
119
|
+
return document.activeElement ?? null;
|
|
120
|
+
}
|
|
121
|
+
contains(ancestor, node) {
|
|
122
|
+
return ancestor.contains(node);
|
|
123
|
+
}
|
|
124
|
+
matches(element, selector) {
|
|
125
|
+
return typeof element.matches === "function" && element.matches(selector);
|
|
126
|
+
}
|
|
107
127
|
isElement(node) {
|
|
108
128
|
return node.nodeType === Node.ELEMENT_NODE;
|
|
109
129
|
}
|
|
@@ -162,6 +182,14 @@ var ServerFragment = class {
|
|
|
162
182
|
parent = null;
|
|
163
183
|
children = [];
|
|
164
184
|
};
|
|
185
|
+
var ServerRawHTML = class {
|
|
186
|
+
kind = "raw";
|
|
187
|
+
parent = null;
|
|
188
|
+
html;
|
|
189
|
+
constructor(html) {
|
|
190
|
+
this.html = html;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
165
193
|
var ServerElement = class {
|
|
166
194
|
kind = "element";
|
|
167
195
|
parent = null;
|
|
@@ -312,6 +340,8 @@ function serializeServerNode(node) {
|
|
|
312
340
|
return `<!--${node.data}-->`;
|
|
313
341
|
case "fragment":
|
|
314
342
|
return serializeChildren(node);
|
|
343
|
+
case "raw":
|
|
344
|
+
return node.html;
|
|
315
345
|
case "element": {
|
|
316
346
|
const el = node;
|
|
317
347
|
const tag = el.tagName;
|
|
@@ -351,6 +381,15 @@ var ServerDOMAdapter = class {
|
|
|
351
381
|
createFragment() {
|
|
352
382
|
return new ServerFragment();
|
|
353
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).
|
|
386
|
+
* Server-only: the browser adapter does not implement this, and the renderer
|
|
387
|
+
* fast path only invokes it when a static SSR plan is present (SSR). The
|
|
388
|
+
* stored HTML was produced by this same serializer, so it is emitted as-is.
|
|
389
|
+
*/
|
|
390
|
+
createRawHTML(html) {
|
|
391
|
+
return new ServerRawHTML(html);
|
|
392
|
+
}
|
|
354
393
|
appendChild(parent, child) {
|
|
355
394
|
const p = asParent(parent);
|
|
356
395
|
const c = asServer(child);
|
|
@@ -452,6 +491,18 @@ var ServerDOMAdapter = class {
|
|
|
452
491
|
}
|
|
453
492
|
focus() {
|
|
454
493
|
}
|
|
494
|
+
body() {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
activeElement() {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
contains(_ancestor, _node) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
matches(_element, _selector) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
455
506
|
isElement(node) {
|
|
456
507
|
return asServer(node).kind === "element";
|
|
457
508
|
}
|
|
@@ -517,6 +568,66 @@ function focusFirst(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
|
517
568
|
dom.focus(el);
|
|
518
569
|
return true;
|
|
519
570
|
}
|
|
571
|
+
function getFocusable(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
572
|
+
return Array.from(dom.querySelectorAll(container, selector)).filter(
|
|
573
|
+
(el) => dom.matches(el, selector)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
function saveFocus(dom) {
|
|
577
|
+
return dom.activeElement();
|
|
578
|
+
}
|
|
579
|
+
function restoreFocus(dom, saved) {
|
|
580
|
+
if (saved !== null) dom.focus(saved);
|
|
581
|
+
}
|
|
582
|
+
function focusInitial(dom, container, initialFocusId) {
|
|
583
|
+
if (initialFocusId !== void 0 && focusById(dom, container, initialFocusId)) return;
|
|
584
|
+
focusFirst(dom, container);
|
|
585
|
+
}
|
|
586
|
+
function trapFocus(dom, container) {
|
|
587
|
+
const onKeydown = (event) => {
|
|
588
|
+
if (event.key !== "Tab") return;
|
|
589
|
+
const items = getFocusable(dom, container);
|
|
590
|
+
if (items.length === 0) {
|
|
591
|
+
event.preventDefault();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const first = items[0];
|
|
595
|
+
const last = items[items.length - 1];
|
|
596
|
+
const active = dom.activeElement();
|
|
597
|
+
if (active === null || !dom.contains(container, active)) {
|
|
598
|
+
event.preventDefault();
|
|
599
|
+
dom.focus(first);
|
|
600
|
+
} else if (event.shiftKey && active === first) {
|
|
601
|
+
event.preventDefault();
|
|
602
|
+
dom.focus(last);
|
|
603
|
+
} else if (!event.shiftKey && active === last) {
|
|
604
|
+
event.preventDefault();
|
|
605
|
+
dom.focus(first);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
609
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
610
|
+
}
|
|
611
|
+
function containFocus(dom, container) {
|
|
612
|
+
const body = dom.body();
|
|
613
|
+
if (body === null) return () => {
|
|
614
|
+
};
|
|
615
|
+
const onFocusIn = (event) => {
|
|
616
|
+
const target = event.target;
|
|
617
|
+
if (target !== null && !dom.contains(container, target)) {
|
|
618
|
+
focusFirst(dom, container);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
dom.addEventListener(body, "focusin", onFocusIn);
|
|
622
|
+
return () => dom.removeEventListener(body, "focusin", onFocusIn);
|
|
623
|
+
}
|
|
624
|
+
function onEscape(dom, target, handler) {
|
|
625
|
+
const onKeydown = (event) => {
|
|
626
|
+
if (event.key === "Escape") handler();
|
|
627
|
+
};
|
|
628
|
+
dom.addEventListener(target, "keydown", onKeydown);
|
|
629
|
+
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
630
|
+
}
|
|
520
631
|
// Annotate the CommonJS export names for ESM import in node:
|
|
521
632
|
0 && (module.exports = {
|
|
522
633
|
BrowserDOMAdapter,
|
|
@@ -525,15 +636,23 @@ function focusFirst(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
|
525
636
|
ServerDOMAdapter,
|
|
526
637
|
ServerElement,
|
|
527
638
|
ServerFragment,
|
|
639
|
+
ServerRawHTML,
|
|
528
640
|
ServerStyle,
|
|
529
641
|
ServerText,
|
|
530
642
|
browserDOMAdapter,
|
|
643
|
+
containFocus,
|
|
531
644
|
escapeHtmlAttr,
|
|
532
645
|
escapeHtmlText,
|
|
533
646
|
focusById,
|
|
534
647
|
focusFirst,
|
|
648
|
+
focusInitial,
|
|
649
|
+
getFocusable,
|
|
650
|
+
onEscape,
|
|
651
|
+
restoreFocus,
|
|
652
|
+
saveFocus,
|
|
535
653
|
serializeChildren,
|
|
536
654
|
serializeServerNode,
|
|
537
|
-
serverDOMAdapter
|
|
655
|
+
serverDOMAdapter,
|
|
656
|
+
trapFocus
|
|
538
657
|
});
|
|
539
658
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/browser-adapter.ts","../src/server-node.ts","../src/server-adapter.ts","../src/focus.ts"],"sourcesContent":["export * from './adapter.js';\nexport * from './browser-adapter.js';\nexport * from './server-node.js';\nexport * from './server-adapter.js';\nexport * from './focus.js';\n","/**\n * Browser implementation of DOMAdapter — delegates directly to browser APIs.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\nexport class BrowserDOMAdapter implements DOMAdapter {\n createElement(tag: string, ns?: string): Element {\n if (ns !== undefined) {\n return document.createElementNS(ns, tag);\n }\n return document.createElement(tag);\n }\n\n createTextNode(data: string): Text {\n return document.createTextNode(data);\n }\n\n createComment(data: string): Comment {\n return document.createComment(data);\n }\n\n createFragment(): DocumentFragment {\n return document.createDocumentFragment();\n }\n\n appendChild(parent: Node, child: Node): void {\n parent.appendChild(child);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n parent.insertBefore(child, reference);\n }\n\n removeChild(parent: Node, child: Node): void {\n parent.removeChild(child);\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n parent.replaceChild(newChild, oldChild);\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n element.setAttribute(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n element.removeAttribute(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return element.getAttribute(name);\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as Record<string, unknown>)[name] = value;\n }\n\n setTextContent(node: Node, text: string): void {\n node.textContent = text;\n }\n\n getTextContent(node: Node): string | null {\n return node.textContent;\n }\n\n addEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ): void {\n target.addEventListener(type, handler, options);\n }\n\n removeEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: EventListenerOptions,\n ): void {\n target.removeEventListener(type, handler, options);\n }\n\n querySelector(root: Element | Document, selector: string): Element | null {\n return root.querySelector(selector);\n }\n\n querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element> {\n return root.querySelectorAll(selector);\n }\n\n getElementById(id: string): Element | null {\n return document.getElementById(id);\n }\n\n focus(element: Element): void {\n (element as unknown as { focus?: () => void }).focus?.();\n }\n\n isElement(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n\n isTextNode(node: Node): node is Text {\n return node.nodeType === Node.TEXT_NODE;\n }\n\n tagName(element: Element): string {\n return element.tagName.toLowerCase();\n }\n\n parentNode(node: Node): Node | null {\n return node.parentNode;\n }\n\n nextSibling(node: Node): Node | null {\n return node.nextSibling;\n }\n\n firstChild(node: Node): Node | null {\n return node.firstChild;\n }\n\n childNodes(node: Node): Node[] {\n return Array.from(node.childNodes);\n }\n}\n\n// `/* @__PURE__ */`: convenience singleton, unreferenced by internal runtime\n// paths. Marking construction pure lets bundlers drop it when unused instead of\n// retaining it (and the BrowserDOMAdapter class) as an import-time side effect.\nexport const browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();\n","/**\n * Server-side DOM node model.\n *\n * A tiny, dependency-free tree of plain objects that mirrors just enough of the\n * browser DOM for StreetUI's renderer to build a tree on the server and\n * serialize it to an HTML string. There is NO browser global here — these are\n * ordinary classes usable in any JavaScript environment (Node, workers, tests).\n *\n * The renderer never touches these types directly; it goes through the\n * `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into\n * operations on this model.\n */\n\nexport type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment';\n\nexport interface ServerNode {\n readonly kind: ServerNodeKind;\n parent: ServerParent | null;\n}\n\nexport type ServerParent = ServerElement | ServerFragment;\n\n/** A minimal inline-style holder mirroring `element.style.setProperty`. */\nexport class ServerStyle {\n readonly declarations = new Map<string, string>();\n setProperty(name: string, value: string): void {\n this.declarations.set(name, value);\n }\n get isEmpty(): boolean {\n return this.declarations.size === 0;\n }\n toCss(): string {\n return [...this.declarations.entries()].map(([k, v]) => `${k}: ${v}`).join('; ');\n }\n}\n\nexport class ServerText implements ServerNode {\n readonly kind = 'text' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerComment implements ServerNode {\n readonly kind = 'comment' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerFragment implements ServerNode {\n readonly kind = 'fragment' as const;\n parent: ServerParent | null = null;\n readonly children: ServerNode[] = [];\n}\n\nexport class ServerElement implements ServerNode {\n readonly kind = 'element' as const;\n parent: ServerParent | null = null;\n readonly tagName: string;\n readonly attributes = new Map<string, string>();\n readonly children: ServerNode[] = [];\n\n // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS\n // properties or inline styles (measured, §5: 1 of 80,029 elements uses\n // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus\n // a `ServerStyle` (which itself holds a Map) per element wasted ~240k\n // allocations per /users render — all in the dominant mount phase. These are\n // created on first WRITE via the `properties`/`style` getters; the serializer\n // reads the raw `_properties`/`_style` fields so a READ never forces an\n // allocation. Output is byte-identical: an unset store previously serialized\n // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store\n // is skipped the same way.\n _properties: Map<string, unknown> | null = null;\n _style: ServerStyle | null = null;\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\n }\n\n /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */\n get properties(): Map<string, unknown> {\n return (this._properties ??= new Map());\n }\n\n /** Inline-style holder mirroring `element.style`. Allocated on first access. */\n get style(): ServerStyle {\n return (this._style ??= new ServerStyle());\n }\n}\n\n// ── HTML serialization ─────────────────────────────────────────────────────────\n\n/**\n * HTML \"void\" elements — self-closing, never given a closing tag or children.\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Element properties that should be reflected into the serialized HTML so the\n * hydrated DOM carries the same initial state. `value`/`checked` matter for\n * form controls whose live state is a JS property, not an attribute.\n */\nconst SERIALIZED_PROPERTIES: Record<string, 'attr' | 'boolean'> = {\n value: 'attr',\n checked: 'boolean',\n selected: 'boolean',\n};\n\n/**\n * Precomputed `[name, kind]` pairs of SERIALIZED_PROPERTIES. Hoisted to module\n * scope so `serializeAttributes` does not allocate a fresh entries array for\n * every element serialized (measured hot: ~80k elements on the 10k-row route).\n */\nconst SERIALIZED_PROPERTY_ENTRIES: ReadonlyArray<readonly [string, 'attr' | 'boolean']> =\n Object.entries(SERIALIZED_PROPERTIES) as Array<[string, 'attr' | 'boolean']>;\n\n// Fast-path escaping. The chained `.replace(/…/g, …)` form makes 3–4 full\n// passes and allocates an intermediate string per pass even when nothing needs\n// escaping. These variants scan once and, in the overwhelmingly common case of\n// no special character, return the input unchanged (zero allocation). Output is\n// byte-identical to the chained form (verified over the real SSR corpus).\nconst TEXT_SPECIAL = /[&<>]/;\nconst ATTR_SPECIAL = /[&<>\"]/;\n\n/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n if (!TEXT_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n if (!ATTR_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n case 34: esc = '"'; break; // \"\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\nfunction serializeAttributes(el: ServerElement): string {\n const parts: string[] = [];\n\n for (const [name, value] of el.attributes) {\n if (value === '') {\n parts.push(` ${name}`);\n } else {\n parts.push(` ${name}=\"${escapeHtmlAttr(value)}\"`);\n }\n }\n\n // Read the raw backing field (may be null): most elements have no JS\n // properties, so skipping the whole loop avoids touching a store that was\n // never allocated (§5).\n const props = el._properties;\n if (props !== null) {\n for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {\n if (!props.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = props.get(name);\n if (kind === 'boolean') {\n if (raw === true) parts.push(` ${name}`);\n } else {\n if (raw !== undefined && raw !== null) {\n parts.push(` ${name}=\"${escapeHtmlAttr(String(raw))}\"`);\n }\n }\n }\n }\n\n const style = el._style;\n if (style !== null && !style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(style.toCss())}\"`);\n }\n\n return parts.join('');\n}\n\n/** Serialize a single server node (element/text/comment/fragment) to HTML. */\nexport function serializeServerNode(node: ServerNode): string {\n switch (node.kind) {\n case 'text':\n return escapeHtmlText((node as ServerText).data);\n case 'comment':\n return `<!--${(node as ServerComment).data}-->`;\n case 'fragment':\n return serializeChildren(node as ServerFragment);\n case 'element': {\n const el = node as ServerElement;\n const tag = el.tagName;\n const attrs = serializeAttributes(el);\n if (VOID_ELEMENTS.has(tag)) {\n return `<${tag}${attrs}>`;\n }\n return `<${tag}${attrs}>${serializeChildren(el)}</${tag}>`;\n }\n }\n}\n\n/** Serialize the children of an element or fragment (its \"inner HTML\"). */\nexport function serializeChildren(node: ServerElement | ServerFragment): string {\n let out = '';\n for (const child of node.children) {\n out += serializeServerNode(child);\n }\n return out;\n}\n","/**\n * Server implementation of `DOMAdapter`.\n *\n * Builds a lightweight in-memory tree (see `server-node.ts`) instead of touching\n * a real browser DOM, then lets the caller serialize it to an HTML string. It is\n * completely free of browser globals, so the exact same renderer that runs in\n * the browser can produce HTML on the server.\n *\n * The `DOMAdapter` interface is typed against the lib DOM types (`Element`,\n * `Node`, `Text`, …). Our server nodes structurally stand in for those at\n * runtime, so the boundary uses `as unknown as` casts in one place. Everything\n * inside operates on the real server-node model.\n */\n\nimport type { DOMAdapter } from './adapter.js';\nimport {\n ServerElement,\n ServerText,\n ServerComment,\n ServerFragment,\n serializeChildren,\n serializeServerNode,\n type ServerNode,\n type ServerParent,\n} from './server-node.js';\n\nfunction asServer(node: unknown): ServerNode {\n return node as unknown as ServerNode;\n}\nfunction asParent(node: unknown): ServerParent {\n return node as unknown as ServerParent;\n}\n\nexport class ServerDOMAdapter implements DOMAdapter {\n createElement(tag: string, _ns?: string): Element {\n return new ServerElement(tag) as unknown as Element;\n }\n\n createTextNode(data: string): Text {\n return new ServerText(data) as unknown as Text;\n }\n\n createComment(data: string): Comment {\n return new ServerComment(data) as unknown as Comment;\n }\n\n createFragment(): DocumentFragment {\n return new ServerFragment() as unknown as DocumentFragment;\n }\n\n appendChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n p.children.push(c);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n if (reference === null) {\n p.children.push(c);\n return;\n }\n const ref = asServer(reference);\n const idx = p.children.indexOf(ref);\n if (idx === -1) p.children.push(c);\n else p.children.splice(idx, 0, c);\n }\n\n removeChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n const idx = p.children.indexOf(c);\n if (idx !== -1) {\n p.children.splice(idx, 1);\n c.parent = null;\n }\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n const p = asParent(parent);\n const nc = asServer(newChild);\n const oc = asServer(oldChild);\n const idx = p.children.indexOf(oc);\n if (idx === -1) return;\n this._detach(nc);\n nc.parent = p;\n p.children.splice(idx, 1, nc);\n oc.parent = null;\n }\n\n private _detach(node: ServerNode): void {\n if (node.parent !== null) {\n const siblings = node.parent.children;\n const idx = siblings.indexOf(node);\n if (idx !== -1) siblings.splice(idx, 1);\n node.parent = null;\n }\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n (element as unknown as ServerElement).attributes.set(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n (element as unknown as ServerElement).attributes.delete(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return (element as unknown as ServerElement).attributes.get(name) ?? null;\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as ServerElement).properties.set(name, value);\n }\n\n setTextContent(node: Node, text: string): void {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n el.children.length = 0;\n const t = new ServerText(text);\n t.parent = el;\n el.children.push(t);\n } else if (n.kind === 'text') {\n (n as ServerText).data = text;\n }\n }\n\n getTextContent(node: Node): string | null {\n const n = asServer(node);\n if (n.kind === 'text') return (n as ServerText).data;\n if (n.kind === 'element' || n.kind === 'fragment') {\n let out = '';\n for (const c of (n as ServerElement | ServerFragment).children) {\n out += this.getTextContent(c as unknown as Node) ?? '';\n }\n return out;\n }\n return null;\n }\n\n // Server nodes never dispatch events — listeners are a no-op on the server.\n addEventListener(): void {\n /* no-op on the server */\n }\n removeEventListener(): void {\n /* no-op on the server */\n }\n\n querySelector(): Element | null {\n return null;\n }\n querySelectorAll(): NodeListOf<Element> {\n return [] as unknown as NodeListOf<Element>;\n }\n getElementById(): Element | null {\n return null;\n }\n\n focus(): void {\n // No focus concept on the server — intentional no-op (SSR-safe).\n }\n\n isElement(node: Node): node is Element {\n return asServer(node).kind === 'element';\n }\n\n isTextNode(node: Node): node is Text {\n return asServer(node).kind === 'text';\n }\n\n tagName(element: Element): string {\n return (element as unknown as ServerElement).tagName;\n }\n\n parentNode(node: Node): Node | null {\n return (asServer(node).parent as unknown as Node | null) ?? null;\n }\n\n nextSibling(node: Node): Node | null {\n const n = asServer(node);\n const parent = n.parent;\n if (parent === null) return null;\n const idx = parent.children.indexOf(n);\n if (idx === -1 || idx + 1 >= parent.children.length) return null;\n return parent.children[idx + 1] as unknown as Node;\n }\n\n firstChild(node: Node): Node | null {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n return (el.children[0] as unknown as Node) ?? null;\n }\n return null;\n }\n\n childNodes(node: Node): Node[] {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return (n as ServerElement | ServerFragment).children as unknown as Node[];\n }\n return [];\n }\n\n // ── Server-only ────────────────────────────────────────────────────────────\n\n /** Serialize a node's children (\"inner HTML\") to an HTML string. */\n serializeInner(node: Node): string {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return serializeChildren(n as ServerElement | ServerFragment);\n }\n return '';\n }\n\n /** Serialize a node (including itself) to an HTML string. */\n serializeOuter(node: Node): string {\n return serializeServerNode(asServer(node));\n }\n}\n\n// `/* @__PURE__ */`: this singleton is a convenience export only (no internal\n// runtime path references it). Marking construction pure lets bundlers drop it —\n// and with it the whole server serializer chain (serializeServerNode/escape/\n// VOID_ELEMENTS) — out of client bundles that never import SSR. Without this,\n// the un-annotated `new` is treated as a side effect and retained everywhere.\nexport const serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();\n","/**\n * Focus helpers built on the {@link DOMAdapter} abstraction.\n *\n * These are the minimal, genuinely-useful focus operations an app needs:\n * focus a specific element (e.g. the first field when a route or modal opens)\n * or focus the first focusable element inside a container (e.g. move focus\n * into a dialog). Both go through the adapter, so they are no-ops on the server\n * (`ServerDOMAdapter.querySelector` returns null / `focus` does nothing) and\n * therefore safe to call from universal code.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\n/** Default selector for natively focusable / tabbable elements. */\nexport const FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Focus the element with the given id, scoped to `root`.\n * Returns true if an element was found and focused.\n */\nexport function focusById(dom: DOMAdapter, root: Element | Document, id: string): boolean {\n const el = dom.querySelector(root, `[id=\"${id}\"]`);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Focus the first focusable element inside `container`.\n * Returns true if a focusable element was found and focused.\n */\nexport function focusFirst(\n dom: DOMAdapter,\n container: Element | Document,\n selector: string = FOCUSABLE_SELECTOR,\n): boolean {\n const el = dom.querySelector(container, selector);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser-adapter.ts","../src/server-node.ts","../src/server-adapter.ts","../src/focus.ts"],"sourcesContent":["export * from './adapter.js';\nexport * from './browser-adapter.js';\nexport * from './server-node.js';\nexport * from './server-adapter.js';\nexport * from './focus.js';\n","/**\n * Browser implementation of DOMAdapter — delegates directly to browser APIs.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\nexport class BrowserDOMAdapter implements DOMAdapter {\n createElement(tag: string, ns?: string): Element {\n if (ns !== undefined) {\n return document.createElementNS(ns, tag);\n }\n return document.createElement(tag);\n }\n\n createTextNode(data: string): Text {\n return document.createTextNode(data);\n }\n\n createComment(data: string): Comment {\n return document.createComment(data);\n }\n\n createFragment(): DocumentFragment {\n return document.createDocumentFragment();\n }\n\n appendChild(parent: Node, child: Node): void {\n parent.appendChild(child);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n parent.insertBefore(child, reference);\n }\n\n removeChild(parent: Node, child: Node): void {\n parent.removeChild(child);\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n parent.replaceChild(newChild, oldChild);\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n element.setAttribute(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n element.removeAttribute(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return element.getAttribute(name);\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as Record<string, unknown>)[name] = value;\n }\n\n setTextContent(node: Node, text: string): void {\n node.textContent = text;\n }\n\n getTextContent(node: Node): string | null {\n return node.textContent;\n }\n\n addEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ): void {\n target.addEventListener(type, handler, options);\n }\n\n removeEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: EventListenerOptions,\n ): void {\n target.removeEventListener(type, handler, options);\n }\n\n querySelector(root: Element | Document, selector: string): Element | null {\n return root.querySelector(selector);\n }\n\n querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element> {\n return root.querySelectorAll(selector);\n }\n\n getElementById(id: string): Element | null {\n return document.getElementById(id);\n }\n\n focus(element: Element): void {\n (element as unknown as { focus?: () => void }).focus?.();\n }\n\n body(): Element | null {\n return document.body ?? null;\n }\n\n activeElement(): Element | null {\n return document.activeElement ?? null;\n }\n\n contains(ancestor: Element, node: Node): boolean {\n return ancestor.contains(node);\n }\n\n matches(element: Element, selector: string): boolean {\n return typeof element.matches === 'function' && element.matches(selector);\n }\n\n isElement(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n\n isTextNode(node: Node): node is Text {\n return node.nodeType === Node.TEXT_NODE;\n }\n\n tagName(element: Element): string {\n return element.tagName.toLowerCase();\n }\n\n parentNode(node: Node): Node | null {\n return node.parentNode;\n }\n\n nextSibling(node: Node): Node | null {\n return node.nextSibling;\n }\n\n firstChild(node: Node): Node | null {\n return node.firstChild;\n }\n\n childNodes(node: Node): Node[] {\n return Array.from(node.childNodes);\n }\n}\n\n// `/* @__PURE__ */`: convenience singleton, unreferenced by internal runtime\n// paths. Marking construction pure lets bundlers drop it when unused instead of\n// retaining it (and the BrowserDOMAdapter class) as an import-time side effect.\nexport const browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();\n","/**\n * Server-side DOM node model.\n *\n * A tiny, dependency-free tree of plain objects that mirrors just enough of the\n * browser DOM for StreetUI's renderer to build a tree on the server and\n * serialize it to an HTML string. There is NO browser global here — these are\n * ordinary classes usable in any JavaScript environment (Node, workers, tests).\n *\n * The renderer never touches these types directly; it goes through the\n * `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into\n * operations on this model.\n */\n\nexport type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment' | 'raw';\n\nexport interface ServerNode {\n readonly kind: ServerNodeKind;\n parent: ServerParent | null;\n}\n\nexport type ServerParent = ServerElement | ServerFragment;\n\n/** A minimal inline-style holder mirroring `element.style.setProperty`. */\nexport class ServerStyle {\n readonly declarations = new Map<string, string>();\n setProperty(name: string, value: string): void {\n this.declarations.set(name, value);\n }\n get isEmpty(): boolean {\n return this.declarations.size === 0;\n }\n toCss(): string {\n return [...this.declarations.entries()].map(([k, v]) => `${k}: ${v}`).join('; ');\n }\n}\n\nexport class ServerText implements ServerNode {\n readonly kind = 'text' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerComment implements ServerNode {\n readonly kind = 'comment' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerFragment implements ServerNode {\n readonly kind = 'fragment' as const;\n parent: ServerParent | null = null;\n readonly children: ServerNode[] = [];\n}\n\n/**\n * A pre-serialized, verbatim HTML fragment (v1.7 static SSR plan).\n *\n * Emitted for provably-static subtrees whose HTML the compiler-derived static\n * SSR plan already computed once. Serializing this node copies its stored\n * string directly — it allocates no ServerElement/ServerText, no attribute Map\n * and no children array for the collapsed subtree. The stored `html` is\n * produced by the exact same mount + serialize pipeline as the runtime path, so\n * the output is byte-identical (the v1.7 byte-identity gate proves this).\n *\n * This node is SSR-only: it is created solely via `ServerDOMAdapter.createRawHTML`\n * on the server render path and never appears in a browser build.\n */\nexport class ServerRawHTML implements ServerNode {\n readonly kind = 'raw' as const;\n parent: ServerParent | null = null;\n readonly html: string;\n constructor(html: string) {\n this.html = html;\n }\n}\n\nexport class ServerElement implements ServerNode {\n readonly kind = 'element' as const;\n parent: ServerParent | null = null;\n readonly tagName: string;\n readonly attributes = new Map<string, string>();\n readonly children: ServerNode[] = [];\n\n // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS\n // properties or inline styles (measured, §5: 1 of 80,029 elements uses\n // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus\n // a `ServerStyle` (which itself holds a Map) per element wasted ~240k\n // allocations per /users render — all in the dominant mount phase. These are\n // created on first WRITE via the `properties`/`style` getters; the serializer\n // reads the raw `_properties`/`_style` fields so a READ never forces an\n // allocation. Output is byte-identical: an unset store previously serialized\n // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store\n // is skipped the same way.\n _properties: Map<string, unknown> | null = null;\n _style: ServerStyle | null = null;\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\n }\n\n /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */\n get properties(): Map<string, unknown> {\n return (this._properties ??= new Map());\n }\n\n /** Inline-style holder mirroring `element.style`. Allocated on first access. */\n get style(): ServerStyle {\n return (this._style ??= new ServerStyle());\n }\n}\n\n// ── HTML serialization ─────────────────────────────────────────────────────────\n\n/**\n * HTML \"void\" elements — self-closing, never given a closing tag or children.\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Element properties that should be reflected into the serialized HTML so the\n * hydrated DOM carries the same initial state. `value`/`checked` matter for\n * form controls whose live state is a JS property, not an attribute.\n */\nconst SERIALIZED_PROPERTIES: Record<string, 'attr' | 'boolean'> = {\n value: 'attr',\n checked: 'boolean',\n selected: 'boolean',\n};\n\n/**\n * Precomputed `[name, kind]` pairs of SERIALIZED_PROPERTIES. Hoisted to module\n * scope so `serializeAttributes` does not allocate a fresh entries array for\n * every element serialized (measured hot: ~80k elements on the 10k-row route).\n */\nconst SERIALIZED_PROPERTY_ENTRIES: ReadonlyArray<readonly [string, 'attr' | 'boolean']> =\n Object.entries(SERIALIZED_PROPERTIES) as Array<[string, 'attr' | 'boolean']>;\n\n// Fast-path escaping. The chained `.replace(/…/g, …)` form makes 3–4 full\n// passes and allocates an intermediate string per pass even when nothing needs\n// escaping. These variants scan once and, in the overwhelmingly common case of\n// no special character, return the input unchanged (zero allocation). Output is\n// byte-identical to the chained form (verified over the real SSR corpus).\nconst TEXT_SPECIAL = /[&<>]/;\nconst ATTR_SPECIAL = /[&<>\"]/;\n\n/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n if (!TEXT_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n if (!ATTR_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n case 34: esc = '"'; break; // \"\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\nfunction serializeAttributes(el: ServerElement): string {\n const parts: string[] = [];\n\n for (const [name, value] of el.attributes) {\n if (value === '') {\n parts.push(` ${name}`);\n } else {\n parts.push(` ${name}=\"${escapeHtmlAttr(value)}\"`);\n }\n }\n\n // Read the raw backing field (may be null): most elements have no JS\n // properties, so skipping the whole loop avoids touching a store that was\n // never allocated (§5).\n const props = el._properties;\n if (props !== null) {\n for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {\n if (!props.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = props.get(name);\n if (kind === 'boolean') {\n if (raw === true) parts.push(` ${name}`);\n } else {\n if (raw !== undefined && raw !== null) {\n parts.push(` ${name}=\"${escapeHtmlAttr(String(raw))}\"`);\n }\n }\n }\n }\n\n const style = el._style;\n if (style !== null && !style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(style.toCss())}\"`);\n }\n\n return parts.join('');\n}\n\n/** Serialize a single server node (element/text/comment/fragment/raw) to HTML. */\nexport function serializeServerNode(node: ServerNode): string {\n switch (node.kind) {\n case 'text':\n return escapeHtmlText((node as ServerText).data);\n case 'comment':\n return `<!--${(node as ServerComment).data}-->`;\n case 'fragment':\n return serializeChildren(node as ServerFragment);\n case 'raw':\n // Verbatim: the string was produced by this same serializer for a static\n // subtree, so it is already correctly escaped. Copy it as-is (§8).\n return (node as ServerRawHTML).html;\n case 'element': {\n const el = node as ServerElement;\n const tag = el.tagName;\n const attrs = serializeAttributes(el);\n if (VOID_ELEMENTS.has(tag)) {\n return `<${tag}${attrs}>`;\n }\n return `<${tag}${attrs}>${serializeChildren(el)}</${tag}>`;\n }\n }\n}\n\n/** Serialize the children of an element or fragment (its \"inner HTML\"). */\nexport function serializeChildren(node: ServerElement | ServerFragment): string {\n let out = '';\n for (const child of node.children) {\n out += serializeServerNode(child);\n }\n return out;\n}\n","/**\n * Server implementation of `DOMAdapter`.\n *\n * Builds a lightweight in-memory tree (see `server-node.ts`) instead of touching\n * a real browser DOM, then lets the caller serialize it to an HTML string. It is\n * completely free of browser globals, so the exact same renderer that runs in\n * the browser can produce HTML on the server.\n *\n * The `DOMAdapter` interface is typed against the lib DOM types (`Element`,\n * `Node`, `Text`, …). Our server nodes structurally stand in for those at\n * runtime, so the boundary uses `as unknown as` casts in one place. Everything\n * inside operates on the real server-node model.\n */\n\nimport type { DOMAdapter } from './adapter.js';\nimport {\n ServerElement,\n ServerText,\n ServerComment,\n ServerFragment,\n ServerRawHTML,\n serializeChildren,\n serializeServerNode,\n type ServerNode,\n type ServerParent,\n} from './server-node.js';\n\nfunction asServer(node: unknown): ServerNode {\n return node as unknown as ServerNode;\n}\nfunction asParent(node: unknown): ServerParent {\n return node as unknown as ServerParent;\n}\n\nexport class ServerDOMAdapter implements DOMAdapter {\n createElement(tag: string, _ns?: string): Element {\n return new ServerElement(tag) as unknown as Element;\n }\n\n createTextNode(data: string): Text {\n return new ServerText(data) as unknown as Text;\n }\n\n createComment(data: string): Comment {\n return new ServerComment(data) as unknown as Comment;\n }\n\n createFragment(): DocumentFragment {\n return new ServerFragment() as unknown as DocumentFragment;\n }\n\n /**\n * Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).\n * Server-only: the browser adapter does not implement this, and the renderer\n * fast path only invokes it when a static SSR plan is present (SSR). The\n * stored HTML was produced by this same serializer, so it is emitted as-is.\n */\n createRawHTML(html: string): Node {\n return new ServerRawHTML(html) as unknown as Node;\n }\n\n appendChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n p.children.push(c);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n if (reference === null) {\n p.children.push(c);\n return;\n }\n const ref = asServer(reference);\n const idx = p.children.indexOf(ref);\n if (idx === -1) p.children.push(c);\n else p.children.splice(idx, 0, c);\n }\n\n removeChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n const idx = p.children.indexOf(c);\n if (idx !== -1) {\n p.children.splice(idx, 1);\n c.parent = null;\n }\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n const p = asParent(parent);\n const nc = asServer(newChild);\n const oc = asServer(oldChild);\n const idx = p.children.indexOf(oc);\n if (idx === -1) return;\n this._detach(nc);\n nc.parent = p;\n p.children.splice(idx, 1, nc);\n oc.parent = null;\n }\n\n private _detach(node: ServerNode): void {\n if (node.parent !== null) {\n const siblings = node.parent.children;\n const idx = siblings.indexOf(node);\n if (idx !== -1) siblings.splice(idx, 1);\n node.parent = null;\n }\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n (element as unknown as ServerElement).attributes.set(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n (element as unknown as ServerElement).attributes.delete(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return (element as unknown as ServerElement).attributes.get(name) ?? null;\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as ServerElement).properties.set(name, value);\n }\n\n setTextContent(node: Node, text: string): void {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n el.children.length = 0;\n const t = new ServerText(text);\n t.parent = el;\n el.children.push(t);\n } else if (n.kind === 'text') {\n (n as ServerText).data = text;\n }\n }\n\n getTextContent(node: Node): string | null {\n const n = asServer(node);\n if (n.kind === 'text') return (n as ServerText).data;\n if (n.kind === 'element' || n.kind === 'fragment') {\n let out = '';\n for (const c of (n as ServerElement | ServerFragment).children) {\n out += this.getTextContent(c as unknown as Node) ?? '';\n }\n return out;\n }\n return null;\n }\n\n // Server nodes never dispatch events — listeners are a no-op on the server.\n addEventListener(): void {\n /* no-op on the server */\n }\n removeEventListener(): void {\n /* no-op on the server */\n }\n\n querySelector(): Element | null {\n return null;\n }\n querySelectorAll(): NodeListOf<Element> {\n return [] as unknown as NodeListOf<Element>;\n }\n getElementById(): Element | null {\n return null;\n }\n\n focus(): void {\n // No focus concept on the server — intentional no-op (SSR-safe).\n }\n\n body(): Element | null {\n // No document on the server — portals degrade to inline rendering.\n return null;\n }\n\n activeElement(): Element | null {\n return null;\n }\n\n contains(_ancestor: Element, _node: Node): boolean {\n return false;\n }\n\n matches(_element: Element, _selector: string): boolean {\n return false;\n }\n\n isElement(node: Node): node is Element {\n return asServer(node).kind === 'element';\n }\n\n isTextNode(node: Node): node is Text {\n return asServer(node).kind === 'text';\n }\n\n tagName(element: Element): string {\n return (element as unknown as ServerElement).tagName;\n }\n\n parentNode(node: Node): Node | null {\n return (asServer(node).parent as unknown as Node | null) ?? null;\n }\n\n nextSibling(node: Node): Node | null {\n const n = asServer(node);\n const parent = n.parent;\n if (parent === null) return null;\n const idx = parent.children.indexOf(n);\n if (idx === -1 || idx + 1 >= parent.children.length) return null;\n return parent.children[idx + 1] as unknown as Node;\n }\n\n firstChild(node: Node): Node | null {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n return (el.children[0] as unknown as Node) ?? null;\n }\n return null;\n }\n\n childNodes(node: Node): Node[] {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return (n as ServerElement | ServerFragment).children as unknown as Node[];\n }\n return [];\n }\n\n // ── Server-only ────────────────────────────────────────────────────────────\n\n /** Serialize a node's children (\"inner HTML\") to an HTML string. */\n serializeInner(node: Node): string {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return serializeChildren(n as ServerElement | ServerFragment);\n }\n return '';\n }\n\n /** Serialize a node (including itself) to an HTML string. */\n serializeOuter(node: Node): string {\n return serializeServerNode(asServer(node));\n }\n}\n\n// `/* @__PURE__ */`: this singleton is a convenience export only (no internal\n// runtime path references it). Marking construction pure lets bundlers drop it —\n// and with it the whole server serializer chain (serializeServerNode/escape/\n// VOID_ELEMENTS) — out of client bundles that never import SSR. Without this,\n// the un-annotated `new` is treated as a side effect and retained everywhere.\nexport const serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();\n","/**\n * Focus helpers built on the {@link DOMAdapter} abstraction.\n *\n * These are the minimal, genuinely-useful focus operations an app needs:\n * focus a specific element (e.g. the first field when a route or modal opens)\n * or focus the first focusable element inside a container (e.g. move focus\n * into a dialog). Both go through the adapter, so they are no-ops on the server\n * (`ServerDOMAdapter.querySelector` returns null / `focus` does nothing) and\n * therefore safe to call from universal code.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\n/** Default selector for natively focusable / tabbable elements. */\nexport const FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Focus the element with the given id, scoped to `root`.\n * Returns true if an element was found and focused.\n */\nexport function focusById(dom: DOMAdapter, root: Element | Document, id: string): boolean {\n const el = dom.querySelector(root, `[id=\"${id}\"]`);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Focus the first focusable element inside `container`.\n * Returns true if a focusable element was found and focused.\n */\nexport function focusFirst(\n dom: DOMAdapter,\n container: Element | Document,\n selector: string = FOCUSABLE_SELECTOR,\n): boolean {\n const el = dom.querySelector(container, selector);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Ordered list of focusable/tabbable descendants of `container`.\n * Re-checks each candidate against the selector so elements disabled after the\n * initial query (e.g. a button toggled to `disabled`) are excluded.\n */\nexport function getFocusable(\n dom: DOMAdapter,\n container: Element,\n selector: string = FOCUSABLE_SELECTOR,\n): Element[] {\n return Array.from(dom.querySelectorAll(container, selector)).filter((el) =>\n dom.matches(el, selector),\n );\n}\n\n/**\n * Capture the currently-focused element so it can be restored later (e.g. when\n * a dialog closes). Returns null on the server or when nothing is focused.\n */\nexport function saveFocus(dom: DOMAdapter): Element | null {\n return dom.activeElement();\n}\n\n/** Restore focus to a previously {@link saveFocus}-d element. No-op if null. */\nexport function restoreFocus(dom: DOMAdapter, saved: Element | null): void {\n if (saved !== null) dom.focus(saved);\n}\n\n/**\n * Move focus into `container` on open: the element with id `initialFocusId` if\n * given and present, otherwise the first focusable element. Server-safe no-op.\n */\nexport function focusInitial(\n dom: DOMAdapter,\n container: Element,\n initialFocusId?: string,\n): void {\n if (initialFocusId !== undefined && focusById(dom, container, initialFocusId)) return;\n focusFirst(dom, container);\n}\n\n/**\n * Trap Tab / Shift+Tab focus within `container` (wrap-around at both ends).\n * Attaches a keydown listener to the container and returns a cleanup function\n * that detaches it. Server-safe: `addEventListener` is a no-op, and the returned\n * cleanup is still callable.\n */\nexport function trapFocus(dom: DOMAdapter, container: Element): () => void {\n const onKeydown = ((event: KeyboardEvent): void => {\n if (event.key !== 'Tab') return;\n const items = getFocusable(dom, container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = dom.activeElement();\n if (active === null || !dom.contains(container, active)) {\n event.preventDefault();\n dom.focus(first);\n } else if (event.shiftKey && active === first) {\n event.preventDefault();\n dom.focus(last);\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n dom.focus(first);\n }\n }) as EventListener;\n dom.addEventListener(container, 'keydown', onKeydown);\n return () => dom.removeEventListener(container, 'keydown', onKeydown);\n}\n\n/**\n * Modal containment: if focus moves to an element outside `container`, redirect\n * it back inside. Listens on the document body (focusin bubbles there) and\n * returns a cleanup function. Server-safe no-op (body() is null).\n */\nexport function containFocus(dom: DOMAdapter, container: Element): () => void {\n const body = dom.body();\n if (body === null) return () => {};\n const onFocusIn = ((event: FocusEvent): void => {\n const target = event.target as Node | null;\n if (target !== null && !dom.contains(container, target)) {\n focusFirst(dom, container);\n }\n }) as EventListener;\n dom.addEventListener(body, 'focusin', onFocusIn);\n return () => dom.removeEventListener(body, 'focusin', onFocusIn);\n}\n\n/**\n * Invoke `handler` when Escape is pressed while focus is within `target`.\n * Returns a cleanup function. Server-safe no-op.\n */\nexport function onEscape(dom: DOMAdapter, target: Element, handler: () => void): () => void {\n const onKeydown = ((event: KeyboardEvent): void => {\n if (event.key === 'Escape') handler();\n }) as EventListener;\n dom.addEventListener(target, 'keydown', onKeydown);\n return () => dom.removeEventListener(target, 'keydown', onKeydown);\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -9,6 +9,13 @@ interface DOMAdapter {
|
|
|
9
9
|
createTextNode(data: string): Text;
|
|
10
10
|
createComment(data: string): Comment;
|
|
11
11
|
createFragment(): DocumentFragment;
|
|
12
|
+
/**
|
|
13
|
+
* Optional, server-only: create a verbatim pre-serialized HTML node used by
|
|
14
|
+
* the v1.7 static SSR plan. The browser adapter does not implement it; the
|
|
15
|
+
* renderer only calls it when a static SSR plan is active (i.e. during SSR),
|
|
16
|
+
* so client builds never reach this path and it stays tree-shakeable.
|
|
17
|
+
*/
|
|
18
|
+
createRawHTML?(html: string): Node;
|
|
12
19
|
appendChild(parent: Node, child: Node): void;
|
|
13
20
|
insertBefore(parent: Node, child: Node, reference: Node | null): void;
|
|
14
21
|
removeChild(parent: Node, child: Node): void;
|
|
@@ -29,6 +36,18 @@ interface DOMAdapter {
|
|
|
29
36
|
* focus) this is a safe no-op, keeping focus management SSR-compatible.
|
|
30
37
|
*/
|
|
31
38
|
focus(element: Element): void;
|
|
39
|
+
/**
|
|
40
|
+
* The document body — the default mount target for portals/overlays. Returns
|
|
41
|
+
* null on the server (no document), which is what makes portal SSR degrade to
|
|
42
|
+
* inline rendering and focus management degrade to a no-op.
|
|
43
|
+
*/
|
|
44
|
+
body(): Element | null;
|
|
45
|
+
/** The currently focused element, or null on the server / when none is focused. */
|
|
46
|
+
activeElement(): Element | null;
|
|
47
|
+
/** True if `ancestor` contains `node` (inclusive). Always false on the server. */
|
|
48
|
+
contains(ancestor: Element, node: Node): boolean;
|
|
49
|
+
/** True if `element` matches the given CSS selector. Always false on the server. */
|
|
50
|
+
matches(element: Element, selector: string): boolean;
|
|
32
51
|
isElement(node: Node): node is Element;
|
|
33
52
|
isTextNode(node: Node): node is Text;
|
|
34
53
|
/** Lower-cased tag name of an element (e.g. "div", "h1"). */
|
|
@@ -66,6 +85,10 @@ declare class BrowserDOMAdapter implements DOMAdapter {
|
|
|
66
85
|
querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element>;
|
|
67
86
|
getElementById(id: string): Element | null;
|
|
68
87
|
focus(element: Element): void;
|
|
88
|
+
body(): Element | null;
|
|
89
|
+
activeElement(): Element | null;
|
|
90
|
+
contains(ancestor: Element, node: Node): boolean;
|
|
91
|
+
matches(element: Element, selector: string): boolean;
|
|
69
92
|
isElement(node: Node): node is Element;
|
|
70
93
|
isTextNode(node: Node): node is Text;
|
|
71
94
|
tagName(element: Element): string;
|
|
@@ -88,7 +111,7 @@ declare const browserDOMAdapter: BrowserDOMAdapter;
|
|
|
88
111
|
* `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into
|
|
89
112
|
* operations on this model.
|
|
90
113
|
*/
|
|
91
|
-
type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment';
|
|
114
|
+
type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment' | 'raw';
|
|
92
115
|
interface ServerNode {
|
|
93
116
|
readonly kind: ServerNodeKind;
|
|
94
117
|
parent: ServerParent | null;
|
|
@@ -118,6 +141,25 @@ declare class ServerFragment implements ServerNode {
|
|
|
118
141
|
parent: ServerParent | null;
|
|
119
142
|
readonly children: ServerNode[];
|
|
120
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* A pre-serialized, verbatim HTML fragment (v1.7 static SSR plan).
|
|
146
|
+
*
|
|
147
|
+
* Emitted for provably-static subtrees whose HTML the compiler-derived static
|
|
148
|
+
* SSR plan already computed once. Serializing this node copies its stored
|
|
149
|
+
* string directly — it allocates no ServerElement/ServerText, no attribute Map
|
|
150
|
+
* and no children array for the collapsed subtree. The stored `html` is
|
|
151
|
+
* produced by the exact same mount + serialize pipeline as the runtime path, so
|
|
152
|
+
* the output is byte-identical (the v1.7 byte-identity gate proves this).
|
|
153
|
+
*
|
|
154
|
+
* This node is SSR-only: it is created solely via `ServerDOMAdapter.createRawHTML`
|
|
155
|
+
* on the server render path and never appears in a browser build.
|
|
156
|
+
*/
|
|
157
|
+
declare class ServerRawHTML implements ServerNode {
|
|
158
|
+
readonly kind: "raw";
|
|
159
|
+
parent: ServerParent | null;
|
|
160
|
+
readonly html: string;
|
|
161
|
+
constructor(html: string);
|
|
162
|
+
}
|
|
121
163
|
declare class ServerElement implements ServerNode {
|
|
122
164
|
readonly kind: "element";
|
|
123
165
|
parent: ServerParent | null;
|
|
@@ -136,7 +178,7 @@ declare class ServerElement implements ServerNode {
|
|
|
136
178
|
declare function escapeHtmlText(value: string): string;
|
|
137
179
|
/** Escape a double-quoted attribute value. */
|
|
138
180
|
declare function escapeHtmlAttr(value: string): string;
|
|
139
|
-
/** Serialize a single server node (element/text/comment/fragment) to HTML. */
|
|
181
|
+
/** Serialize a single server node (element/text/comment/fragment/raw) to HTML. */
|
|
140
182
|
declare function serializeServerNode(node: ServerNode): string;
|
|
141
183
|
/** Serialize the children of an element or fragment (its "inner HTML"). */
|
|
142
184
|
declare function serializeChildren(node: ServerElement | ServerFragment): string;
|
|
@@ -160,6 +202,13 @@ declare class ServerDOMAdapter implements DOMAdapter {
|
|
|
160
202
|
createTextNode(data: string): Text;
|
|
161
203
|
createComment(data: string): Comment;
|
|
162
204
|
createFragment(): DocumentFragment;
|
|
205
|
+
/**
|
|
206
|
+
* Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).
|
|
207
|
+
* Server-only: the browser adapter does not implement this, and the renderer
|
|
208
|
+
* fast path only invokes it when a static SSR plan is present (SSR). The
|
|
209
|
+
* stored HTML was produced by this same serializer, so it is emitted as-is.
|
|
210
|
+
*/
|
|
211
|
+
createRawHTML(html: string): Node;
|
|
163
212
|
appendChild(parent: Node, child: Node): void;
|
|
164
213
|
insertBefore(parent: Node, child: Node, reference: Node | null): void;
|
|
165
214
|
removeChild(parent: Node, child: Node): void;
|
|
@@ -177,6 +226,10 @@ declare class ServerDOMAdapter implements DOMAdapter {
|
|
|
177
226
|
querySelectorAll(): NodeListOf<Element>;
|
|
178
227
|
getElementById(): Element | null;
|
|
179
228
|
focus(): void;
|
|
229
|
+
body(): Element | null;
|
|
230
|
+
activeElement(): Element | null;
|
|
231
|
+
contains(_ancestor: Element, _node: Node): boolean;
|
|
232
|
+
matches(_element: Element, _selector: string): boolean;
|
|
180
233
|
isElement(node: Node): node is Element;
|
|
181
234
|
isTextNode(node: Node): node is Text;
|
|
182
235
|
tagName(element: Element): string;
|
|
@@ -214,5 +267,41 @@ declare function focusById(dom: DOMAdapter, root: Element | Document, id: string
|
|
|
214
267
|
* Returns true if a focusable element was found and focused.
|
|
215
268
|
*/
|
|
216
269
|
declare function focusFirst(dom: DOMAdapter, container: Element | Document, selector?: string): boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Ordered list of focusable/tabbable descendants of `container`.
|
|
272
|
+
* Re-checks each candidate against the selector so elements disabled after the
|
|
273
|
+
* initial query (e.g. a button toggled to `disabled`) are excluded.
|
|
274
|
+
*/
|
|
275
|
+
declare function getFocusable(dom: DOMAdapter, container: Element, selector?: string): Element[];
|
|
276
|
+
/**
|
|
277
|
+
* Capture the currently-focused element so it can be restored later (e.g. when
|
|
278
|
+
* a dialog closes). Returns null on the server or when nothing is focused.
|
|
279
|
+
*/
|
|
280
|
+
declare function saveFocus(dom: DOMAdapter): Element | null;
|
|
281
|
+
/** Restore focus to a previously {@link saveFocus}-d element. No-op if null. */
|
|
282
|
+
declare function restoreFocus(dom: DOMAdapter, saved: Element | null): void;
|
|
283
|
+
/**
|
|
284
|
+
* Move focus into `container` on open: the element with id `initialFocusId` if
|
|
285
|
+
* given and present, otherwise the first focusable element. Server-safe no-op.
|
|
286
|
+
*/
|
|
287
|
+
declare function focusInitial(dom: DOMAdapter, container: Element, initialFocusId?: string): void;
|
|
288
|
+
/**
|
|
289
|
+
* Trap Tab / Shift+Tab focus within `container` (wrap-around at both ends).
|
|
290
|
+
* Attaches a keydown listener to the container and returns a cleanup function
|
|
291
|
+
* that detaches it. Server-safe: `addEventListener` is a no-op, and the returned
|
|
292
|
+
* cleanup is still callable.
|
|
293
|
+
*/
|
|
294
|
+
declare function trapFocus(dom: DOMAdapter, container: Element): () => void;
|
|
295
|
+
/**
|
|
296
|
+
* Modal containment: if focus moves to an element outside `container`, redirect
|
|
297
|
+
* it back inside. Listens on the document body (focusin bubbles there) and
|
|
298
|
+
* returns a cleanup function. Server-safe no-op (body() is null).
|
|
299
|
+
*/
|
|
300
|
+
declare function containFocus(dom: DOMAdapter, container: Element): () => void;
|
|
301
|
+
/**
|
|
302
|
+
* Invoke `handler` when Escape is pressed while focus is within `target`.
|
|
303
|
+
* Returns a cleanup function. Server-safe no-op.
|
|
304
|
+
*/
|
|
305
|
+
declare function onEscape(dom: DOMAdapter, target: Element, handler: () => void): () => void;
|
|
217
306
|
|
|
218
|
-
export { BrowserDOMAdapter, type DOMAdapter, FOCUSABLE_SELECTOR, ServerComment, ServerDOMAdapter, ServerElement, ServerFragment, type ServerNode, type ServerNodeKind, type ServerParent, ServerStyle, ServerText, browserDOMAdapter, escapeHtmlAttr, escapeHtmlText, focusById, focusFirst, serializeChildren, serializeServerNode, serverDOMAdapter };
|
|
307
|
+
export { BrowserDOMAdapter, type DOMAdapter, FOCUSABLE_SELECTOR, ServerComment, ServerDOMAdapter, ServerElement, ServerFragment, type ServerNode, type ServerNodeKind, type ServerParent, ServerRawHTML, ServerStyle, ServerText, browserDOMAdapter, containFocus, escapeHtmlAttr, escapeHtmlText, focusById, focusFirst, focusInitial, getFocusable, onEscape, restoreFocus, saveFocus, serializeChildren, serializeServerNode, serverDOMAdapter, trapFocus };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ interface DOMAdapter {
|
|
|
9
9
|
createTextNode(data: string): Text;
|
|
10
10
|
createComment(data: string): Comment;
|
|
11
11
|
createFragment(): DocumentFragment;
|
|
12
|
+
/**
|
|
13
|
+
* Optional, server-only: create a verbatim pre-serialized HTML node used by
|
|
14
|
+
* the v1.7 static SSR plan. The browser adapter does not implement it; the
|
|
15
|
+
* renderer only calls it when a static SSR plan is active (i.e. during SSR),
|
|
16
|
+
* so client builds never reach this path and it stays tree-shakeable.
|
|
17
|
+
*/
|
|
18
|
+
createRawHTML?(html: string): Node;
|
|
12
19
|
appendChild(parent: Node, child: Node): void;
|
|
13
20
|
insertBefore(parent: Node, child: Node, reference: Node | null): void;
|
|
14
21
|
removeChild(parent: Node, child: Node): void;
|
|
@@ -29,6 +36,18 @@ interface DOMAdapter {
|
|
|
29
36
|
* focus) this is a safe no-op, keeping focus management SSR-compatible.
|
|
30
37
|
*/
|
|
31
38
|
focus(element: Element): void;
|
|
39
|
+
/**
|
|
40
|
+
* The document body — the default mount target for portals/overlays. Returns
|
|
41
|
+
* null on the server (no document), which is what makes portal SSR degrade to
|
|
42
|
+
* inline rendering and focus management degrade to a no-op.
|
|
43
|
+
*/
|
|
44
|
+
body(): Element | null;
|
|
45
|
+
/** The currently focused element, or null on the server / when none is focused. */
|
|
46
|
+
activeElement(): Element | null;
|
|
47
|
+
/** True if `ancestor` contains `node` (inclusive). Always false on the server. */
|
|
48
|
+
contains(ancestor: Element, node: Node): boolean;
|
|
49
|
+
/** True if `element` matches the given CSS selector. Always false on the server. */
|
|
50
|
+
matches(element: Element, selector: string): boolean;
|
|
32
51
|
isElement(node: Node): node is Element;
|
|
33
52
|
isTextNode(node: Node): node is Text;
|
|
34
53
|
/** Lower-cased tag name of an element (e.g. "div", "h1"). */
|
|
@@ -66,6 +85,10 @@ declare class BrowserDOMAdapter implements DOMAdapter {
|
|
|
66
85
|
querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element>;
|
|
67
86
|
getElementById(id: string): Element | null;
|
|
68
87
|
focus(element: Element): void;
|
|
88
|
+
body(): Element | null;
|
|
89
|
+
activeElement(): Element | null;
|
|
90
|
+
contains(ancestor: Element, node: Node): boolean;
|
|
91
|
+
matches(element: Element, selector: string): boolean;
|
|
69
92
|
isElement(node: Node): node is Element;
|
|
70
93
|
isTextNode(node: Node): node is Text;
|
|
71
94
|
tagName(element: Element): string;
|
|
@@ -88,7 +111,7 @@ declare const browserDOMAdapter: BrowserDOMAdapter;
|
|
|
88
111
|
* `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into
|
|
89
112
|
* operations on this model.
|
|
90
113
|
*/
|
|
91
|
-
type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment';
|
|
114
|
+
type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment' | 'raw';
|
|
92
115
|
interface ServerNode {
|
|
93
116
|
readonly kind: ServerNodeKind;
|
|
94
117
|
parent: ServerParent | null;
|
|
@@ -118,6 +141,25 @@ declare class ServerFragment implements ServerNode {
|
|
|
118
141
|
parent: ServerParent | null;
|
|
119
142
|
readonly children: ServerNode[];
|
|
120
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* A pre-serialized, verbatim HTML fragment (v1.7 static SSR plan).
|
|
146
|
+
*
|
|
147
|
+
* Emitted for provably-static subtrees whose HTML the compiler-derived static
|
|
148
|
+
* SSR plan already computed once. Serializing this node copies its stored
|
|
149
|
+
* string directly — it allocates no ServerElement/ServerText, no attribute Map
|
|
150
|
+
* and no children array for the collapsed subtree. The stored `html` is
|
|
151
|
+
* produced by the exact same mount + serialize pipeline as the runtime path, so
|
|
152
|
+
* the output is byte-identical (the v1.7 byte-identity gate proves this).
|
|
153
|
+
*
|
|
154
|
+
* This node is SSR-only: it is created solely via `ServerDOMAdapter.createRawHTML`
|
|
155
|
+
* on the server render path and never appears in a browser build.
|
|
156
|
+
*/
|
|
157
|
+
declare class ServerRawHTML implements ServerNode {
|
|
158
|
+
readonly kind: "raw";
|
|
159
|
+
parent: ServerParent | null;
|
|
160
|
+
readonly html: string;
|
|
161
|
+
constructor(html: string);
|
|
162
|
+
}
|
|
121
163
|
declare class ServerElement implements ServerNode {
|
|
122
164
|
readonly kind: "element";
|
|
123
165
|
parent: ServerParent | null;
|
|
@@ -136,7 +178,7 @@ declare class ServerElement implements ServerNode {
|
|
|
136
178
|
declare function escapeHtmlText(value: string): string;
|
|
137
179
|
/** Escape a double-quoted attribute value. */
|
|
138
180
|
declare function escapeHtmlAttr(value: string): string;
|
|
139
|
-
/** Serialize a single server node (element/text/comment/fragment) to HTML. */
|
|
181
|
+
/** Serialize a single server node (element/text/comment/fragment/raw) to HTML. */
|
|
140
182
|
declare function serializeServerNode(node: ServerNode): string;
|
|
141
183
|
/** Serialize the children of an element or fragment (its "inner HTML"). */
|
|
142
184
|
declare function serializeChildren(node: ServerElement | ServerFragment): string;
|
|
@@ -160,6 +202,13 @@ declare class ServerDOMAdapter implements DOMAdapter {
|
|
|
160
202
|
createTextNode(data: string): Text;
|
|
161
203
|
createComment(data: string): Comment;
|
|
162
204
|
createFragment(): DocumentFragment;
|
|
205
|
+
/**
|
|
206
|
+
* Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).
|
|
207
|
+
* Server-only: the browser adapter does not implement this, and the renderer
|
|
208
|
+
* fast path only invokes it when a static SSR plan is present (SSR). The
|
|
209
|
+
* stored HTML was produced by this same serializer, so it is emitted as-is.
|
|
210
|
+
*/
|
|
211
|
+
createRawHTML(html: string): Node;
|
|
163
212
|
appendChild(parent: Node, child: Node): void;
|
|
164
213
|
insertBefore(parent: Node, child: Node, reference: Node | null): void;
|
|
165
214
|
removeChild(parent: Node, child: Node): void;
|
|
@@ -177,6 +226,10 @@ declare class ServerDOMAdapter implements DOMAdapter {
|
|
|
177
226
|
querySelectorAll(): NodeListOf<Element>;
|
|
178
227
|
getElementById(): Element | null;
|
|
179
228
|
focus(): void;
|
|
229
|
+
body(): Element | null;
|
|
230
|
+
activeElement(): Element | null;
|
|
231
|
+
contains(_ancestor: Element, _node: Node): boolean;
|
|
232
|
+
matches(_element: Element, _selector: string): boolean;
|
|
180
233
|
isElement(node: Node): node is Element;
|
|
181
234
|
isTextNode(node: Node): node is Text;
|
|
182
235
|
tagName(element: Element): string;
|
|
@@ -214,5 +267,41 @@ declare function focusById(dom: DOMAdapter, root: Element | Document, id: string
|
|
|
214
267
|
* Returns true if a focusable element was found and focused.
|
|
215
268
|
*/
|
|
216
269
|
declare function focusFirst(dom: DOMAdapter, container: Element | Document, selector?: string): boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Ordered list of focusable/tabbable descendants of `container`.
|
|
272
|
+
* Re-checks each candidate against the selector so elements disabled after the
|
|
273
|
+
* initial query (e.g. a button toggled to `disabled`) are excluded.
|
|
274
|
+
*/
|
|
275
|
+
declare function getFocusable(dom: DOMAdapter, container: Element, selector?: string): Element[];
|
|
276
|
+
/**
|
|
277
|
+
* Capture the currently-focused element so it can be restored later (e.g. when
|
|
278
|
+
* a dialog closes). Returns null on the server or when nothing is focused.
|
|
279
|
+
*/
|
|
280
|
+
declare function saveFocus(dom: DOMAdapter): Element | null;
|
|
281
|
+
/** Restore focus to a previously {@link saveFocus}-d element. No-op if null. */
|
|
282
|
+
declare function restoreFocus(dom: DOMAdapter, saved: Element | null): void;
|
|
283
|
+
/**
|
|
284
|
+
* Move focus into `container` on open: the element with id `initialFocusId` if
|
|
285
|
+
* given and present, otherwise the first focusable element. Server-safe no-op.
|
|
286
|
+
*/
|
|
287
|
+
declare function focusInitial(dom: DOMAdapter, container: Element, initialFocusId?: string): void;
|
|
288
|
+
/**
|
|
289
|
+
* Trap Tab / Shift+Tab focus within `container` (wrap-around at both ends).
|
|
290
|
+
* Attaches a keydown listener to the container and returns a cleanup function
|
|
291
|
+
* that detaches it. Server-safe: `addEventListener` is a no-op, and the returned
|
|
292
|
+
* cleanup is still callable.
|
|
293
|
+
*/
|
|
294
|
+
declare function trapFocus(dom: DOMAdapter, container: Element): () => void;
|
|
295
|
+
/**
|
|
296
|
+
* Modal containment: if focus moves to an element outside `container`, redirect
|
|
297
|
+
* it back inside. Listens on the document body (focusin bubbles there) and
|
|
298
|
+
* returns a cleanup function. Server-safe no-op (body() is null).
|
|
299
|
+
*/
|
|
300
|
+
declare function containFocus(dom: DOMAdapter, container: Element): () => void;
|
|
301
|
+
/**
|
|
302
|
+
* Invoke `handler` when Escape is pressed while focus is within `target`.
|
|
303
|
+
* Returns a cleanup function. Server-safe no-op.
|
|
304
|
+
*/
|
|
305
|
+
declare function onEscape(dom: DOMAdapter, target: Element, handler: () => void): () => void;
|
|
217
306
|
|
|
218
|
-
export { BrowserDOMAdapter, type DOMAdapter, FOCUSABLE_SELECTOR, ServerComment, ServerDOMAdapter, ServerElement, ServerFragment, type ServerNode, type ServerNodeKind, type ServerParent, ServerStyle, ServerText, browserDOMAdapter, escapeHtmlAttr, escapeHtmlText, focusById, focusFirst, serializeChildren, serializeServerNode, serverDOMAdapter };
|
|
307
|
+
export { BrowserDOMAdapter, type DOMAdapter, FOCUSABLE_SELECTOR, ServerComment, ServerDOMAdapter, ServerElement, ServerFragment, type ServerNode, type ServerNodeKind, type ServerParent, ServerRawHTML, ServerStyle, ServerText, browserDOMAdapter, containFocus, escapeHtmlAttr, escapeHtmlText, focusById, focusFirst, focusInitial, getFocusable, onEscape, restoreFocus, saveFocus, serializeChildren, serializeServerNode, serverDOMAdapter, trapFocus };
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,18 @@ var BrowserDOMAdapter = class {
|
|
|
63
63
|
focus(element) {
|
|
64
64
|
element.focus?.();
|
|
65
65
|
}
|
|
66
|
+
body() {
|
|
67
|
+
return document.body ?? null;
|
|
68
|
+
}
|
|
69
|
+
activeElement() {
|
|
70
|
+
return document.activeElement ?? null;
|
|
71
|
+
}
|
|
72
|
+
contains(ancestor, node) {
|
|
73
|
+
return ancestor.contains(node);
|
|
74
|
+
}
|
|
75
|
+
matches(element, selector) {
|
|
76
|
+
return typeof element.matches === "function" && element.matches(selector);
|
|
77
|
+
}
|
|
66
78
|
isElement(node) {
|
|
67
79
|
return node.nodeType === Node.ELEMENT_NODE;
|
|
68
80
|
}
|
|
@@ -121,6 +133,14 @@ var ServerFragment = class {
|
|
|
121
133
|
parent = null;
|
|
122
134
|
children = [];
|
|
123
135
|
};
|
|
136
|
+
var ServerRawHTML = class {
|
|
137
|
+
kind = "raw";
|
|
138
|
+
parent = null;
|
|
139
|
+
html;
|
|
140
|
+
constructor(html) {
|
|
141
|
+
this.html = html;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
124
144
|
var ServerElement = class {
|
|
125
145
|
kind = "element";
|
|
126
146
|
parent = null;
|
|
@@ -271,6 +291,8 @@ function serializeServerNode(node) {
|
|
|
271
291
|
return `<!--${node.data}-->`;
|
|
272
292
|
case "fragment":
|
|
273
293
|
return serializeChildren(node);
|
|
294
|
+
case "raw":
|
|
295
|
+
return node.html;
|
|
274
296
|
case "element": {
|
|
275
297
|
const el = node;
|
|
276
298
|
const tag = el.tagName;
|
|
@@ -310,6 +332,15 @@ var ServerDOMAdapter = class {
|
|
|
310
332
|
createFragment() {
|
|
311
333
|
return new ServerFragment();
|
|
312
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).
|
|
337
|
+
* Server-only: the browser adapter does not implement this, and the renderer
|
|
338
|
+
* fast path only invokes it when a static SSR plan is present (SSR). The
|
|
339
|
+
* stored HTML was produced by this same serializer, so it is emitted as-is.
|
|
340
|
+
*/
|
|
341
|
+
createRawHTML(html) {
|
|
342
|
+
return new ServerRawHTML(html);
|
|
343
|
+
}
|
|
313
344
|
appendChild(parent, child) {
|
|
314
345
|
const p = asParent(parent);
|
|
315
346
|
const c = asServer(child);
|
|
@@ -411,6 +442,18 @@ var ServerDOMAdapter = class {
|
|
|
411
442
|
}
|
|
412
443
|
focus() {
|
|
413
444
|
}
|
|
445
|
+
body() {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
activeElement() {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
contains(_ancestor, _node) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
matches(_element, _selector) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
414
457
|
isElement(node) {
|
|
415
458
|
return asServer(node).kind === "element";
|
|
416
459
|
}
|
|
@@ -476,6 +519,66 @@ function focusFirst(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
|
476
519
|
dom.focus(el);
|
|
477
520
|
return true;
|
|
478
521
|
}
|
|
522
|
+
function getFocusable(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
523
|
+
return Array.from(dom.querySelectorAll(container, selector)).filter(
|
|
524
|
+
(el) => dom.matches(el, selector)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
function saveFocus(dom) {
|
|
528
|
+
return dom.activeElement();
|
|
529
|
+
}
|
|
530
|
+
function restoreFocus(dom, saved) {
|
|
531
|
+
if (saved !== null) dom.focus(saved);
|
|
532
|
+
}
|
|
533
|
+
function focusInitial(dom, container, initialFocusId) {
|
|
534
|
+
if (initialFocusId !== void 0 && focusById(dom, container, initialFocusId)) return;
|
|
535
|
+
focusFirst(dom, container);
|
|
536
|
+
}
|
|
537
|
+
function trapFocus(dom, container) {
|
|
538
|
+
const onKeydown = (event) => {
|
|
539
|
+
if (event.key !== "Tab") return;
|
|
540
|
+
const items = getFocusable(dom, container);
|
|
541
|
+
if (items.length === 0) {
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const first = items[0];
|
|
546
|
+
const last = items[items.length - 1];
|
|
547
|
+
const active = dom.activeElement();
|
|
548
|
+
if (active === null || !dom.contains(container, active)) {
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
dom.focus(first);
|
|
551
|
+
} else if (event.shiftKey && active === first) {
|
|
552
|
+
event.preventDefault();
|
|
553
|
+
dom.focus(last);
|
|
554
|
+
} else if (!event.shiftKey && active === last) {
|
|
555
|
+
event.preventDefault();
|
|
556
|
+
dom.focus(first);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
560
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
561
|
+
}
|
|
562
|
+
function containFocus(dom, container) {
|
|
563
|
+
const body = dom.body();
|
|
564
|
+
if (body === null) return () => {
|
|
565
|
+
};
|
|
566
|
+
const onFocusIn = (event) => {
|
|
567
|
+
const target = event.target;
|
|
568
|
+
if (target !== null && !dom.contains(container, target)) {
|
|
569
|
+
focusFirst(dom, container);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
dom.addEventListener(body, "focusin", onFocusIn);
|
|
573
|
+
return () => dom.removeEventListener(body, "focusin", onFocusIn);
|
|
574
|
+
}
|
|
575
|
+
function onEscape(dom, target, handler) {
|
|
576
|
+
const onKeydown = (event) => {
|
|
577
|
+
if (event.key === "Escape") handler();
|
|
578
|
+
};
|
|
579
|
+
dom.addEventListener(target, "keydown", onKeydown);
|
|
580
|
+
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
581
|
+
}
|
|
479
582
|
export {
|
|
480
583
|
BrowserDOMAdapter,
|
|
481
584
|
FOCUSABLE_SELECTOR,
|
|
@@ -483,15 +586,23 @@ export {
|
|
|
483
586
|
ServerDOMAdapter,
|
|
484
587
|
ServerElement,
|
|
485
588
|
ServerFragment,
|
|
589
|
+
ServerRawHTML,
|
|
486
590
|
ServerStyle,
|
|
487
591
|
ServerText,
|
|
488
592
|
browserDOMAdapter,
|
|
593
|
+
containFocus,
|
|
489
594
|
escapeHtmlAttr,
|
|
490
595
|
escapeHtmlText,
|
|
491
596
|
focusById,
|
|
492
597
|
focusFirst,
|
|
598
|
+
focusInitial,
|
|
599
|
+
getFocusable,
|
|
600
|
+
onEscape,
|
|
601
|
+
restoreFocus,
|
|
602
|
+
saveFocus,
|
|
493
603
|
serializeChildren,
|
|
494
604
|
serializeServerNode,
|
|
495
|
-
serverDOMAdapter
|
|
605
|
+
serverDOMAdapter,
|
|
606
|
+
trapFocus
|
|
496
607
|
};
|
|
497
608
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser-adapter.ts","../src/server-node.ts","../src/server-adapter.ts","../src/focus.ts"],"sourcesContent":["/**\n * Browser implementation of DOMAdapter — delegates directly to browser APIs.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\nexport class BrowserDOMAdapter implements DOMAdapter {\n createElement(tag: string, ns?: string): Element {\n if (ns !== undefined) {\n return document.createElementNS(ns, tag);\n }\n return document.createElement(tag);\n }\n\n createTextNode(data: string): Text {\n return document.createTextNode(data);\n }\n\n createComment(data: string): Comment {\n return document.createComment(data);\n }\n\n createFragment(): DocumentFragment {\n return document.createDocumentFragment();\n }\n\n appendChild(parent: Node, child: Node): void {\n parent.appendChild(child);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n parent.insertBefore(child, reference);\n }\n\n removeChild(parent: Node, child: Node): void {\n parent.removeChild(child);\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n parent.replaceChild(newChild, oldChild);\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n element.setAttribute(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n element.removeAttribute(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return element.getAttribute(name);\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as Record<string, unknown>)[name] = value;\n }\n\n setTextContent(node: Node, text: string): void {\n node.textContent = text;\n }\n\n getTextContent(node: Node): string | null {\n return node.textContent;\n }\n\n addEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ): void {\n target.addEventListener(type, handler, options);\n }\n\n removeEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: EventListenerOptions,\n ): void {\n target.removeEventListener(type, handler, options);\n }\n\n querySelector(root: Element | Document, selector: string): Element | null {\n return root.querySelector(selector);\n }\n\n querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element> {\n return root.querySelectorAll(selector);\n }\n\n getElementById(id: string): Element | null {\n return document.getElementById(id);\n }\n\n focus(element: Element): void {\n (element as unknown as { focus?: () => void }).focus?.();\n }\n\n isElement(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n\n isTextNode(node: Node): node is Text {\n return node.nodeType === Node.TEXT_NODE;\n }\n\n tagName(element: Element): string {\n return element.tagName.toLowerCase();\n }\n\n parentNode(node: Node): Node | null {\n return node.parentNode;\n }\n\n nextSibling(node: Node): Node | null {\n return node.nextSibling;\n }\n\n firstChild(node: Node): Node | null {\n return node.firstChild;\n }\n\n childNodes(node: Node): Node[] {\n return Array.from(node.childNodes);\n }\n}\n\n// `/* @__PURE__ */`: convenience singleton, unreferenced by internal runtime\n// paths. Marking construction pure lets bundlers drop it when unused instead of\n// retaining it (and the BrowserDOMAdapter class) as an import-time side effect.\nexport const browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();\n","/**\n * Server-side DOM node model.\n *\n * A tiny, dependency-free tree of plain objects that mirrors just enough of the\n * browser DOM for StreetUI's renderer to build a tree on the server and\n * serialize it to an HTML string. There is NO browser global here — these are\n * ordinary classes usable in any JavaScript environment (Node, workers, tests).\n *\n * The renderer never touches these types directly; it goes through the\n * `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into\n * operations on this model.\n */\n\nexport type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment';\n\nexport interface ServerNode {\n readonly kind: ServerNodeKind;\n parent: ServerParent | null;\n}\n\nexport type ServerParent = ServerElement | ServerFragment;\n\n/** A minimal inline-style holder mirroring `element.style.setProperty`. */\nexport class ServerStyle {\n readonly declarations = new Map<string, string>();\n setProperty(name: string, value: string): void {\n this.declarations.set(name, value);\n }\n get isEmpty(): boolean {\n return this.declarations.size === 0;\n }\n toCss(): string {\n return [...this.declarations.entries()].map(([k, v]) => `${k}: ${v}`).join('; ');\n }\n}\n\nexport class ServerText implements ServerNode {\n readonly kind = 'text' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerComment implements ServerNode {\n readonly kind = 'comment' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerFragment implements ServerNode {\n readonly kind = 'fragment' as const;\n parent: ServerParent | null = null;\n readonly children: ServerNode[] = [];\n}\n\nexport class ServerElement implements ServerNode {\n readonly kind = 'element' as const;\n parent: ServerParent | null = null;\n readonly tagName: string;\n readonly attributes = new Map<string, string>();\n readonly children: ServerNode[] = [];\n\n // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS\n // properties or inline styles (measured, §5: 1 of 80,029 elements uses\n // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus\n // a `ServerStyle` (which itself holds a Map) per element wasted ~240k\n // allocations per /users render — all in the dominant mount phase. These are\n // created on first WRITE via the `properties`/`style` getters; the serializer\n // reads the raw `_properties`/`_style` fields so a READ never forces an\n // allocation. Output is byte-identical: an unset store previously serialized\n // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store\n // is skipped the same way.\n _properties: Map<string, unknown> | null = null;\n _style: ServerStyle | null = null;\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\n }\n\n /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */\n get properties(): Map<string, unknown> {\n return (this._properties ??= new Map());\n }\n\n /** Inline-style holder mirroring `element.style`. Allocated on first access. */\n get style(): ServerStyle {\n return (this._style ??= new ServerStyle());\n }\n}\n\n// ── HTML serialization ─────────────────────────────────────────────────────────\n\n/**\n * HTML \"void\" elements — self-closing, never given a closing tag or children.\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Element properties that should be reflected into the serialized HTML so the\n * hydrated DOM carries the same initial state. `value`/`checked` matter for\n * form controls whose live state is a JS property, not an attribute.\n */\nconst SERIALIZED_PROPERTIES: Record<string, 'attr' | 'boolean'> = {\n value: 'attr',\n checked: 'boolean',\n selected: 'boolean',\n};\n\n/**\n * Precomputed `[name, kind]` pairs of SERIALIZED_PROPERTIES. Hoisted to module\n * scope so `serializeAttributes` does not allocate a fresh entries array for\n * every element serialized (measured hot: ~80k elements on the 10k-row route).\n */\nconst SERIALIZED_PROPERTY_ENTRIES: ReadonlyArray<readonly [string, 'attr' | 'boolean']> =\n Object.entries(SERIALIZED_PROPERTIES) as Array<[string, 'attr' | 'boolean']>;\n\n// Fast-path escaping. The chained `.replace(/…/g, …)` form makes 3–4 full\n// passes and allocates an intermediate string per pass even when nothing needs\n// escaping. These variants scan once and, in the overwhelmingly common case of\n// no special character, return the input unchanged (zero allocation). Output is\n// byte-identical to the chained form (verified over the real SSR corpus).\nconst TEXT_SPECIAL = /[&<>]/;\nconst ATTR_SPECIAL = /[&<>\"]/;\n\n/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n if (!TEXT_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n if (!ATTR_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n case 34: esc = '"'; break; // \"\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\nfunction serializeAttributes(el: ServerElement): string {\n const parts: string[] = [];\n\n for (const [name, value] of el.attributes) {\n if (value === '') {\n parts.push(` ${name}`);\n } else {\n parts.push(` ${name}=\"${escapeHtmlAttr(value)}\"`);\n }\n }\n\n // Read the raw backing field (may be null): most elements have no JS\n // properties, so skipping the whole loop avoids touching a store that was\n // never allocated (§5).\n const props = el._properties;\n if (props !== null) {\n for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {\n if (!props.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = props.get(name);\n if (kind === 'boolean') {\n if (raw === true) parts.push(` ${name}`);\n } else {\n if (raw !== undefined && raw !== null) {\n parts.push(` ${name}=\"${escapeHtmlAttr(String(raw))}\"`);\n }\n }\n }\n }\n\n const style = el._style;\n if (style !== null && !style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(style.toCss())}\"`);\n }\n\n return parts.join('');\n}\n\n/** Serialize a single server node (element/text/comment/fragment) to HTML. */\nexport function serializeServerNode(node: ServerNode): string {\n switch (node.kind) {\n case 'text':\n return escapeHtmlText((node as ServerText).data);\n case 'comment':\n return `<!--${(node as ServerComment).data}-->`;\n case 'fragment':\n return serializeChildren(node as ServerFragment);\n case 'element': {\n const el = node as ServerElement;\n const tag = el.tagName;\n const attrs = serializeAttributes(el);\n if (VOID_ELEMENTS.has(tag)) {\n return `<${tag}${attrs}>`;\n }\n return `<${tag}${attrs}>${serializeChildren(el)}</${tag}>`;\n }\n }\n}\n\n/** Serialize the children of an element or fragment (its \"inner HTML\"). */\nexport function serializeChildren(node: ServerElement | ServerFragment): string {\n let out = '';\n for (const child of node.children) {\n out += serializeServerNode(child);\n }\n return out;\n}\n","/**\n * Server implementation of `DOMAdapter`.\n *\n * Builds a lightweight in-memory tree (see `server-node.ts`) instead of touching\n * a real browser DOM, then lets the caller serialize it to an HTML string. It is\n * completely free of browser globals, so the exact same renderer that runs in\n * the browser can produce HTML on the server.\n *\n * The `DOMAdapter` interface is typed against the lib DOM types (`Element`,\n * `Node`, `Text`, …). Our server nodes structurally stand in for those at\n * runtime, so the boundary uses `as unknown as` casts in one place. Everything\n * inside operates on the real server-node model.\n */\n\nimport type { DOMAdapter } from './adapter.js';\nimport {\n ServerElement,\n ServerText,\n ServerComment,\n ServerFragment,\n serializeChildren,\n serializeServerNode,\n type ServerNode,\n type ServerParent,\n} from './server-node.js';\n\nfunction asServer(node: unknown): ServerNode {\n return node as unknown as ServerNode;\n}\nfunction asParent(node: unknown): ServerParent {\n return node as unknown as ServerParent;\n}\n\nexport class ServerDOMAdapter implements DOMAdapter {\n createElement(tag: string, _ns?: string): Element {\n return new ServerElement(tag) as unknown as Element;\n }\n\n createTextNode(data: string): Text {\n return new ServerText(data) as unknown as Text;\n }\n\n createComment(data: string): Comment {\n return new ServerComment(data) as unknown as Comment;\n }\n\n createFragment(): DocumentFragment {\n return new ServerFragment() as unknown as DocumentFragment;\n }\n\n appendChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n p.children.push(c);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n if (reference === null) {\n p.children.push(c);\n return;\n }\n const ref = asServer(reference);\n const idx = p.children.indexOf(ref);\n if (idx === -1) p.children.push(c);\n else p.children.splice(idx, 0, c);\n }\n\n removeChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n const idx = p.children.indexOf(c);\n if (idx !== -1) {\n p.children.splice(idx, 1);\n c.parent = null;\n }\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n const p = asParent(parent);\n const nc = asServer(newChild);\n const oc = asServer(oldChild);\n const idx = p.children.indexOf(oc);\n if (idx === -1) return;\n this._detach(nc);\n nc.parent = p;\n p.children.splice(idx, 1, nc);\n oc.parent = null;\n }\n\n private _detach(node: ServerNode): void {\n if (node.parent !== null) {\n const siblings = node.parent.children;\n const idx = siblings.indexOf(node);\n if (idx !== -1) siblings.splice(idx, 1);\n node.parent = null;\n }\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n (element as unknown as ServerElement).attributes.set(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n (element as unknown as ServerElement).attributes.delete(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return (element as unknown as ServerElement).attributes.get(name) ?? null;\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as ServerElement).properties.set(name, value);\n }\n\n setTextContent(node: Node, text: string): void {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n el.children.length = 0;\n const t = new ServerText(text);\n t.parent = el;\n el.children.push(t);\n } else if (n.kind === 'text') {\n (n as ServerText).data = text;\n }\n }\n\n getTextContent(node: Node): string | null {\n const n = asServer(node);\n if (n.kind === 'text') return (n as ServerText).data;\n if (n.kind === 'element' || n.kind === 'fragment') {\n let out = '';\n for (const c of (n as ServerElement | ServerFragment).children) {\n out += this.getTextContent(c as unknown as Node) ?? '';\n }\n return out;\n }\n return null;\n }\n\n // Server nodes never dispatch events — listeners are a no-op on the server.\n addEventListener(): void {\n /* no-op on the server */\n }\n removeEventListener(): void {\n /* no-op on the server */\n }\n\n querySelector(): Element | null {\n return null;\n }\n querySelectorAll(): NodeListOf<Element> {\n return [] as unknown as NodeListOf<Element>;\n }\n getElementById(): Element | null {\n return null;\n }\n\n focus(): void {\n // No focus concept on the server — intentional no-op (SSR-safe).\n }\n\n isElement(node: Node): node is Element {\n return asServer(node).kind === 'element';\n }\n\n isTextNode(node: Node): node is Text {\n return asServer(node).kind === 'text';\n }\n\n tagName(element: Element): string {\n return (element as unknown as ServerElement).tagName;\n }\n\n parentNode(node: Node): Node | null {\n return (asServer(node).parent as unknown as Node | null) ?? null;\n }\n\n nextSibling(node: Node): Node | null {\n const n = asServer(node);\n const parent = n.parent;\n if (parent === null) return null;\n const idx = parent.children.indexOf(n);\n if (idx === -1 || idx + 1 >= parent.children.length) return null;\n return parent.children[idx + 1] as unknown as Node;\n }\n\n firstChild(node: Node): Node | null {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n return (el.children[0] as unknown as Node) ?? null;\n }\n return null;\n }\n\n childNodes(node: Node): Node[] {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return (n as ServerElement | ServerFragment).children as unknown as Node[];\n }\n return [];\n }\n\n // ── Server-only ────────────────────────────────────────────────────────────\n\n /** Serialize a node's children (\"inner HTML\") to an HTML string. */\n serializeInner(node: Node): string {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return serializeChildren(n as ServerElement | ServerFragment);\n }\n return '';\n }\n\n /** Serialize a node (including itself) to an HTML string. */\n serializeOuter(node: Node): string {\n return serializeServerNode(asServer(node));\n }\n}\n\n// `/* @__PURE__ */`: this singleton is a convenience export only (no internal\n// runtime path references it). Marking construction pure lets bundlers drop it —\n// and with it the whole server serializer chain (serializeServerNode/escape/\n// VOID_ELEMENTS) — out of client bundles that never import SSR. Without this,\n// the un-annotated `new` is treated as a side effect and retained everywhere.\nexport const serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();\n","/**\n * Focus helpers built on the {@link DOMAdapter} abstraction.\n *\n * These are the minimal, genuinely-useful focus operations an app needs:\n * focus a specific element (e.g. the first field when a route or modal opens)\n * or focus the first focusable element inside a container (e.g. move focus\n * into a dialog). Both go through the adapter, so they are no-ops on the server\n * (`ServerDOMAdapter.querySelector` returns null / `focus` does nothing) and\n * therefore safe to call from universal code.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\n/** Default selector for natively focusable / tabbable elements. */\nexport const FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Focus the element with the given id, scoped to `root`.\n * Returns true if an element was found and focused.\n */\nexport function focusById(dom: DOMAdapter, root: Element | Document, id: string): boolean {\n const el = dom.querySelector(root, `[id=\"${id}\"]`);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Focus the first focusable element inside `container`.\n * Returns true if a focusable element was found and focused.\n */\nexport function focusFirst(\n dom: DOMAdapter,\n container: Element | Document,\n selector: string = FOCUSABLE_SELECTOR,\n): boolean {\n const el = dom.querySelector(container, selector);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser-adapter.ts","../src/server-node.ts","../src/server-adapter.ts","../src/focus.ts"],"sourcesContent":["/**\n * Browser implementation of DOMAdapter — delegates directly to browser APIs.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\nexport class BrowserDOMAdapter implements DOMAdapter {\n createElement(tag: string, ns?: string): Element {\n if (ns !== undefined) {\n return document.createElementNS(ns, tag);\n }\n return document.createElement(tag);\n }\n\n createTextNode(data: string): Text {\n return document.createTextNode(data);\n }\n\n createComment(data: string): Comment {\n return document.createComment(data);\n }\n\n createFragment(): DocumentFragment {\n return document.createDocumentFragment();\n }\n\n appendChild(parent: Node, child: Node): void {\n parent.appendChild(child);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n parent.insertBefore(child, reference);\n }\n\n removeChild(parent: Node, child: Node): void {\n parent.removeChild(child);\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n parent.replaceChild(newChild, oldChild);\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n element.setAttribute(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n element.removeAttribute(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return element.getAttribute(name);\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as Record<string, unknown>)[name] = value;\n }\n\n setTextContent(node: Node, text: string): void {\n node.textContent = text;\n }\n\n getTextContent(node: Node): string | null {\n return node.textContent;\n }\n\n addEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ): void {\n target.addEventListener(type, handler, options);\n }\n\n removeEventListener(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: EventListenerOptions,\n ): void {\n target.removeEventListener(type, handler, options);\n }\n\n querySelector(root: Element | Document, selector: string): Element | null {\n return root.querySelector(selector);\n }\n\n querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element> {\n return root.querySelectorAll(selector);\n }\n\n getElementById(id: string): Element | null {\n return document.getElementById(id);\n }\n\n focus(element: Element): void {\n (element as unknown as { focus?: () => void }).focus?.();\n }\n\n body(): Element | null {\n return document.body ?? null;\n }\n\n activeElement(): Element | null {\n return document.activeElement ?? null;\n }\n\n contains(ancestor: Element, node: Node): boolean {\n return ancestor.contains(node);\n }\n\n matches(element: Element, selector: string): boolean {\n return typeof element.matches === 'function' && element.matches(selector);\n }\n\n isElement(node: Node): node is Element {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n\n isTextNode(node: Node): node is Text {\n return node.nodeType === Node.TEXT_NODE;\n }\n\n tagName(element: Element): string {\n return element.tagName.toLowerCase();\n }\n\n parentNode(node: Node): Node | null {\n return node.parentNode;\n }\n\n nextSibling(node: Node): Node | null {\n return node.nextSibling;\n }\n\n firstChild(node: Node): Node | null {\n return node.firstChild;\n }\n\n childNodes(node: Node): Node[] {\n return Array.from(node.childNodes);\n }\n}\n\n// `/* @__PURE__ */`: convenience singleton, unreferenced by internal runtime\n// paths. Marking construction pure lets bundlers drop it when unused instead of\n// retaining it (and the BrowserDOMAdapter class) as an import-time side effect.\nexport const browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();\n","/**\n * Server-side DOM node model.\n *\n * A tiny, dependency-free tree of plain objects that mirrors just enough of the\n * browser DOM for StreetUI's renderer to build a tree on the server and\n * serialize it to an HTML string. There is NO browser global here — these are\n * ordinary classes usable in any JavaScript environment (Node, workers, tests).\n *\n * The renderer never touches these types directly; it goes through the\n * `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into\n * operations on this model.\n */\n\nexport type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment' | 'raw';\n\nexport interface ServerNode {\n readonly kind: ServerNodeKind;\n parent: ServerParent | null;\n}\n\nexport type ServerParent = ServerElement | ServerFragment;\n\n/** A minimal inline-style holder mirroring `element.style.setProperty`. */\nexport class ServerStyle {\n readonly declarations = new Map<string, string>();\n setProperty(name: string, value: string): void {\n this.declarations.set(name, value);\n }\n get isEmpty(): boolean {\n return this.declarations.size === 0;\n }\n toCss(): string {\n return [...this.declarations.entries()].map(([k, v]) => `${k}: ${v}`).join('; ');\n }\n}\n\nexport class ServerText implements ServerNode {\n readonly kind = 'text' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerComment implements ServerNode {\n readonly kind = 'comment' as const;\n parent: ServerParent | null = null;\n data: string;\n constructor(data: string) {\n this.data = data;\n }\n}\n\nexport class ServerFragment implements ServerNode {\n readonly kind = 'fragment' as const;\n parent: ServerParent | null = null;\n readonly children: ServerNode[] = [];\n}\n\n/**\n * A pre-serialized, verbatim HTML fragment (v1.7 static SSR plan).\n *\n * Emitted for provably-static subtrees whose HTML the compiler-derived static\n * SSR plan already computed once. Serializing this node copies its stored\n * string directly — it allocates no ServerElement/ServerText, no attribute Map\n * and no children array for the collapsed subtree. The stored `html` is\n * produced by the exact same mount + serialize pipeline as the runtime path, so\n * the output is byte-identical (the v1.7 byte-identity gate proves this).\n *\n * This node is SSR-only: it is created solely via `ServerDOMAdapter.createRawHTML`\n * on the server render path and never appears in a browser build.\n */\nexport class ServerRawHTML implements ServerNode {\n readonly kind = 'raw' as const;\n parent: ServerParent | null = null;\n readonly html: string;\n constructor(html: string) {\n this.html = html;\n }\n}\n\nexport class ServerElement implements ServerNode {\n readonly kind = 'element' as const;\n parent: ServerParent | null = null;\n readonly tagName: string;\n readonly attributes = new Map<string, string>();\n readonly children: ServerNode[] = [];\n\n // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS\n // properties or inline styles (measured, §5: 1 of 80,029 elements uses\n // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus\n // a `ServerStyle` (which itself holds a Map) per element wasted ~240k\n // allocations per /users render — all in the dominant mount phase. These are\n // created on first WRITE via the `properties`/`style` getters; the serializer\n // reads the raw `_properties`/`_style` fields so a READ never forces an\n // allocation. Output is byte-identical: an unset store previously serialized\n // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store\n // is skipped the same way.\n _properties: Map<string, unknown> | null = null;\n _style: ServerStyle | null = null;\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\n }\n\n /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */\n get properties(): Map<string, unknown> {\n return (this._properties ??= new Map());\n }\n\n /** Inline-style holder mirroring `element.style`. Allocated on first access. */\n get style(): ServerStyle {\n return (this._style ??= new ServerStyle());\n }\n}\n\n// ── HTML serialization ─────────────────────────────────────────────────────────\n\n/**\n * HTML \"void\" elements — self-closing, never given a closing tag or children.\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n]);\n\n/**\n * Element properties that should be reflected into the serialized HTML so the\n * hydrated DOM carries the same initial state. `value`/`checked` matter for\n * form controls whose live state is a JS property, not an attribute.\n */\nconst SERIALIZED_PROPERTIES: Record<string, 'attr' | 'boolean'> = {\n value: 'attr',\n checked: 'boolean',\n selected: 'boolean',\n};\n\n/**\n * Precomputed `[name, kind]` pairs of SERIALIZED_PROPERTIES. Hoisted to module\n * scope so `serializeAttributes` does not allocate a fresh entries array for\n * every element serialized (measured hot: ~80k elements on the 10k-row route).\n */\nconst SERIALIZED_PROPERTY_ENTRIES: ReadonlyArray<readonly [string, 'attr' | 'boolean']> =\n Object.entries(SERIALIZED_PROPERTIES) as Array<[string, 'attr' | 'boolean']>;\n\n// Fast-path escaping. The chained `.replace(/…/g, …)` form makes 3–4 full\n// passes and allocates an intermediate string per pass even when nothing needs\n// escaping. These variants scan once and, in the overwhelmingly common case of\n// no special character, return the input unchanged (zero allocation). Output is\n// byte-identical to the chained form (verified over the real SSR corpus).\nconst TEXT_SPECIAL = /[&<>]/;\nconst ATTR_SPECIAL = /[&<>\"]/;\n\n/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n if (!TEXT_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n if (!ATTR_SPECIAL.test(value)) return value;\n let out = '';\n let last = 0;\n for (let i = 0; i < value.length; i++) {\n let esc: string;\n switch (value.charCodeAt(i)) {\n case 38: esc = '&'; break; // &\n case 60: esc = '<'; break; // <\n case 62: esc = '>'; break; // >\n case 34: esc = '"'; break; // \"\n default: continue;\n }\n out += value.slice(last, i) + esc;\n last = i + 1;\n }\n return out + value.slice(last);\n}\n\nfunction serializeAttributes(el: ServerElement): string {\n const parts: string[] = [];\n\n for (const [name, value] of el.attributes) {\n if (value === '') {\n parts.push(` ${name}`);\n } else {\n parts.push(` ${name}=\"${escapeHtmlAttr(value)}\"`);\n }\n }\n\n // Read the raw backing field (may be null): most elements have no JS\n // properties, so skipping the whole loop avoids touching a store that was\n // never allocated (§5).\n const props = el._properties;\n if (props !== null) {\n for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {\n if (!props.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = props.get(name);\n if (kind === 'boolean') {\n if (raw === true) parts.push(` ${name}`);\n } else {\n if (raw !== undefined && raw !== null) {\n parts.push(` ${name}=\"${escapeHtmlAttr(String(raw))}\"`);\n }\n }\n }\n }\n\n const style = el._style;\n if (style !== null && !style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(style.toCss())}\"`);\n }\n\n return parts.join('');\n}\n\n/** Serialize a single server node (element/text/comment/fragment/raw) to HTML. */\nexport function serializeServerNode(node: ServerNode): string {\n switch (node.kind) {\n case 'text':\n return escapeHtmlText((node as ServerText).data);\n case 'comment':\n return `<!--${(node as ServerComment).data}-->`;\n case 'fragment':\n return serializeChildren(node as ServerFragment);\n case 'raw':\n // Verbatim: the string was produced by this same serializer for a static\n // subtree, so it is already correctly escaped. Copy it as-is (§8).\n return (node as ServerRawHTML).html;\n case 'element': {\n const el = node as ServerElement;\n const tag = el.tagName;\n const attrs = serializeAttributes(el);\n if (VOID_ELEMENTS.has(tag)) {\n return `<${tag}${attrs}>`;\n }\n return `<${tag}${attrs}>${serializeChildren(el)}</${tag}>`;\n }\n }\n}\n\n/** Serialize the children of an element or fragment (its \"inner HTML\"). */\nexport function serializeChildren(node: ServerElement | ServerFragment): string {\n let out = '';\n for (const child of node.children) {\n out += serializeServerNode(child);\n }\n return out;\n}\n","/**\n * Server implementation of `DOMAdapter`.\n *\n * Builds a lightweight in-memory tree (see `server-node.ts`) instead of touching\n * a real browser DOM, then lets the caller serialize it to an HTML string. It is\n * completely free of browser globals, so the exact same renderer that runs in\n * the browser can produce HTML on the server.\n *\n * The `DOMAdapter` interface is typed against the lib DOM types (`Element`,\n * `Node`, `Text`, …). Our server nodes structurally stand in for those at\n * runtime, so the boundary uses `as unknown as` casts in one place. Everything\n * inside operates on the real server-node model.\n */\n\nimport type { DOMAdapter } from './adapter.js';\nimport {\n ServerElement,\n ServerText,\n ServerComment,\n ServerFragment,\n ServerRawHTML,\n serializeChildren,\n serializeServerNode,\n type ServerNode,\n type ServerParent,\n} from './server-node.js';\n\nfunction asServer(node: unknown): ServerNode {\n return node as unknown as ServerNode;\n}\nfunction asParent(node: unknown): ServerParent {\n return node as unknown as ServerParent;\n}\n\nexport class ServerDOMAdapter implements DOMAdapter {\n createElement(tag: string, _ns?: string): Element {\n return new ServerElement(tag) as unknown as Element;\n }\n\n createTextNode(data: string): Text {\n return new ServerText(data) as unknown as Text;\n }\n\n createComment(data: string): Comment {\n return new ServerComment(data) as unknown as Comment;\n }\n\n createFragment(): DocumentFragment {\n return new ServerFragment() as unknown as DocumentFragment;\n }\n\n /**\n * Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).\n * Server-only: the browser adapter does not implement this, and the renderer\n * fast path only invokes it when a static SSR plan is present (SSR). The\n * stored HTML was produced by this same serializer, so it is emitted as-is.\n */\n createRawHTML(html: string): Node {\n return new ServerRawHTML(html) as unknown as Node;\n }\n\n appendChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n p.children.push(c);\n }\n\n insertBefore(parent: Node, child: Node, reference: Node | null): void {\n const p = asParent(parent);\n const c = asServer(child);\n this._detach(c);\n c.parent = p;\n if (reference === null) {\n p.children.push(c);\n return;\n }\n const ref = asServer(reference);\n const idx = p.children.indexOf(ref);\n if (idx === -1) p.children.push(c);\n else p.children.splice(idx, 0, c);\n }\n\n removeChild(parent: Node, child: Node): void {\n const p = asParent(parent);\n const c = asServer(child);\n const idx = p.children.indexOf(c);\n if (idx !== -1) {\n p.children.splice(idx, 1);\n c.parent = null;\n }\n }\n\n replaceChild(parent: Node, newChild: Node, oldChild: Node): void {\n const p = asParent(parent);\n const nc = asServer(newChild);\n const oc = asServer(oldChild);\n const idx = p.children.indexOf(oc);\n if (idx === -1) return;\n this._detach(nc);\n nc.parent = p;\n p.children.splice(idx, 1, nc);\n oc.parent = null;\n }\n\n private _detach(node: ServerNode): void {\n if (node.parent !== null) {\n const siblings = node.parent.children;\n const idx = siblings.indexOf(node);\n if (idx !== -1) siblings.splice(idx, 1);\n node.parent = null;\n }\n }\n\n setAttribute(element: Element, name: string, value: string): void {\n (element as unknown as ServerElement).attributes.set(name, value);\n }\n\n removeAttribute(element: Element, name: string): void {\n (element as unknown as ServerElement).attributes.delete(name);\n }\n\n getAttribute(element: Element, name: string): string | null {\n return (element as unknown as ServerElement).attributes.get(name) ?? null;\n }\n\n setProperty(element: Element, name: string, value: unknown): void {\n (element as unknown as ServerElement).properties.set(name, value);\n }\n\n setTextContent(node: Node, text: string): void {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n el.children.length = 0;\n const t = new ServerText(text);\n t.parent = el;\n el.children.push(t);\n } else if (n.kind === 'text') {\n (n as ServerText).data = text;\n }\n }\n\n getTextContent(node: Node): string | null {\n const n = asServer(node);\n if (n.kind === 'text') return (n as ServerText).data;\n if (n.kind === 'element' || n.kind === 'fragment') {\n let out = '';\n for (const c of (n as ServerElement | ServerFragment).children) {\n out += this.getTextContent(c as unknown as Node) ?? '';\n }\n return out;\n }\n return null;\n }\n\n // Server nodes never dispatch events — listeners are a no-op on the server.\n addEventListener(): void {\n /* no-op on the server */\n }\n removeEventListener(): void {\n /* no-op on the server */\n }\n\n querySelector(): Element | null {\n return null;\n }\n querySelectorAll(): NodeListOf<Element> {\n return [] as unknown as NodeListOf<Element>;\n }\n getElementById(): Element | null {\n return null;\n }\n\n focus(): void {\n // No focus concept on the server — intentional no-op (SSR-safe).\n }\n\n body(): Element | null {\n // No document on the server — portals degrade to inline rendering.\n return null;\n }\n\n activeElement(): Element | null {\n return null;\n }\n\n contains(_ancestor: Element, _node: Node): boolean {\n return false;\n }\n\n matches(_element: Element, _selector: string): boolean {\n return false;\n }\n\n isElement(node: Node): node is Element {\n return asServer(node).kind === 'element';\n }\n\n isTextNode(node: Node): node is Text {\n return asServer(node).kind === 'text';\n }\n\n tagName(element: Element): string {\n return (element as unknown as ServerElement).tagName;\n }\n\n parentNode(node: Node): Node | null {\n return (asServer(node).parent as unknown as Node | null) ?? null;\n }\n\n nextSibling(node: Node): Node | null {\n const n = asServer(node);\n const parent = n.parent;\n if (parent === null) return null;\n const idx = parent.children.indexOf(n);\n if (idx === -1 || idx + 1 >= parent.children.length) return null;\n return parent.children[idx + 1] as unknown as Node;\n }\n\n firstChild(node: Node): Node | null {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n const el = n as ServerElement | ServerFragment;\n return (el.children[0] as unknown as Node) ?? null;\n }\n return null;\n }\n\n childNodes(node: Node): Node[] {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return (n as ServerElement | ServerFragment).children as unknown as Node[];\n }\n return [];\n }\n\n // ── Server-only ────────────────────────────────────────────────────────────\n\n /** Serialize a node's children (\"inner HTML\") to an HTML string. */\n serializeInner(node: Node): string {\n const n = asServer(node);\n if (n.kind === 'element' || n.kind === 'fragment') {\n return serializeChildren(n as ServerElement | ServerFragment);\n }\n return '';\n }\n\n /** Serialize a node (including itself) to an HTML string. */\n serializeOuter(node: Node): string {\n return serializeServerNode(asServer(node));\n }\n}\n\n// `/* @__PURE__ */`: this singleton is a convenience export only (no internal\n// runtime path references it). Marking construction pure lets bundlers drop it —\n// and with it the whole server serializer chain (serializeServerNode/escape/\n// VOID_ELEMENTS) — out of client bundles that never import SSR. Without this,\n// the un-annotated `new` is treated as a side effect and retained everywhere.\nexport const serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();\n","/**\n * Focus helpers built on the {@link DOMAdapter} abstraction.\n *\n * These are the minimal, genuinely-useful focus operations an app needs:\n * focus a specific element (e.g. the first field when a route or modal opens)\n * or focus the first focusable element inside a container (e.g. move focus\n * into a dialog). Both go through the adapter, so they are no-ops on the server\n * (`ServerDOMAdapter.querySelector` returns null / `focus` does nothing) and\n * therefore safe to call from universal code.\n */\n\nimport type { DOMAdapter } from './adapter.js';\n\n/** Default selector for natively focusable / tabbable elements. */\nexport const FOCUSABLE_SELECTOR =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Focus the element with the given id, scoped to `root`.\n * Returns true if an element was found and focused.\n */\nexport function focusById(dom: DOMAdapter, root: Element | Document, id: string): boolean {\n const el = dom.querySelector(root, `[id=\"${id}\"]`);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Focus the first focusable element inside `container`.\n * Returns true if a focusable element was found and focused.\n */\nexport function focusFirst(\n dom: DOMAdapter,\n container: Element | Document,\n selector: string = FOCUSABLE_SELECTOR,\n): boolean {\n const el = dom.querySelector(container, selector);\n if (el === null) return false;\n dom.focus(el);\n return true;\n}\n\n/**\n * Ordered list of focusable/tabbable descendants of `container`.\n * Re-checks each candidate against the selector so elements disabled after the\n * initial query (e.g. a button toggled to `disabled`) are excluded.\n */\nexport function getFocusable(\n dom: DOMAdapter,\n container: Element,\n selector: string = FOCUSABLE_SELECTOR,\n): Element[] {\n return Array.from(dom.querySelectorAll(container, selector)).filter((el) =>\n dom.matches(el, selector),\n );\n}\n\n/**\n * Capture the currently-focused element so it can be restored later (e.g. when\n * a dialog closes). Returns null on the server or when nothing is focused.\n */\nexport function saveFocus(dom: DOMAdapter): Element | null {\n return dom.activeElement();\n}\n\n/** Restore focus to a previously {@link saveFocus}-d element. No-op if null. */\nexport function restoreFocus(dom: DOMAdapter, saved: Element | null): void {\n if (saved !== null) dom.focus(saved);\n}\n\n/**\n * Move focus into `container` on open: the element with id `initialFocusId` if\n * given and present, otherwise the first focusable element. Server-safe no-op.\n */\nexport function focusInitial(\n dom: DOMAdapter,\n container: Element,\n initialFocusId?: string,\n): void {\n if (initialFocusId !== undefined && focusById(dom, container, initialFocusId)) return;\n focusFirst(dom, container);\n}\n\n/**\n * Trap Tab / Shift+Tab focus within `container` (wrap-around at both ends).\n * Attaches a keydown listener to the container and returns a cleanup function\n * that detaches it. Server-safe: `addEventListener` is a no-op, and the returned\n * cleanup is still callable.\n */\nexport function trapFocus(dom: DOMAdapter, container: Element): () => void {\n const onKeydown = ((event: KeyboardEvent): void => {\n if (event.key !== 'Tab') return;\n const items = getFocusable(dom, container);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = dom.activeElement();\n if (active === null || !dom.contains(container, active)) {\n event.preventDefault();\n dom.focus(first);\n } else if (event.shiftKey && active === first) {\n event.preventDefault();\n dom.focus(last);\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n dom.focus(first);\n }\n }) as EventListener;\n dom.addEventListener(container, 'keydown', onKeydown);\n return () => dom.removeEventListener(container, 'keydown', onKeydown);\n}\n\n/**\n * Modal containment: if focus moves to an element outside `container`, redirect\n * it back inside. Listens on the document body (focusin bubbles there) and\n * returns a cleanup function. Server-safe no-op (body() is null).\n */\nexport function containFocus(dom: DOMAdapter, container: Element): () => void {\n const body = dom.body();\n if (body === null) return () => {};\n const onFocusIn = ((event: FocusEvent): void => {\n const target = event.target as Node | null;\n if (target !== null && !dom.contains(container, target)) {\n focusFirst(dom, container);\n }\n }) as EventListener;\n dom.addEventListener(body, 'focusin', onFocusIn);\n return () => dom.removeEventListener(body, 'focusin', onFocusIn);\n}\n\n/**\n * Invoke `handler` when Escape is pressed while focus is within `target`.\n * Returns a cleanup function. Server-safe no-op.\n */\nexport function onEscape(dom: DOMAdapter, target: Element, handler: () => void): () => void {\n const onKeydown = ((event: KeyboardEvent): void => {\n if (event.key === 'Escape') handler();\n }) as EventListener;\n dom.addEventListener(target, 'keydown', onKeydown);\n return () => dom.removeEventListener(target, 'keydown', onKeydown);\n}\n\n"],"mappings":"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","names":[]}
|