@vmz/core 0.3.10 → 0.3.12
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/browser/direct-api.types.d.ts +1 -0
- package/dist/browser/dom/component.d.ts +15 -3
- package/dist/browser/dom/component.js +101 -19
- package/dist/browser/dom/direct-api.d.ts +9 -0
- package/dist/browser/dom/direct-api.js +44 -1
- package/dist/browser/dom-core.d.ts +1 -0
- package/dist/browser/dom-core.js +1 -0
- package/dist/faces/server.js +14 -0
- package/dist/ssr/serialize.js +108 -47
- package/package.json +7 -7
|
@@ -142,6 +142,7 @@ export type DirectApi = {
|
|
|
142
142
|
adoptEnter: (node: Element) => boolean;
|
|
143
143
|
adoptLeave: () => void;
|
|
144
144
|
projectDefaultSlot: (hostEl: HTMLElement, node: Node) => void;
|
|
145
|
+
projectNamedSlot: (hostEl: HTMLElement, name: string, node: Node) => void;
|
|
145
146
|
setHtml: (el: Element, value: unknown) => void;
|
|
146
147
|
setShow: (el: HTMLElement, condition: unknown) => void;
|
|
147
148
|
bindHtml: (inst: DirectInstance, bindingId: BindingId, deps: string[], get: PatchFn, el: Element) => void;
|
|
@@ -3,6 +3,14 @@ export declare function settlePendingChildMounts(inst: any): Promise<void>;
|
|
|
3
3
|
export declare function runDirectCreate(Component: any, inst: any): any;
|
|
4
4
|
export declare function findOwnedDefaultSlot(root: any): any;
|
|
5
5
|
export declare function findOwnedDefaultSlotTarget(root: any): any;
|
|
6
|
+
/**
|
|
7
|
+
* Owned `<slot name="…">` outlet for a named projection.
|
|
8
|
+
*
|
|
9
|
+
* Same ownership rule as {@link findOwnedDefaultSlot}: a nested component host
|
|
10
|
+
* (`[data-vmz]` / `__vmzInst`) is a boundary, so projecting `header` into a Card
|
|
11
|
+
* can never capture a Button's own named slot.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findOwnedNamedSlot(root: any, name: any): any;
|
|
6
14
|
export declare function createInstance(Component: any, props?: any): any;
|
|
7
15
|
export declare function destroy(inst: any): void;
|
|
8
16
|
/**
|
|
@@ -13,15 +21,19 @@ export declare function destroy(inst: any): void;
|
|
|
13
21
|
export declare function slotAnchorFor(node: any): any;
|
|
14
22
|
export declare function insertProjectedSlotNode(target: any, node: any): any;
|
|
15
23
|
/**
|
|
16
|
-
* Hold projected children until the component
|
|
24
|
+
* Hold projected children until the component slot exists.
|
|
17
25
|
*
|
|
18
26
|
* A slot inside an unmounted `v-if` branch (closed drawer/dialog, loading
|
|
19
27
|
* skeleton) is absent at projection time. Appending to the component root would
|
|
20
28
|
* paint the child into the live page, so keep it detached and splice it in when
|
|
21
29
|
* the slot is mounted.
|
|
30
|
+
*
|
|
31
|
+
* `name` parks for a specific `slot name="…"` outlet. Named projections never
|
|
32
|
+
* fall back into the default slot: a missing named outlet keeps the node parked
|
|
33
|
+
* (or drops it on hosts without MutationObserver) instead of polluting it.
|
|
22
34
|
*/
|
|
23
|
-
export declare function parkSlotNode(hostEl: any, node: any): void;
|
|
24
|
-
/** Move parked children into the now-mounted
|
|
35
|
+
export declare function parkSlotNode(hostEl: any, node: any, name?: any): void;
|
|
36
|
+
/** Move parked children into the now-mounted slot. Returns true when any drained. */
|
|
25
37
|
export declare function flushParkedSlotNodes(hostEl: any): boolean;
|
|
26
38
|
export declare function disposeDomTree(root: any): void;
|
|
27
39
|
export declare function snapshotInstanceState(inst: any): {};
|
|
@@ -120,6 +120,37 @@ export function findOwnedDefaultSlotTarget(root) {
|
|
|
120
120
|
};
|
|
121
121
|
return walk(root);
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Owned `<slot name="…">` outlet for a named projection.
|
|
125
|
+
*
|
|
126
|
+
* Same ownership rule as {@link findOwnedDefaultSlot}: a nested component host
|
|
127
|
+
* (`[data-vmz]` / `__vmzInst`) is a boundary, so projecting `header` into a Card
|
|
128
|
+
* can never capture a Button's own named slot.
|
|
129
|
+
*/
|
|
130
|
+
export function findOwnedNamedSlot(root, name) {
|
|
131
|
+
if (!root || root.nodeType !== 1)
|
|
132
|
+
return null;
|
|
133
|
+
if (name == null || name === '')
|
|
134
|
+
return null;
|
|
135
|
+
const wanted = String(name);
|
|
136
|
+
const tag = String(root.tagName || '').toLowerCase();
|
|
137
|
+
if (tag === 'slot' && root.getAttribute('name') === wanted)
|
|
138
|
+
return root;
|
|
139
|
+
const kids = root.children;
|
|
140
|
+
if (!kids || !kids.length)
|
|
141
|
+
return null;
|
|
142
|
+
for (let i = 0; i < kids.length; i++) {
|
|
143
|
+
const c = kids[i];
|
|
144
|
+
if (c.nodeType !== 1)
|
|
145
|
+
continue;
|
|
146
|
+
if (c.hasAttribute('data-vmz') || c.__vmzInst)
|
|
147
|
+
continue;
|
|
148
|
+
const hit = findOwnedNamedSlot(c, wanted);
|
|
149
|
+
if (hit)
|
|
150
|
+
return hit;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
123
154
|
export function createInstance(Component, props = {}) {
|
|
124
155
|
if (precision.enabled)
|
|
125
156
|
precision.componentExecs++;
|
|
@@ -203,17 +234,24 @@ export function insertProjectedSlotNode(target, node) {
|
|
|
203
234
|
return anchor;
|
|
204
235
|
}
|
|
205
236
|
/**
|
|
206
|
-
* Hold projected children until the component
|
|
237
|
+
* Hold projected children until the component slot exists.
|
|
207
238
|
*
|
|
208
239
|
* A slot inside an unmounted `v-if` branch (closed drawer/dialog, loading
|
|
209
240
|
* skeleton) is absent at projection time. Appending to the component root would
|
|
210
241
|
* paint the child into the live page, so keep it detached and splice it in when
|
|
211
242
|
* the slot is mounted.
|
|
243
|
+
*
|
|
244
|
+
* `name` parks for a specific `slot name="…"` outlet. Named projections never
|
|
245
|
+
* fall back into the default slot: a missing named outlet keeps the node parked
|
|
246
|
+
* (or drops it on hosts without MutationObserver) instead of polluting it.
|
|
212
247
|
*/
|
|
213
|
-
export function parkSlotNode(hostEl, node) {
|
|
248
|
+
export function parkSlotNode(hostEl, node, name = null) {
|
|
214
249
|
if (!hostEl || !node)
|
|
215
250
|
return;
|
|
251
|
+
const slotName = name == null ? null : String(name);
|
|
216
252
|
if (typeof MutationObserver === 'undefined') {
|
|
253
|
+
if (slotName != null)
|
|
254
|
+
return;
|
|
217
255
|
// Non-browser DOM (unit hosts): preserve the legacy root fallback.
|
|
218
256
|
const child = hostEl.__vmzInst;
|
|
219
257
|
const root = (child && child.__vmzDomRoot) || hostEl;
|
|
@@ -223,8 +261,28 @@ export function parkSlotNode(hostEl, node) {
|
|
|
223
261
|
hostEl.appendChild(node);
|
|
224
262
|
return;
|
|
225
263
|
}
|
|
226
|
-
|
|
227
|
-
|
|
264
|
+
if (slotName == null) {
|
|
265
|
+
const parked = hostEl.__vmzSlotParked || (hostEl.__vmzSlotParked = []);
|
|
266
|
+
parked.push(node);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
namedSlotParked(hostEl, slotName).push(node);
|
|
270
|
+
}
|
|
271
|
+
ensureSlotObserver(hostEl);
|
|
272
|
+
}
|
|
273
|
+
function namedSlotParked(hostEl, name) {
|
|
274
|
+
const map = hostEl.__vmzNamedSlotParked || (hostEl.__vmzNamedSlotParked = new Map());
|
|
275
|
+
const list = map.get(name);
|
|
276
|
+
if (list)
|
|
277
|
+
return list;
|
|
278
|
+
const fresh = [];
|
|
279
|
+
map.set(name, fresh);
|
|
280
|
+
return fresh;
|
|
281
|
+
}
|
|
282
|
+
function namedSlotAnchor(hostEl) {
|
|
283
|
+
return hostEl.__vmzNamedSlotAnchor || (hostEl.__vmzNamedSlotAnchor = new Map());
|
|
284
|
+
}
|
|
285
|
+
function ensureSlotObserver(hostEl) {
|
|
228
286
|
if (hostEl.__vmzSlotObserver)
|
|
229
287
|
return;
|
|
230
288
|
hostEl.__vmzSlotObserver = new MutationObserver(() => {
|
|
@@ -235,44 +293,64 @@ export function parkSlotNode(hostEl, node) {
|
|
|
235
293
|
hostEl.__vmzSlotObserver.observe(scope, { childList: true, subtree: true });
|
|
236
294
|
}
|
|
237
295
|
}
|
|
238
|
-
/** Move parked children into the now-mounted
|
|
296
|
+
/** Move parked children into the now-mounted slot. Returns true when any drained. */
|
|
239
297
|
export function flushParkedSlotNodes(hostEl) {
|
|
240
298
|
if (!hostEl)
|
|
241
299
|
return false;
|
|
242
|
-
const parked = hostEl.__vmzSlotParked;
|
|
243
|
-
if (!parked || !parked.length)
|
|
244
|
-
return false;
|
|
245
300
|
const child = hostEl.__vmzInst;
|
|
246
301
|
const root = (child && child.__vmzDomRoot) || hostEl;
|
|
247
302
|
if (!root || root.nodeType !== 1)
|
|
248
303
|
return false;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
304
|
+
let drained = false;
|
|
305
|
+
const parkedDefault = hostEl.__vmzSlotParked;
|
|
306
|
+
if (parkedDefault && parkedDefault.length) {
|
|
307
|
+
const target = findOwnedDefaultSlotTarget(root);
|
|
308
|
+
if (target && target.parentNode) {
|
|
309
|
+
hostEl.__vmzSlotParked = [];
|
|
310
|
+
hostEl.__vmzSlotAnchor = insertParked(hostEl, target, parkedDefault);
|
|
311
|
+
drained = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const named = hostEl.__vmzNamedSlotParked;
|
|
315
|
+
if (named && named.size) {
|
|
316
|
+
for (const [name, nodes] of named) {
|
|
317
|
+
if (!nodes.length) {
|
|
318
|
+
named.delete(name);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const target = findOwnedNamedSlot(root, name);
|
|
322
|
+
if (!target || !target.parentNode)
|
|
323
|
+
continue;
|
|
324
|
+
named.delete(name);
|
|
325
|
+
namedSlotAnchor(hostEl).set(name, insertParked(hostEl, target, nodes));
|
|
326
|
+
drained = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const stillParked = (hostEl.__vmzSlotParked && hostEl.__vmzSlotParked.length > 0) ||
|
|
330
|
+
(hostEl.__vmzNamedSlotParked && hostEl.__vmzNamedSlotParked.size > 0);
|
|
331
|
+
if (drained && !stillParked && hostEl.__vmzSlotObserver) {
|
|
253
332
|
hostEl.__vmzSlotObserver.disconnect();
|
|
254
333
|
hostEl.__vmzSlotObserver = null;
|
|
255
334
|
}
|
|
256
|
-
|
|
335
|
+
return drained;
|
|
336
|
+
}
|
|
337
|
+
function insertParked(hostEl, target, parked) {
|
|
257
338
|
const anchor = slotAnchorFor(parked[parked.length - 1]);
|
|
258
339
|
const tag = String(target.tagName || '').toLowerCase();
|
|
259
340
|
if (tag === 'slot') {
|
|
260
341
|
const frag = typeof document !== 'undefined' ? document.createDocumentFragment() : null;
|
|
261
342
|
if (!frag) {
|
|
262
343
|
target.replaceWith(parked[0]);
|
|
263
|
-
|
|
264
|
-
return true;
|
|
344
|
+
return anchor;
|
|
265
345
|
}
|
|
266
346
|
for (const node of parked)
|
|
267
347
|
frag.appendChild(node);
|
|
268
348
|
target.replaceWith(frag);
|
|
269
|
-
|
|
270
|
-
return true;
|
|
349
|
+
return anchor;
|
|
271
350
|
}
|
|
272
351
|
for (const node of parked)
|
|
273
352
|
target.appendChild(node);
|
|
274
|
-
|
|
275
|
-
return true;
|
|
353
|
+
return anchor;
|
|
276
354
|
}
|
|
277
355
|
export function disposeDomTree(root) {
|
|
278
356
|
if (!root)
|
|
@@ -304,6 +382,10 @@ export function disposeDomTree(root) {
|
|
|
304
382
|
node.__vmzSlotParked = [];
|
|
305
383
|
if (node.__vmzSlotAnchor)
|
|
306
384
|
node.__vmzSlotAnchor = null;
|
|
385
|
+
if (node.__vmzNamedSlotParked)
|
|
386
|
+
node.__vmzNamedSlotParked = null;
|
|
387
|
+
if (node.__vmzNamedSlotAnchor)
|
|
388
|
+
node.__vmzNamedSlotAnchor = null;
|
|
307
389
|
let child = node.firstChild;
|
|
308
390
|
while (child) {
|
|
309
391
|
const next = child.nextSibling;
|
|
@@ -83,4 +83,13 @@ export declare const directApi: {
|
|
|
83
83
|
* Uses {@link findOwnedDefaultSlot} so nested Button/Link slots are not stolen.
|
|
84
84
|
*/
|
|
85
85
|
projectDefaultSlot(hostEl: any, node: any): void;
|
|
86
|
+
/**
|
|
87
|
+
* Project parent children into a nested Direct component's `<slot name="…">`.
|
|
88
|
+
*
|
|
89
|
+
* Named projections are name-only (scope-slot values are not passed yet). The
|
|
90
|
+
* outlet search stops at nested component hosts, so a `header` projection never
|
|
91
|
+
* steals a nested Button's own named slot. A name with no outlet parks under that
|
|
92
|
+
* name — it must not fall through into the default slot.
|
|
93
|
+
*/
|
|
94
|
+
projectNamedSlot(hostEl: any, name: any, node: any): void;
|
|
86
95
|
};
|
|
@@ -2,7 +2,7 @@ import { mergeClassParts, mergeStyleParts } from '../../shared/dom-attr-normaliz
|
|
|
2
2
|
import { applyDirectHostBox } from '../direct-host-box.js';
|
|
3
3
|
import { createUnknownComponentElement } from '../unknown-component.js';
|
|
4
4
|
import { applyDomAttr, eventPropHandlerName } from './attributes.js';
|
|
5
|
-
import { createInstance, disposeDomTree, findOwnedDefaultSlotTarget, insertProjectedSlotNode, parkSlotNode, runDirectCreate, slotAnchorFor, stripFns, } from './component.js';
|
|
5
|
+
import { createInstance, disposeDomTree, findOwnedDefaultSlotTarget, findOwnedNamedSlot, insertProjectedSlotNode, parkSlotNode, runDirectCreate, slotAnchorFor, stripFns, } from './component.js';
|
|
6
6
|
import { isEventEntryStrategy, noteDomCreate, noteDomRemove, scheduleClientOn } from './lifecycle.js';
|
|
7
7
|
import { components, resolveComponent } from './registry.js';
|
|
8
8
|
import { beginEventFlush, endEventFlush, inferHandlerMethod, methodAllowsSyncEventFlush, registerBind, runDomEventHandler, runPatch, scheduleRefresh, unregisterBind, } from '../reactivity/patch-scheduler.js';
|
|
@@ -419,11 +419,17 @@ export const directApi = {
|
|
|
419
419
|
// Enter the host's private child pool so nested create cannot see uncle siblings.
|
|
420
420
|
const adopt = directApi._resumeAdopt;
|
|
421
421
|
const entered = adopt && typeof adopt.enter === 'function' ? adopt.enter(host) : false;
|
|
422
|
+
// A child nested inside the parent's `v-for` must not feed the parent item
|
|
423
|
+
// patch sink: those patches belong to the child instance (`this`) and the
|
|
424
|
+
// parent's each replays them with the parent host, wiping child output.
|
|
425
|
+
const prevItemPatches = directApi._itemPatches;
|
|
426
|
+
directApi._itemPatches = null;
|
|
422
427
|
let node;
|
|
423
428
|
try {
|
|
424
429
|
node = runDirectCreate(Ctor, child);
|
|
425
430
|
}
|
|
426
431
|
finally {
|
|
432
|
+
directApi._itemPatches = prevItemPatches;
|
|
427
433
|
if (entered && adopt && typeof adopt.leave === 'function')
|
|
428
434
|
adopt.leave();
|
|
429
435
|
}
|
|
@@ -524,4 +530,41 @@ export const directApi = {
|
|
|
524
530
|
// the component root, where it would otherwise leak into the rendered page.
|
|
525
531
|
parkSlotNode(hostEl, node);
|
|
526
532
|
},
|
|
533
|
+
/**
|
|
534
|
+
* Project parent children into a nested Direct component's `<slot name="…">`.
|
|
535
|
+
*
|
|
536
|
+
* Named projections are name-only (scope-slot values are not passed yet). The
|
|
537
|
+
* outlet search stops at nested component hosts, so a `header` projection never
|
|
538
|
+
* steals a nested Button's own named slot. A name with no outlet parks under that
|
|
539
|
+
* name — it must not fall through into the default slot.
|
|
540
|
+
*/
|
|
541
|
+
projectNamedSlot(hostEl, name, node) {
|
|
542
|
+
if (!hostEl || node == null)
|
|
543
|
+
return;
|
|
544
|
+
const key = name == null ? '' : String(name);
|
|
545
|
+
if (!key || key === 'default') {
|
|
546
|
+
directApi.projectDefaultSlot(hostEl, node);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const child = hostEl.__vmzInst;
|
|
550
|
+
let root = (child && child.__vmzDomRoot) || hostEl;
|
|
551
|
+
if (!root || root.nodeType !== 1)
|
|
552
|
+
root = hostEl;
|
|
553
|
+
const target = root && root.nodeType === 1 ? findOwnedNamedSlot(root, key) : null;
|
|
554
|
+
if (target && target.parentNode) {
|
|
555
|
+
const anchors = hostEl.__vmzNamedSlotAnchor || (hostEl.__vmzNamedSlotAnchor = new Map());
|
|
556
|
+
anchors.set(key, insertProjectedSlotNode(target, node));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// The named `<slot>` was replaced by an earlier projected child: append after
|
|
560
|
+
// the last one for the same name instead of re-searching.
|
|
561
|
+
const anchors = hostEl.__vmzNamedSlotAnchor;
|
|
562
|
+
const anchor = anchors && anchors.get(key);
|
|
563
|
+
if (anchor && anchor.parentNode) {
|
|
564
|
+
anchors.set(key, slotAnchorFor(node));
|
|
565
|
+
anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
parkSlotNode(hostEl, node, key);
|
|
569
|
+
},
|
|
527
570
|
};
|
|
@@ -16,6 +16,7 @@ export { settlePendingChildMounts } from './dom/component.js';
|
|
|
16
16
|
export { runDirectCreate } from './dom/component.js';
|
|
17
17
|
export { findOwnedDefaultSlot } from './dom/component.js';
|
|
18
18
|
export { findOwnedDefaultSlotTarget } from './dom/component.js';
|
|
19
|
+
export { findOwnedNamedSlot } from './dom/component.js';
|
|
19
20
|
export { createInstance } from './dom/component.js';
|
|
20
21
|
export { destroy } from './dom/component.js';
|
|
21
22
|
export { disposeDomTree } from './dom/component.js';
|
package/dist/browser/dom-core.js
CHANGED
|
@@ -16,6 +16,7 @@ export { settlePendingChildMounts } from './dom/component.js';
|
|
|
16
16
|
export { runDirectCreate } from './dom/component.js';
|
|
17
17
|
export { findOwnedDefaultSlot } from './dom/component.js';
|
|
18
18
|
export { findOwnedDefaultSlotTarget } from './dom/component.js';
|
|
19
|
+
export { findOwnedNamedSlot } from './dom/component.js';
|
|
19
20
|
export { createInstance } from './dom/component.js';
|
|
20
21
|
export { destroy } from './dom/component.js';
|
|
21
22
|
export { disposeDomTree } from './dom/component.js';
|
package/dist/faces/server.js
CHANGED
|
@@ -447,6 +447,20 @@ function contentType(filePath, nodePath) {
|
|
|
447
447
|
return 'image/svg+xml';
|
|
448
448
|
case '.map':
|
|
449
449
|
return 'application/json; charset=utf-8';
|
|
450
|
+
case '.wasm':
|
|
451
|
+
return 'application/wasm';
|
|
452
|
+
case '.woff2':
|
|
453
|
+
return 'font/woff2';
|
|
454
|
+
case '.woff':
|
|
455
|
+
return 'font/woff';
|
|
456
|
+
case '.otf':
|
|
457
|
+
return 'font/otf';
|
|
458
|
+
case '.ttf':
|
|
459
|
+
return 'font/ttf';
|
|
460
|
+
case '.xml':
|
|
461
|
+
return 'application/xml';
|
|
462
|
+
case '.txt':
|
|
463
|
+
return 'text/plain; charset=utf-8';
|
|
450
464
|
default:
|
|
451
465
|
return 'application/octet-stream';
|
|
452
466
|
}
|
package/dist/ssr/serialize.js
CHANGED
|
@@ -307,6 +307,64 @@ function makeVirtualEl(tag) {
|
|
|
307
307
|
});
|
|
308
308
|
return node;
|
|
309
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Owned `<slot>` outlet in a serialize el tree.
|
|
312
|
+
*
|
|
313
|
+
* `name === null` matches the default outlet (no `name` attr). Named lookups require
|
|
314
|
+
* an exact match and stop at nested component shells (`data-vmz`), mirroring the
|
|
315
|
+
* browser runtime ownership boundary.
|
|
316
|
+
*/
|
|
317
|
+
function findOwnedSerializeSlot(n, name) {
|
|
318
|
+
if (!n || typeof n !== 'object')
|
|
319
|
+
return null;
|
|
320
|
+
if (n.__kind === 'frag') {
|
|
321
|
+
for (const c of n.children || []) {
|
|
322
|
+
const hit = findOwnedSerializeSlot(c, name);
|
|
323
|
+
if (hit)
|
|
324
|
+
return hit;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
if (n.__kind !== 'el')
|
|
329
|
+
return null;
|
|
330
|
+
if (n.tag === 'slot') {
|
|
331
|
+
const outletName = n.attrs && n.attrs.name != null ? String(n.attrs.name) : '';
|
|
332
|
+
if (name == null ? !outletName : outletName === name)
|
|
333
|
+
return n;
|
|
334
|
+
}
|
|
335
|
+
for (const c of n.children || []) {
|
|
336
|
+
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
337
|
+
continue;
|
|
338
|
+
const hit = findOwnedSerializeSlot(c, name);
|
|
339
|
+
if (hit)
|
|
340
|
+
return hit;
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
function findOwnedSerializeSlotTarget(n, name) {
|
|
345
|
+
const slot = findOwnedSerializeSlot(n, name);
|
|
346
|
+
if (slot)
|
|
347
|
+
return slot;
|
|
348
|
+
if (name != null)
|
|
349
|
+
return null;
|
|
350
|
+
if (!n || n.__kind !== 'el')
|
|
351
|
+
return null;
|
|
352
|
+
const tag = String(n.tag || '').toLowerCase();
|
|
353
|
+
if ((tag === 'button' || tag === 'a') && n.attrs && n.attrs['data-vmz-ui'] === 'button')
|
|
354
|
+
return n;
|
|
355
|
+
if (n.attrs && n.attrs['data-vmz'] != null)
|
|
356
|
+
return null;
|
|
357
|
+
for (const c of n.children || []) {
|
|
358
|
+
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
359
|
+
continue;
|
|
360
|
+
const hit = findOwnedSerializeSlotTarget(c, name);
|
|
361
|
+
if (hit)
|
|
362
|
+
return hit;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
/** Outlets that already received projected content (fallback children dropped once). */
|
|
367
|
+
const filledSerializeSlots = new WeakSet();
|
|
310
368
|
const serializeApi = {
|
|
311
369
|
_inst: null,
|
|
312
370
|
_branchBinds: null,
|
|
@@ -476,69 +534,25 @@ const serializeApi = {
|
|
|
476
534
|
return;
|
|
477
535
|
// serializeApi.component returns a serialize el tree (or island shell).
|
|
478
536
|
const root = hostEl.__kind === 'el' ? hostEl : null;
|
|
479
|
-
const findOwnedSlot = (n) => {
|
|
480
|
-
if (!n || typeof n !== 'object')
|
|
481
|
-
return null;
|
|
482
|
-
if (n.__kind === 'frag') {
|
|
483
|
-
for (const c of n.children || []) {
|
|
484
|
-
const hit = findOwnedSlot(c);
|
|
485
|
-
if (hit)
|
|
486
|
-
return hit;
|
|
487
|
-
}
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
490
|
-
if (n.__kind !== 'el')
|
|
491
|
-
return null;
|
|
492
|
-
if (n.tag === 'slot' && !(n.attrs && n.attrs.name))
|
|
493
|
-
return n;
|
|
494
|
-
for (const c of n.children || []) {
|
|
495
|
-
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
496
|
-
continue;
|
|
497
|
-
const hit = findOwnedSlot(c);
|
|
498
|
-
if (hit)
|
|
499
|
-
return hit;
|
|
500
|
-
}
|
|
501
|
-
return null;
|
|
502
|
-
};
|
|
503
|
-
const findOwnedSlotTarget = (n) => {
|
|
504
|
-
const slot = findOwnedSlot(n);
|
|
505
|
-
if (slot)
|
|
506
|
-
return slot;
|
|
507
|
-
if (!n || n.__kind !== 'el')
|
|
508
|
-
return null;
|
|
509
|
-
const tag = String(n.tag || '').toLowerCase();
|
|
510
|
-
if ((tag === 'button' || tag === 'a') && n.attrs && n.attrs['data-vmz-ui'] === 'button')
|
|
511
|
-
return n;
|
|
512
|
-
if (n.attrs && n.attrs['data-vmz'] != null)
|
|
513
|
-
return null;
|
|
514
|
-
for (const c of n.children || []) {
|
|
515
|
-
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
516
|
-
continue;
|
|
517
|
-
const hit = findOwnedSlotTarget(c);
|
|
518
|
-
if (hit)
|
|
519
|
-
return hit;
|
|
520
|
-
}
|
|
521
|
-
return null;
|
|
522
|
-
};
|
|
523
537
|
// Prefer searching the component body (first child of host wrapper).
|
|
524
538
|
let target = null;
|
|
525
539
|
if (root) {
|
|
526
540
|
for (const c of root.children || []) {
|
|
527
541
|
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
528
542
|
continue;
|
|
529
|
-
target =
|
|
543
|
+
target = findOwnedSerializeSlotTarget(c, null);
|
|
530
544
|
if (target)
|
|
531
545
|
break;
|
|
532
546
|
}
|
|
533
547
|
if (!target)
|
|
534
|
-
target =
|
|
548
|
+
target = findOwnedSerializeSlotTarget(root, null);
|
|
535
549
|
}
|
|
536
550
|
if (target) {
|
|
537
551
|
if (target.tag === 'slot') {
|
|
538
552
|
target.__rawHtml = null;
|
|
539
553
|
if (!Array.isArray(target.children))
|
|
540
554
|
target.children = [];
|
|
541
|
-
// Append
|
|
555
|
+
// Append — multiple projectDefaultSlot calls must accumulate (SSR).
|
|
542
556
|
// Client path replaces the live <slot> then appends siblings; serialize must push.
|
|
543
557
|
target.children.push(node);
|
|
544
558
|
return;
|
|
@@ -552,6 +566,46 @@ const serializeApi = {
|
|
|
552
566
|
// mounted (closed overlay, loading skeleton). Mirror the browser runtime and
|
|
553
567
|
// drop them instead of painting them into the component root.
|
|
554
568
|
},
|
|
569
|
+
/**
|
|
570
|
+
* Project parent children into a nested component's `<slot name="…">` outlet.
|
|
571
|
+
*
|
|
572
|
+
* Named projections are name-only (scope-slot values are not passed yet). Only an
|
|
573
|
+
* outlet with the exact `name` accepts the node — a missing name drops it instead
|
|
574
|
+
* of landing in the default slot. Fallback children stop rendering once the outlet
|
|
575
|
+
* has projected content.
|
|
576
|
+
*/
|
|
577
|
+
projectNamedSlot(hostEl, name, node) {
|
|
578
|
+
if (!hostEl || node == null)
|
|
579
|
+
return;
|
|
580
|
+
const key = name == null ? '' : String(name);
|
|
581
|
+
if (!key || key === 'default') {
|
|
582
|
+
serializeApi.projectDefaultSlot(hostEl, node);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const root = hostEl.__kind === 'el' ? hostEl : null;
|
|
586
|
+
let target = null;
|
|
587
|
+
if (root) {
|
|
588
|
+
for (const c of root.children || []) {
|
|
589
|
+
if (c && c.__kind === 'el' && c.attrs && c.attrs['data-vmz'] != null)
|
|
590
|
+
continue;
|
|
591
|
+
target = findOwnedSerializeSlotTarget(c, key);
|
|
592
|
+
if (target)
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
if (!target)
|
|
596
|
+
target = findOwnedSerializeSlotTarget(root, key);
|
|
597
|
+
}
|
|
598
|
+
if (!target)
|
|
599
|
+
return;
|
|
600
|
+
target.__rawHtml = null;
|
|
601
|
+
if (!Array.isArray(target.children))
|
|
602
|
+
target.children = [];
|
|
603
|
+
if (!filledSerializeSlots.has(target)) {
|
|
604
|
+
filledSerializeSlots.add(target);
|
|
605
|
+
target.children.length = 0;
|
|
606
|
+
}
|
|
607
|
+
target.children.push(node);
|
|
608
|
+
},
|
|
555
609
|
setHtml(el, value) {
|
|
556
610
|
if (!el || el.__kind !== 'el')
|
|
557
611
|
return;
|
|
@@ -638,7 +692,13 @@ const serializeApi = {
|
|
|
638
692
|
const child = serializeApi._ssrChildInstance(Ctor, resolved);
|
|
639
693
|
if (Ctor.__vmzDirect && typeof Ctor.__vmzCreate === 'function') {
|
|
640
694
|
const prev = serializeApi._inst;
|
|
695
|
+
// A child nested inside the parent's `v-for` must not feed the parent
|
|
696
|
+
// item patch sink: those patches belong to the child instance (`this`).
|
|
697
|
+
// Replaying them with the parent's each host clears child output
|
|
698
|
+
// (innerHTML / text) that was already correct at create time.
|
|
699
|
+
const prevItemPatches = serializeApi._itemPatches;
|
|
641
700
|
serializeApi._inst = child;
|
|
701
|
+
serializeApi._itemPatches = null;
|
|
642
702
|
try {
|
|
643
703
|
const node = Ctor.__vmzCreate.call(child, serializeApi);
|
|
644
704
|
const attrs = { 'data-vmz': name };
|
|
@@ -658,6 +718,7 @@ const serializeApi = {
|
|
|
658
718
|
}
|
|
659
719
|
finally {
|
|
660
720
|
serializeApi._inst = prev;
|
|
721
|
+
serializeApi._itemPatches = prevItemPatches;
|
|
661
722
|
}
|
|
662
723
|
}
|
|
663
724
|
throw new Error(`vmz:dom serialize component <${name}> requires __vmzCreate (rebuild child with Direct)`);
|
package/package.json
CHANGED
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
"main": "./dist/faces/server.js",
|
|
69
69
|
"name": "@vmz/core",
|
|
70
70
|
"optionalDependencies": {
|
|
71
|
-
"@vmz/vmz-darwin-arm64": "0.3.
|
|
72
|
-
"@vmz/vmz-darwin-x64": "0.3.
|
|
73
|
-
"@vmz/vmz-linux-arm64": "0.3.
|
|
74
|
-
"@vmz/vmz-linux-x64": "0.3.
|
|
75
|
-
"@vmz/vmz-win32-arm64": "0.3.
|
|
76
|
-
"@vmz/vmz-win32-x64": "0.3.
|
|
71
|
+
"@vmz/vmz-darwin-arm64": "0.3.12",
|
|
72
|
+
"@vmz/vmz-darwin-x64": "0.3.12",
|
|
73
|
+
"@vmz/vmz-linux-arm64": "0.3.12",
|
|
74
|
+
"@vmz/vmz-linux-x64": "0.3.12",
|
|
75
|
+
"@vmz/vmz-win32-arm64": "0.3.12",
|
|
76
|
+
"@vmz/vmz-win32-x64": "0.3.12"
|
|
77
77
|
},
|
|
78
78
|
"publishConfig": {
|
|
79
79
|
"access": "public"
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
},
|
|
89
89
|
"type": "module",
|
|
90
90
|
"types": "./dist/faces/index.d.ts",
|
|
91
|
-
"version": "0.3.
|
|
91
|
+
"version": "0.3.12"
|
|
92
92
|
}
|