@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.
- package/lib/esm/assert.d.ts +26 -0
- package/lib/esm/assert.js +38 -0
- package/lib/esm/awaitable.js +1 -0
- package/lib/esm/case.d.ts +1 -0
- package/lib/esm/case.js +5 -0
- package/lib/esm/components/accordion.d.ts +5 -0
- package/lib/esm/components/accordion.js +9 -0
- package/lib/esm/components/alert.d.ts +7 -0
- package/lib/esm/components/alert.js +31 -0
- package/lib/esm/components/button_bar.d.ts +8 -0
- package/lib/esm/components/button_bar.js +25 -0
- package/lib/esm/components/card.d.ts +8 -0
- package/lib/esm/components/card.js +31 -0
- package/lib/esm/components/children.d.ts +2 -0
- package/{src/components/children.ts → lib/esm/components/children.js} +2 -6
- package/lib/esm/components/form.d.ts +5 -0
- package/lib/esm/components/form.js +13 -0
- package/{src/components/index.ts → lib/esm/components/index.d.ts} +2 -15
- package/lib/esm/components/index.js +10 -0
- package/lib/esm/components/inline_edit.d.ts +12 -0
- package/lib/esm/components/inline_edit.js +48 -0
- package/lib/esm/components/link.d.ts +5 -0
- package/lib/esm/components/link.js +11 -0
- package/lib/esm/components/logger.d.ts +6 -0
- package/lib/esm/components/logger.js +22 -0
- package/lib/esm/components/modal.d.ts +2 -0
- package/{src/components/modal.ts → lib/esm/components/modal.js} +3 -8
- package/lib/esm/components/nav.d.ts +11 -0
- package/lib/esm/components/nav.js +27 -0
- package/lib/esm/components/property.d.ts +9 -0
- package/lib/esm/components/property.js +16 -0
- package/lib/esm/components/select.d.ts +10 -0
- package/lib/esm/components/select.js +3 -0
- package/lib/esm/components/tabs.d.ts +20 -0
- package/lib/esm/components/tabs.js +45 -0
- package/lib/esm/components/virtual_scroll.d.ts +42 -0
- package/lib/esm/components/virtual_scroll.js +94 -0
- package/lib/esm/debounce.d.ts +1 -0
- package/lib/esm/debounce.js +11 -0
- package/lib/esm/diff.d.ts +15 -0
- package/lib/esm/diff.js +50 -0
- package/lib/esm/display.d.ts +5 -0
- package/lib/esm/display.js +11 -0
- package/lib/esm/dom/css/border.d.ts +11 -0
- package/lib/esm/dom/css/border.js +27 -0
- package/lib/esm/dom/css/constants.d.ts +31 -0
- package/lib/esm/dom/css/constants.js +28 -0
- package/lib/esm/dom/css/core.d.ts +5 -0
- package/lib/esm/dom/css/core.js +24 -0
- package/lib/esm/dom/css/fstyle.d.ts +5 -0
- package/lib/esm/dom/css/fstyle.js +32 -0
- package/lib/esm/dom/css/sizing.d.ts +5 -0
- package/lib/esm/dom/css/sizing.js +10 -0
- package/lib/esm/dom/dom.d.ts +36 -0
- package/lib/esm/dom/dom.js +217 -0
- package/lib/esm/dom/fc.d.ts +10 -0
- package/lib/esm/dom/fc.js +32 -0
- package/lib/esm/dom/form/form.app.d.ts +1 -0
- package/lib/esm/dom/form/form.app.js +19 -0
- package/lib/esm/dom/form/form.d.ts +27 -0
- package/lib/esm/dom/form/form.js +65 -0
- package/lib/esm/dom/html.d.ts +112 -0
- package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
- package/lib/esm/dom/hydrate.d.ts +39 -0
- package/lib/esm/dom/hydrate.js +187 -0
- package/lib/esm/dom/index.js +2 -0
- package/lib/esm/dom/navigation/index.d.ts +76 -0
- package/lib/esm/dom/navigation/index.js +292 -0
- package/lib/esm/dom/observable.d.ts +2 -0
- package/lib/esm/dom/observable.js +6 -0
- package/lib/esm/dom/provide.d.ts +3 -0
- package/lib/esm/dom/provide.js +7 -0
- package/lib/esm/dom/render.d.ts +8 -0
- package/lib/esm/dom/render.js +28 -0
- package/lib/esm/dom/router/link.d.ts +6 -0
- package/lib/esm/dom/router/link.js +3 -0
- package/lib/esm/dom/router/router.d.ts +13 -0
- package/lib/esm/dom/router/router.js +52 -0
- package/lib/esm/dom/svg.d.ts +64 -0
- package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -19
- package/lib/esm/dom/types/css.d.ts +6590 -0
- package/lib/esm/dom/types/css.js +1 -0
- package/lib/esm/dom/types/dom.js +1 -0
- package/lib/esm/dom/types/html.d.ts +614 -0
- package/lib/esm/dom/types/html.js +1 -0
- package/lib/esm/dom/xml.d.ts +1 -0
- package/lib/esm/dom/xml.js +4 -0
- package/lib/esm/equal.d.ts +11 -0
- package/lib/esm/equal.js +43 -0
- package/lib/esm/fs.d.ts +72 -0
- package/lib/esm/fs.js +227 -0
- package/lib/esm/fs_node.d.ts +15 -0
- package/lib/esm/fs_node.js +45 -0
- package/lib/esm/generator.d.ts +1 -0
- package/lib/esm/generator.js +10 -0
- package/lib/esm/lock.d.ts +1 -0
- package/lib/esm/lock.js +23 -0
- package/lib/esm/log.d.ts +69 -0
- package/lib/esm/log.js +211 -0
- package/lib/esm/observable/event.d.ts +35 -0
- package/lib/esm/observable/event.js +46 -0
- package/lib/esm/observable/observable.d.ts +134 -0
- package/lib/esm/observable/observable.js +349 -0
- package/lib/esm/range.d.ts +1 -0
- package/lib/esm/range.js +7 -0
- package/lib/esm/result.d.ts +31 -0
- package/lib/esm/result.js +66 -0
- package/lib/esm/safe.d.ts +1 -0
- package/lib/esm/safe.js +10 -0
- package/lib/esm/server/http/apps.d.ts +5 -0
- package/lib/esm/server/http/apps.js +23 -0
- package/lib/esm/server/http/css.d.ts +5 -0
- package/lib/esm/server/http/css.js +43 -0
- package/lib/esm/server/http/index.d.ts +16 -0
- package/lib/esm/server/http/index.js +78 -0
- package/lib/esm/server/http/response.d.ts +4 -0
- package/lib/esm/server/http/response.js +43 -0
- package/lib/esm/server/http/sitemap.d.ts +2 -0
- package/lib/esm/server/http/sitemap.js +22 -0
- package/lib/esm/server/http/static.d.ts +2 -0
- package/lib/esm/server/http/static.js +22 -0
- package/lib/esm/server/http/typescript.d.ts +5 -0
- package/lib/esm/server/http/typescript.js +40 -0
- package/lib/esm/server/live-reload.d.ts +46 -0
- package/lib/esm/server/live-reload.js +161 -0
- package/lib/esm/server/main.d.ts +2 -0
- package/{src/server/main.ts → lib/esm/server/main.js} +8 -15
- package/lib/esm/server/ws/frame.d.ts +2 -0
- package/lib/esm/server/ws/frame.js +35 -0
- package/lib/esm/server/ws/handshake.d.ts +4 -0
- package/lib/esm/server/ws/handshake.js +32 -0
- package/lib/esm/server/ws/index.d.ts +14 -0
- package/lib/esm/server/ws/index.js +68 -0
- package/lib/esm/ssg/bundle.d.ts +14 -0
- package/lib/esm/ssg/bundle.js +73 -0
- package/lib/esm/ssg/copy-public.d.ts +6 -0
- package/lib/esm/ssg/copy-public.js +34 -0
- package/lib/esm/ssg/discover.d.ts +15 -0
- package/lib/esm/ssg/discover.js +117 -0
- package/lib/esm/ssg/main.d.ts +2 -0
- package/lib/esm/ssg/main.js +122 -0
- package/lib/esm/ssg/rewrite.d.ts +9 -0
- package/{src/ssg/rewrite.ts → lib/esm/ssg/rewrite.js} +6 -9
- package/lib/esm/ssg/ssg.d.ts +26 -0
- package/lib/esm/ssg/ssg.js +84 -0
- package/lib/esm/transpile.d.mts +3 -0
- package/lib/esm/transpile.mjs +12 -0
- package/package.json +11 -7
- package/src/404.html +0 -14
- package/src/assert.ts +0 -56
- package/src/case.ts +0 -5
- package/src/components/_notes +0 -33
- package/src/components/accordion.ts +0 -25
- package/src/components/alert.ts +0 -47
- package/src/components/button_bar.ts +0 -42
- package/src/components/card.ts +0 -54
- package/src/components/form.ts +0 -25
- package/src/components/inline_edit.ts +0 -78
- package/src/components/link.ts +0 -22
- package/src/components/logger.ts +0 -35
- package/src/components/nav.ts +0 -42
- package/src/components/property.ts +0 -32
- package/src/components/select.ts +0 -22
- package/src/components/tabs.ts +0 -82
- package/src/components/virtual_scroll.ts +0 -199
- package/src/debounce.ts +0 -14
- package/src/diff.ts +0 -82
- package/src/display.ts +0 -18
- package/src/dom/README.md +0 -107
- package/src/dom/SKILL.md +0 -201
- package/src/dom/css/border.ts +0 -47
- package/src/dom/css/constants.ts +0 -34
- package/src/dom/css/core.ts +0 -28
- package/src/dom/css/fstyle.ts +0 -42
- package/src/dom/css/sizing.ts +0 -11
- package/src/dom/dom.ts +0 -327
- package/src/dom/fc.ts +0 -81
- package/src/dom/form/form.app.ts +0 -44
- package/src/dom/form/form.ts +0 -151
- package/src/dom/form/index.html +0 -15
- package/src/dom/hydrate.ts +0 -206
- package/src/dom/navigation/index.ts +0 -349
- package/src/dom/observable.ts +0 -11
- package/src/dom/provide.ts +0 -11
- package/src/dom/render.ts +0 -41
- package/src/dom/router/link.ts +0 -14
- package/src/dom/router/router.ts +0 -72
- package/src/dom/types/css.ts +0 -10088
- package/src/dom/types/html.ts +0 -629
- package/src/dom/xml.ts +0 -11
- package/src/equal.ts +0 -66
- package/src/favicon.ico +0 -0
- package/src/fs.ts +0 -300
- package/src/fs_node.ts +0 -57
- package/src/generator.ts +0 -12
- package/src/hooks/_notes +0 -6
- package/src/lock.ts +0 -23
- package/src/log.ts +0 -307
- package/src/observable/_notes +0 -26
- package/src/observable/event.ts +0 -93
- package/src/observable/observable.ts +0 -484
- package/src/range.ts +0 -7
- package/src/result.ts +0 -107
- package/src/safe.ts +0 -12
- package/src/server/http/apps.ts +0 -26
- package/src/server/http/css.ts +0 -49
- package/src/server/http/index.ts +0 -127
- package/src/server/http/response.ts +0 -60
- package/src/server/http/sitemap.ts +0 -24
- package/src/server/http/static.ts +0 -28
- package/src/server/http/typescript.ts +0 -46
- package/src/server/live-reload.ts +0 -208
- package/src/server/ws/frame.ts +0 -36
- package/src/server/ws/handshake.ts +0 -42
- package/src/server/ws/index.ts +0 -100
- package/src/ssg/bundle.ts +0 -85
- package/src/ssg/copy-public.ts +0 -44
- package/src/ssg/discover.ts +0 -143
- package/src/ssg/main.ts +0 -168
- package/src/ssg/ssg.ts +0 -134
- package/src/transpile.mjs +0 -16
- package/src/zip/spec.txt +0 -3260
- package/tsconfig.json +0 -34
- /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
- /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
- /package/{src/dom/types/dom.ts → lib/esm/dom/types/dom.d.ts} +0 -0
package/src/dom/form/form.ts
DELETED
|
@@ -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);
|
package/src/dom/form/index.html
DELETED
|
@@ -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>
|
package/src/dom/hydrate.ts
DELETED
|
@@ -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
|
-
}
|