@vmz/core 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4565 @@
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.__vmzT0 = null;
1162
+ entry.__vmzT1 = null;
1163
+ entry.__vmzE0 = null;
1164
+ entry.__vmzE1 = null;
1165
+ entry.__vmzE2 = null;
1166
+ entry.__vmzE3 = null;
1167
+ entry.__vmzTexts = null;
1168
+ entry.__vmzBp = null;
1169
+ return;
1170
+ }
1171
+ if (!entry.bp || entryPool.length >= 4096)
1172
+ return;
1173
+ entry.item = null;
1174
+ entry.dom = null;
1175
+ entry.t0 = null;
1176
+ entry.t1 = null;
1177
+ entry.a0 = null;
1178
+ entry.patches = null;
1179
+ entryPool.push(entry);
1180
+ };
1181
+ const entryDom = (entry) => (entry && entry.nodeType === 1 ? entry : entry && entry.dom);
1182
+ const entryIsBp = (entry) => !!(entry && (entry.nodeType === 1 || entry.bp || entry.__vmzBp));
1183
+ const entryItem = (entry) => {
1184
+ if (!entry)
1185
+ return null;
1186
+ if (entry.nodeType === 1)
1187
+ return entry.__vmzBox;
1188
+ if (entry.bp)
1189
+ return entry.item;
1190
+ return entry.box && entry.box.item;
1191
+ };
1192
+ const rowKeyOf = (item, index) => {
1193
+ if (rowKeyField != null && item != null)
1194
+ return item[rowKeyField];
1195
+ return keyOf(item, index);
1196
+ };
1197
+ /** Drop all row DOM between markers; rowKernel rows skip per-node dispose walks. */
1198
+ const fastWipeRows = () => {
1199
+ const parent = end.parentNode;
1200
+ if (!parent) {
1201
+ keyed.clear();
1202
+ entryByIndex.length = 0;
1203
+ return;
1204
+ }
1205
+ let node = start.nextSibling;
1206
+ if (node && node !== end) {
1207
+ if (hasRowKernel || (blueprint && blueprintOk)) {
1208
+ const range = document.createRange();
1209
+ range.setStartBefore(node);
1210
+ range.setEndBefore(end);
1211
+ range.deleteContents();
1212
+ }
1213
+ else {
1214
+ while (node && node !== end) {
1215
+ const next = node.nextSibling;
1216
+ noteDomRemove();
1217
+ clearDomEvt(node);
1218
+ disposeDomTree(node);
1219
+ node.remove();
1220
+ node = next;
1221
+ }
1222
+ }
1223
+ }
1224
+ for (const [, entry] of keyed)
1225
+ releaseBpEntry(entry);
1226
+ keyed.clear();
1227
+ entryByIndex.length = 0;
1228
+ };
1229
+ /** Rebuild index→entry after fresh create (rowKernel.create only fills keyed Map). */
1230
+ const rebuildEntryByIndex = (list) => {
1231
+ const n = list.length;
1232
+ entryByIndex = new Array(n);
1233
+ for (let i = 0; i < n; i++) {
1234
+ entryByIndex[i] = keyed.get(rowKeyOf(list[i], i));
1235
+ }
1236
+ };
1237
+ const makeChildGetter = (path) => {
1238
+ const len = path.length;
1239
+ if (len === 0)
1240
+ return (root) => root;
1241
+ if (len === 1) {
1242
+ const a = path[0];
1243
+ return (root) => root.childNodes[a];
1244
+ }
1245
+ if (len === 2) {
1246
+ const a = path[0];
1247
+ const b = path[1];
1248
+ return (root) => root.childNodes[a].childNodes[b];
1249
+ }
1250
+ if (len === 3) {
1251
+ const a = path[0];
1252
+ const b = path[1];
1253
+ const c = path[2];
1254
+ return (root) => root.childNodes[a].childNodes[b].childNodes[c];
1255
+ }
1256
+ return (root) => {
1257
+ let n = /** @type {Node} */ (root);
1258
+ for (let i = 0; i < len; i++)
1259
+ n = n.childNodes[path[i]];
1260
+ return n;
1261
+ };
1262
+ };
1263
+ const userCreateItem = spec.createItem;
1264
+ // Compile-time row kernel (Rust Direct emit) — skip runtime blueprint recording.
1265
+ if (spec.rowKernel && typeof spec.rowKernel.html === 'string' && typeof spec.rowKernel.hydrate === 'function') {
1266
+ try {
1267
+ const tplHost = document.createElement('template');
1268
+ tplHost.innerHTML = spec.rowKernel.html;
1269
+ const row = tplHost.content.firstElementChild;
1270
+ if (row && row.nodeType === 1) {
1271
+ blueprint = {
1272
+ tpl: /** @type {Element} */ (row.cloneNode(true)),
1273
+ texts: [],
1274
+ attrs: [],
1275
+ ons: [],
1276
+ bindIds: new Set(),
1277
+ };
1278
+ blueprintOk = true;
1279
+ hasRowKernel = true;
1280
+ rowKeyField = typeof spec.rowKernel.keyField === 'string' && spec.rowKernel.keyField ? spec.rowKernel.keyField : null;
1281
+ rowActArgField =
1282
+ typeof spec.rowKernel.actArgField === 'string' && spec.rowKernel.actArgField ? spec.rowKernel.actArgField : null;
1283
+ blueprintBindIds = new Set(['__vmzRk']);
1284
+ for (const ev of spec.rowKernel.events || [])
1285
+ needDelegate(ev);
1286
+ rkHostFieldSet = new Set();
1287
+ for (const hf of spec.rowKernel.hostFields || []) {
1288
+ if (typeof hf === 'string' && hf) {
1289
+ rkHostFieldSet.add(hf);
1290
+ ensureHostDispatcher(hf);
1291
+ }
1292
+ }
1293
+ // Leaf path writes (`rows.0.label`) need `rows.*.label`, not bare `rows.*`.
1294
+ {
1295
+ const listRoot = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || '';
1296
+ if (listRoot) {
1297
+ /** @type {string[]} */
1298
+ const leafDeps = [`${listRoot}.*`];
1299
+ const fields = Array.isArray(spec.rowKernel.itemFields) ? spec.rowKernel.itemFields : [];
1300
+ for (const f of fields) {
1301
+ if (typeof f === 'string' && f)
1302
+ leafDeps.push(`${listRoot}.*.${f}`);
1303
+ }
1304
+ ensureListDispatcher('__vmzRk', leafDeps);
1305
+ }
1306
+ }
1307
+ const rkHydrate = spec.rowKernel.hydrate;
1308
+ const rkApply = spec.rowKernel.apply;
1309
+ // Assign outer binding — do not shadow with const (leaf hot path reads it).
1310
+ rkApplyByField =
1311
+ spec.rowKernel.applyByField && typeof spec.rowKernel.applyByField === 'object' ? spec.rowKernel.applyByField : null;
1312
+ rkTextSlots = spec.rowKernel.textSlots && typeof spec.rowKernel.textSlots === 'object' ? spec.rowKernel.textSlots : null;
1313
+ hydrateBp = (root, entry) => {
1314
+ const item = entry && typeof entry === 'object' && 'item' in entry && entry.item != null
1315
+ ? entry.item
1316
+ : entry && entry.__vmzBox != null
1317
+ ? entry.__vmzBox
1318
+ : entry;
1319
+ rkHydrate.call(inst, root, item);
1320
+ };
1321
+ applyBp = (entry, slot) => {
1322
+ const root = entry && entry.nodeType === 1 ? entry : entry.dom;
1323
+ const item = entry && entry.nodeType === 1 ? entry.__vmzBox : entry.item;
1324
+ if (slot != null && rkApplyByField) {
1325
+ const f = rkApplyByField[slot];
1326
+ if (typeof f === 'function') {
1327
+ if (rkHostFieldSet.has(slot))
1328
+ f.call(inst, root, item);
1329
+ else
1330
+ f(root, item);
1331
+ return;
1332
+ }
1333
+ }
1334
+ if (typeof rkApply === 'function')
1335
+ rkApply.call(inst, root, item);
1336
+ };
1337
+ // Event update: WritePath accumulates idxs → drain with one applyByField fn.
1338
+ const listRootForLeaf = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || '';
1339
+ inst.__vmzDrainLeafDirty = () => {
1340
+ const ld = inst.__vmzLeafDirty;
1341
+ if (!ld)
1342
+ return;
1343
+ if (!rkApplyByField || (listRootForLeaf && ld.root !== listRootForLeaf)) {
1344
+ promoteLeafDirtyToTrie(inst);
1345
+ return;
1346
+ }
1347
+ const f = rkApplyByField[ld.field];
1348
+ if (typeof f !== 'function') {
1349
+ promoteLeafDirtyToTrie(inst);
1350
+ return;
1351
+ }
1352
+ const idxs = ld.idxs;
1353
+ inst.__vmzLeafDirty = null;
1354
+ const list = readList();
1355
+ const entries = entryByIndex;
1356
+ const nList = list.length;
1357
+ if (rkHostFieldSet.has(ld.field)) {
1358
+ for (let k = 0; k < idxs.length; k++) {
1359
+ const i = idxs[k];
1360
+ if (i < 0 || i >= nList)
1361
+ continue;
1362
+ const entry = entries[i];
1363
+ if (!entry || entry.nodeType !== 1)
1364
+ continue;
1365
+ const item = list[i];
1366
+ if (entry.__vmzBox !== item)
1367
+ entry.__vmzBox = item;
1368
+ f.call(inst, entry, item);
1369
+ }
1370
+ }
1371
+ else {
1372
+ for (let k = 0; k < idxs.length; k++) {
1373
+ const i = idxs[k];
1374
+ if (i < 0 || i >= nList)
1375
+ continue;
1376
+ const entry = entries[i];
1377
+ if (entry && entry.nodeType === 1)
1378
+ f(entry, list[i]);
1379
+ }
1380
+ }
1381
+ };
1382
+ }
1383
+ }
1384
+ catch (err) {
1385
+ console.error('vmz:dom rowKernel', err);
1386
+ blueprint = null;
1387
+ blueprintOk = true;
1388
+ hasRowKernel = false;
1389
+ rowKeyField = null;
1390
+ rowActArgField = null;
1391
+ hydrateBp = null;
1392
+ applyBp = null;
1393
+ rkApplyByField = null;
1394
+ rkTextSlots = null;
1395
+ rkHostFieldSet = new Set();
1396
+ if (inst.__vmzDrainLeafDirty)
1397
+ inst.__vmzDrainLeafDirty = null;
1398
+ inst.__vmzLeafDirty = null;
1399
+ }
1400
+ }
1401
+ const probeItemField = (get, box) => {
1402
+ const item = box.item;
1403
+ if (!item || (typeof item !== 'object' && typeof item !== 'function'))
1404
+ return null;
1405
+ let field = null;
1406
+ const proxy = new Proxy(item, {
1407
+ get(t, p, r) {
1408
+ if (typeof p === 'string' || typeof p === 'symbol')
1409
+ field = String(p);
1410
+ return Reflect.get(t, p, r);
1411
+ },
1412
+ });
1413
+ const prev = box.item;
1414
+ box.item = proxy;
1415
+ try {
1416
+ get.call(inst);
1417
+ }
1418
+ catch {
1419
+ /* ignore */
1420
+ }
1421
+ box.item = prev;
1422
+ return field;
1423
+ };
1424
+ /**
1425
+ * Probe on/off class strings for `this.<host> === item.<itemField> ? … : …`.
1426
+ * Host/item field names come from binding deps — not hardcoded.
1427
+ */
1428
+ const probeHostItemClass = (get, box, hostField, itemField) => {
1429
+ if (!hostField || !itemField)
1430
+ return { onVal: '', offVal: '' };
1431
+ const prev = inst[hostField];
1432
+ const matchVal = box.item != null ? box.item[itemField] : undefined;
1433
+ let onVal = '';
1434
+ let offVal = '';
1435
+ const quiet = !!inst.__vmzQuiet;
1436
+ inst.__vmzQuiet = true;
1437
+ try {
1438
+ inst[hostField] = matchVal;
1439
+ onVal = String(get.call(inst) ?? '');
1440
+ // Distinct off value for number / other keys.
1441
+ if (typeof matchVal === 'number') {
1442
+ inst[hostField] = matchVal === 0 ? -1 : 0;
1443
+ if (inst[hostField] === matchVal)
1444
+ inst[hostField] = undefined;
1445
+ }
1446
+ else {
1447
+ inst[hostField] = matchVal === '' ? '__vmz_off__' : '';
1448
+ if (inst[hostField] === matchVal)
1449
+ inst[hostField] = undefined;
1450
+ }
1451
+ offVal = String(get.call(inst) ?? '');
1452
+ }
1453
+ catch {
1454
+ onVal = '';
1455
+ offVal = '';
1456
+ }
1457
+ finally {
1458
+ inst[hostField] = prev;
1459
+ inst.__vmzQuiet = quiet;
1460
+ }
1461
+ return { onVal, offVal };
1462
+ };
1463
+ const sealBlueprintDispatchers = () => {
1464
+ if (!blueprint || blueprintBindIds)
1465
+ return;
1466
+ /** @type {Set<string>} */
1467
+ const ids = new Set();
1468
+ for (const s of blueprint.texts) {
1469
+ if (s.bindingId != null) {
1470
+ ids.add(String(s.bindingId));
1471
+ ensureListDispatcher(s.bindingId, s.deps);
1472
+ }
1473
+ }
1474
+ for (const s of blueprint.attrs) {
1475
+ if (s.bindingId != null) {
1476
+ ids.add(String(s.bindingId));
1477
+ ensureListDispatcher(s.bindingId, s.deps);
1478
+ }
1479
+ for (const d of s.deps || []) {
1480
+ if (!d || d.includes('.*') || (d.includes('[') && d.includes(']')))
1481
+ continue;
1482
+ const rootField = depRootField(d) || d;
1483
+ if (rootField && rootField.indexOf('.') < 0)
1484
+ ensureHostDispatcher(rootField);
1485
+ }
1486
+ }
1487
+ for (const s of blueprint.ons) {
1488
+ needDelegate(s.type);
1489
+ }
1490
+ blueprintBindIds = ids;
1491
+ blueprint.bindIds = ids;
1492
+ compileBlueprintKernels();
1493
+ };
1494
+ const compileBlueprintKernels = () => {
1495
+ if (!blueprint || hydrateBp)
1496
+ return;
1497
+ const textSlots = blueprint.texts;
1498
+ const attrSlots = blueprint.attrs;
1499
+ const onSlots = blueprint.ons;
1500
+ const nText = textSlots.length;
1501
+ const nAttr = attrSlots.length;
1502
+ const nOn = onSlots.length;
1503
+ // Fallback only (no compile-time rowKernel). Field/path walks come from
1504
+ // recorded slots — shape-specific kernels belong in row_kernel.rs.
1505
+ hydrateBp = (root, entry) => {
1506
+ const item = entry && entry.item != null ? entry.item : entry;
1507
+ root.__vmzBox = item;
1508
+ /** @type {Array<Text>} */
1509
+ const textNodes = new Array(nText);
1510
+ /** @type {Array<Element>} */
1511
+ const attrEls = new Array(nAttr);
1512
+ for (let i = 0; i < nText; i++)
1513
+ textNodes[i] = /** @type {Text} */ (textSlots[i].get(root));
1514
+ for (let i = 0; i < nAttr; i++)
1515
+ attrEls[i] = attrSlots[i].get(root);
1516
+ for (let i = 0; i < nOn; i++) {
1517
+ const el = onSlots[i].get(root);
1518
+ if (!el.__vmzAct) {
1519
+ el.__vmzAct = onSlots[i].method;
1520
+ }
1521
+ }
1522
+ for (let i = 0; i < nText; i++) {
1523
+ const v = item == null ? '' : item[textSlots[i].field];
1524
+ textNodes[i].nodeValue = v == null ? '' : v + '';
1525
+ }
1526
+ for (let i = 0; i < nAttr; i++) {
1527
+ const s = attrSlots[i];
1528
+ const el = attrEls[i];
1529
+ const host = s.hostField;
1530
+ const itemKey = s.itemField;
1531
+ if (!host || !itemKey)
1532
+ continue;
1533
+ const hv = inst[host];
1534
+ if (hv != null && item && hv === item[itemKey]) {
1535
+ if (s.name === 'class' || s.name === 'className')
1536
+ el.className = s.onVal;
1537
+ else
1538
+ applyDomAttr(el, s.name, s.onVal);
1539
+ }
1540
+ else if (s.offVal) {
1541
+ if (s.name === 'class' || s.name === 'className')
1542
+ el.className = s.offVal;
1543
+ else
1544
+ applyDomAttr(el, s.name, s.offVal);
1545
+ }
1546
+ }
1547
+ entry.tn = textNodes;
1548
+ entry.ae = attrEls;
1549
+ entry.dom = root;
1550
+ entry.bp = true;
1551
+ };
1552
+ applyBp = (entry) => {
1553
+ const item = entry.item != null ? entry.item : entry.__vmzBox;
1554
+ const textNodes = entry.tn;
1555
+ const attrEls = entry.ae;
1556
+ for (let i = 0; i < nText; i++) {
1557
+ const v = item == null ? '' : item[textSlots[i].field];
1558
+ textNodes[i].nodeValue = v == null ? '' : v + '';
1559
+ }
1560
+ for (let i = 0; i < nAttr; i++) {
1561
+ const s = attrSlots[i];
1562
+ const el = attrEls[i];
1563
+ if (!s.hostField || !s.itemField)
1564
+ continue;
1565
+ const raw = item && inst[s.hostField] === item[s.itemField] ? s.onVal : s.offVal;
1566
+ if (s.name === 'class' || s.name === 'className')
1567
+ el.className = raw;
1568
+ else
1569
+ applyDomAttr(el, s.name, raw);
1570
+ }
1571
+ };
1572
+ };
1573
+ const wireBlueprintItem = (root, box, patches) => {
1574
+ if (!blueprint)
1575
+ return null;
1576
+ sealBlueprintDispatchers();
1577
+ const entry = {
1578
+ item: box.item,
1579
+ index: box.index,
1580
+ dom: root,
1581
+ bp: true,
1582
+ t0: null,
1583
+ t1: null,
1584
+ a0: null,
1585
+ tn: null,
1586
+ ae: null,
1587
+ patches: patches || null,
1588
+ };
1589
+ hydrateBp(root, entry);
1590
+ if (patches) {
1591
+ const applyAll = () => applyBp(entry);
1592
+ applyAll.__vmzBindingIds = blueprintBindIds;
1593
+ applyAll.__vmzBindingId = null;
1594
+ applyAll.__vmzItemLocal = true;
1595
+ applyAll.__vmzBpEntry = entry;
1596
+ patches.push(applyAll);
1597
+ }
1598
+ return entry;
1599
+ };
1600
+ const recordFirstItem = (api, box, patches) => {
1601
+ /** @type {Element | null} */
1602
+ let root = null;
1603
+ /** Pending slots keep live node refs — Direct emit binds before appendChild. */
1604
+ /** @type {{ texts: any[], attrs: any[], ons: any[] }} */
1605
+ const pending = { texts: [], attrs: [], ons: [] };
1606
+ let recordFailed = false;
1607
+ const recordingApi = Object.assign({}, api, {
1608
+ el(tag) {
1609
+ const el = api.el(tag);
1610
+ if (!root)
1611
+ root = el;
1612
+ return el;
1613
+ },
1614
+ // Capture only — do not wireDirectBind (would orphan first-row binders).
1615
+ bindText(i, bindingId, deps, get, textNode, cf) {
1616
+ if (!root)
1617
+ return;
1618
+ // Blueprint recording aborted: fall back to normal wiring for remaining binds.
1619
+ if (recordFailed) {
1620
+ api.bindText(i, bindingId, deps, get, textNode, cf);
1621
+ return;
1622
+ }
1623
+ try {
1624
+ const raw = get.call(inst);
1625
+ if (textNode.nodeType === 3) /** @type {Text} */
1626
+ (textNode).nodeValue = String(raw ?? '');
1627
+ else
1628
+ textNode.textContent = String(raw ?? '');
1629
+ }
1630
+ catch {
1631
+ /* ignore */
1632
+ }
1633
+ pending.texts.push({
1634
+ node: textNode,
1635
+ bindingId,
1636
+ deps: Array.isArray(deps) ? deps.slice() : [],
1637
+ getFn: get,
1638
+ });
1639
+ },
1640
+ bindAttr(i, bindingId, deps, get, el, name, cf) {
1641
+ if (!root)
1642
+ return;
1643
+ if (recordFailed) {
1644
+ api.bindAttr(i, bindingId, deps, get, el, name, cf);
1645
+ return;
1646
+ }
1647
+ // Class bind eligible when deps include a bare host field (any name).
1648
+ const hasHostDep = (deps || []).some((d) => d && !d.includes('.*') && !d.includes('[') && String(d).indexOf('.') < 0);
1649
+ if ((name === 'class' || name === 'className') && hasHostDep) {
1650
+ try {
1651
+ const raw = get.call(inst);
1652
+ el.className = raw == null ? '' : String(raw);
1653
+ }
1654
+ catch {
1655
+ /* ignore */
1656
+ }
1657
+ pending.attrs.push({
1658
+ node: el,
1659
+ bindingId,
1660
+ deps: Array.isArray(deps) ? deps.slice() : [],
1661
+ name,
1662
+ getFn: get,
1663
+ });
1664
+ }
1665
+ else {
1666
+ recordFailed = true;
1667
+ api.bindAttr(i, bindingId, deps, get, el, name, cf);
1668
+ }
1669
+ },
1670
+ on(el, type, handler) {
1671
+ if (recordFailed) {
1672
+ api.on(el, type, handler);
1673
+ return;
1674
+ }
1675
+ const parsed = parseActionMethod(handler);
1676
+ if (parsed && root) {
1677
+ pending.ons.push({
1678
+ node: el,
1679
+ type,
1680
+ method: parsed.method,
1681
+ argField: parsed.argField,
1682
+ });
1683
+ actionArgFields[parsed.method] = parsed.argField;
1684
+ if (rowActArgField == null)
1685
+ rowActArgField = parsed.argField;
1686
+ root.__vmzBox = box;
1687
+ el.__vmzAct = parsed.method;
1688
+ // Expando only — AttributeMap tax on every row is worse than a per-row write.
1689
+ needDelegate(type);
1690
+ return;
1691
+ }
1692
+ api.on(el, type, handler);
1693
+ recordFailed = true;
1694
+ },
1695
+ });
1696
+ const dom = userCreateItem.call(inst, recordingApi, box);
1697
+ if (!recordFailed && dom && dom.nodeType === 1 && root === dom && (pending.texts.length > 0 || pending.attrs.length > 0)) {
1698
+ /** @type {any[]} */
1699
+ const texts = [];
1700
+ /** @type {any[]} */
1701
+ const attrs = [];
1702
+ /** @type {any[]} */
1703
+ const ons = [];
1704
+ for (const p of pending.texts) {
1705
+ const path = pathFromRoot(root, p.node);
1706
+ const field = probeItemField(p.getFn, box);
1707
+ if (!path || !field) {
1708
+ recordFailed = true;
1709
+ break;
1710
+ }
1711
+ texts.push({
1712
+ path,
1713
+ bindingId: p.bindingId,
1714
+ deps: p.deps,
1715
+ field,
1716
+ get: makeChildGetter(path),
1717
+ });
1718
+ }
1719
+ if (!recordFailed) {
1720
+ for (const p of pending.attrs) {
1721
+ const path = pathFromRoot(root, p.node);
1722
+ if (!path) {
1723
+ recordFailed = true;
1724
+ break;
1725
+ }
1726
+ const hostField = (() => {
1727
+ for (const d of p.deps || []) {
1728
+ if (!d)
1729
+ continue;
1730
+ if (d.includes('.*') || d.includes('['))
1731
+ continue;
1732
+ if (String(d).indexOf('.') < 0)
1733
+ return String(d);
1734
+ }
1735
+ return null;
1736
+ })();
1737
+ let itemField = null;
1738
+ for (const d of p.deps || []) {
1739
+ if (!d)
1740
+ continue;
1741
+ if (d.includes('.*')) {
1742
+ const m = String(d).match(/\*\.([A-Za-z_$][\w$]*)$/);
1743
+ if (m)
1744
+ itemField = m[1];
1745
+ }
1746
+ }
1747
+ if (!hostField || !itemField) {
1748
+ recordFailed = true;
1749
+ break;
1750
+ }
1751
+ const { onVal, offVal } = probeHostItemClass(p.getFn, box, hostField, itemField);
1752
+ attrs.push({
1753
+ path,
1754
+ bindingId: p.bindingId,
1755
+ deps: p.deps,
1756
+ name: p.name,
1757
+ onVal,
1758
+ offVal,
1759
+ hostField,
1760
+ itemField,
1761
+ get: makeChildGetter(path),
1762
+ });
1763
+ }
1764
+ }
1765
+ if (!recordFailed) {
1766
+ for (const p of pending.ons) {
1767
+ const path = pathFromRoot(root, p.node);
1768
+ if (!path) {
1769
+ recordFailed = true;
1770
+ break;
1771
+ }
1772
+ ons.push({
1773
+ path,
1774
+ type: p.type,
1775
+ method: p.method,
1776
+ get: makeChildGetter(path),
1777
+ });
1778
+ }
1779
+ }
1780
+ if (!recordFailed) {
1781
+ const tpl = /** @type {Element} */ (dom.cloneNode(true));
1782
+ clearDomEvt(tpl);
1783
+ blueprint = {
1784
+ tpl,
1785
+ texts,
1786
+ attrs,
1787
+ ons,
1788
+ bindIds: new Set(),
1789
+ };
1790
+ }
1791
+ }
1792
+ if (!blueprint)
1793
+ blueprintOk = false;
1794
+ return dom;
1795
+ };
1796
+ const createItem = (api, box, patches) => {
1797
+ if (blueprint && blueprintOk) {
1798
+ const root = /** @type {Element} */ (blueprint.tpl.cloneNode(true));
1799
+ wireBlueprintItem(root, box, patches);
1800
+ return root;
1801
+ }
1802
+ if (typeof userCreateItem !== 'function') {
1803
+ throw new Error('vmz:dom eachBlock createItem missing (no rowKernel fallback)');
1804
+ }
1805
+ if (blueprintOk) {
1806
+ const dom = recordFirstItem(api, box, patches);
1807
+ if (blueprint && dom && dom.nodeType === 1) {
1808
+ patches.length = 0;
1809
+ wireBlueprintItem(dom, box, patches);
1810
+ }
1811
+ return dom;
1812
+ }
1813
+ return userCreateItem.call(inst, api, box);
1814
+ };
1815
+ /**
1816
+ * Reorder / place item DOM with minimal mutations.
1817
+ * - already-correct → no-op
1818
+ * - pure 2-node swap → 1–2 insertBefore (common keyed list swap)
1819
+ * - append prefix → Fragment insert only new tail
1820
+ * - create / replace / complex → Fragment rebuild before `end`
1821
+ */
1822
+ const reconcileDomOrder = (nextNodes) => {
1823
+ const parent = end.parentNode;
1824
+ if (!parent)
1825
+ return;
1826
+ /** @type {ChildNode[]} */
1827
+ const curr = [];
1828
+ for (let n = start.nextSibling; n && n !== end; n = n.nextSibling) {
1829
+ curr.push(n);
1830
+ }
1831
+ if (curr.length === nextNodes.length) {
1832
+ let same = true;
1833
+ for (let i = 0; i < curr.length; i++) {
1834
+ if (curr[i] !== nextNodes[i]) {
1835
+ same = false;
1836
+ break;
1837
+ }
1838
+ }
1839
+ if (same)
1840
+ return;
1841
+ // Fast path: exactly two positions swapped (benchmark swaprows).
1842
+ /** @type {number[]} */
1843
+ const diff = [];
1844
+ for (let i = 0; i < curr.length; i++) {
1845
+ if (curr[i] !== nextNodes[i])
1846
+ diff.push(i);
1847
+ }
1848
+ if (diff.length === 2 && curr[diff[0]] === nextNodes[diff[1]] && curr[diff[1]] === nextNodes[diff[0]]) {
1849
+ const a = curr[diff[0]];
1850
+ const b = curr[diff[1]];
1851
+ const aNext = a.nextSibling;
1852
+ const bNext = b.nextSibling;
1853
+ noteDomMove();
1854
+ noteDomMove();
1855
+ if (aNext === b) {
1856
+ parent.insertBefore(b, a);
1857
+ }
1858
+ else if (bNext === a) {
1859
+ parent.insertBefore(a, b);
1860
+ }
1861
+ else {
1862
+ parent.insertBefore(b, aNext);
1863
+ parent.insertBefore(a, bNext);
1864
+ }
1865
+ return;
1866
+ }
1867
+ }
1868
+ // Append-only: existing live prefix unchanged, only new tail detached.
1869
+ if (curr.length < nextNodes.length && curr.length > 0) {
1870
+ let prefix = true;
1871
+ for (let i = 0; i < curr.length; i++) {
1872
+ if (curr[i] !== nextNodes[i]) {
1873
+ prefix = false;
1874
+ break;
1875
+ }
1876
+ }
1877
+ if (prefix) {
1878
+ const batch = document.createDocumentFragment();
1879
+ for (let i = curr.length; i < nextNodes.length; i++) {
1880
+ batch.appendChild(nextNodes[i]);
1881
+ }
1882
+ parent.insertBefore(batch, end);
1883
+ return;
1884
+ }
1885
+ }
1886
+ // Create / replace / complex reorder: one Fragment write.
1887
+ const batch = document.createDocumentFragment();
1888
+ for (const dom of nextNodes) {
1889
+ if (dom.parentNode)
1890
+ noteDomMove();
1891
+ batch.appendChild(dom);
1892
+ }
1893
+ parent.insertBefore(batch, end);
1894
+ };
1895
+ const apply = () => {
1896
+ if (inst.__vmzDestroyed)
1897
+ return;
1898
+ const applied = ++gen;
1899
+ const list = readList();
1900
+ const n = list.length;
1901
+ // Clear all rows.
1902
+ if (n === 0) {
1903
+ if (keyed.size)
1904
+ fastWipeRows();
1905
+ return;
1906
+ }
1907
+ // Full replace (no key reuse): wipe then fall into fresh create.
1908
+ if (keyed.size > 0 && hasRowKernel) {
1909
+ let reuse = false;
1910
+ for (let i = 0; i < n; i++) {
1911
+ if (keyed.has(rowKeyOf(list[i], i))) {
1912
+ reuse = true;
1913
+ break;
1914
+ }
1915
+ }
1916
+ if (!reuse)
1917
+ fastWipeRows();
1918
+ }
1919
+ // Fresh create into empty each: rowKernel skips blueprint recording (tpl from html).
1920
+ if (keyed.size === 0 && n > 0) {
1921
+ const parent = end.parentNode;
1922
+ const batch = document.createDocumentFragment();
1923
+ let startIdx = 0;
1924
+ let indexFilled = false;
1925
+ // Only record a runtime blueprint when there is no compile-time rowKernel.
1926
+ if ((!blueprint || !blueprintOk) && !hasRowKernel) {
1927
+ const box0 = { item: list[0], index: 0 };
1928
+ const patches0 = [];
1929
+ const prevPatches = directApi._itemPatches;
1930
+ const prevCtx = directApi._eachCtx;
1931
+ directApi._itemPatches = patches0;
1932
+ directApi._eachCtx = eachCtx;
1933
+ let dom0 = null;
1934
+ try {
1935
+ dom0 = createItem(directApi, box0, patches0);
1936
+ }
1937
+ finally {
1938
+ directApi._itemPatches = prevPatches;
1939
+ directApi._eachCtx = prevCtx;
1940
+ }
1941
+ if (applied !== gen || inst.__vmzDestroyed)
1942
+ return;
1943
+ if (!dom0)
1944
+ return;
1945
+ const k0 = itemKey(box0);
1946
+ if (dom0.nodeType === 1) /** @type {Element} */
1947
+ (dom0).__vmzKey = k0;
1948
+ // First row may already be blueprint-wired (patches cleared + hydrate).
1949
+ let entry0 = keyed.get(k0);
1950
+ if (!entry0) {
1951
+ if (patches0.length && patches0[0] && patches0[0].__vmzBpEntry) {
1952
+ entry0 = patches0[0].__vmzBpEntry;
1953
+ entry0.patches = patches0;
1954
+ }
1955
+ else if (blueprint && blueprintOk) {
1956
+ entry0 = wireBlueprintItem(/** @type {Element} */ (dom0), box0, patches0);
1957
+ }
1958
+ else {
1959
+ tagItemPatches(patches0, 0);
1960
+ entry0 = { box: box0, dom: dom0, patches: patches0 };
1961
+ }
1962
+ keyed.set(k0, entry0);
1963
+ }
1964
+ batch.appendChild(dom0);
1965
+ startIdx = 1;
1966
+ }
1967
+ if (blueprint && blueprintOk) {
1968
+ sealBlueprintDispatchers();
1969
+ const tpl = blueprint.tpl;
1970
+ if (hasRowKernel && spec.rowKernel && typeof spec.rowKernel.create === 'function') {
1971
+ // Shape-specific create loop is Rust-emitted (rowKernel.create).
1972
+ // Direct parent.insertBefore (no Fragment). When parent has only the
1973
+ // each markers as children, detach parent for the fill then reattach —
1974
+ // same structural trick as hand-tuned keyed apps (not app-specific).
1975
+ if (parent) {
1976
+ let detached = null;
1977
+ let reinsertAt = null;
1978
+ if (parent.nodeType === 1 && parent.parentNode) {
1979
+ let onlyMarkers = true;
1980
+ for (let c = parent.firstChild; c; c = c.nextSibling) {
1981
+ if (c !== start && c !== end) {
1982
+ onlyMarkers = false;
1983
+ break;
1984
+ }
1985
+ }
1986
+ if (onlyMarkers) {
1987
+ detached = parent.parentNode;
1988
+ reinsertAt = parent.nextSibling;
1989
+ detached.removeChild(parent);
1990
+ }
1991
+ }
1992
+ // Fill entryByIndex in the create loop — skip post-pass Map.get rebuild.
1993
+ entryByIndex = new Array(n);
1994
+ for (let i = 0; i < startIdx; i++) {
1995
+ entryByIndex[i] = keyed.get(rowKeyOf(list[i], i));
1996
+ }
1997
+ spec.rowKernel.create.call(inst, list, startIdx, tpl, keyed, parent, end, rowKeyOf, entryByIndex);
1998
+ if (detached)
1999
+ detached.insertBefore(parent, reinsertAt);
2000
+ indexFilled = true;
2001
+ }
2002
+ else {
2003
+ const hydrate = spec.rowKernel.hydrate;
2004
+ for (let i = startIdx; i < n; i++) {
2005
+ if (applied !== gen || inst.__vmzDestroyed)
2006
+ return;
2007
+ const item = list[i];
2008
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2009
+ hydrate.call(inst, root, item);
2010
+ const k = rowKeyOf(item, i);
2011
+ root.__vmzKey = k;
2012
+ keyed.set(k, root);
2013
+ batch.appendChild(root);
2014
+ }
2015
+ }
2016
+ }
2017
+ else if (hasRowKernel && hydrateBp) {
2018
+ const hydrate = spec.rowKernel.hydrate;
2019
+ for (let i = startIdx; i < n; i++) {
2020
+ if (applied !== gen || inst.__vmzDestroyed)
2021
+ return;
2022
+ const item = list[i];
2023
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2024
+ hydrate.call(inst, root, item);
2025
+ const k = rowKeyOf(item, i);
2026
+ root.__vmzKey = k;
2027
+ keyed.set(k, root);
2028
+ batch.appendChild(root);
2029
+ }
2030
+ }
2031
+ else {
2032
+ for (let i = startIdx; i < n; i++) {
2033
+ if (applied !== gen || inst.__vmzDestroyed)
2034
+ return;
2035
+ const item = list[i];
2036
+ const k = keyOf(item, i);
2037
+ const root = /** @type {Element} */ (tpl.cloneNode(true));
2038
+ const entry = {
2039
+ item,
2040
+ index: i,
2041
+ dom: root,
2042
+ bp: true,
2043
+ t0: null,
2044
+ t1: null,
2045
+ a0: null,
2046
+ patches: null,
2047
+ };
2048
+ hydrateBp(root, entry);
2049
+ root.__vmzKey = k;
2050
+ keyed.set(k, entry);
2051
+ batch.appendChild(root);
2052
+ }
2053
+ }
2054
+ }
2055
+ else {
2056
+ for (let i = startIdx; i < n; i++) {
2057
+ if (applied !== gen || inst.__vmzDestroyed)
2058
+ return;
2059
+ const box = { item: list[i], index: i };
2060
+ const k = itemKey(box);
2061
+ const patches = [];
2062
+ const prevPatches = directApi._itemPatches;
2063
+ const prevCtx = directApi._eachCtx;
2064
+ directApi._itemPatches = patches;
2065
+ directApi._eachCtx = eachCtx;
2066
+ let dom = null;
2067
+ try {
2068
+ dom = createItem(directApi, box, patches);
2069
+ }
2070
+ finally {
2071
+ directApi._itemPatches = prevPatches;
2072
+ directApi._eachCtx = prevCtx;
2073
+ }
2074
+ if (applied !== gen || inst.__vmzDestroyed)
2075
+ return;
2076
+ if (!dom)
2077
+ continue;
2078
+ tagItemPatches(patches, i);
2079
+ if (dom.nodeType === 1) /** @type {Element} */
2080
+ (dom).__vmzKey = k;
2081
+ keyed.set(k, { box, dom, patches });
2082
+ batch.appendChild(dom);
2083
+ }
2084
+ }
2085
+ if (applied !== gen || inst.__vmzDestroyed)
2086
+ return;
2087
+ if (parent && batch.firstChild)
2088
+ parent.insertBefore(batch, end);
2089
+ if (hasRowKernel && !indexFilled)
2090
+ rebuildEntryByIndex(list);
2091
+ if (end.isConnected)
2092
+ ensureDelegateAttached();
2093
+ else
2094
+ queueMicrotask(() => {
2095
+ if (!inst.__vmzDestroyed)
2096
+ ensureDelegateAttached();
2097
+ });
2098
+ return;
2099
+ }
2100
+ // Pure identity transposition (slice + two index swaps + list replace):
2101
+ // same n entries, exactly two positions exchanged — O(n) identity scan + 2 DOM moves.
2102
+ // Skips Map/key/nextNodes/sibling rebuild (main swap script cost vs hand-tuned keyed apps).
2103
+ if (hasRowKernel && entryByIndex.length === n && keyed.size === n && n > 0) {
2104
+ let missA = -1;
2105
+ let missB = -1;
2106
+ let identityOk = true;
2107
+ for (let i = 0; i < n; i++) {
2108
+ const entry = entryByIndex[i];
2109
+ if (!entry || entry.nodeType !== 1) {
2110
+ identityOk = false;
2111
+ break;
2112
+ }
2113
+ if (entry.__vmzBox === list[i])
2114
+ continue;
2115
+ if (missA < 0)
2116
+ missA = i;
2117
+ else if (missB < 0)
2118
+ missB = i;
2119
+ else {
2120
+ identityOk = false;
2121
+ break;
2122
+ }
2123
+ }
2124
+ if (identityOk) {
2125
+ if (missA < 0) {
2126
+ // Same order / same object refs — replace was a no-op for DOM.
2127
+ return;
2128
+ }
2129
+ if (missB >= 0) {
2130
+ const ea = entryByIndex[missA];
2131
+ const eb = entryByIndex[missB];
2132
+ if (ea.__vmzBox === list[missB] && eb.__vmzBox === list[missA]) {
2133
+ const parent = end.parentNode;
2134
+ if (parent) {
2135
+ const aNext = ea.nextSibling;
2136
+ const bNext = eb.nextSibling;
2137
+ noteDomMove();
2138
+ noteDomMove();
2139
+ if (aNext === eb) {
2140
+ parent.insertBefore(eb, ea);
2141
+ }
2142
+ else if (bNext === ea) {
2143
+ parent.insertBefore(ea, eb);
2144
+ }
2145
+ else {
2146
+ parent.insertBefore(eb, aNext);
2147
+ parent.insertBefore(ea, bNext);
2148
+ }
2149
+ }
2150
+ entryByIndex[missA] = eb;
2151
+ entryByIndex[missB] = ea;
2152
+ if (end.isConnected)
2153
+ ensureDelegateAttached();
2154
+ else
2155
+ queueMicrotask(() => {
2156
+ if (!inst.__vmzDestroyed)
2157
+ ensureDelegateAttached();
2158
+ });
2159
+ return;
2160
+ }
2161
+ }
2162
+ }
2163
+ }
2164
+ const seen = new Set();
2165
+ /** @type {Node[]} */
2166
+ const nextNodes = [];
2167
+ for (let i = 0; i < n; i++) {
2168
+ if (applied !== gen || inst.__vmzDestroyed)
2169
+ return;
2170
+ const item = list[i];
2171
+ const k = rowKeyOf(item, i);
2172
+ seen.add(k);
2173
+ let entry = keyed.get(k);
2174
+ if (!entry) {
2175
+ if (hasRowKernel && blueprint && blueprintOk && hydrateBp) {
2176
+ const root = /** @type {Element} */ (blueprint.tpl.cloneNode(true));
2177
+ spec.rowKernel.hydrate.call(inst, root, item);
2178
+ root.__vmzKey = k;
2179
+ keyed.set(k, root);
2180
+ entry = root;
2181
+ }
2182
+ else {
2183
+ const box = { item, index: i };
2184
+ const patches = [];
2185
+ const prevPatches = directApi._itemPatches;
2186
+ const prevCtx = directApi._eachCtx;
2187
+ directApi._itemPatches = patches;
2188
+ directApi._eachCtx = eachCtx;
2189
+ let dom = null;
2190
+ try {
2191
+ dom = createItem(directApi, box, patches);
2192
+ }
2193
+ finally {
2194
+ directApi._itemPatches = prevPatches;
2195
+ directApi._eachCtx = prevCtx;
2196
+ }
2197
+ if (applied !== gen || inst.__vmzDestroyed)
2198
+ return;
2199
+ tagItemPatches(patches, i);
2200
+ if (dom) {
2201
+ if (dom.nodeType === 1) {
2202
+ // Client identity: expando only (see 01 each identity). SSR uses data-vmz-key.
2203
+ /** @type {Element} */ (dom).__vmzKey = k;
2204
+ }
2205
+ entry = { box, dom, patches };
2206
+ keyed.set(k, entry);
2207
+ }
2208
+ }
2209
+ }
2210
+ else {
2211
+ const sameItem = entryItem(entry) === item;
2212
+ if (entry.nodeType === 1) {
2213
+ entry.__vmzBox = item;
2214
+ }
2215
+ else if (entry.bp) {
2216
+ entry.item = item;
2217
+ entry.index = i;
2218
+ if (entry.dom)
2219
+ entry.dom.__vmzBox = entry.item;
2220
+ }
2221
+ else {
2222
+ entry.box.item = item;
2223
+ entry.box.index = i;
2224
+ tagItemPatches(entry.patches, i);
2225
+ }
2226
+ // Pure reorder (swap / move) keeps object identity — skip leaf patches.
2227
+ if (!sameItem) {
2228
+ if (entryIsBp(entry) && applyBp) {
2229
+ try {
2230
+ applyBp(entry);
2231
+ }
2232
+ catch (err) {
2233
+ console.error('vmz:dom each item', err);
2234
+ }
2235
+ }
2236
+ else if (entry.patches) {
2237
+ for (const p of entry.patches)
2238
+ runPatch(p, null);
2239
+ }
2240
+ }
2241
+ }
2242
+ if (entry)
2243
+ nextNodes.push(entryDom(entry));
2244
+ }
2245
+ if (applied !== gen || inst.__vmzDestroyed)
2246
+ return;
2247
+ for (const [k, entry] of [...keyed.entries()]) {
2248
+ if (seen.has(k))
2249
+ continue;
2250
+ noteDomRemove();
2251
+ const dom = entryDom(entry);
2252
+ if (hasRowKernel) {
2253
+ if (dom && dom.parentNode)
2254
+ dom.remove();
2255
+ }
2256
+ else {
2257
+ clearDomEvt(dom);
2258
+ disposeDomTree(dom);
2259
+ if (dom && dom.parentNode)
2260
+ dom.remove();
2261
+ }
2262
+ keyed.delete(k);
2263
+ releaseBpEntry(entry);
2264
+ }
2265
+ reconcileDomOrder(nextNodes);
2266
+ if (hasRowKernel)
2267
+ rebuildEntryByIndex(list);
2268
+ // First apply may run while start/end still sit in a DocumentFragment
2269
+ // (before mount appends). Defer until connected so clicks work.
2270
+ if (end.isConnected)
2271
+ ensureDelegateAttached();
2272
+ else
2273
+ queueMicrotask(() => {
2274
+ if (!inst.__vmzDestroyed)
2275
+ ensureDelegateAttached();
2276
+ });
2277
+ };
2278
+ registerBind(inst, deps || [], apply, bindingId);
2279
+ if (directApi._itemPatches)
2280
+ directApi._itemPatches.push(apply);
2281
+ // WriteBarrier `__vmzListTranspose` → O(1) entry/DOM exchange (no slice + replace flush).
2282
+ const listRootForTranspose = depRootField((deps && deps[0]) || '') || (deps && deps[0]) || null;
2283
+ const transposeEntries = (ia, ib) => {
2284
+ if (!hasRowKernel)
2285
+ return false;
2286
+ const n = entryByIndex.length;
2287
+ if (ia < 0 || ib < 0 || ia >= n || ib >= n || ia === ib)
2288
+ return false;
2289
+ const ea = entryByIndex[ia];
2290
+ const eb = entryByIndex[ib];
2291
+ if (!ea || !eb || ea.nodeType !== 1 || eb.nodeType !== 1)
2292
+ return false;
2293
+ const parent = end.parentNode;
2294
+ if (parent) {
2295
+ const aNext = ea.nextSibling;
2296
+ const bNext = eb.nextSibling;
2297
+ noteDomMove();
2298
+ noteDomMove();
2299
+ if (aNext === eb) {
2300
+ parent.insertBefore(eb, ea);
2301
+ }
2302
+ else if (bNext === ea) {
2303
+ parent.insertBefore(ea, eb);
2304
+ }
2305
+ else {
2306
+ parent.insertBefore(eb, aNext);
2307
+ parent.insertBefore(ea, bNext);
2308
+ }
2309
+ }
2310
+ entryByIndex[ia] = eb;
2311
+ entryByIndex[ib] = ea;
2312
+ return true;
2313
+ };
2314
+ /** Inline leaf apply for event write path (mutate + DOM, vanillajs-shaped). */
2315
+ const applyLeafAt = (idx, field, item) => {
2316
+ if (!hasRowKernel || !rkApplyByField)
2317
+ return false;
2318
+ const entry = entryByIndex[idx];
2319
+ if (!entry || entry.nodeType !== 1)
2320
+ return false;
2321
+ const f = rkApplyByField[field];
2322
+ if (typeof f !== 'function')
2323
+ return false;
2324
+ if (entry.__vmzBox !== item)
2325
+ entry.__vmzBox = item;
2326
+ if (rkHostFieldSet.has(field))
2327
+ f.call(inst, entry, item);
2328
+ else
2329
+ f(entry, item);
2330
+ return true;
2331
+ };
2332
+ /**
2333
+ * Own the whole stride loop (update-every-Nth): one cross-boundary call,
2334
+ * hoist text slot / applyByField, specialize string `+=`.
2335
+ */
2336
+ const compoundStride = (leaf, op, rhs, start, step) => {
2337
+ if (!hasRowKernel)
2338
+ return false;
2339
+ const arr = inst[listRootForTranspose];
2340
+ if (!Array.isArray(arr))
2341
+ return false;
2342
+ const s = +start || 0;
2343
+ const st = +step || 0;
2344
+ if (st <= 0)
2345
+ return false;
2346
+ const entries = entryByIndex;
2347
+ const n = arr.length;
2348
+ // Fastest path: text-only leaf → mutate + __vmzT{n}.nodeValue (no applyByField).
2349
+ const slot = rkTextSlots ? rkTextSlots[leaf] : undefined;
2350
+ if (op === '+' && slot != null && !rkHostFieldSet.has(leaf)) {
2351
+ if (slot === 0) {
2352
+ for (let i = s; i < n; i += st) {
2353
+ const item = arr[i];
2354
+ if (item == null || typeof item !== 'object')
2355
+ continue;
2356
+ const v = item[leaf] + rhs;
2357
+ item[leaf] = v;
2358
+ const entry = entries[i];
2359
+ if (entry && entry.nodeType === 1)
2360
+ entry.__vmzT0.nodeValue = v;
2361
+ }
2362
+ }
2363
+ else if (slot === 1) {
2364
+ for (let i = s; i < n; i += st) {
2365
+ const item = arr[i];
2366
+ if (item == null || typeof item !== 'object')
2367
+ continue;
2368
+ const v = item[leaf] + rhs;
2369
+ item[leaf] = v;
2370
+ const entry = entries[i];
2371
+ if (entry && entry.nodeType === 1)
2372
+ entry.__vmzT1.nodeValue = v;
2373
+ }
2374
+ }
2375
+ else {
2376
+ const tKey = '__vmzT' + slot;
2377
+ for (let i = s; i < n; i += st) {
2378
+ const item = arr[i];
2379
+ if (item == null || typeof item !== 'object')
2380
+ continue;
2381
+ const v = item[leaf] + rhs;
2382
+ item[leaf] = v;
2383
+ const entry = entries[i];
2384
+ if (entry && entry.nodeType === 1)
2385
+ entry[tKey].nodeValue = v;
2386
+ }
2387
+ }
2388
+ return true;
2389
+ }
2390
+ if (!rkApplyByField)
2391
+ return false;
2392
+ const f = rkApplyByField[leaf];
2393
+ if (typeof f !== 'function')
2394
+ return false;
2395
+ const needsThis = rkHostFieldSet.has(leaf);
2396
+ if (op === '+') {
2397
+ if (needsThis) {
2398
+ for (let i = s; i < n; i += st) {
2399
+ const item = arr[i];
2400
+ if (item == null || typeof item !== 'object')
2401
+ continue;
2402
+ item[leaf] = item[leaf] + rhs;
2403
+ const entry = entries[i];
2404
+ if (!entry || entry.nodeType !== 1)
2405
+ continue;
2406
+ if (entry.__vmzBox !== item)
2407
+ entry.__vmzBox = item;
2408
+ f.call(inst, entry, item);
2409
+ }
2410
+ }
2411
+ else {
2412
+ for (let i = s; i < n; i += st) {
2413
+ const item = arr[i];
2414
+ if (item == null || typeof item !== 'object')
2415
+ continue;
2416
+ item[leaf] = item[leaf] + rhs;
2417
+ const entry = entries[i];
2418
+ if (entry && entry.nodeType === 1)
2419
+ f(entry, item);
2420
+ }
2421
+ }
2422
+ return true;
2423
+ }
2424
+ for (let i = s; i < n; i += st) {
2425
+ const item = arr[i];
2426
+ if (item == null || typeof item !== 'object')
2427
+ continue;
2428
+ const cur = item[leaf];
2429
+ const value = applyCompoundOp(op, cur, rhs);
2430
+ if (Object.is(cur, value))
2431
+ continue;
2432
+ item[leaf] = value;
2433
+ if (slot != null && !needsThis) {
2434
+ const entry = entries[i];
2435
+ if (entry && entry.nodeType === 1) {
2436
+ if (slot === 0) {
2437
+ entry.__vmzT0.nodeValue = value;
2438
+ continue;
2439
+ }
2440
+ if (slot === 1) {
2441
+ entry.__vmzT1.nodeValue = value;
2442
+ continue;
2443
+ }
2444
+ const tn = entry['__vmzT' + slot];
2445
+ if (tn) {
2446
+ tn.nodeValue = value;
2447
+ continue;
2448
+ }
2449
+ }
2450
+ }
2451
+ const entry = entries[i];
2452
+ if (!entry || entry.nodeType !== 1)
2453
+ continue;
2454
+ if (entry.__vmzBox !== item)
2455
+ entry.__vmzBox = item;
2456
+ if (needsThis)
2457
+ f.call(inst, entry, item);
2458
+ else
2459
+ f(entry, item);
2460
+ }
2461
+ return true;
2462
+ };
2463
+ if (listRootForTranspose) {
2464
+ if (!inst.__vmzEachTranspose)
2465
+ inst.__vmzEachTranspose = Object.create(null);
2466
+ inst.__vmzEachTranspose[listRootForTranspose] = transposeEntries;
2467
+ if (!inst.__vmzEachApplyLeaf)
2468
+ inst.__vmzEachApplyLeaf = Object.create(null);
2469
+ inst.__vmzEachApplyLeaf[listRootForTranspose] = applyLeafAt;
2470
+ if (!inst.__vmzEachCompoundStride)
2471
+ inst.__vmzEachCompoundStride = Object.create(null);
2472
+ inst.__vmzEachCompoundStride[listRootForTranspose] = compoundStride;
2473
+ }
2474
+ start.__vmzDispose = () => {
2475
+ teardownDelegate();
2476
+ fastWipeRows();
2477
+ if (listRootForTranspose && inst.__vmzEachTranspose) {
2478
+ if (inst.__vmzEachTranspose[listRootForTranspose] === transposeEntries) {
2479
+ delete inst.__vmzEachTranspose[listRootForTranspose];
2480
+ }
2481
+ }
2482
+ if (listRootForTranspose && inst.__vmzEachApplyLeaf) {
2483
+ if (inst.__vmzEachApplyLeaf[listRootForTranspose] === applyLeafAt) {
2484
+ delete inst.__vmzEachApplyLeaf[listRootForTranspose];
2485
+ }
2486
+ }
2487
+ if (listRootForTranspose && inst.__vmzEachCompoundStride) {
2488
+ if (inst.__vmzEachCompoundStride[listRootForTranspose] === compoundStride) {
2489
+ delete inst.__vmzEachCompoundStride[listRootForTranspose];
2490
+ }
2491
+ }
2492
+ };
2493
+ const softDeps = [...new Set((deps || []).map((d) => `${depRootField(d)}.*`))];
2494
+ const softRefresh = () => {
2495
+ if (inst.__vmzDestroyed)
2496
+ return;
2497
+ const trie = inst.__vmzFlushTrie;
2498
+ const listRoot = depRootField((deps && deps[0]) || '') || '';
2499
+ // Full list replace is owned by apply(); soft channel is item/structure churn.
2500
+ if (trie && listRoot && trie[listRoot] && trie[listRoot].replace)
2501
+ return;
2502
+ const list = readList();
2503
+ const softKey = softDeps[0] || `${listRoot}.*`;
2504
+ for (let i = 0; i < list.length; i++) {
2505
+ const item = list[i];
2506
+ const k = rowKeyOf(item, i);
2507
+ const entry = keyed.get(k);
2508
+ if (!entry)
2509
+ continue;
2510
+ if (entryIsBp(entry)) {
2511
+ if (entry.nodeType === 1)
2512
+ entry.__vmzBox = item;
2513
+ else {
2514
+ entry.item = item;
2515
+ entry.index = i;
2516
+ if (entry.dom)
2517
+ entry.dom.__vmzBox = item;
2518
+ }
2519
+ if (applyBp)
2520
+ applyBp(entry);
2521
+ else if (hydrateBp)
2522
+ hydrateBp(entryDom(entry), entry);
2523
+ continue;
2524
+ }
2525
+ entry.box.item = item;
2526
+ entry.box.index = i;
2527
+ tagItemPatches(entry.patches, i);
2528
+ for (const p of entry.patches) {
2529
+ // Leaf BindingId patches are owned by list/host dispatchers .
2530
+ if (p.__vmzBindingId != null)
2531
+ continue;
2532
+ if (patchHasBindingId(inst, p))
2533
+ continue;
2534
+ try {
2535
+ runPatch(p, softKey, null);
2536
+ }
2537
+ catch (err) {
2538
+ console.error('vmz:dom each soft', err);
2539
+ }
2540
+ }
2541
+ }
2542
+ };
2543
+ registerBind(inst, softDeps, softRefresh, null);
2544
+ apply();
2545
+ return frag;
2546
+ },
2547
+ };
2548
+ /**
2549
+ * @param {object} inst
2550
+ * @param {string[]} deps
2551
+ * @param {() => any} fn
2552
+ * @param {number|string|null|undefined} bindingId
2553
+ */
2554
+ function trackDirectBind(inst, deps, fn, bindingId = null) {
2555
+ if (directApi._branchBinds) {
2556
+ directApi._branchBinds.push({ deps, fn, bindingId });
2557
+ if (directApi._itemPatches)
2558
+ directApi._itemPatches.push(fn);
2559
+ return;
2560
+ }
2561
+ // item binds stay on entry.patches; eachBlock registers one dispatcher per BindingId.
2562
+ if (directApi._itemPatches) {
2563
+ fn.__vmzItemLocal = true;
2564
+ directApi._itemPatches.push(fn);
2565
+ if (directApi._eachCtx) {
2566
+ directApi._eachCtx.noteItemBind(bindingId, deps || [], fn);
2567
+ }
2568
+ return;
2569
+ }
2570
+ registerBind(inst, deps, fn, bindingId);
2571
+ }
2572
+ /**
2573
+ * @param {object} inst
2574
+ * @param {number|string|null} bindingId
2575
+ * @param {string[]} deps
2576
+ * @param {() => any} get
2577
+ * @param {(raw: any) => void} write
2578
+ * @param {{ stable: string[], branches: Array<{ cond?: => any, deps: string[] }> } | null | undefined} [cf]
2579
+ */
2580
+ function wireDirectBind(inst, bindingId, deps, get, write, cf) {
2581
+ let activeBranch = -1;
2582
+ /** @type {string[]} */
2583
+ let liveDeps = Array.isArray(deps) ? deps.slice() : [];
2584
+ const pickCf = () => {
2585
+ if (!cf || !Array.isArray(cf.branches))
2586
+ return -1;
2587
+ for (let i = 0; i < cf.branches.length; i++) {
2588
+ const b = cf.branches[i];
2589
+ if (!b.cond)
2590
+ return i;
2591
+ try {
2592
+ if (b.cond.call(inst))
2593
+ return i;
2594
+ }
2595
+ catch {
2596
+ /* continue */
2597
+ }
2598
+ }
2599
+ return cf.branches.length - 1;
2600
+ };
2601
+ // Item-local CF whose branches only gate the same stable deps: skip branch switching.
2602
+ let simpleCf = false;
2603
+ if (cf && Array.isArray(cf.branches) && directApi._itemPatches) {
2604
+ simpleCf = cf.branches.every((b) => !b.deps || b.deps.length === 0);
2605
+ }
2606
+ const apply = () => {
2607
+ if (precision.enabled) {
2608
+ precision.bindingEvals++;
2609
+ for (const d of liveDeps || [])
2610
+ bumpMap(precision.bindingEvalsByDep, d);
2611
+ if (bindingId != null) {
2612
+ bumpMap(precision.bindingEvalsByBinding, String(bindingId));
2613
+ }
2614
+ }
2615
+ let raw;
2616
+ try {
2617
+ raw = get.call(inst);
2618
+ }
2619
+ catch {
2620
+ raw = null;
2621
+ }
2622
+ write(raw);
2623
+ if (!cf || !Array.isArray(cf.branches) || simpleCf || apply.__vmzItemLocal)
2624
+ return;
2625
+ const next = pickCf();
2626
+ if (next === activeBranch)
2627
+ return;
2628
+ activeBranch = next;
2629
+ const branch = cf.branches[next];
2630
+ const nextDeps = [...(cf.stable || []), ...((branch && branch.deps) || [])];
2631
+ const uniq = [...new Set(nextDeps)];
2632
+ unregisterBind(inst, liveDeps, apply, bindingId);
2633
+ liveDeps = uniq;
2634
+ registerBind(inst, liveDeps, apply, bindingId);
2635
+ };
2636
+ if (cf && Array.isArray(cf.branches) && !simpleCf) {
2637
+ activeBranch = pickCf();
2638
+ const branch = cf.branches[activeBranch];
2639
+ liveDeps = [...(cf.stable || []), ...((branch && branch.deps) || [])];
2640
+ liveDeps = [...new Set(liveDeps)];
2641
+ }
2642
+ else if (cf && Array.isArray(cf.branches) && simpleCf) {
2643
+ liveDeps = Array.isArray(cf.stable) && cf.stable.length ? cf.stable : liveDeps;
2644
+ }
2645
+ // Mark before first apply so CF branch switches never hit global registerBind.
2646
+ if (directApi._itemPatches)
2647
+ apply.__vmzItemLocal = true;
2648
+ apply();
2649
+ trackDirectBind(inst, liveDeps, apply, bindingId);
2650
+ }
2651
+ export function isEventPropName(name) {
2652
+ return typeof name === 'string' && /^on[A-Z]/.test(name);
2653
+ }
2654
+ /** Monotonic id for `bindComponentProp` BindingIds (per process). */
2655
+ let directPropBindSeq = 0;
2656
+ /** HTML boolean attributes: presence means true; `false`/`null` must remove the attr. */
2657
+ export const BOOLEAN_HTML_ATTRS = new Set([
2658
+ 'disabled',
2659
+ 'checked',
2660
+ 'selected',
2661
+ 'readonly',
2662
+ 'required',
2663
+ 'multiple',
2664
+ 'hidden',
2665
+ 'autofocus',
2666
+ 'autoplay',
2667
+ 'controls',
2668
+ 'loop',
2669
+ 'muted',
2670
+ 'open',
2671
+ 'novalidate',
2672
+ 'formnovalidate',
2673
+ 'defer',
2674
+ 'async',
2675
+ 'ismap',
2676
+ 'default',
2677
+ 'inert',
2678
+ ]);
2679
+ /**
2680
+ * @param {Element} el
2681
+ * @param {string} name
2682
+ * @param {any} value
2683
+ */
2684
+ export function applyDomAttr(el, name, value) {
2685
+ const key = name === 'className' ? 'class' : name;
2686
+ if (BOOLEAN_HTML_ATTRS.has(String(key).toLowerCase())) {
2687
+ if (value === false || value == null || value === '') {
2688
+ el.removeAttribute(key);
2689
+ }
2690
+ else {
2691
+ el.setAttribute(key, value === true ? '' : String(value));
2692
+ }
2693
+ return;
2694
+ }
2695
+ if (value == null || value === false)
2696
+ el.removeAttribute(key);
2697
+ else
2698
+ el.setAttribute(key, value === true ? '' : String(value));
2699
+ }
2700
+ export function stripFns(obj) {
2701
+ /** @type {Record<string, unknown>} */
2702
+ const out = {};
2703
+ for (const [k, v] of Object.entries(obj || {})) {
2704
+ if (typeof v === 'function')
2705
+ continue;
2706
+ out[k] = v;
2707
+ }
2708
+ return out;
2709
+ }
2710
+ /**
2711
+ * Marker range host for Direct eachBlock (insert before end comment).
2712
+ * @param {Comment} start
2713
+ * @param {Comment} end
2714
+ */
2715
+ function eachHostApi(start, end) {
2716
+ return {
2717
+ insert(dom) {
2718
+ if (dom.parentNode)
2719
+ noteDomMove();
2720
+ end.parentNode.insertBefore(dom, end);
2721
+ },
2722
+ childrenBetween() {
2723
+ const out = [];
2724
+ let n = start.nextSibling;
2725
+ while (n && n !== end) {
2726
+ if (n.nodeType === 1)
2727
+ out.push(n);
2728
+ n = n.nextSibling;
2729
+ }
2730
+ return out;
2731
+ },
2732
+ };
2733
+ }
2734
+ /**
2735
+ * Snapshot plain state/prop field values for Island HMR (session).
2736
+ * @param {object} inst
2737
+ * @returns {Record<string, unknown> | null}
2738
+ */
2739
+ export function snapshotInstanceState(inst) {
2740
+ if (!inst || inst.__vmzDestroyed)
2741
+ return null;
2742
+ const Ctor = inst.constructor;
2743
+ const keys = [...(Ctor.__vmzState || []), ...(Ctor.__vmzProps || [])];
2744
+ /** @type {Record<string, unknown>} */
2745
+ const out = {};
2746
+ for (const key of keys) {
2747
+ if (!key || String(key).startsWith('__'))
2748
+ continue;
2749
+ try {
2750
+ out[key] = inst[key];
2751
+ }
2752
+ catch {
2753
+ /* ignore accessors that throw */
2754
+ }
2755
+ }
2756
+ return out;
2757
+ }
2758
+ /**
2759
+ * @param {object} inst
2760
+ * @param {Record<string, unknown> | null | undefined} state
2761
+ */
2762
+ export function applyPreservedState(inst, state) {
2763
+ if (!inst || !state)
2764
+ return;
2765
+ for (const [key, value] of Object.entries(state)) {
2766
+ try {
2767
+ inst[key] = value;
2768
+ }
2769
+ catch {
2770
+ /* ignore */
2771
+ }
2772
+ }
2773
+ }
2774
+ /**
2775
+ * Tear down binders and stop patches. Safe to call more than once.
2776
+ * Field writes after destroy no longer update DOM (values may still change).
2777
+ *: also dispose owned DOM trees (child __vmzInst / region __vmzDispose).
2778
+ * @param {object} inst
2779
+ */
2780
+ export function destroy(inst) {
2781
+ if (!inst || inst.__vmzDestroyed)
2782
+ return;
2783
+ inst.__vmzDestroyed = true;
2784
+ inst.__vmzFlushScheduled = false;
2785
+ // async cancel: abort in-flight tasks before tearing down DOM.
2786
+ __vmzCancelTasks(inst);
2787
+ if (inst.__vmzDomRoot) {
2788
+ disposeDomTree(inst.__vmzDomRoot);
2789
+ inst.__vmzDomRoot = null;
2790
+ }
2791
+ if (inst.__vmzDirtyNotices)
2792
+ inst.__vmzDirtyNotices.length = 0;
2793
+ if (inst.__vmzDirtyTrie)
2794
+ inst.__vmzDirtyTrie = Object.create(null);
2795
+ if (inst.__vmzDirty)
2796
+ inst.__vmzDirty.clear();
2797
+ inst.__vmzBinders = Object.create(null);
2798
+ inst.__vmzBindings = Object.create(null);
2799
+ inst.__vmzDepToBindings = Object.create(null);
2800
+ if (typeof inst.onDestroy === 'function') {
2801
+ try {
2802
+ inst.onDestroy();
2803
+ }
2804
+ catch (err) {
2805
+ console.error('vmz:dom onDestroy', err);
2806
+ }
2807
+ }
2808
+ }
2809
+ /**
2810
+ *: walk a DOM subtree and run lifetime dispose hooks + nested instance destroy.
2811
+ * Does not mark the *calling* parent destroyed; safe from destroy(inst).
2812
+ * @param {Node | null | undefined} root
2813
+ */
2814
+ export function disposeDomTree(root) {
2815
+ if (!root)
2816
+ return;
2817
+ const seen = new Set();
2818
+ const visit = (node) => {
2819
+ if (!node || seen.has(node))
2820
+ return;
2821
+ seen.add(node);
2822
+ if (typeof node.__vmzDispose === 'function') {
2823
+ try {
2824
+ node.__vmzDispose();
2825
+ }
2826
+ catch (err) {
2827
+ console.error('vmz:dom __vmzDispose', err);
2828
+ }
2829
+ node.__vmzDispose = null;
2830
+ }
2831
+ if (node.__vmzInst) {
2832
+ const child = node.__vmzInst;
2833
+ node.__vmzInst = null;
2834
+ destroy(child);
2835
+ }
2836
+ let child = node.firstChild;
2837
+ while (child) {
2838
+ const next = child.nextSibling;
2839
+ visit(child);
2840
+ child = next;
2841
+ }
2842
+ };
2843
+ visit(root);
2844
+ }
2845
+ export function scheduleClient(strategy, fn) {
2846
+ scheduleClientOn(null, strategy, fn);
2847
+ }
2848
+ /** @param {string} strategy */
2849
+ export function isEventEntryStrategy(strategy) {
2850
+ const s = String(strategy || '');
2851
+ return s === 'event' || s.startsWith('event:') || s === 'click';
2852
+ }
2853
+ /** @param {string} strategy */
2854
+ function eventEntryType(strategy) {
2855
+ const s = String(strategy || 'event');
2856
+ if (s.startsWith('event:'))
2857
+ return s.slice(6) || 'click';
2858
+ if (s === 'click')
2859
+ return 'click';
2860
+ return 'click';
2861
+ }
2862
+ export function scheduleClientOn(el, strategy, fn) {
2863
+ const run = () => {
2864
+ Promise.resolve(fn()).catch((err) => console.error('vmz:dom island', err));
2865
+ };
2866
+ if (isEventEntryStrategy(strategy)) {
2867
+ if (!el || typeof el.addEventListener !== 'function') {
2868
+ run();
2869
+ return;
2870
+ }
2871
+ const type = eventEntryType(strategy);
2872
+ const once = () => {
2873
+ el.removeEventListener(type, once);
2874
+ run();
2875
+ };
2876
+ el.addEventListener(type, once);
2877
+ return;
2878
+ }
2879
+ if (strategy === 'idle') {
2880
+ if (typeof requestIdleCallback === 'function') {
2881
+ requestIdleCallback(() => run(), { timeout: 2000 });
2882
+ }
2883
+ else {
2884
+ setTimeout(run, 1);
2885
+ }
2886
+ return;
2887
+ }
2888
+ if (strategy === 'visible' && el && typeof IntersectionObserver === 'function') {
2889
+ const io = new IntersectionObserver((entries) => {
2890
+ if (entries.some((e) => e.isIntersecting)) {
2891
+ io.disconnect();
2892
+ run();
2893
+ }
2894
+ });
2895
+ io.observe(el);
2896
+ return;
2897
+ }
2898
+ run();
2899
+ }
2900
+ /**
2901
+ * AsyncTask cancel protocol (first slice): keyed generation + AbortSignal.
2902
+ * Superseded runs and `destroy(inst)` abort prior work; stale results must not apply.
2903
+ * @param {object} inst
2904
+ * @param {string} key
2905
+ * @param {(signal: AbortSignal, meta: { generation: number }) => any | Promise<any>} fn
2906
+ * @returns {Promise<any>}
2907
+ */
2908
+ export function __vmzRunTask(inst, key, fn) {
2909
+ if (!inst)
2910
+ throw new Error('vmz:dom __vmzRunTask requires inst');
2911
+ const k = String(key || 'default');
2912
+ if (!inst.__vmzTasks)
2913
+ inst.__vmzTasks = Object.create(null);
2914
+ const prev = inst.__vmzTasks[k];
2915
+ if (prev) {
2916
+ prev.generation += 1;
2917
+ try {
2918
+ prev.controller.abort();
2919
+ }
2920
+ catch {
2921
+ /* ignore */
2922
+ }
2923
+ prev.status = 'cancelled';
2924
+ }
2925
+ const controller = typeof AbortController !== 'undefined'
2926
+ ? new AbortController()
2927
+ : {
2928
+ signal: { aborted: false },
2929
+ abort() {
2930
+ this.signal.aborted = true;
2931
+ },
2932
+ };
2933
+ const generation = (prev?.generation || 0) + 1;
2934
+ /** @type {{ generation: number, controller: any, status: string, result?: any, error?: any, promise?: Promise<any> }} */
2935
+ const entry = {
2936
+ generation,
2937
+ controller,
2938
+ status: 'pending',
2939
+ };
2940
+ inst.__vmzTasks[k] = entry;
2941
+ // Invoke synchronously so event handlers can call preventDefault before
2942
+ // the browser continues the default action (form submit → native navigation).
2943
+ // Async work still continues via the returned Promise.
2944
+ let syncResult;
2945
+ let syncErr;
2946
+ let threw = false;
2947
+ try {
2948
+ syncResult = fn(controller.signal, { generation });
2949
+ }
2950
+ catch (err) {
2951
+ threw = true;
2952
+ syncErr = err;
2953
+ }
2954
+ const settleOk = (result) => {
2955
+ if (inst.__vmzDestroyed || controller.signal.aborted || inst.__vmzTasks[k] !== entry) {
2956
+ entry.status = 'cancelled';
2957
+ return undefined;
2958
+ }
2959
+ entry.status = 'success';
2960
+ entry.result = result;
2961
+ return result;
2962
+ };
2963
+ const settleErr = (err) => {
2964
+ if (inst.__vmzDestroyed || controller.signal.aborted || inst.__vmzTasks[k] !== entry) {
2965
+ entry.status = 'cancelled';
2966
+ return undefined;
2967
+ }
2968
+ entry.status = 'error';
2969
+ entry.error = err;
2970
+ throw err;
2971
+ };
2972
+ if (threw) {
2973
+ const promise = Promise.resolve().then(() => settleErr(syncErr));
2974
+ entry.promise = promise;
2975
+ return promise;
2976
+ }
2977
+ const promise = Promise.resolve(syncResult).then(settleOk, settleErr);
2978
+ entry.promise = promise;
2979
+ return promise;
2980
+ }
2981
+ /** Abort all keyed tasks on an instance (also called from destroy). */
2982
+ export function __vmzCancelTasks(inst) {
2983
+ const tasks = inst?.__vmzTasks;
2984
+ if (!tasks)
2985
+ return;
2986
+ for (const key of Object.keys(tasks)) {
2987
+ const t = tasks[key];
2988
+ t.generation += 1;
2989
+ try {
2990
+ t.controller.abort();
2991
+ }
2992
+ catch {
2993
+ /* ignore */
2994
+ }
2995
+ t.status = 'cancelled';
2996
+ }
2997
+ }
2998
+ /** @returns {'pending'|'success'|'error'|'cancelled'|null} */
2999
+ export function __vmzTaskStatus(inst, key) {
3000
+ const t = inst?.__vmzTasks?.[String(key || 'default')];
3001
+ return t ? t.status : null;
3002
+ }
3003
+ export function createInstance(Component, props = {}) {
3004
+ if (precision.enabled)
3005
+ precision.componentExecs++;
3006
+ const inst = new Component(props || {});
3007
+ if (typeof inst.__vmzApplyProps === 'function' && !Component.__vmzCtorAppliesProps) {
3008
+ inst.__vmzApplyProps(props || {});
3009
+ }
3010
+ inst.__vmzBinders = Object.create(null);
3011
+ inst.__vmzBindings = Object.create(null);
3012
+ inst.__vmzDepToBindings = Object.create(null);
3013
+ makeReactive(inst, Component.__vmzState || []);
3014
+ makeReactive(inst, Component.__vmzProps || []);
3015
+ // WriteBarrier: install Component helpers once (no import needed in emitted code).
3016
+ if (!Component.__vmzWBInstalled) {
3017
+ Component.__vmzWBInstalled = true;
3018
+ Component.__vmzWritePath = __vmzWritePath;
3019
+ Component.__vmzWritePathItem = __vmzWritePathItem;
3020
+ Component.__vmzWritePathCompound = __vmzWritePathCompound;
3021
+ Component.__vmzWritePathCompoundItem = __vmzWritePathCompoundItem;
3022
+ Component.__vmzWritePathLogical = __vmzWritePathLogical;
3023
+ Component.__vmzReadPath = __vmzReadPath;
3024
+ Component.__vmzArrayMutate = __vmzArrayMutate;
3025
+ Component.__vmzArrayItemCompoundStride = __vmzArrayItemCompoundStride;
3026
+ Component.__vmzListTranspose = __vmzListTranspose;
3027
+ Component.__vmzAllowShared = __vmzAllowShared;
3028
+ Component.__vmzTakeShared = __vmzTakeShared;
3029
+ }
3030
+ return inst;
3031
+ }
3032
+ /** Shared plain-object owners under WriteBarrier (no Proxy). */
3033
+ const wbSharedOwners = new WeakMap();
3034
+ /** Objects explicitly marked OK to share across component instances (13 ). */
3035
+ const wbAllowShared = new WeakSet();
3036
+ /** @type {Array<{ kind: string, message: string }>} */
3037
+ const wbCrossComponentDiags = [];
3038
+ /**
3039
+ * Mark a plain object as intentionally shared across ownership boundaries.
3040
+ * @param {any} value
3041
+ */
3042
+ export function __vmzAllowShared(value) {
3043
+ if (value != null && typeof value === 'object')
3044
+ wbAllowShared.add(value);
3045
+ return value;
3046
+ }
3047
+ /**
3048
+ * Take exclusive ownership intent: clear multi-owner registry for this object.
3049
+ * Subsequent field assigns re-register from the assigning instance only.
3050
+ * @param {any} value
3051
+ */
3052
+ export function __vmzTakeShared(value) {
3053
+ if (value != null && typeof value === 'object') {
3054
+ wbSharedOwners.delete(value);
3055
+ wbAllowShared.delete(value);
3056
+ }
3057
+ return value;
3058
+ }
3059
+ /**
3060
+ * @returns {Array<{ kind: string, message: string }>}
3061
+ */
3062
+ export function __vmzSharedCrossComponentDiagnostics() {
3063
+ return wbCrossComponentDiags.slice();
3064
+ }
3065
+ export function __vmzSharedCrossComponentDiagnosticsReset() {
3066
+ wbCrossComponentDiags.length = 0;
3067
+ }
3068
+ /**
3069
+ * @param {any} value
3070
+ * @param {(segs: string[] | null) => void} report
3071
+ * @param {string[]} baseSegs
3072
+ * @param {any} [inst]
3073
+ */
3074
+ function registerWbOwner(value, report, baseSegs = [], inst = null) {
3075
+ if (value == null || typeof value !== 'object')
3076
+ return;
3077
+ let entry = wbSharedOwners.get(value);
3078
+ if (!entry) {
3079
+ entry = { owners: [] };
3080
+ wbSharedOwners.set(value, entry);
3081
+ }
3082
+ if (entry.owners.some((o) => o.report === report && sameSegs(o.baseSegs, baseSegs))) {
3083
+ return;
3084
+ }
3085
+ entry.owners.push({ report, baseSegs: baseSegs.slice(), inst });
3086
+ // Cross-component share without explicit allow → diagnose (13 ).
3087
+ if (!wbAllowShared.has(value) && inst) {
3088
+ const other = entry.owners.find((o) => o.inst && o.inst !== inst);
3089
+ if (other) {
3090
+ if (!wbCrossComponentDiags.some((d) => d.message === msg)) {
3091
+ wbCrossComponentDiags.push({ kind: 'shared_cross_component', message: msg });
3092
+ }
3093
+ }
3094
+ }
3095
+ }
3096
+ /**
3097
+ * Notify all registered owners of a shared plain object after a barrier write.
3098
+ * @param {any} rootObj field-root value that was written under
3099
+ * @param {string[] | null} localSegs path under that object (null = replace)
3100
+ * @returns {boolean} true when at least one owner was notified
3101
+ */
3102
+ function notifyWbShared(rootObj, localSegs) {
3103
+ const entry = rootObj && typeof rootObj === 'object' ? wbSharedOwners.get(rootObj) : null;
3104
+ if (!entry || !entry.owners.length)
3105
+ return false;
3106
+ for (const o of entry.owners) {
3107
+ if (localSegs == null) {
3108
+ o.report(o.baseSegs.length ? o.baseSegs.slice() : null);
3109
+ }
3110
+ else {
3111
+ o.report([...o.baseSegs, ...localSegs]);
3112
+ }
3113
+ }
3114
+ return true;
3115
+ }
3116
+ /**
3117
+ * Read a nested path under a field root (for compound / update expansion).
3118
+ * @param {any} inst
3119
+ * @param {string} root
3120
+ * @param {string[]} segs
3121
+ */
3122
+ export function __vmzReadPath(inst, root, segs) {
3123
+ if (!inst || !root)
3124
+ return undefined;
3125
+ let obj = inst[root];
3126
+ if (!Array.isArray(segs) || segs.length === 0)
3127
+ return obj;
3128
+ for (let i = 0; i < segs.length; i++) {
3129
+ if (obj == null || typeof obj !== 'object')
3130
+ return undefined;
3131
+ obj = obj[segs[i]];
3132
+ }
3133
+ return obj;
3134
+ }
3135
+ /**
3136
+ * Short-circuit logical path assign (`||=` / `&&=` / `??=`).
3137
+ * @param {any} inst
3138
+ * @param {string} root
3139
+ * @param {string[]} segs
3140
+ * @param {'||'|'&&'|'??'} kind
3141
+ * @param {any} rhs
3142
+ */
3143
+ export function __vmzWritePathLogical(inst, root, segs, kind, rhs) {
3144
+ const cur = __vmzReadPath(inst, root, segs);
3145
+ if (kind === '||') {
3146
+ if (cur)
3147
+ return cur;
3148
+ }
3149
+ else if (kind === '&&') {
3150
+ if (!cur)
3151
+ return cur;
3152
+ }
3153
+ else if (kind === '??') {
3154
+ if (cur != null)
3155
+ return cur;
3156
+ }
3157
+ else {
3158
+ return cur;
3159
+ }
3160
+ return __vmzWritePath(inst, root, segs, rhs);
3161
+ }
3162
+ /**
3163
+ * Mutates a plain owned object/array and schedules the same path notice Proxy would.
3164
+ *
3165
+ * Root-array index assigns (`tags[0] = x`) notify as field replace (structural),
3166
+ * matching the transitional Proxy wrapArray behavior.
3167
+ * Shared multi-owner: writing through one field notifies all owners of the same raw object.
3168
+ *
3169
+ * @param {any} inst
3170
+ * @param {string} root field root
3171
+ * @param {string[]} segs path under root (non-empty); dynamic indices already String(...)'d
3172
+ * @param {any} value
3173
+ */
3174
+ /**
3175
+ * Apply a binary compound/update op without a separate ReadPath call.
3176
+ * @param {string} op
3177
+ * @param {any} cur
3178
+ * @param {any} rhs
3179
+ */
3180
+ function applyCompoundOp(op, cur, rhs) {
3181
+ switch (op) {
3182
+ case '+':
3183
+ return cur + rhs;
3184
+ case '-':
3185
+ return cur - rhs;
3186
+ case '*':
3187
+ return cur * rhs;
3188
+ case '/':
3189
+ return cur / rhs;
3190
+ case '%':
3191
+ return cur % rhs;
3192
+ case '**':
3193
+ return cur ** rhs;
3194
+ case '<<':
3195
+ return cur << rhs;
3196
+ case '>>':
3197
+ return cur >> rhs;
3198
+ case '>>>':
3199
+ return cur >>> rhs;
3200
+ case '|':
3201
+ return cur | rhs;
3202
+ case '^':
3203
+ return cur ^ rhs;
3204
+ case '&':
3205
+ return cur & rhs;
3206
+ default:
3207
+ return cur;
3208
+ }
3209
+ }
3210
+ /**
3211
+ * Schedule leaf refresh after an array-item field mutate (event leaf-batch or trie).
3212
+ * @param {object} inst
3213
+ * @param {string} root
3214
+ * @param {string|number} idx
3215
+ * @param {string} leaf
3216
+ */
3217
+ function notifyArrayItemLeaf(inst, root, idx, leaf) {
3218
+ if (typeof inst.__vmzDrainLeafDirty === 'function' && ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync)) {
3219
+ const i = +idx;
3220
+ const ld = inst.__vmzLeafDirty;
3221
+ if (!ld) {
3222
+ inst.__vmzLeafDirty = { root, field: leaf, idxs: [i] };
3223
+ }
3224
+ else if (ld.root === root && ld.field === leaf) {
3225
+ ld.idxs.push(i);
3226
+ }
3227
+ else {
3228
+ promoteLeafDirtyToTrie(inst);
3229
+ scheduleRefresh(inst, { type: 'path', root, segs: [String(idx), leaf] });
3230
+ return;
3231
+ }
3232
+ inst.__vmzFlushScheduled = true;
3233
+ return;
3234
+ }
3235
+ scheduleRefresh(inst, { type: 'path', root, segs: [String(idx), leaf] });
3236
+ }
3237
+ export function __vmzWritePath(inst, root, segs, value) {
3238
+ if (!inst || inst.__vmzDestroyed)
3239
+ return value;
3240
+ if (!root || !Array.isArray(segs) || segs.length === 0)
3241
+ return value;
3242
+ // Hot path: array item field write (`rows[i].label`) — no map/slice, no shared-owner walk.
3243
+ if (segs.length === 2) {
3244
+ return __vmzWritePathItem(inst, root, segs[0], segs[1], value);
3245
+ }
3246
+ const normSegs = segs.map((s) => String(s));
3247
+ let obj = inst[root];
3248
+ if (obj == null || typeof obj !== 'object')
3249
+ return value;
3250
+ for (let i = 0; i < normSegs.length - 1; i++) {
3251
+ obj = obj[normSegs[i]];
3252
+ if (obj == null || typeof obj !== 'object')
3253
+ return value;
3254
+ }
3255
+ const leaf = normSegs[normSegs.length - 1];
3256
+ if (Object.is(obj[leaf], value))
3257
+ return value;
3258
+ obj[leaf] = value;
3259
+ // Register newly assigned nested objects under this field for future shared writes.
3260
+ if (value != null && typeof value === 'object') {
3261
+ const report = (local) => {
3262
+ if (!local || local.length === 0) {
3263
+ scheduleRefresh(inst, { type: 'replace', root });
3264
+ }
3265
+ else {
3266
+ scheduleRefresh(inst, { type: 'path', root, segs: local });
3267
+ }
3268
+ };
3269
+ registerWbOwner(value, report, normSegs.slice(), inst);
3270
+ }
3271
+ const rootObj = inst[root];
3272
+ const rootArr = rootObj;
3273
+ const isRootIndex = normSegs.length === 1 && Array.isArray(rootArr) && leaf !== 'length' && String(Number(leaf)) === leaf;
3274
+ if (isRootIndex) {
3275
+ if (!notifyWbShared(rootObj, null)) {
3276
+ scheduleRefresh(inst, { type: 'replace', root });
3277
+ }
3278
+ }
3279
+ else if (!notifyWbShared(rootObj, normSegs)) {
3280
+ scheduleRefresh(inst, { type: 'path', root, segs: normSegs });
3281
+ }
3282
+ return value;
3283
+ }
3284
+ /**
3285
+ * Array-item leaf write without segs array alloc (`rows[i].label = v`).
3286
+ * @param {any} inst
3287
+ * @param {string} root
3288
+ * @param {string|number} idx
3289
+ * @param {string} leaf
3290
+ * @param {any} value
3291
+ */
3292
+ export function __vmzWritePathItem(inst, root, idx, leaf, value) {
3293
+ if (!inst || inst.__vmzDestroyed)
3294
+ return value;
3295
+ if (!root || leaf == null)
3296
+ return value;
3297
+ const arr = inst[root];
3298
+ if (!Array.isArray(arr))
3299
+ return value;
3300
+ const item = arr[idx];
3301
+ if (item == null || typeof item !== 'object')
3302
+ return value;
3303
+ if (Object.is(item[leaf], value))
3304
+ return value;
3305
+ item[leaf] = value;
3306
+ if (tryInlineLeafApply(inst, root, idx, leaf, item))
3307
+ return value;
3308
+ notifyArrayItemLeaf(inst, root, idx, leaf);
3309
+ return value;
3310
+ }
3311
+ /**
3312
+ * In-place two-index swap on an owned list field (WriteBarrier Slice 5).
3313
+ * Prefers eachBlock O(1) DOM transpose hook; otherwise schedules a list replace.
3314
+ * @param {any} inst
3315
+ * @param {string} root
3316
+ * @param {number|string} ia
3317
+ * @param {number|string} ib
3318
+ */
3319
+ export function __vmzListTranspose(inst, root, ia, ib) {
3320
+ if (!inst || inst.__vmzDestroyed || !root)
3321
+ return;
3322
+ const arr = inst[root];
3323
+ if (!Array.isArray(arr))
3324
+ return;
3325
+ const a = +ia;
3326
+ const b = +ib;
3327
+ if (a === b || a < 0 || b < 0 || a >= arr.length || b >= arr.length)
3328
+ return;
3329
+ const tmp = arr[a];
3330
+ arr[a] = arr[b];
3331
+ arr[b] = tmp;
3332
+ const hook = inst.__vmzEachTranspose && inst.__vmzEachTranspose[root];
3333
+ if (typeof hook === 'function' && hook(a, b) === true)
3334
+ return;
3335
+ scheduleRefresh(inst, { type: 'replace', root });
3336
+ }
3337
+ /**
3338
+ * Compound leaf write (`rows[i].label += x`) — one item touch, no separate ReadPath.
3339
+ * @param {any} inst
3340
+ * @param {string} root
3341
+ * @param {string[]} segs
3342
+ * @param {string} op
3343
+ * @param {any} rhs
3344
+ */
3345
+ export function __vmzWritePathCompound(inst, root, segs, op, rhs) {
3346
+ if (!inst || inst.__vmzDestroyed)
3347
+ return undefined;
3348
+ if (!root || !Array.isArray(segs) || segs.length === 0)
3349
+ return undefined;
3350
+ if (segs.length === 2) {
3351
+ return __vmzWritePathCompoundItem(inst, root, segs[0], segs[1], op, rhs);
3352
+ }
3353
+ const cur = __vmzReadPath(inst, root, segs);
3354
+ const value = applyCompoundOp(op, cur, rhs);
3355
+ return __vmzWritePath(inst, root, segs, value);
3356
+ }
3357
+ /**
3358
+ * Array-item compound without segs array alloc (`rows[i].label += x`).
3359
+ * @param {any} inst
3360
+ * @param {string} root
3361
+ * @param {string|number} idx
3362
+ * @param {string} leaf
3363
+ * @param {string} op
3364
+ * @param {any} rhs
3365
+ */
3366
+ export function __vmzWritePathCompoundItem(inst, root, idx, leaf, op, rhs) {
3367
+ if (!inst || inst.__vmzDestroyed)
3368
+ return undefined;
3369
+ if (!root || leaf == null)
3370
+ return undefined;
3371
+ const arr = inst[root];
3372
+ if (!Array.isArray(arr))
3373
+ return undefined;
3374
+ const item = arr[idx];
3375
+ if (item == null || typeof item !== 'object')
3376
+ return undefined;
3377
+ const cur = item[leaf];
3378
+ const value = applyCompoundOp(op, cur, rhs);
3379
+ if (Object.is(cur, value))
3380
+ return value;
3381
+ item[leaf] = value;
3382
+ if (tryInlineLeafApply(inst, root, idx, leaf, item))
3383
+ return value;
3384
+ notifyArrayItemLeaf(inst, root, idx, leaf);
3385
+ return value;
3386
+ }
3387
+ /**
3388
+ * Stride compound over an owned array (`for (i=start; i<n; i+=step) arr[i].leaf op= rhs`).
3389
+ * Mutates + applies DOM in one pass when eachBlock leaf hook is installed (update-every-Nth).
3390
+ * @param {any} inst
3391
+ * @param {string} root
3392
+ * @param {string} leaf
3393
+ * @param {string} op
3394
+ * @param {any} rhs
3395
+ * @param {number|string} start
3396
+ * @param {number|string} step
3397
+ */
3398
+ export function __vmzArrayItemCompoundStride(inst, root, leaf, op, rhs, start, step) {
3399
+ if (!inst || inst.__vmzDestroyed || !root || leaf == null)
3400
+ return;
3401
+ // Prefer eachBlock-owned loop (hoisted applyByField, no per-index hook lookup).
3402
+ const owned = inst.__vmzEachCompoundStride && inst.__vmzEachCompoundStride[root];
3403
+ if (typeof owned === 'function' && owned(leaf, op, rhs, start, step) === true)
3404
+ return;
3405
+ const arr = inst[root];
3406
+ if (!Array.isArray(arr))
3407
+ return;
3408
+ const s = +start || 0;
3409
+ const st = +step || 0;
3410
+ if (st <= 0)
3411
+ return;
3412
+ const n = arr.length;
3413
+ const canInline = typeof inst.__vmzEachApplyLeaf === 'object' &&
3414
+ typeof inst.__vmzEachApplyLeaf[root] === 'function' &&
3415
+ ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync);
3416
+ const applyLeaf = canInline ? inst.__vmzEachApplyLeaf[root] : null;
3417
+ // Hot path: string `+=` with inline DOM — no switch / Object.is / idxs.
3418
+ if (op === '+' && applyLeaf) {
3419
+ for (let i = s; i < n; i += st) {
3420
+ const item = arr[i];
3421
+ if (item == null || typeof item !== 'object')
3422
+ continue;
3423
+ item[leaf] = item[leaf] + rhs;
3424
+ applyLeaf(i, leaf, item);
3425
+ }
3426
+ return;
3427
+ }
3428
+ /** @type {number[]} */
3429
+ const idxs = [];
3430
+ for (let i = s; i < n; i += st) {
3431
+ const item = arr[i];
3432
+ if (item == null || typeof item !== 'object')
3433
+ continue;
3434
+ const cur = item[leaf];
3435
+ const value = applyCompoundOp(op, cur, rhs);
3436
+ if (Object.is(cur, value))
3437
+ continue;
3438
+ item[leaf] = value;
3439
+ if (applyLeaf && applyLeaf(i, leaf, item) === true)
3440
+ continue;
3441
+ idxs.push(i);
3442
+ }
3443
+ if (!idxs.length)
3444
+ return;
3445
+ if (typeof inst.__vmzDrainLeafDirty === 'function' && ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync)) {
3446
+ inst.__vmzLeafDirty = { root, field: leaf, idxs };
3447
+ inst.__vmzFlushScheduled = true;
3448
+ return;
3449
+ }
3450
+ for (let k = 0; k < idxs.length; k++) {
3451
+ notifyArrayItemLeaf(inst, root, idxs[k], leaf);
3452
+ }
3453
+ }
3454
+ /**
3455
+ * During event flush, apply a leaf via eachBlock hook (vanillajs-style mutate+DOM).
3456
+ * @param {any} inst
3457
+ * @param {string} root
3458
+ * @param {string|number} idx
3459
+ * @param {string} leaf
3460
+ * @param {any} item
3461
+ */
3462
+ function tryInlineLeafApply(inst, root, idx, leaf, item) {
3463
+ if (!((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync))
3464
+ return false;
3465
+ const hook = inst.__vmzEachApplyLeaf && inst.__vmzEachApplyLeaf[root];
3466
+ if (typeof hook !== 'function')
3467
+ return false;
3468
+ return hook(+idx, leaf, item) === true;
3469
+ }
3470
+ /**
3471
+ * Compiler-inserted array mutator barrier (push/pop/splice/…).
3472
+ * Applies the mutator on the plain array and schedules a structural notice
3473
+ * at `root` + `baseSegs` (empty baseSegs → field replace).
3474
+ *
3475
+ * @param {any} inst
3476
+ * @param {string} root
3477
+ * @param {string[]} baseSegs
3478
+ * @param {string} method
3479
+ * @param {any[]} args
3480
+ */
3481
+ export function __vmzArrayMutate(inst, root, baseSegs, method, args) {
3482
+ if (!inst || inst.__vmzDestroyed)
3483
+ return undefined;
3484
+ if (!root || typeof method !== 'string')
3485
+ return undefined;
3486
+ const segs = Array.isArray(baseSegs) ? baseSegs.map((s) => String(s)) : [];
3487
+ let arr = inst[root];
3488
+ if (arr == null || typeof arr !== 'object')
3489
+ return undefined;
3490
+ for (let i = 0; i < segs.length; i++) {
3491
+ arr = arr[segs[i]];
3492
+ if (arr == null || typeof arr !== 'object')
3493
+ return undefined;
3494
+ }
3495
+ if (!Array.isArray(arr) || typeof arr[method] !== 'function')
3496
+ return undefined;
3497
+ const list = Array.isArray(args) ? args : [];
3498
+ const ret = arr[method](...list);
3499
+ const rootObj = inst[root];
3500
+ if (segs.length === 0) {
3501
+ if (!notifyWbShared(rootObj, null)) {
3502
+ scheduleRefresh(inst, { type: 'replace', root });
3503
+ }
3504
+ }
3505
+ else if (!notifyWbShared(rootObj, segs)) {
3506
+ scheduleRefresh(inst, { type: 'path', root, segs: segs.slice() });
3507
+ }
3508
+ return ret;
3509
+ }
3510
+ function makeReactive(inst, stateKeys) {
3511
+ const barrier = !!inst.constructor.__vmzWriteBarrier;
3512
+ for (const key of stateKeys) {
3513
+ if (!key || key.startsWith('#'))
3514
+ continue;
3515
+ const desc = Object.getOwnPropertyDescriptor(inst, key);
3516
+ if (desc && desc.set && desc.get && !desc.writable)
3517
+ continue;
3518
+ /** @param {string[] | null} segs null/empty → replace field */
3519
+ const report = (segs) => {
3520
+ if (!segs || segs.length === 0) {
3521
+ scheduleRefresh(inst, { type: 'replace', root: key });
3522
+ }
3523
+ else {
3524
+ scheduleRefresh(inst, { type: 'path', root: key, segs });
3525
+ }
3526
+ };
3527
+ // WriteBarrier components keep plain objects — nested writes go through __vmzWritePath.
3528
+ let value = barrier ? inst[key] : wrapReactive(inst[key], report, []);
3529
+ if (barrier)
3530
+ registerWbOwner(value, report, [], inst);
3531
+ Object.defineProperty(inst, key, {
3532
+ configurable: true,
3533
+ enumerable: true,
3534
+ get() {
3535
+ return value;
3536
+ },
3537
+ set(next) {
3538
+ const wrapped = barrier ? next : wrapReactive(next, report, []);
3539
+ if (Object.is(value, wrapped))
3540
+ return;
3541
+ value = wrapped;
3542
+ if (barrier)
3543
+ registerWbOwner(value, report, [], inst);
3544
+ report(null);
3545
+ },
3546
+ });
3547
+ }
3548
+ }
3549
+ /** Targets already wrapped: raw|proxy|barrier → { proxy, owners[], kind }. */
3550
+ const reactiveProxies = new WeakMap();
3551
+ /** Plain objects using defineProperty write barriers (not Proxy). */
3552
+ const writeBarrierOwned = new WeakSet();
3553
+ /**
3554
+ * WriteBarrier: true when value is an owned plain object with path barriers (no Proxy).
3555
+ * @param {any} value
3556
+ */
3557
+ export function __vmzIsWriteBarrierOwned(value) {
3558
+ return writeBarrierOwned.has(value);
3559
+ }
3560
+ /**
3561
+ * True when value is the Proxy wrapper from array (or residual) reactive wrap.
3562
+ * @param {any} value
3563
+ */
3564
+ export function __vmzIsReactiveProxy(value) {
3565
+ const e = reactiveProxies.get(value);
3566
+ return !!(e && e.kind === 'proxy' && e.proxy === value);
3567
+ }
3568
+ const ARRAY_MUTATORS = new Set(['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill', 'copyWithin']);
3569
+ /**
3570
+ * @typedef {{ report: (segs: string[] | null) => void, baseSegs: string[] }} ReactiveOwner
3571
+ * @typedef {{ proxy: object, owners: ReactiveOwner[], kind: 'barrier'|'proxy' }} ReactiveEntry
3572
+ */
3573
+ function sameSegs(a, b) {
3574
+ if (a.length !== b.length)
3575
+ return false;
3576
+ for (let i = 0; i < a.length; i++) {
3577
+ if (a[i] !== b[i])
3578
+ return false;
3579
+ }
3580
+ return true;
3581
+ }
3582
+ /**
3583
+ * @param {ReactiveEntry} entry
3584
+ * @param {(segs: string[] | null) => void} report
3585
+ * @param {string[]} baseSegs
3586
+ */
3587
+ function addOwner(entry, report, baseSegs) {
3588
+ if (entry.owners.some((o) => o.report === report && sameSegs(o.baseSegs, baseSegs))) {
3589
+ return;
3590
+ }
3591
+ entry.owners.push({
3592
+ report,
3593
+ baseSegs: baseSegs.slice(),
3594
+ });
3595
+ }
3596
+ /**
3597
+ * @param {ReactiveOwner[]} owners
3598
+ * @param {string[] | null} localSegs null = structural replace of this node
3599
+ */
3600
+ function notifyOwners(owners, localSegs) {
3601
+ for (const o of owners) {
3602
+ if (localSegs == null) {
3603
+ o.report(o.baseSegs.length ? o.baseSegs.slice() : null);
3604
+ }
3605
+ else {
3606
+ o.report([...o.baseSegs, ...localSegs]);
3607
+ }
3608
+ }
3609
+ }
3610
+ /**
3611
+ * Field-owned write traps for plain objects / arrays on state fields.
3612
+ * Plain objects: WriteBarrier via defineProperty (no Proxy).
3613
+ * Arrays: transitional Proxy tracks list identity/mutators only; elements stay plain
3614
+ * (no per-item wrap on large assign — nested notifies via `__vmzWritePath`).
3615
+ * Shared raw objects notify **all** current owners.
3616
+ *
3617
+ * @param {any} value
3618
+ * @param {(segs: string[] | null) => void} report
3619
+ * @param {string[]} pathSegs path under the field root to this value
3620
+ */
3621
+ function wrapReactive(value, report, pathSegs = []) {
3622
+ if (value == null || typeof value !== 'object')
3623
+ return value;
3624
+ const existing = reactiveProxies.get(value);
3625
+ if (existing) {
3626
+ addOwner(existing, report, pathSegs);
3627
+ return existing.proxy;
3628
+ }
3629
+ if (Array.isArray(value))
3630
+ return wrapArray(value, report, pathSegs);
3631
+ if (isPlainObject(value))
3632
+ return wrapOwnedObject(value, report, pathSegs);
3633
+ return value;
3634
+ }
3635
+ function isPlainObject(value) {
3636
+ const proto = Object.getPrototypeOf(value);
3637
+ return proto === Object.prototype || proto === null;
3638
+ }
3639
+ /**
3640
+ * Path-level write barrier for owned plain objects (no Proxy).
3641
+ */
3642
+ function wrapOwnedObject(obj, report, pathSegs) {
3643
+ const existing = reactiveProxies.get(obj);
3644
+ if (existing) {
3645
+ addOwner(existing, report, pathSegs);
3646
+ return existing.proxy;
3647
+ }
3648
+ /** @type {ReactiveEntry} */
3649
+ const entry = {
3650
+ proxy: obj,
3651
+ owners: [],
3652
+ kind: 'barrier',
3653
+ };
3654
+ addOwner(entry, report, pathSegs);
3655
+ writeBarrierOwned.add(obj);
3656
+ reactiveProxies.set(obj, entry);
3657
+ for (const prop of Object.keys(obj)) {
3658
+ installOwnedProp(obj, prop, entry);
3659
+ }
3660
+ return obj;
3661
+ }
3662
+ /**
3663
+ * @param {object} obj
3664
+ * @param {string} prop
3665
+ * @param {ReactiveEntry} entry
3666
+ */
3667
+ function installOwnedProp(obj, prop, entry) {
3668
+ const desc = Object.getOwnPropertyDescriptor(obj, prop);
3669
+ if (!desc || !desc.configurable)
3670
+ return;
3671
+ if (desc.get || desc.set)
3672
+ return;
3673
+ let current = obj[prop];
3674
+ for (const o of entry.owners) {
3675
+ current = wrapReactive(current, o.report, [...o.baseSegs, prop]);
3676
+ }
3677
+ Object.defineProperty(obj, prop, {
3678
+ configurable: true,
3679
+ enumerable: desc.enumerable !== false,
3680
+ get() {
3681
+ return current;
3682
+ },
3683
+ set(next) {
3684
+ const local = [prop];
3685
+ let wrapped = next;
3686
+ for (const o of entry.owners) {
3687
+ wrapped = wrapReactive(next, o.report, [...o.baseSegs, ...local]);
3688
+ }
3689
+ if (Object.is(current, wrapped))
3690
+ return;
3691
+ current = wrapped;
3692
+ notifyOwners(entry.owners, local);
3693
+ },
3694
+ });
3695
+ }
3696
+ /**
3697
+ * Transitional array Proxy: track list identity / mutators only.
3698
+ * Elements stay plain — no per-item defineProperty on `this.rows = largeArray`
3699
+ * (design: WriteBarrier / list replace must not wrap 1k items). Nested field
3700
+ * notifies go through `__vmzWritePath` or whole-array replace.
3701
+ */
3702
+ function wrapArray(arr, report, pathSegs) {
3703
+ const existing = reactiveProxies.get(arr);
3704
+ if (existing) {
3705
+ addOwner(existing, report, pathSegs);
3706
+ return existing.proxy;
3707
+ }
3708
+ /** @type {ReactiveEntry} */
3709
+ const entry = {
3710
+ proxy: null,
3711
+ owners: [],
3712
+ kind: 'proxy',
3713
+ };
3714
+ addOwner(entry, report, pathSegs);
3715
+ const isArrayIndex = (prop) => typeof prop === 'string' && prop !== 'length' && String(Number(prop)) === prop;
3716
+ const proxy = new Proxy(arr, {
3717
+ get(target, prop, receiver) {
3718
+ if (typeof prop === 'string' && ARRAY_MUTATORS.has(prop)) {
3719
+ const fn = target[prop];
3720
+ return (...args) => {
3721
+ const ret = fn.apply(target, args);
3722
+ notifyOwners(entry.owners, null);
3723
+ return ret;
3724
+ };
3725
+ }
3726
+ // Indices / length / methods: return as-is (plain elements).
3727
+ return Reflect.get(target, prop, receiver);
3728
+ },
3729
+ set(target, prop, next, receiver) {
3730
+ const prev = target[prop];
3731
+ if (Object.is(prev, next))
3732
+ return true;
3733
+ const ok = Reflect.set(target, prop, next, receiver);
3734
+ if (ok) {
3735
+ if (prop === 'length' || isArrayIndex(prop))
3736
+ notifyOwners(entry.owners, null);
3737
+ else if (typeof prop === 'string')
3738
+ notifyOwners(entry.owners, [prop]);
3739
+ else
3740
+ notifyOwners(entry.owners, null);
3741
+ }
3742
+ return ok;
3743
+ },
3744
+ deleteProperty(target, prop) {
3745
+ if (!(prop in target))
3746
+ return true;
3747
+ const ok = Reflect.deleteProperty(target, prop);
3748
+ if (ok) {
3749
+ notifyOwners(entry.owners, typeof prop === 'string' ? [prop] : null);
3750
+ }
3751
+ return ok;
3752
+ },
3753
+ });
3754
+ entry.proxy = proxy;
3755
+ reactiveProxies.set(arr, entry);
3756
+ reactiveProxies.set(proxy, entry);
3757
+ return proxy;
3758
+ }
3759
+ /**
3760
+ * Coalesce field/path patches in the same turn via a dirty path trie.
3761
+ * Still precise deps — never a full-tree re-render. Flush runs as a microtask by default;
3762
+ * DOM event handlers drain synchronously via beginEventFlush/endEventFlush when methodRw
3763
+ * proves the handler is sync (`async: false`, `opaque: false`).
3764
+ * Call `await flushPending(inst)` (may return void or a Promise) for tests / immediate UI.
3765
+ *
3766
+ *
3767
+ * @param {object} inst
3768
+ * @param {{ type: 'replace', root: string } | { type: 'path', root: string, segs: string[] } | string} notice
3769
+ * string form is transitional field-root alias for replace.
3770
+ */
3771
+ function scheduleRefresh(inst, notice) {
3772
+ if (!inst || inst.__vmzDestroyed || inst.__vmzQuiet)
3773
+ return;
3774
+ const n = typeof notice === 'string' ? { type: 'replace', root: notice } : notice;
3775
+ if (!n || !n.root)
3776
+ return;
3777
+ if (precision.enabled) {
3778
+ precision.writes++;
3779
+ bumpMap(precision.writesByRoot, n.root);
3780
+ }
3781
+ pushTrace('write', 'field', n.root, n.root);
3782
+ if (!inst.__vmzDirtyTrie)
3783
+ inst.__vmzDirtyTrie = Object.create(null);
3784
+ insertDirtyNotice(inst.__vmzDirtyTrie, n);
3785
+ // Transitional: keep notice list for flush loop emptiness check / compat.
3786
+ if (!inst.__vmzDirtyNotices)
3787
+ inst.__vmzDirtyNotices = [];
3788
+ inst.__vmzDirtyNotices.push(n);
3789
+ // Inside a UI event (or its sync flush): coalesce; endEventFlush drains without microtask hop.
3790
+ if ((inst.__vmzEventDepth || 0) > 0 || inst.__vmzFlushSync) {
3791
+ inst.__vmzFlushScheduled = true;
3792
+ return;
3793
+ }
3794
+ if (inst.__vmzFlushScheduled)
3795
+ return;
3796
+ inst.__vmzFlushScheduled = true;
3797
+ queueMicrotask(() => {
3798
+ inst.__vmzFlushScheduled = false;
3799
+ const p = flushPending(inst);
3800
+ if (p && typeof p.then === 'function') {
3801
+ p.catch((err) => console.error('vmz:dom flush', err));
3802
+ }
3803
+ });
3804
+ }
3805
+ /**
3806
+ * Infer `this.<method>(...)` from a compiled click arrow / bag handler.
3807
+ * @param {Function} handler
3808
+ * @returns {string | null}
3809
+ */
3810
+ function inferHandlerMethod(handler) {
3811
+ if (typeof handler !== 'function')
3812
+ return null;
3813
+ if (Object.hasOwn(handler, '__vmzMethod')) {
3814
+ return handler.__vmzMethod;
3815
+ }
3816
+ let name = null;
3817
+ try {
3818
+ const src = Function.prototype.toString.call(handler);
3819
+ const m = src.match(/this\.([A-Za-z_$][\w$]*)\s*\(/);
3820
+ name = m ? m[1] : null;
3821
+ }
3822
+ catch {
3823
+ name = null;
3824
+ }
3825
+ try {
3826
+ handler.__vmzMethod = name;
3827
+ }
3828
+ catch {
3829
+ /* non-extensible function */
3830
+ }
3831
+ return name;
3832
+ }
3833
+ /**
3834
+ * Sync event flush only when `__vmzMethodRw` proves the method is non-async / non-opaque.
3835
+ * Missing summary → sync (Direct UI default). Async/opaque → microtask coalesce.
3836
+ * @param {object} inst
3837
+ * @param {string | null | undefined} methodName
3838
+ */
3839
+ function methodAllowsSyncEventFlush(inst, methodName) {
3840
+ if (!inst)
3841
+ return false;
3842
+ if (!methodName)
3843
+ return true;
3844
+ const table = inst.constructor && inst.constructor.__vmzMethodRw;
3845
+ const rw = table && table[methodName];
3846
+ if (!rw)
3847
+ return true;
3848
+ if (rw.async || rw.opaque)
3849
+ return false;
3850
+ return true;
3851
+ }
3852
+ /**
3853
+ * @param {object} inst
3854
+ * @param {string | null | undefined} methodHint
3855
+ * @param {() => any} fn
3856
+ */
3857
+ function runDomEventHandler(inst, methodHint, fn) {
3858
+ const sync = methodAllowsSyncEventFlush(inst, methodHint);
3859
+ if (sync)
3860
+ beginEventFlush(inst);
3861
+ try {
3862
+ return fn();
3863
+ }
3864
+ finally {
3865
+ if (sync)
3866
+ endEventFlush(inst);
3867
+ }
3868
+ }
3869
+ function beginEventFlush(inst) {
3870
+ if (!inst)
3871
+ return;
3872
+ inst.__vmzEventDepth = (inst.__vmzEventDepth || 0) + 1;
3873
+ }
3874
+ function endEventFlush(inst) {
3875
+ if (!inst)
3876
+ return;
3877
+ inst.__vmzEventDepth = Math.max(0, (inst.__vmzEventDepth || 0) - 1);
3878
+ if (inst.__vmzEventDepth !== 0 || !inst.__vmzFlushScheduled)
3879
+ return;
3880
+ // Keep __vmzFlushSync so nested writes during flush stay off the microtask path.
3881
+ inst.__vmzFlushSync = true;
3882
+ try {
3883
+ inst.__vmzFlushScheduled = false;
3884
+ const ret = flushPending(inst);
3885
+ if (ret && typeof ret.then === 'function') {
3886
+ ret.catch((err) => console.error('vmz:dom flush', err));
3887
+ }
3888
+ }
3889
+ finally {
3890
+ inst.__vmzFlushSync = false;
3891
+ // Async binder left dirties: fall back to microtask coalesce.
3892
+ if (inst.__vmzFlushScheduled) {
3893
+ const again = inst.__vmzFlushScheduled;
3894
+ inst.__vmzFlushScheduled = false;
3895
+ if (again) {
3896
+ inst.__vmzFlushScheduled = true;
3897
+ queueMicrotask(() => {
3898
+ inst.__vmzFlushScheduled = false;
3899
+ const p = flushPending(inst);
3900
+ if (p && typeof p.then === 'function') {
3901
+ p.catch((err) => console.error('vmz:dom flush', err));
3902
+ }
3903
+ });
3904
+ }
3905
+ }
3906
+ }
3907
+ }
3908
+ /**
3909
+ * Promote an abandoned leaf batch into the dirty trie so mixed writes stay correct.
3910
+ * @param {object} inst
3911
+ */
3912
+ function promoteLeafDirtyToTrie(inst) {
3913
+ const ld = inst.__vmzLeafDirty;
3914
+ if (!ld || !ld.idxs || !ld.idxs.length) {
3915
+ inst.__vmzLeafDirty = null;
3916
+ return;
3917
+ }
3918
+ inst.__vmzLeafDirty = null;
3919
+ if (!inst.__vmzDirtyTrie)
3920
+ inst.__vmzDirtyTrie = Object.create(null);
3921
+ if (!inst.__vmzDirtyNotices)
3922
+ inst.__vmzDirtyNotices = [];
3923
+ const field = ld.field;
3924
+ const root = ld.root;
3925
+ for (let k = 0; k < ld.idxs.length; k++) {
3926
+ const segs = [String(ld.idxs[k]), field];
3927
+ insertDirtyNotice(inst.__vmzDirtyTrie, { type: 'path', root, segs });
3928
+ inst.__vmzDirtyNotices.push({ type: 'path', root, segs });
3929
+ }
3930
+ inst.__vmzFlushScheduled = true;
3931
+ }
3932
+ /**
3933
+ * @param {Record<string, any>} trie
3934
+ * @param {{ type: string, root: string, segs?: string[] }} notice
3935
+ */
3936
+ function insertDirtyNotice(trie, notice) {
3937
+ if (notice.type === 'replace') {
3938
+ trie[notice.root] = { replace: true };
3939
+ return;
3940
+ }
3941
+ const segs = notice.segs || [];
3942
+ let node = trie[notice.root];
3943
+ if (node && node.replace)
3944
+ return;
3945
+ if (!node) {
3946
+ node = { children: Object.create(null) };
3947
+ trie[notice.root] = node;
3948
+ }
3949
+ if (!segs.length) {
3950
+ trie[notice.root] = { replace: true };
3951
+ return;
3952
+ }
3953
+ if (!node.children)
3954
+ node.children = Object.create(null);
3955
+ let cur = node;
3956
+ for (let i = 0; i < segs.length; i++) {
3957
+ const seg = segs[i];
3958
+ if (cur.dirty)
3959
+ return; // ancestor already dirty
3960
+ if (!cur.children)
3961
+ cur.children = Object.create(null);
3962
+ if (i === segs.length - 1) {
3963
+ cur.children[seg] = { dirty: true };
3964
+ return;
3965
+ }
3966
+ let next = cur.children[seg];
3967
+ if (!next) {
3968
+ next = { children: Object.create(null) };
3969
+ cur.children[seg] = next;
3970
+ }
3971
+ else if (next.dirty) {
3972
+ return;
3973
+ }
3974
+ else if (!next.children) {
3975
+ next.children = Object.create(null);
3976
+ }
3977
+ cur = next;
3978
+ }
3979
+ }
3980
+ /** @param {object} inst */
3981
+ export function flushPending(inst) {
3982
+ if (!inst || inst.__vmzDestroyed)
3983
+ return undefined;
3984
+ inst.__vmzFlushScheduled = false;
3985
+ // rowKernel leaf batch (event update): apply before trie emptiness check.
3986
+ if (typeof inst.__vmzDrainLeafDirty === 'function') {
3987
+ try {
3988
+ inst.__vmzDrainLeafDirty();
3989
+ }
3990
+ catch (err) {
3991
+ console.error('vmz:dom leafDirty', err);
3992
+ }
3993
+ }
3994
+ let guard = 0;
3995
+ while (!inst.__vmzDestroyed &&
3996
+ (dirtyTrieHasEntries(inst.__vmzDirtyTrie) || (inst.__vmzDirtyNotices && inst.__vmzDirtyNotices.length > 0)) &&
3997
+ guard++ < 64) {
3998
+ const trie = inst.__vmzDirtyTrie || Object.create(null);
3999
+ inst.__vmzDirtyTrie = Object.create(null);
4000
+ if (inst.__vmzDirtyNotices)
4001
+ inst.__vmzDirtyNotices.length = 0;
4002
+ inst.__vmzFlushTrie = trie;
4003
+ const jobs = [];
4004
+ // Prefer BindingId scheduling (IR). String `__vmzBinders` is adapter-only.
4005
+ // Pass `trie` into refresh — dirty map is cleared above before patches run.
4006
+ try {
4007
+ const bindingIds = bindingIdsMatchingTrie(inst, trie);
4008
+ const coveredDeps = Object.create(null);
4009
+ for (const id of bindingIds) {
4010
+ const entry = inst.__vmzBindings && inst.__vmzBindings[id];
4011
+ if (entry) {
4012
+ for (const d of entry.deps || [])
4013
+ coveredDeps[d] = true;
4014
+ }
4015
+ jobs.push(...refreshBinding(inst, id, trie));
4016
+ }
4017
+ for (const key of binderKeysMatchingTrie(inst, trie)) {
4018
+ if (coveredDeps[key] || (inst.__vmzDepToBindings && inst.__vmzDepToBindings[key]?.length)) {
4019
+ // BindingId path already flushed IR patches for this dep.
4020
+ // Still run binder-only patches (bindComponentProp uses bindingId null).
4021
+ jobs.push(...refreshFieldBinderOnly(inst, key));
4022
+ continue;
4023
+ }
4024
+ jobs.push(...refreshField(inst, key));
4025
+ }
4026
+ const pending = jobs.filter((j) => j && typeof j.then === 'function');
4027
+ if (pending.length) {
4028
+ // Async binders: resume after they settle (default microtask path after).
4029
+ return Promise.all(pending).then(() => flushPending(inst));
4030
+ }
4031
+ }
4032
+ finally {
4033
+ inst.__vmzFlushTrie = null;
4034
+ }
4035
+ }
4036
+ return undefined;
4037
+ }
4038
+ /** Avoid `Object.keys(trie).length` alloc on the common empty-after-leaf-drain path. */
4039
+ function dirtyTrieHasEntries(trie) {
4040
+ if (!trie)
4041
+ return false;
4042
+ for (const _ in trie)
4043
+ return true;
4044
+ return false;
4045
+ }
4046
+ /**
4047
+ * @param {object} inst
4048
+ * @param {Record<string, any>} trie
4049
+ * @returns {Array<number|string>}
4050
+ */
4051
+ function bindingIdsMatchingTrie(inst, trie) {
4052
+ const index = inst.__vmzDepToBindings;
4053
+ if (!index)
4054
+ return [];
4055
+ const out = [];
4056
+ const seen = Object.create(null);
4057
+ for (const key of Object.keys(index)) {
4058
+ if (!depMatchesTrie(trie, key))
4059
+ continue;
4060
+ for (const id of index[key]) {
4061
+ const k = String(id);
4062
+ if (seen[k])
4063
+ continue;
4064
+ seen[k] = true;
4065
+ out.push(id);
4066
+ }
4067
+ }
4068
+ return out;
4069
+ }
4070
+ /**
4071
+ * @param {object} inst
4072
+ * @param {Record<string, any>} trie
4073
+ * @returns {string[]}
4074
+ */
4075
+ function binderKeysMatchingTrie(inst, trie) {
4076
+ const binders = inst.__vmzBinders;
4077
+ if (!binders)
4078
+ return [];
4079
+ const out = [];
4080
+ for (const key of Object.keys(binders)) {
4081
+ if (depMatchesTrie(trie, key))
4082
+ out.push(key);
4083
+ }
4084
+ return out;
4085
+ }
4086
+ /**
4087
+ * @param {Record<string, any>} trie
4088
+ * @param {string} key
4089
+ */
4090
+ function depMatchesTrie(trie, key) {
4091
+ const root = depRootField(key);
4092
+ const node = trie[root];
4093
+ if (!node)
4094
+ return false;
4095
+ if (node.replace) {
4096
+ return key === root || key === `${root}.*` || key.startsWith(`${root}.`) || key.startsWith(`${root}[`);
4097
+ }
4098
+ if (key === `${root}.*`) {
4099
+ // Bare `field.*` soft/structure channel: item replace / array structure only —
4100
+ // NOT deep leaf writes (`tags.0.label`); those use `tags.*.label` BindingId.
4101
+ return structureStarMatches(node);
4102
+ }
4103
+ // Bare field: replace-only.
4104
+ if (key === root)
4105
+ return false;
4106
+ // Path channel: `tags.*.label` — wildcard index under list root.
4107
+ const starPrefix = `${root}.*`;
4108
+ if (key === starPrefix || key.startsWith(`${starPrefix}.`)) {
4109
+ const rest = key === starPrefix
4110
+ ? []
4111
+ : key
4112
+ .slice(starPrefix.length + 1)
4113
+ .split('.')
4114
+ .filter(Boolean);
4115
+ return wildcardIndexDirtyCovers(node, rest);
4116
+ }
4117
+ // Stable ListItem form `tags[key=…].label` — treat `[key=…]` as wildcard index.
4118
+ if (key.startsWith(`${root}[`)) {
4119
+ const afterBracket = key.indexOf(']');
4120
+ if (afterBracket > root.length) {
4121
+ const rest = key.length > afterBracket + 1 && key[afterBracket + 1] === '.'
4122
+ ? key
4123
+ .slice(afterBracket + 2)
4124
+ .split('.')
4125
+ .filter(Boolean)
4126
+ : [];
4127
+ return wildcardIndexDirtyCovers(node, rest);
4128
+ }
4129
+ }
4130
+ const segs = key
4131
+ .slice(root.length + 1)
4132
+ .split('.')
4133
+ .filter(Boolean);
4134
+ return pathDirtyCovers(node, segs);
4135
+ }
4136
+ /** `tags.*` structure soft-refresh: replace or index-level dirty, not leaf-only. */
4137
+ function structureStarMatches(node) {
4138
+ if (!node)
4139
+ return false;
4140
+ if (node.replace || node.dirty)
4141
+ return true;
4142
+ if (!node.children)
4143
+ return false;
4144
+ for (const idx of Object.keys(node.children)) {
4145
+ const child = node.children[idx];
4146
+ // Index node dirty/replace → item identity changed.
4147
+ if (child && (child.replace || child.dirty))
4148
+ return true;
4149
+ }
4150
+ return false;
4151
+ }
4152
+ /** `tags.*.label` / `tags[key=x].label` vs dirty trie under `tags`. */
4153
+ function wildcardIndexDirtyCovers(node, restSegs) {
4154
+ if (!node || node.replace)
4155
+ return !!node?.replace;
4156
+ if (node.dirty)
4157
+ return true;
4158
+ if (!node.children)
4159
+ return false;
4160
+ for (const idx of Object.keys(node.children)) {
4161
+ const child = node.children[idx];
4162
+ if (restSegs.length === 0) {
4163
+ if (trieHasAnyDirty(child))
4164
+ return true;
4165
+ }
4166
+ else if (pathDirtyCovers(child, restSegs)) {
4167
+ return true;
4168
+ }
4169
+ }
4170
+ return false;
4171
+ }
4172
+ function trieHasAnyDirty(node) {
4173
+ if (!node || node.replace)
4174
+ return !!node;
4175
+ if (node.dirty)
4176
+ return true;
4177
+ if (!node.children)
4178
+ return false;
4179
+ for (const k of Object.keys(node.children)) {
4180
+ if (trieHasAnyDirty(node.children[k]))
4181
+ return true;
4182
+ }
4183
+ return false;
4184
+ }
4185
+ /**
4186
+ * Wake if write is at/under dep, or dep is under write (parent covers children).
4187
+ * @param {any} node root trie node for field
4188
+ * @param {string[]} depSegs
4189
+ */
4190
+ function pathDirtyCovers(node, depSegs) {
4191
+ let cur = node;
4192
+ for (let i = 0; i < depSegs.length; i++) {
4193
+ if (!cur || cur.replace)
4194
+ return !!cur?.replace;
4195
+ if (cur.dirty)
4196
+ return true; // write parent covers this dep
4197
+ if (!cur.children)
4198
+ return false;
4199
+ const next = cur.children[depSegs[i]];
4200
+ if (!next) {
4201
+ // No write along this dep path — but a write under a prefix?
4202
+ return false;
4203
+ }
4204
+ cur = next;
4205
+ }
4206
+ // Reached dep node: wake if dirty here or any dirty descendant (write under dep).
4207
+ return trieHasAnyDirty(cur);
4208
+ }
4209
+ /**
4210
+ * Dual-track match retained for tests / tooling.
4211
+ * @param {{ type: string, root: string, segs?: string[] }} notice
4212
+ * @param {string} key
4213
+ */
4214
+ function noticeMatchesDepKey(notice, key) {
4215
+ const trie = Object.create(null);
4216
+ insertDirtyNotice(trie, notice);
4217
+ return depMatchesTrie(trie, key);
4218
+ }
4219
+ /** Root field name from a dep key string (`user.name` → `user`, `tags.*` → `tags`). */
4220
+ function depRootField(dep) {
4221
+ if (!dep)
4222
+ return '';
4223
+ const star = dep.indexOf('.*');
4224
+ if (star >= 0)
4225
+ return dep.slice(0, star);
4226
+ const dot = dep.indexOf('.');
4227
+ if (dot >= 0)
4228
+ return dep.slice(0, dot);
4229
+ const bracket = dep.indexOf('[');
4230
+ if (bracket >= 0)
4231
+ return dep.slice(0, bracket);
4232
+ return dep;
4233
+ }
4234
+ /** Precise patches only — no full-tree fallback. @returns {Promise[]} */
4235
+ function refreshBinding(inst, bindingId, dirtyTrie = null) {
4236
+ const entry = inst.__vmzBindings && inst.__vmzBindings[bindingId];
4237
+ const jobs = [];
4238
+ if (!inst || inst.__vmzDestroyed || bindingId == null || !entry) {
4239
+ return jobs;
4240
+ }
4241
+ const depKey = (entry.deps && entry.deps[0]) || null;
4242
+ const trie = dirtyTrie || inst.__vmzDirtyTrie;
4243
+ const allowIdx = itemIndicesAllowedForDeps(trie, entry.deps);
4244
+ for (const fn of entry.patches) {
4245
+ if (allowIdx && !patchMatchesDirtyIndex(fn, allowIdx))
4246
+ continue;
4247
+ try {
4248
+ const ret = runPatch(fn, depKey, bindingId);
4249
+ if (ret && typeof ret.then === 'function')
4250
+ jobs.push(ret);
4251
+ }
4252
+ catch (err) {
4253
+ console.error('vmz:dom patch', err);
4254
+ }
4255
+ }
4256
+ return jobs;
4257
+ }
4258
+ /**
4259
+ * For ListItem path-channel deps (`tags.*.label`), restrict to dirty indices.
4260
+ * @param {Record<string, any>|null|undefined} trie
4261
+ * @param {string[]|null|undefined} deps
4262
+ * @returns {Set<string>|null} null = run all patches (replace / non-list deps)
4263
+ */
4264
+ function itemIndicesAllowedForDeps(trie, deps) {
4265
+ if (!trie || !deps || !deps.length)
4266
+ return null;
4267
+ let sawListChannel = false;
4268
+ /** @type {Set<string>|null} */
4269
+ let allow = null;
4270
+ for (const dep of deps) {
4271
+ const root = depRootField(dep);
4272
+ if (!root)
4273
+ continue;
4274
+ const starPrefix = `${root}.*`;
4275
+ const isListChannel = dep === starPrefix || dep.startsWith(`${starPrefix}.`) || (dep.startsWith(`${root}[`) && dep.includes(']'));
4276
+ if (!isListChannel)
4277
+ return null;
4278
+ sawListChannel = true;
4279
+ const node = trie[root];
4280
+ if (!node)
4281
+ continue;
4282
+ if (node.replace || node.dirty)
4283
+ return null; // whole list
4284
+ if (!node.children)
4285
+ continue;
4286
+ if (!allow)
4287
+ allow = new Set();
4288
+ for (const idx of Object.keys(node.children)) {
4289
+ const child = node.children[idx];
4290
+ if (!child)
4291
+ continue;
4292
+ if (child.replace || child.dirty || trieHasAnyDirty(child)) {
4293
+ allow.add(String(idx));
4294
+ }
4295
+ }
4296
+ }
4297
+ if (!sawListChannel)
4298
+ return null;
4299
+ return allow && allow.size ? allow : null;
4300
+ }
4301
+ /**
4302
+ * If every allowed list index is dirty on exactly the same single item field, return that field.
4303
+ * Used to hoist one monomorphic `applyByField[field]` across the update batch.
4304
+ * @param {Record<string, any> | null | undefined} trie
4305
+ * @param {string} listRoot
4306
+ * @param {Set<string>} allowIdx
4307
+ * @returns {string | null}
4308
+ */
4309
+ function soleDirtyItemField(trie, listRoot, allowIdx) {
4310
+ if (!trie || !listRoot || !allowIdx || !allowIdx.size)
4311
+ return null;
4312
+ const node = trie[listRoot];
4313
+ if (!node || node.replace || node.dirty || !node.children)
4314
+ return null;
4315
+ // Peek first index for the sole dirty field, then verify the rest match.
4316
+ // Fast path: index child has a single key (typical WritePath leaf) — no sibling scan.
4317
+ let field = null;
4318
+ for (const idx of allowIdx) {
4319
+ const child = node.children[String(idx)];
4320
+ if (!child || child.replace || child.dirty || !child.children)
4321
+ return null;
4322
+ const kids = child.children;
4323
+ const keys = Object.keys(kids);
4324
+ if (keys.length === 1) {
4325
+ const f = keys[0];
4326
+ const n = kids[f];
4327
+ if (!n || !(n.dirty || n.replace || trieHasAnyDirty(n)))
4328
+ return null;
4329
+ if (field == null)
4330
+ field = f;
4331
+ else if (field !== f)
4332
+ return null;
4333
+ continue;
4334
+ }
4335
+ if (field == null) {
4336
+ for (let k = 0; k < keys.length; k++) {
4337
+ const f = keys[k];
4338
+ const n = kids[f];
4339
+ if (n && (n.dirty || n.replace || trieHasAnyDirty(n))) {
4340
+ if (field != null)
4341
+ return null;
4342
+ field = f;
4343
+ }
4344
+ }
4345
+ if (field == null)
4346
+ return null;
4347
+ continue;
4348
+ }
4349
+ const n = kids[field];
4350
+ if (!n || !(n.dirty || n.replace || trieHasAnyDirty(n)))
4351
+ return null;
4352
+ for (let k = 0; k < keys.length; k++) {
4353
+ const f = keys[k];
4354
+ if (f === field)
4355
+ continue;
4356
+ const o = kids[f];
4357
+ if (o && (o.dirty || o.replace || trieHasAnyDirty(o)))
4358
+ return null;
4359
+ }
4360
+ }
4361
+ return field;
4362
+ }
4363
+ /**
4364
+ * Dirty item field names at list index (`rows.3.label` → `["label"]`).
4365
+ * @returns {string[]|null} null = whole item / unknown → full apply; [] = nothing
4366
+ */
4367
+ function dirtyItemFieldsAt(trie, listRoot, idx) {
4368
+ if (!trie || !listRoot)
4369
+ return null;
4370
+ const node = trie[listRoot];
4371
+ if (!node)
4372
+ return [];
4373
+ if (node.replace || node.dirty)
4374
+ return null;
4375
+ const child = node.children && node.children[String(idx)];
4376
+ if (!child)
4377
+ return [];
4378
+ if (child.replace || child.dirty)
4379
+ return null;
4380
+ if (!child.children)
4381
+ return null;
4382
+ /** @type {string[]} */
4383
+ const out = [];
4384
+ for (const field of Object.keys(child.children)) {
4385
+ const n = child.children[field];
4386
+ if (n && (n.dirty || n.replace || trieHasAnyDirty(n)))
4387
+ out.push(field);
4388
+ }
4389
+ return out;
4390
+ }
4391
+ function patchMatchesDirtyIndex(fn, allowIdx) {
4392
+ const idx = fn && fn.__vmzItemIndex;
4393
+ if (idx == null || idx === '')
4394
+ return true;
4395
+ return allowIdx.has(String(idx));
4396
+ }
4397
+ /** Legacy string-key patches (hand blueprints without BindingId). @returns {Promise[]} */
4398
+ function refreshField(inst, field) {
4399
+ const binders = inst.__vmzBinders;
4400
+ const jobs = [];
4401
+ if (!inst || inst.__vmzDestroyed || !field || !binders || !binders[field]) {
4402
+ return jobs;
4403
+ }
4404
+ for (const fn of binders[field]) {
4405
+ try {
4406
+ const ret = runPatch(fn, field, null);
4407
+ if (ret && typeof ret.then === 'function')
4408
+ jobs.push(ret);
4409
+ }
4410
+ catch (err) {
4411
+ console.error('vmz:dom patch', err);
4412
+ }
4413
+ }
4414
+ return jobs;
4415
+ }
4416
+ /**
4417
+ * Run `__vmzBinders` patches that are not owned by a BindingId entry.
4418
+ * Needed so `bindComponentProp` (bindingId null) still flushes when the same
4419
+ * dep also has IR bindText/bindAttr BindingIds.
4420
+ * @returns {Promise[]}
4421
+ */
4422
+ function refreshFieldBinderOnly(inst, field) {
4423
+ const binders = inst.__vmzBinders;
4424
+ const jobs = [];
4425
+ if (!inst || inst.__vmzDestroyed || !field || !binders || !binders[field]) {
4426
+ return jobs;
4427
+ }
4428
+ for (const fn of binders[field]) {
4429
+ if (patchHasBindingId(inst, fn))
4430
+ continue;
4431
+ try {
4432
+ const ret = runPatch(fn, field, null);
4433
+ if (ret && typeof ret.then === 'function')
4434
+ jobs.push(ret);
4435
+ }
4436
+ catch (err) {
4437
+ console.error('vmz:dom patch', err);
4438
+ }
4439
+ }
4440
+ return jobs;
4441
+ }
4442
+ /**
4443
+ * @param {object} inst
4444
+ * @param {number|string} bindingId
4445
+ * @param {string[]} deps
4446
+ */
4447
+ function reindexBindingDeps(inst, bindingId, deps) {
4448
+ if (!inst.__vmzDepToBindings)
4449
+ inst.__vmzDepToBindings = Object.create(null);
4450
+ const entry = inst.__vmzBindings[bindingId];
4451
+ if (!entry)
4452
+ return;
4453
+ for (const dep of entry.deps || []) {
4454
+ const list = inst.__vmzDepToBindings[dep];
4455
+ if (!list)
4456
+ continue;
4457
+ const j = list.indexOf(bindingId);
4458
+ if (j >= 0)
4459
+ list.splice(j, 1);
4460
+ if (list.length === 0)
4461
+ delete inst.__vmzDepToBindings[dep];
4462
+ }
4463
+ entry.deps = [...(deps || [])];
4464
+ for (const dep of entry.deps) {
4465
+ if (!inst.__vmzDepToBindings[dep])
4466
+ inst.__vmzDepToBindings[dep] = [];
4467
+ if (!inst.__vmzDepToBindings[dep].includes(bindingId)) {
4468
+ inst.__vmzDepToBindings[dep].push(bindingId);
4469
+ }
4470
+ }
4471
+ }
4472
+ /**
4473
+ * @param {object} inst
4474
+ * @param {string[]} deps
4475
+ * @param {() => any} fn
4476
+ * @param {number|string|null|undefined} [bindingId]
4477
+ */
4478
+ function registerBind(inst, deps, fn, bindingId = null) {
4479
+ if (!inst.__vmzBinders)
4480
+ inst.__vmzBinders = Object.create(null);
4481
+ for (const dep of deps || []) {
4482
+ if (!inst.__vmzBinders[dep])
4483
+ inst.__vmzBinders[dep] = [];
4484
+ inst.__vmzBinders[dep].push(fn);
4485
+ }
4486
+ if (bindingId == null)
4487
+ return;
4488
+ if (!inst.__vmzBindings)
4489
+ inst.__vmzBindings = Object.create(null);
4490
+ let entry = inst.__vmzBindings[bindingId];
4491
+ if (!entry) {
4492
+ entry = { id: bindingId, deps: [], patches: [] };
4493
+ inst.__vmzBindings[bindingId] = entry;
4494
+ }
4495
+ if (!entry.patches.includes(fn))
4496
+ entry.patches.push(fn);
4497
+ reindexBindingDeps(inst, bindingId, deps || []);
4498
+ }
4499
+ /**
4500
+ * @param {object} inst
4501
+ * @param {string[]} deps
4502
+ * @param {() => any} fn
4503
+ * @param {number|string|null|undefined} [bindingId]
4504
+ */
4505
+ function unregisterBind(inst, deps, fn, bindingId = null) {
4506
+ const binders = inst.__vmzBinders;
4507
+ if (binders) {
4508
+ for (const dep of deps || []) {
4509
+ const list = binders[dep];
4510
+ if (!list)
4511
+ continue;
4512
+ const i = list.indexOf(fn);
4513
+ if (i >= 0)
4514
+ list.splice(i, 1);
4515
+ if (list.length === 0)
4516
+ delete binders[dep];
4517
+ }
4518
+ }
4519
+ if (bindingId == null || !inst.__vmzBindings)
4520
+ return;
4521
+ const entry = inst.__vmzBindings[bindingId];
4522
+ if (!entry)
4523
+ return;
4524
+ const i = entry.patches.indexOf(fn);
4525
+ if (i >= 0)
4526
+ entry.patches.splice(i, 1);
4527
+ if (entry.patches.length === 0) {
4528
+ reindexBindingDeps(inst, bindingId, []);
4529
+ delete inst.__vmzBindings[bindingId];
4530
+ }
4531
+ }
4532
+ /**
4533
+ * True when the container already has meaningful DOM (SSR / resume shell).
4534
+ * @param {Element} el
4535
+ */
4536
+ export function hasMeaningfulChild(el) {
4537
+ for (const n of el.childNodes) {
4538
+ if (n.nodeType === 1)
4539
+ return true;
4540
+ if (n.nodeType === 3 && String(n.textContent).trim() !== '')
4541
+ return true;
4542
+ }
4543
+ return false;
4544
+ }
4545
+ function patchHasBindingId(inst, fn) {
4546
+ const bindings = inst && inst.__vmzBindings;
4547
+ if (!bindings)
4548
+ return false;
4549
+ for (const id of Object.keys(bindings)) {
4550
+ const patches = bindings[id].patches;
4551
+ if (patches && patches.includes(fn))
4552
+ return true;
4553
+ }
4554
+ return false;
4555
+ }
4556
+ /** Tag each-item patches with list index for ListItem path-channel filtering. */
4557
+ function tagItemPatches(patches, index) {
4558
+ if (!patches)
4559
+ return;
4560
+ const idx = String(index);
4561
+ for (const p of patches) {
4562
+ if (typeof p === 'function')
4563
+ p.__vmzItemIndex = idx;
4564
+ }
4565
+ }