@streetui/dom 1.3.0 → 1.5.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 CHANGED
@@ -126,7 +126,7 @@ var BrowserDOMAdapter = class {
126
126
  return Array.from(node.childNodes);
127
127
  }
128
128
  };
129
- var browserDOMAdapter = new BrowserDOMAdapter();
129
+ var browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();
130
130
 
131
131
  // src/server-node.ts
132
132
  var ServerStyle = class {
@@ -167,13 +167,30 @@ var ServerElement = class {
167
167
  parent = null;
168
168
  tagName;
169
169
  attributes = /* @__PURE__ */ new Map();
170
- /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
171
- properties = /* @__PURE__ */ new Map();
172
170
  children = [];
173
- style = new ServerStyle();
171
+ // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS
172
+ // properties or inline styles (measured, §5: 1 of 80,029 elements uses
173
+ // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus
174
+ // a `ServerStyle` (which itself holds a Map) per element wasted ~240k
175
+ // allocations per /users render — all in the dominant mount phase. These are
176
+ // created on first WRITE via the `properties`/`style` getters; the serializer
177
+ // reads the raw `_properties`/`_style` fields so a READ never forces an
178
+ // allocation. Output is byte-identical: an unset store previously serialized
179
+ // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store
180
+ // is skipped the same way.
181
+ _properties = null;
182
+ _style = null;
174
183
  constructor(tagName) {
175
184
  this.tagName = tagName.toLowerCase();
176
185
  }
186
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */
187
+ get properties() {
188
+ return this._properties ??= /* @__PURE__ */ new Map();
189
+ }
190
+ /** Inline-style holder mirroring `element.style`. Allocated on first access. */
191
+ get style() {
192
+ return this._style ??= new ServerStyle();
193
+ }
177
194
  };
178
195
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
179
196
  "area",
@@ -196,11 +213,66 @@ var SERIALIZED_PROPERTIES = {
196
213
  checked: "boolean",
197
214
  selected: "boolean"
198
215
  };
216
+ var SERIALIZED_PROPERTY_ENTRIES = Object.entries(SERIALIZED_PROPERTIES);
217
+ var TEXT_SPECIAL = /[&<>]/;
218
+ var ATTR_SPECIAL = /[&<>"]/;
199
219
  function escapeHtmlText(value) {
200
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
220
+ if (!TEXT_SPECIAL.test(value)) return value;
221
+ let out = "";
222
+ let last = 0;
223
+ for (let i = 0; i < value.length; i++) {
224
+ let esc;
225
+ switch (value.charCodeAt(i)) {
226
+ case 38:
227
+ esc = "&amp;";
228
+ break;
229
+ // &
230
+ case 60:
231
+ esc = "&lt;";
232
+ break;
233
+ // <
234
+ case 62:
235
+ esc = "&gt;";
236
+ break;
237
+ // >
238
+ default:
239
+ continue;
240
+ }
241
+ out += value.slice(last, i) + esc;
242
+ last = i + 1;
243
+ }
244
+ return out + value.slice(last);
201
245
  }
202
246
  function escapeHtmlAttr(value) {
203
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
247
+ if (!ATTR_SPECIAL.test(value)) return value;
248
+ let out = "";
249
+ let last = 0;
250
+ for (let i = 0; i < value.length; i++) {
251
+ let esc;
252
+ switch (value.charCodeAt(i)) {
253
+ case 38:
254
+ esc = "&amp;";
255
+ break;
256
+ // &
257
+ case 60:
258
+ esc = "&lt;";
259
+ break;
260
+ // <
261
+ case 62:
262
+ esc = "&gt;";
263
+ break;
264
+ // >
265
+ case 34:
266
+ esc = "&quot;";
267
+ break;
268
+ // "
269
+ default:
270
+ continue;
271
+ }
272
+ out += value.slice(last, i) + esc;
273
+ last = i + 1;
274
+ }
275
+ return out + value.slice(last);
204
276
  }
205
277
  function serializeAttributes(el) {
206
278
  const parts = [];
@@ -211,20 +283,24 @@ function serializeAttributes(el) {
211
283
  parts.push(` ${name}="${escapeHtmlAttr(value)}"`);
212
284
  }
213
285
  }
214
- for (const [name, kind] of Object.entries(SERIALIZED_PROPERTIES)) {
215
- if (!el.properties.has(name)) continue;
216
- if (el.attributes.has(name)) continue;
217
- const raw = el.properties.get(name);
218
- if (kind === "boolean") {
219
- if (raw === true) parts.push(` ${name}`);
220
- } else {
221
- if (raw !== void 0 && raw !== null) {
222
- parts.push(` ${name}="${escapeHtmlAttr(String(raw))}"`);
286
+ const props = el._properties;
287
+ if (props !== null) {
288
+ for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {
289
+ if (!props.has(name)) continue;
290
+ if (el.attributes.has(name)) continue;
291
+ const raw = props.get(name);
292
+ if (kind === "boolean") {
293
+ if (raw === true) parts.push(` ${name}`);
294
+ } else {
295
+ if (raw !== void 0 && raw !== null) {
296
+ parts.push(` ${name}="${escapeHtmlAttr(String(raw))}"`);
297
+ }
223
298
  }
224
299
  }
225
300
  }
226
- if (!el.style.isEmpty && !el.attributes.has("style")) {
227
- parts.push(` style="${escapeHtmlAttr(el.style.toCss())}"`);
301
+ const style = el._style;
302
+ if (style !== null && !style.isEmpty && !el.attributes.has("style")) {
303
+ parts.push(` style="${escapeHtmlAttr(style.toCss())}"`);
228
304
  }
229
305
  return parts.join("");
230
306
  }
@@ -425,7 +501,7 @@ var ServerDOMAdapter = class {
425
501
  return serializeServerNode(asServer(node));
426
502
  }
427
503
  };
428
- var serverDOMAdapter = new ServerDOMAdapter();
504
+ var serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();
429
505
 
430
506
  // src/focus.ts
431
507
  var FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
@@ -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\nexport const browserDOMAdapter = 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 /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */\n readonly properties = new Map<string, unknown>();\n readonly children: ServerNode[] = [];\n readonly style = new ServerStyle();\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\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/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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 for (const [name, kind] of Object.entries(SERIALIZED_PROPERTIES)) {\n if (!el.properties.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = el.properties.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 if (!el.style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(el.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\nexport const serverDOMAdapter = 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 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 = '&amp;'; break; // &\n case 60: esc = '&lt;'; break; // <\n case 62: esc = '&gt;'; 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 = '&amp;'; break; // &\n case 60: esc = '&lt;'; break; // <\n case 62: esc = '&gt;'; break; // >\n case 34: esc = '&quot;'; 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":[]}
package/dist/index.d.cts CHANGED
@@ -123,11 +123,14 @@ declare class ServerElement implements ServerNode {
123
123
  parent: ServerParent | null;
124
124
  readonly tagName: string;
125
125
  readonly attributes: Map<string, string>;
126
- /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
127
- readonly properties: Map<string, unknown>;
128
126
  readonly children: ServerNode[];
129
- readonly style: ServerStyle;
127
+ _properties: Map<string, unknown> | null;
128
+ _style: ServerStyle | null;
130
129
  constructor(tagName: string);
130
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */
131
+ get properties(): Map<string, unknown>;
132
+ /** Inline-style holder mirroring `element.style`. Allocated on first access. */
133
+ get style(): ServerStyle;
131
134
  }
132
135
  /** Escape text node content. */
133
136
  declare function escapeHtmlText(value: string): string;
package/dist/index.d.ts CHANGED
@@ -123,11 +123,14 @@ declare class ServerElement implements ServerNode {
123
123
  parent: ServerParent | null;
124
124
  readonly tagName: string;
125
125
  readonly attributes: Map<string, string>;
126
- /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
127
- readonly properties: Map<string, unknown>;
128
126
  readonly children: ServerNode[];
129
- readonly style: ServerStyle;
127
+ _properties: Map<string, unknown> | null;
128
+ _style: ServerStyle | null;
130
129
  constructor(tagName: string);
130
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */
131
+ get properties(): Map<string, unknown>;
132
+ /** Inline-style holder mirroring `element.style`. Allocated on first access. */
133
+ get style(): ServerStyle;
131
134
  }
132
135
  /** Escape text node content. */
133
136
  declare function escapeHtmlText(value: string): string;
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ var BrowserDOMAdapter = class {
85
85
  return Array.from(node.childNodes);
86
86
  }
87
87
  };
88
- var browserDOMAdapter = new BrowserDOMAdapter();
88
+ var browserDOMAdapter = /* @__PURE__ */ new BrowserDOMAdapter();
89
89
 
90
90
  // src/server-node.ts
91
91
  var ServerStyle = class {
@@ -126,13 +126,30 @@ var ServerElement = class {
126
126
  parent = null;
127
127
  tagName;
128
128
  attributes = /* @__PURE__ */ new Map();
129
- /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
130
- properties = /* @__PURE__ */ new Map();
131
129
  children = [];
132
- style = new ServerStyle();
130
+ // Lazily-allocated stores. On the 10k-row SSR corpus ~0% of elements carry JS
131
+ // properties or inline styles (measured, §5: 1 of 80,029 elements uses
132
+ // `properties`, 0 use `style`), so eagerly allocating a `properties` Map plus
133
+ // a `ServerStyle` (which itself holds a Map) per element wasted ~240k
134
+ // allocations per /users render — all in the dominant mount phase. These are
135
+ // created on first WRITE via the `properties`/`style` getters; the serializer
136
+ // reads the raw `_properties`/`_style` fields so a READ never forces an
137
+ // allocation. Output is byte-identical: an unset store previously serialized
138
+ // to nothing (empty `properties.has(...)` / `style.isEmpty`), and a null store
139
+ // is skipped the same way.
140
+ _properties = null;
141
+ _style = null;
133
142
  constructor(tagName) {
134
143
  this.tagName = tagName.toLowerCase();
135
144
  }
145
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). Allocated on first access. */
146
+ get properties() {
147
+ return this._properties ??= /* @__PURE__ */ new Map();
148
+ }
149
+ /** Inline-style holder mirroring `element.style`. Allocated on first access. */
150
+ get style() {
151
+ return this._style ??= new ServerStyle();
152
+ }
136
153
  };
137
154
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
138
155
  "area",
@@ -155,11 +172,66 @@ var SERIALIZED_PROPERTIES = {
155
172
  checked: "boolean",
156
173
  selected: "boolean"
157
174
  };
175
+ var SERIALIZED_PROPERTY_ENTRIES = Object.entries(SERIALIZED_PROPERTIES);
176
+ var TEXT_SPECIAL = /[&<>]/;
177
+ var ATTR_SPECIAL = /[&<>"]/;
158
178
  function escapeHtmlText(value) {
159
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
179
+ if (!TEXT_SPECIAL.test(value)) return value;
180
+ let out = "";
181
+ let last = 0;
182
+ for (let i = 0; i < value.length; i++) {
183
+ let esc;
184
+ switch (value.charCodeAt(i)) {
185
+ case 38:
186
+ esc = "&amp;";
187
+ break;
188
+ // &
189
+ case 60:
190
+ esc = "&lt;";
191
+ break;
192
+ // <
193
+ case 62:
194
+ esc = "&gt;";
195
+ break;
196
+ // >
197
+ default:
198
+ continue;
199
+ }
200
+ out += value.slice(last, i) + esc;
201
+ last = i + 1;
202
+ }
203
+ return out + value.slice(last);
160
204
  }
161
205
  function escapeHtmlAttr(value) {
162
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
206
+ if (!ATTR_SPECIAL.test(value)) return value;
207
+ let out = "";
208
+ let last = 0;
209
+ for (let i = 0; i < value.length; i++) {
210
+ let esc;
211
+ switch (value.charCodeAt(i)) {
212
+ case 38:
213
+ esc = "&amp;";
214
+ break;
215
+ // &
216
+ case 60:
217
+ esc = "&lt;";
218
+ break;
219
+ // <
220
+ case 62:
221
+ esc = "&gt;";
222
+ break;
223
+ // >
224
+ case 34:
225
+ esc = "&quot;";
226
+ break;
227
+ // "
228
+ default:
229
+ continue;
230
+ }
231
+ out += value.slice(last, i) + esc;
232
+ last = i + 1;
233
+ }
234
+ return out + value.slice(last);
163
235
  }
164
236
  function serializeAttributes(el) {
165
237
  const parts = [];
@@ -170,20 +242,24 @@ function serializeAttributes(el) {
170
242
  parts.push(` ${name}="${escapeHtmlAttr(value)}"`);
171
243
  }
172
244
  }
173
- for (const [name, kind] of Object.entries(SERIALIZED_PROPERTIES)) {
174
- if (!el.properties.has(name)) continue;
175
- if (el.attributes.has(name)) continue;
176
- const raw = el.properties.get(name);
177
- if (kind === "boolean") {
178
- if (raw === true) parts.push(` ${name}`);
179
- } else {
180
- if (raw !== void 0 && raw !== null) {
181
- parts.push(` ${name}="${escapeHtmlAttr(String(raw))}"`);
245
+ const props = el._properties;
246
+ if (props !== null) {
247
+ for (const [name, kind] of SERIALIZED_PROPERTY_ENTRIES) {
248
+ if (!props.has(name)) continue;
249
+ if (el.attributes.has(name)) continue;
250
+ const raw = props.get(name);
251
+ if (kind === "boolean") {
252
+ if (raw === true) parts.push(` ${name}`);
253
+ } else {
254
+ if (raw !== void 0 && raw !== null) {
255
+ parts.push(` ${name}="${escapeHtmlAttr(String(raw))}"`);
256
+ }
182
257
  }
183
258
  }
184
259
  }
185
- if (!el.style.isEmpty && !el.attributes.has("style")) {
186
- parts.push(` style="${escapeHtmlAttr(el.style.toCss())}"`);
260
+ const style = el._style;
261
+ if (style !== null && !style.isEmpty && !el.attributes.has("style")) {
262
+ parts.push(` style="${escapeHtmlAttr(style.toCss())}"`);
187
263
  }
188
264
  return parts.join("");
189
265
  }
@@ -384,7 +460,7 @@ var ServerDOMAdapter = class {
384
460
  return serializeServerNode(asServer(node));
385
461
  }
386
462
  };
387
- var serverDOMAdapter = new ServerDOMAdapter();
463
+ var serverDOMAdapter = /* @__PURE__ */ new ServerDOMAdapter();
388
464
 
389
465
  // src/focus.ts
390
466
  var FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
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\nexport const browserDOMAdapter = 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 /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */\n readonly properties = new Map<string, unknown>();\n readonly children: ServerNode[] = [];\n readonly style = new ServerStyle();\n\n constructor(tagName: string) {\n this.tagName = tagName.toLowerCase();\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/** Escape text node content. */\nexport function escapeHtmlText(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/** Escape a double-quoted attribute value. */\nexport function escapeHtmlAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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 for (const [name, kind] of Object.entries(SERIALIZED_PROPERTIES)) {\n if (!el.properties.has(name)) continue;\n if (el.attributes.has(name)) continue; // an explicit attribute already won\n const raw = el.properties.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 if (!el.style.isEmpty && !el.attributes.has('style')) {\n parts.push(` style=\"${escapeHtmlAttr(el.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\nexport const serverDOMAdapter = 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 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 = '&amp;'; break; // &\n case 60: esc = '&lt;'; break; // <\n case 62: esc = '&gt;'; 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 = '&amp;'; break; // &\n case 60: esc = '&lt;'; break; // <\n case 62: esc = '&gt;'; break; // >\n case 34: esc = '&quot;'; 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streetui/dom",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "StreetUI DOM abstraction layer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",