@davidsouther/jiffies 2026.24.0 → 2026.24.2

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.
Files changed (226) hide show
  1. package/lib/esm/assert.d.ts +26 -0
  2. package/lib/esm/assert.js +38 -0
  3. package/lib/esm/awaitable.js +1 -0
  4. package/lib/esm/case.d.ts +1 -0
  5. package/lib/esm/case.js +5 -0
  6. package/lib/esm/components/accordion.d.ts +5 -0
  7. package/lib/esm/components/accordion.js +9 -0
  8. package/lib/esm/components/alert.d.ts +7 -0
  9. package/lib/esm/components/alert.js +31 -0
  10. package/lib/esm/components/button_bar.d.ts +8 -0
  11. package/lib/esm/components/button_bar.js +25 -0
  12. package/lib/esm/components/card.d.ts +8 -0
  13. package/lib/esm/components/card.js +31 -0
  14. package/lib/esm/components/children.d.ts +2 -0
  15. package/{src/components/children.ts → lib/esm/components/children.js} +2 -6
  16. package/lib/esm/components/form.d.ts +5 -0
  17. package/lib/esm/components/form.js +13 -0
  18. package/{src/components/index.ts → lib/esm/components/index.d.ts} +2 -15
  19. package/lib/esm/components/index.js +10 -0
  20. package/lib/esm/components/inline_edit.d.ts +12 -0
  21. package/lib/esm/components/inline_edit.js +48 -0
  22. package/lib/esm/components/link.d.ts +5 -0
  23. package/lib/esm/components/link.js +11 -0
  24. package/lib/esm/components/logger.d.ts +6 -0
  25. package/lib/esm/components/logger.js +22 -0
  26. package/lib/esm/components/modal.d.ts +2 -0
  27. package/{src/components/modal.ts → lib/esm/components/modal.js} +3 -8
  28. package/lib/esm/components/nav.d.ts +11 -0
  29. package/lib/esm/components/nav.js +27 -0
  30. package/lib/esm/components/property.d.ts +9 -0
  31. package/lib/esm/components/property.js +16 -0
  32. package/lib/esm/components/select.d.ts +10 -0
  33. package/lib/esm/components/select.js +3 -0
  34. package/lib/esm/components/tabs.d.ts +20 -0
  35. package/lib/esm/components/tabs.js +45 -0
  36. package/lib/esm/components/virtual_scroll.d.ts +42 -0
  37. package/lib/esm/components/virtual_scroll.js +94 -0
  38. package/lib/esm/debounce.d.ts +1 -0
  39. package/lib/esm/debounce.js +11 -0
  40. package/lib/esm/diff.d.ts +15 -0
  41. package/lib/esm/diff.js +50 -0
  42. package/lib/esm/display.d.ts +5 -0
  43. package/lib/esm/display.js +11 -0
  44. package/lib/esm/dom/css/border.d.ts +11 -0
  45. package/lib/esm/dom/css/border.js +27 -0
  46. package/lib/esm/dom/css/constants.d.ts +31 -0
  47. package/lib/esm/dom/css/constants.js +28 -0
  48. package/lib/esm/dom/css/core.d.ts +5 -0
  49. package/lib/esm/dom/css/core.js +24 -0
  50. package/lib/esm/dom/css/fstyle.d.ts +5 -0
  51. package/lib/esm/dom/css/fstyle.js +32 -0
  52. package/lib/esm/dom/css/sizing.d.ts +5 -0
  53. package/lib/esm/dom/css/sizing.js +10 -0
  54. package/lib/esm/dom/dom.d.ts +36 -0
  55. package/lib/esm/dom/dom.js +217 -0
  56. package/lib/esm/dom/fc.d.ts +10 -0
  57. package/lib/esm/dom/fc.js +32 -0
  58. package/lib/esm/dom/form/form.app.d.ts +1 -0
  59. package/lib/esm/dom/form/form.app.js +19 -0
  60. package/lib/esm/dom/form/form.d.ts +27 -0
  61. package/lib/esm/dom/form/form.js +65 -0
  62. package/lib/esm/dom/html.d.ts +112 -0
  63. package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
  64. package/lib/esm/dom/hydrate.d.ts +39 -0
  65. package/lib/esm/dom/hydrate.js +187 -0
  66. package/lib/esm/dom/index.js +2 -0
  67. package/lib/esm/dom/navigation/index.d.ts +76 -0
  68. package/lib/esm/dom/navigation/index.js +292 -0
  69. package/lib/esm/dom/observable.d.ts +2 -0
  70. package/lib/esm/dom/observable.js +6 -0
  71. package/lib/esm/dom/provide.d.ts +3 -0
  72. package/lib/esm/dom/provide.js +7 -0
  73. package/lib/esm/dom/render.d.ts +8 -0
  74. package/lib/esm/dom/render.js +28 -0
  75. package/lib/esm/dom/router/link.d.ts +6 -0
  76. package/lib/esm/dom/router/link.js +3 -0
  77. package/lib/esm/dom/router/router.d.ts +13 -0
  78. package/lib/esm/dom/router/router.js +52 -0
  79. package/lib/esm/dom/svg.d.ts +64 -0
  80. package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -19
  81. package/lib/esm/dom/types/css.d.ts +6590 -0
  82. package/lib/esm/dom/types/css.js +1 -0
  83. package/lib/esm/dom/types/dom.js +1 -0
  84. package/lib/esm/dom/types/html.d.ts +614 -0
  85. package/lib/esm/dom/types/html.js +1 -0
  86. package/lib/esm/dom/xml.d.ts +1 -0
  87. package/lib/esm/dom/xml.js +4 -0
  88. package/lib/esm/equal.d.ts +11 -0
  89. package/lib/esm/equal.js +43 -0
  90. package/lib/esm/fs.d.ts +72 -0
  91. package/lib/esm/fs.js +227 -0
  92. package/lib/esm/fs_node.d.ts +15 -0
  93. package/lib/esm/fs_node.js +45 -0
  94. package/lib/esm/generator.d.ts +1 -0
  95. package/lib/esm/generator.js +10 -0
  96. package/lib/esm/lock.d.ts +1 -0
  97. package/lib/esm/lock.js +23 -0
  98. package/lib/esm/log.d.ts +69 -0
  99. package/lib/esm/log.js +211 -0
  100. package/lib/esm/observable/event.d.ts +35 -0
  101. package/lib/esm/observable/event.js +46 -0
  102. package/lib/esm/observable/observable.d.ts +134 -0
  103. package/lib/esm/observable/observable.js +349 -0
  104. package/lib/esm/range.d.ts +1 -0
  105. package/lib/esm/range.js +7 -0
  106. package/lib/esm/result.d.ts +31 -0
  107. package/lib/esm/result.js +66 -0
  108. package/lib/esm/safe.d.ts +1 -0
  109. package/lib/esm/safe.js +10 -0
  110. package/lib/esm/server/http/apps.d.ts +5 -0
  111. package/lib/esm/server/http/apps.js +23 -0
  112. package/lib/esm/server/http/css.d.ts +5 -0
  113. package/lib/esm/server/http/css.js +43 -0
  114. package/lib/esm/server/http/index.d.ts +16 -0
  115. package/lib/esm/server/http/index.js +78 -0
  116. package/lib/esm/server/http/response.d.ts +4 -0
  117. package/lib/esm/server/http/response.js +43 -0
  118. package/lib/esm/server/http/sitemap.d.ts +2 -0
  119. package/lib/esm/server/http/sitemap.js +22 -0
  120. package/lib/esm/server/http/static.d.ts +2 -0
  121. package/lib/esm/server/http/static.js +22 -0
  122. package/lib/esm/server/http/typescript.d.ts +5 -0
  123. package/lib/esm/server/http/typescript.js +40 -0
  124. package/lib/esm/server/live-reload.d.ts +46 -0
  125. package/lib/esm/server/live-reload.js +161 -0
  126. package/lib/esm/server/main.d.ts +2 -0
  127. package/{src/server/main.ts → lib/esm/server/main.js} +8 -15
  128. package/lib/esm/server/ws/frame.d.ts +2 -0
  129. package/lib/esm/server/ws/frame.js +35 -0
  130. package/lib/esm/server/ws/handshake.d.ts +4 -0
  131. package/lib/esm/server/ws/handshake.js +32 -0
  132. package/lib/esm/server/ws/index.d.ts +14 -0
  133. package/lib/esm/server/ws/index.js +68 -0
  134. package/lib/esm/ssg/bundle.d.ts +14 -0
  135. package/lib/esm/ssg/bundle.js +73 -0
  136. package/lib/esm/ssg/copy-public.d.ts +6 -0
  137. package/lib/esm/ssg/copy-public.js +34 -0
  138. package/lib/esm/ssg/discover.d.ts +15 -0
  139. package/lib/esm/ssg/discover.js +117 -0
  140. package/lib/esm/ssg/main.d.ts +2 -0
  141. package/lib/esm/ssg/main.js +122 -0
  142. package/lib/esm/ssg/rewrite.d.ts +9 -0
  143. package/{src/ssg/rewrite.ts → lib/esm/ssg/rewrite.js} +6 -9
  144. package/lib/esm/ssg/ssg.d.ts +26 -0
  145. package/lib/esm/ssg/ssg.js +84 -0
  146. package/lib/esm/transpile.d.mts +3 -0
  147. package/lib/esm/transpile.mjs +12 -0
  148. package/package.json +11 -7
  149. package/src/404.html +0 -14
  150. package/src/assert.ts +0 -56
  151. package/src/case.ts +0 -5
  152. package/src/components/_notes +0 -33
  153. package/src/components/accordion.ts +0 -25
  154. package/src/components/alert.ts +0 -47
  155. package/src/components/button_bar.ts +0 -42
  156. package/src/components/card.ts +0 -54
  157. package/src/components/form.ts +0 -25
  158. package/src/components/inline_edit.ts +0 -78
  159. package/src/components/link.ts +0 -22
  160. package/src/components/logger.ts +0 -35
  161. package/src/components/nav.ts +0 -42
  162. package/src/components/property.ts +0 -32
  163. package/src/components/select.ts +0 -22
  164. package/src/components/tabs.ts +0 -82
  165. package/src/components/virtual_scroll.ts +0 -199
  166. package/src/debounce.ts +0 -14
  167. package/src/diff.ts +0 -82
  168. package/src/display.ts +0 -18
  169. package/src/dom/README.md +0 -107
  170. package/src/dom/SKILL.md +0 -201
  171. package/src/dom/css/border.ts +0 -47
  172. package/src/dom/css/constants.ts +0 -34
  173. package/src/dom/css/core.ts +0 -28
  174. package/src/dom/css/fstyle.ts +0 -42
  175. package/src/dom/css/sizing.ts +0 -11
  176. package/src/dom/dom.ts +0 -327
  177. package/src/dom/fc.ts +0 -81
  178. package/src/dom/form/form.app.ts +0 -44
  179. package/src/dom/form/form.ts +0 -151
  180. package/src/dom/form/index.html +0 -15
  181. package/src/dom/hydrate.ts +0 -206
  182. package/src/dom/navigation/index.ts +0 -349
  183. package/src/dom/observable.ts +0 -11
  184. package/src/dom/provide.ts +0 -11
  185. package/src/dom/render.ts +0 -41
  186. package/src/dom/router/link.ts +0 -14
  187. package/src/dom/router/router.ts +0 -72
  188. package/src/dom/types/css.ts +0 -10088
  189. package/src/dom/types/html.ts +0 -629
  190. package/src/dom/xml.ts +0 -11
  191. package/src/equal.ts +0 -66
  192. package/src/favicon.ico +0 -0
  193. package/src/fs.ts +0 -300
  194. package/src/fs_node.ts +0 -57
  195. package/src/generator.ts +0 -12
  196. package/src/hooks/_notes +0 -6
  197. package/src/lock.ts +0 -23
  198. package/src/log.ts +0 -307
  199. package/src/observable/_notes +0 -26
  200. package/src/observable/event.ts +0 -93
  201. package/src/observable/observable.ts +0 -484
  202. package/src/range.ts +0 -7
  203. package/src/result.ts +0 -107
  204. package/src/safe.ts +0 -12
  205. package/src/server/http/apps.ts +0 -26
  206. package/src/server/http/css.ts +0 -49
  207. package/src/server/http/index.ts +0 -127
  208. package/src/server/http/response.ts +0 -60
  209. package/src/server/http/sitemap.ts +0 -24
  210. package/src/server/http/static.ts +0 -28
  211. package/src/server/http/typescript.ts +0 -46
  212. package/src/server/live-reload.ts +0 -208
  213. package/src/server/ws/frame.ts +0 -36
  214. package/src/server/ws/handshake.ts +0 -42
  215. package/src/server/ws/index.ts +0 -100
  216. package/src/ssg/bundle.ts +0 -85
  217. package/src/ssg/copy-public.ts +0 -44
  218. package/src/ssg/discover.ts +0 -143
  219. package/src/ssg/main.ts +0 -168
  220. package/src/ssg/ssg.ts +0 -134
  221. package/src/transpile.mjs +0 -16
  222. package/src/zip/spec.txt +0 -3260
  223. package/tsconfig.json +0 -34
  224. /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
  225. /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
  226. /package/{src/dom/types/dom.ts → lib/esm/dom/types/dom.d.ts} +0 -0
@@ -1,151 +0,0 @@
1
- import type { Attrs, DenormChildren } from "../dom.ts";
2
- import {
3
- button,
4
- fieldset,
5
- form,
6
- input,
7
- label,
8
- legend,
9
- option,
10
- select,
11
- } from "../html.ts";
12
- import type {
13
- FormAttributes,
14
- InputAttributes,
15
- LabelAttributes,
16
- OptionAttributes,
17
- SelectAttributes,
18
- } from "../types/html";
19
-
20
- export const Form = (attrs: FormAttributes, ...children: DenormChildren[]) => {
21
- if (attrs.events?.submit) {
22
- const submit = attrs.events.submit;
23
- attrs.events.submit = (event) => {
24
- event.preventDefault();
25
- submit(event);
26
- };
27
- }
28
- return form(attrs as Attrs<HTMLFormElement>, ...children);
29
- };
30
- export const Input = (attrs: InputAttributes, ...children: DenormChildren[]) =>
31
- label(
32
- input(
33
- // @ts-expect-error
34
- attrs as Attrs<HTMLInputElement>,
35
- ),
36
- ...children,
37
- );
38
-
39
- export const Select = (
40
- attrs: { options: string[] | object; selected?: string } & SelectAttributes &
41
- LabelAttributes,
42
- ) =>
43
- label(
44
- { style: attrs.style ?? {} },
45
- select(
46
- { events: attrs.events ?? {} },
47
- ...prepareOptions(attrs.options as string[], attrs.selected).map(Option),
48
- ),
49
- );
50
- // Sanctioned jiffies-css button variants. The default button needs no class.
51
- export type ButtonVariant = "secondary" | "contrast" | "outline";
52
-
53
- // Button emits button[type=button] so it never accidentally submits a form. The
54
- // optional variant maps to the matching sanctioned jiffies-css class.
55
- export const Button = (
56
- variant?: ButtonVariant,
57
- ...children: DenormChildren[]
58
- ) =>
59
- button(
60
- variant ? { type: "button", class: variant } : { type: "button" },
61
- ...children,
62
- );
63
-
64
- const prepareOptions = (
65
- attrs:
66
- | string[]
67
- | Record<
68
- string,
69
- string | { label: string; disabled?: boolean; selected?: boolean }
70
- >,
71
- selected?: string,
72
- ): Parameters<typeof Option>[0][] =>
73
- Array.isArray(attrs)
74
- ? attrs.map((value) => ({
75
- value,
76
- label: value,
77
- selected: selected === value,
78
- }))
79
- : Object.entries(attrs).map(([value, label]) =>
80
- typeof label === "string"
81
- ? { value, label, selected: selected === value }
82
- : { value, ...label },
83
- );
84
- export const Option = (attrs: OptionAttributes) =>
85
- option(attrs as Attrs<HTMLOptionElement>);
86
-
87
- export const Dropdown = (
88
- attrs: SelectAttributes | { selected?: string },
89
- ...options: Parameters<typeof prepareOptions>[0][]
90
- ) =>
91
- Select({
92
- ...attrs,
93
- options: typeof options[0] === "string" ? options : options[0],
94
- });
95
- // A {value: label} map: option value (also the id/name stem) to display text.
96
- export type ChoiceOptions = Record<string, string>;
97
-
98
- // Derive a stable name/id stem from the legend text.
99
- const slug = (text: string) =>
100
- text
101
- .toLowerCase()
102
- .replace(/[^a-z0-9]+/g, "-")
103
- .replace(/^-+|-+$/g, "");
104
-
105
- // Shared builder for Radios/Checks/Switches: fieldset[role=group] > legend +
106
- // (input[type] + label[for])* — the jiffies-css grouped-controls structure. The
107
- // shared name groups the inputs; id/for pairs each input to its label.
108
- const choiceGroup = (
109
- type: "radio" | "checkbox",
110
- legendText: string,
111
- options: ChoiceOptions,
112
- role?: "switch",
113
- ): HTMLFieldSetElement => {
114
- const name = slug(legendText);
115
- const children: DenormChildren[] = [legend(legendText)];
116
- for (const [value, labelText] of Object.entries(options)) {
117
- const id = `${name}-${value}`;
118
- const box = input({ type, name, id, value });
119
- if (role) {
120
- box.setAttribute("role", role);
121
- }
122
- const lbl = label(labelText);
123
- lbl.setAttribute("for", id);
124
- children.push(box, lbl);
125
- }
126
- const group = fieldset(...children);
127
- group.setAttribute("role", "group");
128
- return group;
129
- };
130
-
131
- export const Radios = (legendText: string, options: ChoiceOptions) =>
132
- choiceGroup("radio", legendText, options);
133
- export const Checks = (legendText: string, options: ChoiceOptions) =>
134
- choiceGroup("checkbox", legendText, options);
135
- export const Switches = (legendText: string, options: ChoiceOptions) =>
136
- choiceGroup("checkbox", legendText, options, "switch");
137
-
138
- // Single-item controls wrap the input in its label (label > input + text), the
139
- // jiffies-css labelled-control pattern. type and role are fixed per variant.
140
- export const Radio = (
141
- labelText: string,
142
- attrs: Omit<InputAttributes, "type"> = {},
143
- ) => Input({ ...attrs, type: "radio" }, labelText);
144
- export const Checkbox = (
145
- labelText: string,
146
- attrs: Omit<InputAttributes, "type"> = {},
147
- ) => Input({ ...attrs, type: "checkbox" }, labelText);
148
- export const Switch = (
149
- labelText: string,
150
- attrs: Omit<InputAttributes, "type" | "role"> = {},
151
- ) => Input({ ...attrs, type: "checkbox", role: "switch" }, labelText);
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en-US">
3
- <head>
4
- <title>Jiffies Form</title>
5
- <base href="/dom/form/" />
6
- <link rel="stylesheet" href="https://unpkg.com/@davidsouther/jiffies-css/dist/index.css">
7
- </head>
8
- <body>
9
- <script type="module">
10
- import {App} from './form.app.ts';
11
-
12
- document.body.appendChild(App());
13
- </script>
14
- </body>
15
- </html>
@@ -1,206 +0,0 @@
1
- "use client"; // Hydrate runs entirely client side.
2
-
3
- import { reconcileChildren } from "./dom.ts";
4
-
5
- /**
6
- * Self-contained IIFE source for the capture stub. Embedded inline by the SSG
7
- * build pass so events fired before the client bundle loads are queued in
8
- * window.__hydrateQueue and replayed after hydration. No external references.
9
- */
10
- export const captureStubSource = `(function(){
11
- window.__hydrateQueue = window.__hydrateQueue || [];
12
- var queue = window.__hydrateQueue;
13
- var handler = function(event) {
14
- var path = event.composedPath();
15
- var unitEl = null;
16
- for (var i = 0; i < path.length; i++) {
17
- var node = path[i];
18
- if (node instanceof Element && customElements.get(node.localName)) {
19
- unitEl = node;
20
- break;
21
- }
22
- }
23
- if (!unitEl) return;
24
- var target = event.target;
25
- var targetPath = [];
26
- var cur = target;
27
- while (cur !== unitEl) {
28
- var parent = cur.parentNode;
29
- var siblings = Array.from(parent.childNodes);
30
- targetPath.unshift(siblings.indexOf(cur));
31
- cur = parent;
32
- }
33
- queue.push({ unitEl: unitEl, type: event.type, targetPath: targetPath, init: { bubbles: event.bubbles, cancelable: event.cancelable } });
34
- };
35
- var types = ["click","input","change","submit","keydown"];
36
- for (var t = 0; t < types.length; t++) {
37
- document.addEventListener(types[t], handler, true);
38
- }
39
- })()`;
40
-
41
- /**
42
- * Serialize `units` to a JSON string safe for embedding in an HTML script tag
43
- * (angle brackets and ampersands are Unicode-escaped).
44
- */
45
- export function buildPayload(units: Record<string, unknown>[]): string {
46
- return JSON.stringify(units)
47
- .replace(/&/g, "\\u0026")
48
- .replace(/</g, "\\u003c")
49
- .replace(/>/g, "\\u003e");
50
- }
51
-
52
- /**
53
- * Read the hydration payload from the `#__hydration` script element embedded
54
- * by the SSG build step. Returns an empty array when the element is absent.
55
- */
56
- export function readPayload(): Record<string, unknown>[] {
57
- const el = window.document.getElementById("__hydration");
58
- if (!el) return [];
59
- return JSON.parse(el.textContent ?? "[]") as Record<string, unknown>[];
60
- }
61
-
62
- /**
63
- * Walk `root` depth-first, returning every element whose localName is a
64
- * defined custom element. Does NOT descend into matched elements — each custom
65
- * element owns its own subtree; inner elements will be reached by their
66
- * parent's `el.update()` call, not by `start()`.
67
- */
68
- function scanUnits(root: ParentNode): Element[] {
69
- const results: Element[] = [];
70
- const stack: Element[] = [...root.children].reverse();
71
- while (stack.length > 0) {
72
- const el = stack.pop() as Element;
73
- if (customElements.get(el.localName)) {
74
- results.push(el);
75
- } else {
76
- for (let i = el.children.length - 1; i >= 0; i--) {
77
- stack.push(el.children[i] as Element);
78
- }
79
- }
80
- }
81
- return results;
82
- }
83
-
84
- /**
85
- * Scan `root` for registered custom elements and schedule each for hydration.
86
- * `customElements.whenDefined` resolves as a microtask even when the element
87
- * is already defined. The callback clears server-rendered children then runs
88
- * `el.update()`, which re-executes the element's render function and rebuilds
89
- * its subtree. `root` defaults to `window.document.body`.
90
- */
91
- export function start(root?: ParentNode): void {
92
- const r = root ?? window.document.body;
93
- const units = scanUnits(r);
94
- const payload = readPayload();
95
- units.forEach((el, index) => {
96
- customElements.whenDefined(el.localName).then(() => {
97
- el.replaceChildren();
98
- el.update(payload[index]);
99
- drainQueue(el);
100
- });
101
- });
102
- }
103
-
104
- // Hydrate custom elements inside `root` without clearing their server-rendered
105
- // children first. `el.update()` reconciles onto the existing DOM so attributes
106
- // and listeners are grafted in place. Recurses after each element hydrates so
107
- // parents are always processed before their nested custom elements.
108
- function startHydrate(root: ParentNode): void {
109
- for (const el of scanUnits(root)) {
110
- customElements.whenDefined(el.localName).then(() => {
111
- el.update();
112
- startHydrate(el);
113
- });
114
- }
115
- }
116
-
117
- interface HydrateQueueEntry {
118
- unitEl: Element;
119
- type: string;
120
- targetPath: number[];
121
- init: { bubbles: boolean; cancelable: boolean };
122
- }
123
-
124
- function getHydrateQueue(): HydrateQueueEntry[] {
125
- const w = window as unknown as Record<string, unknown>;
126
- w.__hydrateQueue ??= [];
127
- return w.__hydrateQueue as HydrateQueueEntry[];
128
- }
129
-
130
- /**
131
- * Install capture-phase listeners on `document` that intercept events
132
- * targeting nodes inside un-hydrated custom elements and push descriptors into
133
- * `window.__hydrateQueue`. `start()` drains the queue for each element after
134
- * `el.update()` runs by calling `drainQueue`.
135
- */
136
- export function installCaptureStub(): void {
137
- const queue = getHydrateQueue();
138
- const handler = (event: Event) => {
139
- const path = event.composedPath() as Node[];
140
- let unitEl: Element | null = null;
141
- for (const node of path) {
142
- if (node instanceof Element && customElements.get(node.localName)) {
143
- unitEl = node;
144
- break;
145
- }
146
- }
147
- if (!unitEl) return;
148
- const target = event.target as Node;
149
- const targetPath: number[] = [];
150
- let cur: Node = target;
151
- while (cur !== unitEl) {
152
- const parent = cur.parentNode as Node;
153
- targetPath.unshift(
154
- Array.from(parent.childNodes).indexOf(cur as ChildNode),
155
- );
156
- cur = parent;
157
- }
158
- queue.push({
159
- unitEl,
160
- type: event.type,
161
- targetPath,
162
- init: { bubbles: event.bubbles, cancelable: event.cancelable },
163
- });
164
- };
165
- for (const type of ["click", "input", "change", "submit", "keydown"]) {
166
- window.document.addEventListener(type, handler, true);
167
- }
168
- }
169
-
170
- function drainQueue(el: Element): void {
171
- const w = window as unknown as Record<string, unknown>;
172
- const allEntries = (w.__hydrateQueue ?? []) as HydrateQueueEntry[];
173
- const mine = allEntries.filter((e) => e.unitEl === el);
174
- w.__hydrateQueue = allEntries.filter((e) => e.unitEl !== el);
175
- for (const entry of mine) {
176
- let node: Node = el;
177
- let resolved = true;
178
- for (const idx of entry.targetPath) {
179
- const child = node.childNodes[idx];
180
- if (!child) {
181
- console.warn(
182
- `hydrateQueue: path index ${idx} out of range, dropping queued ${entry.type}`,
183
- );
184
- resolved = false;
185
- break;
186
- }
187
- node = child;
188
- }
189
- if (resolved) {
190
- node.dispatchEvent(new Event(entry.type, entry.init));
191
- }
192
- }
193
- }
194
-
195
- /**
196
- * Hydrate a server-rendered page: call `render` once, reconcile the result
197
- * into `mount` without replacing existing DOM nodes, and graft event handlers
198
- * onto kept server nodes. Custom-element boundaries are left for each element
199
- * to hydrate itself. Use this instead of `start` when the full page tree is
200
- * produced by a single render function rather than independent custom elements.
201
- */
202
- export function hydrateRoot(mount: Element, render: () => Node | Node[]): void {
203
- const fresh = [render()].flat() as Node[];
204
- reconcileChildren(mount, fresh);
205
- startHydrate(mount);
206
- }