@esmx/router-vue 3.0.0-rc.25 → 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".concat(name.charAt(0).toUpperCase()).concat(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/package.json CHANGED
@@ -50,11 +50,11 @@
50
50
  "vue": "^3.5.0 || ^2.7.0"
51
51
  },
52
52
  "dependencies": {
53
- "@esmx/router": "3.0.0-rc.25"
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.25",
57
+ "@esmx/lint": "3.0.0-rc.26",
58
58
  "@types/node": "^24.0.10",
59
59
  "@vitest/coverage-v8": "3.2.4",
60
60
  "stylelint": "16.21.0",
@@ -64,7 +64,7 @@
64
64
  "vue": "3.5.13",
65
65
  "vue2": "npm:vue@2.7.16"
66
66
  },
67
- "version": "3.0.0-rc.25",
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": "da6576d7505ad48cb78905b18e17940d43510250"
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
  });