@vmz/core 0.3.7 → 0.3.9
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/dom/component.d.ts +6 -0
- package/dist/browser/dom/component.js +15 -5
- package/dist/browser/dom/direct-api.d.ts +2 -0
- package/dist/browser/dom/direct-api.js +41 -5
- package/dist/browser/dom-core.types.d.ts +1 -1
- package/dist/browser/resume/adopt.js +24 -23
- package/dist/host/serve/page-render.js +3 -3
- package/dist/ssr/document.js +6 -2
- package/package.json +7 -7
|
@@ -5,6 +5,12 @@ export declare function findOwnedDefaultSlot(root: any): any;
|
|
|
5
5
|
export declare function findOwnedDefaultSlotTarget(root: any): any;
|
|
6
6
|
export declare function createInstance(Component: any, props?: any): any;
|
|
7
7
|
export declare function destroy(inst: any): void;
|
|
8
|
+
/**
|
|
9
|
+
* DocumentFragment anchors are emptied by insertion (children move out, the
|
|
10
|
+
* fragment keeps no parentNode), so a projected fragment must anchor on its
|
|
11
|
+
* last child. Capture before inserting.
|
|
12
|
+
*/
|
|
13
|
+
export declare function slotAnchorFor(node: any): any;
|
|
8
14
|
export declare function insertProjectedSlotNode(target: any, node: any): any;
|
|
9
15
|
/**
|
|
10
16
|
* Hold projected children until the component's default slot exists.
|
|
@@ -183,15 +183,24 @@ export function destroy(inst) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* DocumentFragment anchors are emptied by insertion (children move out, the
|
|
188
|
+
* fragment keeps no parentNode), so a projected fragment must anchor on its
|
|
189
|
+
* last child. Capture before inserting.
|
|
190
|
+
*/
|
|
191
|
+
export function slotAnchorFor(node) {
|
|
192
|
+
return node && node.nodeType === 11 ? node.lastChild : node;
|
|
193
|
+
}
|
|
186
194
|
export function insertProjectedSlotNode(target, node) {
|
|
187
195
|
if (!target || !node)
|
|
188
196
|
return null;
|
|
197
|
+
const anchor = slotAnchorFor(node);
|
|
189
198
|
if (String(target.tagName || '').toLowerCase() === 'slot') {
|
|
190
199
|
target.replaceWith(node);
|
|
191
|
-
return
|
|
200
|
+
return anchor;
|
|
192
201
|
}
|
|
193
202
|
target.appendChild(node);
|
|
194
|
-
return
|
|
203
|
+
return anchor;
|
|
195
204
|
}
|
|
196
205
|
/**
|
|
197
206
|
* Hold projected children until the component's default slot exists.
|
|
@@ -245,23 +254,24 @@ export function flushParkedSlotNodes(hostEl) {
|
|
|
245
254
|
hostEl.__vmzSlotObserver = null;
|
|
246
255
|
}
|
|
247
256
|
hostEl.__vmzSlotParked = [];
|
|
257
|
+
const anchor = slotAnchorFor(parked[parked.length - 1]);
|
|
248
258
|
const tag = String(target.tagName || '').toLowerCase();
|
|
249
259
|
if (tag === 'slot') {
|
|
250
260
|
const frag = typeof document !== 'undefined' ? document.createDocumentFragment() : null;
|
|
251
261
|
if (!frag) {
|
|
252
262
|
target.replaceWith(parked[0]);
|
|
253
|
-
hostEl.__vmzSlotAnchor =
|
|
263
|
+
hostEl.__vmzSlotAnchor = anchor;
|
|
254
264
|
return true;
|
|
255
265
|
}
|
|
256
266
|
for (const node of parked)
|
|
257
267
|
frag.appendChild(node);
|
|
258
268
|
target.replaceWith(frag);
|
|
259
|
-
hostEl.__vmzSlotAnchor =
|
|
269
|
+
hostEl.__vmzSlotAnchor = anchor;
|
|
260
270
|
return true;
|
|
261
271
|
}
|
|
262
272
|
for (const node of parked)
|
|
263
273
|
target.appendChild(node);
|
|
264
|
-
hostEl.__vmzSlotAnchor =
|
|
274
|
+
hostEl.__vmzSlotAnchor = anchor;
|
|
265
275
|
return true;
|
|
266
276
|
}
|
|
267
277
|
export function disposeDomTree(root) {
|
|
@@ -13,6 +13,8 @@ export declare const directApi: {
|
|
|
13
13
|
el(tag: any): any;
|
|
14
14
|
elNS(ns: any, tag: any): Element;
|
|
15
15
|
text(value: any): Text;
|
|
16
|
+
nsScopeDepth(): number;
|
|
17
|
+
rewindNsScope(depth: any): void;
|
|
16
18
|
adoptEnter(node: any): boolean;
|
|
17
19
|
adoptLeave(): void;
|
|
18
20
|
frag(): DocumentFragment;
|
|
@@ -2,11 +2,34 @@ 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, stripFns, } from './component.js';
|
|
5
|
+
import { createInstance, disposeDomTree, findOwnedDefaultSlotTarget, 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';
|
|
9
9
|
let directPropBindSeq = 0;
|
|
10
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
11
|
+
/**
|
|
12
|
+
* Nesting depth of inline `<svg>` subtrees during create/resume.
|
|
13
|
+
*
|
|
14
|
+
* Template emit creates every element through `api.el(tag)` and brackets each
|
|
15
|
+
* subtree with `adoptEnter`/`adoptLeave`. Elements inside `<svg>` must be built
|
|
16
|
+
* with `createElementNS`, otherwise the browser returns an HTMLUnknownElement:
|
|
17
|
+
* attribute names lose their case (`viewBox` → `viewbox`) and the node lays out
|
|
18
|
+
* at 0×0. Track the depth here so the generator stays namespace-agnostic.
|
|
19
|
+
*/
|
|
20
|
+
const nsStack = [];
|
|
21
|
+
function inSvgScope() {
|
|
22
|
+
return nsStack.length > 0 && nsStack[nsStack.length - 1] === true;
|
|
23
|
+
}
|
|
24
|
+
function isSvgNode(node) {
|
|
25
|
+
if (!node || typeof node !== 'object')
|
|
26
|
+
return false;
|
|
27
|
+
const ns = node.namespaceURI;
|
|
28
|
+
if (ns === SVG_NS)
|
|
29
|
+
return true;
|
|
30
|
+
const tag = node.tagName;
|
|
31
|
+
return tag != null && String(tag).toLowerCase() === 'svg' && ns == null;
|
|
32
|
+
}
|
|
10
33
|
export const directApi = {
|
|
11
34
|
_inst: null,
|
|
12
35
|
_branchBinds: null,
|
|
@@ -14,17 +37,19 @@ export const directApi = {
|
|
|
14
37
|
_eachCtx: null,
|
|
15
38
|
_resumeAdopt: null,
|
|
16
39
|
el(tag) {
|
|
40
|
+
const name = tag || 'div';
|
|
41
|
+
const svg = inSvgScope() || name === 'svg';
|
|
17
42
|
if (directApi._resumeAdopt)
|
|
18
|
-
return directApi._resumeAdopt.el(
|
|
43
|
+
return directApi._resumeAdopt.el(name, svg);
|
|
19
44
|
noteDomCreate();
|
|
20
|
-
return document.
|
|
45
|
+
return svg ? document.createElementNS(SVG_NS, name) : document.createElement(name);
|
|
21
46
|
},
|
|
22
47
|
elNS(ns, tag) {
|
|
23
48
|
if (directApi._resumeAdopt && typeof directApi._resumeAdopt.elNS === 'function') {
|
|
24
49
|
return directApi._resumeAdopt.elNS(ns, tag);
|
|
25
50
|
}
|
|
26
51
|
noteDomCreate();
|
|
27
|
-
return document.createElementNS(ns ||
|
|
52
|
+
return document.createElementNS(ns || SVG_NS, tag || 'g');
|
|
28
53
|
},
|
|
29
54
|
text(value) {
|
|
30
55
|
if (directApi._resumeAdopt)
|
|
@@ -32,7 +57,16 @@ export const directApi = {
|
|
|
32
57
|
noteDomCreate();
|
|
33
58
|
return document.createTextNode(value == null ? '' : String(value));
|
|
34
59
|
},
|
|
60
|
+
nsScopeDepth() {
|
|
61
|
+
return nsStack.length;
|
|
62
|
+
},
|
|
63
|
+
rewindNsScope(depth) {
|
|
64
|
+
const target = Math.max(0, Number(depth) || 0);
|
|
65
|
+
while (nsStack.length > target)
|
|
66
|
+
nsStack.pop();
|
|
67
|
+
},
|
|
35
68
|
adoptEnter(node) {
|
|
69
|
+
nsStack.push(isSvgNode(node));
|
|
36
70
|
const adopt = directApi._resumeAdopt;
|
|
37
71
|
if (adopt && typeof adopt.enter === 'function') {
|
|
38
72
|
const ok = adopt.enter(node);
|
|
@@ -43,6 +77,7 @@ export const directApi = {
|
|
|
43
77
|
return false;
|
|
44
78
|
},
|
|
45
79
|
adoptLeave() {
|
|
80
|
+
nsStack.pop();
|
|
46
81
|
const adopt = directApi._resumeAdopt;
|
|
47
82
|
// Emit always pairs leave after enter; if enter failed (fresh node, no
|
|
48
83
|
// pool), do not pop ??unbalanced leave previously stole parent scopes
|
|
@@ -438,8 +473,9 @@ export const directApi = {
|
|
|
438
473
|
// call appends after the last one instead of re-searching for a slot.
|
|
439
474
|
const anchor = hostEl.__vmzSlotAnchor;
|
|
440
475
|
if (anchor && anchor.parentNode) {
|
|
476
|
+
const nextAnchor = slotAnchorFor(node);
|
|
441
477
|
anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
442
|
-
hostEl.__vmzSlotAnchor =
|
|
478
|
+
hostEl.__vmzSlotAnchor = nextAnchor;
|
|
443
479
|
return;
|
|
444
480
|
}
|
|
445
481
|
// Component slot may live inside an unmounted `v-if` branch (closed overlay,
|
|
@@ -69,7 +69,7 @@ export type TraceBuffer = {
|
|
|
69
69
|
events: TraceEvent[];
|
|
70
70
|
};
|
|
71
71
|
export type ResumeAdoptCtx = {
|
|
72
|
-
el: (tag: string) => Element;
|
|
72
|
+
el: (tag: string, svg?: boolean) => Element;
|
|
73
73
|
elNS?: (ns: string, tag: string) => Element;
|
|
74
74
|
text: (value?: unknown) => Text;
|
|
75
75
|
componentHost?: (name: string) => HTMLElement | null;
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
import { directApi, noteDomCreate, runDirectCreate, } from '../dom-core.js';
|
|
2
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
2
3
|
function createResumeAdopt(pool, rootEl) {
|
|
3
4
|
const used = new WeakSet();
|
|
4
5
|
const scopeStack = [pool];
|
|
5
|
-
const walkTexts = (root, visit) => {
|
|
6
|
-
if (!root)
|
|
7
|
-
return;
|
|
8
|
-
if (root.nodeType === 3) {
|
|
9
|
-
visit(root);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (root.nodeType !== 1)
|
|
13
|
-
return;
|
|
14
|
-
for (let c = root.firstChild; c; c = c.nextSibling)
|
|
15
|
-
walkTexts(c, visit);
|
|
16
|
-
};
|
|
17
6
|
const currentScope = () => scopeStack[scopeStack.length - 1] || pool;
|
|
18
7
|
const markElement = (n) => {
|
|
19
8
|
used.add(n);
|
|
@@ -42,27 +31,36 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
42
31
|
}
|
|
43
32
|
}
|
|
44
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Text bindings always belong to the element whose scope is active, so only
|
|
36
|
+
* direct text children are candidates. Recursing into element children
|
|
37
|
+
* lets an earlier binding steal the text of a sibling that has not been
|
|
38
|
+
* adopted yet (soft navigation lost the landing CTA label this way).
|
|
39
|
+
*/
|
|
45
40
|
function* textCandidates() {
|
|
46
41
|
const scope = currentScope();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
walkTexts(n, (t) => {
|
|
51
|
-
if (!used.has(t))
|
|
52
|
-
found.push(t);
|
|
53
|
-
});
|
|
54
|
-
for (const t of found)
|
|
55
|
-
yield t;
|
|
42
|
+
for (const n of [...scope.childNodes]) {
|
|
43
|
+
if (n.nodeType === 3 && !used.has(n))
|
|
44
|
+
yield n;
|
|
56
45
|
}
|
|
57
46
|
}
|
|
58
|
-
const adoptElement = (tag) => {
|
|
47
|
+
const adoptElement = (tag, svg = false) => {
|
|
59
48
|
const want = String(tag || 'div').toLowerCase();
|
|
60
49
|
for (const el of elementCandidates()) {
|
|
61
50
|
if (String(el.tagName).toLowerCase() === want)
|
|
62
51
|
return markElement(el);
|
|
63
52
|
}
|
|
64
53
|
noteDomCreate();
|
|
65
|
-
return document.createElement(tag || 'div');
|
|
54
|
+
return svg ? document.createElementNS(SVG_NS, tag || 'svg') : document.createElement(tag || 'div');
|
|
55
|
+
};
|
|
56
|
+
const adoptElementNS = (ns, tag) => {
|
|
57
|
+
const want = String(tag || 'g');
|
|
58
|
+
for (const el of elementCandidates()) {
|
|
59
|
+
if (el.namespaceURI === ns && String(el.localName) === want)
|
|
60
|
+
return markElement(el);
|
|
61
|
+
}
|
|
62
|
+
noteDomCreate();
|
|
63
|
+
return document.createElementNS(ns || SVG_NS, want);
|
|
66
64
|
};
|
|
67
65
|
const adoptText = (value) => {
|
|
68
66
|
for (const n of textCandidates()) {
|
|
@@ -125,6 +123,7 @@ function createResumeAdopt(pool, rootEl) {
|
|
|
125
123
|
};
|
|
126
124
|
return {
|
|
127
125
|
el: adoptElement,
|
|
126
|
+
elNS: adoptElementNS,
|
|
128
127
|
text: adoptText,
|
|
129
128
|
componentHost,
|
|
130
129
|
enter,
|
|
@@ -148,6 +147,7 @@ export function runDirectResume(Component, inst, container) {
|
|
|
148
147
|
const prevItems = directApi._itemPatches;
|
|
149
148
|
const prevEach = directApi._eachCtx;
|
|
150
149
|
const prevAdopt = directApi._resumeAdopt;
|
|
150
|
+
const prevNsDepth = directApi.nsScopeDepth();
|
|
151
151
|
directApi._inst = inst;
|
|
152
152
|
directApi._branchBinds = null;
|
|
153
153
|
directApi._itemPatches = null;
|
|
@@ -157,6 +157,7 @@ export function runDirectResume(Component, inst, container) {
|
|
|
157
157
|
return Component.__vmzCreate.call(inst, directApi);
|
|
158
158
|
}
|
|
159
159
|
finally {
|
|
160
|
+
directApi.rewindNsScope(prevNsDepth);
|
|
160
161
|
directApi._resumeAdopt = prevAdopt;
|
|
161
162
|
directApi._inst = prevInst;
|
|
162
163
|
directApi._branchBinds = prevBranch;
|
|
@@ -222,11 +222,11 @@ async function* emitAccessShell(marker) {
|
|
|
222
222
|
bodyAttrs: [],
|
|
223
223
|
});
|
|
224
224
|
}
|
|
225
|
-
function resolvePageDocumentMeta(Page) {
|
|
225
|
+
function resolvePageDocumentMeta(Page, ctx = {}) {
|
|
226
226
|
try {
|
|
227
227
|
let raw = {};
|
|
228
228
|
if (typeof Page?.meta === 'function')
|
|
229
|
-
raw = Page.meta() || {};
|
|
229
|
+
raw = Page.meta(ctx) || {};
|
|
230
230
|
else if (Page?.meta && typeof Page.meta === 'object')
|
|
231
231
|
raw = Page.meta;
|
|
232
232
|
const title = String(raw.title || '').trim();
|
|
@@ -349,7 +349,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
349
349
|
htmlExtraAttrs.push('data-vmz-locale-hrefs', JSON.stringify(hrefTable));
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
const pageDocMeta = resolvePageDocumentMeta(Page);
|
|
352
|
+
const pageDocMeta = resolvePageDocumentMeta(Page, { localeId });
|
|
353
353
|
const prevLocaleHint = globalThis.__vmzLocaleIdHint;
|
|
354
354
|
globalThis.__vmzLocaleIdHint = localeId;
|
|
355
355
|
if (!serveState.ssrRenderHost) {
|
package/dist/ssr/document.js
CHANGED
|
@@ -58,8 +58,12 @@ export function ensureSsrDocument() {
|
|
|
58
58
|
const detail = errors.length ? `\n${errors.join('\n')}` : '';
|
|
59
59
|
throw new Error(`linkedom unresolved for SSR document${detail}`);
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// SSR host contract: expose `document` only. Installing the linkedom
|
|
62
|
+
// `window` would make `typeof window !== "undefined"` true on the server
|
|
63
|
+
// while still having no browser `location`, so client code would take
|
|
64
|
+
// browser branches and diverge from the browser lifecycle. The window is
|
|
65
|
+
// still reachable as `document.defaultView` when DOM APIs need it.
|
|
66
|
+
const { document } = parseHTML('<!DOCTYPE html><html><body></body></html>');
|
|
63
67
|
globalThis.document = document;
|
|
64
68
|
_ssrDocumentLastError = null;
|
|
65
69
|
return typeof document.createElement === 'function';
|
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.9",
|
|
72
|
+
"@vmz/vmz-darwin-x64": "0.3.9",
|
|
73
|
+
"@vmz/vmz-linux-arm64": "0.3.9",
|
|
74
|
+
"@vmz/vmz-linux-x64": "0.3.9",
|
|
75
|
+
"@vmz/vmz-win32-arm64": "0.3.9",
|
|
76
|
+
"@vmz/vmz-win32-x64": "0.3.9"
|
|
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.9"
|
|
92
92
|
}
|