@vmz/core 0.3.8 → 0.3.10

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.
@@ -99,6 +99,14 @@ export type ComponentCtorLike = (new (props?: object) => DirectInstance) & {
99
99
  __vmzProps?: string[];
100
100
  __vmzWBInstalled?: boolean;
101
101
  __vmzCtorAppliesProps?: boolean;
102
+ /**
103
+ * Compile-time embedded BindingId → deps[] map. When the Rust generator
104
+ * emits this static map on the component class, `registerBind` resolves
105
+ * deps by lookup instead of rebuilding O(N×M) indices from the string[]
106
+ * the artifact passes per call. Absent (today) — falls back to the call
107
+ * site argument unchanged.
108
+ */
109
+ __vmzDepsMap?: Record<string | number, string[]>;
102
110
  name?: string;
103
111
  };
104
112
  export type DirectEachCtx = {
@@ -135,6 +143,7 @@ export type DirectApi = {
135
143
  adoptLeave: () => void;
136
144
  projectDefaultSlot: (hostEl: HTMLElement, node: Node) => void;
137
145
  setHtml: (el: Element, value: unknown) => void;
146
+ setShow: (el: HTMLElement, condition: unknown) => void;
138
147
  bindHtml: (inst: DirectInstance, bindingId: BindingId, deps: string[], get: PatchFn, el: Element) => void;
139
148
  bindComponentProp: (inst: DirectInstance, bindingId: BindingId, deps: string[], get: PatchFn, hostInst: DirectInstance, propName: string) => void;
140
149
  component: (Ctor: ComponentCtorLike | string, props?: object) => HTMLElement;
@@ -30,7 +30,7 @@ export declare const directApi: {
30
30
  attr(el: any, name: any, value: any): void;
31
31
  mergeClass(...parts: any[]): string;
32
32
  mergeStyle(...parts: any[]): string;
33
- on(el: any, type: any, handler: any): void;
33
+ on(el: any, type: any, handler: any, options?: any): void;
34
34
  /**
35
35
  * Bind a named instance method (no arrow / Function#toString).
36
36
  * Learns `skipFlush` after a sync invocation that schedules no dirty work
@@ -46,7 +46,25 @@ export declare const directApi: {
46
46
  */
47
47
  specFieldText(inst: any, bindingId: any, fieldName: any, textNode: any): void;
48
48
  setHtml(el: any, value: any): void;
49
+ /**
50
+ * Vue `v-text` — sets `textContent`, escaping all HTML (unlike `setHtml`).
51
+ * Overrides any existing children on the element. Static create-time path;
52
+ * reactive bindings use {@link bindText}.
53
+ */
54
+ setText(el: any, value: any): void;
55
+ /**
56
+ * Vue `v-show` — toggles `style.display` between empty string (visible,
57
+ * original CSS wins) and `'none'`. Element stays in the DOM tree (unlike
58
+ * `v-if`). Used for static-show values; reactive bindings emit inline
59
+ * patches in {@link trackPatch}.
60
+ */
61
+ setShow(el: any, condition: any): void;
49
62
  bindHtml(inst: any, bindingId: any, deps: any, get: any, el: any): void;
63
+ /**
64
+ * Reactive `v-text` textContent binding — mirrors {@link bindHtml} but
65
+ * writes `textContent` (HTML-escaped, safe).
66
+ */
67
+ bindText(inst: any, bindingId: any, deps: any, get: any, el: any): void;
50
68
  /**
51
69
  * Nested component (sync Direct child or island schedule).
52
70
  */
@@ -169,7 +169,11 @@ export const directApi = {
169
169
  mergeStyle(...parts) {
170
170
  return mergeStyleParts(...parts);
171
171
  },
172
- on(el, type, handler) {
172
+ on(el, type, handler, options) {
173
+ // options: { capture?: boolean; passive?: boolean; once?: boolean }
174
+ const listenerOpts = options && (options.capture || options.passive || options.once)
175
+ ? { capture: !!options.capture, passive: !!options.passive, once: !!options.once }
176
+ : undefined;
173
177
  const inst = directApi._inst;
174
178
  if (directApi._eachCtx && typeof handler === 'function') {
175
179
  const bag = el.__vmzEvt || (el.__vmzEvt = Object.create(null));
@@ -191,7 +195,7 @@ export const directApi = {
191
195
  };
192
196
  bag[type] = listener;
193
197
  directApi._eachCtx.needDelegate(type);
194
- el.addEventListener(type, listener);
198
+ el.addEventListener(type, listener, listenerOpts);
195
199
  return;
196
200
  }
197
201
  // Infer once at bind time ??never Function#toString on the click hot path.
@@ -208,7 +212,7 @@ export const directApi = {
208
212
  return m.call(inst, ev);
209
213
  return handler.call(inst, ev);
210
214
  });
211
- });
215
+ }, listenerOpts);
212
216
  return;
213
217
  }
214
218
  el.addEventListener(type, (ev) => {
@@ -219,7 +223,7 @@ export const directApi = {
219
223
  if (typeof handler === 'function') {
220
224
  runDomEventHandler(inst, methodHint, () => handler.call(inst, ev));
221
225
  }
222
- });
226
+ }, listenerOpts);
223
227
  },
224
228
  /**
225
229
  * Bind a named instance method (no arrow / Function#toString).
@@ -227,6 +231,10 @@ export const directApi = {
227
231
  * (stride / transpose self-apply DOM).
228
232
  */
229
233
  onMethod(el, type, methodName, opts) {
234
+ // opts: { skipFlush?: boolean; capture?: boolean; passive?: boolean; once?: boolean }
235
+ const listenerOpts = opts && (opts.capture || opts.passive || opts.once)
236
+ ? { capture: !!opts.capture, passive: !!opts.passive, once: !!opts.once }
237
+ : undefined;
230
238
  // Prefer each-block owner: item create often runs during flush when `_inst` is unset.
231
239
  const eachOwner = directApi._eachCtx && directApi._eachCtx.inst;
232
240
  const inst = eachOwner || directApi._inst;
@@ -262,10 +270,10 @@ export const directApi = {
262
270
  const listener = (ev) => invoke(ev);
263
271
  bag[type] = listener;
264
272
  directApi._eachCtx.needDelegate(type);
265
- el.addEventListener(type, listener);
273
+ el.addEventListener(type, listener, listenerOpts);
266
274
  return;
267
275
  }
268
- el.addEventListener(type, (ev) => invoke(ev));
276
+ el.addEventListener(type, (ev) => invoke(ev), listenerOpts);
269
277
  },
270
278
  /**
271
279
  * Specialized single-field attr bind (0.1.29): compile-time field name, no generic get closure in artifact.
@@ -303,6 +311,23 @@ export const directApi = {
303
311
  setHtml(el, value) {
304
312
  el.innerHTML = value == null ? '' : String(value);
305
313
  },
314
+ /**
315
+ * Vue `v-text` — sets `textContent`, escaping all HTML (unlike `setHtml`).
316
+ * Overrides any existing children on the element. Static create-time path;
317
+ * reactive bindings use {@link bindText}.
318
+ */
319
+ setText(el, value) {
320
+ el.textContent = value == null ? '' : String(value);
321
+ },
322
+ /**
323
+ * Vue `v-show` — toggles `style.display` between empty string (visible,
324
+ * original CSS wins) and `'none'`. Element stays in the DOM tree (unlike
325
+ * `v-if`). Used for static-show values; reactive bindings emit inline
326
+ * patches in {@link trackPatch}.
327
+ */
328
+ setShow(el, condition) {
329
+ el.style.display = condition ? '' : 'none';
330
+ },
306
331
  bindHtml(inst, bindingId, deps, get, el) {
307
332
  directApi.trackPatch(inst, deps || [], function bindHtmlPatch() {
308
333
  let raw;
@@ -315,6 +340,22 @@ export const directApi = {
315
340
  el.innerHTML = raw == null ? '' : String(raw);
316
341
  }, bindingId);
317
342
  },
343
+ /**
344
+ * Reactive `v-text` textContent binding — mirrors {@link bindHtml} but
345
+ * writes `textContent` (HTML-escaped, safe).
346
+ */
347
+ bindText(inst, bindingId, deps, get, el) {
348
+ directApi.trackPatch(inst, deps || [], function bindTextPatch() {
349
+ let raw;
350
+ try {
351
+ raw = get.call(inst);
352
+ }
353
+ catch {
354
+ raw = '';
355
+ }
356
+ el.textContent = raw == null ? '' : String(raw);
357
+ }, bindingId);
358
+ },
318
359
  /**
319
360
  * Nested component (sync Direct child or island schedule).
320
361
  */
@@ -645,6 +645,9 @@ function reindexBindingDeps(inst, bindingId, deps) {
645
645
  const entry = inst.__vmzBindings[bindingId];
646
646
  if (!entry)
647
647
  return;
648
+ // Fast path: deps 完全相同 → 无需重建 dep→bindings 索引。
649
+ if (arraysEqual(entry.deps, deps))
650
+ return;
648
651
  for (const dep of entry.deps || []) {
649
652
  const list = inst.__vmzDepToBindings[dep];
650
653
  if (!list)
@@ -664,10 +667,47 @@ function reindexBindingDeps(inst, bindingId, deps) {
664
667
  }
665
668
  }
666
669
  }
670
+ function arraysEqual(a, b) {
671
+ if (a === b)
672
+ return true;
673
+ const la = a ? a.length : 0;
674
+ const lb = b ? b.length : 0;
675
+ if (la !== lb)
676
+ return false;
677
+ for (let i = 0; i < la; i++)
678
+ if (a[i] !== b[i])
679
+ return false;
680
+ return true;
681
+ }
682
+ /**
683
+ * Resolve deps for a binding: prefer the argument from generated artifact,
684
+ * fall back to a compile-time-embedded static map on the component class.
685
+ *
686
+ * The Rust generator will later emit `Ctor.__vmzDepsMap = { [bindingId]: deps[] }`
687
+ * so runtime can do a single lookup instead of rebuilding O(N×M) dep→binding
688
+ * indices from the string[] the artifact passes today.
689
+ */
690
+ function resolveDeps(inst, depsArg, bindingId) {
691
+ if (depsArg && depsArg.length)
692
+ return depsArg;
693
+ if (bindingId == null)
694
+ return depsArg || [];
695
+ const ctor = inst && inst.constructor;
696
+ const map = ctor && ctor.__vmzDepsMap;
697
+ if (map) {
698
+ const hit = map[bindingId];
699
+ if (hit)
700
+ return hit;
701
+ }
702
+ return depsArg || [];
703
+ }
667
704
  export function registerBind(inst, deps, fn, bindingId = null) {
705
+ // 查表优先: generator 侧若已在 Ctor.__vmzDepsMap 嵌入完整 deps map
706
+ // 则直接取用, 避免每次 registerBind 都用传入 string[] 重建索引.
707
+ const resolvedDeps = resolveDeps(inst, deps, bindingId);
668
708
  if (!inst.__vmzBinders)
669
709
  inst.__vmzBinders = Object.create(null);
670
- for (const dep of deps || []) {
710
+ for (const dep of resolvedDeps) {
671
711
  if (!inst.__vmzBinders[dep])
672
712
  inst.__vmzBinders[dep] = [];
673
713
  // Dedup: cf untrack/retrack with identical deps must not grow the binder list.
@@ -685,7 +725,7 @@ export function registerBind(inst, deps, fn, bindingId = null) {
685
725
  }
686
726
  if (!entry.patches.includes(fn))
687
727
  entry.patches.push(fn);
688
- reindexBindingDeps(inst, bindingId, deps || []);
728
+ reindexBindingDeps(inst, bindingId, resolvedDeps);
689
729
  }
690
730
  export function unregisterBind(inst, deps, fn, bindingId = null) {
691
731
  const binders = inst.__vmzBinders;
package/package.json CHANGED
@@ -68,12 +68,12 @@
68
68
  "main": "./dist/faces/server.js",
69
69
  "name": "@vmz/core",
70
70
  "optionalDependencies": {
71
- "@vmz/vmz-darwin-arm64": "0.3.8",
72
- "@vmz/vmz-darwin-x64": "0.3.8",
73
- "@vmz/vmz-linux-arm64": "0.3.8",
74
- "@vmz/vmz-linux-x64": "0.3.8",
75
- "@vmz/vmz-win32-arm64": "0.3.8",
76
- "@vmz/vmz-win32-x64": "0.3.8"
71
+ "@vmz/vmz-darwin-arm64": "0.3.10",
72
+ "@vmz/vmz-darwin-x64": "0.3.10",
73
+ "@vmz/vmz-linux-arm64": "0.3.10",
74
+ "@vmz/vmz-linux-x64": "0.3.10",
75
+ "@vmz/vmz-win32-arm64": "0.3.10",
76
+ "@vmz/vmz-win32-x64": "0.3.10"
77
77
  },
78
78
  "publishConfig": {
79
79
  "access": "public"
@@ -88,5 +88,5 @@
88
88
  },
89
89
  "type": "module",
90
90
  "types": "./dist/faces/index.d.ts",
91
- "version": "0.3.8"
91
+ "version": "0.3.10"
92
92
  }