@miaskiewicz/turbo-dom 0.5.8 → 0.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.
@@ -1,8 +1,9 @@
1
1
  // HTML serialization for innerHTML / outerHTML (WHATWG-ish fragment serializer).
2
2
 
3
+ // HTML "serializes as void" (Chrome 156 includes the legacy basefont / bgsound / frame / keygen).
3
4
  const VOID = new Set([
4
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
5
- 'meta', 'param', 'source', 'track', 'wbr',
5
+ 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input',
6
+ 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr',
6
7
  ]);
7
8
 
8
9
  // Elements whose content is raw text (not escaped).
@@ -11,14 +12,20 @@ const RAW_TEXT = new Set(['style', 'script', 'xmp', 'iframe', 'noembed', 'nofram
11
12
  function escapeText(s) {
12
13
  return s.replace(/&/g, '&amp;').replace(/ /g, '&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
13
14
  }
15
+ // attribute values also escape < and > (Chrome 156)
14
16
  function escapeAttr(s) {
15
- return s.replace(/&/g, '&amp;').replace(/ /g, '&nbsp;').replace(/"/g, '&quot;');
17
+ return s.replace(/&/g, '&amp;').replace(/ /g, '&nbsp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16
18
  }
17
19
 
18
20
  function serializeNode(node, out) {
19
21
  switch (node.nodeType) {
20
22
  case 1: { // element
21
- const tag = node.localName;
23
+ // HTML / SVG / MathML elements serialize their local name, other namespaces the
24
+ // qualified name; a createElementNS prefix is kept on all of them (Chrome 156:
25
+ // <h:br>, <svg:g>). Only HTML elements can be void or <template>, keyed on the local name.
26
+ const ns = node.__ns, html = ns === '', local = node.localName;
27
+ const tag = node.__prefix !== undefined ? `${node.__prefix}:${local}`
28
+ : html || ns === 'svg' || ns === 'math' ? local : node.tagName;
22
29
  out.push('<' + tag);
23
30
  // __attrs is lazy: buffer-backed elements leave it undefined (store __attrIdx).
24
31
  // Build from the SoA on first touch, matching every other __attrs read site.
@@ -28,8 +35,8 @@ function serializeNode(node, out) {
28
35
  out.push(` ${name}="${escapeAttr(a.value)}"`);
29
36
  }
30
37
  out.push('>');
31
- if (VOID.has(tag)) break;
32
- if (tag === 'template' && node.content) {
38
+ if (html && VOID.has(local)) break;
39
+ if (html && local === 'template' && node.content) {
33
40
  serializeChildren(node.content, out);
34
41
  } else {
35
42
  serializeChildren(node, out);
@@ -38,7 +45,8 @@ function serializeNode(node, out) {
38
45
  break;
39
46
  }
40
47
  case 3: { // text
41
- const parentTag = node.parentNode && node.parentNode.localName;
48
+ // raw text keys on the parent's prefix-free local name (Chrome 156: <h:script> escapes)
49
+ const parent = node.parentNode, parentTag = parent && parent.__prefix === undefined && parent.localName;
42
50
  out.push(RAW_TEXT.has(parentTag) ? node.data : escapeText(node.data));
43
51
  break;
44
52
  }
@@ -0,0 +1,100 @@
1
+ // Types for `@miaskiewicz/turbo-dom/runtime` (src/runtime/index.mjs).
2
+ //
3
+ // The runtime classes implement the DOM's shape but are not the lib.dom classes, so
4
+ // they are typed loosely here (members are `any`); the entry points are typed exactly.
5
+
6
+ /** A parser binding (native napi, wasm, or an injected one). */
7
+ export interface ParserBinding {
8
+ parse(html: string): unknown;
9
+ parseBuffer(html: string): unknown;
10
+ parseFragment(html: string, context?: string): unknown;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ export type ParserMode = 'wasm' | 'native' | 'auto';
15
+
16
+ export type CustomElementState = 'undefined' | 'failed' | 'uncustomized' | 'precustomized' | 'custom';
17
+
18
+ export interface EnvironmentOptions {
19
+ /** Parser selection, applied process-wide before the first parse. */
20
+ parser?: ParserMode;
21
+ /** The document URL (default `http://localhost/`). */
22
+ url?: string;
23
+ }
24
+
25
+ export interface Environment {
26
+ /** The lazy window (globals are created on first access). */
27
+ window: any;
28
+ document: Document;
29
+ /** The global names the tests have touched since the last reset. */
30
+ touched: Set<string>;
31
+ /** Every global name the window can provide. */
32
+ globalKeys: string[];
33
+ /** Re-point the document at a fresh (or new) parse and drop per-test state. */
34
+ reset(html?: string): void;
35
+ }
36
+
37
+ export declare function createEnvironment(html?: string, options?: EnvironmentOptions): Environment;
38
+
39
+ /** Inject a ready parser binding; `null` clears it. */
40
+ export declare function setParser(binding: ParserBinding | null): void;
41
+ /** Force a parser mode; `null` consults the environment again. */
42
+ export declare function setParserMode(mode: ParserMode | null): void;
43
+ export declare function getParser(): ParserBinding;
44
+
45
+ /** Install a virtual clock (milliseconds); anything but a function restores the real one. */
46
+ export declare function setClock(fn: (() => number) | null): void;
47
+
48
+ export declare function customElementState(el: unknown): CustomElementState;
49
+ /** Record an element's custom element state; returns whether it changed. */
50
+ export declare function setCustomElementState(el: unknown, state: CustomElementState): boolean;
51
+
52
+ /** Parse an HTML string into a new Document. */
53
+ export declare function parseDocument(html: string): Document;
54
+
55
+ export declare const ELEMENT_NODE: 1;
56
+ export declare const TEXT_NODE: 3;
57
+ export declare const COMMENT_NODE: 8;
58
+ export declare const DOCUMENT_NODE: 9;
59
+ export declare const DOCUMENT_TYPE_NODE: 10;
60
+ export declare const DOCUMENT_FRAGMENT_NODE: 11;
61
+
62
+ export declare class Event {
63
+ constructor(type: string, init?: { bubbles?: boolean; cancelable?: boolean; composed?: boolean });
64
+ readonly type: string;
65
+ [key: string]: any;
66
+ }
67
+ export declare class CustomEvent<T = any> extends Event {
68
+ constructor(type: string, init?: { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail?: T });
69
+ readonly detail: T;
70
+ }
71
+
72
+ export declare class Node {
73
+ readonly nodeType: number;
74
+ readonly nodeName: string;
75
+ [key: string]: any;
76
+ }
77
+ export declare class Text extends Node {}
78
+ export declare class Comment extends Node {}
79
+ export declare class DocumentType extends Node {}
80
+ export declare class Element extends Node {}
81
+ export declare class SVGElement extends Element {}
82
+ export declare class MathMLElement extends Element {}
83
+ export declare class DocumentFragment extends Node {}
84
+ export declare class ShadowRoot extends DocumentFragment {}
85
+ export declare class Document extends Node {
86
+ constructor();
87
+ }
88
+
89
+ export declare class DOMParser {
90
+ parseFromString(str: string, type?: string): Document;
91
+ }
92
+ export declare class XMLSerializer {
93
+ serializeToString(node: Node): string;
94
+ }
95
+ export declare class MutationObserver {
96
+ constructor(callback: (records: any[], observer: MutationObserver) => void);
97
+ observe(target: Node, options?: Record<string, unknown>): void;
98
+ disconnect(): void;
99
+ takeRecords(): any[];
100
+ }
@@ -14,6 +14,11 @@ import { getParser, setParserMode } from './parser.mjs';
14
14
  export { Document } from './dom.mjs';
15
15
  export * from './dom.mjs';
16
16
 
17
+ // Custom element state seam (public API): the state behind `:defined` (custom-elements.mjs),
18
+ // for embedders that upgrade elements themselves — the JS twin of the Rust engine's
19
+ // Tree::custom_element_state / set_custom_element_state.
20
+ export { customElementState, setCustomElementState } from './custom-elements.mjs';
21
+
17
22
  // Parser-selection seam (public API): inject a ready binding for a node-free host,
18
23
  // or force 'wasm' | 'native' | 'auto'. See parser.mjs.
19
24
  export { setParser, setParserMode, getParser } from './parser.mjs';
@@ -45,8 +45,7 @@ export function setParserMode(mode) {
45
45
 
46
46
  function resolveMode() {
47
47
  if (_mode) return _mode;
48
- const g = typeof globalThis !== 'undefined' ? globalThis : {};
49
- if (g.__TURBO_DOM_PARSER_MODE__) return g.__TURBO_DOM_PARSER_MODE__;
48
+ if (globalThis.__TURBO_DOM_PARSER_MODE__) return globalThis.__TURBO_DOM_PARSER_MODE__;
50
49
  if (typeof process !== 'undefined' && process.env && process.env.TURBO_DOM_PARSER) return process.env.TURBO_DOM_PARSER;
51
50
  return 'auto';
52
51
  }
@@ -56,8 +55,7 @@ function resolveMode() {
56
55
  // freely without caching it themselves.
57
56
  export function getParser() {
58
57
  if (_parser) return _parser;
59
- const g = typeof globalThis !== 'undefined' ? globalThis : {};
60
- if (g.__TURBO_DOM_PARSER__) return (_parser = g.__TURBO_DOM_PARSER__);
58
+ if (globalThis.__TURBO_DOM_PARSER__) return (_parser = globalThis.__TURBO_DOM_PARSER__);
61
59
  const mode = resolveMode();
62
60
  if (mode === 'wasm') return (_parser = loadWasm());
63
61
  if (mode === 'native') return (_parser = loadNative());