@vmz/core 0.2.5 → 0.2.7

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 (77) hide show
  1. package/dist/browser/client-nav.d.ts +5 -55
  2. package/dist/browser/client-nav.js +5 -769
  3. package/dist/browser/diagnostics/precision-trace.d.ts +47 -0
  4. package/dist/browser/diagnostics/precision-trace.js +86 -0
  5. package/dist/browser/dom/attributes.d.ts +5 -0
  6. package/dist/browser/dom/attributes.js +102 -0
  7. package/dist/browser/dom/component.d.ts +11 -0
  8. package/dist/browser/dom/component.js +255 -0
  9. package/dist/browser/dom/direct-api.d.ts +66 -0
  10. package/dist/browser/dom/direct-api.js +446 -0
  11. package/dist/browser/dom/lifecycle.d.ts +9 -0
  12. package/dist/browser/dom/lifecycle.js +161 -0
  13. package/dist/browser/dom/registry.d.ts +5 -0
  14. package/dist/browser/dom/registry.js +16 -0
  15. package/dist/browser/dom-core.d.ts +51 -249
  16. package/dist/browser/dom-core.js +51 -2521
  17. package/dist/browser/navigation/client-navigation.d.ts +44 -0
  18. package/dist/browser/navigation/client-navigation.js +277 -0
  19. package/dist/browser/navigation/extract-html.d.ts +1 -0
  20. package/dist/browser/navigation/extract-html.js +5 -0
  21. package/dist/browser/navigation/layout-retain.d.ts +3 -0
  22. package/dist/browser/navigation/layout-retain.js +34 -0
  23. package/dist/browser/navigation/locale-transition.d.ts +24 -0
  24. package/dist/browser/navigation/locale-transition.js +323 -0
  25. package/dist/browser/navigation/scroll-focus.d.ts +14 -0
  26. package/dist/browser/navigation/scroll-focus.js +87 -0
  27. package/dist/browser/reactivity/patch-scheduler.d.ts +12 -0
  28. package/dist/browser/reactivity/patch-scheduler.js +720 -0
  29. package/dist/browser/reactivity/write-barrier.d.ts +16 -0
  30. package/dist/browser/reactivity/write-barrier.js +546 -0
  31. package/dist/browser/resume/adopt.d.ts +1 -0
  32. package/dist/browser/resume/adopt.js +168 -0
  33. package/dist/browser/resume/hydrate.d.ts +4 -0
  34. package/dist/browser/resume/hydrate.js +107 -0
  35. package/dist/browser/resume/resume.d.ts +3 -0
  36. package/dist/browser/resume/resume.js +102 -0
  37. package/dist/faces/dom.browser.d.ts +2 -1
  38. package/dist/faces/dom.browser.js +2 -1
  39. package/dist/host/{deployment-registry.d.ts → deployment/deployment-registry.d.ts} +1 -1
  40. package/dist/host/{deployment-registry.js → deployment/deployment-registry.js} +1 -1
  41. package/dist/host/list-client-components.d.ts +2 -2
  42. package/dist/host/list-client-components.js +4 -4
  43. package/dist/host/{render-host.d.ts → render/render-host.d.ts} +2 -2
  44. package/dist/host/{render-host.js → render/render-host.js} +1 -1
  45. package/dist/host/{route-layout-chain.js → render/route-layout-chain.js} +1 -1
  46. package/dist/host/serve/bootstrap.d.ts +1 -0
  47. package/dist/host/serve/bootstrap.js +25 -0
  48. package/dist/host/serve/constants.d.ts +6 -0
  49. package/dist/host/serve/constants.js +6 -0
  50. package/dist/host/serve/hmr-dev.d.ts +34 -0
  51. package/dist/host/serve/hmr-dev.js +294 -0
  52. package/dist/host/serve/http.d.ts +1 -0
  53. package/dist/host/serve/http.js +124 -0
  54. package/dist/host/serve/locale-theme.d.ts +19 -0
  55. package/dist/host/serve/locale-theme.js +169 -0
  56. package/dist/host/serve/page-catalog.d.ts +14 -0
  57. package/dist/host/serve/page-catalog.js +105 -0
  58. package/dist/host/serve/page-render.d.ts +17 -0
  59. package/dist/host/serve/page-render.js +419 -0
  60. package/dist/host/serve/paths.d.ts +3 -0
  61. package/dist/host/serve/paths.js +74 -0
  62. package/dist/host/serve/route-catalog.d.ts +6 -0
  63. package/dist/host/serve/route-catalog.js +108 -0
  64. package/dist/host/serve/state.d.ts +30 -0
  65. package/dist/host/serve/state.js +36 -0
  66. package/dist/host/serve-host.d.ts +0 -17
  67. package/dist/host/serve-host.mjs +4 -1420
  68. package/dist/ssr/document.d.ts +2 -0
  69. package/dist/ssr/document.js +71 -0
  70. package/dist/ssr/dom-ssr.d.ts +10 -47
  71. package/dist/ssr/dom-ssr.js +10 -1284
  72. package/dist/ssr/render.d.ts +2 -0
  73. package/dist/ssr/render.js +78 -0
  74. package/dist/ssr/serialize.d.ts +3 -0
  75. package/dist/ssr/serialize.js +678 -0
  76. package/package.json +11 -11
  77. /package/dist/host/{route-layout-chain.d.ts → render/route-layout-chain.d.ts} +0 -0
@@ -1,1284 +1,10 @@
1
- /**
2
- * VMZ DOM SSR / hydrate / resume ??precise patches, no VDOM diff.
3
- * Imports client DOM primitives from ./dom-core.js for tree-shakeable browser entry.
4
- */
5
- import { applyDirectHostBox, directHostBoxStyleAttr } from '../browser/direct-host-box.js';
6
- import { applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, findOwnedDefaultSlot, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mergeClassParts, mergeStyleParts, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from '../browser/dom-core.js';
7
- import { markUnknownComponentHost, serializeUnknownComponentNode } from '../browser/unknown-component.js';
8
- let _ssrDocumentLastError = null;
9
- /**
10
- * Node SSR has no browser `document`. rowKernel prefers document-free HTML fill;
11
- * DOM hydrate is a fallback (class ternaries / missing textSlots) and needs linkedom.
12
- *
13
- * Must not statically import `node:module`: `dom.js` / `vmz-dom.js` re-export this file,
14
- * and browser hosts load that barrel.
15
- *
16
- * When this file is copied into an app `dist/`, `createRequire(import.meta.url)` cannot
17
- * see `@vmz/core`'s dependency tree ??resolve linkedom from cwd / `@vmz/core` as well.
18
- */
19
- function ensureSsrDocument() {
20
- if (typeof globalThis.document !== 'undefined' && typeof globalThis.document.createElement === 'function') {
21
- return true;
22
- }
23
- const proc = globalThis.process;
24
- if (!proc?.versions?.node) {
25
- _ssrDocumentLastError = new Error('vmz:dom SSR document: not running on Node');
26
- return false;
27
- }
28
- try {
29
- // Node 20.16+ / 22.3+: sync builtin load without a static `node:` import.
30
- const mod = typeof proc.getBuiltinModule === 'function' ? proc.getBuiltinModule('module') : null;
31
- if (!mod?.createRequire) {
32
- _ssrDocumentLastError = new Error('vmz:dom SSR document: createRequire unavailable');
33
- return false;
34
- }
35
- const pathMod = typeof proc.getBuiltinModule === 'function' ? proc.getBuiltinModule('path') : null;
36
- const createRequire = mod.createRequire;
37
- const bases = [];
38
- bases.push(import.meta.url);
39
- if (pathMod && typeof proc.cwd === 'function') {
40
- bases.push(pathMod.join(proc.cwd(), 'package.json'));
41
- }
42
- const errors = [];
43
- let parseHTML = null;
44
- for (const base of bases) {
45
- let req;
46
- try {
47
- req = createRequire(base);
48
- }
49
- catch (e) {
50
- errors.push(`${base}: createRequire failed: ${e && e.message ? e.message : e}`);
51
- continue;
52
- }
53
- try {
54
- parseHTML = req('linkedom').parseHTML;
55
- break;
56
- }
57
- catch (e) {
58
- errors.push(`${base} ??linkedom: ${e && e.message ? e.message : e}`);
59
- }
60
- // Walk into @vmz/core's dependency tree (linkedom is declared there).
61
- for (const coreId of ['@vmz/core', '@vmz/core/dom', '@vmz/core/server']) {
62
- try {
63
- const coreEntry = req.resolve(coreId);
64
- parseHTML = createRequire(coreEntry)('linkedom').parseHTML;
65
- break;
66
- }
67
- catch (e) {
68
- errors.push(`${base} ??${coreId}/linkedom: ${e && e.message ? e.message : e}`);
69
- }
70
- }
71
- if (parseHTML)
72
- break;
73
- }
74
- if (typeof parseHTML !== 'function') {
75
- const detail = errors.length ? `\n${errors.join('\n')}` : '';
76
- throw new Error(`linkedom unresolved for SSR document${detail}`);
77
- }
78
- const { window, document } = parseHTML('<!DOCTYPE html><html><body></body></html>');
79
- globalThis.window = window;
80
- globalThis.document = document;
81
- _ssrDocumentLastError = null;
82
- return typeof document.createElement === 'function';
83
- }
84
- catch (err) {
85
- _ssrDocumentLastError = err instanceof Error ? err : new Error(String(err));
86
- return false;
87
- }
88
- }
89
- export async function renderToString(Component, props = {}, opts = {}) {
90
- ensureSsrDocument();
91
- const signal = opts && opts.signal;
92
- if (signal && signal.aborted)
93
- return '';
94
- const inst = createInstance(Component, props);
95
- if (typeof inst.onMount === 'function') {
96
- await inst.onMount();
97
- }
98
- if (signal && signal.aborted)
99
- return '';
100
- // production Direct emit: SSR only via Direct serialize schedule ??never `render`.
101
- if (!(Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function')) {
102
- throw new Error(`vmz:dom renderToString() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
103
- }
104
- const root = await runDirectSerializeTreeWithMounts(Component, inst);
105
- if (opts && opts.slotHtml != null)
106
- injectDefaultSlotHtml(root, opts.slotHtml);
107
- return flattenSerializeNode(root);
108
- }
109
- /**
110
- * Stream SSR via the same Direct serialize schedule as `renderToString`.
111
- * Yields HTML chunks (open tag ??children ??close). Joining chunks equals `renderToString`.
112
- * Supports AbortSignal for cancel; consumers should respect backpressure (await between chunks).
113
- */
114
- export async function* renderToStream(Component, props = {}, opts = {}) {
115
- ensureSsrDocument();
116
- const signal = opts && opts.signal;
117
- const aborted = () => Boolean(signal && signal.aborted);
118
- if (aborted())
119
- return;
120
- const inst = createInstance(Component, props);
121
- try {
122
- if (typeof inst.onMount === 'function') {
123
- await inst.onMount();
124
- }
125
- if (aborted())
126
- return;
127
- if (!(Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function')) {
128
- throw new Error(`vmz:dom renderToStream() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
129
- }
130
- const root = await runDirectSerializeTreeWithMounts(Component, inst);
131
- if (opts && opts.slotHtml != null)
132
- injectDefaultSlotHtml(root, opts.slotHtml);
133
- if (aborted())
134
- return;
135
- for (const chunk of streamSerializeChunks(root)) {
136
- if (aborted())
137
- return;
138
- yield chunk;
139
- // Allow consumers / HTTP hosts to flush between chunks (backpressure point).
140
- await Promise.resolve();
141
- }
142
- }
143
- finally {
144
- // Abort and normal completion both dispose the SSR instance (lifetime).
145
- destroy(inst);
146
- }
147
- }
148
- /**
149
- * Fill the layout-owned default `<slot>` with pre-rendered HTML (layout SSR wrap).
150
- * Skips nested component hosts (`data-vmz`) ??their slots are for child projection
151
- * (e.g. Button label), not the page outlet. Without this, DFS hits LocaleToggle?Button
152
- * before Layout's `<main><slot>`, and the entire page HTML lands inside a button.
153
- */
154
- function injectDefaultSlotHtml(node, html) {
155
- if (!node || typeof node !== 'object')
156
- return false;
157
- if (node.__kind === 'el' && node.tag === 'slot' && !(node.attrs && node.attrs.name)) {
158
- node.__rawHtml = String(html ?? '');
159
- node.children = [];
160
- return true;
161
- }
162
- // Nested Direct component wrapper from serializeApi.component ??do not search inside.
163
- if (node.__kind === 'el' && node.attrs && node.attrs['data-vmz'] != null) {
164
- return false;
165
- }
166
- const kids = node.children;
167
- if (Array.isArray(kids)) {
168
- for (const c of kids) {
169
- if (injectDefaultSlotHtml(c, html))
170
- return true;
171
- }
172
- }
173
- return false;
174
- }
175
- /**
176
- * Live-DOM counterpart: first default `<slot>` owned by this tree, not by a nested
177
- * `[data-vmz]` component (Button/Link labels, etc.).
178
- * Re-exported from dom-core (single implementation).
179
- */
180
- export { findOwnedDefaultSlot };
181
- /**
182
- * Hydrate/mount a file-route page inside an optional layout chain (outer ??inner).
183
- * Mirrors SSR `slotHtml` wrapping: each layout's owned default slot becomes the
184
- * outlet for the next layout or the page. Retains layout instances on `container`
185
- * so SPA transitions can dispose only the page host.
186
- */
187
- export async function hydrateRoute(Page, container, props = {}, layoutCtors = [], opts = {}) {
188
- if (typeof document === 'undefined') {
189
- throw new Error('vmz:dom hydrateRoute() requires a document (browser)');
190
- }
191
- if (container.__vmzInst) {
192
- destroy(container.__vmzInst);
193
- container.__vmzInst = null;
194
- }
195
- container.__vmzPageHost = null;
196
- container.__vmzLayoutInsts = null;
197
- const layoutInsts = [];
198
- let host = container;
199
- const ctors = Array.isArray(layoutCtors) ? layoutCtors.filter(Boolean) : [];
200
- for (const Layout of ctors) {
201
- const inst = await mount(Layout, host, {});
202
- layoutInsts.push(inst);
203
- const slot = findOwnedDefaultSlot(inst.__vmzDomRoot);
204
- const outlet = document.createElement('div');
205
- outlet.setAttribute('data-vmz-outlet', '');
206
- if (slot && slot.parentNode)
207
- slot.replaceWith(outlet);
208
- else if (inst.__vmzDomRoot && typeof inst.__vmzDomRoot.appendChild === 'function') {
209
- inst.__vmzDomRoot.appendChild(outlet);
210
- }
211
- else {
212
- host.appendChild(outlet);
213
- }
214
- host = outlet;
215
- }
216
- const pageInst = await hydrate(Page, host, props, opts);
217
- container.__vmzPageHost = host;
218
- container.__vmzLayoutInsts = layoutInsts;
219
- // Outer layout (or page if no layouts) owns the #app instance for destroy().
220
- container.__vmzInst = layoutInsts[0] || pageInst;
221
- return pageInst;
222
- }
223
- /**
224
- * SPA same-layout transition: dispose only the page host, keep layout instances.
225
- * Callers must verify `data-vmz-layout` is unchanged before using this.
226
- */
227
- export async function hydrateRoutePage(Page, container, props = {}, opts = {}) {
228
- if (typeof document === 'undefined') {
229
- throw new Error('vmz:dom hydrateRoutePage() requires a document (browser)');
230
- }
231
- const pageHost = container.__vmzPageHost || container;
232
- if (pageHost.__vmzInst) {
233
- destroy(pageHost.__vmzInst);
234
- pageHost.__vmzInst = null;
235
- }
236
- const pageInst = await hydrate(Page, pageHost, props, opts);
237
- container.__vmzPageHost = pageHost;
238
- const layouts = container.__vmzLayoutInsts;
239
- if (!Array.isArray(layouts) || layouts.length === 0) {
240
- container.__vmzInst = pageInst;
241
- }
242
- return pageInst;
243
- }
244
- /**
245
- * SSR: run the same __vmzCreate schedule against a serialize host (no render).
246
- */
247
- function runDirectSerializeTree(Component, inst) {
248
- serializeApi._inst = inst;
249
- try {
250
- return Component.__vmzCreate.call(inst, serializeApi);
251
- }
252
- finally {
253
- serializeApi._inst = null;
254
- }
255
- }
256
- /**
257
- * SSR child onMount: sync `__vmzCreate` cannot await nested mounts.
258
- * Expand rounds ??reuse prior child instances, await newly discovered onMounts, re-emit.
259
- */
260
- async function runDirectSerializeTreeWithMounts(Component, inst) {
261
- let preMounted = [];
262
- let tree = null;
263
- for (let round = 0; round < 32; round++) {
264
- serializeApi._ssrPreMounted = preMounted;
265
- serializeApi._ssrPreIdx = 0;
266
- serializeApi._ssrCollected = [];
267
- tree = runDirectSerializeTree(Component, inst);
268
- if (serializeApi._ssrPreIdx !== preMounted.length) {
269
- throw new Error(`vmz:dom SSR child mount queue desync (used ${serializeApi._ssrPreIdx}, had ${preMounted.length})`);
270
- }
271
- const collected = serializeApi._ssrCollected;
272
- serializeApi._ssrPreMounted = null;
273
- serializeApi._ssrCollected = null;
274
- if (!collected.length)
275
- return tree;
276
- for (const child of collected) {
277
- if (typeof child.onMount === 'function') {
278
- await child.onMount();
279
- }
280
- }
281
- preMounted = preMounted.concat(collected);
282
- }
283
- throw new Error('vmz:dom SSR child onMount expansion exceeded 32 rounds');
284
- }
285
- function serializeOpenTag(node) {
286
- const tag = node.tag || 'div';
287
- let attrs = '';
288
- for (const [k, v] of Object.entries(node.attrs || {})) {
289
- if (v == null || v === false)
290
- continue;
291
- if (k === 'className')
292
- attrs += ` class="${escapeHtml(v)}"`;
293
- else
294
- attrs += ` ${k}="${escapeHtml(v)}"`;
295
- }
296
- return { tag, open: `<${tag}${attrs}>` };
297
- }
298
- function flattenSerializeNode(node) {
299
- if (node == null || node === false)
300
- return '';
301
- if (typeof node === 'string' || typeof node === 'number')
302
- return escapeHtml(node);
303
- if (node.__kind === 'text')
304
- return escapeHtml(node.value);
305
- if (node.__kind === 'comment')
306
- return '';
307
- if (node.__kind === 'frag') {
308
- if (node.__rawHtml != null)
309
- return String(node.__rawHtml);
310
- return (node.children || []).map(flattenSerializeNode).join('');
311
- }
312
- if (node.__kind === 'el') {
313
- const tag = node.tag || 'div';
314
- if (tag === 'slot') {
315
- if (node.__rawHtml != null)
316
- return String(node.__rawHtml);
317
- return (node.children || []).map(flattenSerializeNode).join('');
318
- }
319
- // rowKernel SSR: full outerHTML payload (do not re-wrap).
320
- if (node.__rawOuter && node.__rawHtml != null)
321
- return String(node.__rawHtml);
322
- const { open } = serializeOpenTag(node);
323
- if (node.__rawHtml != null) {
324
- return `${open}${String(node.__rawHtml)}</${tag}>`;
325
- }
326
- const inner = (node.children || []).map(flattenSerializeNode).join('');
327
- return `${open}${inner}</${tag}>`;
328
- }
329
- return '';
330
- }
331
- /**
332
- * Progressive HTML chunks from a serialize tree (same nodes as flattenSerializeNode).
333
- */
334
- function* streamSerializeChunks(node) {
335
- if (node == null || node === false)
336
- return;
337
- if (typeof node === 'string' || typeof node === 'number') {
338
- yield escapeHtml(node);
339
- return;
340
- }
341
- if (node.__kind === 'text') {
342
- yield escapeHtml(node.value);
343
- return;
344
- }
345
- if (node.__kind === 'comment')
346
- return;
347
- if (node.__kind === 'frag') {
348
- if (node.__rawHtml != null) {
349
- yield String(node.__rawHtml);
350
- return;
351
- }
352
- for (const c of node.children || [])
353
- yield* streamSerializeChunks(c);
354
- return;
355
- }
356
- if (node.__kind === 'el') {
357
- const tag = node.tag || 'div';
358
- if (tag === 'slot') {
359
- if (node.__rawHtml != null) {
360
- yield String(node.__rawHtml);
361
- return;
362
- }
363
- for (const c of node.children || [])
364
- yield* streamSerializeChunks(c);
365
- return;
366
- }
367
- if (node.__rawOuter && node.__rawHtml != null) {
368
- yield String(node.__rawHtml);
369
- return;
370
- }
371
- const { open } = serializeOpenTag(node);
372
- yield open;
373
- if (node.__rawHtml != null) {
374
- yield String(node.__rawHtml);
375
- }
376
- else {
377
- for (const c of node.children || [])
378
- yield* streamSerializeChunks(c);
379
- }
380
- yield `</${tag}>`;
381
- }
382
- }
383
- /**
384
- * Document-free rowKernel SSR fill (transitional / pre-0.1.7 emit only).
385
- * Prefer `serializeItem` (IR schedule). Do not treat this as the long-term contract.
386
- */
387
- function fillRowKernelHtml(rk, item, key) {
388
- const slots = rk.textSlots;
389
- if (!slots || typeof slots !== 'object')
390
- return null;
391
- const fields = Object.entries(slots)
392
- .filter(([, i]) => typeof i === 'number' && Number.isFinite(i))
393
- .sort((a, b) => Number(a[1]) - Number(b[1]))
394
- .map(([f]) => f);
395
- if (!fields.length)
396
- return null;
397
- let slotI = 0;
398
- // Generator emits one space per text interp as a dedicated text node (`> <`).
399
- const filled = String(rk.html).replace(/>([^<]*)</g, (m, text) => {
400
- if (text === ' ' && slotI < fields.length) {
401
- const f = fields[slotI++];
402
- const v = item == null ? '' : item[f];
403
- return `>${escapeHtml(v == null ? '' : String(v))}<`;
404
- }
405
- return m;
406
- });
407
- if (slotI !== fields.length)
408
- return null;
409
- if (key == null)
410
- return filled;
411
- // Inject data-vmz-key on the root opening tag (same attr hydrate would set).
412
- return filled.replace(/^<([A-Za-z][\w:-]*)/, `<$1 data-vmz-key="${escapeHtml(String(key))}"`);
413
- }
414
- /**
415
- * SSR row when `createItem` was omitted (rowKernel client emit).
416
- * Prefer document-free fill from `html` + `textSlots`; fall back to linkedom + hydrate
417
- * when host class ternaries need live DOM, or when placeholders cannot be string-filled.
418
- */
419
- function serializeRowFromKernel(inst, rk, box, key) {
420
- const item = box.item;
421
- const hostFields = Array.isArray(rk.hostFields) ? rk.hostFields : [];
422
- // Text-only kernels: no document. Host class ternaries still need hydrate/DOM.
423
- if (hostFields.length === 0) {
424
- const raw = fillRowKernelHtml(rk, item, key);
425
- if (raw != null) {
426
- return {
427
- __kind: 'el',
428
- tag: 'div',
429
- attrs: Object.create(null),
430
- children: [],
431
- __rawOuter: true,
432
- __rawHtml: raw,
433
- appendChild() { },
434
- };
435
- }
436
- }
437
- if (!ensureSsrDocument()) {
438
- // Degrade: still ship text fill if possible (class may be wrong until client hydrate).
439
- const raw = fillRowKernelHtml(rk, item, key);
440
- if (raw != null) {
441
- return {
442
- __kind: 'el',
443
- tag: 'div',
444
- attrs: Object.create(null),
445
- children: [],
446
- __rawOuter: true,
447
- __rawHtml: raw,
448
- appendChild() { },
449
- };
450
- }
451
- const cause = _ssrDocumentLastError;
452
- const detail = cause && cause.message ? cause.message : 'unavailable';
453
- throw new Error(`vmz:dom SSR rowKernel requires a document (createItem omitted): ${detail}`, cause ? { cause } : undefined);
454
- }
455
- const tpl = document.createElement('template');
456
- tpl.innerHTML = rk.html;
457
- const root = tpl.content.firstElementChild;
458
- if (!root || root.nodeType !== 1) {
459
- throw new Error('vmz:dom SSR rowKernel html produced no element');
460
- }
461
- if (typeof rk.hydrate === 'function') {
462
- rk.hydrate.call(inst, root, item);
463
- }
464
- if (key != null)
465
- root.setAttribute('data-vmz-key', String(key));
466
- return {
467
- __kind: 'el',
468
- tag: root.tagName.toLowerCase(),
469
- attrs: Object.create(null),
470
- children: [],
471
- __rawOuter: true,
472
- __rawHtml: root.outerHTML,
473
- appendChild() { },
474
- };
475
- }
476
- function virtualLink(parent, child) {
477
- if (child != null && typeof child === 'object')
478
- child.parentNode = parent;
479
- }
480
- function virtualAppend(parent, child) {
481
- if (parent == null || child == null)
482
- return;
483
- if (!Array.isArray(parent.children))
484
- parent.children = [];
485
- parent.children.push(child);
486
- virtualLink(parent, child);
487
- }
488
- function virtualInsertBefore(parent, node, ref) {
489
- if (parent == null || node == null)
490
- return;
491
- if (!Array.isArray(parent.children))
492
- parent.children = [];
493
- if (ref == null) {
494
- virtualAppend(parent, node);
495
- return;
496
- }
497
- const idx = parent.children.indexOf(ref);
498
- if (idx < 0)
499
- virtualAppend(parent, node);
500
- else {
501
- parent.children.splice(idx, 0, node);
502
- virtualLink(parent, node);
503
- }
504
- }
505
- function virtualRemove(node) {
506
- const p = node && node.parentNode;
507
- if (!p || !Array.isArray(p.children))
508
- return;
509
- const idx = p.children.indexOf(node);
510
- if (idx >= 0)
511
- p.children.splice(idx, 1);
512
- node.parentNode = null;
513
- }
514
- function makeVirtualEl(tag) {
515
- const node = {
516
- __kind: 'el',
517
- tag: tag || 'div',
518
- attrs: {},
519
- children: [],
520
- style: {},
521
- __rawHtml: null,
522
- parentNode: null,
523
- setAttribute(name, value) {
524
- applySerializeAttr(this, name, value);
525
- },
526
- appendChild(c) {
527
- virtualAppend(this, c);
528
- },
529
- insertBefore(node, ref) {
530
- virtualInsertBefore(this, node, ref);
531
- },
532
- };
533
- // Direct emit writes `el.innerHTML` for v-html. Mirror that DOM property
534
- // on the SSR virtual element so the serializer receives the same payload.
535
- Object.defineProperty(node, 'innerHTML', {
536
- configurable: true,
537
- get() {
538
- return this.__rawHtml ?? '';
539
- },
540
- set(value) {
541
- this.__rawHtml = value == null ? '' : String(value);
542
- this.children = [];
543
- },
544
- });
545
- return node;
546
- }
547
- const serializeApi = {
548
- _inst: null,
549
- _branchBinds: null,
550
- _itemPatches: null,
551
- _eachCtx: null,
552
- _ssrPreMounted: null,
553
- _ssrPreIdx: 0,
554
- _ssrCollected: null,
555
- _ssrChildInstance(Ctor, resolved) {
556
- const pre = serializeApi._ssrPreMounted;
557
- if (pre && serializeApi._ssrPreIdx < pre.length) {
558
- return pre[serializeApi._ssrPreIdx++];
559
- }
560
- const child = createInstance(Ctor, resolved);
561
- if (serializeApi._ssrCollected)
562
- serializeApi._ssrCollected.push(child);
563
- return child;
564
- },
565
- el(tag) {
566
- const node = makeVirtualEl(tag);
567
- if (String(tag || '').toLowerCase() === 'svg') {
568
- node.namespaceURI = 'http://www.w3.org/2000/svg';
569
- }
570
- return node;
571
- },
572
- elNS(ns, tag) {
573
- const node = makeVirtualEl(tag);
574
- node.namespaceURI = ns || 'http://www.w3.org/2000/svg';
575
- return node;
576
- },
577
- text(value) {
578
- const node = { __kind: 'text', value: value == null ? '' : String(value), parentNode: null };
579
- // Generated Direct patches use DOM `textContent`; virtual text uses `value`.
580
- Object.defineProperty(node, 'textContent', {
581
- configurable: true,
582
- enumerable: false,
583
- get() {
584
- return node.value;
585
- },
586
- set(v) {
587
- node.value = v == null ? '' : String(v);
588
- },
589
- });
590
- return node;
591
- },
592
- comment(value) {
593
- return { __kind: 'comment', value: value == null ? '' : String(value), parentNode: null };
594
- },
595
- frag() {
596
- return {
597
- __kind: 'frag',
598
- children: [],
599
- parentNode: null,
600
- appendChild(c) {
601
- virtualAppend(this, c);
602
- },
603
- insertBefore(node, ref) {
604
- virtualInsertBefore(this, node, ref);
605
- },
606
- };
607
- },
608
- insertBefore(parent, node, ref) {
609
- virtualInsertBefore(parent, node, ref);
610
- },
611
- removeNode(node) {
612
- virtualRemove(node);
613
- },
614
- trackPatch(inst, deps, patch, bindingId = null) {
615
- if (typeof patch !== 'function')
616
- return;
617
- if (serializeApi._branchBinds) {
618
- serializeApi._branchBinds.push({ deps: deps || [], fn: patch, bindingId });
619
- try {
620
- patch.call(inst);
621
- }
622
- catch (err) {
623
- console.error('vmz:ssr trackPatch branch', err);
624
- }
625
- return;
626
- }
627
- if (serializeApi._itemPatches) {
628
- patch.__vmzItemLocal = true;
629
- serializeApi._itemPatches.push(patch);
630
- if (serializeApi._eachCtx) {
631
- serializeApi._eachCtx.noteItemBind(bindingId, deps || [], patch);
632
- }
633
- try {
634
- patch.call(inst);
635
- }
636
- catch (err) {
637
- console.error('vmz:ssr trackPatch item', err);
638
- }
639
- return;
640
- }
641
- try {
642
- patch.call(inst);
643
- }
644
- catch (err) {
645
- console.error('vmz:ssr trackPatch', err);
646
- }
647
- },
648
- untrackPatch() {
649
- /* SSR one-shot schedule ??no reactive unregister */
650
- },
651
- disposeTree() {
652
- /* SSR one-shot schedule */
653
- },
654
- attr(el, name, value) {
655
- if (!el || el.__kind !== 'el')
656
- return;
657
- applySerializeAttr(el, name, value);
658
- },
659
- mergeClass(...parts) {
660
- return mergeClassParts(...parts);
661
- },
662
- mergeStyle(...parts) {
663
- return mergeStyleParts(...parts);
664
- },
665
- on() {
666
- /* events are no-ops during SSR */
667
- },
668
- onMethod() {
669
- /* named method events are also attached only during client resume */
670
- },
671
- adoptEnter() {
672
- return false;
673
- },
674
- adoptLeave() {
675
- /* resume scope only */
676
- },
677
- onComponentEvent() {
678
- /* component events attach only on the client */
679
- },
680
- specFieldText(inst, _bindingId, fieldName, textNode) {
681
- let raw;
682
- try {
683
- raw = inst[fieldName];
684
- }
685
- catch {
686
- raw = '';
687
- }
688
- textNode.value = String(raw ?? '');
689
- },
690
- specFieldAttr(inst, _bindingId, fieldName, el, name) {
691
- let raw;
692
- try {
693
- raw = inst[fieldName];
694
- }
695
- catch {
696
- raw = null;
697
- }
698
- if (name === 'class' || name === 'className') {
699
- applySerializeAttr(el, 'class', mergeClassParts(raw));
700
- return;
701
- }
702
- if (name === 'style') {
703
- applySerializeAttr(el, 'style', mergeStyleParts(raw));
704
- return;
705
- }
706
- applySerializeAttr(el, name, raw);
707
- },
708
- bindComponentProp() {
709
- /* SSR: props already resolved into the child instance at create */
710
- },
711
- projectDefaultSlot(hostEl, node) {
712
- if (!hostEl || node == null)
713
- return;
714
- // serializeApi.component returns a serialize el tree (or island shell).
715
- const root = hostEl.__kind === 'el' ? hostEl : null;
716
- const findOwnedSlot = (n) => {
717
- if (!n || typeof n !== 'object')
718
- return null;
719
- if (n.__kind === 'frag') {
720
- for (const c of n.children || []) {
721
- const hit = findOwnedSlot(c);
722
- if (hit)
723
- return hit;
724
- }
725
- return null;
726
- }
727
- if (n.__kind !== 'el')
728
- return null;
729
- if (n.tag === 'slot' && !(n.attrs && n.attrs.name))
730
- return n;
731
- for (const c of n.children || []) {
732
- if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
733
- continue;
734
- const hit = findOwnedSlot(c);
735
- if (hit)
736
- return hit;
737
- }
738
- return null;
739
- };
740
- const findOwnedSlotTarget = (n) => {
741
- const slot = findOwnedSlot(n);
742
- if (slot)
743
- return slot;
744
- if (!n || n.__kind !== 'el')
745
- return null;
746
- const tag = String(n.tag || '').toLowerCase();
747
- if ((tag === 'button' || tag === 'a') && n.attrs && n.attrs['data-vmz-ui'] === 'button')
748
- return n;
749
- if (n.attrs && n.attrs['data-vmz'] != null)
750
- return null;
751
- for (const c of n.children || []) {
752
- if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
753
- continue;
754
- const hit = findOwnedSlotTarget(c);
755
- if (hit)
756
- return hit;
757
- }
758
- return null;
759
- };
760
- // Prefer searching the component body (first child of host wrapper).
761
- let target = null;
762
- if (root) {
763
- for (const c of root.children || []) {
764
- if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
765
- continue;
766
- target = findOwnedSlotTarget(c);
767
- if (target)
768
- break;
769
- }
770
- if (!target)
771
- target = findOwnedSlotTarget(root);
772
- }
773
- if (target) {
774
- if (target.tag === 'slot') {
775
- target.__rawHtml = null;
776
- if (!Array.isArray(target.children))
777
- target.children = [];
778
- // Append ??multiple projectDefaultSlot calls must accumulate (SSR).
779
- // Client path replaces the live <slot> then appends siblings; serialize must push.
780
- target.children.push(node);
781
- return;
782
- }
783
- if (!Array.isArray(target.children))
784
- target.children = [];
785
- target.children.push(node);
786
- return;
787
- }
788
- if (root)
789
- root.appendChild(node);
790
- },
791
- setHtml(el, value) {
792
- if (!el || el.__kind !== 'el')
793
- return;
794
- el.__rawHtml = value == null ? '' : String(value);
795
- el.children = [];
796
- },
797
- bindHtml(inst, bindingId, deps, get, el) {
798
- serializeApi.trackPatch(inst, deps || [], function bindHtmlPatch() {
799
- let raw;
800
- try {
801
- raw = get.call(inst);
802
- }
803
- catch {
804
- raw = '';
805
- }
806
- serializeApi.setHtml(el, raw);
807
- }, bindingId);
808
- },
809
- component(hostInst, nameOrCtor, props, client) {
810
- const name = typeof nameOrCtor === 'function' ? nameOrCtor.__vmzTag || nameOrCtor.name || 'Component' : nameOrCtor;
811
- const Ctor = typeof nameOrCtor === 'function' ? nameOrCtor : getRegisteredComponent(name);
812
- if (!Ctor) {
813
- return serializeUnknownComponentNode(name);
814
- }
815
- const resolved = {};
816
- for (const [k, v] of Object.entries(props || {})) {
817
- const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
818
- if (onKey)
819
- continue;
820
- else if (typeof v === 'function')
821
- resolved[k] = v.call(hostInst);
822
- else
823
- resolved[k] = v;
824
- }
825
- if (client) {
826
- // resume: Island SSR includes body + ResumeEntry slice (same Direct schedule).
827
- const child = serializeApi._ssrChildInstance(Ctor, resolved);
828
- let body = null;
829
- if (Ctor.__vmzDirect && typeof Ctor.__vmzCreate === 'function') {
830
- const prev = serializeApi._inst;
831
- serializeApi._inst = child;
832
- try {
833
- body = Ctor.__vmzCreate.call(child, serializeApi);
834
- }
835
- finally {
836
- serializeApi._inst = prev;
837
- }
838
- }
839
- const state = snapshotInstanceState(child) || {};
840
- const plan = Ctor.__vmzPlan || null;
841
- const resume = {
842
- schema: 'vmz.resume.v0',
843
- component: name,
844
- strategy: String(client),
845
- props: stripFns(resolved),
846
- state,
847
- planSchema: plan?.schema || null,
848
- planRootIds: plan?.root_ids || [],
849
- };
850
- const attrs = {
851
- 'data-vmz': name,
852
- 'data-vmz-island': name,
853
- 'data-vmz-client': String(client),
854
- 'data-vmz-props': JSON.stringify(stripFns(resolved)),
855
- 'data-vmz-resume': JSON.stringify(resume),
856
- };
857
- const boxStyle = directHostBoxStyleAttr(name, Ctor);
858
- if (boxStyle)
859
- attrs.style = boxStyle;
860
- if (isEventEntryStrategy(String(client))) {
861
- attrs['data-vmz-entry'] = 'event';
862
- }
863
- return {
864
- __kind: 'el',
865
- tag: 'div',
866
- attrs,
867
- children: body ? [body] : [],
868
- appendChild(c) {
869
- if (c != null)
870
- this.children.push(c);
871
- },
872
- };
873
- }
874
- const child = serializeApi._ssrChildInstance(Ctor, resolved);
875
- if (Ctor.__vmzDirect && typeof Ctor.__vmzCreate === 'function') {
876
- const prev = serializeApi._inst;
877
- serializeApi._inst = child;
878
- try {
879
- const node = Ctor.__vmzCreate.call(child, serializeApi);
880
- const attrs = { 'data-vmz': name };
881
- const boxStyle = directHostBoxStyleAttr(name, Ctor);
882
- if (boxStyle)
883
- attrs.style = boxStyle;
884
- return {
885
- __kind: 'el',
886
- tag: 'div',
887
- attrs,
888
- children: node ? [node] : [],
889
- appendChild(c) {
890
- if (c != null)
891
- this.children.push(c);
892
- },
893
- };
894
- }
895
- finally {
896
- serializeApi._inst = prev;
897
- }
898
- }
899
- throw new Error(`vmz:dom serialize component <${name}> requires __vmzCreate (rebuild child with Direct)`);
900
- },
901
- };
902
- /**
903
- * Serialize-tree attr write (SSR).
904
- */
905
- function applySerializeAttr(el, name, value) {
906
- if (!el || el.__kind !== 'el')
907
- return;
908
- const key = name === 'className' ? 'class' : name;
909
- if (key === 'class') {
910
- const s = mergeClassParts(value);
911
- if (s)
912
- el.attrs[key] = s;
913
- else
914
- delete el.attrs[key];
915
- return;
916
- }
917
- if (key === 'style') {
918
- const s = mergeStyleParts(value);
919
- if (s)
920
- el.attrs[key] = s;
921
- else
922
- delete el.attrs[key];
923
- return;
924
- }
925
- if (BOOLEAN_HTML_ATTRS.has(String(key).toLowerCase())) {
926
- if (value === false || value == null || value === '')
927
- delete el.attrs[key];
928
- else
929
- el.attrs[key] = value === true ? '' : String(value);
930
- return;
931
- }
932
- if (value == null || value === false)
933
- delete el.attrs[key];
934
- else
935
- el.attrs[key] = value === true ? '' : String(value);
936
- }
937
- /**
938
- * resume: attach to existing Island DOM without re-running construct structure or onMount.
939
- * Consumes ResumeEntry product (`data-vmz-resume`) derived from the same Execution Plan.
940
- */
941
- export async function resume(Component, container, slice = null) {
942
- if (typeof document === 'undefined') {
943
- throw new Error('vmz:dom resume() requires a document (browser)');
944
- }
945
- let parsed = slice;
946
- if (!parsed) {
947
- const raw = container.getAttribute('data-vmz-resume');
948
- if (raw) {
949
- try {
950
- parsed = JSON.parse(raw);
951
- }
952
- catch {
953
- parsed = null;
954
- }
955
- }
956
- }
957
- if (!parsed) {
958
- let props = {};
959
- try {
960
- props = JSON.parse(container.getAttribute('data-vmz-props') || '{}');
961
- }
962
- catch {
963
- props = {};
964
- }
965
- parsed = { props, state: {} };
966
- }
967
- if (container.__vmzInst) {
968
- destroy(container.__vmzInst);
969
- container.__vmzInst = null;
970
- }
971
- const hostName = container.getAttribute('data-vmz') || container.getAttribute('data-vmz-island') || Component.name || '';
972
- applyDirectHostBox(container, hostName, Component);
973
- const props = parsed.props || {};
974
- const inst = createInstance(Component, props);
975
- if (parsed.state)
976
- applyPreservedState(inst, parsed.state);
977
- // Intentionally never call onMount ??SSR already completed that work.
978
- if (Component.__vmzDirect && typeof Component.__vmzCreate === 'function') {
979
- if (!hasMeaningfulChild(container)) {
980
- const node = runDirectCreate(Component, inst);
981
- if (node) {
982
- inst.__vmzDomRoot = node;
983
- container.appendChild(node);
984
- }
985
- }
986
- else {
987
- // Island leaf adopt: preserve Element identity (resume nodeIdentity).
988
- const node = runDirectResume(Component, inst, container);
989
- if (node)
990
- inst.__vmzDomRoot = node;
991
- }
992
- }
993
- else {
994
- throw new Error(`vmz:dom resume() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
995
- }
996
- container.__vmzInst = inst;
997
- container.__vmzResumed = true;
998
- return inst;
999
- }
1000
- /**
1001
- * Resume all `[data-vmz-island]` hosts (prefer ResumeEntry / EventEntry over mount).
1002
- * Event strategy islands wait for the DOM event before attach (lazy EventEntry).
1003
- */
1004
- export function resumeIslands(root = globalThis.document) {
1005
- if (!root || typeof root.querySelectorAll !== 'function') {
1006
- throw new Error('vmz:dom resumeIslands() requires a DOM root');
1007
- }
1008
- const nodes = [...root.querySelectorAll('[data-vmz-island]')];
1009
- for (const el of nodes) {
1010
- const name = el.getAttribute('data-vmz-island');
1011
- const strategy = el.getAttribute('data-vmz-client') || 'load';
1012
- scheduleClientOn(el, strategy, async () => {
1013
- const Ctor = await resolveComponent(name);
1014
- if (!Ctor) {
1015
- markUnknownComponentHost(el, name, 'resume');
1016
- return;
1017
- }
1018
- await resume(Ctor, el);
1019
- });
1020
- }
1021
- }
1022
- /**
1023
- * EventEntry attach: only wire `client:event` / `client:event:*` islands.
1024
- * Idle/load/visible ResumeEntries are left alone (static shell can defer framework work).
1025
- */
1026
- export function attachEventEntries(root = globalThis.document) {
1027
- if (!root || typeof root.querySelectorAll !== 'function') {
1028
- throw new Error('vmz:dom attachEventEntries() requires a DOM root');
1029
- }
1030
- const nodes = [...root.querySelectorAll('[data-vmz-island]')];
1031
- for (const el of nodes) {
1032
- const strategy = el.getAttribute('data-vmz-client') || '';
1033
- if (!isEventEntryStrategy(strategy))
1034
- continue;
1035
- const name = el.getAttribute('data-vmz-island');
1036
- el.setAttribute('data-vmz-entry', 'event');
1037
- scheduleClientOn(el, strategy, async () => {
1038
- const Ctor = await resolveComponent(name);
1039
- if (!Ctor) {
1040
- markUnknownComponentHost(el, name, 'event-entry');
1041
- return;
1042
- }
1043
- await resume(Ctor, el);
1044
- });
1045
- }
1046
- }
1047
- /**
1048
- * Adopt parked SSR/Island nodes while `__vmzCreate` rebuilds the live tree.
1049
- * Children of `rootEl` are moved into `pool` first so the create schedule never
1050
- * leaves orphan SSR siblings beside if/each markers (duplicate h2 / stale text).
1051
- *
1052
- * Scope rules (sibling-host invariant):
1053
- * - Candidates are **direct children** of the current scope pool only (no deep
1054
- * walk into other parked siblings).
1055
- * - Adopting a node parks its children into a **private** `__vmzResumePool`.
1056
- * - `enter(node)` / `leave()` push that private pool for depth-first child create.
1057
- * - `beginBranchScope()` moves one top-level SSR node into an isolated pool so
1058
- * ifBlock branch create cannot steal parent siblings (Drawer / Button hosts).
1059
- *
1060
- */
1061
- function createResumeAdopt(pool, rootEl) {
1062
- const used = new WeakSet();
1063
- const scopeStack = [pool];
1064
- const walkTexts = (root, visit) => {
1065
- if (!root)
1066
- return;
1067
- if (root.nodeType === 3) {
1068
- visit(root);
1069
- return;
1070
- }
1071
- if (root.nodeType !== 1)
1072
- return;
1073
- for (let c = root.firstChild; c; c = c.nextSibling)
1074
- walkTexts(c, visit);
1075
- };
1076
- const currentScope = () => scopeStack[scopeStack.length - 1] || pool;
1077
- const markElement = (n) => {
1078
- used.add(n);
1079
- if (typeof n.getAttribute === 'function') {
1080
- const k = n.getAttribute('data-vmz-key');
1081
- if (k != null && n.__vmzKey == null)
1082
- n.__vmzKey = k;
1083
- }
1084
- // Private child pool ??never dump into the parent/global pool (that let
1085
- // if branches reclaim Drawer/sibling hosts parked as uncles).
1086
- const childPool = document.createDocumentFragment();
1087
- while (n.firstChild)
1088
- childPool.appendChild(n.firstChild);
1089
- n.__vmzResumePool = childPool;
1090
- return n;
1091
- };
1092
- function* elementCandidates() {
1093
- const scope = currentScope();
1094
- if (scope === pool && rootEl && !used.has(rootEl))
1095
- yield rootEl;
1096
- // Snapshot ??adopt may move nodes out of scope while iterating.
1097
- const kids = [...scope.childNodes];
1098
- for (const n of kids) {
1099
- if (n.nodeType === 1 && !used.has(n)) {
1100
- yield n;
1101
- }
1102
- }
1103
- }
1104
- function* textCandidates() {
1105
- const scope = currentScope();
1106
- const kids = [...scope.childNodes];
1107
- for (const n of kids) {
1108
- const found = [];
1109
- walkTexts(n, (t) => {
1110
- if (!used.has(t))
1111
- found.push(t);
1112
- });
1113
- for (const t of found)
1114
- yield t;
1115
- }
1116
- }
1117
- const adoptElement = (tag) => {
1118
- const want = String(tag || 'div').toLowerCase();
1119
- for (const el of elementCandidates()) {
1120
- if (String(el.tagName).toLowerCase() === want)
1121
- return markElement(el);
1122
- }
1123
- noteDomCreate();
1124
- return document.createElement(tag || 'div');
1125
- };
1126
- const adoptText = (value) => {
1127
- for (const n of textCandidates()) {
1128
- used.add(n);
1129
- if (value != null && value !== '')
1130
- n.textContent = String(value);
1131
- return n;
1132
- }
1133
- noteDomCreate();
1134
- return document.createTextNode(value == null ? '' : String(value));
1135
- };
1136
- const componentHost = (name) => {
1137
- const want = String(name || '');
1138
- for (const el of elementCandidates()) {
1139
- if (String(el.tagName).toLowerCase() !== 'div')
1140
- continue;
1141
- if (el.getAttribute('data-vmz') !== want)
1142
- continue;
1143
- return markElement(el);
1144
- }
1145
- return null;
1146
- };
1147
- const enter = (node) => {
1148
- if (!node || !node.__vmzResumePool)
1149
- return false;
1150
- scopeStack.push(node.__vmzResumePool);
1151
- return true;
1152
- };
1153
- const leave = () => {
1154
- if (scopeStack.length <= 1)
1155
- return;
1156
- scopeStack.pop();
1157
- };
1158
- const scopeDepth = () => scopeStack.length;
1159
- const rewindScope = (depth) => {
1160
- const d = Math.max(1, Number(depth) || 1);
1161
- while (scopeStack.length > d)
1162
- scopeStack.pop();
1163
- };
1164
- /**
1165
- * Isolate the next top-level SSR sibling as the only candidates for an
1166
- * if/else branch create (commercial Card: Empty branch must not adopt Drawer).
1167
- */
1168
- const beginBranchScope = () => {
1169
- const parent = currentScope();
1170
- const branchPool = document.createDocumentFragment();
1171
- for (const n of [...parent.childNodes]) {
1172
- if (n.nodeType === 1 && !used.has(n)) {
1173
- branchPool.appendChild(n);
1174
- break;
1175
- }
1176
- }
1177
- scopeStack.push(branchPool);
1178
- const depth = scopeStack.length;
1179
- return () => {
1180
- while (branchPool.firstChild)
1181
- branchPool.removeChild(branchPool.firstChild);
1182
- rewindScope(depth - 1);
1183
- };
1184
- };
1185
- return {
1186
- el: adoptElement,
1187
- text: adoptText,
1188
- componentHost,
1189
- enter,
1190
- leave,
1191
- scopeDepth,
1192
- rewindScope,
1193
- beginBranchScope,
1194
- };
1195
- }
1196
- /**
1197
- * Adopt existing Island DOM while running the same `__vmzCreate` schedule (resume).
1198
- */
1199
- function runDirectResume(Component, inst, container) {
1200
- const rootEl = [...container.childNodes].find((n) => n.nodeType === 1 || (n.nodeType === 3 && String(n.textContent).trim() !== ''));
1201
- if (!rootEl || rootEl.nodeType !== 1) {
1202
- return runDirectCreate(Component, inst);
1203
- }
1204
- // Park SSR descendants so create can append if/each structure without orphan siblings.
1205
- const pool = document.createDocumentFragment();
1206
- while (rootEl.firstChild)
1207
- pool.appendChild(rootEl.firstChild);
1208
- const prevInst = directApi._inst;
1209
- const prevBranch = directApi._branchBinds;
1210
- const prevItems = directApi._itemPatches;
1211
- const prevEach = directApi._eachCtx;
1212
- const prevAdopt = directApi._resumeAdopt;
1213
- directApi._inst = inst;
1214
- directApi._branchBinds = null;
1215
- directApi._itemPatches = null;
1216
- directApi._eachCtx = null;
1217
- directApi._resumeAdopt = createResumeAdopt(pool, rootEl);
1218
- try {
1219
- return Component.__vmzCreate.call(inst, directApi);
1220
- }
1221
- finally {
1222
- directApi._resumeAdopt = prevAdopt;
1223
- directApi._inst = prevInst;
1224
- directApi._branchBinds = prevBranch;
1225
- directApi._itemPatches = prevItems;
1226
- directApi._eachCtx = prevEach;
1227
- while (pool.firstChild)
1228
- pool.removeChild(pool.firstChild);
1229
- }
1230
- }
1231
- export async function hydrate(Component, container, props = {}, opts = {}) {
1232
- if (typeof document === 'undefined') {
1233
- throw new Error('vmz:dom hydrate() requires a document (browser)');
1234
- }
1235
- let preserved = null;
1236
- if (opts.preserveState && typeof opts.preserveState === 'object') {
1237
- preserved = opts.preserveState;
1238
- }
1239
- else if (opts.preserveState === true && container.__vmzInst) {
1240
- preserved = snapshotInstanceState(container.__vmzInst);
1241
- }
1242
- if (container.__vmzInst) {
1243
- destroy(container.__vmzInst);
1244
- container.__vmzInst = null;
1245
- }
1246
- const inst = createInstance(Component, props);
1247
- if (preserved) {
1248
- applyPreservedState(inst, preserved);
1249
- }
1250
- // production Direct emit: hydrate uses the same Direct schedule as resume (no render).
1251
- if (!(Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function')) {
1252
- throw new Error(`vmz:dom hydrate() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
1253
- }
1254
- // Wire DOM + events BEFORE awaiting onMount. SSR shell is already visible; if we
1255
- // wait on RPC/bootstrap first, buttons look real but have no listeners (dead UI).
1256
- // onMount may still patch state / redirect afterwards (same end state as SSR order).
1257
- if (!hasMeaningfulChild(container)) {
1258
- const node = runDirectCreate(Component, inst);
1259
- if (node) {
1260
- inst.__vmzDomRoot = node;
1261
- container.appendChild(node);
1262
- }
1263
- }
1264
- else {
1265
- // Deep adopt: park SSR children, rebuild schedule, reclaim nodes (no orphan siblings).
1266
- const node = runDirectResume(Component, inst, container);
1267
- if (node)
1268
- inst.__vmzDomRoot = node;
1269
- }
1270
- await settlePendingChildMounts(inst);
1271
- container.__vmzInst = inst;
1272
- const runMount = opts.skipOnMount !== true && !preserved && typeof inst.onMount === 'function';
1273
- if (runMount) {
1274
- await inst.onMount();
1275
- }
1276
- return inst;
1277
- }
1278
- export function hydrateIslands(root = globalThis.document) {
1279
- // resume: hydrateIslands is an alias for resumeIslands (same Plan attach).
1280
- return resumeIslands(root);
1281
- }
1282
- function escapeHtml(s) {
1283
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1284
- }
1
+ /** SSR and resume barrel. */
2
+ export { renderToString } from './render.js';
3
+ export { renderToStream } from './render.js';
4
+ export { hydrateRoute } from '../browser/resume/hydrate.js';
5
+ export { hydrateRoutePage } from '../browser/resume/hydrate.js';
6
+ export { hydrate } from '../browser/resume/hydrate.js';
7
+ export { hydrateIslands } from '../browser/resume/hydrate.js';
8
+ export { resume } from '../browser/resume/resume.js';
9
+ export { resumeIslands } from '../browser/resume/resume.js';
10
+ export { attachEventEntries } from '../browser/resume/resume.js';