@vmz/core 0.1.25 → 0.1.26

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.
@@ -161,6 +161,14 @@ export declare const directApi: {
161
161
  * @param {string | null} client
162
162
  */
163
163
  component(hostInst: any, name: any, props: any, client: any): any;
164
+ /**
165
+ * Subscribe to a child component event (`@submit` → event name `submit`).
166
+ * Orthogonal to function props (`:on-submit` → prop `onSubmit`).
167
+ * @param {HTMLElement} hostEl
168
+ * @param {string} eventName
169
+ * @param {(ev?: any, ...rest: any[]) => any} handler
170
+ */
171
+ onComponentEvent(hostEl: any, eventName: any, handler: any): void;
164
172
  /**
165
173
  * Keep nested Direct child props live with parent field writes.
166
174
  * @param {object} hostInst
@@ -198,12 +206,20 @@ export declare const directApi: {
198
206
  eachBlock(inst: any, bindingId: any, deps: any, spec: any, regionId?: any): DocumentFragment;
199
207
  };
200
208
  /**
201
- * Vue-familiar `@click` / legacy `onClick` → canonical `onXxx` prop name.
202
- * Function props that are not events are treated as getters (`v.call(host)`).
209
+ * Function prop wire: only `onXxx` camelCase is a handler prop.
210
+ * Component `@event` must never normalize here — that channel is `onComponentEvent`.
203
211
  * @param {unknown} name
204
212
  * @returns {string | null}
205
213
  */
206
214
  export declare function eventPropHandlerName(name: any): string;
215
+ /**
216
+ * Dispatch a component event to parent subscribers registered via `onComponentEvent`.
217
+ * Installed on every Direct instance as `inst.emit`.
218
+ * @param {object} inst
219
+ * @param {string} eventName
220
+ * @param {...any} payload
221
+ */
222
+ export declare function emitComponentEvent(inst: any, eventName: any, ...payload: any[]): void;
207
223
  export declare function isEventPropName(name: any): boolean;
208
224
  /** HTML boolean attributes: presence means true; `false`/`null` must remove the attr. */
209
225
  export declare const BOOLEAN_HTML_ATTRS: Set<string>;
package/dist/dom-core.js CHANGED
@@ -505,6 +505,8 @@ export const directApi = {
505
505
  /** @type {Record<string, any>} */
506
506
  const resolved = {};
507
507
  for (const [k, v] of Object.entries(props || {})) {
508
+ // Function props that already look like `onXxx` stay as handlers.
509
+ // Component `@event` never arrives here (emit uses onComponentEvent).
508
510
  const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
509
511
  if (onKey)
510
512
  resolved[onKey] = v;
@@ -574,6 +576,23 @@ export const directApi = {
574
576
  }
575
577
  return host;
576
578
  },
579
+ /**
580
+ * Subscribe to a child component event (`@submit` → event name `submit`).
581
+ * Orthogonal to function props (`:on-submit` → prop `onSubmit`).
582
+ * @param {HTMLElement} hostEl
583
+ * @param {string} eventName
584
+ * @param {(ev?: any, ...rest: any[]) => any} handler
585
+ */
586
+ onComponentEvent(hostEl, eventName, handler) {
587
+ const child = hostEl && hostEl.__vmzInst;
588
+ if (!child || typeof eventName !== 'string' || !eventName)
589
+ return;
590
+ if (typeof handler !== 'function')
591
+ return;
592
+ const bag = child.__vmzComponentListeners || (child.__vmzComponentListeners = Object.create(null));
593
+ const list = bag[eventName] || (bag[eventName] = []);
594
+ list.push(handler);
595
+ },
577
596
  /**
578
597
  * Keep nested Direct child props live with parent field writes.
579
598
  * @param {object} hostInst
@@ -2736,8 +2755,8 @@ function wireDirectBind(inst, bindingId, deps, get, write, cf) {
2736
2755
  trackDirectBind(inst, liveDeps, apply, bindingId);
2737
2756
  }
2738
2757
  /**
2739
- * Vue-familiar `@click` / legacy `onClick` → canonical `onXxx` prop name.
2740
- * Function props that are not events are treated as getters (`v.call(host)`).
2758
+ * Function prop wire: only `onXxx` camelCase is a handler prop.
2759
+ * Component `@event` must never normalize here — that channel is `onComponentEvent`.
2741
2760
  * @param {unknown} name
2742
2761
  * @returns {string | null}
2743
2762
  */
@@ -2746,15 +2765,27 @@ export function eventPropHandlerName(name) {
2746
2765
  return null;
2747
2766
  if (/^on[A-Z]/.test(name))
2748
2767
  return name;
2749
- // `@input` → `onInput`; reject `@update:modelValue` until that surface exists.
2750
- if (name.charAt(0) === '@' && name.length > 1 && !name.includes(':')) {
2751
- const ev = name.slice(1);
2752
- if (!ev || !/^[A-Za-z]/.test(ev))
2753
- return null;
2754
- return `on${ev.charAt(0).toUpperCase()}${ev.slice(1)}`;
2755
- }
2756
2768
  return null;
2757
2769
  }
2770
+ /**
2771
+ * Dispatch a component event to parent subscribers registered via `onComponentEvent`.
2772
+ * Installed on every Direct instance as `inst.emit`.
2773
+ * @param {object} inst
2774
+ * @param {string} eventName
2775
+ * @param {...any} payload
2776
+ */
2777
+ export function emitComponentEvent(inst, eventName, ...payload) {
2778
+ if (!inst || typeof eventName !== 'string' || !eventName)
2779
+ return;
2780
+ const bag = inst.__vmzComponentListeners;
2781
+ const list = bag && bag[eventName];
2782
+ if (!Array.isArray(list) || list.length === 0)
2783
+ return;
2784
+ for (const fn of list) {
2785
+ if (typeof fn === 'function')
2786
+ fn(...payload);
2787
+ }
2788
+ }
2758
2789
  export function isEventPropName(name) {
2759
2790
  return eventPropHandlerName(name) != null;
2760
2791
  }
@@ -3137,6 +3168,11 @@ export function createInstance(Component, props = {}) {
3137
3168
  inst.__vmzBinders = Object.create(null);
3138
3169
  inst.__vmzBindings = Object.create(null);
3139
3170
  inst.__vmzDepToBindings = Object.create(null);
3171
+ inst.__vmzComponentListeners = Object.create(null);
3172
+ // Compiler intrinsic surface: `this.emit('submit', payload)` — not a string bus.
3173
+ inst.emit = function emit(eventName, ...payload) {
3174
+ return emitComponentEvent(inst, eventName, ...payload);
3175
+ };
3140
3176
  makeReactive(inst, Component.__vmzState || []);
3141
3177
  makeReactive(inst, Component.__vmzProps || []);
3142
3178
  // WriteBarrier: install Component helpers once (no import needed in emitted code).
package/dist/dom-ssr.js CHANGED
@@ -578,6 +578,9 @@ const serializeApi = {
578
578
  adoptLeave() {
579
579
  /* resume scope only */
580
580
  },
581
+ onComponentEvent() {
582
+ /* component events attach only on the client */
583
+ },
581
584
  bindText(inst, bindingId, deps, get, textNode) {
582
585
  let raw = '';
583
586
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/core",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "type": "module",
5
5
  "description": "VMZ production runtime core — DOM / SSR / HTTP / WriteBarrier (no compiler)",
6
6
  "exports": {
@@ -66,12 +66,12 @@
66
66
  "linkedom": "^0.18.13"
67
67
  },
68
68
  "optionalDependencies": {
69
- "@vmz/vmz-win32-x64": "0.1.25",
70
- "@vmz/vmz-win32-arm64": "0.1.25",
71
- "@vmz/vmz-darwin-x64": "0.1.25",
72
- "@vmz/vmz-darwin-arm64": "0.1.25",
73
- "@vmz/vmz-linux-x64": "0.1.25",
74
- "@vmz/vmz-linux-arm64": "0.1.25"
69
+ "@vmz/vmz-win32-x64": "0.1.26",
70
+ "@vmz/vmz-win32-arm64": "0.1.26",
71
+ "@vmz/vmz-darwin-x64": "0.1.26",
72
+ "@vmz/vmz-darwin-arm64": "0.1.26",
73
+ "@vmz/vmz-linux-x64": "0.1.26",
74
+ "@vmz/vmz-linux-arm64": "0.1.26"
75
75
  },
76
76
  "publishConfig": {
77
77
  "access": "public"