@vmz/core 0.1.32 → 0.2.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/dist/{client-nav.d.ts → browser/client-nav.d.ts} +4 -13
- package/dist/{client-nav.js → browser/client-nav.js} +16 -74
- package/dist/browser/direct-api.types.d.ts +58 -0
- package/dist/browser/direct-api.types.js +4 -0
- package/dist/browser/direct-host-box.d.ts +11 -0
- package/dist/{direct-host-box.js → browser/direct-host-box.js} +2 -18
- package/dist/{dom-core.d.ts → browser/dom-core.d.ts} +64 -220
- package/dist/browser/dom-core.js +2453 -0
- package/dist/browser/dom-core.types.d.ts +138 -0
- package/dist/browser/dom-core.types.js +4 -0
- package/dist/{position-context.js → browser/position-context.js} +1 -1
- package/dist/browser/unknown-component.d.ts +14 -0
- package/dist/{unknown-component.js → browser/unknown-component.js} +4 -28
- package/dist/{dom.browser.d.ts → faces/dom.browser.d.ts} +2 -2
- package/dist/{dom.browser.js → faces/dom.browser.js} +2 -3
- package/dist/faces/dom.client.d.ts +4 -0
- package/dist/faces/dom.client.js +4 -0
- package/dist/faces/dom.d.ts +6 -0
- package/dist/faces/dom.js +6 -0
- package/dist/{http.d.ts → faces/http.d.ts} +1 -1
- package/dist/{http.js → faces/http.js} +1 -2
- package/dist/{server.d.ts → faces/server.d.ts} +8 -25
- package/dist/{server.js → faces/server.js} +25 -155
- package/dist/{vmz-dom.d.ts → faces/vmz-dom.d.ts} +1 -1
- package/dist/{vmz-dom.js → faces/vmz-dom.js} +1 -2
- package/dist/{vmz-runtime.d.ts → faces/vmz-runtime.d.ts} +1 -1
- package/dist/{vmz-runtime.js → faces/vmz-runtime.js} +1 -2
- package/dist/host/deployment-registry.d.ts +16 -0
- package/dist/{deployment-registry.js → host/deployment-registry.js} +1 -68
- package/dist/host/list-client-components.d.ts +14 -0
- package/dist/{list-client-components.js → host/list-client-components.js} +0 -37
- package/dist/{localize-body-links.js → host/localize-body-links.js} +0 -1
- package/dist/host/native-addon.d.ts +6 -0
- package/dist/{native-addon.js → host/native-addon.js} +0 -20
- package/dist/host/render-host.d.ts +20 -0
- package/dist/{render-host.js → host/render-host.js} +0 -16
- package/dist/{serve-host.d.ts → host/serve-host.d.ts} +3 -3
- package/dist/{serve-host.mjs → host/serve-host.mjs} +45 -168
- package/dist/shared/host.types.d.ts +67 -0
- package/dist/shared/host.types.js +4 -0
- package/dist/shared/server.types.d.ts +39 -0
- package/dist/shared/server.types.js +4 -0
- package/dist/{dom-ssr.d.ts → ssr/dom-ssr.d.ts} +8 -37
- package/dist/{dom-ssr.js → ssr/dom-ssr.js} +159 -217
- package/package.json +34 -34
- package/dist/deployment-registry.d.ts +0 -70
- package/dist/direct-host-box.d.ts +0 -26
- package/dist/dom-core.js +0 -4815
- package/dist/dom.client.d.ts +0 -4
- package/dist/dom.client.js +0 -5
- package/dist/dom.d.ts +0 -6
- package/dist/dom.js +0 -7
- package/dist/list-client-components.d.ts +0 -62
- package/dist/native-addon.d.ts +0 -12
- package/dist/render-host.d.ts +0 -32
- package/dist/unknown-component.d.ts +0 -40
- /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
- /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
- /package/dist/{index.js → faces/index.js} +0 -0
- /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
- /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
- /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
|
-
* VMZ DOM SSR / hydrate / resume
|
|
2
|
+
* VMZ DOM SSR / hydrate / resume ??precise patches, no VDOM diff.
|
|
4
3
|
* Imports client DOM primitives from ./dom-core.js for tree-shakeable browser entry.
|
|
5
4
|
*/
|
|
6
|
-
import { applyDirectHostBox, directHostBoxStyleAttr } from '
|
|
7
|
-
import { applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, findOwnedDefaultSlot, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from '
|
|
8
|
-
import { markUnknownComponentHost, serializeUnknownComponentNode } from '
|
|
9
|
-
/** @type {Error | null} last linkedom resolve failure (for clear SSR errors) */
|
|
5
|
+
import { applyDirectHostBox, directHostBoxStyleAttr } from '../browser/direct-host-box.js';
|
|
6
|
+
import { applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, findOwnedDefaultSlot, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from '../browser/dom-core.js';
|
|
7
|
+
import { markUnknownComponentHost, serializeUnknownComponentNode } from '../browser/unknown-component.js';
|
|
10
8
|
let _ssrDocumentLastError = null;
|
|
11
9
|
/**
|
|
12
10
|
* Node SSR has no browser `document`. rowKernel prefers document-free HTML fill;
|
|
@@ -16,7 +14,7 @@ let _ssrDocumentLastError = null;
|
|
|
16
14
|
* and browser hosts load that barrel.
|
|
17
15
|
*
|
|
18
16
|
* When this file is copied into an app `dist/`, `createRequire(import.meta.url)` cannot
|
|
19
|
-
* see `@vmz/core`'s dependency tree
|
|
17
|
+
* see `@vmz/core`'s dependency tree ??resolve linkedom from cwd / `@vmz/core` as well.
|
|
20
18
|
*/
|
|
21
19
|
function ensureSsrDocument() {
|
|
22
20
|
if (typeof globalThis.document !== 'undefined' && typeof globalThis.document.createElement === 'function') {
|
|
@@ -36,13 +34,11 @@ function ensureSsrDocument() {
|
|
|
36
34
|
}
|
|
37
35
|
const pathMod = typeof proc.getBuiltinModule === 'function' ? proc.getBuiltinModule('path') : null;
|
|
38
36
|
const createRequire = mod.createRequire;
|
|
39
|
-
/** @type {string[]} */
|
|
40
37
|
const bases = [];
|
|
41
38
|
bases.push(import.meta.url);
|
|
42
39
|
if (pathMod && typeof proc.cwd === 'function') {
|
|
43
40
|
bases.push(pathMod.join(proc.cwd(), 'package.json'));
|
|
44
41
|
}
|
|
45
|
-
/** @type {string[]} */
|
|
46
42
|
const errors = [];
|
|
47
43
|
let parseHTML = null;
|
|
48
44
|
for (const base of bases) {
|
|
@@ -59,7 +55,7 @@ function ensureSsrDocument() {
|
|
|
59
55
|
break;
|
|
60
56
|
}
|
|
61
57
|
catch (e) {
|
|
62
|
-
errors.push(`${base}
|
|
58
|
+
errors.push(`${base} ??linkedom: ${e && e.message ? e.message : e}`);
|
|
63
59
|
}
|
|
64
60
|
// Walk into @vmz/core's dependency tree (linkedom is declared there).
|
|
65
61
|
for (const coreId of ['@vmz/core', '@vmz/core/dom', '@vmz/core/server']) {
|
|
@@ -69,7 +65,7 @@ function ensureSsrDocument() {
|
|
|
69
65
|
break;
|
|
70
66
|
}
|
|
71
67
|
catch (e) {
|
|
72
|
-
errors.push(`${base}
|
|
68
|
+
errors.push(`${base} ??${coreId}/linkedom: ${e && e.message ? e.message : e}`);
|
|
73
69
|
}
|
|
74
70
|
}
|
|
75
71
|
if (parseHTML)
|
|
@@ -101,7 +97,7 @@ export async function renderToString(Component, props = {}, opts = {}) {
|
|
|
101
97
|
}
|
|
102
98
|
if (signal && signal.aborted)
|
|
103
99
|
return '';
|
|
104
|
-
// production Direct emit: SSR only via Direct serialize schedule
|
|
100
|
+
// production Direct emit: SSR only via Direct serialize schedule ??never `render`.
|
|
105
101
|
if (!(Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function')) {
|
|
106
102
|
throw new Error(`vmz:dom renderToString() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
|
|
107
103
|
}
|
|
@@ -112,12 +108,8 @@ export async function renderToString(Component, props = {}, opts = {}) {
|
|
|
112
108
|
}
|
|
113
109
|
/**
|
|
114
110
|
* Stream SSR via the same Direct serialize schedule as `renderToString`.
|
|
115
|
-
* Yields HTML chunks (open tag
|
|
111
|
+
* Yields HTML chunks (open tag ??children ??close). Joining chunks equals `renderToString`.
|
|
116
112
|
* Supports AbortSignal for cancel; consumers should respect backpressure (await between chunks).
|
|
117
|
-
* @param {new (props?: object) => any} Component
|
|
118
|
-
* @param {object} [props]
|
|
119
|
-
* @param {{ signal?: AbortSignal, slotHtml?: string }} [opts]
|
|
120
|
-
* @returns {AsyncGenerator<string, void, void>}
|
|
121
113
|
*/
|
|
122
114
|
export async function* renderToStream(Component, props = {}, opts = {}) {
|
|
123
115
|
ensureSsrDocument();
|
|
@@ -155,11 +147,9 @@ export async function* renderToStream(Component, props = {}, opts = {}) {
|
|
|
155
147
|
}
|
|
156
148
|
/**
|
|
157
149
|
* Fill the layout-owned default `<slot>` with pre-rendered HTML (layout SSR wrap).
|
|
158
|
-
* Skips nested component hosts (`data-vmz`)
|
|
159
|
-
* (e.g. Button label), not the page outlet. Without this, DFS hits LocaleToggle
|
|
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
|
|
160
152
|
* before Layout's `<main><slot>`, and the entire page HTML lands inside a button.
|
|
161
|
-
* @param {any} node
|
|
162
|
-
* @param {string} html
|
|
163
153
|
*/
|
|
164
154
|
function injectDefaultSlotHtml(node, html) {
|
|
165
155
|
if (!node || typeof node !== 'object')
|
|
@@ -169,7 +159,7 @@ function injectDefaultSlotHtml(node, html) {
|
|
|
169
159
|
node.children = [];
|
|
170
160
|
return true;
|
|
171
161
|
}
|
|
172
|
-
// Nested Direct component wrapper from serializeApi.component
|
|
162
|
+
// Nested Direct component wrapper from serializeApi.component ??do not search inside.
|
|
173
163
|
if (node.__kind === 'el' && node.attrs && node.attrs['data-vmz'] != null) {
|
|
174
164
|
return false;
|
|
175
165
|
}
|
|
@@ -186,20 +176,13 @@ function injectDefaultSlotHtml(node, html) {
|
|
|
186
176
|
* Live-DOM counterpart: first default `<slot>` owned by this tree, not by a nested
|
|
187
177
|
* `[data-vmz]` component (Button/Link labels, etc.).
|
|
188
178
|
* Re-exported from dom-core (single implementation).
|
|
189
|
-
* @param {Element | null | undefined} root
|
|
190
|
-
* @returns {Element | null}
|
|
191
179
|
*/
|
|
192
180
|
export { findOwnedDefaultSlot };
|
|
193
181
|
/**
|
|
194
|
-
* Hydrate/mount a file-route page inside an optional layout chain (outer
|
|
182
|
+
* Hydrate/mount a file-route page inside an optional layout chain (outer ??inner).
|
|
195
183
|
* Mirrors SSR `slotHtml` wrapping: each layout's owned default slot becomes the
|
|
196
184
|
* outlet for the next layout or the page. Retains layout instances on `container`
|
|
197
185
|
* so SPA transitions can dispose only the page host.
|
|
198
|
-
* @param {new (props?: object) => any} Page
|
|
199
|
-
* @param {Element} container
|
|
200
|
-
* @param {object} [props]
|
|
201
|
-
* @param {Array<new (props?: object) => any>} [layoutCtors] outer → inner
|
|
202
|
-
* @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
|
|
203
186
|
*/
|
|
204
187
|
export async function hydrateRoute(Page, container, props = {}, layoutCtors = [], opts = {}) {
|
|
205
188
|
if (typeof document === 'undefined') {
|
|
@@ -211,7 +194,6 @@ export async function hydrateRoute(Page, container, props = {}, layoutCtors = []
|
|
|
211
194
|
}
|
|
212
195
|
container.__vmzPageHost = null;
|
|
213
196
|
container.__vmzLayoutInsts = null;
|
|
214
|
-
/** @type {object[]} */
|
|
215
197
|
const layoutInsts = [];
|
|
216
198
|
let host = container;
|
|
217
199
|
const ctors = Array.isArray(layoutCtors) ? layoutCtors.filter(Boolean) : [];
|
|
@@ -241,10 +223,6 @@ export async function hydrateRoute(Page, container, props = {}, layoutCtors = []
|
|
|
241
223
|
/**
|
|
242
224
|
* SPA same-layout transition: dispose only the page host, keep layout instances.
|
|
243
225
|
* Callers must verify `data-vmz-layout` is unchanged before using this.
|
|
244
|
-
* @param {new (props?: object) => any} Page
|
|
245
|
-
* @param {Element} container `#app` that already has `__vmzPageHost` / `__vmzLayoutInsts`
|
|
246
|
-
* @param {object} [props]
|
|
247
|
-
* @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
|
|
248
226
|
*/
|
|
249
227
|
export async function hydrateRoutePage(Page, container, props = {}, opts = {}) {
|
|
250
228
|
if (typeof document === 'undefined') {
|
|
@@ -265,8 +243,6 @@ export async function hydrateRoutePage(Page, container, props = {}, opts = {}) {
|
|
|
265
243
|
}
|
|
266
244
|
/**
|
|
267
245
|
* SSR: run the same __vmzCreate schedule against a serialize host (no render).
|
|
268
|
-
* @param {new (props?: object) => any} Component
|
|
269
|
-
* @param {object} inst
|
|
270
246
|
*/
|
|
271
247
|
function runDirectSerializeTree(Component, inst) {
|
|
272
248
|
serializeApi._inst = inst;
|
|
@@ -279,14 +255,10 @@ function runDirectSerializeTree(Component, inst) {
|
|
|
279
255
|
}
|
|
280
256
|
/**
|
|
281
257
|
* SSR child onMount: sync `__vmzCreate` cannot await nested mounts.
|
|
282
|
-
* Expand rounds
|
|
283
|
-
* @param {new (props?: object) => any} Component
|
|
284
|
-
* @param {object} inst
|
|
258
|
+
* Expand rounds ??reuse prior child instances, await newly discovered onMounts, re-emit.
|
|
285
259
|
*/
|
|
286
260
|
async function runDirectSerializeTreeWithMounts(Component, inst) {
|
|
287
|
-
/** @type {object[]} */
|
|
288
261
|
let preMounted = [];
|
|
289
|
-
/** @type {any} */
|
|
290
262
|
let tree = null;
|
|
291
263
|
for (let round = 0; round < 32; round++) {
|
|
292
264
|
serializeApi._ssrPreMounted = preMounted;
|
|
@@ -330,6 +302,8 @@ function flattenSerializeNode(node) {
|
|
|
330
302
|
return escapeHtml(node);
|
|
331
303
|
if (node.__kind === 'text')
|
|
332
304
|
return escapeHtml(node.value);
|
|
305
|
+
if (node.__kind === 'comment')
|
|
306
|
+
return '';
|
|
333
307
|
if (node.__kind === 'frag') {
|
|
334
308
|
if (node.__rawHtml != null)
|
|
335
309
|
return String(node.__rawHtml);
|
|
@@ -356,8 +330,6 @@ function flattenSerializeNode(node) {
|
|
|
356
330
|
}
|
|
357
331
|
/**
|
|
358
332
|
* Progressive HTML chunks from a serialize tree (same nodes as flattenSerializeNode).
|
|
359
|
-
* @param {any} node
|
|
360
|
-
* @returns {Generator<string, void, void>}
|
|
361
333
|
*/
|
|
362
334
|
function* streamSerializeChunks(node) {
|
|
363
335
|
if (node == null || node === false)
|
|
@@ -370,6 +342,8 @@ function* streamSerializeChunks(node) {
|
|
|
370
342
|
yield escapeHtml(node.value);
|
|
371
343
|
return;
|
|
372
344
|
}
|
|
345
|
+
if (node.__kind === 'comment')
|
|
346
|
+
return;
|
|
373
347
|
if (node.__kind === 'frag') {
|
|
374
348
|
if (node.__rawHtml != null) {
|
|
375
349
|
yield String(node.__rawHtml);
|
|
@@ -409,19 +383,14 @@ function* streamSerializeChunks(node) {
|
|
|
409
383
|
/**
|
|
410
384
|
* Document-free rowKernel SSR fill (transitional / pre-0.1.7 emit only).
|
|
411
385
|
* Prefer `serializeItem` (IR schedule). Do not treat this as the long-term contract.
|
|
412
|
-
* @param {{ html: string, textSlots?: Record<string, number>, hostFields?: string[] }} rk
|
|
413
|
-
* @param {any} item
|
|
414
|
-
* @param {any} key
|
|
415
|
-
* @returns {string | null} filled outer HTML, or null if shape is not fillable
|
|
416
386
|
*/
|
|
417
387
|
function fillRowKernelHtml(rk, item, key) {
|
|
418
388
|
const slots = rk.textSlots;
|
|
419
389
|
if (!slots || typeof slots !== 'object')
|
|
420
390
|
return null;
|
|
421
|
-
/** @type {string[]} */
|
|
422
391
|
const fields = Object.entries(slots)
|
|
423
392
|
.filter(([, i]) => typeof i === 'number' && Number.isFinite(i))
|
|
424
|
-
.sort((a, b) =>
|
|
393
|
+
.sort((a, b) => Number(a[1]) - Number(b[1]))
|
|
425
394
|
.map(([f]) => f);
|
|
426
395
|
if (!fields.length)
|
|
427
396
|
return null;
|
|
@@ -446,10 +415,6 @@ function fillRowKernelHtml(rk, item, key) {
|
|
|
446
415
|
* SSR row when `createItem` was omitted (rowKernel client emit).
|
|
447
416
|
* Prefer document-free fill from `html` + `textSlots`; fall back to linkedom + hydrate
|
|
448
417
|
* when host class ternaries need live DOM, or when placeholders cannot be string-filled.
|
|
449
|
-
* @param {object} inst
|
|
450
|
-
* @param {{ html: string, hydrate?: Function, textSlots?: Record<string, number>, hostFields?: string[] }} rk
|
|
451
|
-
* @param {{ item: any, index: number }} box
|
|
452
|
-
* @param {any} key
|
|
453
418
|
*/
|
|
454
419
|
function serializeRowFromKernel(inst, rk, box, key) {
|
|
455
420
|
const item = box.item;
|
|
@@ -508,24 +473,71 @@ function serializeRowFromKernel(inst, rk, box, key) {
|
|
|
508
473
|
appendChild() { },
|
|
509
474
|
};
|
|
510
475
|
}
|
|
511
|
-
|
|
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
|
+
return {
|
|
516
|
+
__kind: 'el',
|
|
517
|
+
tag: tag || 'div',
|
|
518
|
+
attrs: {},
|
|
519
|
+
children: [],
|
|
520
|
+
style: {},
|
|
521
|
+
parentNode: null,
|
|
522
|
+
setAttribute(name, value) {
|
|
523
|
+
applySerializeAttr(this, name, value);
|
|
524
|
+
},
|
|
525
|
+
appendChild(c) {
|
|
526
|
+
virtualAppend(this, c);
|
|
527
|
+
},
|
|
528
|
+
insertBefore(node, ref) {
|
|
529
|
+
virtualInsertBefore(this, node, ref);
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
}
|
|
512
533
|
const serializeApi = {
|
|
513
|
-
/** @type {object | null} */
|
|
514
534
|
_inst: null,
|
|
515
|
-
/** @type {null} */
|
|
516
535
|
_branchBinds: null,
|
|
517
|
-
/** @type {null} */
|
|
518
536
|
_itemPatches: null,
|
|
519
|
-
|
|
537
|
+
_eachCtx: null,
|
|
520
538
|
_ssrPreMounted: null,
|
|
521
|
-
/** @type {number} */
|
|
522
539
|
_ssrPreIdx: 0,
|
|
523
|
-
/** @type {object[] | null} newly created child instances this round */
|
|
524
540
|
_ssrCollected: null,
|
|
525
|
-
/**
|
|
526
|
-
* @param {new (props?: object) => any} Ctor
|
|
527
|
-
* @param {object} resolved
|
|
528
|
-
*/
|
|
529
541
|
_ssrChildInstance(Ctor, resolved) {
|
|
530
542
|
const pre = serializeApi._ssrPreMounted;
|
|
531
543
|
if (pre && serializeApi._ssrPreIdx < pre.length) {
|
|
@@ -537,30 +549,85 @@ const serializeApi = {
|
|
|
537
549
|
return child;
|
|
538
550
|
},
|
|
539
551
|
el(tag) {
|
|
540
|
-
return
|
|
541
|
-
__kind: 'el',
|
|
542
|
-
tag: tag || 'div',
|
|
543
|
-
attrs: {},
|
|
544
|
-
children: [],
|
|
545
|
-
appendChild(c) {
|
|
546
|
-
if (c != null)
|
|
547
|
-
this.children.push(c);
|
|
548
|
-
},
|
|
549
|
-
};
|
|
552
|
+
return makeVirtualEl(tag);
|
|
550
553
|
},
|
|
551
554
|
text(value) {
|
|
552
|
-
|
|
555
|
+
const node = { __kind: 'text', value: value == null ? '' : String(value), parentNode: null };
|
|
556
|
+
// Generated Direct patches use DOM `textContent`; virtual text uses `value`.
|
|
557
|
+
Object.defineProperty(node, 'textContent', {
|
|
558
|
+
configurable: true,
|
|
559
|
+
enumerable: false,
|
|
560
|
+
get() {
|
|
561
|
+
return node.value;
|
|
562
|
+
},
|
|
563
|
+
set(v) {
|
|
564
|
+
node.value = v == null ? '' : String(v);
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
return node;
|
|
568
|
+
},
|
|
569
|
+
comment(value) {
|
|
570
|
+
return { __kind: 'comment', value: value == null ? '' : String(value), parentNode: null };
|
|
553
571
|
},
|
|
554
572
|
frag() {
|
|
555
573
|
return {
|
|
556
574
|
__kind: 'frag',
|
|
557
575
|
children: [],
|
|
576
|
+
parentNode: null,
|
|
558
577
|
appendChild(c) {
|
|
559
|
-
|
|
560
|
-
|
|
578
|
+
virtualAppend(this, c);
|
|
579
|
+
},
|
|
580
|
+
insertBefore(node, ref) {
|
|
581
|
+
virtualInsertBefore(this, node, ref);
|
|
561
582
|
},
|
|
562
583
|
};
|
|
563
584
|
},
|
|
585
|
+
insertBefore(parent, node, ref) {
|
|
586
|
+
virtualInsertBefore(parent, node, ref);
|
|
587
|
+
},
|
|
588
|
+
removeNode(node) {
|
|
589
|
+
virtualRemove(node);
|
|
590
|
+
},
|
|
591
|
+
trackPatch(inst, deps, patch, bindingId = null) {
|
|
592
|
+
if (typeof patch !== 'function')
|
|
593
|
+
return;
|
|
594
|
+
if (serializeApi._branchBinds) {
|
|
595
|
+
serializeApi._branchBinds.push({ deps: deps || [], fn: patch, bindingId });
|
|
596
|
+
try {
|
|
597
|
+
patch.call(inst);
|
|
598
|
+
}
|
|
599
|
+
catch (err) {
|
|
600
|
+
console.error('vmz:ssr trackPatch branch', err);
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (serializeApi._itemPatches) {
|
|
605
|
+
patch.__vmzItemLocal = true;
|
|
606
|
+
serializeApi._itemPatches.push(patch);
|
|
607
|
+
if (serializeApi._eachCtx) {
|
|
608
|
+
serializeApi._eachCtx.noteItemBind(bindingId, deps || [], patch);
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
patch.call(inst);
|
|
612
|
+
}
|
|
613
|
+
catch (err) {
|
|
614
|
+
console.error('vmz:ssr trackPatch item', err);
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
patch.call(inst);
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
console.error('vmz:ssr trackPatch', err);
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
untrackPatch() {
|
|
626
|
+
/* SSR one-shot schedule ??no reactive unregister */
|
|
627
|
+
},
|
|
628
|
+
disposeTree() {
|
|
629
|
+
/* SSR one-shot schedule */
|
|
630
|
+
},
|
|
564
631
|
attr(el, name, value) {
|
|
565
632
|
if (!el || el.__kind !== 'el')
|
|
566
633
|
return;
|
|
@@ -581,16 +648,6 @@ const serializeApi = {
|
|
|
581
648
|
onComponentEvent() {
|
|
582
649
|
/* component events attach only on the client */
|
|
583
650
|
},
|
|
584
|
-
bindText(inst, bindingId, deps, get, textNode) {
|
|
585
|
-
let raw = '';
|
|
586
|
-
try {
|
|
587
|
-
raw = get.call(inst);
|
|
588
|
-
}
|
|
589
|
-
catch {
|
|
590
|
-
raw = '';
|
|
591
|
-
}
|
|
592
|
-
textNode.value = String(raw ?? '');
|
|
593
|
-
},
|
|
594
651
|
specFieldText(inst, _bindingId, fieldName, textNode) {
|
|
595
652
|
let raw;
|
|
596
653
|
try {
|
|
@@ -601,16 +658,6 @@ const serializeApi = {
|
|
|
601
658
|
}
|
|
602
659
|
textNode.value = String(raw ?? '');
|
|
603
660
|
},
|
|
604
|
-
bindAttr(inst, bindingId, deps, get, el, name) {
|
|
605
|
-
let raw;
|
|
606
|
-
try {
|
|
607
|
-
raw = get.call(inst);
|
|
608
|
-
}
|
|
609
|
-
catch {
|
|
610
|
-
raw = null;
|
|
611
|
-
}
|
|
612
|
-
applySerializeAttr(el, name, raw);
|
|
613
|
-
},
|
|
614
661
|
specFieldAttr(inst, _bindingId, fieldName, el, name) {
|
|
615
662
|
let raw;
|
|
616
663
|
try {
|
|
@@ -660,7 +707,7 @@ const serializeApi = {
|
|
|
660
707
|
slot.__rawHtml = null;
|
|
661
708
|
if (!Array.isArray(slot.children))
|
|
662
709
|
slot.children = [];
|
|
663
|
-
// Append
|
|
710
|
+
// Append ??multiple projectDefaultSlot calls must accumulate (SSR).
|
|
664
711
|
// Client path replaces the live <slot> then appends siblings; serialize must push.
|
|
665
712
|
slot.children.push(node);
|
|
666
713
|
return;
|
|
@@ -675,85 +722,16 @@ const serializeApi = {
|
|
|
675
722
|
el.children = [];
|
|
676
723
|
},
|
|
677
724
|
bindHtml(inst, bindingId, deps, get, el) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
raw = get.call(inst);
|
|
681
|
-
}
|
|
682
|
-
catch {
|
|
683
|
-
raw = '';
|
|
684
|
-
}
|
|
685
|
-
el.__rawHtml = raw == null ? '' : String(raw);
|
|
686
|
-
el.children = [];
|
|
687
|
-
},
|
|
688
|
-
ifBlock(inst, bindingId, deps, branches) {
|
|
689
|
-
// No empty `span[data-vmz-if]` box (`ui-vif-dom`): false → empty frag.
|
|
690
|
-
const frag = serializeApi.frag();
|
|
691
|
-
let idx = -1;
|
|
692
|
-
for (let i = 0; i < branches.length; i++) {
|
|
693
|
-
const b = branches[i];
|
|
694
|
-
if (!b.cond) {
|
|
695
|
-
idx = i;
|
|
696
|
-
break;
|
|
697
|
-
}
|
|
725
|
+
serializeApi.trackPatch(inst, deps || [], function bindHtmlPatch() {
|
|
726
|
+
let raw;
|
|
698
727
|
try {
|
|
699
|
-
|
|
700
|
-
idx = i;
|
|
701
|
-
break;
|
|
702
|
-
}
|
|
728
|
+
raw = get.call(inst);
|
|
703
729
|
}
|
|
704
730
|
catch {
|
|
705
|
-
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
if (idx >= 0 && branches[idx].create) {
|
|
709
|
-
const created = branches[idx].create.call(inst, serializeApi);
|
|
710
|
-
if (created)
|
|
711
|
-
frag.appendChild(created);
|
|
712
|
-
}
|
|
713
|
-
return frag;
|
|
714
|
-
},
|
|
715
|
-
eachBlock(inst, bindingId, deps, spec) {
|
|
716
|
-
const frag = serializeApi.frag();
|
|
717
|
-
let list = [];
|
|
718
|
-
try {
|
|
719
|
-
list = spec.list.call(inst) || [];
|
|
720
|
-
}
|
|
721
|
-
catch {
|
|
722
|
-
list = [];
|
|
723
|
-
}
|
|
724
|
-
if (!Array.isArray(list))
|
|
725
|
-
list = [...list];
|
|
726
|
-
for (let i = 0; i < list.length; i++) {
|
|
727
|
-
const box = { item: list[i], index: i };
|
|
728
|
-
let k = i;
|
|
729
|
-
if (typeof spec.key === 'function') {
|
|
730
|
-
try {
|
|
731
|
-
k = spec.key.call(inst, box);
|
|
732
|
-
}
|
|
733
|
-
catch {
|
|
734
|
-
k = i;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
let dom = null;
|
|
738
|
-
if (typeof spec.createItem === 'function') {
|
|
739
|
-
dom = spec.createItem.call(inst, serializeApi, box);
|
|
740
|
-
}
|
|
741
|
-
else if (typeof spec.serializeItem === 'function') {
|
|
742
|
-
// IR-homologous schedule (v0.1.7): same Direct body as fat createItem.
|
|
743
|
-
dom = spec.serializeItem.call(inst, serializeApi, box);
|
|
731
|
+
raw = '';
|
|
744
732
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
dom = serializeRowFromKernel(inst, spec.rowKernel, box, k);
|
|
748
|
-
}
|
|
749
|
-
if (dom) {
|
|
750
|
-
// SSR only: serialize key into HTML for hydrate/debug. Direct client does not write this attr.
|
|
751
|
-
if (dom.__kind === 'el' && !dom.__rawOuter)
|
|
752
|
-
serializeApi.attr(dom, 'data-vmz-key', String(k));
|
|
753
|
-
frag.appendChild(dom);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return frag;
|
|
733
|
+
serializeApi.setHtml(el, raw);
|
|
734
|
+
}, bindingId);
|
|
757
735
|
},
|
|
758
736
|
component(hostInst, nameOrCtor, props, client) {
|
|
759
737
|
const name = typeof nameOrCtor === 'function' ? nameOrCtor.__vmzTag || nameOrCtor.name || 'Component' : nameOrCtor;
|
|
@@ -761,7 +739,6 @@ const serializeApi = {
|
|
|
761
739
|
if (!Ctor) {
|
|
762
740
|
return serializeUnknownComponentNode(name);
|
|
763
741
|
}
|
|
764
|
-
/** @type {Record<string, any>} */
|
|
765
742
|
const resolved = {};
|
|
766
743
|
for (const [k, v] of Object.entries(props || {})) {
|
|
767
744
|
const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
|
|
@@ -797,7 +774,6 @@ const serializeApi = {
|
|
|
797
774
|
planSchema: plan?.schema || null,
|
|
798
775
|
planRootIds: plan?.root_ids || [],
|
|
799
776
|
};
|
|
800
|
-
/** @type {Record<string, string>} */
|
|
801
777
|
const attrs = {
|
|
802
778
|
'data-vmz': name,
|
|
803
779
|
'data-vmz-island': name,
|
|
@@ -828,7 +804,6 @@ const serializeApi = {
|
|
|
828
804
|
serializeApi._inst = child;
|
|
829
805
|
try {
|
|
830
806
|
const node = Ctor.__vmzCreate.call(child, serializeApi);
|
|
831
|
-
/** @type {Record<string, string>} */
|
|
832
807
|
const attrs = { 'data-vmz': name };
|
|
833
808
|
const boxStyle = directHostBoxStyleAttr(name, Ctor);
|
|
834
809
|
if (boxStyle)
|
|
@@ -853,9 +828,6 @@ const serializeApi = {
|
|
|
853
828
|
};
|
|
854
829
|
/**
|
|
855
830
|
* Serialize-tree attr write (SSR).
|
|
856
|
-
* @param {any} el
|
|
857
|
-
* @param {string} name
|
|
858
|
-
* @param {any} value
|
|
859
831
|
*/
|
|
860
832
|
function applySerializeAttr(el, name, value) {
|
|
861
833
|
if (!el || el.__kind !== 'el')
|
|
@@ -876,9 +848,6 @@ function applySerializeAttr(el, name, value) {
|
|
|
876
848
|
/**
|
|
877
849
|
* resume: attach to existing Island DOM without re-running construct structure or onMount.
|
|
878
850
|
* Consumes ResumeEntry product (`data-vmz-resume`) derived from the same Execution Plan.
|
|
879
|
-
* @param {new (props?: object) => any} Component
|
|
880
|
-
* @param {HTMLElement} container
|
|
881
|
-
* @param {{ props?: object, state?: Record<string, unknown>, strategy?: string } | null} [slice]
|
|
882
851
|
*/
|
|
883
852
|
export async function resume(Component, container, slice = null) {
|
|
884
853
|
if (typeof document === 'undefined') {
|
|
@@ -916,7 +885,7 @@ export async function resume(Component, container, slice = null) {
|
|
|
916
885
|
const inst = createInstance(Component, props);
|
|
917
886
|
if (parsed.state)
|
|
918
887
|
applyPreservedState(inst, parsed.state);
|
|
919
|
-
// Intentionally never call onMount
|
|
888
|
+
// Intentionally never call onMount ??SSR already completed that work.
|
|
920
889
|
if (Component.__vmzDirect && typeof Component.__vmzCreate === 'function') {
|
|
921
890
|
if (!hasMeaningfulChild(container)) {
|
|
922
891
|
const node = runDirectCreate(Component, inst);
|
|
@@ -942,7 +911,6 @@ export async function resume(Component, container, slice = null) {
|
|
|
942
911
|
/**
|
|
943
912
|
* Resume all `[data-vmz-island]` hosts (prefer ResumeEntry / EventEntry over mount).
|
|
944
913
|
* Event strategy islands wait for the DOM event before attach (lazy EventEntry).
|
|
945
|
-
* @param {ParentNode} [root]
|
|
946
914
|
*/
|
|
947
915
|
export function resumeIslands(root = globalThis.document) {
|
|
948
916
|
if (!root || typeof root.querySelectorAll !== 'function') {
|
|
@@ -965,7 +933,6 @@ export function resumeIslands(root = globalThis.document) {
|
|
|
965
933
|
/**
|
|
966
934
|
* EventEntry attach: only wire `client:event` / `client:event:*` islands.
|
|
967
935
|
* Idle/load/visible ResumeEntries are left alone (static shell can defer framework work).
|
|
968
|
-
* @param {ParentNode} [root]
|
|
969
936
|
*/
|
|
970
937
|
export function attachEventEntries(root = globalThis.document) {
|
|
971
938
|
if (!root || typeof root.querySelectorAll !== 'function') {
|
|
@@ -1001,22 +968,15 @@ export function attachEventEntries(root = globalThis.document) {
|
|
|
1001
968
|
* - `beginBranchScope()` moves one top-level SSR node into an isolated pool so
|
|
1002
969
|
* ifBlock branch create cannot steal parent siblings (Drawer / Button hosts).
|
|
1003
970
|
*
|
|
1004
|
-
* @param {DocumentFragment} pool
|
|
1005
|
-
* @param {Element} rootEl
|
|
1006
971
|
*/
|
|
1007
972
|
function createResumeAdopt(pool, rootEl) {
|
|
1008
973
|
const used = new WeakSet();
|
|
1009
|
-
/** @type {DocumentFragment[]} */
|
|
1010
974
|
const scopeStack = [pool];
|
|
1011
|
-
/**
|
|
1012
|
-
* @param {Node} root
|
|
1013
|
-
* @param {(n: Text) => void} visit
|
|
1014
|
-
*/
|
|
1015
975
|
const walkTexts = (root, visit) => {
|
|
1016
976
|
if (!root)
|
|
1017
977
|
return;
|
|
1018
978
|
if (root.nodeType === 3) {
|
|
1019
|
-
visit(
|
|
979
|
+
visit(root);
|
|
1020
980
|
return;
|
|
1021
981
|
}
|
|
1022
982
|
if (root.nodeType !== 1)
|
|
@@ -1032,7 +992,7 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1032
992
|
if (k != null && n.__vmzKey == null)
|
|
1033
993
|
n.__vmzKey = k;
|
|
1034
994
|
}
|
|
1035
|
-
// Private child pool
|
|
995
|
+
// Private child pool ??never dump into the parent/global pool (that let
|
|
1036
996
|
// if branches reclaim Drawer/sibling hosts parked as uncles).
|
|
1037
997
|
const childPool = document.createDocumentFragment();
|
|
1038
998
|
while (n.firstChild)
|
|
@@ -1040,25 +1000,22 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1040
1000
|
n.__vmzResumePool = childPool;
|
|
1041
1001
|
return n;
|
|
1042
1002
|
};
|
|
1043
|
-
/** @returns {Generator<Element>} */
|
|
1044
1003
|
function* elementCandidates() {
|
|
1045
1004
|
const scope = currentScope();
|
|
1046
1005
|
if (scope === pool && rootEl && !used.has(rootEl))
|
|
1047
1006
|
yield rootEl;
|
|
1048
|
-
// Snapshot
|
|
1007
|
+
// Snapshot ??adopt may move nodes out of scope while iterating.
|
|
1049
1008
|
const kids = [...scope.childNodes];
|
|
1050
1009
|
for (const n of kids) {
|
|
1051
|
-
if (n.nodeType === 1 && !used.has(
|
|
1052
|
-
yield
|
|
1010
|
+
if (n.nodeType === 1 && !used.has(n)) {
|
|
1011
|
+
yield n;
|
|
1053
1012
|
}
|
|
1054
1013
|
}
|
|
1055
1014
|
}
|
|
1056
|
-
/** @returns {Generator<Text>} */
|
|
1057
1015
|
function* textCandidates() {
|
|
1058
1016
|
const scope = currentScope();
|
|
1059
1017
|
const kids = [...scope.childNodes];
|
|
1060
1018
|
for (const n of kids) {
|
|
1061
|
-
/** @type {Text[]} */
|
|
1062
1019
|
const found = [];
|
|
1063
1020
|
walkTexts(n, (t) => {
|
|
1064
1021
|
if (!used.has(t))
|
|
@@ -1087,7 +1044,6 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1087
1044
|
noteDomCreate();
|
|
1088
1045
|
return document.createTextNode(value == null ? '' : String(value));
|
|
1089
1046
|
};
|
|
1090
|
-
/** Reclaim SSR `<div data-vmz="Name">` hosts for nested Direct components. */
|
|
1091
1047
|
const componentHost = (name) => {
|
|
1092
1048
|
const want = String(name || '');
|
|
1093
1049
|
for (const el of elementCandidates()) {
|
|
@@ -1119,13 +1075,12 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1119
1075
|
/**
|
|
1120
1076
|
* Isolate the next top-level SSR sibling as the only candidates for an
|
|
1121
1077
|
* if/else branch create (commercial Card: Empty branch must not adopt Drawer).
|
|
1122
|
-
* @returns {() => void} end callback
|
|
1123
1078
|
*/
|
|
1124
1079
|
const beginBranchScope = () => {
|
|
1125
1080
|
const parent = currentScope();
|
|
1126
1081
|
const branchPool = document.createDocumentFragment();
|
|
1127
1082
|
for (const n of [...parent.childNodes]) {
|
|
1128
|
-
if (n.nodeType === 1 && !used.has(
|
|
1083
|
+
if (n.nodeType === 1 && !used.has(n)) {
|
|
1129
1084
|
branchPool.appendChild(n);
|
|
1130
1085
|
break;
|
|
1131
1086
|
}
|
|
@@ -1151,9 +1106,6 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1151
1106
|
}
|
|
1152
1107
|
/**
|
|
1153
1108
|
* Adopt existing Island DOM while running the same `__vmzCreate` schedule (resume).
|
|
1154
|
-
* @param {new (props?: object) => any} Component
|
|
1155
|
-
* @param {object} inst
|
|
1156
|
-
* @param {Element} container
|
|
1157
1109
|
*/
|
|
1158
1110
|
function runDirectResume(Component, inst, container) {
|
|
1159
1111
|
const rootEl = [...container.childNodes].find((n) => n.nodeType === 1 || (n.nodeType === 3 && String(n.textContent).trim() !== ''));
|
|
@@ -1187,17 +1139,10 @@ function runDirectResume(Component, inst, container) {
|
|
|
1187
1139
|
pool.removeChild(pool.firstChild);
|
|
1188
1140
|
}
|
|
1189
1141
|
}
|
|
1190
|
-
/**
|
|
1191
|
-
* @param {new (props?: object) => any} Component
|
|
1192
|
-
* @param {HTMLElement} container
|
|
1193
|
-
* @param {object} [props]
|
|
1194
|
-
* @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
|
|
1195
|
-
*/
|
|
1196
1142
|
export async function hydrate(Component, container, props = {}, opts = {}) {
|
|
1197
1143
|
if (typeof document === 'undefined') {
|
|
1198
1144
|
throw new Error('vmz:dom hydrate() requires a document (browser)');
|
|
1199
1145
|
}
|
|
1200
|
-
/** @type {Record<string, unknown> | null} */
|
|
1201
1146
|
let preserved = null;
|
|
1202
1147
|
if (opts.preserveState && typeof opts.preserveState === 'object') {
|
|
1203
1148
|
preserved = opts.preserveState;
|
|
@@ -1241,9 +1186,6 @@ export async function hydrate(Component, container, props = {}, opts = {}) {
|
|
|
1241
1186
|
}
|
|
1242
1187
|
return inst;
|
|
1243
1188
|
}
|
|
1244
|
-
/**
|
|
1245
|
-
* @param {ParentNode} [root]
|
|
1246
|
-
*/
|
|
1247
1189
|
export function hydrateIslands(root = globalThis.document) {
|
|
1248
1190
|
// resume: hydrateIslands is an alias for resumeIslands (same Plan attach).
|
|
1249
1191
|
return resumeIslands(root);
|