@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.
Files changed (88) hide show
  1. package/README.md +13 -16
  2. package/dist/config.d.ts +77 -25
  3. package/dist/config.js +27 -6
  4. package/dist/config.js.map +1 -1
  5. package/dist/content.d.ts +8 -22
  6. package/dist/content.js +6 -28
  7. package/dist/content.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/island-boundary.d.ts +4 -0
  12. package/dist/island-boundary.js +42 -0
  13. package/dist/island-boundary.js.map +1 -0
  14. package/dist/island.d.ts +2 -118
  15. package/dist/island.js +4 -284
  16. package/dist/island.js.map +1 -1
  17. package/dist/jsx-types.d.ts +2 -38
  18. package/dist/jsx-types.js +3 -10
  19. package/dist/jsx-types.js.map +1 -1
  20. package/dist/plugins.d.ts +40 -0
  21. package/dist/plugins.js.map +1 -1
  22. package/dist/runtime.d.ts +26 -77
  23. package/dist/runtime.js +307 -340
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/zudo-react/client.d.ts +3 -0
  26. package/dist/zudo-react/client.js +3 -0
  27. package/dist/zudo-react/client.js.map +1 -0
  28. package/dist/zudo-react/description.d.ts +16 -0
  29. package/dist/zudo-react/description.js +64 -0
  30. package/dist/zudo-react/description.js.map +1 -0
  31. package/dist/zudo-react/dom-bindings.d.ts +13 -0
  32. package/dist/zudo-react/dom-bindings.js +58 -0
  33. package/dist/zudo-react/dom-bindings.js.map +1 -0
  34. package/dist/zudo-react/escape.d.ts +2 -0
  35. package/dist/zudo-react/escape.js +7 -0
  36. package/dist/zudo-react/escape.js.map +1 -0
  37. package/dist/zudo-react/forms.d.ts +29 -0
  38. package/dist/zudo-react/forms.js +371 -0
  39. package/dist/zudo-react/forms.js.map +1 -0
  40. package/dist/zudo-react/hydrate.d.ts +4 -0
  41. package/dist/zudo-react/hydrate.js +912 -0
  42. package/dist/zudo-react/hydrate.js.map +1 -0
  43. package/dist/zudo-react/index.d.ts +47 -0
  44. package/dist/zudo-react/index.js +13 -0
  45. package/dist/zudo-react/index.js.map +1 -0
  46. package/dist/zudo-react/island-root-type.d.ts +1 -0
  47. package/dist/zudo-react/island-root-type.js +2 -0
  48. package/dist/zudo-react/island-root-type.js.map +1 -0
  49. package/dist/zudo-react/jsx-dev-runtime.d.ts +8 -0
  50. package/dist/zudo-react/jsx-dev-runtime.js +6 -0
  51. package/dist/zudo-react/jsx-dev-runtime.js.map +1 -0
  52. package/dist/zudo-react/jsx-runtime.d.ts +5 -0
  53. package/dist/zudo-react/jsx-runtime.js +7 -0
  54. package/dist/zudo-react/jsx-runtime.js.map +1 -0
  55. package/dist/zudo-react/jsx-types.d.ts +216 -0
  56. package/dist/zudo-react/jsx-types.js +2 -0
  57. package/dist/zudo-react/jsx-types.js.map +1 -0
  58. package/dist/zudo-react/props-transport.d.ts +2 -0
  59. package/dist/zudo-react/props-transport.js +95 -0
  60. package/dist/zudo-react/props-transport.js.map +1 -0
  61. package/dist/zudo-react/reactive-types.d.ts +8 -0
  62. package/dist/zudo-react/reactive-types.js +2 -0
  63. package/dist/zudo-react/reactive-types.js.map +1 -0
  64. package/dist/zudo-react/reactive.d.ts +20 -0
  65. package/dist/zudo-react/reactive.js +176 -0
  66. package/dist/zudo-react/reactive.js.map +1 -0
  67. package/dist/zudo-react/render-html.d.ts +3 -0
  68. package/dist/zudo-react/render-html.js +535 -0
  69. package/dist/zudo-react/render-html.js.map +1 -0
  70. package/dist/zudo-react/root.d.ts +20 -0
  71. package/dist/zudo-react/root.js +74 -0
  72. package/dist/zudo-react/root.js.map +1 -0
  73. package/dist/zudo-react/scheduler.d.ts +12 -0
  74. package/dist/zudo-react/scheduler.js +113 -0
  75. package/dist/zudo-react/scheduler.js.map +1 -0
  76. package/dist/zudo-react/scope.d.ts +42 -0
  77. package/dist/zudo-react/scope.js +220 -0
  78. package/dist/zudo-react/scope.js.map +1 -0
  79. package/dist/zudo-react/server.d.ts +15 -0
  80. package/dist/zudo-react/server.js +11 -0
  81. package/dist/zudo-react/server.js.map +1 -0
  82. package/dist/zudo-react/structure.d.ts +16 -0
  83. package/dist/zudo-react/structure.js +49 -0
  84. package/dist/zudo-react/structure.js.map +1 -0
  85. package/dist/zudo-react/vocabulary.d.ts +12 -0
  86. package/dist/zudo-react/vocabulary.js +96 -0
  87. package/dist/zudo-react/vocabulary.js.map +1 -0
  88. package/package.json +28 -24
@@ -0,0 +1,535 @@
1
+ import { Fragment, isDescription } from "./description.js";
2
+ import { escapeAttribute, escapeText } from "./escape.js";
3
+ import { readSnapshot } from "./reactive.js";
4
+ import { createScope, withScope } from "./scope.js";
5
+ import { serializeProps } from "./props-transport.js";
6
+ import { islandRootType } from "./island-root-type.js";
7
+ import { isShowType, isForType, showProps, forProps, keyed, keyPayload, view, } from "./structure.js";
8
+ import { booleanAttrs, commonAttrs, dialectSuggestion, formProps, htmlAttrs, htmlTags, isDialectProp, reactiveModelSuggestion, svgAttrs, svgTags, voidTags, } from "./vocabulary.js";
9
+ const words = (value) => new Set(value.split(" "));
10
+ const reserved = words("key ref children rawHtml");
11
+ const sensitive = words("noscript xmp iframe noembed noframes plaintext");
12
+ const tableChildren = {
13
+ table: words("caption colgroup thead tbody tfoot"),
14
+ thead: words("tr"),
15
+ tbody: words("tr"),
16
+ tfoot: words("tr"),
17
+ tr: words("td th"),
18
+ colgroup: words("col"),
19
+ };
20
+ function fail(code, context, detail) {
21
+ throw new TypeError(`${code}: ${detail} at ${context.path} in ${context.identity?.component ?? "static render"}`);
22
+ }
23
+ function reactive(value) {
24
+ return (typeof value === "object" &&
25
+ value !== null &&
26
+ "$$zudoReactive" in value &&
27
+ value.$$zudoReactive === "zudo-react.reactive.v1");
28
+ }
29
+ function read(value) {
30
+ return reactive(value) ? readSnapshot(value) : value;
31
+ }
32
+ function region(context, kind, content) {
33
+ if (!context.identity)
34
+ return content();
35
+ const id = context.next++;
36
+ return `<!--zr:1:${id}:${kind}-->${content()}<!--/zr:1:${id}-->`;
37
+ }
38
+ function at(context, part, fn) {
39
+ const old = context.path;
40
+ context.path += part;
41
+ try {
42
+ return fn();
43
+ }
44
+ finally {
45
+ context.path = old;
46
+ }
47
+ }
48
+ function children(value, context, namespace, parent) {
49
+ if (value == null || typeof value === "boolean")
50
+ return "";
51
+ if (Array.isArray(value))
52
+ return value
53
+ .map((item, index) => at(context, `[${index}]`, () => render(item, context, namespace, parent)))
54
+ .join("");
55
+ return render(value, context, namespace, parent);
56
+ }
57
+ function renderText(value, context) {
58
+ if (value == null || typeof value === "boolean")
59
+ return "";
60
+ if (typeof value === "string" || (typeof value === "number" && Number.isFinite(value)))
61
+ return escapeText(String(value));
62
+ fail("ZR_CHILD", context, `invalid scalar ${typeof value}`);
63
+ }
64
+ function style(value, context) {
65
+ if (typeof value === "string")
66
+ return value;
67
+ if (value === null ||
68
+ typeof value !== "object" ||
69
+ Array.isArray(value) ||
70
+ Object.getPrototypeOf(value) !== Object.prototype)
71
+ fail("ZR_STYLE", context, "style must be a string or flat object");
72
+ let result = "";
73
+ for (const [name, entry] of Object.entries(value)) {
74
+ if (!/^(--[a-zA-Z0-9_-]+|[a-z][a-z0-9-]*)$/.test(name) || /[A-Z]/.test(name))
75
+ fail("ZR_STYLE", context, `invalid property ${name}`);
76
+ if (entry == null)
77
+ continue;
78
+ if (typeof entry !== "string" && !(typeof entry === "number" && Number.isFinite(entry)))
79
+ fail("ZR_STYLE", context, `invalid value for ${name}`);
80
+ result += `${name}:${entry};`;
81
+ }
82
+ return result;
83
+ }
84
+ function attributes(tag, props, context, namespace, custom) {
85
+ let output = "";
86
+ for (const [name, original] of Object.entries(props)) {
87
+ if (reserved.has(name))
88
+ continue;
89
+ if (formProps.has(name))
90
+ continue;
91
+ if (isDialectProp(name, namespace, custom)) {
92
+ const suggestion = dialectSuggestion(name, namespace, custom);
93
+ fail("ZR_PROP_DIALECT", context, `${tag}.${name}${suggestion ? ` (use \`${suggestion}\` instead of \`${name}\`)` : ""}`);
94
+ }
95
+ if (name.startsWith("on:")) {
96
+ if (!/^on:[A-Za-z][A-Za-z0-9_-]*(?::capture)?$/.test(name))
97
+ fail("ZR_LISTENER", context, `${tag}.${name}`);
98
+ if (typeof original !== "function")
99
+ fail("ZR_LISTENER", context, `${tag}.${name}`);
100
+ const inline = `on${name.slice(3).replace(/:capture$/, "")}`;
101
+ if (inline in props)
102
+ fail("ZR_LISTENER_CONFLICT", context, `${tag}.${inline}`);
103
+ continue;
104
+ }
105
+ if (!/^[A-Za-z_:][A-Za-z0-9_:.-]*$/.test(name))
106
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name}`);
107
+ if (!custom &&
108
+ !commonAttrs.has(name) &&
109
+ !(namespace === "svg" ? svgAttrs.has(name) : htmlAttrs.has(name)) &&
110
+ !/^data-[\w.-]+$/.test(name) &&
111
+ !/^aria-[\w.-]+$/.test(name) &&
112
+ !/^on[a-z]+$/.test(name))
113
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name}`);
114
+ const value = read(original);
115
+ if ((name === "value" || name === "checked") && reactive(original)) {
116
+ const suggestion = reactiveModelSuggestion(tag, name, String(read(props.type) ?? "text"), custom);
117
+ fail("ZR_MODEL_UNSUPPORTED", context, `${tag}.${name} requires ${suggestion ?? "a static value"}`);
118
+ }
119
+ if (value == null)
120
+ continue;
121
+ if (/^on[a-z]/.test(name) && typeof value === "function")
122
+ fail("ZR_PROP_DIALECT", context, `${tag}.${name} must use on:${name.slice(2)}`);
123
+ if (custom && typeof value !== "string")
124
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name} requires a string`);
125
+ if (name === "style")
126
+ output += ` style="${escapeAttribute(style(value, context))}"`;
127
+ else if (booleanAttrs.has(name)) {
128
+ if (typeof value !== "boolean")
129
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name} requires a boolean`);
130
+ if (value)
131
+ output += ` ${name}`;
132
+ }
133
+ else {
134
+ if (typeof value !== "string" &&
135
+ typeof value !== "boolean" &&
136
+ !(typeof value === "number" && Number.isFinite(value)))
137
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name} requires a scalar`);
138
+ if (typeof value === "boolean" && !name.startsWith("aria-") && !name.startsWith("data-"))
139
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name} requires a string`);
140
+ if (/^on[a-z]/.test(name) && typeof value !== "string")
141
+ fail("ZR_ATTRIBUTE", context, `${tag}.${name} requires a string`);
142
+ output += ` ${name}="${escapeAttribute(String(value))}"`;
143
+ }
144
+ }
145
+ return output;
146
+ }
147
+ function restricted(value, context, namespace, parent) {
148
+ if (value == null || typeof value === "boolean")
149
+ return "";
150
+ if (Array.isArray(value))
151
+ return value
152
+ .map((item, index) => at(context, `[${index}]`, () => restricted(item, context, namespace, parent)))
153
+ .join("");
154
+ if (typeof value === "string" || typeof value === "number") {
155
+ if (parent in tableChildren && String(value).trim())
156
+ fail("ZR_PARSER_CONTEXT", context, `${parent} text`);
157
+ return renderText(value, context);
158
+ }
159
+ if (parent === "title" || parent === "option")
160
+ fail("ZR_PARSER_CONTEXT", context, `${parent} requires static text`);
161
+ if (!isDescription(value) || typeof value.type !== "string")
162
+ fail("ZR_PARSER_CONTEXT", context, `${parent} requires intrinsic children`);
163
+ return element(value, context, namespace, parent);
164
+ }
165
+ function element(description, context, namespace, parent) {
166
+ const tag = description.type;
167
+ const custom = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(tag);
168
+ const elementNamespace = tag === "svg" || namespace === "svg" ? "svg" : "html";
169
+ const childNamespace = tag === "foreignObject" ? "html" : elementNamespace;
170
+ if (elementNamespace === "svg" ? !svgTags.has(tag) : !custom && !htmlTags.has(tag))
171
+ fail("ZR_TAG", context, tag);
172
+ if (context.identity && (tag === "template" || sensitive.has(tag)))
173
+ fail("ZR_PARSER_CONTEXT", context, tag);
174
+ if (parent in tableChildren && !tableChildren[parent]?.has(tag))
175
+ fail("ZR_PARSER_CONTEXT", context, `${parent} > ${tag}`);
176
+ if ((parent === "select" && tag !== "option" && tag !== "optgroup") ||
177
+ (parent === "optgroup" && tag !== "option"))
178
+ fail("ZR_PARSER_CONTEXT", context, `${parent} > ${tag}`);
179
+ const previousFormId = context.formId;
180
+ if (tag === "form")
181
+ context.formId =
182
+ typeof description.props.id === "string"
183
+ ? `id:${description.props.id}`
184
+ : `form:${context.nextFormId++}`;
185
+ const source = description.props;
186
+ const props = { ...source };
187
+ const hasValue = source.modelValue !== undefined;
188
+ const hasChecked = source.modelChecked !== undefined;
189
+ if ((hasValue && hasChecked) ||
190
+ (source.defaultValue !== undefined && source.value !== undefined) ||
191
+ (source.defaultChecked !== undefined && source.checked !== undefined) ||
192
+ (hasValue &&
193
+ (source.defaultValue !== undefined ||
194
+ (source.type !== "radio" && source.value !== undefined))) ||
195
+ (hasChecked && (source.defaultChecked !== undefined || source.checked !== undefined)))
196
+ fail("ZR_MODEL_CONFLICT", context, tag);
197
+ if (hasValue || hasChecked) {
198
+ for (const name of ["type", "multiple", "name", "form"])
199
+ if (reactive(source[name]))
200
+ fail("ZR_MODEL_UNSUPPORTED", context, `reactive ${name} shape`);
201
+ const model = hasValue ? source.modelValue : source.modelChecked;
202
+ let prototype = model;
203
+ let writable = false;
204
+ while (prototype && typeof prototype === "object") {
205
+ const property = Object.getOwnPropertyDescriptor(prototype, "value");
206
+ if (property) {
207
+ writable = typeof property.set === "function";
208
+ break;
209
+ }
210
+ prototype = Object.getPrototypeOf(prototype);
211
+ }
212
+ if (!reactive(model) ||
213
+ !writable ||
214
+ !("$$zudoWritable" in model) ||
215
+ model.$$zudoWritable !== "zudo-react.writable.v1")
216
+ fail("ZR_MODEL_READONLY", context, tag);
217
+ }
218
+ if ((hasValue || hasChecked) && source.contenteditable !== undefined)
219
+ fail("ZR_MODEL_UNSUPPORTED", context, `${tag}[contenteditable]`);
220
+ const inputType = String(source.type ?? "text");
221
+ if (hasValue &&
222
+ !(tag === "textarea" ||
223
+ tag === "select" ||
224
+ (tag === "input" &&
225
+ ["text", "search", "email", "url", "tel", "password", "radio"].includes(inputType))))
226
+ fail("ZR_MODEL_UNSUPPORTED", context, `${tag}[type=${inputType}]`);
227
+ if (hasChecked && !(tag === "input" && inputType === "checkbox"))
228
+ fail("ZR_MODEL_UNSUPPORTED", context, tag);
229
+ if (source.defaultValue !== undefined &&
230
+ !(tag === "textarea" || tag === "select" || tag === "input"))
231
+ fail("ZR_MODEL_UNSUPPORTED", context, tag);
232
+ if (source.defaultValue !== undefined &&
233
+ tag === "input" &&
234
+ !["text", "search", "email", "url", "tel", "password"].includes(inputType))
235
+ fail("ZR_MODEL_UNSUPPORTED", context, `${tag}[type=${inputType}]`);
236
+ if (source.defaultChecked !== undefined &&
237
+ !(tag === "input" && ["checkbox", "radio"].includes(inputType)))
238
+ fail("ZR_MODEL_UNSUPPORTED", context, tag);
239
+ if (tag === "input") {
240
+ if (source.defaultValue !== undefined || (hasValue && inputType !== "radio"))
241
+ props.value = read(source.modelValue ?? source.defaultValue);
242
+ if (source.defaultChecked !== undefined || hasChecked)
243
+ props.checked = read(source.modelChecked ?? source.defaultChecked);
244
+ if (hasValue && inputType === "radio") {
245
+ if (typeof source.name !== "string" || !source.name || typeof source.value !== "string")
246
+ fail("ZR_MODEL_UNSUPPORTED", context, "radio requires static name and value");
247
+ const selected = read(source.modelValue);
248
+ if (selected !== null && typeof selected !== "string")
249
+ fail("ZR_MODEL_VALUE", context, "radio requires string or null");
250
+ const form = typeof source.form === "string" ? `id:${source.form}` : context.formId;
251
+ const key = `${source.name}\u0000${form}`;
252
+ const owner = context.radioNames.get(key);
253
+ if (owner && owner !== source.modelValue)
254
+ fail("ZR_MODEL_CONFLICT", context, "same-name radios use different models");
255
+ context.radioNames.set(key, source.modelValue);
256
+ const group = context.radioGroups.get(source.modelValue);
257
+ if (group &&
258
+ (group.name !== source.name || group.form !== form || group.options.has(source.value)))
259
+ fail("ZR_MODEL_CONFLICT", context, "radio membership or duplicate value");
260
+ if (group)
261
+ group.options.add(source.value);
262
+ else
263
+ context.radioGroups.set(source.modelValue, {
264
+ name: source.name,
265
+ form,
266
+ value: selected,
267
+ options: new Set([source.value]),
268
+ });
269
+ props.checked = selected === source.value;
270
+ }
271
+ }
272
+ if (tag === "option" && context.selectValue !== undefined) {
273
+ const value = source.value;
274
+ if (typeof value !== "string")
275
+ fail("ZR_MODEL_UNSUPPORTED", context, "select option requires static value");
276
+ if (context.selectSeen.has(value))
277
+ fail("ZR_MODEL_CONFLICT", context, "duplicate option value");
278
+ context.selectSeen.add(value);
279
+ if (source.selected !== undefined)
280
+ fail("ZR_MODEL_CONFLICT", context, "select-level value with option selected");
281
+ props.selected = value === context.selectValue;
282
+ if (props.selected)
283
+ context.selectMatches = (context.selectMatches ?? 0) + 1;
284
+ }
285
+ if (context.boundary && ("data-zfb-island" in props || "data-zfb-island-skip-ssr" in props))
286
+ fail("ZR_NESTED_ISLAND", context, tag);
287
+ if (tag === "select" &&
288
+ read(props.multiple) === true &&
289
+ (context.identity || hasValue || source.defaultValue !== undefined))
290
+ fail("ZR_MODEL_UNSUPPORTED", context, "select[multiple]");
291
+ const attrs = attributes(tag, props, context, elementNamespace, custom);
292
+ const raw = props.rawHtml;
293
+ const hasChildren = Object.hasOwn(props, "children");
294
+ if (raw !== undefined && hasChildren)
295
+ fail("ZR_RAW_HTML", context, `${tag} has children and rawHtml`);
296
+ if (voidTags.has(tag) && (hasChildren || raw !== undefined))
297
+ fail("ZR_VOID_CHILDREN", context, tag);
298
+ if (raw !== undefined &&
299
+ (elementNamespace === "svg" ||
300
+ tag === "textarea" ||
301
+ tag === "title" ||
302
+ tag === "select" ||
303
+ tag === "option"))
304
+ fail("ZR_RAW_HTML", context, tag);
305
+ if (tag === "textarea" && hasChildren && (hasValue || source.defaultValue !== undefined))
306
+ fail("ZR_MODEL_CONFLICT", context, tag);
307
+ let content = "";
308
+ if (raw !== undefined) {
309
+ if ((tag === "script" || tag === "style") && reactive(raw))
310
+ fail("ZR_RAW_HTML", context, `${tag} requires static rawHtml`);
311
+ const payload = read(raw);
312
+ if (typeof payload !== "string")
313
+ fail("ZR_RAW_HTML", context, `${tag} requires string rawHtml`);
314
+ if (/<!--\/?zr:1:|data-zfb-island(?:-skip-ssr)?\s*=/.test(payload))
315
+ fail("ZR_RAW_HTML", context, "reserved boundary in rawHtml");
316
+ if ((tag === "script" && /<\/script/i.test(payload)) ||
317
+ (tag === "style" && /<\/style/i.test(payload)))
318
+ fail("ZR_RAW_HTML", context, `closing ${tag} in rawHtml`);
319
+ content = tag === "script" || tag === "style" ? payload : region(context, "h", () => payload);
320
+ }
321
+ else if (tag === "textarea" && (hasValue || source.defaultValue !== undefined)) {
322
+ const value = read(source.modelValue ?? source.defaultValue);
323
+ if (typeof value !== "string")
324
+ fail("ZR_MODEL_VALUE", context, "textarea requires string");
325
+ content = `${value.startsWith("\n") ? "\n" : ""}${escapeText(value)}`;
326
+ }
327
+ else if (tag === "script" || tag === "style") {
328
+ if (hasChildren)
329
+ fail("ZR_RAW_HTML", context, `${tag} requires rawHtml`);
330
+ }
331
+ else if (tag === "title" ||
332
+ tag === "option" ||
333
+ tag === "optgroup" ||
334
+ tag === "select" ||
335
+ tag in tableChildren) {
336
+ if (tag === "select" && (hasValue || source.defaultValue !== undefined)) {
337
+ const value = read(source.modelValue ?? source.defaultValue);
338
+ if (typeof value !== "string")
339
+ fail("ZR_MODEL_VALUE", context, "select requires string");
340
+ const oldValue = context.selectValue;
341
+ const oldSeen = context.selectSeen;
342
+ const oldMatches = context.selectMatches;
343
+ context.selectValue = value;
344
+ context.selectSeen = new Set();
345
+ context.selectMatches = 0;
346
+ try {
347
+ content = restricted(props.children, context, childNamespace, tag);
348
+ if (context.selectMatches !== 1)
349
+ fail("ZR_MODEL_VALUE", context, `select has no option ${value}`);
350
+ }
351
+ finally {
352
+ context.selectValue = oldValue;
353
+ context.selectSeen = oldSeen;
354
+ context.selectMatches = oldMatches;
355
+ }
356
+ }
357
+ else
358
+ content = restricted(props.children, context, childNamespace, tag);
359
+ }
360
+ else
361
+ content = children(props.children, context, childNamespace, tag);
362
+ if (tag === "pre" && raw === undefined && (content.startsWith("\n") || content.startsWith("\r")))
363
+ content = `\n${content}`;
364
+ context.formId = previousFormId;
365
+ return `<${tag}${attrs}>${voidTags.has(tag) ? "" : `${content}</${tag}>`}`;
366
+ }
367
+ function render(value, context, namespace, parent) {
368
+ if (value == null || typeof value === "boolean")
369
+ return "";
370
+ if (Array.isArray(value))
371
+ return region(context, "f", () => children(value, context, namespace, parent));
372
+ if (reactive(value))
373
+ return region(context, "t", () => renderText(read(value), context));
374
+ if (typeof value === "string" || typeof value === "number")
375
+ return renderText(value, context);
376
+ if (typeof value === "object" && value !== null && "then" in value)
377
+ fail("ZR_ASYNC_COMPONENT", context, "promise child");
378
+ if (!isDescription(value))
379
+ fail("ZR_CHILD", context, typeof value);
380
+ if (value.type === islandRootType)
381
+ return island(value, context, namespace, parent);
382
+ if (value.type === Fragment)
383
+ return region(context, "f", () => children(value.props.children, context, namespace, parent));
384
+ if (isShowType(value.type)) {
385
+ if (parent in tableChildren ||
386
+ ["select", "optgroup", "option", "title", "textarea", "script", "style"].includes(parent))
387
+ fail("ZR_PARSER_CONTEXT", context, parent);
388
+ const props = showProps(value);
389
+ if (!reactive(props.when) ||
390
+ typeof props.children !== "function" ||
391
+ (props.fallback !== undefined && typeof props.fallback !== "function"))
392
+ fail("ZR_CHILD", context, "Show requires signal and factories");
393
+ const selected = read(props.when);
394
+ if (typeof selected !== "boolean")
395
+ fail("ZR_CHILD", context, "Show.when requires boolean");
396
+ return region(context, `s:${selected ? 1 : 0}`, () => {
397
+ const factory = selected ? props.children : props.fallback;
398
+ if (!factory)
399
+ return "";
400
+ const scope = context.scope.child("Show");
401
+ const previous = context.scope;
402
+ context.scope = scope;
403
+ try {
404
+ return withScope(scope, () => render(factory(), context, namespace, parent));
405
+ }
406
+ finally {
407
+ context.scope = previous;
408
+ }
409
+ });
410
+ }
411
+ if (isForType(value.type)) {
412
+ if (parent in tableChildren ||
413
+ ["select", "optgroup", "option", "title", "textarea", "script", "style"].includes(parent))
414
+ fail("ZR_PARSER_CONTEXT", context, parent);
415
+ const props = forProps(value);
416
+ if (!reactive(props.each) ||
417
+ typeof props.by !== "function" ||
418
+ typeof props.children !== "function")
419
+ fail("ZR_CHILD", context, "For requires signal, key and factory");
420
+ const items = read(props.each);
421
+ if (!Array.isArray(items))
422
+ fail("ZR_CHILD", context, "For.each requires array");
423
+ const keys = keyed(items, props.by, context.path);
424
+ return region(context, "l", () => items
425
+ .map((item, index) => {
426
+ const scope = context.scope.child("For");
427
+ const previous = context.scope;
428
+ context.scope = scope;
429
+ try {
430
+ return region(context, `i:${keyPayload(keys[index])}`, () => withScope(scope, () => render(props.children(view(item).value, view(index).value), context, namespace, parent)));
431
+ }
432
+ finally {
433
+ context.scope = previous;
434
+ }
435
+ })
436
+ .join(""));
437
+ }
438
+ if (typeof value.type === "string")
439
+ return element(value, context, namespace, parent);
440
+ const component = value.type;
441
+ const scope = context.scope.child(component.name || "Anonymous");
442
+ const prior = context.scope;
443
+ context.scope = scope;
444
+ try {
445
+ return region(context, "c", () => {
446
+ const output = withScope(scope, () => component(value.props));
447
+ if (output && typeof output === "object" && "then" in output)
448
+ fail("ZR_ASYNC_COMPONENT", context, component.name);
449
+ return render(output, context, namespace, parent);
450
+ });
451
+ }
452
+ finally {
453
+ context.scope = prior;
454
+ }
455
+ }
456
+ function empty(value) {
457
+ return (value == null || typeof value === "boolean" || (Array.isArray(value) && value.every(empty)));
458
+ }
459
+ function island(description, context, namespace, parent) {
460
+ if (context.boundary)
461
+ fail("ZR_NESTED_ISLAND", context, "nested island");
462
+ if (namespace !== "html" || parent === "p" || parent in tableChildren || parent === "select")
463
+ fail("ZR_PARSER_CONTEXT", context, "island wrapper");
464
+ const child = description.props.child;
465
+ const options = description.props.options;
466
+ if (!isDescription(child) || typeof child.type !== "function")
467
+ fail("ZR_ISLAND_CHILD", context, "one function component required");
468
+ if (!options?.identity?.component || !options.identity.build)
469
+ fail("ZR_ISLAND_IDENTITY", context, "component/build required");
470
+ const component = child.type.displayName ?? child.type.name;
471
+ if (component !== options.identity.component)
472
+ fail("ZR_ISLAND_IDENTITY", context, `expected ${options.identity.component}, got ${component}`);
473
+ if (Object.hasOwn(child.props, "children") && !empty(child.props.children))
474
+ fail("ZR_ISLAND_CHILD", context, "nonempty children");
475
+ const props = { ...child.props };
476
+ delete props.children;
477
+ let payload;
478
+ try {
479
+ payload = serializeProps(props);
480
+ }
481
+ catch (error) {
482
+ throw new TypeError(`ZR_ISLAND_PROPS ${component}: ${error instanceof Error ? error.message : String(error)}`);
483
+ }
484
+ const when = options.when ?? "load";
485
+ if (!["load", "idle", "visible", "media"].includes(when) ||
486
+ (when === "media" && !options.media?.trim()) ||
487
+ (when !== "media" && options.media !== undefined))
488
+ fail("ZR_ISLAND_WHEN", context, when);
489
+ const marker = options.skipSsr ? "data-zfb-island-skip-ssr" : "data-zfb-island";
490
+ const attrs = ` ${marker}="${escapeAttribute(options.identity.component)}" data-when="${when}"${when === "media" ? ` data-media="${escapeAttribute(options.media)}"` : ""} data-zfb-transport="json/1" data-zfb-protocol="zudo-react/1" data-zfb-build="${escapeAttribute(options.identity.build)}" data-props="${escapeAttribute(payload)}"`;
491
+ const local = {
492
+ radioGroups: new Map(),
493
+ radioNames: new Map(),
494
+ formId: "",
495
+ nextFormId: 0,
496
+ identity: options.skipSsr ? undefined : options.identity,
497
+ boundary: true,
498
+ next: 0,
499
+ scope: context.scope,
500
+ path: context.path,
501
+ };
502
+ const content = options.skipSsr
503
+ ? children(options.fallback, local, namespace, "div")
504
+ : render({ ...child, props }, local, "html", "div");
505
+ validateRadioGroups(local);
506
+ return `<div${attrs}>${content}</div>`;
507
+ }
508
+ function validateRadioGroups(context) {
509
+ for (const group of context.radioGroups.values())
510
+ if (group.value !== null && !group.options.has(group.value))
511
+ fail("ZR_MODEL_VALUE", context, `radio has no option ${group.value}`);
512
+ }
513
+ export function renderHtml(node, options) {
514
+ const root = createScope({ component: options.island?.component ?? "<root>" });
515
+ const context = {
516
+ radioGroups: new Map(),
517
+ radioNames: new Map(),
518
+ formId: "",
519
+ nextFormId: 0,
520
+ identity: options.island,
521
+ boundary: options.island !== undefined,
522
+ next: 0,
523
+ scope: root,
524
+ path: "root",
525
+ };
526
+ try {
527
+ const html = render(node, context, "html", "");
528
+ validateRadioGroups(context);
529
+ return html;
530
+ }
531
+ finally {
532
+ root.dispose();
533
+ }
534
+ }
535
+ //# sourceMappingURL=render-html.js.map