@esmx/router-vue 3.0.0-rc.24 → 3.0.0-rc.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.
@@ -70,7 +70,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
70
70
  };
71
71
  /**
72
72
  * @deprecated Use 'type="replace"' instead
73
- * @example replace={true} → type="replace"
73
+ * @example :replace={true} → type="replace"
74
74
  */
75
75
  replace: {
76
76
  type: BooleanConstructor;
@@ -120,6 +120,33 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
120
120
  layerOptions: {
121
121
  type: PropType<RouteLayerOptions>;
122
122
  };
123
+ /**
124
+ * Custom event handler to control navigation behavior.
125
+ * Should return `true` to allow router to navigate, otherwise to prevent it.
126
+ *
127
+ * @Note you need to call `e.preventDefault()` to prevent default browser navigation.
128
+ * @default
129
+ *
130
+ * (event: Event & Partial<MouseEvent>): boolean => {
131
+ * // don't redirect with control keys
132
+ * if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
133
+ * // don't redirect when preventDefault called
134
+ * if (e.defaultPrevented) return false;
135
+ * // don't redirect on right click
136
+ * if (e.button !== undefined && e.button !== 0) return false;
137
+ * // don't redirect if `target="_blank"`
138
+ * const target = e.currentTarget?.getAttribute?.('target') ?? '';
139
+ * if (/\b_blank\b/i.test(target)) return false;
140
+ * // Prevent default browser navigation to enable SPA routing
141
+ * // Note: this may be a Weex event which doesn't have this method
142
+ * if (e.preventDefault) e.preventDefault();
143
+ *
144
+ * return true;
145
+ * }
146
+ */
147
+ eventHandler: {
148
+ type: PropType<(event: Event) => boolean | undefined | void>;
149
+ };
123
150
  }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
124
151
  [key: string]: any;
125
152
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -143,7 +170,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
143
170
  };
144
171
  /**
145
172
  * @deprecated Use 'type="replace"' instead
146
- * @example replace={true} → type="replace"
173
+ * @example :replace={true} → type="replace"
147
174
  */
148
175
  replace: {
149
176
  type: BooleanConstructor;
@@ -193,6 +220,33 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
193
220
  layerOptions: {
194
221
  type: PropType<RouteLayerOptions>;
195
222
  };
223
+ /**
224
+ * Custom event handler to control navigation behavior.
225
+ * Should return `true` to allow router to navigate, otherwise to prevent it.
226
+ *
227
+ * @Note you need to call `e.preventDefault()` to prevent default browser navigation.
228
+ * @default
229
+ *
230
+ * (event: Event & Partial<MouseEvent>): boolean => {
231
+ * // don't redirect with control keys
232
+ * if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
233
+ * // don't redirect when preventDefault called
234
+ * if (e.defaultPrevented) return false;
235
+ * // don't redirect on right click
236
+ * if (e.button !== undefined && e.button !== 0) return false;
237
+ * // don't redirect if `target="_blank"`
238
+ * const target = e.currentTarget?.getAttribute?.('target') ?? '';
239
+ * if (/\b_blank\b/i.test(target)) return false;
240
+ * // Prevent default browser navigation to enable SPA routing
241
+ * // Note: this may be a Weex event which doesn't have this method
242
+ * if (e.preventDefault) e.preventDefault();
243
+ *
244
+ * return true;
245
+ * }
246
+ */
247
+ eventHandler: {
248
+ type: PropType<(event: Event) => boolean | undefined | void>;
249
+ };
196
250
  }>> & Readonly<{}>, {
197
251
  type: RouterLinkType;
198
252
  replace: boolean;
@@ -21,7 +21,7 @@ export const RouterLink = defineComponent({
21
21
  type: { type: String, default: "push" },
22
22
  /**
23
23
  * @deprecated Use 'type="replace"' instead
24
- * @example replace={true} → type="replace"
24
+ * @example :replace={true} → type="replace"
25
25
  */
26
26
  replace: { type: Boolean, default: false },
27
27
  /**
@@ -57,28 +57,84 @@ export const RouterLink = defineComponent({
57
57
  * Only used when type='pushLayer'.
58
58
  * @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
59
59
  */
60
- layerOptions: { type: Object }
60
+ layerOptions: { type: Object },
61
+ /**
62
+ * Custom event handler to control navigation behavior.
63
+ * Should return `true` to allow router to navigate, otherwise to prevent it.
64
+ *
65
+ * @Note you need to call `e.preventDefault()` to prevent default browser navigation.
66
+ * @default
67
+ *
68
+ * (event: Event & Partial<MouseEvent>): boolean => {
69
+ * // don't redirect with control keys
70
+ * if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
71
+ * // don't redirect when preventDefault called
72
+ * if (e.defaultPrevented) return false;
73
+ * // don't redirect on right click
74
+ * if (e.button !== undefined && e.button !== 0) return false;
75
+ * // don't redirect if `target="_blank"`
76
+ * const target = e.currentTarget?.getAttribute?.('target') ?? '';
77
+ * if (/\b_blank\b/i.test(target)) return false;
78
+ * // Prevent default browser navigation to enable SPA routing
79
+ * // Note: this may be a Weex event which doesn't have this method
80
+ * if (e.preventDefault) e.preventDefault();
81
+ *
82
+ * return true;
83
+ * }
84
+ */
85
+ eventHandler: {
86
+ type: Function
87
+ }
61
88
  },
62
- setup(props, { slots }) {
89
+ setup(props, context) {
90
+ const { slots, attrs } = context;
63
91
  const link = useLink(props);
64
- return () => {
92
+ const wrapHandler = (externalHandler, internalHandler) => !internalHandler ? externalHandler : async (e) => {
93
+ try {
94
+ await externalHandler(e);
95
+ } finally {
96
+ await internalHandler(e);
97
+ }
98
+ };
99
+ const vue3renderer = () => {
65
100
  var _a;
66
101
  const data = link.value;
67
- const eventHandlers = data.getEventHandlers(
68
- isVue3 ? (name) => `on${name.charAt(0).toUpperCase()}${name.slice(1)}` : void 0
102
+ const genEventName = (name) => "on".concat(name.charAt(0).toUpperCase()).concat(name.slice(1));
103
+ const eventHandlers = data.getEventHandlers(genEventName);
104
+ Object.entries(attrs).forEach(([key, listener]) => {
105
+ if (!key.startsWith("on") || typeof listener !== "function")
106
+ return;
107
+ eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
108
+ });
109
+ return h(
110
+ data.tag,
111
+ {
112
+ ...data.attributes,
113
+ ...eventHandlers
114
+ },
115
+ (_a = slots.default) == null ? void 0 : _a.call(slots)
69
116
  );
70
- const props2 = {};
71
- if (isVue3) {
72
- Object.assign(props2, data.attributes, eventHandlers);
73
- } else {
74
- const { class: className, ...attrs } = data.attributes;
75
- Object.assign(props2, {
76
- attrs,
117
+ };
118
+ const vue2renderer = () => {
119
+ var _a;
120
+ const data = link.value;
121
+ const eventHandlers = data.getEventHandlers();
122
+ const $listeners = context.listeners || {};
123
+ Object.entries($listeners).forEach(([key, listener]) => {
124
+ if (typeof listener !== "function") return;
125
+ eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
126
+ });
127
+ const { class: className, ...attrs2 } = data.attributes;
128
+ return h(
129
+ data.tag,
130
+ {
131
+ attrs: attrs2,
77
132
  class: className,
78
133
  on: eventHandlers
79
- });
80
- }
81
- return h(data.tag, props2, (_a = slots.default) == null ? void 0 : _a.call(slots));
134
+ },
135
+ (_a = slots.default) == null ? void 0 : _a.call(slots)
136
+ );
82
137
  };
138
+ return isVue3 ? vue3renderer : vue2renderer;
83
139
  }
84
140
  });
package/dist/use.mjs CHANGED
@@ -10,7 +10,7 @@ import { createSymbolProperty } from "./util.mjs";
10
10
  const ROUTER_CONTEXT_KEY = Symbol("router-context");
11
11
  const ROUTER_INJECT_KEY = Symbol("router-inject");
12
12
  const ERROR_MESSAGES = {
13
- SETUP_ONLY: (fnName) => `[@esmx/router-vue] ${fnName}() can only be called during setup()`,
13
+ SETUP_ONLY: (fnName) => "[@esmx/router-vue] ".concat(fnName, "() can only be called during setup()"),
14
14
  CONTEXT_NOT_FOUND: "[@esmx/router-vue] Router context not found. Please ensure useProvideRouter() is called in a parent component."
15
15
  };
16
16
  const routerContextProperty = createSymbolProperty(ROUTER_CONTEXT_KEY);
package/dist/use.test.mjs CHANGED
@@ -75,7 +75,7 @@ describe("use.ts - Vue Router Integration", () => {
75
75
  }
76
76
  ];
77
77
  contextErrorTestCases.forEach(({ name, test }) => {
78
- it(`should throw error when ${name}`, () => {
78
+ it("should throw error when ".concat(name), () => {
79
79
  expect(test()).toThrow(contextNotFoundError);
80
80
  });
81
81
  });
@@ -106,7 +106,7 @@ describe("use.ts - Vue Router Integration", () => {
106
106
  }
107
107
  ];
108
108
  compositionContextErrorTestCases.forEach(({ name, setupFn }) => {
109
- it(`should throw error when ${name}`, () => {
109
+ it("should throw error when ".concat(name), () => {
110
110
  const TestComponent = defineComponent({
111
111
  setup() {
112
112
  setupFn();
@@ -147,7 +147,7 @@ describe("use.ts - Vue Router Integration", () => {
147
147
  }
148
148
  ];
149
149
  setupOnlyTestCases.forEach(({ name, fn, expectedError }) => {
150
- it(`should throw error when ${name}`, () => {
150
+ it("should throw error when ".concat(name), () => {
151
151
  expect(fn).toThrow(expectedError);
152
152
  });
153
153
  });
@@ -385,7 +385,7 @@ describe("use.ts - Vue Router Integration", () => {
385
385
  useProvideRouter(router);
386
386
  const route = useRoute();
387
387
  routeRef = route;
388
- return () => `<div>Current: ${route.path}</div>`;
388
+ return () => "<div>Current: ".concat(route.path, "</div>");
389
389
  }
390
390
  });
391
391
  const app = createApp(TestComponent);
@@ -1,3 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
4
  import { beforeEach, describe, expect, it } from "vitest";
2
5
  import { version } from "vue";
3
6
  import {
@@ -202,7 +205,7 @@ describe("util.ts - Utility Functions", () => {
202
205
  describe("should return null for falsy inputs", () => {
203
206
  const falsyValues = [null, void 0, false, 0, "", Number.NaN];
204
207
  falsyValues.forEach((value) => {
205
- it(`should return null for ${value}`, () => {
208
+ it("should return null for ".concat(value), () => {
206
209
  expect(resolveComponent(value)).toBeNull();
207
210
  });
208
211
  });
@@ -272,7 +275,9 @@ describe("util.ts - Utility Functions", () => {
272
275
  });
273
276
  it("should return class components directly", () => {
274
277
  class ClassComponent {
275
- name = "ClassComponent";
278
+ constructor() {
279
+ __publicField(this, "name", "ClassComponent");
280
+ }
276
281
  }
277
282
  const result = resolveComponent(ClassComponent);
278
283
  expect(result).toBe(ClassComponent);
package/package.json CHANGED
@@ -50,21 +50,21 @@
50
50
  "vue": "^3.5.0 || ^2.7.0"
51
51
  },
52
52
  "dependencies": {
53
- "@esmx/router": "3.0.0-rc.24"
53
+ "@esmx/router": "3.0.0-rc.26"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@biomejs/biome": "1.9.4",
57
- "@esmx/lint": "3.0.0-rc.24",
58
- "@types/node": "22.15.18",
59
- "@vitest/coverage-v8": "3.1.3",
60
- "stylelint": "16.19.1",
61
- "typescript": "5.8.2",
57
+ "@esmx/lint": "3.0.0-rc.26",
58
+ "@types/node": "^24.0.10",
59
+ "@vitest/coverage-v8": "3.2.4",
60
+ "stylelint": "16.21.0",
61
+ "typescript": "5.8.3",
62
62
  "unbuild": "3.5.0",
63
- "vitest": "3.1.3",
63
+ "vitest": "3.2.4",
64
64
  "vue": "3.5.13",
65
65
  "vue2": "npm:vue@2.7.16"
66
66
  },
67
- "version": "3.0.0-rc.24",
67
+ "version": "3.0.0-rc.26",
68
68
  "type": "module",
69
69
  "private": false,
70
70
  "exports": {
@@ -83,5 +83,5 @@
83
83
  "template",
84
84
  "public"
85
85
  ],
86
- "gitHead": "ef6208ea42a7105ca056ca9cd3c364d989c4d97d"
86
+ "gitHead": "2f52f94c8ac27aca32ef3148afd109d0f0470d52"
87
87
  }
@@ -77,7 +77,7 @@ export const RouterLink = defineComponent({
77
77
  type: { type: String as PropType<RouterLinkType>, default: 'push' },
78
78
  /**
79
79
  * @deprecated Use 'type="replace"' instead
80
- * @example replace={true} → type="replace"
80
+ * @example :replace={true} → type="replace"
81
81
  */
82
82
  replace: { type: Boolean, default: false },
83
83
  /**
@@ -113,36 +113,102 @@ export const RouterLink = defineComponent({
113
113
  * Only used when type='pushLayer'.
114
114
  * @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
115
115
  */
116
- layerOptions: { type: Object as PropType<RouteLayerOptions> }
116
+ layerOptions: { type: Object as PropType<RouteLayerOptions> },
117
+ /**
118
+ * Custom event handler to control navigation behavior.
119
+ * Should return `true` to allow router to navigate, otherwise to prevent it.
120
+ *
121
+ * @Note you need to call `e.preventDefault()` to prevent default browser navigation.
122
+ * @default
123
+ *
124
+ * (event: Event & Partial<MouseEvent>): boolean => {
125
+ * // don't redirect with control keys
126
+ * if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
127
+ * // don't redirect when preventDefault called
128
+ * if (e.defaultPrevented) return false;
129
+ * // don't redirect on right click
130
+ * if (e.button !== undefined && e.button !== 0) return false;
131
+ * // don't redirect if `target="_blank"`
132
+ * const target = e.currentTarget?.getAttribute?.('target') ?? '';
133
+ * if (/\b_blank\b/i.test(target)) return false;
134
+ * // Prevent default browser navigation to enable SPA routing
135
+ * // Note: this may be a Weex event which doesn't have this method
136
+ * if (e.preventDefault) e.preventDefault();
137
+ *
138
+ * return true;
139
+ * }
140
+ */
141
+ eventHandler: {
142
+ type: Function as PropType<
143
+ (event: Event) => boolean | undefined | void
144
+ >
145
+ }
117
146
  },
118
147
 
119
- setup(props, { slots }) {
148
+ setup(props, context) {
149
+ const { slots, attrs } = context;
120
150
  const link = useLink(props);
121
151
 
122
- return () => {
152
+ const wrapHandler = (
153
+ externalHandler: Function,
154
+ internalHandler: Function | undefined
155
+ ) =>
156
+ !internalHandler
157
+ ? (externalHandler as (e: Event) => Promise<void>)
158
+ : async (e: Event) => {
159
+ try {
160
+ await externalHandler(e);
161
+ } finally {
162
+ await internalHandler(e);
163
+ }
164
+ };
165
+
166
+ const vue3renderer = () => {
123
167
  const data = link.value;
168
+ const genEventName = (name: string): string =>
169
+ `on${name.charAt(0).toUpperCase()}${name.slice(1)}`;
124
170
 
125
- // Generate event handlers with proper type transformation for Vue 2/3 compatibility
126
- const eventHandlers = data.getEventHandlers(
127
- isVue3
128
- ? (name: string): string =>
129
- `on${name.charAt(0).toUpperCase()}${name.slice(1)}`
130
- : undefined
171
+ const eventHandlers = data.getEventHandlers(genEventName);
172
+ Object.entries(attrs).forEach(([key, listener]) => {
173
+ // In Vue 3, external event handlers are in attrs with 'on' prefix
174
+ if (!key.startsWith('on') || typeof listener !== 'function')
175
+ return;
176
+ eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
177
+ });
178
+
179
+ return h(
180
+ data.tag,
181
+ {
182
+ ...data.attributes,
183
+ ...eventHandlers
184
+ },
185
+ slots.default?.()
131
186
  );
187
+ };
132
188
 
133
- const props = {};
134
- if (isVue3) {
135
- Object.assign(props, data.attributes, eventHandlers);
136
- } else {
137
- const { class: className, ...attrs } = data.attributes;
138
- Object.assign(props, {
189
+ const vue2renderer = () => {
190
+ const data = link.value;
191
+
192
+ const eventHandlers = data.getEventHandlers();
193
+ // Vue 2: get external listeners from context
194
+ const $listeners = (context as any).listeners || {};
195
+ Object.entries($listeners).forEach(([key, listener]) => {
196
+ if (typeof listener !== 'function') return;
197
+ eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
198
+ });
199
+
200
+ const { class: className, ...attrs } = data.attributes;
201
+ return h(
202
+ data.tag,
203
+ {
139
204
  attrs,
140
205
  class: className,
141
206
  on: eventHandlers
142
- });
143
- }
144
-
145
- return h(data.tag, props, slots.default?.());
207
+ },
208
+ slots.default?.()
209
+ );
146
210
  };
211
+
212
+ return isVue3 ? vue3renderer : vue2renderer;
147
213
  }
148
214
  });