vue-intercept-plugin 1.0.0 → 1.0.2

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.
package/README.md CHANGED
@@ -1,28 +1,39 @@
1
1
  # vue-intercept-plugin
2
2
 
3
- [中文](README_ZH.md) | English
3
+ <div align="center">
4
4
 
5
- A lightweight Vue plugin providing the `v-intercept` custom directive for intercepting DOM events. Defaults to click, with support for any event type via directive arguments.
5
+ [中文](README_ZH.md) | **English**
6
6
 
7
- > The plugin does not handle permission checking it does one thing: intercepts an event and passes it to your function.
7
+ **Intercept DOM events with a single directiveclean and non-intrusive.**
8
8
 
9
- ## How It Works
9
+ > The plugin does **one thing only**: it catches an event and calls your function. Permission checking, validation, or any interception logic? That's entirely up to you.
10
10
 
11
- ### 🎯 Event Interception Mechanism
12
11
 
13
- 1. **Directive Parsing**: Resolves the `v-intercept` value at runtime, supporting both function and array formats
14
- 2. **Auto Binding**: Uses `addEventListener` to bind to the specified event type (default: click)
15
- 3. **Parameter Forwarding**: When using array syntax, the event object is automatically appended as the last argument
16
- 4. **Auto Cleanup**: Removes event listeners via `removeEventListener` on component unmount to prevent memory leaks
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## The Problem
17
+
18
+ You have a delete button. You want to check permissions before allowing the delete. The typical approach is:
19
+
20
+ ```html
21
+ <el-button @click="handleDelete">Delete</el-button>
22
+ ```
23
+
24
+ ```js
25
+ const handleDelete = () => {
26
+ if (!checkPermission('delete')) {
27
+ ElMessage.warning('No permission')
28
+ return
29
+ }
30
+ deleteApi()
31
+ }
32
+ ```
17
33
 
18
- ### 📋 Directive Format Reference
34
+ What if there are dozens of buttons, selects, and switches across your app? You end up repeating permission checks everywhere. This plugin lets you **signal visually** which actions need interception, and keeps your handler logic clean.
19
35
 
20
- | Syntax | Description |
21
- |--------|-------------|
22
- | `v-intercept="handleFn"` | Pass a function directly. Receives the event object automatically |
23
- | `v-intercept="[handleFn, arg1, arg2]"` | Array syntax. First item is the function, rest are arguments. **Event object auto-appended to the end** |
24
- | `v-intercept:change="handleFn"` | Specify event type via directive argument. Defaults to `click` |
25
- | `v-intercept:change="[handleFn, arg1]"` | Custom event + array arguments, work together |
36
+ ---
26
37
 
27
38
  ## Installation
28
39
 
@@ -30,18 +41,18 @@ A lightweight Vue plugin providing the `v-intercept` custom directive for interc
30
41
  npm install vue-intercept-plugin
31
42
  ```
32
43
 
33
- ## Quick Start
44
+ ## Register the Plugin
34
45
 
35
- ```typescript
46
+ ```ts
47
+ // Vue 3
36
48
  import { createApp } from 'vue'
37
49
  import VueInterceptPlugin from 'vue-intercept-plugin'
38
50
 
39
- // Register plugin (no configuration needed)
40
51
  const app = createApp(App)
41
52
  app.use(VueInterceptPlugin)
42
53
  ```
43
54
 
44
- ```typescript
55
+ ```ts
45
56
  // Vue 2
46
57
  import Vue from 'vue'
47
58
  import VueInterceptPlugin from 'vue-intercept-plugin'
@@ -49,17 +60,25 @@ import VueInterceptPlugin from 'vue-intercept-plugin'
49
60
  Vue.use(VueInterceptPlugin)
50
61
  ```
51
62
 
52
- ```vue
63
+ That's it. No configuration needed.
64
+
65
+ ---
66
+
67
+ ## Quick Start
68
+
69
+ Here's a complete example with both template and script:
70
+
71
+ ```html
53
72
  <template>
54
- <!-- No arguments: pass function directly -->
73
+ <!-- Direct function: intercepts click by default -->
55
74
  <el-button v-intercept="handleDelete" type="danger">Delete</el-button>
56
75
 
57
- <!-- Array syntax: [function, arg1, arg2, ...] -->
76
+ <!-- Array syntax: extra arguments, event auto-appended -->
58
77
  <el-button v-intercept="[handleDeleteById, 1001]" type="warning">
59
78
  Delete Order #1001
60
79
  </el-button>
61
80
 
62
- <!-- Custom event type via directive argument -->
81
+ <!-- Custom event type: intercepts "change" instead of "click" -->
63
82
  <select v-intercept:change="handleChange">
64
83
  <option value="">Select...</option>
65
84
  <option value="1">Option 1</option>
@@ -68,95 +87,126 @@ Vue.use(VueInterceptPlugin)
68
87
  </template>
69
88
 
70
89
  <script setup lang="ts">
71
- const handleDelete = () => {
72
- if (!hasPermission) {
90
+ // Called as: handleDelete(event)
91
+ // You check permission inside — if not allowed, just return
92
+ const handleDelete = (event: MouseEvent) => {
93
+ if (!checkPermission('delete')) {
73
94
  ElMessage.warning('No permission')
74
- return // Intercept
95
+ return // ← intercepted
75
96
  }
76
- // Execute delete logic
97
+ deleteApi()
77
98
  }
78
99
 
100
+ // ② Called as: handleDeleteById(1001, event)
101
+ // The plugin appends the event object as the last argument automatically
79
102
  const handleDeleteById = (id: number, event: MouseEvent) => {
80
- if (!hasPermission) return
103
+ if (!checkPermission('delete')) return
81
104
  deleteApi(id)
82
105
  }
83
106
 
107
+ // ③ Called as: handleChange(event)
84
108
  const handleChange = (event: Event) => {
85
- if (!canChange) return
109
+ if (!checkPermission('change')) return
86
110
  const value = (event.target as HTMLSelectElement).value
87
- // Handle selection logic
111
+ // handle selection...
88
112
  }
89
113
  </script>
90
114
  ```
91
115
 
92
- ## API
116
+ > **Key idea**: The plugin does not block anything. It simply calls your function. **You** decide whether to proceed or return early.
93
117
 
94
- ### `app.use(VueInterceptPlugin)`
118
+ ---
95
119
 
96
- Registers the plugin. No arguments required. Internally auto-detects the Vue version (Vue 3 uses `mounted`/`updated`/`unmounted` hooks; Vue 2 uses `bind`/`update`/`unbind` hooks).
120
+ ## Directive Syntax Reference
97
121
 
98
- ### v-intercept Directive
122
+ ### Event Type (default: `click`)
99
123
 
100
- **Directive Argument (Event Type):**
124
+ | Syntax | Fires on |
125
+ |--------|----------|
126
+ | `v-intercept="handler"` | `click` (default) |
127
+ | `v-intercept:click="handler"` | click |
128
+ | `v-intercept:change="handler"` | change |
129
+ | `v-intercept:submit="handler"` | form submit |
130
+ | `v-intercept:contextmenu="handler"` | right-click |
131
+ | `v-intercept:dblclick="handler"` | double-click |
132
+ | `v-intercept:dragstart="handler"` | drag start |
133
+ | `v-intercept:copy="handler"` | copy |
134
+ | `v-intercept:<any-event>="handler"` | any native DOM event |
101
135
 
102
- | Syntax | Description |
103
- |--------|-------------|
104
- | `v-intercept="handler"` | Default: click |
105
- | `v-intercept:click="handler"` | Explicit click |
106
- | `v-intercept:change="handler"` | change event |
107
- | `v-intercept:submit="handler"` | Form submit |
108
- | `v-intercept:contextmenu="handler"` | Right-click menu |
109
- | `v-intercept:dblclick="handler"` | Double-click |
110
- | `v-intercept:dragstart="handler"` | Drag start |
111
- | `v-intercept:copy="handler"` | Copy |
112
- | `...` | Any native DOM event |
136
+ ### Handler Value
113
137
 
114
- **Directive Value (Handler):**
138
+ | Format | Example | Your function receives |
139
+ |--------|---------|----------------------|
140
+ | `handler` | `v-intercept="handleDelete"` | `handleDelete(event)` |
141
+ | `[handler, ...args]` | `v-intercept="[handleDelete, 1001]"` | `handleDelete(1001, event)` |
142
+ | `[handler, ...args]` | `v-intercept="[handleDelete, 1001, 'abc']"` | `handleDelete(1001, 'abc', event)` |
143
+ | `[handler]` | `v-intercept="[handleDelete]"` | `handleDelete(event)` (same as direct) |
115
144
 
116
- | Format | Example | Description |
117
- |--------|---------|-------------|
118
- | `handler` | `v-intercept="handleDelete"` | Pass function directly. Receives event object |
119
- | `[handler, ...args]` | `v-intercept="[handleDelete, 1001]"` | Array syntax. **Event object auto-appended to arguments** |
120
- | `[handler]` | `v-intercept="[handleDelete]"` | Function only, no extra args. Equivalent to passing function directly |
145
+ > **Rule**: The event object is **always appended as the last argument** when using array syntax.
121
146
 
122
- **Function Signature Reference:**
147
+ ---
123
148
 
124
- | Template Syntax | Actual Arguments Received |
125
- |----------------|-------------------------|
126
- | `v-intercept="handle"` | `handle(event)` |
127
- | `v-intercept="[handle, id]"` | `handle(id, event)` |
128
- | `v-intercept="[handle, id, name]"` | `handle(id, name, event)` |
129
- | `v-intercept:change="handle"` | `handle(event)` |
130
- | `v-intercept:change="[handle, id]"` | `handle(id, event)` |
149
+ ## Common Scenarios
131
150
 
132
- ## Advanced Usage
151
+ > Examples below use **Element Plus** components (`el-button`, `el-select`, `el-switch`, etc.). This plugin has no UI library dependency — native `<button>`, `<select>`, etc. work just as well.
133
152
 
134
- ### Permission-Guarded Buttons
153
+ ### Permission-Guarded Button
135
154
 
136
- ```vue
137
- <el-button v-intercept="handleDelete" type="danger">Delete</el-button>
138
- <el-button v-intercept="[handleApprove, 'order_001']" type="primary">Approve</el-button>
155
+ ```html
156
+ <!-- Element Plus component -->
157
+ <el-button v-intercept="handleApprove" type="primary">Approve</el-button>
139
158
  ```
140
159
 
141
- ```typescript
142
- const handleDelete = () => {
143
- if (!userHasPermission('delete')) {
144
- ElMessage.warning('No permission to delete')
160
+ ```ts
161
+ const handleApprove = (event: MouseEvent) => {
162
+ if (!userHasRole('admin')) {
163
+ ElMessage.warning('Admins only')
145
164
  return
146
165
  }
147
- deleteApi()
166
+ approveApi()
167
+ }
168
+ ```
169
+
170
+ ### Permission-Guarded Button with Extra Params
171
+
172
+ ```html
173
+ <el-button v-intercept="[handleApproveById, orderId]" type="primary">
174
+ Approve
175
+ </el-button>
176
+ <!-- Element Plus component, same as above -->
177
+ ```
178
+
179
+ ```ts
180
+ const handleApproveById = (id: string, event: MouseEvent) => {
181
+ if (!userHasRole('admin')) return
182
+ approveApi(id)
183
+ }
184
+ ```
185
+
186
+ ### Double-Click Edit Guard
187
+
188
+ ```html
189
+ <div v-intercept:dblclick="[handleEdit, item.id]">
190
+ {{ item.name }}
191
+ </div>
192
+ ```
193
+
194
+ ```ts
195
+ const handleEdit = (id: number, event: MouseEvent) => {
196
+ if (!canEdit) return
197
+ enterEditMode(id)
148
198
  }
149
199
  ```
150
200
 
151
201
  ### Right-Click Menu Guard
152
202
 
153
- ```vue
154
- <div v-intercept:contextmenu="handleContextMenu" class="table-row">
155
- Right-click this area
203
+ ```html
204
+ <div v-intercept:contextmenu="handleContextMenu">
205
+ Right-click here
156
206
  </div>
157
207
  ```
158
208
 
159
- ```typescript
209
+ ```ts
160
210
  const handleContextMenu = (event: MouseEvent) => {
161
211
  event.preventDefault()
162
212
  if (!hasRightClickPermission) return
@@ -166,18 +216,18 @@ const handleContextMenu = (event: MouseEvent) => {
166
216
 
167
217
  ### Select Change Guard
168
218
 
169
- ```vue
170
- <el-select v-intercept:change="[handleRoleChange, 'User Management']" placeholder="Select role">
219
+ ```html
220
+ <!-- Element Plus components -->
221
+ <el-select v-intercept:change="[handleRoleChange, 'User Mgmt']">
171
222
  <el-option label="Admin" value="admin" />
172
223
  <el-option label="Editor" value="editor" />
173
- <el-option label="Guest" value="guest" />
174
224
  </el-select>
175
225
  ```
176
226
 
177
- ```typescript
227
+ ```ts
178
228
  const handleRoleChange = (section: string, event: Event) => {
179
229
  if (!canChangeRole) {
180
- ElMessage.warning(`Cannot change role for 「${section}」`)
230
+ ElMessage.warning(`Cannot change role for ${section}`)
181
231
  return
182
232
  }
183
233
  updateRole()
@@ -186,108 +236,76 @@ const handleRoleChange = (section: string, event: Event) => {
186
236
 
187
237
  ### Toggle Switch Guard
188
238
 
189
- ```vue
190
- <el-switch v-intercept:change="[handleFeatureToggle, 'Export']" />
239
+ ```html
240
+ <!-- Element Plus component -->
241
+ <el-switch v-intercept:change="[handleToggle, 'Export']" />
191
242
  ```
192
243
 
193
- ```typescript
194
- const handleFeatureToggle = (feature: string, event: Event) => {
244
+ ```ts
245
+ const handleToggle = (feature: string, event: Event) => {
195
246
  const checked = (event.target as HTMLInputElement).checked
196
247
  if (checked && !canEnableFeature(feature)) {
197
- ElMessage.warning(`Cannot enable 「${feature}」`)
248
+ ElMessage.warning(`Cannot enable ${feature}`)
198
249
  return
199
250
  }
200
251
  toggleFeature(feature, checked)
201
252
  }
202
253
  ```
203
254
 
204
- ### Double-Click Edit Guard
205
-
206
- ```vue
207
- <div v-intercept:dblclick="[handleDoubleClick, item.id]">
208
- {{ item.name }}
209
- </div>
210
- ```
211
-
212
- ```typescript
213
- const handleDoubleClick = (id: number, event: MouseEvent) => {
214
- if (!canEdit) return
215
- enterEditMode(id)
216
- }
217
- ```
218
-
219
255
  ### Drag Guard
220
256
 
221
- ```vue
222
- <div v-intercept:dragstart="[handleDragStart, file.name]" draggable="true">
257
+ ```html
258
+ <div v-intercept:dragstart="[handleDrag, file.name]" draggable="true">
223
259
  {{ file.name }}
224
260
  </div>
225
261
  ```
226
262
 
227
- ```typescript
228
- const handleDragStart = (fileName: string, event: DragEvent) => {
263
+ ```ts
264
+ const handleDrag = (name: string, event: DragEvent) => {
229
265
  if (!canDrag) {
230
266
  event.preventDefault()
231
267
  return
232
268
  }
233
- event.dataTransfer?.setData('text/plain', fileName)
269
+ event.dataTransfer?.setData('text/plain', name)
234
270
  }
235
271
  ```
236
272
 
237
- ## Build Artifacts
238
-
239
- | File | Format | Description |
240
- |------|--------|-------------|
241
- | `dist/vue-intercept-plugin.cjs.js` | CJS | CommonJS for Node.js require |
242
- | `dist/vue-intercept-plugin.esm.js` | ESM | ES Module, Tree-shakable |
273
+ ---
243
274
 
244
275
  ## Technical Details
245
276
 
246
- ### Why Not Wrap Components?
277
+ ### Why a Directive, Not a Wrapper Component?
247
278
 
248
- When using third-party UI libraries like Element Plus, wrapping every permission-sensitive component (e.g., `PermissionButton`) leads to:
249
- - High wrapping cost requires handling extensive prop forwarding
250
- - Poor coverage hard to cover all UI components that need permission control (buttons, selects, switches, etc.)
251
- - Duplicated effort different UI libraries need separate wrappers
279
+ If you wrap `el-button` into a `<PermissionButton>`, you have to:
280
+ - Forward every prop (size, type, icon, loading, disabled, ...)
281
+ - Do the same for `<el-select>`, `<el-switch>`, `<el-input>`, etc.
282
+ - Repeat the work for every UI library
252
283
 
253
- Vue custom directives operate directly on native DOM events — zero component wrapping, one line of code.
284
+ A **Vue custom directive** hooks directly into the native DOM event — zero wrapping, one line of code.
254
285
 
255
- ### Vue 2 / Vue 3 Compatibility
286
+ ### Vue 2 / Vue 3 Auto-Detection
256
287
 
257
- The plugin auto-detects the Vue version via `app.version` and selects the appropriate directive hooks:
258
-
259
- | Vue Version | Bind Hook | Update Hook | Unbind Hook |
260
- |-------------|-----------|-------------|-------------|
288
+ | Vue | Bind Hook | Update Hook | Unbind Hook |
289
+ |-----|-----------|-------------|-------------|
261
290
  | Vue 3 | `mounted` | `updated` | `unmounted` |
262
291
  | Vue 2 | `bind` | `update` | `unbind` |
263
292
 
264
293
  ### Memory Safety
265
294
 
266
- - Old listeners are removed before re-binding to prevent duplicate handlers
267
- - Listeners are automatically removed on component unmount, references are cleaned up
268
- - Different event types use separate storage keys (`_intcpt_click` / `_intcpt_change`), keeping them isolated
269
-
270
- ## Development
271
-
272
- ```bash
273
- # Install dependencies
274
- npm install
295
+ - Old listeners are removed before re-binding (prevents duplicates)
296
+ - Listeners are cleaned up on component unmount
297
+ - Different event types use separate storage (isolated, no collisions)
275
298
 
276
- # Build
277
- npm run build
299
+ ---
278
300
 
279
- # Run tests
280
- npm test
281
- ```
282
-
283
- ## Testing
301
+ ## Build Artifacts
284
302
 
285
- Uses Vitest + jsdom with 28 test cases covering:
303
+ | File | Format |
304
+ |------|--------|
305
+ | `dist/vue-intercept-plugin.cjs.js` | CommonJS |
306
+ | `dist/vue-intercept-plugin.esm.js` | ES Module (tree-shakable) |
286
307
 
287
- - `resolveHandler` — direct function passthrough, array argument forwarding with event auto-append, multiple arguments, invalid value warnings, null/undefined edge cases
288
- - `bindHandler / unbindHandler` — click binding, change binding, auto-replacement on re-bind, unbind cleanup, event type isolation, contextmenu/dblclick special events
289
- - Plugin install — Vue3/Vue2 directive registration, correct hook names, mounted event binding, custom event types, array arguments with event auto-append, unmounted cleanup, graceful handling of invalid values
290
308
 
291
309
  ## License
292
310
 
293
- MIT
311
+ MIT
@@ -48,12 +48,12 @@ function resolveHandler(value) {
48
48
  if (Array.isArray(value)) {
49
49
  const [fn, ...args] = value;
50
50
  if (typeof fn !== 'function') {
51
- console.warn('[vue-intercept-plugin] 数组第一项必须是一个函数');
51
+ console.warn('[vue-intercept-plugin] The first item in the array must be a function');
52
52
  return null;
53
53
  }
54
54
  return (event) => { fn(...args, event); };
55
55
  }
56
- console.warn('[vue-intercept-plugin] v-intercept 的值必须是函数或 [函数, ...参数] 数组');
56
+ console.warn('[vue-intercept-plugin] v-intercept value must be a function or [function, ...args] array');
57
57
  return null;
58
58
  }
59
59
  /**
@@ -44,12 +44,12 @@ function resolveHandler(value) {
44
44
  if (Array.isArray(value)) {
45
45
  const [fn, ...args] = value;
46
46
  if (typeof fn !== 'function') {
47
- console.warn('[vue-intercept-plugin] 数组第一项必须是一个函数');
47
+ console.warn('[vue-intercept-plugin] The first item in the array must be a function');
48
48
  return null;
49
49
  }
50
50
  return (event) => { fn(...args, event); };
51
51
  }
52
- console.warn('[vue-intercept-plugin] v-intercept 的值必须是函数或 [函数, ...参数] 数组');
52
+ console.warn('[vue-intercept-plugin] v-intercept value must be a function or [function, ...args] array');
53
53
  return null;
54
54
  }
55
55
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-intercept-plugin",
3
- "version": "1.0.0",
4
- "description": "v-intercept 自定义指令,纯点击拦截,兼容 Vue 2 & Vue 3",
3
+ "version": "1.0.2",
4
+ "description": "A lightweight Vue plugin providing v-intercept custom directive — intercept DOM events with a single line of code, supports any event type, compatible with Vue 2 & Vue 3.",
5
5
  "main": "dist/vue-intercept-plugin.cjs.js",
6
6
  "module": "dist/vue-intercept-plugin.esm.js",
7
7
  "files": [