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