@vmz/core 0.1.31 → 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/browser/dom-core.d.ts +246 -0
- 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} +47 -185
- 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} +162 -219
- 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.d.ts +0 -402
- package/dist/dom-core.js +0 -4813
- 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,92 +722,23 @@ 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
|
-
component(hostInst,
|
|
759
|
-
const
|
|
736
|
+
component(hostInst, nameOrCtor, props, client) {
|
|
737
|
+
const name = typeof nameOrCtor === 'function' ? nameOrCtor.__vmzTag || nameOrCtor.name || 'Component' : nameOrCtor;
|
|
738
|
+
const Ctor = typeof nameOrCtor === 'function' ? nameOrCtor : getRegisteredComponent(name);
|
|
760
739
|
if (!Ctor) {
|
|
761
740
|
return serializeUnknownComponentNode(name);
|
|
762
741
|
}
|
|
763
|
-
/** @type {Record<string, any>} */
|
|
764
742
|
const resolved = {};
|
|
765
743
|
for (const [k, v] of Object.entries(props || {})) {
|
|
766
744
|
const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
|
|
@@ -796,7 +774,6 @@ const serializeApi = {
|
|
|
796
774
|
planSchema: plan?.schema || null,
|
|
797
775
|
planRootIds: plan?.root_ids || [],
|
|
798
776
|
};
|
|
799
|
-
/** @type {Record<string, string>} */
|
|
800
777
|
const attrs = {
|
|
801
778
|
'data-vmz': name,
|
|
802
779
|
'data-vmz-island': name,
|
|
@@ -827,7 +804,6 @@ const serializeApi = {
|
|
|
827
804
|
serializeApi._inst = child;
|
|
828
805
|
try {
|
|
829
806
|
const node = Ctor.__vmzCreate.call(child, serializeApi);
|
|
830
|
-
/** @type {Record<string, string>} */
|
|
831
807
|
const attrs = { 'data-vmz': name };
|
|
832
808
|
const boxStyle = directHostBoxStyleAttr(name, Ctor);
|
|
833
809
|
if (boxStyle)
|
|
@@ -852,9 +828,6 @@ const serializeApi = {
|
|
|
852
828
|
};
|
|
853
829
|
/**
|
|
854
830
|
* Serialize-tree attr write (SSR).
|
|
855
|
-
* @param {any} el
|
|
856
|
-
* @param {string} name
|
|
857
|
-
* @param {any} value
|
|
858
831
|
*/
|
|
859
832
|
function applySerializeAttr(el, name, value) {
|
|
860
833
|
if (!el || el.__kind !== 'el')
|
|
@@ -875,9 +848,6 @@ function applySerializeAttr(el, name, value) {
|
|
|
875
848
|
/**
|
|
876
849
|
* resume: attach to existing Island DOM without re-running construct structure or onMount.
|
|
877
850
|
* Consumes ResumeEntry product (`data-vmz-resume`) derived from the same Execution Plan.
|
|
878
|
-
* @param {new (props?: object) => any} Component
|
|
879
|
-
* @param {HTMLElement} container
|
|
880
|
-
* @param {{ props?: object, state?: Record<string, unknown>, strategy?: string } | null} [slice]
|
|
881
851
|
*/
|
|
882
852
|
export async function resume(Component, container, slice = null) {
|
|
883
853
|
if (typeof document === 'undefined') {
|
|
@@ -915,7 +885,7 @@ export async function resume(Component, container, slice = null) {
|
|
|
915
885
|
const inst = createInstance(Component, props);
|
|
916
886
|
if (parsed.state)
|
|
917
887
|
applyPreservedState(inst, parsed.state);
|
|
918
|
-
// Intentionally never call onMount
|
|
888
|
+
// Intentionally never call onMount ??SSR already completed that work.
|
|
919
889
|
if (Component.__vmzDirect && typeof Component.__vmzCreate === 'function') {
|
|
920
890
|
if (!hasMeaningfulChild(container)) {
|
|
921
891
|
const node = runDirectCreate(Component, inst);
|
|
@@ -941,7 +911,6 @@ export async function resume(Component, container, slice = null) {
|
|
|
941
911
|
/**
|
|
942
912
|
* Resume all `[data-vmz-island]` hosts (prefer ResumeEntry / EventEntry over mount).
|
|
943
913
|
* Event strategy islands wait for the DOM event before attach (lazy EventEntry).
|
|
944
|
-
* @param {ParentNode} [root]
|
|
945
914
|
*/
|
|
946
915
|
export function resumeIslands(root = globalThis.document) {
|
|
947
916
|
if (!root || typeof root.querySelectorAll !== 'function') {
|
|
@@ -964,7 +933,6 @@ export function resumeIslands(root = globalThis.document) {
|
|
|
964
933
|
/**
|
|
965
934
|
* EventEntry attach: only wire `client:event` / `client:event:*` islands.
|
|
966
935
|
* Idle/load/visible ResumeEntries are left alone (static shell can defer framework work).
|
|
967
|
-
* @param {ParentNode} [root]
|
|
968
936
|
*/
|
|
969
937
|
export function attachEventEntries(root = globalThis.document) {
|
|
970
938
|
if (!root || typeof root.querySelectorAll !== 'function') {
|
|
@@ -1000,22 +968,15 @@ export function attachEventEntries(root = globalThis.document) {
|
|
|
1000
968
|
* - `beginBranchScope()` moves one top-level SSR node into an isolated pool so
|
|
1001
969
|
* ifBlock branch create cannot steal parent siblings (Drawer / Button hosts).
|
|
1002
970
|
*
|
|
1003
|
-
* @param {DocumentFragment} pool
|
|
1004
|
-
* @param {Element} rootEl
|
|
1005
971
|
*/
|
|
1006
972
|
function createResumeAdopt(pool, rootEl) {
|
|
1007
973
|
const used = new WeakSet();
|
|
1008
|
-
/** @type {DocumentFragment[]} */
|
|
1009
974
|
const scopeStack = [pool];
|
|
1010
|
-
/**
|
|
1011
|
-
* @param {Node} root
|
|
1012
|
-
* @param {(n: Text) => void} visit
|
|
1013
|
-
*/
|
|
1014
975
|
const walkTexts = (root, visit) => {
|
|
1015
976
|
if (!root)
|
|
1016
977
|
return;
|
|
1017
978
|
if (root.nodeType === 3) {
|
|
1018
|
-
visit(
|
|
979
|
+
visit(root);
|
|
1019
980
|
return;
|
|
1020
981
|
}
|
|
1021
982
|
if (root.nodeType !== 1)
|
|
@@ -1031,7 +992,7 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1031
992
|
if (k != null && n.__vmzKey == null)
|
|
1032
993
|
n.__vmzKey = k;
|
|
1033
994
|
}
|
|
1034
|
-
// Private child pool
|
|
995
|
+
// Private child pool ??never dump into the parent/global pool (that let
|
|
1035
996
|
// if branches reclaim Drawer/sibling hosts parked as uncles).
|
|
1036
997
|
const childPool = document.createDocumentFragment();
|
|
1037
998
|
while (n.firstChild)
|
|
@@ -1039,25 +1000,22 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1039
1000
|
n.__vmzResumePool = childPool;
|
|
1040
1001
|
return n;
|
|
1041
1002
|
};
|
|
1042
|
-
/** @returns {Generator<Element>} */
|
|
1043
1003
|
function* elementCandidates() {
|
|
1044
1004
|
const scope = currentScope();
|
|
1045
1005
|
if (scope === pool && rootEl && !used.has(rootEl))
|
|
1046
1006
|
yield rootEl;
|
|
1047
|
-
// Snapshot
|
|
1007
|
+
// Snapshot ??adopt may move nodes out of scope while iterating.
|
|
1048
1008
|
const kids = [...scope.childNodes];
|
|
1049
1009
|
for (const n of kids) {
|
|
1050
|
-
if (n.nodeType === 1 && !used.has(
|
|
1051
|
-
yield
|
|
1010
|
+
if (n.nodeType === 1 && !used.has(n)) {
|
|
1011
|
+
yield n;
|
|
1052
1012
|
}
|
|
1053
1013
|
}
|
|
1054
1014
|
}
|
|
1055
|
-
/** @returns {Generator<Text>} */
|
|
1056
1015
|
function* textCandidates() {
|
|
1057
1016
|
const scope = currentScope();
|
|
1058
1017
|
const kids = [...scope.childNodes];
|
|
1059
1018
|
for (const n of kids) {
|
|
1060
|
-
/** @type {Text[]} */
|
|
1061
1019
|
const found = [];
|
|
1062
1020
|
walkTexts(n, (t) => {
|
|
1063
1021
|
if (!used.has(t))
|
|
@@ -1086,7 +1044,6 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1086
1044
|
noteDomCreate();
|
|
1087
1045
|
return document.createTextNode(value == null ? '' : String(value));
|
|
1088
1046
|
};
|
|
1089
|
-
/** Reclaim SSR `<div data-vmz="Name">` hosts for nested Direct components. */
|
|
1090
1047
|
const componentHost = (name) => {
|
|
1091
1048
|
const want = String(name || '');
|
|
1092
1049
|
for (const el of elementCandidates()) {
|
|
@@ -1118,13 +1075,12 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1118
1075
|
/**
|
|
1119
1076
|
* Isolate the next top-level SSR sibling as the only candidates for an
|
|
1120
1077
|
* if/else branch create (commercial Card: Empty branch must not adopt Drawer).
|
|
1121
|
-
* @returns {() => void} end callback
|
|
1122
1078
|
*/
|
|
1123
1079
|
const beginBranchScope = () => {
|
|
1124
1080
|
const parent = currentScope();
|
|
1125
1081
|
const branchPool = document.createDocumentFragment();
|
|
1126
1082
|
for (const n of [...parent.childNodes]) {
|
|
1127
|
-
if (n.nodeType === 1 && !used.has(
|
|
1083
|
+
if (n.nodeType === 1 && !used.has(n)) {
|
|
1128
1084
|
branchPool.appendChild(n);
|
|
1129
1085
|
break;
|
|
1130
1086
|
}
|
|
@@ -1150,9 +1106,6 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
1150
1106
|
}
|
|
1151
1107
|
/**
|
|
1152
1108
|
* Adopt existing Island DOM while running the same `__vmzCreate` schedule (resume).
|
|
1153
|
-
* @param {new (props?: object) => any} Component
|
|
1154
|
-
* @param {object} inst
|
|
1155
|
-
* @param {Element} container
|
|
1156
1109
|
*/
|
|
1157
1110
|
function runDirectResume(Component, inst, container) {
|
|
1158
1111
|
const rootEl = [...container.childNodes].find((n) => n.nodeType === 1 || (n.nodeType === 3 && String(n.textContent).trim() !== ''));
|
|
@@ -1186,17 +1139,10 @@ function runDirectResume(Component, inst, container) {
|
|
|
1186
1139
|
pool.removeChild(pool.firstChild);
|
|
1187
1140
|
}
|
|
1188
1141
|
}
|
|
1189
|
-
/**
|
|
1190
|
-
* @param {new (props?: object) => any} Component
|
|
1191
|
-
* @param {HTMLElement} container
|
|
1192
|
-
* @param {object} [props]
|
|
1193
|
-
* @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
|
|
1194
|
-
*/
|
|
1195
1142
|
export async function hydrate(Component, container, props = {}, opts = {}) {
|
|
1196
1143
|
if (typeof document === 'undefined') {
|
|
1197
1144
|
throw new Error('vmz:dom hydrate() requires a document (browser)');
|
|
1198
1145
|
}
|
|
1199
|
-
/** @type {Record<string, unknown> | null} */
|
|
1200
1146
|
let preserved = null;
|
|
1201
1147
|
if (opts.preserveState && typeof opts.preserveState === 'object') {
|
|
1202
1148
|
preserved = opts.preserveState;
|
|
@@ -1240,9 +1186,6 @@ export async function hydrate(Component, container, props = {}, opts = {}) {
|
|
|
1240
1186
|
}
|
|
1241
1187
|
return inst;
|
|
1242
1188
|
}
|
|
1243
|
-
/**
|
|
1244
|
-
* @param {ParentNode} [root]
|
|
1245
|
-
*/
|
|
1246
1189
|
export function hydrateIslands(root = globalThis.document) {
|
|
1247
1190
|
// resume: hydrateIslands is an alias for resumeIslands (same Plan attach).
|
|
1248
1191
|
return resumeIslands(root);
|