@takazudo/zfb 2.22.1 → 3.1.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/README.md +13 -16
- package/dist/config.d.ts +77 -25
- package/dist/config.js +27 -6
- package/dist/config.js.map +1 -1
- package/dist/content.d.ts +8 -22
- package/dist/content.js +6 -28
- package/dist/content.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/island-boundary.d.ts +4 -0
- package/dist/island-boundary.js +42 -0
- package/dist/island-boundary.js.map +1 -0
- package/dist/island.d.ts +2 -118
- package/dist/island.js +4 -284
- package/dist/island.js.map +1 -1
- package/dist/jsx-types.d.ts +2 -38
- package/dist/jsx-types.js +3 -10
- package/dist/jsx-types.js.map +1 -1
- package/dist/plugins.d.ts +40 -0
- package/dist/plugins.js.map +1 -1
- package/dist/runtime.d.ts +26 -77
- package/dist/runtime.js +307 -340
- package/dist/runtime.js.map +1 -1
- package/dist/zudo-react/client.d.ts +3 -0
- package/dist/zudo-react/client.js +3 -0
- package/dist/zudo-react/client.js.map +1 -0
- package/dist/zudo-react/description.d.ts +16 -0
- package/dist/zudo-react/description.js +64 -0
- package/dist/zudo-react/description.js.map +1 -0
- package/dist/zudo-react/dom-bindings.d.ts +13 -0
- package/dist/zudo-react/dom-bindings.js +58 -0
- package/dist/zudo-react/dom-bindings.js.map +1 -0
- package/dist/zudo-react/escape.d.ts +2 -0
- package/dist/zudo-react/escape.js +7 -0
- package/dist/zudo-react/escape.js.map +1 -0
- package/dist/zudo-react/forms.d.ts +29 -0
- package/dist/zudo-react/forms.js +371 -0
- package/dist/zudo-react/forms.js.map +1 -0
- package/dist/zudo-react/hydrate.d.ts +4 -0
- package/dist/zudo-react/hydrate.js +912 -0
- package/dist/zudo-react/hydrate.js.map +1 -0
- package/dist/zudo-react/index.d.ts +47 -0
- package/dist/zudo-react/index.js +13 -0
- package/dist/zudo-react/index.js.map +1 -0
- package/dist/zudo-react/island-root-type.d.ts +1 -0
- package/dist/zudo-react/island-root-type.js +2 -0
- package/dist/zudo-react/island-root-type.js.map +1 -0
- package/dist/zudo-react/jsx-dev-runtime.d.ts +8 -0
- package/dist/zudo-react/jsx-dev-runtime.js +6 -0
- package/dist/zudo-react/jsx-dev-runtime.js.map +1 -0
- package/dist/zudo-react/jsx-runtime.d.ts +5 -0
- package/dist/zudo-react/jsx-runtime.js +7 -0
- package/dist/zudo-react/jsx-runtime.js.map +1 -0
- package/dist/zudo-react/jsx-types.d.ts +216 -0
- package/dist/zudo-react/jsx-types.js +2 -0
- package/dist/zudo-react/jsx-types.js.map +1 -0
- package/dist/zudo-react/props-transport.d.ts +2 -0
- package/dist/zudo-react/props-transport.js +95 -0
- package/dist/zudo-react/props-transport.js.map +1 -0
- package/dist/zudo-react/reactive-types.d.ts +8 -0
- package/dist/zudo-react/reactive-types.js +2 -0
- package/dist/zudo-react/reactive-types.js.map +1 -0
- package/dist/zudo-react/reactive.d.ts +20 -0
- package/dist/zudo-react/reactive.js +176 -0
- package/dist/zudo-react/reactive.js.map +1 -0
- package/dist/zudo-react/render-html.d.ts +3 -0
- package/dist/zudo-react/render-html.js +535 -0
- package/dist/zudo-react/render-html.js.map +1 -0
- package/dist/zudo-react/root.d.ts +20 -0
- package/dist/zudo-react/root.js +74 -0
- package/dist/zudo-react/root.js.map +1 -0
- package/dist/zudo-react/scheduler.d.ts +12 -0
- package/dist/zudo-react/scheduler.js +113 -0
- package/dist/zudo-react/scheduler.js.map +1 -0
- package/dist/zudo-react/scope.d.ts +42 -0
- package/dist/zudo-react/scope.js +220 -0
- package/dist/zudo-react/scope.js.map +1 -0
- package/dist/zudo-react/server.d.ts +15 -0
- package/dist/zudo-react/server.js +11 -0
- package/dist/zudo-react/server.js.map +1 -0
- package/dist/zudo-react/structure.d.ts +16 -0
- package/dist/zudo-react/structure.js +49 -0
- package/dist/zudo-react/structure.js.map +1 -0
- package/dist/zudo-react/vocabulary.d.ts +12 -0
- package/dist/zudo-react/vocabulary.js +96 -0
- package/dist/zudo-react/vocabulary.js.map +1 -0
- package/package.json +28 -24
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
import { activateForms, prepareForms, reconcileForms, FormError } from "./forms.js";
|
|
2
|
+
import { Fragment, isDescription } from "./description.js";
|
|
3
|
+
import { createScope, withScope } from "./scope.js";
|
|
4
|
+
import { isReactive, read, styleText, setAttribute, bind, listener } from "./dom-bindings.js";
|
|
5
|
+
import { createRoot, diagnostic, report, rootPath, storedRoot, } from "./root.js";
|
|
6
|
+
import { subscribe } from "./reactive.js";
|
|
7
|
+
import { batch } from "./scheduler.js";
|
|
8
|
+
import { Show, For, showProps, forProps, keyed, keyPayload, view } from "./structure.js";
|
|
9
|
+
import { booleanAttrs, commonAttrs, dialectSuggestion, formProps, htmlAttrs, htmlTags, isDialectProp, reactiveModelSuggestion, svgAttrs, svgTags, voidTags, } from "./vocabulary.js";
|
|
10
|
+
const HTML = "http://www.w3.org/1999/xhtml";
|
|
11
|
+
const SVG = "http://www.w3.org/2000/svg";
|
|
12
|
+
const sensitive = new Set("template noscript xmp iframe noembed noframes plaintext".split(" "));
|
|
13
|
+
const tableChildren = {
|
|
14
|
+
table: new Set("caption colgroup thead tbody tfoot".split(" ")),
|
|
15
|
+
thead: new Set(["tr"]),
|
|
16
|
+
tbody: new Set(["tr"]),
|
|
17
|
+
tfoot: new Set(["tr"]),
|
|
18
|
+
tr: new Set(["td", "th"]),
|
|
19
|
+
colgroup: new Set(["col"]),
|
|
20
|
+
};
|
|
21
|
+
const restricted = new Set("title select optgroup option table thead tbody tfoot tr colgroup".split(" "));
|
|
22
|
+
const shapeAttrs = {
|
|
23
|
+
input: ["type", "form"],
|
|
24
|
+
select: ["multiple", "form"],
|
|
25
|
+
option: ["value"],
|
|
26
|
+
textarea: ["form"],
|
|
27
|
+
};
|
|
28
|
+
const protocol = "zudo-react/1";
|
|
29
|
+
function owned(context, name, namespace, parentTag, path, factory, initial, kind, parentScope) {
|
|
30
|
+
const scope = (parentScope ?? context.scope).child(name);
|
|
31
|
+
const fragment = context.document.createDocumentFragment();
|
|
32
|
+
const operationsStart = context.operations.length;
|
|
33
|
+
const formsStart = context.forms.length;
|
|
34
|
+
const previous = context.scope;
|
|
35
|
+
context.scope = scope;
|
|
36
|
+
const cleanups = [];
|
|
37
|
+
try {
|
|
38
|
+
withScope(scope, () => {
|
|
39
|
+
scope.onCleanup(() => {
|
|
40
|
+
for (const cleanup of [...cleanups].reverse())
|
|
41
|
+
cleanup();
|
|
42
|
+
});
|
|
43
|
+
const output = factory();
|
|
44
|
+
if (kind)
|
|
45
|
+
marker(context, fragment, kind, () => render(output, fragment, context, namespace, parentTag, path));
|
|
46
|
+
else
|
|
47
|
+
render(output, fragment, context, namespace, parentTag, path);
|
|
48
|
+
});
|
|
49
|
+
const operations = context.operations.splice(operationsStart);
|
|
50
|
+
const forms = initial ? context.forms.slice(formsStart) : context.forms.splice(formsStart);
|
|
51
|
+
const instance = { scope, fragment, operations, forms, cleanups };
|
|
52
|
+
if (initial)
|
|
53
|
+
for (const form of forms)
|
|
54
|
+
if (!context.formOwners.has(form))
|
|
55
|
+
context.formOwners.set(form, instance);
|
|
56
|
+
return instance;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
context.operations.length = operationsStart;
|
|
60
|
+
context.forms.length = formsStart;
|
|
61
|
+
scope.dispose();
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
context.scope = previous;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function installOwned(instance, map, context, mode) {
|
|
69
|
+
if (mode === "mount" && instance.forms.length) {
|
|
70
|
+
const bindings = prepareForms(instance.forms, map, "mount", context.container);
|
|
71
|
+
reconcileForms(bindings, "mount");
|
|
72
|
+
activateForms(bindings, instance.scope, instance.cleanups, context.options, context.container);
|
|
73
|
+
}
|
|
74
|
+
for (const operation of instance.operations)
|
|
75
|
+
operation(map, instance.cleanups);
|
|
76
|
+
}
|
|
77
|
+
function selfMap(fragment) {
|
|
78
|
+
const map = new Map();
|
|
79
|
+
const visit = (node) => {
|
|
80
|
+
map.set(node, node);
|
|
81
|
+
for (const child of node.childNodes)
|
|
82
|
+
visit(child);
|
|
83
|
+
};
|
|
84
|
+
for (const node of fragment.childNodes)
|
|
85
|
+
visit(node);
|
|
86
|
+
return map;
|
|
87
|
+
}
|
|
88
|
+
class Failure extends Error {
|
|
89
|
+
code;
|
|
90
|
+
phase;
|
|
91
|
+
expected;
|
|
92
|
+
actual;
|
|
93
|
+
path;
|
|
94
|
+
constructor(code, phase, expected, actual, path) {
|
|
95
|
+
super(`${code}: ${expected}; got ${actual}`);
|
|
96
|
+
this.code = code;
|
|
97
|
+
this.phase = phase;
|
|
98
|
+
this.expected = expected;
|
|
99
|
+
this.actual = actual;
|
|
100
|
+
this.path = path;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function fail(code, phase, expected, actual, path) {
|
|
104
|
+
throw new Failure(code, phase, expected, actual, path);
|
|
105
|
+
}
|
|
106
|
+
function append(parent, node) {
|
|
107
|
+
if (node.nodeType === Node.TEXT_NODE && parent.lastChild?.nodeType === Node.TEXT_NODE) {
|
|
108
|
+
parent.lastChild.textContent = (parent.lastChild.textContent ?? "") + (node.textContent ?? "");
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
parent.appendChild(node);
|
|
112
|
+
}
|
|
113
|
+
function marker(context, parent, kind, content) {
|
|
114
|
+
const id = context.next++;
|
|
115
|
+
const open = context.document.createComment(`zr:1:${id}:${kind}`);
|
|
116
|
+
parent.appendChild(open);
|
|
117
|
+
content();
|
|
118
|
+
const close = context.document.createComment(`/zr:1:${id}`);
|
|
119
|
+
parent.appendChild(close);
|
|
120
|
+
if (kind === "h")
|
|
121
|
+
context.opaque.add(open);
|
|
122
|
+
if (kind === "t")
|
|
123
|
+
context.textSlots.add(open);
|
|
124
|
+
return [open, close];
|
|
125
|
+
}
|
|
126
|
+
function range(start, end) {
|
|
127
|
+
const nodes = [];
|
|
128
|
+
for (let node = start; node; node = node.nextSibling) {
|
|
129
|
+
nodes.push(node);
|
|
130
|
+
if (node === end)
|
|
131
|
+
return nodes;
|
|
132
|
+
}
|
|
133
|
+
throw new Error("ZR_REGION_RANGE");
|
|
134
|
+
}
|
|
135
|
+
function removeRange(start, end) {
|
|
136
|
+
for (const node of range(start, end))
|
|
137
|
+
node.parentNode?.removeChild(node);
|
|
138
|
+
}
|
|
139
|
+
function moveRange(start, end, before) {
|
|
140
|
+
if (end.nextSibling === before)
|
|
141
|
+
return;
|
|
142
|
+
const parent = before.parentNode;
|
|
143
|
+
const nodes = range(start, end);
|
|
144
|
+
if (nodes.includes(before))
|
|
145
|
+
return;
|
|
146
|
+
const focused = parent.ownerDocument?.activeElement;
|
|
147
|
+
const focusedInside = focused &&
|
|
148
|
+
nodes.some((node) => node === focused || (node instanceof Element && node.contains(focused)));
|
|
149
|
+
const selection = focusedInside && focused instanceof HTMLInputElement
|
|
150
|
+
? [focused.selectionStart, focused.selectionEnd]
|
|
151
|
+
: null;
|
|
152
|
+
for (const node of nodes)
|
|
153
|
+
parent.insertBefore(node, before);
|
|
154
|
+
if (focusedInside && focused instanceof HTMLElement) {
|
|
155
|
+
focused.focus();
|
|
156
|
+
if (selection &&
|
|
157
|
+
focused instanceof HTMLInputElement &&
|
|
158
|
+
selection[0] != null &&
|
|
159
|
+
selection[1] != null)
|
|
160
|
+
focused.setSelectionRange(selection[0], selection[1]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function containsRange(start, end, target) {
|
|
164
|
+
if (!start.parentNode || start.parentNode !== end.parentNode)
|
|
165
|
+
return false;
|
|
166
|
+
return range(start, end).some((node) => node === target || (node instanceof Element && node.contains(target)));
|
|
167
|
+
}
|
|
168
|
+
function scalar(value, path) {
|
|
169
|
+
if (value == null || typeof value === "boolean")
|
|
170
|
+
return "";
|
|
171
|
+
if (typeof value === "string" || (typeof value === "number" && Number.isFinite(value)))
|
|
172
|
+
return String(value);
|
|
173
|
+
fail("ZR_CHILD", "setup", "scalar", typeof value, path);
|
|
174
|
+
}
|
|
175
|
+
function validateRawHtml(value, tag, path) {
|
|
176
|
+
if (typeof value !== "string" ||
|
|
177
|
+
/<!--\/?zr:1:|data-zfb-island(?:-skip-ssr)?\s*=/.test(value) ||
|
|
178
|
+
(tag === "script" && /<\/script/i.test(value)) ||
|
|
179
|
+
(tag === "style" && /<\/style/i.test(value)))
|
|
180
|
+
fail("ZR_RAW_HTML", "setup", "valid rawHtml", typeof value, path);
|
|
181
|
+
}
|
|
182
|
+
function children(value, parent, context, namespace, tag, path) {
|
|
183
|
+
if (value == null || typeof value === "boolean")
|
|
184
|
+
return;
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
value.forEach((entry, index) => render(entry, parent, context, namespace, tag, `${path}[${index}]`));
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
render(value, parent, context, namespace, tag, path);
|
|
190
|
+
}
|
|
191
|
+
function validatePosition(tag, parent, context, path) {
|
|
192
|
+
if (sensitive.has(tag))
|
|
193
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "hydratable element", tag, path);
|
|
194
|
+
if (tableChildren[parent] && !tableChildren[parent].has(tag))
|
|
195
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", `valid ${parent} child`, tag, path);
|
|
196
|
+
if ((parent === "select" && tag !== "option" && tag !== "optgroup") ||
|
|
197
|
+
(parent === "optgroup" && tag !== "option"))
|
|
198
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", `valid ${parent} child`, tag, path);
|
|
199
|
+
}
|
|
200
|
+
function element(desc, parent, context, namespace, parentTag, path) {
|
|
201
|
+
const tag = desc.type;
|
|
202
|
+
validatePosition(tag, parentTag, context, path);
|
|
203
|
+
const ownNamespace = tag === "svg" || namespace === SVG ? SVG : HTML;
|
|
204
|
+
const custom = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(tag);
|
|
205
|
+
if (ownNamespace === SVG ? !svgTags.has(tag) : !custom && !htmlTags.has(tag))
|
|
206
|
+
fail("ZR_TAG", "setup", "supported element", tag, path);
|
|
207
|
+
const childNamespace = tag === "foreignObject" ? HTML : ownNamespace;
|
|
208
|
+
const element = context.document.createElementNS(ownNamespace, tag);
|
|
209
|
+
const props = desc.props;
|
|
210
|
+
if (tag === "select" && read(props.multiple) === true)
|
|
211
|
+
fail("ZR_MODEL_UNSUPPORTED", "preflight", "single select", "multiple", path);
|
|
212
|
+
if (voidTags.has(tag) && ("children" in props || "rawHtml" in props))
|
|
213
|
+
fail("ZR_VOID_CHILDREN", "setup", "empty element", tag, path);
|
|
214
|
+
if ("rawHtml" in props && "children" in props)
|
|
215
|
+
fail("ZR_RAW_HTML", "setup", "rawHtml without children", tag, path);
|
|
216
|
+
if (tag === "textarea" &&
|
|
217
|
+
"children" in props &&
|
|
218
|
+
("modelValue" in props || "defaultValue" in props))
|
|
219
|
+
fail("ZR_MODEL_CONFLICT", "preflight", "textarea model/default without children", "children", path);
|
|
220
|
+
context.forms.push({ element, props, path });
|
|
221
|
+
for (const [name, original] of Object.entries(props)) {
|
|
222
|
+
if (name === "children" || name === "key" || name === "rawHtml")
|
|
223
|
+
continue;
|
|
224
|
+
if (formProps.has(name)) {
|
|
225
|
+
if (name === "defaultValue" && tag === "input")
|
|
226
|
+
setAttribute(element, "value", original);
|
|
227
|
+
if (name === "defaultChecked" && tag === "input")
|
|
228
|
+
setAttribute(element, "checked", original);
|
|
229
|
+
if (name === "modelValue" && tag === "input" && read(props.type) !== "radio")
|
|
230
|
+
setAttribute(element, "value", read(original));
|
|
231
|
+
if (name === "modelChecked" && tag === "input")
|
|
232
|
+
setAttribute(element, "checked", read(original));
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (isDialectProp(name, ownNamespace === SVG ? "svg" : "html", custom))
|
|
236
|
+
fail("ZR_PROP_DIALECT", "setup", dialectSuggestion(name, ownNamespace === SVG ? "svg" : "html", custom) ??
|
|
237
|
+
"HTML-spelled prop", name, path);
|
|
238
|
+
if (name === "ref") {
|
|
239
|
+
if (!original || typeof original !== "object" || !("current" in original))
|
|
240
|
+
fail("ZR_REF", "setup", "object ref", typeof original, path);
|
|
241
|
+
const ref = original;
|
|
242
|
+
context.operations.push((map, cleanups) => {
|
|
243
|
+
const node = map.get(element);
|
|
244
|
+
ref.current = node;
|
|
245
|
+
cleanups.push(() => {
|
|
246
|
+
if (ref.current === node)
|
|
247
|
+
ref.current = null;
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (name.startsWith("on:")) {
|
|
253
|
+
const parsed = listener(name);
|
|
254
|
+
if (!parsed || typeof original !== "function")
|
|
255
|
+
fail("ZR_LISTENER", "setup", "native listener", name, path);
|
|
256
|
+
context.operations.push((map, cleanups) => {
|
|
257
|
+
const node = map.get(element);
|
|
258
|
+
const handler = (event) => {
|
|
259
|
+
try {
|
|
260
|
+
const result = original(event);
|
|
261
|
+
if (result && typeof result === "object" && "then" in result)
|
|
262
|
+
Promise.resolve(result).catch((error) => report(context.options, diagnostic(context.options, "ZR_EVENT_ERROR", "update", `${rootPath(context.container)}${path}`, "handler success", String(error))));
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
report(context.options, diagnostic(context.options, "ZR_EVENT_ERROR", "update", `${rootPath(context.container)}${path}`, "handler success", String(error)));
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
node.addEventListener(parsed.name, handler, parsed.capture);
|
|
269
|
+
cleanups.push(() => node.removeEventListener(parsed.name, handler, parsed.capture));
|
|
270
|
+
});
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (name === "value" || name === "checked") {
|
|
274
|
+
if (isReactive(original)) {
|
|
275
|
+
const suggestion = reactiveModelSuggestion(tag, name, String(read(props.type) ?? "text"), custom);
|
|
276
|
+
fail("ZR_MODEL_UNSUPPORTED", "preflight", suggestion ?? "static value", name, path);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!/^[A-Za-z_:][A-Za-z0-9_:.-]*$/.test(name) ||
|
|
280
|
+
(!custom &&
|
|
281
|
+
!commonAttrs.has(name) &&
|
|
282
|
+
!(ownNamespace === SVG ? svgAttrs.has(name) : htmlAttrs.has(name)) &&
|
|
283
|
+
!/^data-[\w.-]+$/.test(name) &&
|
|
284
|
+
!/^aria-[\w.-]+$/.test(name) &&
|
|
285
|
+
!/^on[a-z]+$/.test(name)))
|
|
286
|
+
fail("ZR_ATTRIBUTE", "setup", "supported attribute", name, path);
|
|
287
|
+
const initial = read(original);
|
|
288
|
+
if (custom && initial != null && typeof initial !== "string")
|
|
289
|
+
fail("ZR_ATTRIBUTE", "setup", "custom element string attribute", name, path);
|
|
290
|
+
if (/^on[a-z]/.test(name) && typeof initial === "function")
|
|
291
|
+
fail("ZR_PROP_DIALECT", "setup", "on:event", name, path);
|
|
292
|
+
if (initial != null) {
|
|
293
|
+
if (name === "start" &&
|
|
294
|
+
typeof initial !== "string" &&
|
|
295
|
+
!(typeof initial === "number" && Number.isFinite(initial)))
|
|
296
|
+
fail("ZR_ATTRIBUTE", "setup", "string or finite number", typeof initial, path);
|
|
297
|
+
else if (ownNamespace === SVG &&
|
|
298
|
+
(name === "width" || name === "height") &&
|
|
299
|
+
typeof initial !== "string" &&
|
|
300
|
+
!(typeof initial === "number" && Number.isFinite(initial)))
|
|
301
|
+
fail("ZR_ATTRIBUTE", "setup", "SVG dimension string or number", typeof initial, path);
|
|
302
|
+
if (name === "style")
|
|
303
|
+
styleText(initial);
|
|
304
|
+
else if (booleanAttrs.has(name) && typeof initial !== "boolean")
|
|
305
|
+
fail("ZR_ATTRIBUTE", "setup", "boolean", typeof initial, path);
|
|
306
|
+
else if (typeof initial !== "string" &&
|
|
307
|
+
typeof initial !== "number" &&
|
|
308
|
+
typeof initial !== "boolean")
|
|
309
|
+
fail("ZR_ATTRIBUTE", "setup", "scalar", typeof initial, path);
|
|
310
|
+
setAttribute(element, name, initial);
|
|
311
|
+
}
|
|
312
|
+
if (isReactive(original)) {
|
|
313
|
+
const bindingScope = context.scope;
|
|
314
|
+
context.operations.push((map, cleanups) => {
|
|
315
|
+
const node = map.get(element);
|
|
316
|
+
const subscription = bind(original, bindingScope, context.options, context.container, path, (value) => {
|
|
317
|
+
if (name === "start" &&
|
|
318
|
+
value != null &&
|
|
319
|
+
typeof value !== "string" &&
|
|
320
|
+
!(typeof value === "number" && Number.isFinite(value)))
|
|
321
|
+
throw new TypeError("ZR_ATTRIBUTE: start requires a string or finite number");
|
|
322
|
+
if (ownNamespace === SVG &&
|
|
323
|
+
(name === "width" || name === "height") &&
|
|
324
|
+
value != null &&
|
|
325
|
+
typeof value !== "string" &&
|
|
326
|
+
!(typeof value === "number" && Number.isFinite(value)))
|
|
327
|
+
throw new TypeError(`ZR_ATTRIBUTE: SVG dimension ${name} requires a string or number`);
|
|
328
|
+
setAttribute(node, name, value);
|
|
329
|
+
}, initial);
|
|
330
|
+
cleanups.push(() => subscription.dispose());
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if ("rawHtml" in props) {
|
|
335
|
+
const raw = props.rawHtml;
|
|
336
|
+
if (ownNamespace === SVG || ["textarea", "title", "select", "option"].includes(tag))
|
|
337
|
+
fail("ZR_RAW_HTML", "setup", "ordinary HTML container", tag, path);
|
|
338
|
+
if ((tag === "script" || tag === "style") && isReactive(raw))
|
|
339
|
+
fail("ZR_RAW_HTML", "setup", "static rawHtml", tag, path);
|
|
340
|
+
const initial = read(raw);
|
|
341
|
+
validateRawHtml(initial, tag, path);
|
|
342
|
+
let region;
|
|
343
|
+
if (tag === "script" || tag === "style")
|
|
344
|
+
element.innerHTML = initial;
|
|
345
|
+
else
|
|
346
|
+
region = marker(context, element, "h", () => {
|
|
347
|
+
element.insertAdjacentHTML("beforeend", initial);
|
|
348
|
+
});
|
|
349
|
+
const bindingScope = context.scope;
|
|
350
|
+
if (isReactive(raw) && region) {
|
|
351
|
+
const [open, close] = region;
|
|
352
|
+
context.operations.push((map, cleanups) => {
|
|
353
|
+
const node = map.get(element);
|
|
354
|
+
const start = map.get(open);
|
|
355
|
+
const end = map.get(close);
|
|
356
|
+
let previous = initial;
|
|
357
|
+
const subscription = bind(raw, bindingScope, context.options, context.container, path, (value) => {
|
|
358
|
+
validateRawHtml(value, tag, path);
|
|
359
|
+
if (value === previous)
|
|
360
|
+
return;
|
|
361
|
+
const template = context.document.createElement("template");
|
|
362
|
+
template.innerHTML = value;
|
|
363
|
+
if (start.parentNode !== node || end.parentNode !== node)
|
|
364
|
+
throw new Error("ZR_REGION_RANGE");
|
|
365
|
+
for (const child of range(start, end).slice(1, -1))
|
|
366
|
+
node.removeChild(child);
|
|
367
|
+
node.insertBefore(template.content, end);
|
|
368
|
+
previous = value;
|
|
369
|
+
}, initial);
|
|
370
|
+
cleanups.push(() => subscription.dispose());
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (tag === "textarea" &&
|
|
375
|
+
(props.modelValue !== undefined || props.defaultValue !== undefined)) {
|
|
376
|
+
const value = String(read(props.modelValue ?? props.defaultValue));
|
|
377
|
+
element.textContent = value;
|
|
378
|
+
}
|
|
379
|
+
else if (restricted.has(tag)) {
|
|
380
|
+
if (tag === "title" || tag === "option") {
|
|
381
|
+
const value = props.children;
|
|
382
|
+
if (isReactive(value) || isDescription(value))
|
|
383
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "static text", tag, path);
|
|
384
|
+
append(element, context.document.createTextNode(scalar(value, path)));
|
|
385
|
+
}
|
|
386
|
+
else
|
|
387
|
+
children(props.children, element, context, childNamespace, tag, `${path}/${tag}`);
|
|
388
|
+
}
|
|
389
|
+
else
|
|
390
|
+
children(props.children, element, context, childNamespace, tag, `${path}/${tag}`);
|
|
391
|
+
if (tag === "select" && (props.modelValue !== undefined || props.defaultValue !== undefined)) {
|
|
392
|
+
const value = String(read(props.modelValue ?? props.defaultValue));
|
|
393
|
+
const select = element;
|
|
394
|
+
if (![...select.options].some((option) => option.value === value))
|
|
395
|
+
fail("ZR_MODEL_VALUE", "preflight", "matching select option", value, path);
|
|
396
|
+
for (const option of select.options)
|
|
397
|
+
option.selected = option.value === value;
|
|
398
|
+
}
|
|
399
|
+
if (tag === "input" && read(props.type) === "radio" && props.modelValue !== undefined)
|
|
400
|
+
setAttribute(element, "checked", read(props.modelValue) === read(props.value));
|
|
401
|
+
parent.appendChild(element);
|
|
402
|
+
}
|
|
403
|
+
function render(value, parent, context, namespace, parentTag, path) {
|
|
404
|
+
if (value == null || typeof value === "boolean")
|
|
405
|
+
return;
|
|
406
|
+
if (Array.isArray(value)) {
|
|
407
|
+
marker(context, parent, "f", () => children(value, parent, context, namespace, parentTag, path));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (isReactive(value)) {
|
|
411
|
+
const initialValue = read(value);
|
|
412
|
+
const initial = scalar(initialValue, path);
|
|
413
|
+
marker(context, parent, "t", () => {
|
|
414
|
+
if (initial)
|
|
415
|
+
parent.appendChild(context.document.createTextNode(initial));
|
|
416
|
+
});
|
|
417
|
+
const opener = parent.childNodes[parent.childNodes.length - (initial ? 3 : 2)];
|
|
418
|
+
const bindingScope = context.scope;
|
|
419
|
+
context.operations.push((map, cleanups) => {
|
|
420
|
+
const start = map.get(opener);
|
|
421
|
+
const end = start.nextSibling?.nodeType === Node.TEXT_NODE
|
|
422
|
+
? start.nextSibling.nextSibling
|
|
423
|
+
: start.nextSibling;
|
|
424
|
+
let textNode = start.nextSibling?.nodeType === Node.TEXT_NODE ? start.nextSibling : null;
|
|
425
|
+
if (!textNode) {
|
|
426
|
+
textNode = context.document.createTextNode("");
|
|
427
|
+
start.parentNode.insertBefore(textNode, end ?? null);
|
|
428
|
+
}
|
|
429
|
+
const slot = textNode;
|
|
430
|
+
const subscription = bind(value, bindingScope, context.options, context.container, path, (next) => {
|
|
431
|
+
const text = scalar(next, path);
|
|
432
|
+
if (slot.data !== text)
|
|
433
|
+
slot.data = text;
|
|
434
|
+
}, initialValue);
|
|
435
|
+
cleanups.push(() => subscription.dispose());
|
|
436
|
+
});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
440
|
+
append(parent, context.document.createTextNode(scalar(value, path)));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (!isDescription(value))
|
|
444
|
+
fail("ZR_CHILD", "setup", "description", typeof value, path);
|
|
445
|
+
if (value.type === Fragment) {
|
|
446
|
+
marker(context, parent, "f", () => children(value.props.children, parent, context, namespace, parentTag, path));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (value.type === Show) {
|
|
450
|
+
if (restricted.has(parentTag))
|
|
451
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "flow region", parentTag, path);
|
|
452
|
+
const props = showProps(value);
|
|
453
|
+
if (!isReactive(props.when) ||
|
|
454
|
+
typeof props.children !== "function" ||
|
|
455
|
+
(props.fallback !== undefined && typeof props.fallback !== "function"))
|
|
456
|
+
fail("ZR_CHILD", "setup", "Show signal and factories", "invalid props", path);
|
|
457
|
+
const selected = read(props.when);
|
|
458
|
+
if (typeof selected !== "boolean")
|
|
459
|
+
fail("ZR_CHILD", "setup", "boolean Show.when", typeof selected, path);
|
|
460
|
+
let initial = null;
|
|
461
|
+
const ends = marker(context, parent, `s:${selected ? 1 : 0}`, () => {
|
|
462
|
+
const factory = selected ? props.children : props.fallback;
|
|
463
|
+
if (factory) {
|
|
464
|
+
initial = owned(context, "Show", namespace, parentTag, path, factory, true);
|
|
465
|
+
parent.appendChild(initial.fragment);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
const owner = context.scope;
|
|
469
|
+
context.operations.push((map, cleanups) => {
|
|
470
|
+
const start = map.get(ends[0]);
|
|
471
|
+
const end = map.get(ends[1]);
|
|
472
|
+
let current = initial;
|
|
473
|
+
let shown = selected;
|
|
474
|
+
if (current)
|
|
475
|
+
installOwned(current, map, context, "hydrate");
|
|
476
|
+
const subscription = subscribe(() => {
|
|
477
|
+
const next = props.when.value;
|
|
478
|
+
if (next === shown)
|
|
479
|
+
return;
|
|
480
|
+
const factory = next ? props.children : props.fallback;
|
|
481
|
+
let replacement = null;
|
|
482
|
+
try {
|
|
483
|
+
if (factory)
|
|
484
|
+
replacement = owned(context, "Show", namespace, parentTag, path, factory, false, undefined, owner);
|
|
485
|
+
const replacementMap = replacement ? selfMap(replacement.fragment) : null;
|
|
486
|
+
if (replacement && replacement.forms.length)
|
|
487
|
+
prepareForms(replacement.forms, replacementMap, "mount", context.container);
|
|
488
|
+
if (current) {
|
|
489
|
+
current.scope.dispose();
|
|
490
|
+
let node = start.nextSibling;
|
|
491
|
+
while (node && node !== end) {
|
|
492
|
+
const following = node.nextSibling;
|
|
493
|
+
node.parentNode?.removeChild(node);
|
|
494
|
+
node = following;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (replacement) {
|
|
498
|
+
end.parentNode.insertBefore(replacement.fragment, end);
|
|
499
|
+
installOwned(replacement, replacementMap, context, "mount");
|
|
500
|
+
replacement.scope.activate();
|
|
501
|
+
}
|
|
502
|
+
current = replacement;
|
|
503
|
+
shown = next;
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
replacement?.scope.dispose();
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}, {
|
|
510
|
+
active: () => owner.active,
|
|
511
|
+
report: (code, actual) => report(context.options, diagnostic(context.options, code, "update", path, "Show update", actual)),
|
|
512
|
+
});
|
|
513
|
+
subscription.run();
|
|
514
|
+
cleanups.push(() => subscription.dispose());
|
|
515
|
+
});
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (value.type === For) {
|
|
519
|
+
if (restricted.has(parentTag))
|
|
520
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "flow region", parentTag, path);
|
|
521
|
+
const props = forProps(value);
|
|
522
|
+
if (!isReactive(props.each) ||
|
|
523
|
+
typeof props.by !== "function" ||
|
|
524
|
+
typeof props.children !== "function")
|
|
525
|
+
fail("ZR_CHILD", "setup", "For signal, key and factory", "invalid props", path);
|
|
526
|
+
const items = read(props.each);
|
|
527
|
+
if (!Array.isArray(items))
|
|
528
|
+
fail("ZR_CHILD", "setup", "array For.each", typeof items, path);
|
|
529
|
+
const keys = keyed(items, props.by, path);
|
|
530
|
+
const entries = [];
|
|
531
|
+
const ends = marker(context, parent, "l", () => {
|
|
532
|
+
items.forEach((item, index) => {
|
|
533
|
+
const key = keys[index];
|
|
534
|
+
const itemView = view(item);
|
|
535
|
+
const indexView = view(index);
|
|
536
|
+
const instance = owned(context, "For", namespace, parentTag, path, () => props.children(itemView.value, indexView.value), true, `i:${keyPayload(key)}`);
|
|
537
|
+
const nodes = [...instance.fragment.childNodes];
|
|
538
|
+
entries.push({
|
|
539
|
+
key,
|
|
540
|
+
owned: instance,
|
|
541
|
+
start: nodes[0],
|
|
542
|
+
end: nodes[nodes.length - 1],
|
|
543
|
+
item: itemView,
|
|
544
|
+
index: indexView,
|
|
545
|
+
});
|
|
546
|
+
parent.appendChild(instance.fragment);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
const owner = context.scope;
|
|
550
|
+
context.operations.push((map, cleanups) => {
|
|
551
|
+
let current = entries.map((entry) => ({
|
|
552
|
+
...entry,
|
|
553
|
+
start: map.get(entry.start),
|
|
554
|
+
end: map.get(entry.end),
|
|
555
|
+
}));
|
|
556
|
+
for (const entry of current)
|
|
557
|
+
installOwned(entry.owned, map, context, "hydrate");
|
|
558
|
+
const end = map.get(ends[1]);
|
|
559
|
+
const subscription = subscribe(() => {
|
|
560
|
+
const next = props.each.value;
|
|
561
|
+
if (!Array.isArray(next))
|
|
562
|
+
throw new TypeError(`ZR_CHILD: For.each at ${path}`);
|
|
563
|
+
let nextKeys;
|
|
564
|
+
try {
|
|
565
|
+
nextKeys = keyed(next, props.by, path);
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
const detail = String(error);
|
|
569
|
+
const code = detail.includes("ZR_DUPLICATE_KEY")
|
|
570
|
+
? "ZR_DUPLICATE_KEY"
|
|
571
|
+
: detail.includes("ZR_KEY_TYPE")
|
|
572
|
+
? "ZR_KEY_TYPE"
|
|
573
|
+
: "ZR_SUBSCRIBER_ERROR";
|
|
574
|
+
report(context.options, diagnostic(context.options, code, "update", path, "unique string or finite number keys", detail));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const old = new Map(current.map((entry) => [entry.key, entry]));
|
|
578
|
+
const created = [];
|
|
579
|
+
try {
|
|
580
|
+
const ordered = nextKeys.map((key, index) => {
|
|
581
|
+
const retained = old.get(key);
|
|
582
|
+
if (retained)
|
|
583
|
+
return retained;
|
|
584
|
+
const itemView = view(next[index]);
|
|
585
|
+
const indexView = view(index);
|
|
586
|
+
const instance = owned(context, "For", namespace, parentTag, path, () => props.children(itemView.value, indexView.value), false, `i:${keyPayload(key)}`, owner);
|
|
587
|
+
const nodes = [...instance.fragment.childNodes];
|
|
588
|
+
const entry = {
|
|
589
|
+
key,
|
|
590
|
+
owned: instance,
|
|
591
|
+
start: nodes[0],
|
|
592
|
+
end: nodes[nodes.length - 1],
|
|
593
|
+
item: itemView,
|
|
594
|
+
index: indexView,
|
|
595
|
+
};
|
|
596
|
+
created.push(entry);
|
|
597
|
+
return entry;
|
|
598
|
+
});
|
|
599
|
+
for (const entry of created)
|
|
600
|
+
if (entry.owned.forms.length)
|
|
601
|
+
prepareForms(entry.owned.forms, selfMap(entry.owned.fragment), "mount", context.container);
|
|
602
|
+
for (const entry of current)
|
|
603
|
+
if (!nextKeys.includes(entry.key)) {
|
|
604
|
+
entry.owned.scope.dispose();
|
|
605
|
+
removeRange(entry.start, entry.end);
|
|
606
|
+
}
|
|
607
|
+
const focused = end.ownerDocument?.activeElement;
|
|
608
|
+
const anchored = focused && current.find((entry) => containsRange(entry.start, entry.end, focused));
|
|
609
|
+
const anchorIndex = anchored ? ordered.indexOf(anchored) : -1;
|
|
610
|
+
const place = (index, before) => {
|
|
611
|
+
const entry = ordered[index];
|
|
612
|
+
if (created.includes(entry)) {
|
|
613
|
+
const itemMap = selfMap(entry.owned.fragment);
|
|
614
|
+
before.parentNode.insertBefore(entry.owned.fragment, before);
|
|
615
|
+
installOwned(entry.owned, itemMap, context, "mount");
|
|
616
|
+
entry.owned.scope.activate();
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
moveRange(entry.start, entry.end, before);
|
|
620
|
+
return entry.start;
|
|
621
|
+
};
|
|
622
|
+
let before = end;
|
|
623
|
+
for (let index = ordered.length - 1; index > anchorIndex; index--)
|
|
624
|
+
before = place(index, before);
|
|
625
|
+
if (anchorIndex >= 0) {
|
|
626
|
+
before = anchored.start;
|
|
627
|
+
for (let index = anchorIndex - 1; index >= 0; index--)
|
|
628
|
+
before = place(index, before);
|
|
629
|
+
}
|
|
630
|
+
batch(() => ordered.forEach((entry, index) => {
|
|
631
|
+
entry.item.write(next[index]);
|
|
632
|
+
entry.index.write(index);
|
|
633
|
+
}));
|
|
634
|
+
current = ordered;
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
for (const entry of created)
|
|
638
|
+
entry.owned.scope.dispose();
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
}, {
|
|
642
|
+
active: () => owner.active,
|
|
643
|
+
report: (code, actual) => report(context.options, diagnostic(context.options, code, "update", path, "For update", actual)),
|
|
644
|
+
});
|
|
645
|
+
subscription.run();
|
|
646
|
+
cleanups.push(() => subscription.dispose());
|
|
647
|
+
});
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (typeof value.type === "string") {
|
|
651
|
+
element(value, parent, context, namespace, parentTag, path);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (typeof value.type !== "function")
|
|
655
|
+
fail("ZR_CHILD", "setup", "component", typeof value.type, path);
|
|
656
|
+
if (restricted.has(parentTag))
|
|
657
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "intrinsic child", "component", path);
|
|
658
|
+
const component = value.type;
|
|
659
|
+
const childScope = context.scope.child(component.name || "Anonymous");
|
|
660
|
+
const previous = context.scope;
|
|
661
|
+
context.scope = childScope;
|
|
662
|
+
try {
|
|
663
|
+
marker(context, parent, "c", () => {
|
|
664
|
+
const output = withScope(childScope, () => component(value.props));
|
|
665
|
+
if (output && typeof output === "object" && "then" in output)
|
|
666
|
+
fail("ZR_ASYNC_COMPONENT", "setup", "synchronous component", "promise", path);
|
|
667
|
+
render(output, parent, context, namespace, parentTag, path);
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
context.scope = previous;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function normalize(text, pre) {
|
|
675
|
+
const line = text.replace(/\r\n?/g, "\n");
|
|
676
|
+
return pre ? line : line.replace(/[\t\n\f\r ]+/g, " ").replace(/^ | $/g, "");
|
|
677
|
+
}
|
|
678
|
+
function compare(expected, actual, map, context, path, pre = false) {
|
|
679
|
+
if (expected.nodeType !== actual.nodeType)
|
|
680
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", expected.nodeName, actual.nodeName, path);
|
|
681
|
+
map.set(expected, actual);
|
|
682
|
+
if (expected.nodeType === Node.TEXT_NODE) {
|
|
683
|
+
if (normalize(expected.textContent ?? "", pre) !== normalize(actual.textContent ?? "", pre))
|
|
684
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "matching text", "different text", path);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (expected.nodeType === Node.COMMENT_NODE) {
|
|
688
|
+
if (expected.textContent !== actual.textContent)
|
|
689
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", expected.textContent ?? "", actual.textContent ?? "", path);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (expected.nodeType === Node.ELEMENT_NODE) {
|
|
693
|
+
const wanted = expected;
|
|
694
|
+
const found = actual;
|
|
695
|
+
if (wanted.localName !== found.localName || wanted.namespaceURI !== found.namespaceURI)
|
|
696
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", `${wanted.namespaceURI}:${wanted.localName}`, `${found.namespaceURI}:${found.localName}`, path);
|
|
697
|
+
const shape = [...(shapeAttrs[wanted.localName] ?? [])];
|
|
698
|
+
if (wanted.localName === "input" && (wanted.getAttribute("type") ?? "text") === "radio")
|
|
699
|
+
shape.push("name", "value");
|
|
700
|
+
for (const name of shape) {
|
|
701
|
+
const expectedValue = name === "type" && wanted.localName === "input"
|
|
702
|
+
? (wanted.getAttribute(name) ?? "text")
|
|
703
|
+
: wanted.getAttribute(name);
|
|
704
|
+
const actualValue = name === "type" && wanted.localName === "input"
|
|
705
|
+
? (found.getAttribute(name) ?? "text")
|
|
706
|
+
: found.getAttribute(name);
|
|
707
|
+
if (expectedValue !== actualValue)
|
|
708
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", `${name}=${expectedValue}`, `${name}=${actualValue}`, path);
|
|
709
|
+
}
|
|
710
|
+
if (wanted.localName === "script" || wanted.localName === "style")
|
|
711
|
+
return;
|
|
712
|
+
compareChildren(wanted, found, map, context, path, pre || wanted.localName === "pre");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function significant(nodes, pre) {
|
|
716
|
+
return pre
|
|
717
|
+
? nodes
|
|
718
|
+
: nodes.filter((node) => node.nodeType !== Node.TEXT_NODE || normalize(node.textContent ?? "", false) !== "");
|
|
719
|
+
}
|
|
720
|
+
function matchingClose(nodes, start) {
|
|
721
|
+
const open = nodes[start].textContent ?? "";
|
|
722
|
+
const id = /^zr:1:(\d+):/.exec(open)?.[1];
|
|
723
|
+
if (id === undefined)
|
|
724
|
+
return -1;
|
|
725
|
+
let depth = 0;
|
|
726
|
+
for (let i = start + 1; i < nodes.length; i++) {
|
|
727
|
+
const value = nodes[i].textContent;
|
|
728
|
+
if (value?.startsWith("zr:1:"))
|
|
729
|
+
depth++;
|
|
730
|
+
else if (value?.startsWith("/zr:1:")) {
|
|
731
|
+
if (depth)
|
|
732
|
+
depth--;
|
|
733
|
+
else
|
|
734
|
+
return value === `/zr:1:${id}` ? i : -1;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return -1;
|
|
738
|
+
}
|
|
739
|
+
function compareChildren(expected, actual, map, context, path, pre = false) {
|
|
740
|
+
const wants = significant([...expected.childNodes], pre);
|
|
741
|
+
const has = significant([...actual.childNodes], pre);
|
|
742
|
+
let i = 0;
|
|
743
|
+
let j = 0;
|
|
744
|
+
while (i < wants.length && j < has.length) {
|
|
745
|
+
const wanted = wants[i];
|
|
746
|
+
const found = has[j];
|
|
747
|
+
const childPath = `${path}/${wanted.nodeName.toLowerCase()}[${i}]`;
|
|
748
|
+
if (context.opaque.has(wanted)) {
|
|
749
|
+
compare(wanted, found, map, context, childPath, pre);
|
|
750
|
+
const wantedEnd = matchingClose(wants, i);
|
|
751
|
+
const foundEnd = matchingClose(has, j);
|
|
752
|
+
if (wantedEnd < 0 || foundEnd < 0)
|
|
753
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "balanced rawHtml marker", "missing marker", childPath);
|
|
754
|
+
map.set(wants[wantedEnd], has[foundEnd]);
|
|
755
|
+
i = wantedEnd + 1;
|
|
756
|
+
j = foundEnd + 1;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (context.textSlots.has(wanted)) {
|
|
760
|
+
compare(wanted, found, map, context, childPath, pre);
|
|
761
|
+
const wantedEnd = matchingClose(wants, i);
|
|
762
|
+
const foundEnd = matchingClose(has, j);
|
|
763
|
+
if (wantedEnd !== i + 1 && wantedEnd !== i + 2)
|
|
764
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "single text slot", "invalid slot", childPath);
|
|
765
|
+
if (foundEnd !== j + 1 && foundEnd !== j + 2)
|
|
766
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "single text slot", "invalid slot", childPath);
|
|
767
|
+
if (wantedEnd === i + 2) {
|
|
768
|
+
if (foundEnd !== j + 2)
|
|
769
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "text slot", "empty slot", childPath);
|
|
770
|
+
compare(wants[i + 1], has[j + 1], map, context, childPath, pre);
|
|
771
|
+
}
|
|
772
|
+
else if (foundEnd === j + 2 && normalize(has[j + 1].textContent ?? "", pre) !== "")
|
|
773
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", "empty slot", "nonempty slot", childPath);
|
|
774
|
+
map.set(wants[wantedEnd], has[foundEnd]);
|
|
775
|
+
i = wantedEnd + 1;
|
|
776
|
+
j = foundEnd + 1;
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
compare(wanted, found, map, context, childPath, pre);
|
|
780
|
+
i++;
|
|
781
|
+
j++;
|
|
782
|
+
}
|
|
783
|
+
if (i !== wants.length || j !== has.length)
|
|
784
|
+
fail("ZR_HYDRATION_MISMATCH", "preflight", `${wants.length} children`, `${has.length} children`, path);
|
|
785
|
+
}
|
|
786
|
+
function validate(container, options, mode) {
|
|
787
|
+
const path = rootPath(container);
|
|
788
|
+
if (storedRoot(container) && !storedRoot(container).disposed)
|
|
789
|
+
fail("ZR_DUPLICATE_ROOT", "preflight", "unowned root", "live root", path);
|
|
790
|
+
if (options.signal?.aborted || !container.isConnected)
|
|
791
|
+
fail("ZR_CANCELLED", "preflight", "connected root", "aborted or disconnected", path);
|
|
792
|
+
const component = container.getAttribute(mode === "hydrate" ? "data-zfb-island" : "data-zfb-island-skip-ssr") ??
|
|
793
|
+
container.getAttribute("data-zfb-island");
|
|
794
|
+
const transport = container.getAttribute("data-zfb-transport");
|
|
795
|
+
const serverProtocol = container.getAttribute("data-zfb-protocol");
|
|
796
|
+
const build = container.getAttribute("data-zfb-build");
|
|
797
|
+
if (component !== options.identity.component ||
|
|
798
|
+
transport !== "json/1" ||
|
|
799
|
+
serverProtocol !== protocol ||
|
|
800
|
+
build !== options.identity.build)
|
|
801
|
+
fail("ZR_IDENTITY", "preflight", `${options.identity.component}/${protocol}/${options.identity.build}/json/1`, `${component}/${serverProtocol}/${build}/${transport}`, path);
|
|
802
|
+
if (container.querySelector("[data-zfb-island], [data-zfb-island-skip-ssr]"))
|
|
803
|
+
fail("ZR_UNSUPPORTED_POSITION", "preflight", "no nested root", "nested root", path);
|
|
804
|
+
}
|
|
805
|
+
function execute(node, container, options, mode) {
|
|
806
|
+
let scope;
|
|
807
|
+
const cleanups = [];
|
|
808
|
+
let committed = false;
|
|
809
|
+
try {
|
|
810
|
+
validate(container, options, mode);
|
|
811
|
+
if (!isDescription(node) ||
|
|
812
|
+
typeof node.type !== "function" ||
|
|
813
|
+
(node.type.displayName ?? node.type.name) !==
|
|
814
|
+
options.identity.component)
|
|
815
|
+
fail("ZR_IDENTITY", "preflight", options.identity.component, "different child component", rootPath(container));
|
|
816
|
+
scope = createScope({
|
|
817
|
+
component: options.identity.component,
|
|
818
|
+
...(options.report ? { reporter: options.report } : {}),
|
|
819
|
+
protocol,
|
|
820
|
+
build: options.identity.build,
|
|
821
|
+
path: rootPath(container),
|
|
822
|
+
});
|
|
823
|
+
const context = {
|
|
824
|
+
document: container.ownerDocument,
|
|
825
|
+
options,
|
|
826
|
+
container,
|
|
827
|
+
root: scope,
|
|
828
|
+
scope,
|
|
829
|
+
next: 0,
|
|
830
|
+
operations: [],
|
|
831
|
+
forms: [],
|
|
832
|
+
formOwners: new Map(),
|
|
833
|
+
opaque: new Set(),
|
|
834
|
+
textSlots: new Set(),
|
|
835
|
+
};
|
|
836
|
+
const fragment = container.ownerDocument.createDocumentFragment();
|
|
837
|
+
render(node, fragment, context, HTML, "", rootPath(container));
|
|
838
|
+
const built = [...fragment.childNodes];
|
|
839
|
+
const map = new Map();
|
|
840
|
+
if (mode === "hydrate")
|
|
841
|
+
compareChildren(fragment, container, map, context, rootPath(container));
|
|
842
|
+
else {
|
|
843
|
+
for (const expected of built) {
|
|
844
|
+
const visit = (item) => {
|
|
845
|
+
map.set(item, item);
|
|
846
|
+
for (const child of item.childNodes)
|
|
847
|
+
visit(child);
|
|
848
|
+
};
|
|
849
|
+
visit(expected);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const formBindings = prepareForms(context.forms, map, mode, container);
|
|
853
|
+
if (options.signal?.aborted || !container.isConnected)
|
|
854
|
+
fail("ZR_CANCELLED", "commit", "connected root", "aborted or disconnected", rootPath(container));
|
|
855
|
+
reconcileForms(formBindings, mode);
|
|
856
|
+
if (mode === "mount")
|
|
857
|
+
container.replaceChildren(fragment);
|
|
858
|
+
committed = true;
|
|
859
|
+
for (const operation of context.operations)
|
|
860
|
+
operation(map, cleanups);
|
|
861
|
+
const formOwnerByNode = new Map();
|
|
862
|
+
for (const [plan, owner] of context.formOwners)
|
|
863
|
+
formOwnerByNode.set(map.get(plan.element), owner);
|
|
864
|
+
const rootForms = formBindings.filter((binding) => !formOwnerByNode.has(binding.node));
|
|
865
|
+
activateForms(rootForms, scope, cleanups, options, container);
|
|
866
|
+
const ownedForms = new Map();
|
|
867
|
+
for (const binding of formBindings) {
|
|
868
|
+
const owner = formOwnerByNode.get(binding.node);
|
|
869
|
+
if (owner)
|
|
870
|
+
ownedForms.set(owner, [...(ownedForms.get(owner) ?? []), binding]);
|
|
871
|
+
}
|
|
872
|
+
for (const [owner, bindings] of ownedForms)
|
|
873
|
+
activateForms(bindings, owner.scope, owner.cleanups, options, container);
|
|
874
|
+
const owned = mode === "hydrate" ? [...container.childNodes] : built;
|
|
875
|
+
const handle = createRoot(container, options, scope, cleanups, owned);
|
|
876
|
+
try {
|
|
877
|
+
scope.activate();
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
handle.dispose();
|
|
881
|
+
report(options, diagnostic(options, "ZR_ACTIVATION_ERROR", "activation", rootPath(container), "activation", String(error)));
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
return handle;
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
for (const cleanup of [...cleanups].reverse())
|
|
888
|
+
try {
|
|
889
|
+
cleanup();
|
|
890
|
+
}
|
|
891
|
+
catch {
|
|
892
|
+
/* preserve original error */
|
|
893
|
+
}
|
|
894
|
+
scope?.dispose();
|
|
895
|
+
const failure = error instanceof Failure
|
|
896
|
+
? error
|
|
897
|
+
: error instanceof FormError
|
|
898
|
+
? new Failure(error.code, "preflight", "valid form model", error.detail, error.path)
|
|
899
|
+
: new Failure(error instanceof TypeError && /ZR_(DUPLICATE_KEY|KEY_TYPE):/.test(error.message)
|
|
900
|
+
? error.message.match(/ZR_(DUPLICATE_KEY|KEY_TYPE):/)[0].slice(0, -1)
|
|
901
|
+
: "ZR_HYDRATION_MISMATCH", committed ? "commit" : "setup", "successful root", String(error), rootPath(container));
|
|
902
|
+
report(options, diagnostic(options, failure.code, failure.phase, failure.path, failure.expected, failure.actual));
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
export function hydrate(node, container, options) {
|
|
907
|
+
return execute(node, container, options, "hydrate");
|
|
908
|
+
}
|
|
909
|
+
export function mount(node, container, options) {
|
|
910
|
+
return execute(node, container, options, "mount");
|
|
911
|
+
}
|
|
912
|
+
//# sourceMappingURL=hydrate.js.map
|