@vmz/core 0.0.4 → 0.1.1

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.
@@ -0,0 +1,4545 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * VMZ DOM client runtime — precise patches, no VDOM diff (SSR lives in dom-ssr).
4
+ *
5
+ *
6
+ * Direct components expose `__vmzCreate` / `__vmzSerialize` / `__vmzPlan`.
7
+ * Mount and client patches run that same schedule (SSR/hydrate/resume in dom-ssr).
8
+ * Field writes only run registered dep patches — never re-create structure.
9
+ */
10
+ /** @type {Record<string, new (props?: object) => any>} */
11
+ const components = Object.create(null);
12
+ /**
13
+ * Precision lab counters (test / MCP / benchmarks — not a user API).
14
+ * Primary keys: BindingId (IR). `*ByDep` is transitional stable-string adapter.
15
+ */
16
+ const precision = {
17
+ enabled: false,
18
+ writes: 0,
19
+ bindingEvals: 0,
20
+ patchExecs: 0,
21
+ domCreates: 0,
22
+ domMoves: 0,
23
+ domRemoves: 0,
24
+ componentExecs: 0,
25
+ /** @type {Record<string, number>} */
26
+ writesByRoot: Object.create(null),
27
+ /** @type {Record<string, number>} */
28
+ bindingEvalsByDep: Object.create(null),
29
+ /** @type {Record<string, number>} */
30
+ patchesByDep: Object.create(null),
31
+ /** @type {Record<string, number>} BindingId → count */
32
+ bindingEvalsByBinding: Object.create(null),
33
+ /** @type {Record<string, number>} BindingId → count */
34
+ patchesByBinding: Object.create(null),
35
+ };
36
+ /** optional StableId event ring (enabled with precision or __vmzTraceEnable). */
37
+ const TRACE_CAP = 256;
38
+ /** @type {{ enabled: boolean, events: Array<{ kind: string, stableId: { kind: string, id: string }, dep?: string|null, t?: number, chunkId?: string|null }> }} */
39
+ const traceBuf = {
40
+ enabled: false,
41
+ events: [],
42
+ };
43
+ function pushTrace(kind, stableKind, stableId, dep = null) {
44
+ if (!traceBuf.enabled && !precision.enabled)
45
+ return;
46
+ traceBuf.events.push({
47
+ kind,
48
+ stableId: { kind: stableKind, id: String(stableId) },
49
+ dep: dep == null ? undefined : String(dep),
50
+ t: Date.now(),
51
+ });
52
+ if (traceBuf.events.length > TRACE_CAP) {
53
+ traceBuf.events.splice(0, traceBuf.events.length - TRACE_CAP);
54
+ }
55
+ }
56
+ function bumpMap(map, key, n = 1) {
57
+ if (key == null || key === '')
58
+ return;
59
+ map[key] = (map[key] || 0) + n;
60
+ }
61
+ /** @param {boolean} [on] */
62
+ export function __vmzPrecisionEnable(on = true) {
63
+ precision.enabled = !!on;
64
+ }
65
+ /** @param {boolean} [on] */
66
+ export function __vmzTraceEnable(on = true) {
67
+ traceBuf.enabled = !!on;
68
+ }
69
+ export function __vmzPrecisionReset() {
70
+ precision.writes = 0;
71
+ precision.bindingEvals = 0;
72
+ precision.patchExecs = 0;
73
+ precision.domCreates = 0;
74
+ precision.domMoves = 0;
75
+ precision.domRemoves = 0;
76
+ precision.componentExecs = 0;
77
+ precision.writesByRoot = Object.create(null);
78
+ precision.bindingEvalsByDep = Object.create(null);
79
+ precision.patchesByDep = Object.create(null);
80
+ precision.bindingEvalsByBinding = Object.create(null);
81
+ precision.patchesByBinding = Object.create(null);
82
+ }
83
+ export function __vmzTraceReset() {
84
+ traceBuf.events = [];
85
+ }
86
+ /**
87
+ * StableId event snapshot (`vmz.dx.trace.v0` shape without schema stamp —
88
+ * host may wrap via ingestRuntimeTrace).
89
+ * @returns {{ schema: string, events: typeof traceBuf.events, status: string }}
90
+ */
91
+ export function __vmzTraceSnapshot() {
92
+ const events = traceBuf.events.map((e) => ({ ...e, stableId: { ...e.stableId } }));
93
+ return {
94
+ schema: 'vmz.dx.trace.v0',
95
+ events,
96
+ status: events.length ? 'ready' : 'empty',
97
+ };
98
+ }
99
+ /** @returns {typeof precision} */
100
+ export function __vmzPrecisionSnapshot() {
101
+ return {
102
+ enabled: precision.enabled,
103
+ writes: precision.writes,
104
+ bindingEvals: precision.bindingEvals,
105
+ patchExecs: precision.patchExecs,
106
+ domCreates: precision.domCreates,
107
+ domMoves: precision.domMoves,
108
+ domRemoves: precision.domRemoves,
109
+ componentExecs: precision.componentExecs,
110
+ writesByRoot: { ...precision.writesByRoot },
111
+ bindingEvalsByDep: { ...precision.bindingEvalsByDep },
112
+ patchesByDep: { ...precision.patchesByDep },
113
+ bindingEvalsByBinding: { ...precision.bindingEvalsByBinding },
114
+ patchesByBinding: { ...precision.patchesByBinding },
115
+ };
116
+ }
117
+ /**
118
+ * @param {() => any} fn
119
+ * @param {string | null} [depKey]
120
+ * @param {number | string | null} [bindingId]
121
+ */
122
+ function runPatch(fn, depKey = null, bindingId = null) {
123
+ if (precision.enabled) {
124
+ precision.patchExecs++;
125
+ if (depKey)
126
+ bumpMap(precision.patchesByDep, depKey);
127
+ if (bindingId != null)
128
+ bumpMap(precision.patchesByBinding, String(bindingId));
129
+ }
130
+ if (bindingId != null) {
131
+ pushTrace('patch', 'binding', bindingId, depKey);
132
+ }
133
+ return fn();
134
+ }
135
+ export function noteDomCreate() {
136
+ if (precision.enabled)
137
+ precision.domCreates++;
138
+ }
139
+ function noteDomRemove() {
140
+ if (precision.enabled)
141
+ precision.domRemoves++;
142
+ }
143
+ function noteDomMove() {
144
+ if (precision.enabled)
145
+ precision.domMoves++;
146
+ }
147
+ /** @param {Record<string, any>} map */
148
+ export function registerComponents(map) {
149
+ Object.assign(components, map);
150
+ }
151
+ /** Sync registry lookup (SSR serialize / client). */
152
+ export function getRegisteredComponent(name) {
153
+ return components[name] || null;
154
+ }
155
+ /**
156
+ * Lazy component loader for EventEntry mixed packs (set by entry-client / entry-event).
157
+ * @param {string} name
158
+ * @returns {Promise<any>}
159
+ */
160
+ export async function resolveComponent(name) {
161
+ let Ctor = components[name];
162
+ if (!Ctor && typeof globalThis.__vmzLoadComponent === 'function') {
163
+ Ctor = await globalThis.__vmzLoadComponent(name);
164
+ if (Ctor)
165
+ registerComponents({ [name]: Ctor });
166
+ }
167
+ return Ctor || null;
168
+ }
169
+ /**
170
+ * Mount once; later updates are dep patches only (never re-run structure).
171
+ * Requires compiler `__vmzCreate` (production Direct emit — no blueprint fallback).
172
+ * @param {new (props?: object) => any} Component
173
+ * @param {Element} container
174
+ * @param {object} [props]
175
+ */
176
+ export async function mount(Component, container, props = {}) {
177
+ if (container.__vmzInst) {
178
+ destroy(container.__vmzInst);
179
+ container.__vmzInst = null;
180
+ }
181
+ const inst = createInstance(Component, props);
182
+ inst.__vmzBinders = Object.create(null);
183
+ inst.__vmzBindings = Object.create(null);
184
+ inst.__vmzDepToBindings = Object.create(null);
185
+ container.replaceChildren();
186
+ const node = await createFromComponent(Component, inst);
187
+ if (node) {
188
+ inst.__vmzDomRoot = node;
189
+ container.appendChild(node);
190
+ }
191
+ if (typeof inst.onMount === 'function') {
192
+ await inst.onMount();
193
+ }
194
+ await settlePendingChildMounts(inst);
195
+ container.__vmzInst = inst;
196
+ return inst;
197
+ }
198
+ /**
199
+ * Nested Direct `component` schedules child onMount asynchronously; drain before return
200
+ * so SSR/hydrate callers see post-mount DOM (e.g. UserCard Ada, not Loading…).
201
+ * @param {object} inst
202
+ */
203
+ export async function settlePendingChildMounts(inst) {
204
+ if (!inst || !Array.isArray(inst.__vmzPendingChildMounts) || !inst.__vmzPendingChildMounts.length)
205
+ return;
206
+ await Promise.all(inst.__vmzPendingChildMounts);
207
+ inst.__vmzPendingChildMounts = [];
208
+ const hosts = [];
209
+ const root = inst.__vmzDomRoot;
210
+ if (root && root.nodeType === 1) {
211
+ if (root.__vmzInst)
212
+ hosts.push(root.__vmzInst);
213
+ for (const el of root.querySelectorAll('[data-vmz]')) {
214
+ if (el.__vmzInst)
215
+ hosts.push(el.__vmzInst);
216
+ }
217
+ }
218
+ for (const child of hosts) {
219
+ await flushPending(child);
220
+ await settlePendingChildMounts(child);
221
+ }
222
+ }
223
+ /**
224
+ * Direct create only (production Direct emit).
225
+ * @param {new (props?: object) => any} Component
226
+ * @param {object} inst
227
+ */
228
+ async function createFromComponent(Component, inst) {
229
+ if (Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function') {
230
+ return runDirectCreate(Component, inst);
231
+ }
232
+ throw new Error(`vmz:dom mount requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
233
+ }
234
+ /**
235
+ * @param {new (props?: object) => any} Component
236
+ * @param {object} inst
237
+ */
238
+ export function runDirectCreate(Component, inst) {
239
+ // Nested component creates (e.g. Button inside parent ifBlock branch) must not
240
+ // leak bindAttr/bindText into the parent's `_branchBinds` / `_itemPatches` sink —
241
+ // that steals numeric BindingIds (0) and corrupts parent deps (density → type).
242
+ const prevInst = directApi._inst;
243
+ const prevBranch = directApi._branchBinds;
244
+ const prevItems = directApi._itemPatches;
245
+ const prevEach = directApi._eachCtx;
246
+ directApi._inst = inst;
247
+ directApi._branchBinds = null;
248
+ directApi._itemPatches = null;
249
+ directApi._eachCtx = null;
250
+ try {
251
+ return Component.__vmzCreate.call(inst, directApi);
252
+ }
253
+ finally {
254
+ directApi._inst = prevInst;
255
+ directApi._branchBinds = prevBranch;
256
+ directApi._itemPatches = prevItems;
257
+ directApi._eachCtx = prevEach;
258
+ }
259
+ }
260
+ /** Host API for compiler-emitted `__vmzCreate` (direct path, Program IR B). */
261
+ export const directApi = {
262
+ /** @type {object | null} */
263
+ _inst: null,
264
+ /** @type {Array<{ deps: string[], fn: => any, bindingId?: number|string|null }> | null} */
265
+ _branchBinds: null,
266
+ /** @type {Array< => void> | null} */
267
+ _itemPatches: null,
268
+ /**
269
+ * Active keyed-each context (/): item binds + event delegation.
270
+ * @type {null | {
271
+ * noteItemBind: (bindingId: number|string|null, deps: string[], fn: => void) => void,
272
+ * needDelegate: (type: string) => void,
273
+ * }}
274
+ */
275
+ _eachCtx: null,
276
+ el(tag) {
277
+ noteDomCreate();
278
+ return document.createElement(tag || 'div');
279
+ },
280
+ text(value) {
281
+ noteDomCreate();
282
+ return document.createTextNode(value == null ? '' : String(value));
283
+ },
284
+ frag() {
285
+ noteDomCreate();
286
+ return document.createDocumentFragment();
287
+ },
288
+ attr(el, name, value) {
289
+ applyDomAttr(el, name, value);
290
+ },
291
+ on(el, type, handler) {
292
+ const inst = directApi._inst;
293
+ if (directApi._eachCtx && typeof handler === 'function') {
294
+ /** @type {Record<string, Function>} */
295
+ const bag = el.__vmzEvt || (el.__vmzEvt = Object.create(null));
296
+ bag[type] = handler;
297
+ directApi._eachCtx.needDelegate(type);
298
+ return;
299
+ }
300
+ // Infer once at bind time — never Function#toString on the click hot path.
301
+ const methodHint = typeof handler === 'function' ? inferHandlerMethod(handler) : null;
302
+ if (methodHint && methodAllowsSyncEventFlush(inst, methodHint)) {
303
+ // Direct method bind: skip arrow wrapper + nested handler.call.
304
+ el.addEventListener(type, (ev) => {
305
+ if (type === 'submit' && ev && typeof ev.preventDefault === 'function') {
306
+ ev.preventDefault();
307
+ }
308
+ runDomEventHandler(inst, methodHint, () => {
309
+ const m = inst[methodHint];
310
+ if (typeof m === 'function')
311
+ return m.call(inst, ev);
312
+ return handler.call(inst, ev);
313
+ });
314
+ });
315
+ return;
316
+ }
317
+ el.addEventListener(type, (ev) => {
318
+ // Belt-and-suspenders: form submit must not navigate before handler runs.
319
+ if (type === 'submit' && ev && typeof ev.preventDefault === 'function') {
320
+ ev.preventDefault();
321
+ }
322
+ if (typeof handler === 'function') {
323
+ runDomEventHandler(inst, methodHint, () => handler.call(inst, ev));
324
+ }
325
+ });
326
+ },
327
+ /**
328
+ * Bind a named instance method (no arrow / Function#toString).
329
+ * Learns `skipFlush` after a sync invocation that schedules no dirty work
330
+ * (stride / transpose self-apply DOM).
331
+ * @param {Element} el
332
+ * @param {string} type
333
+ * @param {string} methodName
334
+ * @param {{ skipFlush?: boolean } | null | undefined} [opts]
335
+ */
336
+ onMethod(el, type, methodName, opts) {
337
+ const inst = directApi._inst;
338
+ let skipFlush = !!(opts && opts.skipFlush);
339
+ el.addEventListener(type, (ev) => {
340
+ if (type === 'submit' && ev && typeof ev.preventDefault === 'function') {
341
+ ev.preventDefault();
342
+ }
343
+ const m = inst[methodName];
344
+ if (typeof m !== 'function')
345
+ return;
346
+ if (skipFlush) {
347
+ m.call(inst, ev);
348
+ return;
349
+ }
350
+ beginEventFlush(inst);
351
+ try {
352
+ m.call(inst, ev);
353
+ }
354
+ finally {
355
+ const scheduled = !!inst.__vmzFlushScheduled;
356
+ endEventFlush(inst);
357
+ // Barrier-owned methods (stride/transpose) never schedule → skip frame next time.
358
+ if (!scheduled && methodAllowsSyncEventFlush(inst, methodName)) {
359
+ skipFlush = true;
360
+ }
361
+ }
362
+ });
363
+ },
364
+ /**
365
+ * @param {object} inst
366
+ * @param {number|string|null} bindingId
367
+ * @param {string[]} deps
368
+ * @param {() => any} get
369
+ * @param {Text} textNode
370
+ * @param {{ stable: string[], branches: Array<{ cond?: => any, deps: string[] }> } | null | undefined} [cf]
371
+ */
372
+ bindText(inst, bindingId, deps, get, textNode, cf) {
373
+ wireDirectBind(inst, bindingId, deps, get, (raw) => {
374
+ textNode.textContent = String(raw ?? '');
375
+ }, cf);
376
+ },
377
+ /**
378
+ * @param {object} inst
379
+ * @param {number|string|null} bindingId
380
+ * @param {string[]} deps
381
+ * @param {() => any} get
382
+ * @param {Element} el
383
+ * @param {string} name
384
+ * @param {{ stable: string[], branches: Array<{ cond?: => any, deps: string[] }> } | null | undefined} [cf]
385
+ */
386
+ bindAttr(inst, bindingId, deps, get, el, name, cf) {
387
+ wireDirectBind(inst, bindingId, deps, get, (raw) => {
388
+ if (name === 'class' || name === 'className') {
389
+ const s = String(raw ?? '');
390
+ if (s)
391
+ el.setAttribute('class', s);
392
+ else if (el.hasAttribute('class'))
393
+ el.removeAttribute('class');
394
+ }
395
+ else {
396
+ applyDomAttr(el, name, raw);
397
+ }
398
+ }, cf);
399
+ },
400
+ setHtml(el, value) {
401
+ el.innerHTML = value == null ? '' : String(value);
402
+ },
403
+ /**
404
+ * Trusted HTML binding (`html={expr}`). Author/plugin responsibility.
405
+ * @param {object} inst
406
+ * @param {number|string|null} bindingId
407
+ * @param {string[]} deps
408
+ * @param {() => any} get
409
+ * @param {Element} el
410
+ * @param {{ stable: string[], branches: Array<{ cond?: => any, deps: string[] }> } | null | undefined} [cf]
411
+ */
412
+ bindHtml(inst, bindingId, deps, get, el, cf) {
413
+ wireDirectBind(inst, bindingId, deps, get, (raw) => {
414
+ el.innerHTML = raw == null ? '' : String(raw);
415
+ }, cf);
416
+ },
417
+ /**
418
+ * Nested component (sync Direct child or island schedule).
419
+ * @param {object} hostInst
420
+ * @param {string} name
421
+ * @param {Record<string, any>} props
422
+ * @param {string | null} client
423
+ */
424
+ component(hostInst, name, props, client) {
425
+ noteDomCreate();
426
+ const host = document.createElement('div');
427
+ host.setAttribute('data-vmz', name);
428
+ /** @type {Record<string, any>} */
429
+ const resolved = {};
430
+ for (const [k, v] of Object.entries(props || {})) {
431
+ if (typeof v === 'function' && isEventPropName(k))
432
+ resolved[k] = v;
433
+ else if (typeof v === 'function')
434
+ resolved[k] = v.call(hostInst);
435
+ else
436
+ resolved[k] = v;
437
+ }
438
+ if (client) {
439
+ host.setAttribute('data-vmz-island', name);
440
+ host.setAttribute('data-vmz-client', String(client));
441
+ host.setAttribute('data-vmz-props', JSON.stringify(stripFns(resolved)));
442
+ if (isEventEntryStrategy(String(client))) {
443
+ host.setAttribute('data-vmz-entry', 'event');
444
+ }
445
+ // resume: resume on schedule; EventEntry may lazy-load chunk via __vmzLoadComponent.
446
+ scheduleClientOn(host, String(client), async () => {
447
+ const Ctor = await resolveComponent(name);
448
+ if (!Ctor)
449
+ throw new Error(`vmz:dom unknown component <${name} />`);
450
+ await resume(Ctor, host, { props: resolved, state: {} });
451
+ });
452
+ return host;
453
+ }
454
+ const Ctor = components[name];
455
+ if (!Ctor)
456
+ throw new Error(`vmz:dom unknown component <${name} />`);
457
+ const child = createInstance(Ctor, resolved);
458
+ if (!(Ctor.__vmzDirect && typeof Ctor.__vmzCreate === 'function')) {
459
+ throw new Error(`vmz:dom direct component <${name}> requires __vmzCreate (rebuild child with Direct)`);
460
+ }
461
+ const node = runDirectCreate(Ctor, child);
462
+ if (node) {
463
+ child.__vmzDomRoot = node;
464
+ host.appendChild(node);
465
+ }
466
+ host.__vmzInst = child;
467
+ if (typeof child.onMount === 'function') {
468
+ const pending = Promise.resolve().then(() => {
469
+ if (!child.__vmzDestroyed)
470
+ return child.onMount();
471
+ });
472
+ const bag = hostInst.__vmzPendingChildMounts || (hostInst.__vmzPendingChildMounts = []);
473
+ bag.push(pending);
474
+ }
475
+ return host;
476
+ },
477
+ /**
478
+ * Keep nested Direct child props live with parent field writes.
479
+ * @param {object} hostInst
480
+ * @param {HTMLElement} hostEl
481
+ * @param {string} propName
482
+ * @param {string[]} deps
483
+ * @param {() => any} get
484
+ */
485
+ bindComponentProp(hostInst, hostEl, propName, deps, get) {
486
+ // Use a stable BindingId so flushPending schedules this patch via the IR
487
+ // path. A null bindingId only lands in `__vmzBinders` and was skipped when
488
+ // the same parent field also had bindText/bindAttr BindingIds.
489
+ if (hostEl && hostEl.__vmzPropBindSeq == null) {
490
+ hostEl.__vmzPropBindSeq = ++directPropBindSeq;
491
+ }
492
+ const seq = hostEl && hostEl.__vmzPropBindSeq != null ? hostEl.__vmzPropBindSeq : ++directPropBindSeq;
493
+ const bindingId = `pc:${seq}:${propName}`;
494
+ wireDirectBind(hostInst, bindingId, deps, get, (raw) => {
495
+ const child = hostEl && hostEl.__vmzInst;
496
+ if (!child || child.__vmzDestroyed)
497
+ return;
498
+ if (typeof propName !== 'string' || !propName || propName.startsWith('#'))
499
+ return;
500
+ child[propName] = raw;
501
+ scheduleRefresh(child, { type: 'replace', root: propName });
502
+ });
503
+ },
504
+ /**
505
+ * Project parent children into nested Direct component default `<slot>`.
506
+ * @param {HTMLElement} hostEl
507
+ * @param {Node} node
508
+ */
509
+ projectDefaultSlot(hostEl, node) {
510
+ if (!hostEl || node == null)
511
+ return;
512
+ const child = hostEl.__vmzInst;
513
+ const root = (child && child.__vmzDomRoot) || hostEl;
514
+ /** @type {Element | null} */
515
+ let slot = null;
516
+ if (root && root.nodeType === 1) {
517
+ if (String(root.tagName || '').toLowerCase() === 'slot' && !root.getAttribute('name')) {
518
+ slot = root;
519
+ }
520
+ else if (typeof root.querySelector === 'function') {
521
+ slot = root.querySelector('slot:not([name])');
522
+ }
523
+ }
524
+ if (slot && slot.parentNode) {
525
+ slot.replaceWith(node);
526
+ return;
527
+ }
528
+ if (root && typeof root.appendChild === 'function')
529
+ root.appendChild(node);
530
+ else
531
+ hostEl.appendChild(node);
532
+ },
533
+ /**
534
+ * Direct if/else — no blueprint `kind: "if"` dispatch.
535
+ * @param {object} inst
536
+ * @param {number|string|null} bindingId
537
+ * @param {string[]} deps
538
+ * @param {Array<{ cond?: => any, create: (api: typeof directApi) => Node }>} branches
539
+ * @param {number|string|null} [regionId]
540
+ */
541
+ ifBlock(inst, bindingId, deps, branches, regionId = null) {
542
+ noteDomCreate();
543
+ const host = document.createElement('span');
544
+ host.setAttribute('data-vmz-if', '');
545
+ if (regionId != null)
546
+ host.setAttribute('data-vmz-region', String(regionId));
547
+ /** @type {Array<Node | null>} */
548
+ const cached = branches.map(() => null);
549
+ /** @type {Array<Array<{ deps: string[], fn: => any, bindingId?: number|string|null }>>} */
550
+ const branchBinds = branches.map(() => []);
551
+ let active = -1;
552
+ let gen = 0;
553
+ const pick = () => {
554
+ for (let i = 0; i < branches.length; i++) {
555
+ const b = branches[i];
556
+ if (!b.cond)
557
+ return i;
558
+ try {
559
+ if (b.cond.call(inst))
560
+ return i;
561
+ }
562
+ catch {
563
+ /* continue */
564
+ }
565
+ }
566
+ return -1;
567
+ };
568
+ const wireBranch = (idx) => {
569
+ if (idx < 0)
570
+ return;
571
+ for (const { deps: d, fn, bindingId: bid } of branchBinds[idx]) {
572
+ registerBind(inst, d, fn, bid);
573
+ try {
574
+ runPatch(fn, (d && d[0]) || null, bid ?? null);
575
+ }
576
+ catch (err) {
577
+ console.error('vmz:dom if branch', err);
578
+ }
579
+ }
580
+ };
581
+ const unwireBranch = (idx) => {
582
+ if (idx < 0)
583
+ return;
584
+ for (const { deps: d, fn, bindingId: bid } of branchBinds[idx]) {
585
+ unregisterBind(inst, d, fn, bid);
586
+ }
587
+ };
588
+ const apply = () => {
589
+ if (inst.__vmzDestroyed)
590
+ return;
591
+ const applied = ++gen;
592
+ const next = pick();
593
+ if (next === active)
594
+ return;
595
+ if (next >= 0 && !cached[next]) {
596
+ const binds = [];
597
+ const prevSink = directApi._branchBinds;
598
+ const prevInst = directApi._inst;
599
+ directApi._branchBinds = binds;
600
+ directApi._inst = inst;
601
+ let created = null;
602
+ try {
603
+ created = branches[next].create.call(inst, directApi);
604
+ }
605
+ finally {
606
+ directApi._branchBinds = prevSink;
607
+ directApi._inst = prevInst;
608
+ }
609
+ if (applied !== gen || inst.__vmzDestroyed)
610
+ return;
611
+ if (!cached[next]) {
612
+ cached[next] = created;
613
+ branchBinds[next] = binds;
614
+ }
615
+ }
616
+ if (applied !== gen || inst.__vmzDestroyed)
617
+ return;
618
+ if (active >= 0) {
619
+ unwireBranch(active);
620
+ if (cached[active] && cached[active].parentNode) {
621
+ noteDomRemove();
622
+ cached[active].remove();
623
+ }
624
+ }
625
+ active = next;
626
+ if (next < 0)
627
+ return;
628
+ wireBranch(next);
629
+ if (cached[next])
630
+ host.appendChild(cached[next]);
631
+ };
632
+ registerBind(inst, deps || [], apply, bindingId);
633
+ if (directApi._itemPatches)
634
+ directApi._itemPatches.push(apply);
635
+ // parent destroy disposes all cached branch trees (pause ≠ destroy on switch).
636
+ host.__vmzDispose = () => {
637
+ for (let i = 0; i < cached.length; i++) {
638
+ unwireBranch(i);
639
+ if (cached[i])
640
+ disposeDomTree(cached[i]);
641
+ cached[i] = null;
642
+ }
643
+ active = -1;
644
+ };
645
+ apply();
646
+ return host;
647
+ },
648
+ /**
649
+ * Direct keyed each — no blueprint `kind: "each"` dispatch.
650
+ * /: Set/Map + Fragment batch insert; item-local binds; host field dispatch; event delegate.
651
+ * @param {object} inst
652
+ * @param {number|string|null} bindingId
653
+ * @param {string[]} deps
654
+ * @param {{ as?: string, list: => any, key?: (box: {item:any,index:number}) => any, createItem: (api: typeof directApi, box: {item:any,index:number}) => Node }} spec
655
+ * @param {number|string|null} [regionId]
656
+ */
657
+ eachBlock(inst, bindingId, deps, spec, regionId = null) {
658
+ const start = document.createComment(`vmz-each:${spec.as || ''}`);
659
+ const end = document.createComment('/vmz-each');
660
+ if (regionId != null)
661
+ start.__vmzRegion = regionId;
662
+ const frag = document.createDocumentFragment();
663
+ frag.appendChild(start);
664
+ frag.appendChild(end);
665
+ /** @type {Map<any, { box: { item: any, index: number }, dom: Node, patches: Array< => void> }>} */
666
+ const keyed = new Map();
667
+ let gen = 0;
668
+ /** @type {Map<string, => void>} */
669
+ const listDispatchers = new Map();
670
+ /** @type {Set<string>} */
671
+ const hostDispatchers = new Set();
672
+ /** @type {Record<string, any>} */
673
+ const hostPrev = Object.create(null);
674
+ /** @type {Set<string>} */
675
+ const delegateTypes = new Set();
676
+ /** @type {Record<string, EventListener>} */
677
+ const delegateListeners = Object.create(null);
678
+ /** @type {Element | null} */
679
+ let delegateRoot = null;
680
+ const itemKey = (box) => {
681
+ if (rowKeyField != null && box && box.item != null) {
682
+ return box.item[rowKeyField];
683
+ }
684
+ if (typeof spec.key === 'function') {
685
+ try {
686
+ return spec.key.call(inst, box);
687
+ }
688
+ catch {
689
+ return box.index;
690
+ }
691
+ }
692
+ return box.index;
693
+ };
694
+ /** Reused for keyed lookups — avoid per-row `{item,index}` alloc on create/update. */
695
+ const keyScratch = { item: null, index: 0 };
696
+ const keyOf = (item, index) => {
697
+ keyScratch.item = item;
698
+ keyScratch.index = index;
699
+ return itemKey(keyScratch);
700
+ };
701
+ const readList = () => {
702
+ let list = [];
703
+ try {
704
+ list = spec.list.call(inst) || [];
705
+ }
706
+ catch {
707
+ list = [];
708
+ }
709
+ if (!Array.isArray(list))
710
+ list = [...list];
711
+ return list;
712
+ };
713
+ const runEntryPatches = (entry, depKey, onlyBindingId) => {
714
+ if (!entry)
715
+ return;
716
+ if (entryIsBp(entry) && applyBp) {
717
+ if (onlyBindingId != null && blueprintBindIds && !blueprintBindIds.has(String(onlyBindingId))) {
718
+ return;
719
+ }
720
+ try {
721
+ // depKey: item field, host field, or null → full apply (slot-aware rowKernel).
722
+ applyBp(entry, depKey);
723
+ }
724
+ catch (err) {
725
+ console.error('vmz:dom each item', err);
726
+ }
727
+ return;
728
+ }
729
+ if (!entry.patches)
730
+ return;
731
+ for (const p of entry.patches) {
732
+ if (onlyBindingId != null) {
733
+ if (p.__vmzBindingIds) {
734
+ if (!p.__vmzBindingIds.has(String(onlyBindingId)))
735
+ continue;
736
+ }
737
+ else if (p.__vmzBindingId != null && String(p.__vmzBindingId) !== String(onlyBindingId)) {
738
+ continue;
739
+ }
740
+ }
741
+ try {
742
+ runPatch(p, depKey, onlyBindingId);
743
+ }
744
+ catch (err) {
745
+ console.error('vmz:dom each item', err);
746
+ }
747
+ }
748
+ };
749
+ const refreshByListIndex = (onlyBindingId, leafDeps, trie) => {
750
+ const list = readList();
751
+ const allowIdx = itemIndicesAllowedForDeps(trie, leafDeps);
752
+ const listRoot = depRootField((leafDeps && leafDeps[0]) || '') || (leafDeps && leafDeps[0]) || '';
753
+ // Batch monomorphic path: every dirty index shares one item field → one applyByField fn.
754
+ // Skips per-index dirtyItemFieldsAt + runEntryPatches (update-every-10th hot path).
755
+ // Note: list dispatcher passes BindingId (`__vmzRk`); do not require onlyBindingId == null.
756
+ if (hasRowKernel && rkApplyByField && allowIdx && allowIdx.size && listRoot) {
757
+ if (onlyBindingId == null || !blueprintBindIds || blueprintBindIds.has(String(onlyBindingId))) {
758
+ const field = soleDirtyItemField(trie, listRoot, allowIdx);
759
+ if (field) {
760
+ const f = rkApplyByField[field];
761
+ if (typeof f === 'function') {
762
+ // Text-only slots need no `this`; host class slots do.
763
+ const needsThis = rkHostFieldSet.has(field);
764
+ const entries = entryByIndex;
765
+ const nList = list.length;
766
+ if (needsThis) {
767
+ for (const idx of allowIdx) {
768
+ const i = Number(idx);
769
+ if (i < 0 || i >= nList)
770
+ continue;
771
+ const item = list[i];
772
+ let entry = entries[i];
773
+ if (!entry) {
774
+ entry = keyed.get(rowKeyOf(item, i));
775
+ if (entry)
776
+ entries[i] = entry;
777
+ }
778
+ if (!entry || entry.nodeType !== 1)
779
+ continue;
780
+ if (entry.__vmzBox !== item)
781
+ entry.__vmzBox = item;
782
+ f.call(inst, entry, item);
783
+ }
784
+ }
785
+ else {
786
+ for (const idx of allowIdx) {
787
+ const i = Number(idx);
788
+ if (i < 0 || i >= nList)
789
+ continue;
790
+ const item = list[i];
791
+ let entry = entries[i];
792
+ if (!entry) {
793
+ entry = keyed.get(rowKeyOf(item, i));
794
+ if (entry)
795
+ entries[i] = entry;
796
+ }
797
+ if (!entry || entry.nodeType !== 1)
798
+ continue;
799
+ if (entry.__vmzBox !== item)
800
+ entry.__vmzBox = item;
801
+ f(entry, item);
802
+ }
803
+ }
804
+ return;
805
+ }
806
+ }
807
+ }
808
+ }
809
+ const runAt = (i) => {
810
+ if (i < 0 || i >= list.length)
811
+ return;
812
+ const item = list[i];
813
+ let entry = entryByIndex[i];
814
+ if (!entry) {
815
+ entry = keyed.get(rowKeyOf(item, i));
816
+ if (entry)
817
+ entryByIndex[i] = entry;
818
+ }
819
+ if (!entry)
820
+ return;
821
+ const slots = dirtyItemFieldsAt(trie, listRoot, i);
822
+ // Hot leaf: monomorphic applyByField — no Map miss, no runEntryPatches wrapper.
823
+ if (hasRowKernel && entry.nodeType === 1 && rkApplyByField && slots && slots.length === 1) {
824
+ if (entry.__vmzBox !== item)
825
+ entry.__vmzBox = item;
826
+ const slot = slots[0];
827
+ const f = rkApplyByField[slot];
828
+ if (typeof f === 'function') {
829
+ if (rkHostFieldSet.has(slot))
830
+ f.call(inst, entry, item);
831
+ else
832
+ f(entry, item);
833
+ return;
834
+ }
835
+ }
836
+ if (entry.nodeType === 1)
837
+ entry.__vmzBox = item;
838
+ else {
839
+ entry.item = item;
840
+ entry.index = i;
841
+ if (entry.dom && entry.bp)
842
+ entry.dom.__vmzBox = item;
843
+ }
844
+ if (entry.patches)
845
+ tagItemPatches(entry.patches, i);
846
+ if (slots == null) {
847
+ runEntryPatches(entry, null, onlyBindingId);
848
+ }
849
+ else if (slots.length) {
850
+ for (const slot of slots)
851
+ runEntryPatches(entry, slot, onlyBindingId);
852
+ }
853
+ };
854
+ if (allowIdx) {
855
+ for (const idx of allowIdx)
856
+ runAt(Number(idx));
857
+ return;
858
+ }
859
+ for (let i = 0; i < list.length; i++)
860
+ runAt(i);
861
+ };
862
+ const refreshHostKeyed = (fields, onlyBindingId) => {
863
+ for (const field of fields) {
864
+ const next = inst[field];
865
+ const prev = hostPrev[field];
866
+ hostPrev[field] = next;
867
+ const todo = [];
868
+ if (prev !== undefined && prev !== null)
869
+ todo.push(prev);
870
+ if (next !== undefined && next !== null && next !== prev)
871
+ todo.push(next);
872
+ for (const k of todo) {
873
+ const entry = keyed.get(k);
874
+ if (!entry)
875
+ continue;
876
+ if (hasRowKernel && entry.nodeType === 1 && rkApplyByField) {
877
+ const f = rkApplyByField[field];
878
+ if (typeof f === 'function') {
879
+ f.call(inst, entry, entry.__vmzBox);
880
+ continue;
881
+ }
882
+ }
883
+ runEntryPatches(entry, field, onlyBindingId);
884
+ }
885
+ }
886
+ };
887
+ const ensureListDispatcher = (bId, leafDeps) => {
888
+ if (bId == null)
889
+ return;
890
+ const idKey = String(bId);
891
+ if (listDispatchers.has(idKey))
892
+ return;
893
+ const dispatch = () => {
894
+ if (inst.__vmzDestroyed)
895
+ return;
896
+ const trie = inst.__vmzFlushTrie;
897
+ const hostFields = [];
898
+ let listReplaced = false;
899
+ for (const d of leafDeps || []) {
900
+ if (!d)
901
+ continue;
902
+ if (d.includes('.*') || (d.includes('[') && d.includes(']'))) {
903
+ const root = depRootField(d);
904
+ if (trie && root && trie[root] && trie[root].replace)
905
+ listReplaced = true;
906
+ continue;
907
+ }
908
+ hostFields.push(depRootField(d) || d);
909
+ }
910
+ const hostDirty = !!trie &&
911
+ hostFields.some((f) => {
912
+ const n = trie[f];
913
+ return n && (n.replace || n.dirty);
914
+ });
915
+ // Full list replace is owned by eachBlock apply() — skip leaf re-walk.
916
+ if (listReplaced && !hostDirty)
917
+ return;
918
+ if (hostDirty && hostFields.length) {
919
+ refreshHostKeyed(hostFields, bId);
920
+ return;
921
+ }
922
+ refreshByListIndex(bId, leafDeps, trie);
923
+ };
924
+ listDispatchers.set(idKey, dispatch);
925
+ registerBind(inst, leafDeps || [], dispatch, bId);
926
+ };
927
+ const ensureHostDispatcher = (field) => {
928
+ if (!field || hostDispatchers.has(field))
929
+ return;
930
+ hostDispatchers.add(field);
931
+ const dispatch = () => {
932
+ if (inst.__vmzDestroyed)
933
+ return;
934
+ refreshHostKeyed([field], null);
935
+ };
936
+ registerBind(inst, [field], dispatch, null);
937
+ };
938
+ const noteItemBind = (bId, bindDeps, fn) => {
939
+ fn.__vmzItemDeps = Array.isArray(bindDeps) ? bindDeps.slice() : [];
940
+ fn.__vmzBindingId = bId;
941
+ const leaf = fn.__vmzItemDeps;
942
+ if (bId != null) {
943
+ // One dispatcher per BindingId also covers bare host fields (e.g. selected).
944
+ ensureListDispatcher(bId, leaf);
945
+ return;
946
+ }
947
+ for (const d of leaf) {
948
+ if (!d)
949
+ continue;
950
+ if (d.includes('.*') || (d.includes('[') && d.includes(']')))
951
+ continue;
952
+ const root = depRootField(d) || d;
953
+ if (root && root.indexOf('.') < 0)
954
+ ensureHostDispatcher(root);
955
+ }
956
+ };
957
+ const teardownDelegate = () => {
958
+ if (!delegateRoot)
959
+ return;
960
+ for (const type of Object.keys(delegateListeners)) {
961
+ delegateRoot.removeEventListener(type, delegateListeners[type]);
962
+ delete delegateListeners[type];
963
+ }
964
+ delegateRoot = null;
965
+ };
966
+ const ensureDelegateAttached = () => {
967
+ const parent = end.parentNode;
968
+ if (!parent || parent.nodeType !== 1)
969
+ return;
970
+ if (delegateRoot && delegateRoot !== parent)
971
+ teardownDelegate();
972
+ delegateRoot = /** @type {Element} */ (parent);
973
+ for (const type of delegateTypes) {
974
+ if (delegateListeners[type])
975
+ continue;
976
+ const listener = (ev) => {
977
+ if (type === 'submit' && ev && typeof ev.preventDefault === 'function') {
978
+ ev.preventDefault();
979
+ }
980
+ let n = /** @type {Node | null} */ (ev.target);
981
+ while (n && n !== delegateRoot) {
982
+ if (n.nodeType === 1) {
983
+ const el = /** @type {Element} */ (n);
984
+ const act = el.__vmzAct || (el.__vmzAct = el.getAttribute('data-vmz-act'));
985
+ if (typeof act === 'string' && act) {
986
+ actionHandler(act).call(inst, ev, el);
987
+ return;
988
+ }
989
+ const bag = el.__vmzEvt;
990
+ if (bag && typeof bag[type] === 'function') {
991
+ // Pass the element so shared each-item handlers can read __vmzBox.
992
+ const h = bag[type];
993
+ runDomEventHandler(inst, inferHandlerMethod(h), () => h.call(inst, ev, el));
994
+ return;
995
+ }
996
+ }
997
+ n = n.parentNode;
998
+ }
999
+ };
1000
+ delegateListeners[type] = listener;
1001
+ delegateRoot.addEventListener(type, listener);
1002
+ }
1003
+ };
1004
+ const needDelegate = (type) => {
1005
+ if (!type)
1006
+ return;
1007
+ delegateTypes.add(type);
1008
+ ensureDelegateAttached();
1009
+ };
1010
+ const eachCtx = { noteItemBind, needDelegate };
1011
+ const clearDomEvt = (root) => {
1012
+ if (!root || root.nodeType !== 1)
1013
+ return;
1014
+ const walk = (node) => {
1015
+ if (node.nodeType === 1) {
1016
+ if (node.__vmzEvt)
1017
+ node.__vmzEvt = null;
1018
+ if (node.__vmzBox)
1019
+ node.__vmzBox = null;
1020
+ if (node.__vmzAct)
1021
+ node.__vmzAct = null;
1022
+ if (node.__vmzKey != null)
1023
+ node.__vmzKey = null;
1024
+ for (let c = node.firstChild; c; c = c.nextSibling)
1025
+ walk(c);
1026
+ }
1027
+ };
1028
+ walk(root);
1029
+ };
1030
+ const pathFromRoot = (root, node) => {
1031
+ /** @type {number[]} */
1032
+ const path = [];
1033
+ let n = /** @type {Node | null} */ (node);
1034
+ while (n && n !== root) {
1035
+ const parent = n.parentNode;
1036
+ if (!parent)
1037
+ return null;
1038
+ let i = 0;
1039
+ for (let c = parent.firstChild; c; c = c.nextSibling) {
1040
+ if (c === n)
1041
+ break;
1042
+ i++;
1043
+ }
1044
+ path.push(i);
1045
+ n = parent;
1046
+ }
1047
+ if (n !== root)
1048
+ return null;
1049
+ path.reverse();
1050
+ return path;
1051
+ };
1052
+ const nodeAtPath = (root, path) => {
1053
+ let n = /** @type {Node | null} */ (root);
1054
+ for (let i = 0; i < path.length; i++) {
1055
+ if (!n)
1056
+ return null;
1057
+ n = n.childNodes[path[i]] || null;
1058
+ }
1059
+ return n;
1060
+ };
1061
+ /**
1062
+ * Shared each-item event handlers (one per method name for the whole block).
1063
+ * Element carries `__vmzBox`; delegate passes the element as 2nd arg.
1064
+ * @type {Record<string, (ev: Event, el: Element) => void>}
1065
+ */
1066
+ const sharedActions = Object.create(null);
1067
+ /** @type {Record<string, string>} method → item field for action arg (fallback blueprint). */
1068
+ const actionArgFields = Object.create(null);
1069
+ const actionHandler = (method) => {
1070
+ if (!sharedActions[method]) {
1071
+ sharedActions[method] = function (ev, el) {
1072
+ let n = /** @type {Node | null} */ (el);
1073
+ while (n && n.nodeType === 1) {
1074
+ const box = /** @type {Element} */ (n).__vmzBox;
1075
+ if (box) {
1076
+ const item = box.item != null ? box.item : box;
1077
+ const argField = rowActArgField != null ? rowActArgField : actionArgFields[method] != null ? actionArgFields[method] : null;
1078
+ if (argField == null || item == null)
1079
+ return;
1080
+ const arg = item[argField];
1081
+ const fn = this[method];
1082
+ if (typeof fn === 'function') {
1083
+ runDomEventHandler(this, method, () => fn.call(this, arg));
1084
+ }
1085
+ return;
1086
+ }
1087
+ n = n.parentNode;
1088
+ }
1089
+ };
1090
+ }
1091
+ return sharedActions[method];
1092
+ };
1093
+ /**
1094
+ * @returns {{ method: string, argField: string } | null}
1095
+ */
1096
+ const parseActionMethod = (handler) => {
1097
+ if (typeof handler !== 'function')
1098
+ return null;
1099
+ try {
1100
+ const src = Function.prototype.toString.call(handler);
1101
+ // this.m(box.item.<field>) — field from author surface.
1102
+ const m = src.match(/this\.([A-Za-z_$][\w$]*)\s*\(\s*[A-Za-z_$][\w$]*\.item\.([A-Za-z_$][\w$]*)\s*\)/);
1103
+ return m ? { method: m[1], argField: m[2] } : null;
1104
+ }
1105
+ catch {
1106
+ return null;
1107
+ }
1108
+ };
1109
+ /**
1110
+ * Row blueprint: first createItem records dynamic slots; later rows clone + hydrate
1111
+ * without re-running compiled createItem (avoids per-row get/CF/on closures).
1112
+ * @type {null | {
1113
+ * tpl: Element,
1114
+ * texts: Array<{ path: number[], bindingId: any, deps: string[], field: string, get: (root: Element) => Node }>,
1115
+ * attrs: Array<{ path: number[], bindingId: any, deps: string[], name: string, onVal: string, offVal: string, get: (root: Element) => Element }>,
1116
+ * ons: Array<{ path: number[], type: string, method: string, get: (root: Element) => Element }>,
1117
+ * bindIds: Set<string>,
1118
+ * }}
1119
+ */
1120
+ let blueprint = null;
1121
+ let blueprintOk = true;
1122
+ /** @type {Set<string> | null} */
1123
+ let blueprintBindIds = null;
1124
+ /** @type {null | ((root: Element, entry: any) => void)} */
1125
+ let hydrateBp = null;
1126
+ /** @type {null | ((entry: any) => void)} */
1127
+ let applyBp = null;
1128
+ /**
1129
+ * Compile-time rowKernel installed — static HTML rows, no nested component dispose.
1130
+ * Shape-specific walks live in emitted hydrate/apply (Rust), not here.
1131
+ */
1132
+ let hasRowKernel = false;
1133
+ /** @type {Record<string, (root: Element, item: any) => void> | null} */
1134
+ let rkApplyByField = null;
1135
+ /** @type {Record<string, number> | null} */
1136
+ let rkTextSlots = null;
1137
+ /** Host fields whose applyByField needs `this` (class ternaries). */
1138
+ let rkHostFieldSet = new Set();
1139
+ /** Parallel to list index → entry (Element or bp). Leaf updates skip Map.get(id). */
1140
+ /** @type {any[]} */
1141
+ let entryByIndex = [];
1142
+ /** @type {string | null} item field used as key when rowKernel.keyField is set */
1143
+ let rowKeyField = null;
1144
+ /** @type {string | null} item field passed to delegated actions (from rowKernel.actArgField) */
1145
+ let rowActArgField = null;
1146
+ /** Recycle object bp entries (runtime-recorded blueprint fallback). */
1147
+ /** @type {any[]} */
1148
+ const entryPool = [];
1149
+ const allocBpEntry = () => {
1150
+ const e = entryPool.pop();
1151
+ if (e)
1152
+ return e;
1153
+ return { item: null, dom: null, bp: 1, t0: null, t1: null };
1154
+ };
1155
+ const releaseBpEntry = (entry) => {
1156
+ if (!entry)
1157
+ return;
1158
+ // DOM-as-entry (Element): drop expandos.
1159
+ if (entry.nodeType === 1) {
1160
+ entry.__vmzBox = null;
1161
+ entry.__vmzT = null;
1162
+ entry.__vmzE = null;
1163
+ entry.__vmzTexts = null;
1164
+ entry.__vmzBp = null;
1165
+ return;
1166
+ }
1167
+ if (!entry.bp || entryPool.length >= 4096)
1168
+ return;
1169
+ entry.item = null;
1170
+ entry.dom = null;
1171
+ entry.t0 = null;
1172
+ entry.t1 = null;
1173
+ entry.a0 = null;
1174
+ entry.patches = null;
1175
+ entryPool.push(entry);
1176
+ };
1177
+ const entryDom = (entry) => (entry && entry.nodeType === 1 ? entry : entry && entry.dom);
1178
+ const entryIsBp = (entry) => !!(entry && (entry.nodeType === 1 || entry.bp || entry.__vmzBp));
1179
+ const entryItem = (entry) => {
1180
+ if (!entry)
1181
+ return null;
1182
+ if (entry.nodeType === 1)
1183
+ return entry.__vmzBox;
1184
+ if (entry.bp)
1185
+ return entry.item;
1186
+ return entry.box && entry.box.item;
1187
+ };
1188
+ const rowKeyOf = (item, index) => {
1189
+ if (rowKeyField != null && item != null)
1190
+ return item[rowKeyField];
1191
+ return keyOf(item, index);
1192
+ };
1193
+ /** Drop all row DOM between markers; rowKernel rows skip per-node dispose walks. */
1194
+ const fastWipeRows = () => {
1195
+ const parent = end.parentNode;
1196
+ if (!parent) {
1197
+ keyed.clear();
1198
+ entryByIndex.length = 0;
1199
+ return;
1200
+ }
1201
+ let node = start.nextSibling;
1202
+ if (node && node !== end) {
1203
+ if (hasRowKernel || (blueprint && blueprintOk)) {
1204
+ const range = document.createRange();
1205
+ range.setStartBefore(node);
1206
+ range.setEndBefore(end);
1207
+ range.deleteContents();
1208
+ }
1209
+ else {
1210
+ while (node && node !== end) {
1211
+ const next = node.nextSibling;
1212
+ noteDomRemove();
1213
+ clearDomEvt(node);
1214
+ disposeDomTree(node);
1215
+ node.remove();
1216
+ node = next;
1217
+ }
1218
+ }
1219
+ }
1220
+ for (const [, entry] of keyed)
1221
+ releaseBpEntry(entry);
1222
+ keyed.clear();
1223
+ entryByIndex.length = 0;
1224
+ };
1225
+ /** Rebuild index→entry after fresh create (rowKernel.create only fills keyed Map). */
1226
+ const rebuildEntryByIndex = (list) => {
1227
+ const n = list.length;
1228
+ entryByIndex = new Array(n);
1229
+ for (let i = 0; i < n; i++) {
1230
+ entryByIndex[i] = keyed.get(rowKeyOf(list[i], i));
1231
+ }
1232
+ };
1233
+ const makeChildGetter = (path) => {
1234
+ const len = path.length;
1235
+ if (len === 0)
1236
+ return (root) => root;
1237
+ if (len === 1) {
1238
+ const a = path[0];
1239
+ return (root) => root.childNodes[a];
1240
+ }
1241
+ if (len === 2) {
1242
+ const a = path[0];
1243
+ const b = path[1];
1244
+ return (root) => root.childNodes[a].childNodes[b];
1245
+ }
1246
+ if (len === 3) {
1247
+ const a = path[0];
1248
+ const b = path[1];
1249
+ const c = path[2];
1250
+ return (root) => root.childNodes[a].childNodes[b].childNodes[c];
1251
+ }
1252
+ return (root) => {
1253
+ let n = /** @type {Node} */ (root);
1254
+ for (let i = 0; i < len; i++)
1255
+ n = n.childNodes[path[i]];
1256
+ return n;
1257
+ };
1258
+ };
1259
+ const userCreateItem = spec.createItem;
1260
+ // Compile-time row kernel (Rust Direct emit) — skip runtime blueprint recording.
1261
+ if (spec.rowKernel && typeof spec.rowKernel.html === 'string' && typeof spec.rowKernel.hydrate === 'function') {
1262
+ try {
1263
+ const tplHost = document.createElement('template');
1264
+ tplHost.innerHTML = spec.rowKernel.html;
1265
+ const row = tplHost.content.firstElementChild;
1266
+ if (row && row.nodeType === 1) {
1267
+ blueprint = {
1268
+ tpl: /** @type {Element} */ (row.cloneNode(true)),
1269
+ texts: [],
1270
+ attrs: [],
1271
+ ons: [],
1272
+ bindIds: new Set(),
1273
+ };
1274
+ blueprintOk = true;
1275
+ hasRowKernel = true;
1276
+ rowKeyField = typeof spec.rowKernel.keyField === 'string' && spec.rowKernel.keyField ? spec.rowKernel.keyField : null;
1277
+ rowActArgField =
1278
+ typeof spec.rowKernel.actArgField === 'string' && spec.rowKernel.actArgField ? spec.rowKernel.actArgField : null;
1279
+ blueprintBindIds = new Set(['__vmzRk']);
1280
+ for (const ev of spec.rowKernel.events || [])
1281
+ needDelegate(ev);
1282
+ rkHostFieldSet = new Set();
1283
+ for (const hf of spec.rowKernel.hostFields || []) {
1284
+ if (typeof hf === 'string' && hf) {
1285
+ rkHostFieldSet.add(hf);
1286
+ ensureHostDispatcher(hf);
1287
+ }
1288
+ }
1289
+ // Leaf path writes (`rows.0.label`) need `rows.*.label`, not bare `rows.*`.
1290
+ {
1291
+ const listRoot = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || '';
1292
+ if (listRoot) {
1293
+ /** @type {string[]} */
1294
+ const leafDeps = [`${listRoot}.*`];
1295
+ const fields = Array.isArray(spec.rowKernel.itemFields) ? spec.rowKernel.itemFields : [];
1296
+ for (const f of fields) {
1297
+ if (typeof f === 'string' && f)
1298
+ leafDeps.push(`${listRoot}.*.${f}`);
1299
+ }
1300
+ ensureListDispatcher('__vmzRk', leafDeps);
1301
+ }
1302
+ }
1303
+ const rkHydrate = spec.rowKernel.hydrate;
1304
+ const rkApply = spec.rowKernel.apply;
1305
+ // Assign outer binding — do not shadow with const (leaf hot path reads it).
1306
+ rkApplyByField =
1307
+ spec.rowKernel.applyByField && typeof spec.rowKernel.applyByField === 'object' ? spec.rowKernel.applyByField : null;
1308
+ rkTextSlots = spec.rowKernel.textSlots && typeof spec.rowKernel.textSlots === 'object' ? spec.rowKernel.textSlots : null;
1309
+ hydrateBp = (root, entry) => {
1310
+ const item = entry && typeof entry === 'object' && 'item' in entry && entry.item != null
1311
+ ? entry.item
1312
+ : entry && entry.__vmzBox != null
1313
+ ? entry.__vmzBox
1314
+ : entry;
1315
+ rkHydrate.call(inst, root, item);
1316
+ };
1317
+ applyBp = (entry, slot) => {
1318
+ const root = entry && entry.nodeType === 1 ? entry : entry.dom;
1319
+ const item = entry && entry.nodeType === 1 ? entry.__vmzBox : entry.item;
1320
+ if (slot != null && rkApplyByField) {
1321
+ const f = rkApplyByField[slot];
1322
+ if (typeof f === 'function') {
1323
+ if (rkHostFieldSet.has(slot))
1324
+ f.call(inst, root, item);
1325
+ else
1326
+ f(root, item);
1327
+ return;
1328
+ }
1329
+ }
1330
+ if (typeof rkApply === 'function')
1331
+ rkApply.call(inst, root, item);
1332
+ };
1333
+ // Event update: WritePath accumulates idxs → drain with one applyByField fn.
1334
+ const listRootForLeaf = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || '';
1335
+ inst.__vmzDrainLeafDirty = () => {
1336
+ const ld = inst.__vmzLeafDirty;
1337
+ if (!ld)
1338
+ return;
1339
+ if (!rkApplyByField || (listRootForLeaf && ld.root !== listRootForLeaf)) {
1340
+ promoteLeafDirtyToTrie(inst);
1341
+ return;
1342
+ }
1343
+ const f = rkApplyByField[ld.field];
1344
+ if (typeof f !== 'function') {
1345
+ promoteLeafDirtyToTrie(inst);
1346
+ return;
1347
+ }
1348
+ const idxs = ld.idxs;
1349
+ inst.__vmzLeafDirty = null;
1350
+ const list = readList();
1351
+ const entries = entryByIndex;
1352
+ const nList = list.length;
1353
+ if (rkHostFieldSet.has(ld.field)) {
1354
+ for (let k = 0; k < idxs.length; k++) {
1355
+ const i = idxs[k];
1356
+ if (i < 0 || i >= nList)
1357
+ continue;
1358
+ const entry = entries[i];
1359
+ if (!entry || entry.nodeType !== 1)
1360
+ continue;
1361
+ const item = list[i];
1362
+ if (entry.__vmzBox !== item)
1363
+ entry.__vmzBox = item;
1364
+ f.call(inst, entry, item);
1365
+ }
1366
+ }
1367
+ else {
1368
+ for (let k = 0; k < idxs.length; k++) {
1369
+ const i = idxs[k];
1370
+ if (i < 0 || i >= nList)
1371
+ continue;
1372
+ const entry = entries[i];
1373
+ if (entry && entry.nodeType === 1)
1374
+ f(entry, list[i]);
1375
+ }
1376
+ }
1377
+ };
1378
+ }
1379
+ }
1380
+ catch (err) {
1381
+ console.error('vmz:dom rowKernel', err);
1382
+ blueprint = null;
1383
+ blueprintOk = true;
1384
+ hasRowKernel = false;
1385
+ rowKeyField = null;
1386
+ rowActArgField = null;
1387
+ hydrateBp = null;
1388
+ applyBp = null;
1389
+ rkApplyByField = null;
1390
+ rkTextSlots = null;
1391
+ rkHostFieldSet = new Set();
1392
+ if (inst.__vmzDrainLeafDirty)
1393
+ inst.__vmzDrainLeafDirty = null;
1394
+ inst.__vmzLeafDirty = null;
1395
+ }
1396
+ }
1397
+ const probeItemField = (get, box) => {
1398
+ const item = box.item;
1399
+ if (!item || (typeof item !== 'object' && typeof item !== 'function'))
1400
+ return null;
1401
+ let field = null;
1402
+ const proxy = new Proxy(item, {
1403
+ get(t, p, r) {
1404
+ if (typeof p === 'string' || typeof p === 'symbol')
1405
+ field = String(p);
1406
+ return Reflect.get(t, p, r);
1407
+ },
1408
+ });
1409
+ const prev = box.item;
1410
+ box.item = proxy;
1411
+ try {
1412
+ get.call(inst);
1413
+ }
1414
+ catch {
1415
+ /* ignore */
1416
+ }
1417
+ box.item = prev;
1418
+ return field;
1419
+ };
1420
+ /**
1421
+ * Probe on/off class strings for `this.<host> === item.<itemField> ? … : …`.
1422
+ * Host/item field names come from binding deps — not hardcoded.
1423
+ */
1424
+ const probeHostItemClass = (get, box, hostField, itemField) => {
1425
+ if (!hostField || !itemField)
1426
+ return { onVal: '', offVal: '' };
1427
+ const prev = inst[hostField];
1428
+ const matchVal = box.item != null ? box.item[itemField] : undefined;
1429
+ let onVal = '';
1430
+ let offVal = '';
1431
+ const quiet = !!inst.__vmzQuiet;
1432
+ inst.__vmzQuiet = true;
1433
+ try {
1434
+ inst[hostField] = matchVal;
1435
+ onVal = String(get.call(inst) ?? '');
1436
+ // Distinct off value for number / other keys.
1437
+ if (typeof matchVal === 'number') {
1438
+ inst[hostField] = matchVal === 0 ? -1 : 0;
1439
+ if (inst[hostField] === matchVal)
1440
+ inst[hostField] = undefined;
1441
+ }
1442
+ else {
1443
+ inst[hostField] = matchVal === '' ? '__vmz_off__' : '';
1444
+ if (inst[hostField] === matchVal)
1445
+ inst[hostField] = undefined;
1446
+ }
1447
+ offVal = String(get.call(inst) ?? '');
1448
+ }
1449
+ catch {
1450
+ onVal = '';
1451
+ offVal = '';
1452
+ }
1453
+ finally {
1454
+ inst[hostField] = prev;
1455
+ inst.__vmzQuiet = quiet;
1456
+ }
1457
+ return { onVal, offVal };
1458
+ };
1459
+ const sealBlueprintDispatchers = () => {
1460
+ if (!blueprint || blueprintBindIds)
1461
+ return;
1462
+ /** @type {Set<string>} */
1463
+ const ids = new Set();
1464
+ for (const s of blueprint.texts) {
1465
+ if (s.bindingId != null) {
1466
+ ids.add(String(s.bindingId));
1467
+ ensureListDispatcher(s.bindingId, s.deps);
1468
+ }
1469
+ }
1470
+ for (const s of blueprint.attrs) {
1471
+ if (s.bindingId != null) {
1472
+ ids.add(String(s.bindingId));
1473
+ ensureListDispatcher(s.bindingId, s.deps);
1474
+ }
1475
+ for (const d of s.deps || []) {
1476
+ if (!d || d.includes('.*') || (d.includes('[') && d.includes(']')))
1477
+ continue;
1478
+ const rootField = depRootField(d) || d;
1479
+ if (rootField && rootField.indexOf('.') < 0)
1480
+ ensureHostDispatcher(rootField);
1481
+ }
1482
+ }
1483
+ for (const s of blueprint.ons) {
1484
+ needDelegate(s.type);
1485
+ }
1486
+ blueprintBindIds = ids;
1487
+ blueprint.bindIds = ids;
1488
+ compileBlueprintKernels();
1489
+ };
1490
+ const compileBlueprintKernels = () => {
1491
+ if (!blueprint || hydrateBp)
1492
+ return;
1493
+ const textSlots = blueprint.texts;
1494
+ const attrSlots = blueprint.attrs;
1495
+ const onSlots = blueprint.ons;
1496
+ const nText = textSlots.length;
1497
+ const nAttr = attrSlots.length;
1498
+ const nOn = onSlots.length;
1499
+ // Fallback only (no compile-time rowKernel). Field/path walks come from
1500
+ // recorded slots — shape-specific kernels belong in row_kernel.rs.
1501
+ hydrateBp = (root, entry) => {
1502
+ const item = entry && entry.item != null ? entry.item : entry;
1503
+ root.__vmzBox = item;
1504
+ /** @type {Array<Text>} */
1505
+ const textNodes = new Array(nText);
1506
+ /** @type {Array<Element>} */
1507
+ const attrEls = new Array(nAttr);
1508
+ for (let i = 0; i < nText; i++)
1509
+ textNodes[i] = /** @type {Text} */ (textSlots[i].get(root));
1510
+ for (let i = 0; i < nAttr; i++)
1511
+ attrEls[i] = attrSlots[i].get(root);
1512
+ for (let i = 0; i < nOn; i++) {
1513
+ const el = onSlots[i].get(root);
1514
+ if (!el.__vmzAct) {
1515
+ el.__vmzAct = onSlots[i].method;
1516
+ }
1517
+ }
1518
+ for (let i = 0; i < nText; i++) {
1519
+ const v = item == null ? '' : item[textSlots[i].field];
1520
+ textNodes[i].nodeValue = v == null ? '' : v + '';
1521
+ }
1522
+ for (let i = 0; i < nAttr; i++) {
1523
+ const s = attrSlots[i];
1524
+ const el = attrEls[i];
1525
+ const host = s.hostField;
1526
+ const itemKey = s.itemField;
1527
+ if (!host || !itemKey)
1528
+ continue;
1529
+ const hv = inst[host];
1530
+ if (hv != null && item && hv === item[itemKey]) {
1531
+ if (s.name === 'class' || s.name === 'className')
1532
+ el.className = s.onVal;
1533
+ else
1534
+ applyDomAttr(el, s.name, s.onVal);
1535
+ }
1536
+ else if (s.offVal) {
1537
+ if (s.name === 'class' || s.name === 'className')
1538
+ el.className = s.offVal;
1539
+ else
1540
+ applyDomAttr(el, s.name, s.offVal);
1541
+ }
1542
+ }
1543
+ entry.tn = textNodes;
1544
+ entry.ae = attrEls;
1545
+ entry.dom = root;
1546
+ entry.bp = true;
1547
+ };
1548
+ applyBp = (entry) => {
1549
+ const item = entry.item != null ? entry.item : entry.__vmzBox;
1550
+ const textNodes = entry.tn;
1551
+ const attrEls = entry.ae;
1552
+ for (let i = 0; i < nText; i++) {
1553
+ const v = item == null ? '' : item[textSlots[i].field];
1554
+ textNodes[i].nodeValue = v == null ? '' : v + '';
1555
+ }
1556
+ for (let i = 0; i < nAttr; i++) {
1557
+ const s = attrSlots[i];
1558
+ const el = attrEls[i];
1559
+ if (!s.hostField || !s.itemField)
1560
+ continue;
1561
+ const raw = item && inst[s.hostField] === item[s.itemField] ? s.onVal : s.offVal;
1562
+ if (s.name === 'class' || s.name === 'className')
1563
+ el.className = raw;
1564
+ else
1565
+ applyDomAttr(el, s.name, raw);
1566
+ }
1567
+ };
1568
+ };
1569
+ const wireBlueprintItem = (root, box, patches) => {
1570
+ if (!blueprint)
1571
+ return null;
1572
+ sealBlueprintDispatchers();
1573
+ const entry = {
1574
+ item: box.item,
1575
+ index: box.index,
1576
+ dom: root,
1577
+ bp: true,
1578
+ t0: null,
1579
+ t1: null,
1580
+ a0: null,
1581
+ tn: null,
1582
+ ae: null,
1583
+ patches: patches || null,
1584
+ };
1585
+ hydrateBp(root, entry);
1586
+ if (patches) {
1587
+ const applyAll = () => applyBp(entry);
1588
+ applyAll.__vmzBindingIds = blueprintBindIds;
1589
+ applyAll.__vmzBindingId = null;
1590
+ applyAll.__vmzItemLocal = true;
1591
+ applyAll.__vmzBpEntry = entry;
1592
+ patches.push(applyAll);
1593
+ }
1594
+ return entry;
1595
+ };
1596
+ const recordFirstItem = (api, box, patches) => {
1597
+ /** @type {Element | null} */
1598
+ let root = null;
1599
+ /** Pending slots keep live node refs — Direct emit binds before appendChild. */
1600
+ /** @type {{ texts: any[], attrs: any[], ons: any[] }} */
1601
+ const pending = { texts: [], attrs: [], ons: [] };
1602
+ let recordFailed = false;
1603
+ const recordingApi = Object.assign({}, api, {
1604
+ el(tag) {
1605
+ const el = api.el(tag);
1606
+ if (!root)
1607
+ root = el;
1608
+ return el;
1609
+ },
1610
+ // Capture only — do not wireDirectBind (would orphan first-row binders).
1611
+ bindText(i, bindingId, deps, get, textNode, cf) {
1612
+ if (!root)
1613
+ return;
1614
+ // Blueprint recording aborted: fall back to normal wiring for remaining binds.
1615
+ if (recordFailed) {
1616
+ api.bindText(i, bindingId, deps, get, textNode, cf);
1617
+ return;
1618
+ }
1619
+ try {
1620
+ const raw = get.call(inst);
1621
+ if (textNode.nodeType === 3) /** @type {Text} */
1622
+ (textNode).nodeValue = String(raw ?? '');
1623
+ else
1624
+ textNode.textContent = String(raw ?? '');
1625
+ }
1626
+ catch {
1627
+ /* ignore */
1628
+ }
1629
+ pending.texts.push({
1630
+ node: textNode,
1631
+ bindingId,
1632
+ deps: Array.isArray(deps) ? deps.slice() : [],
1633
+ getFn: get,
1634
+ });
1635
+ },
1636
+ bindAttr(i, bindingId, deps, get, el, name, cf) {
1637
+ if (!root)
1638
+ return;
1639
+ if (recordFailed) {
1640
+ api.bindAttr(i, bindingId, deps, get, el, name, cf);
1641
+ return;
1642
+ }
1643
+ // Class bind eligible when deps include a bare host field (any name).
1644
+ const hasHostDep = (deps || []).some((d) => d && !d.includes('.*') && !d.includes('[') && String(d).indexOf('.') < 0);
1645
+ if ((name === 'class' || name === 'className') && hasHostDep) {
1646
+ try {
1647
+ const raw = get.call(inst);
1648
+ el.className = raw == null ? '' : String(raw);
1649
+ }
1650
+ catch {
1651
+ /* ignore */
1652
+ }
1653
+ pending.attrs.push({
1654
+ node: el,
1655
+ bindingId,
1656
+ deps: Array.isArray(deps) ? deps.slice() : [],
1657
+ name,
1658
+ getFn: get,
1659
+ });
1660
+ }
1661
+ else {
1662
+ recordFailed = true;
1663
+ api.bindAttr(i, bindingId, deps, get, el, name, cf);
1664
+ }
1665
+ },
1666
+ on(el, type, handler) {
1667
+ if (recordFailed) {
1668
+ api.on(el, type, handler);
1669
+ return;
1670
+ }
1671
+ const parsed = parseActionMethod(handler);
1672
+ if (parsed && root) {
1673
+ pending.ons.push({
1674
+ node: el,
1675
+ type,
1676
+ method: parsed.method,
1677
+ argField: parsed.argField,
1678
+ });
1679
+ actionArgFields[parsed.method] = parsed.argField;
1680
+ if (rowActArgField == null)
1681
+ rowActArgField = parsed.argField;
1682
+ root.__vmzBox = box;
1683
+ el.__vmzAct = parsed.method;
1684
+ // Expando only — AttributeMap tax on every row is worse than a per-row write.
1685
+ needDelegate(type);
1686
+ return;
1687
+ }
1688
+ api.on(el, type, handler);
1689
+ recordFailed = true;
1690
+ },
1691
+ });
1692
+ const dom = userCreateItem.call(inst, recordingApi, box);
1693
+ if (!recordFailed && dom && dom.nodeType === 1 && root === dom && (pending.texts.length > 0 || pending.attrs.length > 0)) {
1694
+ /** @type {any[]} */
1695
+ const texts = [];
1696
+ /** @type {any[]} */
1697
+ const attrs = [];
1698
+ /** @type {any[]} */
1699
+ const ons = [];
1700
+ for (const p of pending.texts) {
1701
+ const path = pathFromRoot(root, p.node);
1702
+ const field = probeItemField(p.getFn, box);
1703
+ if (!path || !field) {
1704
+ recordFailed = true;
1705
+ break;
1706
+ }
1707
+ texts.push({
1708
+ path,
1709
+ bindingId: p.bindingId,
1710
+ deps: p.deps,
1711
+ field,
1712
+ get: makeChildGetter(path),
1713
+ });
1714
+ }
1715
+ if (!recordFailed) {
1716
+ for (const p of pending.attrs) {
1717
+ const path = pathFromRoot(root, p.node);
1718
+ if (!path) {
1719
+ recordFailed = true;
1720
+ break;
1721
+ }
1722
+ const hostField = (() => {
1723
+ for (const d of p.deps || []) {
1724
+ if (!d)
1725
+ continue;
1726
+ if (d.includes('.*') || d.includes('['))
1727
+ continue;
1728
+ if (String(d).indexOf('.') < 0)
1729
+ return String(d);
1730
+ }
1731
+ return null;
1732
+ })();
1733
+ let itemField = null;
1734
+ for (const d of p.deps || []) {
1735
+ if (!d)
1736
+ continue;
1737
+ if (d.includes('.*')) {
1738
+ const m = String(d).match(/\*\.([A-Za-z_$][\w$]*)$/);
1739
+ if (m)
1740
+ itemField = m[1];
1741
+ }
1742
+ }
1743
+ if (!hostField || !itemField) {
1744
+ recordFailed = true;
1745
+ break;
1746
+ }
1747
+ const { onVal, offVal } = probeHostItemClass(p.getFn, box, hostField, itemField);
1748
+ attrs.push({
1749
+ path,
1750
+ bindingId: p.bindingId,
1751
+ deps: p.deps,
1752
+ name: p.name,
1753
+ onVal,
1754
+ offVal,
1755
+ hostField,
1756
+ itemField,
1757
+ get: makeChildGetter(path),
1758
+ });
1759
+ }
1760
+ }
1761
+ if (!recordFailed) {
1762
+ for (const p of pending.ons) {
1763
+ const path = pathFromRoot(root, p.node);
1764
+ if (!path) {
1765
+ recordFailed = true;
1766
+ break;
1767
+ }
1768
+ ons.push({
1769
+ path,
1770
+ type: p.type,
1771
+ method: p.method,
1772
+ get: makeChildGetter(path),
1773
+ });
1774
+ }
1775
+ }
1776
+ if (!recordFailed) {
1777
+ const tpl = /** @type {Element} */ (dom.cloneNode(true));
1778
+ clearDomEvt(tpl);
1779
+ blueprint = {
1780
+ tpl,
1781
+ texts,
1782
+ attrs,
1783
+ ons,
1784
+ bindIds: new Set(),
1785
+ };
1786
+ }
1787
+ }
1788
+ if (!blueprint)
1789
+ blueprintOk = false;
1790
+ return dom;
1791
+ };
1792
+ const createItem = (api, box, patches) => {
1793
+ if (blueprint && blueprintOk) {
1794
+ const root = /** @type {Element} */ (blueprint.tpl.cloneNode(true));
1795
+ wireBlueprintItem(root, box, patches);
1796
+ return root;
1797
+ }
1798
+ if (typeof userCreateItem !== 'function') {
1799
+ throw new Error('vmz:dom eachBlock createItem missing (no rowKernel fallback)');
1800
+ }
1801
+ if (blueprintOk) {
1802
+ const dom = recordFirstItem(api, box, patches);
1803
+ if (blueprint && dom && dom.nodeType === 1) {
1804
+ patches.length = 0;
1805
+ wireBlueprintItem(dom, box, patches);
1806
+ }
1807
+ return dom;
1808
+ }
1809
+ return userCreateItem.call(inst, api, box);
1810
+ };
1811
+ /**
1812
+ * Reorder / place item DOM with minimal mutations.
1813
+ * - already-correct → no-op
1814
+ * - pure 2-node swap → 1–2 insertBefore (common keyed list swap)
1815
+ * - append prefix → Fragment insert only new tail
1816
+ * - create / replace / complex → Fragment rebuild before `end`
1817
+ */
1818
+ const reconcileDomOrder = (nextNodes) => {
1819
+ const parent = end.parentNode;
1820
+ if (!parent)
1821
+ return;
1822
+ /** @type {ChildNode[]} */
1823
+ const curr = [];
1824
+ for (let n = start.nextSibling; n && n !== end; n = n.nextSibling) {
1825
+ curr.push(n);
1826
+ }
1827
+ if (curr.length === nextNodes.length) {
1828
+ let same = true;
1829
+ for (let i = 0; i < curr.length; i++) {
1830
+ if (curr[i] !== nextNodes[i]) {
1831
+ same = false;
1832
+ break;
1833
+ }
1834
+ }
1835
+ if (same)
1836
+ return;
1837
+ // Fast path: exactly two positions swapped (benchmark swaprows).
1838
+ /** @type {number[]} */
1839
+ const diff = [];
1840
+ for (let i = 0; i < curr.length; i++) {
1841
+ if (curr[i] !== nextNodes[i])
1842
+ diff.push(i);
1843
+ }
1844
+ if (diff.length === 2 && curr[diff[0]] === nextNodes[diff[1]] && curr[diff[1]] === nextNodes[diff[0]]) {
1845
+ const a = curr[diff[0]];
1846
+ const b = curr[diff[1]];
1847
+ const aNext = a.nextSibling;
1848
+ const bNext = b.nextSibling;
1849
+ noteDomMove();
1850
+ noteDomMove();
1851
+ if (aNext === b) {
1852
+ parent.insertBefore(b, a);
1853
+ }
1854
+ else if (bNext === a) {
1855
+ parent.insertBefore(a, b);
1856
+ }
1857
+ else {
1858
+ parent.insertBefore(b, aNext);
1859
+ parent.insertBefore(a, bNext);
1860
+ }
1861
+ return;
1862
+ }
1863
+ }
1864
+ // Append-only: existing live prefix unchanged, only new tail detached.
1865
+ if (curr.length < nextNodes.length && curr.length > 0) {
1866
+ let prefix = true;
1867
+ for (let i = 0; i < curr.length; i++) {
1868
+ if (curr[i] !== nextNodes[i]) {
1869
+ prefix = false;
1870
+ break;
1871
+ }
1872
+ }
1873
+ if (prefix) {
1874
+ const batch = document.createDocumentFragment();
1875
+ for (let i = curr.length; i < nextNodes.length; i++) {
1876
+ batch.appendChild(nextNodes[i]);
1877
+ }
1878
+ parent.insertBefore(batch, end);
1879
+ return;
1880
+ }
1881
+ }
1882
+ // Create / replace / complex reorder: one Fragment write.
1883
+ const batch = document.createDocumentFragment();
1884
+ for (const dom of nextNodes) {
1885
+ if (dom.parentNode)
1886
+ noteDomMove();
1887
+ batch.appendChild(dom);
1888
+ }
1889
+ parent.insertBefore(batch, end);
1890
+ };
1891
+ const apply = () => {
1892
+ if (inst.__vmzDestroyed)
1893
+ return;
1894
+ const applied = ++gen;
1895
+ const list = readList();
1896
+ const n = list.length;
1897
+ // Clear all rows.
1898
+ if (n === 0) {
1899
+ if (keyed.size)
1900
+ fastWipeRows();
1901
+ return;
1902
+ }
1903
+ // Full replace (no key reuse): wipe then fall into fresh create.
1904
+ if (keyed.size > 0 && hasRowKernel) {
1905
+ let reuse = false;
1906
+ for (let i = 0; i < n; i++) {
1907
+ if (keyed.has(rowKeyOf(list[i], i))) {
1908
+ reuse = true;
1909
+ break;
1910
+ }
1911
+ }
1912
+ if (!reuse)
1913
+ fastWipeRows();
1914
+ }
1915
+ // Fresh create into empty each: rowKernel skips blueprint recording (tpl from html).
1916
+ if (keyed.size === 0 && n > 0) {
1917
+ const parent = end.parentNode;
1918
+ const batch = document.createDocumentFragment();
1919
+ let startIdx = 0;
1920
+ let indexFilled = false;
1921
+ // Only record a runtime blueprint when there is no compile-time rowKernel.
1922
+ if ((!blueprint || !blueprintOk) && !hasRowKernel) {
1923
+ const box0 = { item: list[0], index: 0 };
1924
+ const patches0 = [];
1925
+ const prevPatches = directApi._itemPatches;
1926
+ const prevCtx = directApi._eachCtx;
1927
+ directApi._itemPatches = patches0;
1928
+ directApi._eachCtx = eachCtx;
1929
+ let dom0 = null;
1930
+ try {
1931
+ dom0 = createItem(directApi, box0, patches0);
1932
+ }
1933
+ finally {
1934
+ directApi._itemPatches = prevPatches;
1935
+ directApi._eachCtx = prevCtx;
1936
+ }
1937
+ if (applied !== gen || inst.__vmzDestroyed)
1938
+ return;
1939
+ if (!dom0)
1940
+ return;
1941
+ const k0 = itemKey(box0);
1942
+ if (dom0.nodeType === 1) /** @type {Element} */
1943
+ (dom0).__vmzKey = k0;
1944
+ // First row may already be blueprint-wired (patches cleared + hydrate).
1945
+ let entry0 = keyed.get(k0);
1946
+ if (!entry0) {
1947
+ if (patches0.length && patches0[0] && patches0[0].__vmzBpEntry) {
1948
+ entry0 = patches0[0].__vmzBpEntry;
1949
+ entry0.patches = patches0;
1950
+ }
1951
+ else if (blueprint && blueprintOk) {
1952
+ entry0 = wireBlueprintItem(/** @type {Element} */ (dom0), box0, patches0);
1953
+ }
1954
+ else {
1955
+ tagItemPatches(patches0, 0);
1956
+ entry0 = { box: box0, dom: dom0, patches: patches0 };
1957
+ }
1958
+ keyed.set(k0, entry0);
1959
+ }
1960
+ batch.appendChild(dom0);
1961
+ startIdx = 1;
1962
+ }
1963
+ if (blueprint && blueprintOk) {
1964
+ sealBlueprintDispatchers();
1965
+ const tpl = blueprint.tpl;
1966
+ if (hasRowKernel && spec.rowKernel && typeof spec.rowKernel.create === 'function') {
1967
+ // Shape-specific create loop is Rust-emitted (rowKernel.create).
1968
+ // Direct parent.insertBefore (no Fragment). When parent has only the
1969
+ // each markers as children, detach parent for the fill then reattach —
1970
+ // same structural trick as hand-tuned keyed apps (not app-specific).
1971
+ if (parent) {
1972
+ let detached = null;
1973
+ let reinsertAt = null;
1974
+ if (parent.nodeType === 1 && parent.parentNode) {
1975
+ let onlyMarkers = true;
1976
+ for (let c = parent.firstChild; c; c = c.nextSibling) {
1977
+ if (c !== start && c !== end) {
1978
+ onlyMarkers = false;
1979
+ break;
1980
+ }
1981
+ }
1982
+ if (onlyMarkers) {
1983
+ detached = parent.parentNode;
1984
+ reinsertAt = parent.nextSibling;
1985
+ detached.removeChild(parent);
1986
+ }
1987
+ }
1988
+ // Fill entryByIndex in the create loop — skip post-pass Map.get rebuild.
1989
+ entryByIndex = new Array(n);
1990
+ for (let i = 0; i < startIdx; i++) {
1991
+ entryByIndex[i] = keyed.get(rowKeyOf(list[i], i));
1992
+ }
1993
+ spec.rowKernel.create.call(inst, list, startIdx, tpl, keyed, parent, end, rowKeyOf, entryByIndex);
1994
+ if (detached)
1995
+ detached.insertBefore(parent, reinsertAt);
1996
+ indexFilled = true;
1997
+ }
1998
+ else {
1999
+ const hydrate = spec.rowKernel.hydrate;
2000
+ for (let i = startIdx; i < n; i++) {
2001
+ if (applied !== gen || inst.__vmzDestroyed)
2002
+ return;
2003
+ const item = list[i];
2004
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2005
+ hydrate.call(inst, root, item);
2006
+ const k = rowKeyOf(item, i);
2007
+ root.__vmzKey = k;
2008
+ keyed.set(k, root);
2009
+ batch.appendChild(root);
2010
+ }
2011
+ }
2012
+ }
2013
+ else if (hasRowKernel && hydrateBp) {
2014
+ const hydrate = spec.rowKernel.hydrate;
2015
+ for (let i = startIdx; i < n; i++) {
2016
+ if (applied !== gen || inst.__vmzDestroyed)
2017
+ return;
2018
+ const item = list[i];
2019
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2020
+ hydrate.call(inst, root, item);
2021
+ const k = rowKeyOf(item, i);
2022
+ root.__vmzKey = k;
2023
+ keyed.set(k, root);
2024
+ batch.appendChild(root);
2025
+ }
2026
+ }
2027
+ else {
2028
+ for (let i = startIdx; i < n; i++) {
2029
+ if (applied !== gen || inst.__vmzDestroyed)
2030
+ return;
2031
+ const item = list[i];
2032
+ const k = keyOf(item, i);
2033
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2034
+ const entry = {
2035
+ item,
2036
+ index: i,
2037
+ dom: root,
2038
+ bp: true,
2039
+ t0: null,
2040
+ t1: null,
2041
+ a0: null,
2042
+ patches: null,
2043
+ };
2044
+ hydrateBp(root, entry);
2045
+ root.__vmzKey = k;
2046
+ keyed.set(k, entry);
2047
+ batch.appendChild(root);
2048
+ }
2049
+ }
2050
+ }
2051
+ else {
2052
+ for (let i = startIdx; i < n; i++) {
2053
+ if (applied !== gen || inst.__vmzDestroyed)
2054
+ return;
2055
+ const box = { item: list[i], index: i };
2056
+ const k = itemKey(box);
2057
+ const patches = [];
2058
+ const prevPatches = directApi._itemPatches;
2059
+ const prevCtx = directApi._eachCtx;
2060
+ directApi._itemPatches = patches;
2061
+ directApi._eachCtx = eachCtx;
2062
+ let dom = null;
2063
+ try {
2064
+ dom = createItem(directApi, box, patches);
2065
+ }
2066
+ finally {
2067
+ directApi._itemPatches = prevPatches;
2068
+ directApi._eachCtx = prevCtx;
2069
+ }
2070
+ if (applied !== gen || inst.__vmzDestroyed)
2071
+ return;
2072
+ if (!dom)
2073
+ continue;
2074
+ tagItemPatches(patches, i);
2075
+ if (dom.nodeType === 1) /** @type {Element} */
2076
+ (dom).__vmzKey = k;
2077
+ keyed.set(k, { box, dom, patches });
2078
+ batch.appendChild(dom);
2079
+ }
2080
+ }
2081
+ if (applied !== gen || inst.__vmzDestroyed)
2082
+ return;
2083
+ if (parent && batch.firstChild)
2084
+ parent.insertBefore(batch, end);
2085
+ if (hasRowKernel && !indexFilled)
2086
+ rebuildEntryByIndex(list);
2087
+ if (end.isConnected)
2088
+ ensureDelegateAttached();
2089
+ else
2090
+ queueMicrotask(() => {
2091
+ if (!inst.__vmzDestroyed)
2092
+ ensureDelegateAttached();
2093
+ });
2094
+ return;
2095
+ }
2096
+ // Pure identity transposition (slice + two index swaps + list replace):
2097
+ // same n entries, exactly two positions exchanged — O(n) identity scan + 2 DOM moves.
2098
+ // Skips Map/key/nextNodes/sibling rebuild (main swap script cost vs hand-tuned keyed apps).
2099
+ if (hasRowKernel && entryByIndex.length === n && keyed.size === n && n > 0) {
2100
+ let missA = -1;
2101
+ let missB = -1;
2102
+ let identityOk = true;
2103
+ for (let i = 0; i < n; i++) {
2104
+ const entry = entryByIndex[i];
2105
+ if (!entry || entry.nodeType !== 1) {
2106
+ identityOk = false;
2107
+ break;
2108
+ }
2109
+ if (entry.__vmzBox === list[i])
2110
+ continue;
2111
+ if (missA < 0)
2112
+ missA = i;
2113
+ else if (missB < 0)
2114
+ missB = i;
2115
+ else {
2116
+ identityOk = false;
2117
+ break;
2118
+ }
2119
+ }
2120
+ if (identityOk) {
2121
+ if (missA < 0) {
2122
+ // Same order / same object refs — replace was a no-op for DOM.
2123
+ return;
2124
+ }
2125
+ if (missB >= 0) {
2126
+ const ea = entryByIndex[missA];
2127
+ const eb = entryByIndex[missB];
2128
+ if (ea.__vmzBox === list[missB] && eb.__vmzBox === list[missA]) {
2129
+ const parent = end.parentNode;
2130
+ if (parent) {
2131
+ const aNext = ea.nextSibling;
2132
+ const bNext = eb.nextSibling;
2133
+ noteDomMove();
2134
+ noteDomMove();
2135
+ if (aNext === eb) {
2136
+ parent.insertBefore(eb, ea);
2137
+ }
2138
+ else if (bNext === ea) {
2139
+ parent.insertBefore(ea, eb);
2140
+ }
2141
+ else {
2142
+ parent.insertBefore(eb, aNext);
2143
+ parent.insertBefore(ea, bNext);
2144
+ }
2145
+ }
2146
+ entryByIndex[missA] = eb;
2147
+ entryByIndex[missB] = ea;
2148
+ if (end.isConnected)
2149
+ ensureDelegateAttached();
2150
+ else
2151
+ queueMicrotask(() => {
2152
+ if (!inst.__vmzDestroyed)
2153
+ ensureDelegateAttached();
2154
+ });
2155
+ return;
2156
+ }
2157
+ }
2158
+ }
2159
+ }
2160
+ const seen = new Set();
2161
+ /** @type {Node[]} */
2162
+ const nextNodes = [];
2163
+ for (let i = 0; i < n; i++) {
2164
+ if (applied !== gen || inst.__vmzDestroyed)
2165
+ return;
2166
+ const item = list[i];
2167
+ const k = rowKeyOf(item, i);
2168
+ seen.add(k);
2169
+ let entry = keyed.get(k);
2170
+ if (!entry) {
2171
+ if (hasRowKernel && blueprint && blueprintOk && hydrateBp) {
2172
+ const root = /** @type {Element} */ (blueprint.tpl.cloneNode(true));
2173
+ spec.rowKernel.hydrate.call(inst, root, item);
2174
+ root.__vmzKey = k;
2175
+ keyed.set(k, root);
2176
+ entry = root;
2177
+ }
2178
+ else {
2179
+ const box = { item, index: i };
2180
+ const patches = [];
2181
+ const prevPatches = directApi._itemPatches;
2182
+ const prevCtx = directApi._eachCtx;
2183
+ directApi._itemPatches = patches;
2184
+ directApi._eachCtx = eachCtx;
2185
+ let dom = null;
2186
+ try {
2187
+ dom = createItem(directApi, box, patches);
2188
+ }
2189
+ finally {
2190
+ directApi._itemPatches = prevPatches;
2191
+ directApi._eachCtx = prevCtx;
2192
+ }
2193
+ if (applied !== gen || inst.__vmzDestroyed)
2194
+ return;
2195
+ tagItemPatches(patches, i);
2196
+ if (dom) {
2197
+ if (dom.nodeType === 1) {
2198
+ // Client identity: expando only (see 01 each identity). SSR uses data-vmz-key.
2199
+ /** @type {Element} */ (dom).__vmzKey = k;
2200
+ }
2201
+ entry = { box, dom, patches };
2202
+ keyed.set(k, entry);
2203
+ }
2204
+ }
2205
+ }
2206
+ else {
2207
+ const sameItem = entryItem(entry) === item;
2208
+ if (entry.nodeType === 1) {
2209
+ entry.__vmzBox = item;
2210
+ }
2211
+ else if (entry.bp) {
2212
+ entry.item = item;
2213
+ entry.index = i;
2214
+ if (entry.dom)
2215
+ entry.dom.__vmzBox = entry.item;
2216
+ }
2217
+ else {
2218
+ entry.box.item = item;
2219
+ entry.box.index = i;
2220
+ tagItemPatches(entry.patches, i);
2221
+ }
2222
+ // Pure reorder (swap / move) keeps object identity — skip leaf patches.
2223
+ if (!sameItem) {
2224
+ if (entryIsBp(entry) && applyBp) {
2225
+ try {
2226
+ applyBp(entry);
2227
+ }
2228
+ catch (err) {
2229
+ console.error('vmz:dom each item', err);
2230
+ }
2231
+ }
2232
+ else if (entry.patches) {
2233
+ for (const p of entry.patches)
2234
+ runPatch(p, null);
2235
+ }
2236
+ }
2237
+ }
2238
+ if (entry)
2239
+ nextNodes.push(entryDom(entry));
2240
+ }
2241
+ if (applied !== gen || inst.__vmzDestroyed)
2242
+ return;
2243
+ for (const [k, entry] of [...keyed.entries()]) {
2244
+ if (seen.has(k))
2245
+ continue;
2246
+ noteDomRemove();
2247
+ const dom = entryDom(entry);
2248
+ if (hasRowKernel) {
2249
+ if (dom && dom.parentNode)
2250
+ dom.remove();
2251
+ }
2252
+ else {
2253
+ clearDomEvt(dom);
2254
+ disposeDomTree(dom);
2255
+ if (dom && dom.parentNode)
2256
+ dom.remove();
2257
+ }
2258
+ keyed.delete(k);
2259
+ releaseBpEntry(entry);
2260
+ }
2261
+ reconcileDomOrder(nextNodes);
2262
+ if (hasRowKernel)
2263
+ rebuildEntryByIndex(list);
2264
+ // First apply may run while start/end still sit in a DocumentFragment
2265
+ // (before mount appends). Defer until connected so clicks work.
2266
+ if (end.isConnected)
2267
+ ensureDelegateAttached();
2268
+ else
2269
+ queueMicrotask(() => {
2270
+ if (!inst.__vmzDestroyed)
2271
+ ensureDelegateAttached();
2272
+ });
2273
+ };
2274
+ registerBind(inst, deps || [], apply, bindingId);
2275
+ if (directApi._itemPatches)
2276
+ directApi._itemPatches.push(apply);
2277
+ // WriteBarrier `__vmzListTranspose` → O(1) entry/DOM exchange (no slice + replace flush).
2278
+ const listRootForTranspose = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || null;
2279
+ const transposeEntries = (ia, ib) => {
2280
+ if (!hasRowKernel)
2281
+ return false;
2282
+ const n = entryByIndex.length;
2283
+ if (ia < 0 || ib < 0 || ia >= n || ib >= n || ia === ib)
2284
+ return false;
2285
+ const ea = entryByIndex[ia];
2286
+ const eb = entryByIndex[ib];
2287
+ if (!ea || !eb || ea.nodeType !== 1 || eb.nodeType !== 1)
2288
+ return false;
2289
+ const parent = end.parentNode;
2290
+ if (parent) {
2291
+ const aNext = ea.nextSibling;
2292
+ const bNext = eb.nextSibling;
2293
+ noteDomMove();
2294
+ noteDomMove();
2295
+ if (aNext === eb) {
2296
+ parent.insertBefore(eb, ea);
2297
+ }
2298
+ else if (bNext === ea) {
2299
+ parent.insertBefore(ea, eb);
2300
+ }
2301
+ else {
2302
+ parent.insertBefore(eb, aNext);
2303
+ parent.insertBefore(ea, bNext);
2304
+ }
2305
+ }
2306
+ entryByIndex[ia] = eb;
2307
+ entryByIndex[ib] = ea;
2308
+ return true;
2309
+ };
2310
+ /** Inline leaf apply for event write path (mutate + DOM, vanillajs-shaped). */
2311
+ const applyLeafAt = (idx, field, item) => {
2312
+ if (!hasRowKernel || !rkApplyByField)
2313
+ return false;
2314
+ const entry = entryByIndex[idx];
2315
+ if (!entry || entry.nodeType !== 1)
2316
+ return false;
2317
+ const f = rkApplyByField[field];
2318
+ if (typeof f !== 'function')
2319
+ return false;
2320
+ if (entry.__vmzBox !== item)
2321
+ entry.__vmzBox = item;
2322
+ if (rkHostFieldSet.has(field))
2323
+ f.call(inst, entry, item);
2324
+ else
2325
+ f(entry, item);
2326
+ return true;
2327
+ };
2328
+ /**
2329
+ * Own the whole stride loop (update-every-Nth): one cross-boundary call,
2330
+ * hoist text slot / applyByField, specialize string `+=`.
2331
+ */
2332
+ const compoundStride = (leaf, op, rhs, start, step) => {
2333
+ if (!hasRowKernel)
2334
+ return false;
2335
+ const arr = inst[listRootForTranspose];
2336
+ if (!Array.isArray(arr))
2337
+ return false;
2338
+ const s = +start || 0;
2339
+ const st = +step || 0;
2340
+ if (st <= 0)
2341
+ return false;
2342
+ const entries = entryByIndex;
2343
+ const n = arr.length;
2344
+ // Fastest path: text-only leaf → mutate + __vmzT[slot].nodeValue (no applyByField).
2345
+ const slot = rkTextSlots ? rkTextSlots[leaf] : undefined;
2346
+ if (op === '+' && slot != null && !rkHostFieldSet.has(leaf)) {
2347
+ for (let i = s; i < n; i += st) {
2348
+ const item = arr[i];
2349
+ if (item == null || typeof item !== 'object')
2350
+ continue;
2351
+ const v = item[leaf] + rhs;
2352
+ item[leaf] = v;
2353
+ const entry = entries[i];
2354
+ const texts = entry && entry.nodeType === 1 ? entry.__vmzT : null;
2355
+ if (texts)
2356
+ texts[slot].nodeValue = v;
2357
+ }
2358
+ return true;
2359
+ }
2360
+ if (!rkApplyByField)
2361
+ return false;
2362
+ const f = rkApplyByField[leaf];
2363
+ if (typeof f !== 'function')
2364
+ return false;
2365
+ const needsThis = rkHostFieldSet.has(leaf);
2366
+ if (op === '+') {
2367
+ if (needsThis) {
2368
+ for (let i = s; i < n; i += st) {
2369
+ const item = arr[i];
2370
+ if (item == null || typeof item !== 'object')
2371
+ continue;
2372
+ item[leaf] = item[leaf] + rhs;
2373
+ const entry = entries[i];
2374
+ if (!entry || entry.nodeType !== 1)
2375
+ continue;
2376
+ if (entry.__vmzBox !== item)
2377
+ entry.__vmzBox = item;
2378
+ f.call(inst, entry, item);
2379
+ }
2380
+ }
2381
+ else {
2382
+ for (let i = s; i < n; i += st) {
2383
+ const item = arr[i];
2384
+ if (item == null || typeof item !== 'object')
2385
+ continue;
2386
+ item[leaf] = item[leaf] + rhs;
2387
+ const entry = entries[i];
2388
+ if (entry && entry.nodeType === 1)
2389
+ f(entry, item);
2390
+ }
2391
+ }
2392
+ return true;
2393
+ }
2394
+ for (let i = s; i < n; i += st) {
2395
+ const item = arr[i];
2396
+ if (item == null || typeof item !== 'object')
2397
+ continue;
2398
+ const cur = item[leaf];
2399
+ const value = applyCompoundOp(op, cur, rhs);
2400
+ if (Object.is(cur, value))
2401
+ continue;
2402
+ item[leaf] = value;
2403
+ if (slot != null && !needsThis) {
2404
+ const entry = entries[i];
2405
+ const texts = entry && entry.nodeType === 1 ? entry.__vmzT : null;
2406
+ if (texts) {
2407
+ texts[slot].nodeValue = value;
2408
+ continue;
2409
+ }
2410
+ }
2411
+ const entry = entries[i];
2412
+ if (!entry || entry.nodeType !== 1)
2413
+ continue;
2414
+ if (entry.__vmzBox !== item)
2415
+ entry.__vmzBox = item;
2416
+ if (needsThis)
2417
+ f.call(inst, entry, item);
2418
+ else
2419
+ f(entry, item);
2420
+ }
2421
+ return true;
2422
+ };
2423
+ if (listRootForTranspose) {
2424
+ if (!inst.__vmzEachTranspose)
2425
+ inst.__vmzEachTranspose = Object.create(null);
2426
+ inst.__vmzEachTranspose[listRootForTranspose] = transposeEntries;
2427
+ if (!inst.__vmzEachApplyLeaf)
2428
+ inst.__vmzEachApplyLeaf = Object.create(null);
2429
+ inst.__vmzEachApplyLeaf[listRootForTranspose] = applyLeafAt;
2430
+ if (!inst.__vmzEachCompoundStride)
2431
+ inst.__vmzEachCompoundStride = Object.create(null);
2432
+ inst.__vmzEachCompoundStride[listRootForTranspose] = compoundStride;
2433
+ }
2434
+ start.__vmzDispose = () => {
2435
+ teardownDelegate();
2436
+ fastWipeRows();
2437
+ if (listRootForTranspose && inst.__vmzEachTranspose) {
2438
+ if (inst.__vmzEachTranspose[listRootForTranspose] === transposeEntries) {
2439
+ delete inst.__vmzEachTranspose[listRootForTranspose];
2440
+ }
2441
+ }
2442
+ if (listRootForTranspose && inst.__vmzEachApplyLeaf) {
2443
+ if (inst.__vmzEachApplyLeaf[listRootForTranspose] === applyLeafAt) {
2444
+ delete inst.__vmzEachApplyLeaf[listRootForTranspose];
2445
+ }
2446
+ }
2447
+ if (listRootForTranspose && inst.__vmzEachCompoundStride) {
2448
+ if (inst.__vmzEachCompoundStride[listRootForTranspose] === compoundStride) {
2449
+ delete inst.__vmzEachCompoundStride[listRootForTranspose];
2450
+ }
2451
+ }
2452
+ };
2453
+ const softDeps = [...new Set((deps || []).map((d) => `${depRootField(d)}.*`))];
2454
+ const softRefresh = () => {
2455
+ if (inst.__vmzDestroyed)
2456
+ return;
2457
+ const trie = inst.__vmzFlushTrie;
2458
+ const listRoot = depRootField((deps && deps[0]) || '') || '';
2459
+ // Full list replace is owned by apply(); soft channel is item/structure churn.
2460
+ if (trie && listRoot && trie[listRoot] && trie[listRoot].replace)
2461
+ return;
2462
+ const list = readList();
2463
+ const softKey = softDeps[0] || `${listRoot}.*`;
2464
+ for (let i = 0; i < list.length; i++) {
2465
+ const item = list[i];
2466
+ const k = rowKeyOf(item, i);
2467
+ const entry = keyed.get(k);
2468
+ if (!entry)
2469
+ continue;
2470
+ if (entryIsBp(entry)) {
2471
+ if (entry.nodeType === 1)
2472
+ entry.__vmzBox = item;
2473
+ else {
2474
+ entry.item = item;
2475
+ entry.index = i;
2476
+ if (entry.dom)
2477
+ entry.dom.__vmzBox = item;
2478
+ }
2479
+ if (applyBp)
2480
+ applyBp(entry);
2481
+ else if (hydrateBp)
2482
+ hydrateBp(entryDom(entry), entry);
2483
+ continue;
2484
+ }
2485
+ entry.box.item = item;
2486
+ entry.box.index = i;
2487
+ tagItemPatches(entry.patches, i);
2488
+ for (const p of entry.patches) {
2489
+ // Leaf BindingId patches are owned by list/host dispatchers .
2490
+ if (p.__vmzBindingId != null)
2491
+ continue;
2492
+ if (patchHasBindingId(inst, p))
2493
+ continue;
2494
+ try {
2495
+ runPatch(p, softKey, null);
2496
+ }
2497
+ catch (err) {
2498
+ console.error('vmz:dom each soft', err);
2499
+ }
2500
+ }
2501
+ }
2502
+ };
2503
+ registerBind(inst, softDeps, softRefresh, null);
2504
+ apply();
2505
+ return frag;
2506
+ },
2507
+ };
2508
+ /**
2509
+ * @param {object} inst
2510
+ * @param {string[]} deps
2511
+ * @param {() => any} fn
2512
+ * @param {number|string|null|undefined} bindingId
2513
+ */
2514
+ function trackDirectBind(inst, deps, fn, bindingId = null) {
2515
+ if (directApi._branchBinds) {
2516
+ directApi._branchBinds.push({ deps, fn, bindingId });
2517
+ if (directApi._itemPatches)
2518
+ directApi._itemPatches.push(fn);
2519
+ return;
2520
+ }
2521
+ // item binds stay on entry.patches; eachBlock registers one dispatcher per BindingId.
2522
+ if (directApi._itemPatches) {
2523
+ fn.__vmzItemLocal = true;
2524
+ directApi._itemPatches.push(fn);
2525
+ if (directApi._eachCtx) {
2526
+ directApi._eachCtx.noteItemBind(bindingId, deps || [], fn);
2527
+ }
2528
+ return;
2529
+ }
2530
+ registerBind(inst, deps, fn, bindingId);
2531
+ }
2532
+ /**
2533
+ * @param {object} inst
2534
+ * @param {number|string|null} bindingId
2535
+ * @param {string[]} deps
2536
+ * @param {() => any} get
2537
+ * @param {(raw: any) => void} write
2538
+ * @param {{ stable: string[], branches: Array<{ cond?: => any, deps: string[] }> } | null | undefined} [cf]
2539
+ */
2540
+ function wireDirectBind(inst, bindingId, deps, get, write, cf) {
2541
+ let activeBranch = -1;
2542
+ /** @type {string[]} */
2543
+ let liveDeps = Array.isArray(deps) ? deps.slice() : [];
2544
+ const pickCf = () => {
2545
+ if (!cf || !Array.isArray(cf.branches))
2546
+ return -1;
2547
+ for (let i = 0; i < cf.branches.length; i++) {
2548
+ const b = cf.branches[i];
2549
+ if (!b.cond)
2550
+ return i;
2551
+ try {
2552
+ if (b.cond.call(inst))
2553
+ return i;
2554
+ }
2555
+ catch {
2556
+ /* continue */
2557
+ }
2558
+ }
2559
+ return cf.branches.length - 1;
2560
+ };
2561
+ // Item-local CF whose branches only gate the same stable deps: skip branch switching.
2562
+ let simpleCf = false;
2563
+ if (cf && Array.isArray(cf.branches) && directApi._itemPatches) {
2564
+ simpleCf = cf.branches.every((b) => !b.deps || b.deps.length === 0);
2565
+ }
2566
+ const apply = () => {
2567
+ if (precision.enabled) {
2568
+ precision.bindingEvals++;
2569
+ for (const d of liveDeps || [])
2570
+ bumpMap(precision.bindingEvalsByDep, d);
2571
+ if (bindingId != null) {
2572
+ bumpMap(precision.bindingEvalsByBinding, String(bindingId));
2573
+ }
2574
+ }
2575
+ let raw;
2576
+ try {
2577
+ raw = get.call(inst);
2578
+ }
2579
+ catch {
2580
+ raw = null;
2581
+ }
2582
+ write(raw);
2583
+ if (!cf || !Array.isArray(cf.branches) || simpleCf || apply.__vmzItemLocal)
2584
+ return;
2585
+ const next = pickCf();
2586
+ if (next === activeBranch)
2587
+ return;
2588
+ activeBranch = next;
2589
+ const branch = cf.branches[next];
2590
+ const nextDeps = [...(cf.stable || []), ...((branch && branch.deps) || [])];
2591
+ const uniq = [...new Set(nextDeps)];
2592
+ unregisterBind(inst, liveDeps, apply, bindingId);
2593
+ liveDeps = uniq;
2594
+ registerBind(inst, liveDeps, apply, bindingId);
2595
+ };
2596
+ if (cf && Array.isArray(cf.branches) && !simpleCf) {
2597
+ activeBranch = pickCf();
2598
+ const branch = cf.branches[activeBranch];
2599
+ liveDeps = [...(cf.stable || []), ...((branch && branch.deps) || [])];
2600
+ liveDeps = [...new Set(liveDeps)];
2601
+ }
2602
+ else if (cf && Array.isArray(cf.branches) && simpleCf) {
2603
+ liveDeps = Array.isArray(cf.stable) && cf.stable.length ? cf.stable : liveDeps;
2604
+ }
2605
+ // Mark before first apply so CF branch switches never hit global registerBind.
2606
+ if (directApi._itemPatches)
2607
+ apply.__vmzItemLocal = true;
2608
+ apply();
2609
+ trackDirectBind(inst, liveDeps, apply, bindingId);
2610
+ }
2611
+ export function isEventPropName(name) {
2612
+ return typeof name === 'string' && /^on[A-Z]/.test(name);
2613
+ }
2614
+ /** Monotonic id for `bindComponentProp` BindingIds (per process). */
2615
+ let directPropBindSeq = 0;
2616
+ /** HTML boolean attributes: presence means true; `false`/`null` must remove the attr. */
2617
+ export const BOOLEAN_HTML_ATTRS = new Set([
2618
+ 'disabled',
2619
+ 'checked',
2620
+ 'selected',
2621
+ 'readonly',
2622
+ 'required',
2623
+ 'multiple',
2624
+ 'hidden',
2625
+ 'autofocus',
2626
+ 'autoplay',
2627
+ 'controls',
2628
+ 'loop',
2629
+ 'muted',
2630
+ 'open',
2631
+ 'novalidate',
2632
+ 'formnovalidate',
2633
+ 'defer',
2634
+ 'async',
2635
+ 'ismap',
2636
+ 'default',
2637
+ 'inert',
2638
+ ]);
2639
+ /**
2640
+ * @param {Element} el
2641
+ * @param {string} name
2642
+ * @param {any} value
2643
+ */
2644
+ export function applyDomAttr(el, name, value) {
2645
+ const key = name === 'className' ? 'class' : name;
2646
+ if (BOOLEAN_HTML_ATTRS.has(String(key).toLowerCase())) {
2647
+ if (value === false || value == null || value === '') {
2648
+ el.removeAttribute(key);
2649
+ }
2650
+ else {
2651
+ el.setAttribute(key, value === true ? '' : String(value));
2652
+ }
2653
+ return;
2654
+ }
2655
+ // <textarea value="…"> as an attribute does not update visible text; INPUT/SELECT
2656
+ // also need the IDL `.value` property so controlled updates stay in sync after switches.
2657
+ // linkedom `<select>.value` is getter-only — sync via `option.selected` instead of throwing.
2658
+ if (key === 'value' && el && (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT' || el.tagName === 'SELECT')) {
2659
+ const next = value == null || value === false ? '' : String(value);
2660
+ if (el.tagName === 'SELECT') {
2661
+ const opts = el.options || el.querySelectorAll?.('option') || [];
2662
+ for (const opt of opts) {
2663
+ opt.selected = String(opt.value ?? '') === next;
2664
+ }
2665
+ }
2666
+ else if (el.value !== next) {
2667
+ el.value = next;
2668
+ }
2669
+ if (value == null || value === false)
2670
+ el.removeAttribute('value');
2671
+ else
2672
+ el.setAttribute('value', next);
2673
+ return;
2674
+ }
2675
+ if (value == null || value === false)
2676
+ el.removeAttribute(key);
2677
+ else
2678
+ el.setAttribute(key, value === true ? '' : String(value));
2679
+ }
2680
+ export function stripFns(obj) {
2681
+ /** @type {Record<string, unknown>} */
2682
+ const out = {};
2683
+ for (const [k, v] of Object.entries(obj || {})) {
2684
+ if (typeof v === 'function')
2685
+ continue;
2686
+ out[k] = v;
2687
+ }
2688
+ return out;
2689
+ }
2690
+ /**
2691
+ * Marker range host for Direct eachBlock (insert before end comment).
2692
+ * @param {Comment} start
2693
+ * @param {Comment} end
2694
+ */
2695
+ function eachHostApi(start, end) {
2696
+ return {
2697
+ insert(dom) {
2698
+ if (dom.parentNode)
2699
+ noteDomMove();
2700
+ end.parentNode.insertBefore(dom, end);
2701
+ },
2702
+ childrenBetween() {
2703
+ const out = [];
2704
+ let n = start.nextSibling;
2705
+ while (n && n !== end) {
2706
+ if (n.nodeType === 1)
2707
+ out.push(n);
2708
+ n = n.nextSibling;
2709
+ }
2710
+ return out;
2711
+ },
2712
+ };
2713
+ }
2714
+ /**
2715
+ * Snapshot plain state/prop field values for Island HMR (session).
2716
+ * @param {object} inst
2717
+ * @returns {Record<string, unknown> | null}
2718
+ */
2719
+ export function snapshotInstanceState(inst) {
2720
+ if (!inst || inst.__vmzDestroyed)
2721
+ return null;
2722
+ const Ctor = inst.constructor;
2723
+ const keys = [...(Ctor.__vmzState || []), ...(Ctor.__vmzProps || [])];
2724
+ /** @type {Record<string, unknown>} */
2725
+ const out = {};
2726
+ for (const key of keys) {
2727
+ if (!key || String(key).startsWith('__'))
2728
+ continue;
2729
+ try {
2730
+ out[key] = inst[key];
2731
+ }
2732
+ catch {
2733
+ /* ignore accessors that throw */
2734
+ }
2735
+ }
2736
+ return out;
2737
+ }
2738
+ /**
2739
+ * @param {object} inst
2740
+ * @param {Record<string, unknown> | null | undefined} state
2741
+ */
2742
+ export function applyPreservedState(inst, state) {
2743
+ if (!inst || !state)
2744
+ return;
2745
+ for (const [key, value] of Object.entries(state)) {
2746
+ try {
2747
+ inst[key] = value;
2748
+ }
2749
+ catch {
2750
+ /* ignore */
2751
+ }
2752
+ }
2753
+ }
2754
+ /**
2755
+ * Tear down binders and stop patches. Safe to call more than once.
2756
+ * Field writes after destroy no longer update DOM (values may still change).
2757
+ *: also dispose owned DOM trees (child __vmzInst / region __vmzDispose).
2758
+ * @param {object} inst
2759
+ */
2760
+ export function destroy(inst) {
2761
+ if (!inst || inst.__vmzDestroyed)
2762
+ return;
2763
+ inst.__vmzDestroyed = true;
2764
+ inst.__vmzFlushScheduled = false;
2765
+ // async cancel: abort in-flight tasks before tearing down DOM.
2766
+ __vmzCancelTasks(inst);
2767
+ if (inst.__vmzDomRoot) {
2768
+ disposeDomTree(inst.__vmzDomRoot);
2769
+ inst.__vmzDomRoot = null;
2770
+ }
2771
+ if (inst.__vmzDirtyNotices)
2772
+ inst.__vmzDirtyNotices.length = 0;
2773
+ if (inst.__vmzDirtyTrie)
2774
+ inst.__vmzDirtyTrie = Object.create(null);
2775
+ if (inst.__vmzDirty)
2776
+ inst.__vmzDirty.clear();
2777
+ inst.__vmzBinders = Object.create(null);
2778
+ inst.__vmzBindings = Object.create(null);
2779
+ inst.__vmzDepToBindings = Object.create(null);
2780
+ if (typeof inst.onDestroy === 'function') {
2781
+ try {
2782
+ inst.onDestroy();
2783
+ }
2784
+ catch (err) {
2785
+ console.error('vmz:dom onDestroy', err);
2786
+ }
2787
+ }
2788
+ }
2789
+ /**
2790
+ *: walk a DOM subtree and run lifetime dispose hooks + nested instance destroy.
2791
+ * Does not mark the *calling* parent destroyed; safe from destroy(inst).
2792
+ * @param {Node | null | undefined} root
2793
+ */
2794
+ export function disposeDomTree(root) {
2795
+ if (!root)
2796
+ return;
2797
+ const seen = new Set();
2798
+ const visit = (node) => {
2799
+ if (!node || seen.has(node))
2800
+ return;
2801
+ seen.add(node);
2802
+ if (typeof node.__vmzDispose === 'function') {
2803
+ try {
2804
+ node.__vmzDispose();
2805
+ }
2806
+ catch (err) {
2807
+ console.error('vmz:dom __vmzDispose', err);
2808
+ }
2809
+ node.__vmzDispose = null;
2810
+ }
2811
+ if (node.__vmzInst) {
2812
+ const child = node.__vmzInst;
2813
+ node.__vmzInst = null;
2814
+ destroy(child);
2815
+ }
2816
+ let child = node.firstChild;
2817
+ while (child) {
2818
+ const next = child.nextSibling;
2819
+ visit(child);
2820
+ child = next;
2821
+ }
2822
+ };
2823
+ visit(root);
2824
+ }
2825
+ export function scheduleClient(strategy, fn) {
2826
+ scheduleClientOn(null, strategy, fn);
2827
+ }
2828
+ /** @param {string} strategy */
2829
+ export function isEventEntryStrategy(strategy) {
2830
+ const s = String(strategy || '');
2831
+ return s === 'event' || s.startsWith('event:') || s === 'click';
2832
+ }
2833
+ /** @param {string} strategy */
2834
+ function eventEntryType(strategy) {
2835
+ const s = String(strategy || 'event');
2836
+ if (s.startsWith('event:'))
2837
+ return s.slice(6) || 'click';
2838
+ if (s === 'click')
2839
+ return 'click';
2840
+ return 'click';
2841
+ }
2842
+ export function scheduleClientOn(el, strategy, fn) {
2843
+ const run = () => {
2844
+ Promise.resolve(fn()).catch((err) => console.error('vmz:dom island', err));
2845
+ };
2846
+ if (isEventEntryStrategy(strategy)) {
2847
+ if (!el || typeof el.addEventListener !== 'function') {
2848
+ run();
2849
+ return;
2850
+ }
2851
+ const type = eventEntryType(strategy);
2852
+ const once = () => {
2853
+ el.removeEventListener(type, once);
2854
+ run();
2855
+ };
2856
+ el.addEventListener(type, once);
2857
+ return;
2858
+ }
2859
+ if (strategy === 'idle') {
2860
+ if (typeof requestIdleCallback === 'function') {
2861
+ requestIdleCallback(() => run(), { timeout: 2000 });
2862
+ }
2863
+ else {
2864
+ setTimeout(run, 1);
2865
+ }
2866
+ return;
2867
+ }
2868
+ if (strategy === 'visible' && el && typeof IntersectionObserver === 'function') {
2869
+ const io = new IntersectionObserver((entries) => {
2870
+ if (entries.some((e) => e.isIntersecting)) {
2871
+ io.disconnect();
2872
+ run();
2873
+ }
2874
+ });
2875
+ io.observe(el);
2876
+ return;
2877
+ }
2878
+ run();
2879
+ }
2880
+ /**
2881
+ * AsyncTask cancel protocol (first slice): keyed generation + AbortSignal.
2882
+ * Superseded runs and `destroy(inst)` abort prior work; stale results must not apply.
2883
+ * @param {object} inst
2884
+ * @param {string} key
2885
+ * @param {(signal: AbortSignal, meta: { generation: number }) => any | Promise<any>} fn
2886
+ * @returns {Promise<any>}
2887
+ */
2888
+ export function __vmzRunTask(inst, key, fn) {
2889
+ if (!inst)
2890
+ throw new Error('vmz:dom __vmzRunTask requires inst');
2891
+ const k = String(key || 'default');
2892
+ if (!inst.__vmzTasks)
2893
+ inst.__vmzTasks = Object.create(null);
2894
+ const prev = inst.__vmzTasks[k];
2895
+ if (prev) {
2896
+ prev.generation += 1;
2897
+ try {
2898
+ prev.controller.abort();
2899
+ }
2900
+ catch {
2901
+ /* ignore */
2902
+ }
2903
+ prev.status = 'cancelled';
2904
+ }
2905
+ const controller = typeof AbortController !== 'undefined'
2906
+ ? new AbortController()
2907
+ : {
2908
+ signal: { aborted: false },
2909
+ abort() {
2910
+ this.signal.aborted = true;
2911
+ },
2912
+ };
2913
+ const generation = (prev?.generation || 0) + 1;
2914
+ /** @type {{ generation: number, controller: any, status: string, result?: any, error?: any, promise?: Promise<any> }} */
2915
+ const entry = {
2916
+ generation,
2917
+ controller,
2918
+ status: 'pending',
2919
+ };
2920
+ inst.__vmzTasks[k] = entry;
2921
+ // Invoke synchronously so event handlers can call preventDefault before
2922
+ // the browser continues the default action (form submit → native navigation).
2923
+ // Async work still continues via the returned Promise.
2924
+ let syncResult;
2925
+ let syncErr;
2926
+ let threw = false;
2927
+ try {
2928
+ syncResult = fn(controller.signal, { generation });
2929
+ }
2930
+ catch (err) {
2931
+ threw = true;
2932
+ syncErr = err;
2933
+ }
2934
+ const settleOk = (result) => {
2935
+ if (inst.__vmzDestroyed || controller.signal.aborted || inst.__vmzTasks[k] !== entry) {
2936
+ entry.status = 'cancelled';
2937
+ return undefined;
2938
+ }
2939
+ entry.status = 'success';
2940
+ entry.result = result;
2941
+ return result;
2942
+ };
2943
+ const settleErr = (err) => {
2944
+ if (inst.__vmzDestroyed || controller.signal.aborted || inst.__vmzTasks[k] !== entry) {
2945
+ entry.status = 'cancelled';
2946
+ return undefined;
2947
+ }
2948
+ entry.status = 'error';
2949
+ entry.error = err;
2950
+ throw err;
2951
+ };
2952
+ if (threw) {
2953
+ const promise = Promise.resolve().then(() => settleErr(syncErr));
2954
+ entry.promise = promise;
2955
+ return promise;
2956
+ }
2957
+ const promise = Promise.resolve(syncResult).then(settleOk, settleErr);
2958
+ entry.promise = promise;
2959
+ return promise;
2960
+ }
2961
+ /** Abort all keyed tasks on an instance (also called from destroy). */
2962
+ export function __vmzCancelTasks(inst) {
2963
+ const tasks = inst?.__vmzTasks;
2964
+ if (!tasks)
2965
+ return;
2966
+ for (const key of Object.keys(tasks)) {
2967
+ const t = tasks[key];
2968
+ t.generation += 1;
2969
+ try {
2970
+ t.controller.abort();
2971
+ }
2972
+ catch {
2973
+ /* ignore */
2974
+ }
2975
+ t.status = 'cancelled';
2976
+ }
2977
+ }
2978
+ /** @returns {'pending'|'success'|'error'|'cancelled'|null} */
2979
+ export function __vmzTaskStatus(inst, key) {
2980
+ const t = inst?.__vmzTasks?.[String(key || 'default')];
2981
+ return t ? t.status : null;
2982
+ }
2983
+ export function createInstance(Component, props = {}) {
2984
+ if (precision.enabled)
2985
+ precision.componentExecs++;
2986
+ const inst = new Component(props || {});
2987
+ if (typeof inst.__vmzApplyProps === 'function' && !Component.__vmzCtorAppliesProps) {
2988
+ inst.__vmzApplyProps(props || {});
2989
+ }
2990
+ inst.__vmzBinders = Object.create(null);
2991
+ inst.__vmzBindings = Object.create(null);
2992
+ inst.__vmzDepToBindings = Object.create(null);
2993
+ makeReactive(inst, Component.__vmzState || []);
2994
+ makeReactive(inst, Component.__vmzProps || []);
2995
+ // WriteBarrier: install Component helpers once (no import needed in emitted code).
2996
+ if (!Component.__vmzWBInstalled) {
2997
+ Component.__vmzWBInstalled = true;
2998
+ Component.__vmzWritePath = __vmzWritePath;
2999
+ Component.__vmzWritePathItem = __vmzWritePathItem;
3000
+ Component.__vmzWritePathCompound = __vmzWritePathCompound;
3001
+ Component.__vmzWritePathCompoundItem = __vmzWritePathCompoundItem;
3002
+ Component.__vmzWritePathLogical = __vmzWritePathLogical;
3003
+ Component.__vmzReadPath = __vmzReadPath;
3004
+ Component.__vmzArrayMutate = __vmzArrayMutate;
3005
+ Component.__vmzArrayItemCompoundStride = __vmzArrayItemCompoundStride;
3006
+ Component.__vmzListTranspose = __vmzListTranspose;
3007
+ Component.__vmzAllowShared = __vmzAllowShared;
3008
+ Component.__vmzTakeShared = __vmzTakeShared;
3009
+ }
3010
+ return inst;
3011
+ }
3012
+ /** Shared plain-object owners under WriteBarrier (no Proxy). */
3013
+ const wbSharedOwners = new WeakMap();
3014
+ /** Objects explicitly marked OK to share across component instances (13 ). */
3015
+ const wbAllowShared = new WeakSet();
3016
+ /** @type {Array<{ kind: string, message: string }>} */
3017
+ const wbCrossComponentDiags = [];
3018
+ /**
3019
+ * Mark a plain object as intentionally shared across ownership boundaries.
3020
+ * @param {any} value
3021
+ */
3022
+ export function __vmzAllowShared(value) {
3023
+ if (value != null && typeof value === 'object')
3024
+ wbAllowShared.add(value);
3025
+ return value;
3026
+ }
3027
+ /**
3028
+ * Take exclusive ownership intent: clear multi-owner registry for this object.
3029
+ * Subsequent field assigns re-register from the assigning instance only.
3030
+ * @param {any} value
3031
+ */
3032
+ export function __vmzTakeShared(value) {
3033
+ if (value != null && typeof value === 'object') {
3034
+ wbSharedOwners.delete(value);
3035
+ wbAllowShared.delete(value);
3036
+ }
3037
+ return value;
3038
+ }
3039
+ /**
3040
+ * @returns {Array<{ kind: string, message: string }>}
3041
+ */
3042
+ export function __vmzSharedCrossComponentDiagnostics() {
3043
+ return wbCrossComponentDiags.slice();
3044
+ }
3045
+ export function __vmzSharedCrossComponentDiagnosticsReset() {
3046
+ wbCrossComponentDiags.length = 0;
3047
+ }
3048
+ /**
3049
+ * @param {any} value
3050
+ * @param {(segs: string[] | null) => void} report
3051
+ * @param {string[]} baseSegs
3052
+ * @param {any} [inst]
3053
+ */
3054
+ function registerWbOwner(value, report, baseSegs = [], inst = null) {
3055
+ if (value == null || typeof value !== 'object')
3056
+ return;
3057
+ let entry = wbSharedOwners.get(value);
3058
+ if (!entry) {
3059
+ entry = { owners: [] };
3060
+ wbSharedOwners.set(value, entry);
3061
+ }
3062
+ if (entry.owners.some((o) => o.report === report && sameSegs(o.baseSegs, baseSegs))) {
3063
+ return;
3064
+ }
3065
+ entry.owners.push({ report, baseSegs: baseSegs.slice(), inst });
3066
+ // Cross-component share without explicit allow → diagnose (13 ).
3067
+ if (!wbAllowShared.has(value) && inst) {
3068
+ const other = entry.owners.find((o) => o.inst && o.inst !== inst);
3069
+ if (other) {
3070
+ if (!wbCrossComponentDiags.some((d) => d.message === msg)) {
3071
+ wbCrossComponentDiags.push({ kind: 'shared_cross_component', message: msg });
3072
+ }
3073
+ }
3074
+ }
3075
+ }
3076
+ /**
3077
+ * Notify all registered owners of a shared plain object after a barrier write.
3078
+ * @param {any} rootObj field-root value that was written under
3079
+ * @param {string[] | null} localSegs path under that object (null = replace)
3080
+ * @returns {boolean} true when at least one owner was notified
3081
+ */
3082
+ function notifyWbShared(rootObj, localSegs) {
3083
+ const entry = rootObj && typeof rootObj === 'object' ? wbSharedOwners.get(rootObj) : null;
3084
+ if (!entry || !entry.owners.length)
3085
+ return false;
3086
+ for (const o of entry.owners) {
3087
+ if (localSegs == null) {
3088
+ o.report(o.baseSegs.length ? o.baseSegs.slice() : null);
3089
+ }
3090
+ else {
3091
+ o.report([...o.baseSegs, ...localSegs]);
3092
+ }
3093
+ }
3094
+ return true;
3095
+ }
3096
+ /**
3097
+ * Read a nested path under a field root (for compound / update expansion).
3098
+ * @param {any} inst
3099
+ * @param {string} root
3100
+ * @param {string[]} segs
3101
+ */
3102
+ export function __vmzReadPath(inst, root, segs) {
3103
+ if (!inst || !root)
3104
+ return undefined;
3105
+ let obj = inst[root];
3106
+ if (!Array.isArray(segs) || segs.length === 0)
3107
+ return obj;
3108
+ for (let i = 0; i < segs.length; i++) {
3109
+ if (obj == null || typeof obj !== 'object')
3110
+ return undefined;
3111
+ obj = obj[segs[i]];
3112
+ }
3113
+ return obj;
3114
+ }
3115
+ /**
3116
+ * Short-circuit logical path assign (`||=` / `&&=` / `??=`).
3117
+ * @param {any} inst
3118
+ * @param {string} root
3119
+ * @param {string[]} segs
3120
+ * @param {'||'|'&&'|'??'} kind
3121
+ * @param {any} rhs
3122
+ */
3123
+ export function __vmzWritePathLogical(inst, root, segs, kind, rhs) {
3124
+ const cur = __vmzReadPath(inst, root, segs);
3125
+ if (kind === '||') {
3126
+ if (cur)
3127
+ return cur;
3128
+ }
3129
+ else if (kind === '&&') {
3130
+ if (!cur)
3131
+ return cur;
3132
+ }
3133
+ else if (kind === '??') {
3134
+ if (cur != null)
3135
+ return cur;
3136
+ }
3137
+ else {
3138
+ return cur;
3139
+ }
3140
+ return __vmzWritePath(inst, root, segs, rhs);
3141
+ }
3142
+ /**
3143
+ * Mutates a plain owned object/array and schedules the same path notice Proxy would.
3144
+ *
3145
+ * Root-array index assigns (`tags[0] = x`) notify as field replace (structural),
3146
+ * matching the transitional Proxy wrapArray behavior.
3147
+ * Shared multi-owner: writing through one field notifies all owners of the same raw object.
3148
+ *
3149
+ * @param {any} inst
3150
+ * @param {string} root field root
3151
+ * @param {string[]} segs path under root (non-empty); dynamic indices already String(...)'d
3152
+ * @param {any} value
3153
+ */
3154
+ /**
3155
+ * Apply a binary compound/update op without a separate ReadPath call.
3156
+ * @param {string} op
3157
+ * @param {any} cur
3158
+ * @param {any} rhs
3159
+ */
3160
+ function applyCompoundOp(op, cur, rhs) {
3161
+ switch (op) {
3162
+ case '+':
3163
+ return cur + rhs;
3164
+ case '-':
3165
+ return cur - rhs;
3166
+ case '*':
3167
+ return cur * rhs;
3168
+ case '/':
3169
+ return cur / rhs;
3170
+ case '%':
3171
+ return cur % rhs;
3172
+ case '**':
3173
+ return cur ** rhs;
3174
+ case '<<':
3175
+ return cur << rhs;
3176
+ case '>>':
3177
+ return cur >> rhs;
3178
+ case '>>>':
3179
+ return cur >>> rhs;
3180
+ case '|':
3181
+ return cur | rhs;
3182
+ case '^':
3183
+ return cur ^ rhs;
3184
+ case '&':
3185
+ return cur & rhs;
3186
+ default:
3187
+ return cur;
3188
+ }
3189
+ }
3190
+ /**
3191
+ * Schedule leaf refresh after an array-item field mutate (event leaf-batch or trie).
3192
+ * @param {object} inst
3193
+ * @param {string} root
3194
+ * @param {string|number} idx
3195
+ * @param {string} leaf
3196
+ */
3197
+ function notifyArrayItemLeaf(inst, root, idx, leaf) {
3198
+ if (typeof inst.__vmzDrainLeafDirty === 'function' && ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync)) {
3199
+ const i = +idx;
3200
+ const ld = inst.__vmzLeafDirty;
3201
+ if (!ld) {
3202
+ inst.__vmzLeafDirty = { root, field: leaf, idxs: [i] };
3203
+ }
3204
+ else if (ld.root === root && ld.field === leaf) {
3205
+ ld.idxs.push(i);
3206
+ }
3207
+ else {
3208
+ promoteLeafDirtyToTrie(inst);
3209
+ scheduleRefresh(inst, { type: 'path', root, segs: [String(idx), leaf] });
3210
+ return;
3211
+ }
3212
+ inst.__vmzFlushScheduled = true;
3213
+ return;
3214
+ }
3215
+ scheduleRefresh(inst, { type: 'path', root, segs: [String(idx), leaf] });
3216
+ }
3217
+ export function __vmzWritePath(inst, root, segs, value) {
3218
+ if (!inst || inst.__vmzDestroyed)
3219
+ return value;
3220
+ if (!root || !Array.isArray(segs) || segs.length === 0)
3221
+ return value;
3222
+ // Hot path: array item field write (`rows[i].label`) — no map/slice, no shared-owner walk.
3223
+ if (segs.length === 2) {
3224
+ return __vmzWritePathItem(inst, root, segs[0], segs[1], value);
3225
+ }
3226
+ const normSegs = segs.map((s) => String(s));
3227
+ let obj = inst[root];
3228
+ if (obj == null || typeof obj !== 'object')
3229
+ return value;
3230
+ for (let i = 0; i < normSegs.length - 1; i++) {
3231
+ obj = obj[normSegs[i]];
3232
+ if (obj == null || typeof obj !== 'object')
3233
+ return value;
3234
+ }
3235
+ const leaf = normSegs[normSegs.length - 1];
3236
+ if (Object.is(obj[leaf], value))
3237
+ return value;
3238
+ obj[leaf] = value;
3239
+ // Register newly assigned nested objects under this field for future shared writes.
3240
+ if (value != null && typeof value === 'object') {
3241
+ const report = (local) => {
3242
+ if (!local || local.length === 0) {
3243
+ scheduleRefresh(inst, { type: 'replace', root });
3244
+ }
3245
+ else {
3246
+ scheduleRefresh(inst, { type: 'path', root, segs: local });
3247
+ }
3248
+ };
3249
+ registerWbOwner(value, report, normSegs.slice(), inst);
3250
+ }
3251
+ const rootObj = inst[root];
3252
+ const rootArr = rootObj;
3253
+ const isRootIndex = normSegs.length === 1 && Array.isArray(rootArr) && leaf !== 'length' && String(Number(leaf)) === leaf;
3254
+ if (isRootIndex) {
3255
+ if (!notifyWbShared(rootObj, null)) {
3256
+ scheduleRefresh(inst, { type: 'replace', root });
3257
+ }
3258
+ }
3259
+ else if (!notifyWbShared(rootObj, normSegs)) {
3260
+ scheduleRefresh(inst, { type: 'path', root, segs: normSegs });
3261
+ }
3262
+ return value;
3263
+ }
3264
+ /**
3265
+ * Array-item leaf write without segs array alloc (`rows[i].label = v`).
3266
+ * @param {any} inst
3267
+ * @param {string} root
3268
+ * @param {string|number} idx
3269
+ * @param {string} leaf
3270
+ * @param {any} value
3271
+ */
3272
+ export function __vmzWritePathItem(inst, root, idx, leaf, value) {
3273
+ if (!inst || inst.__vmzDestroyed)
3274
+ return value;
3275
+ if (!root || leaf == null)
3276
+ return value;
3277
+ const arr = inst[root];
3278
+ if (!Array.isArray(arr))
3279
+ return value;
3280
+ const item = arr[idx];
3281
+ if (item == null || typeof item !== 'object')
3282
+ return value;
3283
+ if (Object.is(item[leaf], value))
3284
+ return value;
3285
+ item[leaf] = value;
3286
+ if (tryInlineLeafApply(inst, root, idx, leaf, item))
3287
+ return value;
3288
+ notifyArrayItemLeaf(inst, root, idx, leaf);
3289
+ return value;
3290
+ }
3291
+ /**
3292
+ * In-place two-index swap on an owned list field (WriteBarrier Slice 5).
3293
+ * Prefers eachBlock O(1) DOM transpose hook; otherwise schedules a list replace.
3294
+ * @param {any} inst
3295
+ * @param {string} root
3296
+ * @param {number|string} ia
3297
+ * @param {number|string} ib
3298
+ */
3299
+ export function __vmzListTranspose(inst, root, ia, ib) {
3300
+ if (!inst || inst.__vmzDestroyed || !root)
3301
+ return;
3302
+ const arr = inst[root];
3303
+ if (!Array.isArray(arr))
3304
+ return;
3305
+ const a = +ia;
3306
+ const b = +ib;
3307
+ if (a === b || a < 0 || b < 0 || a >= arr.length || b >= arr.length)
3308
+ return;
3309
+ const tmp = arr[a];
3310
+ arr[a] = arr[b];
3311
+ arr[b] = tmp;
3312
+ const hook = inst.__vmzEachTranspose && inst.__vmzEachTranspose[root];
3313
+ if (typeof hook === 'function' && hook(a, b) === true)
3314
+ return;
3315
+ scheduleRefresh(inst, { type: 'replace', root });
3316
+ }
3317
+ /**
3318
+ * Compound leaf write (`rows[i].label += x`) — one item touch, no separate ReadPath.
3319
+ * @param {any} inst
3320
+ * @param {string} root
3321
+ * @param {string[]} segs
3322
+ * @param {string} op
3323
+ * @param {any} rhs
3324
+ */
3325
+ export function __vmzWritePathCompound(inst, root, segs, op, rhs) {
3326
+ if (!inst || inst.__vmzDestroyed)
3327
+ return undefined;
3328
+ if (!root || !Array.isArray(segs) || segs.length === 0)
3329
+ return undefined;
3330
+ if (segs.length === 2) {
3331
+ return __vmzWritePathCompoundItem(inst, root, segs[0], segs[1], op, rhs);
3332
+ }
3333
+ const cur = __vmzReadPath(inst, root, segs);
3334
+ const value = applyCompoundOp(op, cur, rhs);
3335
+ return __vmzWritePath(inst, root, segs, value);
3336
+ }
3337
+ /**
3338
+ * Array-item compound without segs array alloc (`rows[i].label += x`).
3339
+ * @param {any} inst
3340
+ * @param {string} root
3341
+ * @param {string|number} idx
3342
+ * @param {string} leaf
3343
+ * @param {string} op
3344
+ * @param {any} rhs
3345
+ */
3346
+ export function __vmzWritePathCompoundItem(inst, root, idx, leaf, op, rhs) {
3347
+ if (!inst || inst.__vmzDestroyed)
3348
+ return undefined;
3349
+ if (!root || leaf == null)
3350
+ return undefined;
3351
+ const arr = inst[root];
3352
+ if (!Array.isArray(arr))
3353
+ return undefined;
3354
+ const item = arr[idx];
3355
+ if (item == null || typeof item !== 'object')
3356
+ return undefined;
3357
+ const cur = item[leaf];
3358
+ const value = applyCompoundOp(op, cur, rhs);
3359
+ if (Object.is(cur, value))
3360
+ return value;
3361
+ item[leaf] = value;
3362
+ if (tryInlineLeafApply(inst, root, idx, leaf, item))
3363
+ return value;
3364
+ notifyArrayItemLeaf(inst, root, idx, leaf);
3365
+ return value;
3366
+ }
3367
+ /**
3368
+ * Stride compound over an owned array (`for (i=start; i<n; i+=step) arr[i].leaf op= rhs`).
3369
+ * Mutates + applies DOM in one pass when eachBlock leaf hook is installed (update-every-Nth).
3370
+ * @param {any} inst
3371
+ * @param {string} root
3372
+ * @param {string} leaf
3373
+ * @param {string} op
3374
+ * @param {any} rhs
3375
+ * @param {number|string} start
3376
+ * @param {number|string} step
3377
+ */
3378
+ export function __vmzArrayItemCompoundStride(inst, root, leaf, op, rhs, start, step) {
3379
+ if (!inst || inst.__vmzDestroyed || !root || leaf == null)
3380
+ return;
3381
+ // Prefer eachBlock-owned loop (hoisted applyByField, no per-index hook lookup).
3382
+ const owned = inst.__vmzEachCompoundStride && inst.__vmzEachCompoundStride[root];
3383
+ if (typeof owned === 'function' && owned(leaf, op, rhs, start, step) === true)
3384
+ return;
3385
+ const arr = inst[root];
3386
+ if (!Array.isArray(arr))
3387
+ return;
3388
+ const s = +start || 0;
3389
+ const st = +step || 0;
3390
+ if (st <= 0)
3391
+ return;
3392
+ const n = arr.length;
3393
+ const canInline = typeof inst.__vmzEachApplyLeaf === 'object' &&
3394
+ typeof inst.__vmzEachApplyLeaf[root] === 'function' &&
3395
+ ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync);
3396
+ const applyLeaf = canInline ? inst.__vmzEachApplyLeaf[root] : null;
3397
+ // Hot path: string `+=` with inline DOM — no switch / Object.is / idxs.
3398
+ if (op === '+' && applyLeaf) {
3399
+ for (let i = s; i < n; i += st) {
3400
+ const item = arr[i];
3401
+ if (item == null || typeof item !== 'object')
3402
+ continue;
3403
+ item[leaf] = item[leaf] + rhs;
3404
+ applyLeaf(i, leaf, item);
3405
+ }
3406
+ return;
3407
+ }
3408
+ /** @type {number[]} */
3409
+ const idxs = [];
3410
+ for (let i = s; i < n; i += st) {
3411
+ const item = arr[i];
3412
+ if (item == null || typeof item !== 'object')
3413
+ continue;
3414
+ const cur = item[leaf];
3415
+ const value = applyCompoundOp(op, cur, rhs);
3416
+ if (Object.is(cur, value))
3417
+ continue;
3418
+ item[leaf] = value;
3419
+ if (applyLeaf && applyLeaf(i, leaf, item) === true)
3420
+ continue;
3421
+ idxs.push(i);
3422
+ }
3423
+ if (!idxs.length)
3424
+ return;
3425
+ if (typeof inst.__vmzDrainLeafDirty === 'function' && ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync)) {
3426
+ inst.__vmzLeafDirty = { root, field: leaf, idxs };
3427
+ inst.__vmzFlushScheduled = true;
3428
+ return;
3429
+ }
3430
+ for (let k = 0; k < idxs.length; k++) {
3431
+ notifyArrayItemLeaf(inst, root, idxs[k], leaf);
3432
+ }
3433
+ }
3434
+ /**
3435
+ * During event flush, apply a leaf via eachBlock hook (vanillajs-style mutate+DOM).
3436
+ * @param {any} inst
3437
+ * @param {string} root
3438
+ * @param {string|number} idx
3439
+ * @param {string} leaf
3440
+ * @param {any} item
3441
+ */
3442
+ function tryInlineLeafApply(inst, root, idx, leaf, item) {
3443
+ if (!((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync))
3444
+ return false;
3445
+ const hook = inst.__vmzEachApplyLeaf && inst.__vmzEachApplyLeaf[root];
3446
+ if (typeof hook !== 'function')
3447
+ return false;
3448
+ return hook(+idx, leaf, item) === true;
3449
+ }
3450
+ /**
3451
+ * Compiler-inserted array mutator barrier (push/pop/splice/…).
3452
+ * Applies the mutator on the plain array and schedules a structural notice
3453
+ * at `root` + `baseSegs` (empty baseSegs → field replace).
3454
+ *
3455
+ * @param {any} inst
3456
+ * @param {string} root
3457
+ * @param {string[]} baseSegs
3458
+ * @param {string} method
3459
+ * @param {any[]} args
3460
+ */
3461
+ export function __vmzArrayMutate(inst, root, baseSegs, method, args) {
3462
+ if (!inst || inst.__vmzDestroyed)
3463
+ return undefined;
3464
+ if (!root || typeof method !== 'string')
3465
+ return undefined;
3466
+ const segs = Array.isArray(baseSegs) ? baseSegs.map((s) => String(s)) : [];
3467
+ let arr = inst[root];
3468
+ if (arr == null || typeof arr !== 'object')
3469
+ return undefined;
3470
+ for (let i = 0; i < segs.length; i++) {
3471
+ arr = arr[segs[i]];
3472
+ if (arr == null || typeof arr !== 'object')
3473
+ return undefined;
3474
+ }
3475
+ if (!Array.isArray(arr) || typeof arr[method] !== 'function')
3476
+ return undefined;
3477
+ const list = Array.isArray(args) ? args : [];
3478
+ const ret = arr[method](...list);
3479
+ const rootObj = inst[root];
3480
+ if (segs.length === 0) {
3481
+ if (!notifyWbShared(rootObj, null)) {
3482
+ scheduleRefresh(inst, { type: 'replace', root });
3483
+ }
3484
+ }
3485
+ else if (!notifyWbShared(rootObj, segs)) {
3486
+ scheduleRefresh(inst, { type: 'path', root, segs: segs.slice() });
3487
+ }
3488
+ return ret;
3489
+ }
3490
+ function makeReactive(inst, stateKeys) {
3491
+ const barrier = !!inst.constructor.__vmzWriteBarrier;
3492
+ for (const key of stateKeys) {
3493
+ if (!key || key.startsWith('#'))
3494
+ continue;
3495
+ const desc = Object.getOwnPropertyDescriptor(inst, key);
3496
+ if (desc && desc.set && desc.get && !desc.writable)
3497
+ continue;
3498
+ /** @param {string[] | null} segs null/empty → replace field */
3499
+ const report = (segs) => {
3500
+ if (!segs || segs.length === 0) {
3501
+ scheduleRefresh(inst, { type: 'replace', root: key });
3502
+ }
3503
+ else {
3504
+ scheduleRefresh(inst, { type: 'path', root: key, segs });
3505
+ }
3506
+ };
3507
+ // WriteBarrier components keep plain objects — nested writes go through __vmzWritePath.
3508
+ let value = barrier ? inst[key] : wrapReactive(inst[key], report, []);
3509
+ if (barrier)
3510
+ registerWbOwner(value, report, [], inst);
3511
+ Object.defineProperty(inst, key, {
3512
+ configurable: true,
3513
+ enumerable: true,
3514
+ get() {
3515
+ return value;
3516
+ },
3517
+ set(next) {
3518
+ const wrapped = barrier ? next : wrapReactive(next, report, []);
3519
+ if (Object.is(value, wrapped))
3520
+ return;
3521
+ value = wrapped;
3522
+ if (barrier)
3523
+ registerWbOwner(value, report, [], inst);
3524
+ report(null);
3525
+ },
3526
+ });
3527
+ }
3528
+ }
3529
+ /** Targets already wrapped: raw|proxy|barrier → { proxy, owners[], kind }. */
3530
+ const reactiveProxies = new WeakMap();
3531
+ /** Plain objects using defineProperty write barriers (not Proxy). */
3532
+ const writeBarrierOwned = new WeakSet();
3533
+ /**
3534
+ * WriteBarrier: true when value is an owned plain object with path barriers (no Proxy).
3535
+ * @param {any} value
3536
+ */
3537
+ export function __vmzIsWriteBarrierOwned(value) {
3538
+ return writeBarrierOwned.has(value);
3539
+ }
3540
+ /**
3541
+ * True when value is the Proxy wrapper from array (or residual) reactive wrap.
3542
+ * @param {any} value
3543
+ */
3544
+ export function __vmzIsReactiveProxy(value) {
3545
+ const e = reactiveProxies.get(value);
3546
+ return !!(e && e.kind === 'proxy' && e.proxy === value);
3547
+ }
3548
+ const ARRAY_MUTATORS = new Set(['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill', 'copyWithin']);
3549
+ /**
3550
+ * @typedef {{ report: (segs: string[] | null) => void, baseSegs: string[] }} ReactiveOwner
3551
+ * @typedef {{ proxy: object, owners: ReactiveOwner[], kind: 'barrier'|'proxy' }} ReactiveEntry
3552
+ */
3553
+ function sameSegs(a, b) {
3554
+ if (a.length !== b.length)
3555
+ return false;
3556
+ for (let i = 0; i < a.length; i++) {
3557
+ if (a[i] !== b[i])
3558
+ return false;
3559
+ }
3560
+ return true;
3561
+ }
3562
+ /**
3563
+ * @param {ReactiveEntry} entry
3564
+ * @param {(segs: string[] | null) => void} report
3565
+ * @param {string[]} baseSegs
3566
+ */
3567
+ function addOwner(entry, report, baseSegs) {
3568
+ if (entry.owners.some((o) => o.report === report && sameSegs(o.baseSegs, baseSegs))) {
3569
+ return;
3570
+ }
3571
+ entry.owners.push({
3572
+ report,
3573
+ baseSegs: baseSegs.slice(),
3574
+ });
3575
+ }
3576
+ /**
3577
+ * @param {ReactiveOwner[]} owners
3578
+ * @param {string[] | null} localSegs null = structural replace of this node
3579
+ */
3580
+ function notifyOwners(owners, localSegs) {
3581
+ for (const o of owners) {
3582
+ if (localSegs == null) {
3583
+ o.report(o.baseSegs.length ? o.baseSegs.slice() : null);
3584
+ }
3585
+ else {
3586
+ o.report([...o.baseSegs, ...localSegs]);
3587
+ }
3588
+ }
3589
+ }
3590
+ /**
3591
+ * Field-owned write traps for plain objects / arrays on state fields.
3592
+ * Plain objects: WriteBarrier via defineProperty (no Proxy).
3593
+ * Arrays: transitional Proxy tracks list identity/mutators only; elements stay plain
3594
+ * (no per-item wrap on large assign — nested notifies via `__vmzWritePath`).
3595
+ * Shared raw objects notify **all** current owners.
3596
+ *
3597
+ * @param {any} value
3598
+ * @param {(segs: string[] | null) => void} report
3599
+ * @param {string[]} pathSegs path under the field root to this value
3600
+ */
3601
+ function wrapReactive(value, report, pathSegs = []) {
3602
+ if (value == null || typeof value !== 'object')
3603
+ return value;
3604
+ const existing = reactiveProxies.get(value);
3605
+ if (existing) {
3606
+ addOwner(existing, report, pathSegs);
3607
+ return existing.proxy;
3608
+ }
3609
+ if (Array.isArray(value))
3610
+ return wrapArray(value, report, pathSegs);
3611
+ if (isPlainObject(value))
3612
+ return wrapOwnedObject(value, report, pathSegs);
3613
+ return value;
3614
+ }
3615
+ function isPlainObject(value) {
3616
+ const proto = Object.getPrototypeOf(value);
3617
+ return proto === Object.prototype || proto === null;
3618
+ }
3619
+ /**
3620
+ * Path-level write barrier for owned plain objects (no Proxy).
3621
+ */
3622
+ function wrapOwnedObject(obj, report, pathSegs) {
3623
+ const existing = reactiveProxies.get(obj);
3624
+ if (existing) {
3625
+ addOwner(existing, report, pathSegs);
3626
+ return existing.proxy;
3627
+ }
3628
+ /** @type {ReactiveEntry} */
3629
+ const entry = {
3630
+ proxy: obj,
3631
+ owners: [],
3632
+ kind: 'barrier',
3633
+ };
3634
+ addOwner(entry, report, pathSegs);
3635
+ writeBarrierOwned.add(obj);
3636
+ reactiveProxies.set(obj, entry);
3637
+ for (const prop of Object.keys(obj)) {
3638
+ installOwnedProp(obj, prop, entry);
3639
+ }
3640
+ return obj;
3641
+ }
3642
+ /**
3643
+ * @param {object} obj
3644
+ * @param {string} prop
3645
+ * @param {ReactiveEntry} entry
3646
+ */
3647
+ function installOwnedProp(obj, prop, entry) {
3648
+ const desc = Object.getOwnPropertyDescriptor(obj, prop);
3649
+ if (!desc || !desc.configurable)
3650
+ return;
3651
+ if (desc.get || desc.set)
3652
+ return;
3653
+ let current = obj[prop];
3654
+ for (const o of entry.owners) {
3655
+ current = wrapReactive(current, o.report, [...o.baseSegs, prop]);
3656
+ }
3657
+ Object.defineProperty(obj, prop, {
3658
+ configurable: true,
3659
+ enumerable: desc.enumerable !== false,
3660
+ get() {
3661
+ return current;
3662
+ },
3663
+ set(next) {
3664
+ const local = [prop];
3665
+ let wrapped = next;
3666
+ for (const o of entry.owners) {
3667
+ wrapped = wrapReactive(next, o.report, [...o.baseSegs, ...local]);
3668
+ }
3669
+ if (Object.is(current, wrapped))
3670
+ return;
3671
+ current = wrapped;
3672
+ notifyOwners(entry.owners, local);
3673
+ },
3674
+ });
3675
+ }
3676
+ /**
3677
+ * Transitional array Proxy: track list identity / mutators only.
3678
+ * Elements stay plain — no per-item defineProperty on `this.rows = largeArray`
3679
+ * (design: WriteBarrier / list replace must not wrap 1k items). Nested field
3680
+ * notifies go through `__vmzWritePath` or whole-array replace.
3681
+ */
3682
+ function wrapArray(arr, report, pathSegs) {
3683
+ const existing = reactiveProxies.get(arr);
3684
+ if (existing) {
3685
+ addOwner(existing, report, pathSegs);
3686
+ return existing.proxy;
3687
+ }
3688
+ /** @type {ReactiveEntry} */
3689
+ const entry = {
3690
+ proxy: null,
3691
+ owners: [],
3692
+ kind: 'proxy',
3693
+ };
3694
+ addOwner(entry, report, pathSegs);
3695
+ const isArrayIndex = (prop) => typeof prop === 'string' && prop !== 'length' && String(Number(prop)) === prop;
3696
+ const proxy = new Proxy(arr, {
3697
+ get(target, prop, receiver) {
3698
+ if (typeof prop === 'string' && ARRAY_MUTATORS.has(prop)) {
3699
+ const fn = target[prop];
3700
+ return (...args) => {
3701
+ const ret = fn.apply(target, args);
3702
+ notifyOwners(entry.owners, null);
3703
+ return ret;
3704
+ };
3705
+ }
3706
+ // Indices / length / methods: return as-is (plain elements).
3707
+ return Reflect.get(target, prop, receiver);
3708
+ },
3709
+ set(target, prop, next, receiver) {
3710
+ const prev = target[prop];
3711
+ if (Object.is(prev, next))
3712
+ return true;
3713
+ const ok = Reflect.set(target, prop, next, receiver);
3714
+ if (ok) {
3715
+ if (prop === 'length' || isArrayIndex(prop))
3716
+ notifyOwners(entry.owners, null);
3717
+ else if (typeof prop === 'string')
3718
+ notifyOwners(entry.owners, [prop]);
3719
+ else
3720
+ notifyOwners(entry.owners, null);
3721
+ }
3722
+ return ok;
3723
+ },
3724
+ deleteProperty(target, prop) {
3725
+ if (!(prop in target))
3726
+ return true;
3727
+ const ok = Reflect.deleteProperty(target, prop);
3728
+ if (ok) {
3729
+ notifyOwners(entry.owners, typeof prop === 'string' ? [prop] : null);
3730
+ }
3731
+ return ok;
3732
+ },
3733
+ });
3734
+ entry.proxy = proxy;
3735
+ reactiveProxies.set(arr, entry);
3736
+ reactiveProxies.set(proxy, entry);
3737
+ return proxy;
3738
+ }
3739
+ /**
3740
+ * Coalesce field/path patches in the same turn via a dirty path trie.
3741
+ * Still precise deps — never a full-tree re-render. Flush runs as a microtask by default;
3742
+ * DOM event handlers drain synchronously via beginEventFlush/endEventFlush when methodRw
3743
+ * proves the handler is sync (`async: false`, `opaque: false`).
3744
+ * Call `await flushPending(inst)` (may return void or a Promise) for tests / immediate UI.
3745
+ *
3746
+ *
3747
+ * @param {object} inst
3748
+ * @param {{ type: 'replace', root: string } | { type: 'path', root: string, segs: string[] } | string} notice
3749
+ * string form is transitional field-root alias for replace.
3750
+ */
3751
+ function scheduleRefresh(inst, notice) {
3752
+ if (!inst || inst.__vmzDestroyed || inst.__vmzQuiet)
3753
+ return;
3754
+ const n = typeof notice === 'string' ? { type: 'replace', root: notice } : notice;
3755
+ if (!n || !n.root)
3756
+ return;
3757
+ if (precision.enabled) {
3758
+ precision.writes++;
3759
+ bumpMap(precision.writesByRoot, n.root);
3760
+ }
3761
+ pushTrace('write', 'field', n.root, n.root);
3762
+ if (!inst.__vmzDirtyTrie)
3763
+ inst.__vmzDirtyTrie = Object.create(null);
3764
+ insertDirtyNotice(inst.__vmzDirtyTrie, n);
3765
+ // Transitional: keep notice list for flush loop emptiness check / compat.
3766
+ if (!inst.__vmzDirtyNotices)
3767
+ inst.__vmzDirtyNotices = [];
3768
+ inst.__vmzDirtyNotices.push(n);
3769
+ // Inside a UI event (or its sync flush): coalesce; endEventFlush drains without microtask hop.
3770
+ if ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync) {
3771
+ inst.__vmzFlushScheduled = true;
3772
+ return;
3773
+ }
3774
+ if (inst.__vmzFlushScheduled)
3775
+ return;
3776
+ inst.__vmzFlushScheduled = true;
3777
+ queueMicrotask(() => {
3778
+ inst.__vmzFlushScheduled = false;
3779
+ const p = flushPending(inst);
3780
+ if (p && typeof p.then === 'function') {
3781
+ p.catch((err) => console.error('vmz:dom flush', err));
3782
+ }
3783
+ });
3784
+ }
3785
+ /**
3786
+ * Infer `this.<method>(...)` from a compiled click arrow / bag handler.
3787
+ * @param {Function} handler
3788
+ * @returns {string | null}
3789
+ */
3790
+ function inferHandlerMethod(handler) {
3791
+ if (typeof handler !== 'function')
3792
+ return null;
3793
+ if (Object.hasOwn(handler, '__vmzMethod')) {
3794
+ return handler.__vmzMethod;
3795
+ }
3796
+ let name = null;
3797
+ try {
3798
+ const src = Function.prototype.toString.call(handler);
3799
+ const m = src.match(/this\.([A-Za-z_$][\w$]*)\s*\(/);
3800
+ name = m ? m[1] : null;
3801
+ }
3802
+ catch {
3803
+ name = null;
3804
+ }
3805
+ try {
3806
+ handler.__vmzMethod = name;
3807
+ }
3808
+ catch {
3809
+ /* non-extensible function */
3810
+ }
3811
+ return name;
3812
+ }
3813
+ /**
3814
+ * Sync event flush only when `__vmzMethodRw` proves the method is non-async / non-opaque.
3815
+ * Missing summary → sync (Direct UI default). Async/opaque → microtask coalesce.
3816
+ * @param {object} inst
3817
+ * @param {string | null | undefined} methodName
3818
+ */
3819
+ function methodAllowsSyncEventFlush(inst, methodName) {
3820
+ if (!inst)
3821
+ return false;
3822
+ if (!methodName)
3823
+ return true;
3824
+ const table = inst.constructor && inst.constructor.__vmzMethodRw;
3825
+ const rw = table && table[methodName];
3826
+ if (!rw)
3827
+ return true;
3828
+ if (rw.async || rw.opaque)
3829
+ return false;
3830
+ return true;
3831
+ }
3832
+ /**
3833
+ * @param {object} inst
3834
+ * @param {string | null | undefined} methodHint
3835
+ * @param {() => any} fn
3836
+ */
3837
+ function runDomEventHandler(inst, methodHint, fn) {
3838
+ const sync = methodAllowsSyncEventFlush(inst, methodHint);
3839
+ if (sync)
3840
+ beginEventFlush(inst);
3841
+ try {
3842
+ return fn();
3843
+ }
3844
+ finally {
3845
+ if (sync)
3846
+ endEventFlush(inst);
3847
+ }
3848
+ }
3849
+ function beginEventFlush(inst) {
3850
+ if (!inst)
3851
+ return;
3852
+ inst.__vmzEventDepth = (inst.__vmzEventDepth || 0) + 1;
3853
+ }
3854
+ function endEventFlush(inst) {
3855
+ if (!inst)
3856
+ return;
3857
+ inst.__vmzEventDepth = Math.max(0, (inst.__vmzEventDepth || 0) - 1);
3858
+ if (inst.__vmzEventDepth !== 0 || !inst.__vmzFlushScheduled)
3859
+ return;
3860
+ // Keep __vmzFlushSync so nested writes during flush stay off the microtask path.
3861
+ inst.__vmzFlushSync = true;
3862
+ try {
3863
+ inst.__vmzFlushScheduled = false;
3864
+ const ret = flushPending(inst);
3865
+ if (ret && typeof ret.then === 'function') {
3866
+ ret.catch((err) => console.error('vmz:dom flush', err));
3867
+ }
3868
+ }
3869
+ finally {
3870
+ inst.__vmzFlushSync = false;
3871
+ // Async binder left dirties: fall back to microtask coalesce.
3872
+ if (inst.__vmzFlushScheduled) {
3873
+ const again = inst.__vmzFlushScheduled;
3874
+ inst.__vmzFlushScheduled = false;
3875
+ if (again) {
3876
+ inst.__vmzFlushScheduled = true;
3877
+ queueMicrotask(() => {
3878
+ inst.__vmzFlushScheduled = false;
3879
+ const p = flushPending(inst);
3880
+ if (p && typeof p.then === 'function') {
3881
+ p.catch((err) => console.error('vmz:dom flush', err));
3882
+ }
3883
+ });
3884
+ }
3885
+ }
3886
+ }
3887
+ }
3888
+ /**
3889
+ * Promote an abandoned leaf batch into the dirty trie so mixed writes stay correct.
3890
+ * @param {object} inst
3891
+ */
3892
+ function promoteLeafDirtyToTrie(inst) {
3893
+ const ld = inst.__vmzLeafDirty;
3894
+ if (!ld || !ld.idxs || !ld.idxs.length) {
3895
+ inst.__vmzLeafDirty = null;
3896
+ return;
3897
+ }
3898
+ inst.__vmzLeafDirty = null;
3899
+ if (!inst.__vmzDirtyTrie)
3900
+ inst.__vmzDirtyTrie = Object.create(null);
3901
+ if (!inst.__vmzDirtyNotices)
3902
+ inst.__vmzDirtyNotices = [];
3903
+ const field = ld.field;
3904
+ const root = ld.root;
3905
+ for (let k = 0; k < ld.idxs.length; k++) {
3906
+ const segs = [String(ld.idxs[k]), field];
3907
+ insertDirtyNotice(inst.__vmzDirtyTrie, { type: 'path', root, segs });
3908
+ inst.__vmzDirtyNotices.push({ type: 'path', root, segs });
3909
+ }
3910
+ inst.__vmzFlushScheduled = true;
3911
+ }
3912
+ /**
3913
+ * @param {Record<string, any>} trie
3914
+ * @param {{ type: string, root: string, segs?: string[] }} notice
3915
+ */
3916
+ function insertDirtyNotice(trie, notice) {
3917
+ if (notice.type === 'replace') {
3918
+ trie[notice.root] = { replace: true };
3919
+ return;
3920
+ }
3921
+ const segs = notice.segs || [];
3922
+ let node = trie[notice.root];
3923
+ if (node && node.replace)
3924
+ return;
3925
+ if (!node) {
3926
+ node = { children: Object.create(null) };
3927
+ trie[notice.root] = node;
3928
+ }
3929
+ if (!segs.length) {
3930
+ trie[notice.root] = { replace: true };
3931
+ return;
3932
+ }
3933
+ if (!node.children)
3934
+ node.children = Object.create(null);
3935
+ let cur = node;
3936
+ for (let i = 0; i < segs.length; i++) {
3937
+ const seg = segs[i];
3938
+ if (cur.dirty)
3939
+ return; // ancestor already dirty
3940
+ if (!cur.children)
3941
+ cur.children = Object.create(null);
3942
+ if (i === segs.length - 1) {
3943
+ cur.children[seg] = { dirty: true };
3944
+ return;
3945
+ }
3946
+ let next = cur.children[seg];
3947
+ if (!next) {
3948
+ next = { children: Object.create(null) };
3949
+ cur.children[seg] = next;
3950
+ }
3951
+ else if (next.dirty) {
3952
+ return;
3953
+ }
3954
+ else if (!next.children) {
3955
+ next.children = Object.create(null);
3956
+ }
3957
+ cur = next;
3958
+ }
3959
+ }
3960
+ /** @param {object} inst */
3961
+ export function flushPending(inst) {
3962
+ if (!inst || inst.__vmzDestroyed)
3963
+ return undefined;
3964
+ inst.__vmzFlushScheduled = false;
3965
+ // rowKernel leaf batch (event update): apply before trie emptiness check.
3966
+ if (typeof inst.__vmzDrainLeafDirty === 'function') {
3967
+ try {
3968
+ inst.__vmzDrainLeafDirty();
3969
+ }
3970
+ catch (err) {
3971
+ console.error('vmz:dom leafDirty', err);
3972
+ }
3973
+ }
3974
+ let guard = 0;
3975
+ while (!inst.__vmzDestroyed &&
3976
+ (dirtyTrieHasEntries(inst.__vmzDirtyTrie) || (inst.__vmzDirtyNotices && inst.__vmzDirtyNotices.length > 0)) &&
3977
+ guard++ < 64) {
3978
+ const trie = inst.__vmzDirtyTrie || Object.create(null);
3979
+ inst.__vmzDirtyTrie = Object.create(null);
3980
+ if (inst.__vmzDirtyNotices)
3981
+ inst.__vmzDirtyNotices.length = 0;
3982
+ inst.__vmzFlushTrie = trie;
3983
+ const jobs = [];
3984
+ // Prefer BindingId scheduling (IR). String `__vmzBinders` is adapter-only.
3985
+ // Pass `trie` into refresh — dirty map is cleared above before patches run.
3986
+ try {
3987
+ const bindingIds = bindingIdsMatchingTrie(inst, trie);
3988
+ const coveredDeps = Object.create(null);
3989
+ for (const id of bindingIds) {
3990
+ const entry = inst.__vmzBindings && inst.__vmzBindings[id];
3991
+ if (entry) {
3992
+ for (const d of entry.deps || [])
3993
+ coveredDeps[d] = true;
3994
+ }
3995
+ jobs.push(...refreshBinding(inst, id, trie));
3996
+ }
3997
+ for (const key of binderKeysMatchingTrie(inst, trie)) {
3998
+ if (coveredDeps[key] || (inst.__vmzDepToBindings && inst.__vmzDepToBindings[key]?.length)) {
3999
+ // BindingId path already flushed IR patches for this dep.
4000
+ // Still run binder-only patches (bindComponentProp uses bindingId null).
4001
+ jobs.push(...refreshFieldBinderOnly(inst, key));
4002
+ continue;
4003
+ }
4004
+ jobs.push(...refreshField(inst, key));
4005
+ }
4006
+ const pending = jobs.filter((j) => j && typeof j.then === 'function');
4007
+ if (pending.length) {
4008
+ // Async binders: resume after they settle (default microtask path after).
4009
+ return Promise.all(pending).then(() => flushPending(inst));
4010
+ }
4011
+ }
4012
+ finally {
4013
+ inst.__vmzFlushTrie = null;
4014
+ }
4015
+ }
4016
+ return undefined;
4017
+ }
4018
+ /** Avoid `Object.keys(trie).length` alloc on the common empty-after-leaf-drain path. */
4019
+ function dirtyTrieHasEntries(trie) {
4020
+ if (!trie)
4021
+ return false;
4022
+ for (const _ in trie)
4023
+ return true;
4024
+ return false;
4025
+ }
4026
+ /**
4027
+ * @param {object} inst
4028
+ * @param {Record<string, any>} trie
4029
+ * @returns {Array<number|string>}
4030
+ */
4031
+ function bindingIdsMatchingTrie(inst, trie) {
4032
+ const index = inst.__vmzDepToBindings;
4033
+ if (!index)
4034
+ return [];
4035
+ const out = [];
4036
+ const seen = Object.create(null);
4037
+ for (const key of Object.keys(index)) {
4038
+ if (!depMatchesTrie(trie, key))
4039
+ continue;
4040
+ for (const id of index[key]) {
4041
+ const k = String(id);
4042
+ if (seen[k])
4043
+ continue;
4044
+ seen[k] = true;
4045
+ out.push(id);
4046
+ }
4047
+ }
4048
+ return out;
4049
+ }
4050
+ /**
4051
+ * @param {object} inst
4052
+ * @param {Record<string, any>} trie
4053
+ * @returns {string[]}
4054
+ */
4055
+ function binderKeysMatchingTrie(inst, trie) {
4056
+ const binders = inst.__vmzBinders;
4057
+ if (!binders)
4058
+ return [];
4059
+ const out = [];
4060
+ for (const key of Object.keys(binders)) {
4061
+ if (depMatchesTrie(trie, key))
4062
+ out.push(key);
4063
+ }
4064
+ return out;
4065
+ }
4066
+ /**
4067
+ * @param {Record<string, any>} trie
4068
+ * @param {string} key
4069
+ */
4070
+ function depMatchesTrie(trie, key) {
4071
+ const root = depRootField(key);
4072
+ const node = trie[root];
4073
+ if (!node)
4074
+ return false;
4075
+ if (node.replace) {
4076
+ return key === root || key === `${root}.*` || key.startsWith(`${root}.`) || key.startsWith(`${root}[`);
4077
+ }
4078
+ if (key === `${root}.*`) {
4079
+ // Bare `field.*` soft/structure channel: item replace / array structure only —
4080
+ // NOT deep leaf writes (`tags.0.label`); those use `tags.*.label` BindingId.
4081
+ return structureStarMatches(node);
4082
+ }
4083
+ // Bare field: replace-only.
4084
+ if (key === root)
4085
+ return false;
4086
+ // Path channel: `tags.*.label` — wildcard index under list root.
4087
+ const starPrefix = `${root}.*`;
4088
+ if (key === starPrefix || key.startsWith(`${starPrefix}.`)) {
4089
+ const rest = key === starPrefix
4090
+ ? []
4091
+ : key
4092
+ .slice(starPrefix.length + 1)
4093
+ .split('.')
4094
+ .filter(Boolean);
4095
+ return wildcardIndexDirtyCovers(node, rest);
4096
+ }
4097
+ // Stable ListItem form `tags[key=…].label` — treat `[key=…]` as wildcard index.
4098
+ if (key.startsWith(`${root}[`)) {
4099
+ const afterBracket = key.indexOf(']');
4100
+ if (afterBracket > root.length) {
4101
+ const rest = key.length > afterBracket + 1 && key[afterBracket + 1] === '.'
4102
+ ? key
4103
+ .slice(afterBracket + 2)
4104
+ .split('.')
4105
+ .filter(Boolean)
4106
+ : [];
4107
+ return wildcardIndexDirtyCovers(node, rest);
4108
+ }
4109
+ }
4110
+ const segs = key
4111
+ .slice(root.length + 1)
4112
+ .split('.')
4113
+ .filter(Boolean);
4114
+ return pathDirtyCovers(node, segs);
4115
+ }
4116
+ /** `tags.*` structure soft-refresh: replace or index-level dirty, not leaf-only. */
4117
+ function structureStarMatches(node) {
4118
+ if (!node)
4119
+ return false;
4120
+ if (node.replace || node.dirty)
4121
+ return true;
4122
+ if (!node.children)
4123
+ return false;
4124
+ for (const idx of Object.keys(node.children)) {
4125
+ const child = node.children[idx];
4126
+ // Index node dirty/replace → item identity changed.
4127
+ if (child && (child.replace || child.dirty))
4128
+ return true;
4129
+ }
4130
+ return false;
4131
+ }
4132
+ /** `tags.*.label` / `tags[key=x].label` vs dirty trie under `tags`. */
4133
+ function wildcardIndexDirtyCovers(node, restSegs) {
4134
+ if (!node || node.replace)
4135
+ return !!node?.replace;
4136
+ if (node.dirty)
4137
+ return true;
4138
+ if (!node.children)
4139
+ return false;
4140
+ for (const idx of Object.keys(node.children)) {
4141
+ const child = node.children[idx];
4142
+ if (restSegs.length === 0) {
4143
+ if (trieHasAnyDirty(child))
4144
+ return true;
4145
+ }
4146
+ else if (pathDirtyCovers(child, restSegs)) {
4147
+ return true;
4148
+ }
4149
+ }
4150
+ return false;
4151
+ }
4152
+ function trieHasAnyDirty(node) {
4153
+ if (!node || node.replace)
4154
+ return !!node;
4155
+ if (node.dirty)
4156
+ return true;
4157
+ if (!node.children)
4158
+ return false;
4159
+ for (const k of Object.keys(node.children)) {
4160
+ if (trieHasAnyDirty(node.children[k]))
4161
+ return true;
4162
+ }
4163
+ return false;
4164
+ }
4165
+ /**
4166
+ * Wake if write is at/under dep, or dep is under write (parent covers children).
4167
+ * @param {any} node root trie node for field
4168
+ * @param {string[]} depSegs
4169
+ */
4170
+ function pathDirtyCovers(node, depSegs) {
4171
+ let cur = node;
4172
+ for (let i = 0; i < depSegs.length; i++) {
4173
+ if (!cur || cur.replace)
4174
+ return !!cur?.replace;
4175
+ if (cur.dirty)
4176
+ return true; // write parent covers this dep
4177
+ if (!cur.children)
4178
+ return false;
4179
+ const next = cur.children[depSegs[i]];
4180
+ if (!next) {
4181
+ // No write along this dep path — but a write under a prefix?
4182
+ return false;
4183
+ }
4184
+ cur = next;
4185
+ }
4186
+ // Reached dep node: wake if dirty here or any dirty descendant (write under dep).
4187
+ return trieHasAnyDirty(cur);
4188
+ }
4189
+ /**
4190
+ * Dual-track match retained for tests / tooling.
4191
+ * @param {{ type: string, root: string, segs?: string[] }} notice
4192
+ * @param {string} key
4193
+ */
4194
+ function noticeMatchesDepKey(notice, key) {
4195
+ const trie = Object.create(null);
4196
+ insertDirtyNotice(trie, notice);
4197
+ return depMatchesTrie(trie, key);
4198
+ }
4199
+ /** Root field name from a dep key string (`user.name` → `user`, `tags.*` → `tags`). */
4200
+ function depRootField(dep) {
4201
+ if (!dep)
4202
+ return '';
4203
+ const star = dep.indexOf('.*');
4204
+ if (star >= 0)
4205
+ return dep.slice(0, star);
4206
+ const dot = dep.indexOf('.');
4207
+ if (dot >= 0)
4208
+ return dep.slice(0, dot);
4209
+ const bracket = dep.indexOf('[');
4210
+ if (bracket >= 0)
4211
+ return dep.slice(0, bracket);
4212
+ return dep;
4213
+ }
4214
+ /** Precise patches only — no full-tree fallback. @returns {Promise[]} */
4215
+ function refreshBinding(inst, bindingId, dirtyTrie = null) {
4216
+ const entry = inst.__vmzBindings && inst.__vmzBindings[bindingId];
4217
+ const jobs = [];
4218
+ if (!inst || inst.__vmzDestroyed || bindingId == null || !entry) {
4219
+ return jobs;
4220
+ }
4221
+ const depKey = (entry.deps && entry.deps[0]) || null;
4222
+ const trie = dirtyTrie || inst.__vmzDirtyTrie;
4223
+ const allowIdx = itemIndicesAllowedForDeps(trie, entry.deps);
4224
+ for (const fn of entry.patches) {
4225
+ if (allowIdx && !patchMatchesDirtyIndex(fn, allowIdx))
4226
+ continue;
4227
+ try {
4228
+ const ret = runPatch(fn, depKey, bindingId);
4229
+ if (ret && typeof ret.then === 'function')
4230
+ jobs.push(ret);
4231
+ }
4232
+ catch (err) {
4233
+ console.error('vmz:dom patch', err);
4234
+ }
4235
+ }
4236
+ return jobs;
4237
+ }
4238
+ /**
4239
+ * For ListItem path-channel deps (`tags.*.label`), restrict to dirty indices.
4240
+ * @param {Record<string, any>|null|undefined} trie
4241
+ * @param {string[]|null|undefined} deps
4242
+ * @returns {Set<string>|null} null = run all patches (replace / non-list deps)
4243
+ */
4244
+ function itemIndicesAllowedForDeps(trie, deps) {
4245
+ if (!trie || !deps || !deps.length)
4246
+ return null;
4247
+ let sawListChannel = false;
4248
+ /** @type {Set<string>|null} */
4249
+ let allow = null;
4250
+ for (const dep of deps) {
4251
+ const root = depRootField(dep);
4252
+ if (!root)
4253
+ continue;
4254
+ const starPrefix = `${root}.*`;
4255
+ const isListChannel = dep === starPrefix || dep.startsWith(`${starPrefix}.`) || (dep.startsWith(`${root}[`) && dep.includes(']'));
4256
+ if (!isListChannel)
4257
+ return null;
4258
+ sawListChannel = true;
4259
+ const node = trie[root];
4260
+ if (!node)
4261
+ continue;
4262
+ if (node.replace || node.dirty)
4263
+ return null; // whole list
4264
+ if (!node.children)
4265
+ continue;
4266
+ if (!allow)
4267
+ allow = new Set();
4268
+ for (const idx of Object.keys(node.children)) {
4269
+ const child = node.children[idx];
4270
+ if (!child)
4271
+ continue;
4272
+ if (child.replace || child.dirty || trieHasAnyDirty(child)) {
4273
+ allow.add(String(idx));
4274
+ }
4275
+ }
4276
+ }
4277
+ if (!sawListChannel)
4278
+ return null;
4279
+ return allow && allow.size ? allow : null;
4280
+ }
4281
+ /**
4282
+ * If every allowed list index is dirty on exactly the same single item field, return that field.
4283
+ * Used to hoist one monomorphic `applyByField[field]` across the update batch.
4284
+ * @param {Record<string, any> | null | undefined} trie
4285
+ * @param {string} listRoot
4286
+ * @param {Set<string>} allowIdx
4287
+ * @returns {string | null}
4288
+ */
4289
+ function soleDirtyItemField(trie, listRoot, allowIdx) {
4290
+ if (!trie || !listRoot || !allowIdx || !allowIdx.size)
4291
+ return null;
4292
+ const node = trie[listRoot];
4293
+ if (!node || node.replace || node.dirty || !node.children)
4294
+ return null;
4295
+ // Peek first index for the sole dirty field, then verify the rest match.
4296
+ // Fast path: index child has a single key (typical WritePath leaf) — no sibling scan.
4297
+ let field = null;
4298
+ for (const idx of allowIdx) {
4299
+ const child = node.children[String(idx)];
4300
+ if (!child || child.replace || child.dirty || !child.children)
4301
+ return null;
4302
+ const kids = child.children;
4303
+ const keys = Object.keys(kids);
4304
+ if (keys.length === 1) {
4305
+ const f = keys[0];
4306
+ const n = kids[f];
4307
+ if (!n || !(n.dirty || n.replace || trieHasAnyDirty(n)))
4308
+ return null;
4309
+ if (field == null)
4310
+ field = f;
4311
+ else if (field !== f)
4312
+ return null;
4313
+ continue;
4314
+ }
4315
+ if (field == null) {
4316
+ for (let k = 0; k < keys.length; k++) {
4317
+ const f = keys[k];
4318
+ const n = kids[f];
4319
+ if (n && (n.dirty || n.replace || trieHasAnyDirty(n))) {
4320
+ if (field != null)
4321
+ return null;
4322
+ field = f;
4323
+ }
4324
+ }
4325
+ if (field == null)
4326
+ return null;
4327
+ continue;
4328
+ }
4329
+ const n = kids[field];
4330
+ if (!n || !(n.dirty || n.replace || trieHasAnyDirty(n)))
4331
+ return null;
4332
+ for (let k = 0; k < keys.length; k++) {
4333
+ const f = keys[k];
4334
+ if (f === field)
4335
+ continue;
4336
+ const o = kids[f];
4337
+ if (o && (o.dirty || o.replace || trieHasAnyDirty(o)))
4338
+ return null;
4339
+ }
4340
+ }
4341
+ return field;
4342
+ }
4343
+ /**
4344
+ * Dirty item field names at list index (`rows.3.label` → `["label"]`).
4345
+ * @returns {string[]|null} null = whole item / unknown → full apply; [] = nothing
4346
+ */
4347
+ function dirtyItemFieldsAt(trie, listRoot, idx) {
4348
+ if (!trie || !listRoot)
4349
+ return null;
4350
+ const node = trie[listRoot];
4351
+ if (!node)
4352
+ return [];
4353
+ if (node.replace || node.dirty)
4354
+ return null;
4355
+ const child = node.children && node.children[String(idx)];
4356
+ if (!child)
4357
+ return [];
4358
+ if (child.replace || child.dirty)
4359
+ return null;
4360
+ if (!child.children)
4361
+ return null;
4362
+ /** @type {string[]} */
4363
+ const out = [];
4364
+ for (const field of Object.keys(child.children)) {
4365
+ const n = child.children[field];
4366
+ if (n && (n.dirty || n.replace || trieHasAnyDirty(n)))
4367
+ out.push(field);
4368
+ }
4369
+ return out;
4370
+ }
4371
+ function patchMatchesDirtyIndex(fn, allowIdx) {
4372
+ const idx = fn && fn.__vmzItemIndex;
4373
+ if (idx == null || idx === '')
4374
+ return true;
4375
+ return allowIdx.has(String(idx));
4376
+ }
4377
+ /** Legacy string-key patches (hand blueprints without BindingId). @returns {Promise[]} */
4378
+ function refreshField(inst, field) {
4379
+ const binders = inst.__vmzBinders;
4380
+ const jobs = [];
4381
+ if (!inst || inst.__vmzDestroyed || !field || !binders || !binders[field]) {
4382
+ return jobs;
4383
+ }
4384
+ for (const fn of binders[field]) {
4385
+ try {
4386
+ const ret = runPatch(fn, field, null);
4387
+ if (ret && typeof ret.then === 'function')
4388
+ jobs.push(ret);
4389
+ }
4390
+ catch (err) {
4391
+ console.error('vmz:dom patch', err);
4392
+ }
4393
+ }
4394
+ return jobs;
4395
+ }
4396
+ /**
4397
+ * Run `__vmzBinders` patches that are not owned by a BindingId entry.
4398
+ * Needed so `bindComponentProp` (bindingId null) still flushes when the same
4399
+ * dep also has IR bindText/bindAttr BindingIds.
4400
+ * @returns {Promise[]}
4401
+ */
4402
+ function refreshFieldBinderOnly(inst, field) {
4403
+ const binders = inst.__vmzBinders;
4404
+ const jobs = [];
4405
+ if (!inst || inst.__vmzDestroyed || !field || !binders || !binders[field]) {
4406
+ return jobs;
4407
+ }
4408
+ for (const fn of binders[field]) {
4409
+ if (patchHasBindingId(inst, fn))
4410
+ continue;
4411
+ try {
4412
+ const ret = runPatch(fn, field, null);
4413
+ if (ret && typeof ret.then === 'function')
4414
+ jobs.push(ret);
4415
+ }
4416
+ catch (err) {
4417
+ console.error('vmz:dom patch', err);
4418
+ }
4419
+ }
4420
+ return jobs;
4421
+ }
4422
+ /**
4423
+ * @param {object} inst
4424
+ * @param {number|string} bindingId
4425
+ * @param {string[]} deps
4426
+ */
4427
+ function reindexBindingDeps(inst, bindingId, deps) {
4428
+ if (!inst.__vmzDepToBindings)
4429
+ inst.__vmzDepToBindings = Object.create(null);
4430
+ const entry = inst.__vmzBindings[bindingId];
4431
+ if (!entry)
4432
+ return;
4433
+ for (const dep of entry.deps || []) {
4434
+ const list = inst.__vmzDepToBindings[dep];
4435
+ if (!list)
4436
+ continue;
4437
+ const j = list.indexOf(bindingId);
4438
+ if (j >= 0)
4439
+ list.splice(j, 1);
4440
+ if (list.length === 0)
4441
+ delete inst.__vmzDepToBindings[dep];
4442
+ }
4443
+ entry.deps = [...(deps || [])];
4444
+ for (const dep of entry.deps) {
4445
+ if (!inst.__vmzDepToBindings[dep])
4446
+ inst.__vmzDepToBindings[dep] = [];
4447
+ if (!inst.__vmzDepToBindings[dep].includes(bindingId)) {
4448
+ inst.__vmzDepToBindings[dep].push(bindingId);
4449
+ }
4450
+ }
4451
+ }
4452
+ /**
4453
+ * @param {object} inst
4454
+ * @param {string[]} deps
4455
+ * @param {() => any} fn
4456
+ * @param {number|string|null|undefined} [bindingId]
4457
+ */
4458
+ function registerBind(inst, deps, fn, bindingId = null) {
4459
+ if (!inst.__vmzBinders)
4460
+ inst.__vmzBinders = Object.create(null);
4461
+ for (const dep of deps || []) {
4462
+ if (!inst.__vmzBinders[dep])
4463
+ inst.__vmzBinders[dep] = [];
4464
+ inst.__vmzBinders[dep].push(fn);
4465
+ }
4466
+ if (bindingId == null)
4467
+ return;
4468
+ if (!inst.__vmzBindings)
4469
+ inst.__vmzBindings = Object.create(null);
4470
+ let entry = inst.__vmzBindings[bindingId];
4471
+ if (!entry) {
4472
+ entry = { id: bindingId, deps: [], patches: [] };
4473
+ inst.__vmzBindings[bindingId] = entry;
4474
+ }
4475
+ if (!entry.patches.includes(fn))
4476
+ entry.patches.push(fn);
4477
+ reindexBindingDeps(inst, bindingId, deps || []);
4478
+ }
4479
+ /**
4480
+ * @param {object} inst
4481
+ * @param {string[]} deps
4482
+ * @param {() => any} fn
4483
+ * @param {number|string|null|undefined} [bindingId]
4484
+ */
4485
+ function unregisterBind(inst, deps, fn, bindingId = null) {
4486
+ const binders = inst.__vmzBinders;
4487
+ if (binders) {
4488
+ for (const dep of deps || []) {
4489
+ const list = binders[dep];
4490
+ if (!list)
4491
+ continue;
4492
+ const i = list.indexOf(fn);
4493
+ if (i >= 0)
4494
+ list.splice(i, 1);
4495
+ if (list.length === 0)
4496
+ delete binders[dep];
4497
+ }
4498
+ }
4499
+ if (bindingId == null || !inst.__vmzBindings)
4500
+ return;
4501
+ const entry = inst.__vmzBindings[bindingId];
4502
+ if (!entry)
4503
+ return;
4504
+ const i = entry.patches.indexOf(fn);
4505
+ if (i >= 0)
4506
+ entry.patches.splice(i, 1);
4507
+ if (entry.patches.length === 0) {
4508
+ reindexBindingDeps(inst, bindingId, []);
4509
+ delete inst.__vmzBindings[bindingId];
4510
+ }
4511
+ }
4512
+ /**
4513
+ * True when the container already has meaningful DOM (SSR / resume shell).
4514
+ * @param {Element} el
4515
+ */
4516
+ export function hasMeaningfulChild(el) {
4517
+ for (const n of el.childNodes) {
4518
+ if (n.nodeType === 1)
4519
+ return true;
4520
+ if (n.nodeType === 3 && String(n.textContent).trim() !== '')
4521
+ return true;
4522
+ }
4523
+ return false;
4524
+ }
4525
+ function patchHasBindingId(inst, fn) {
4526
+ const bindings = inst && inst.__vmzBindings;
4527
+ if (!bindings)
4528
+ return false;
4529
+ for (const id of Object.keys(bindings)) {
4530
+ const patches = bindings[id].patches;
4531
+ if (patches && patches.includes(fn))
4532
+ return true;
4533
+ }
4534
+ return false;
4535
+ }
4536
+ /** Tag each-item patches with list index for ListItem path-channel filtering. */
4537
+ function tagItemPatches(patches, index) {
4538
+ if (!patches)
4539
+ return;
4540
+ const idx = String(index);
4541
+ for (const p of patches) {
4542
+ if (typeof p === 'function')
4543
+ p.__vmzItemIndex = idx;
4544
+ }
4545
+ }