vuse-directive 0.0.3 → 1.0.0

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,6 +1,6 @@
1
- # vue-directive
1
+ # vuse-directive
2
2
 
3
- A collection of Vue 3 custom directives
3
+ A collection of Vue 3 custom directives.
4
4
 
5
5
  ## Installation
6
6
 
@@ -14,101 +14,104 @@ yarn add vuse-directive
14
14
 
15
15
  ## Getting Started
16
16
 
17
- ### Global Registration
17
+ ### Plugin (register all directives globally)
18
18
 
19
19
  ```ts
20
20
  import { createApp } from 'vue'
21
21
  import App from './App.vue'
22
- import { throttleClick } from 'vuse-directive'
22
+ import VuseDirective from 'vuse-directive'
23
+
24
+ const app = createApp(App)
25
+ app.use(VuseDirective)
26
+ app.mount('#app')
27
+ ```
28
+
29
+ ### Single directive
30
+
31
+ ```ts
32
+ import { createApp } from 'vue'
33
+ import App from './App.vue'
34
+ import { throttleClick, debounceClick } from 'vuse-directive'
23
35
 
24
36
  const app = createApp(App)
25
37
  app.directive('throttle-click', throttleClick)
38
+ app.directive('debounce-click', debounceClick)
26
39
  app.mount('#app')
27
40
  ```
28
41
 
29
- ### On-demand Import
42
+ ### On-demand import (local registration)
30
43
 
31
44
  ```ts
32
- import { throttleClick } from 'vuse-directive'
45
+ import { throttleClick, debounceClick } from 'vuse-directive'
33
46
  ```
34
47
 
35
48
  ---
36
49
 
37
50
  ## Directives
38
51
 
39
- ### `v-throttle-click` — Throttled Click
40
52
 
41
- Throttles click events to prevent repeated triggers within a short period. Supports custom delay, async lock, trailing call, and Vue modifier pass-through.
53
+ | Directive | Description | Docs |
54
+ | ------------------ | ------------------------------------------------------------------------------ | ---- |
55
+ | `v-throttle-click` | Throttle click events — fires immediately, then locks for a cooldown period | |
56
+ | `v-debounce-click` | Debounce click events — fires after the user stops clicking for a set duration | |
57
+
58
+
59
+ ---
60
+
61
+ ## `v-throttle-click`
42
62
 
43
- #### Basic Usage
63
+ Fires immediately on click, then ignores subsequent clicks until the cooldown ends.
44
64
 
45
65
  ```vue
46
- <!-- Default throttle interval: 300ms -->
66
+ <!-- Default: 300ms cooldown -->
47
67
  <button v-throttle-click="handleClick">Submit</button>
48
68
 
49
- <!-- Custom interval (in ms) -->
50
- <button v-throttle-click:500="handleClick">Submit</button>
69
+ <!-- Custom cooldown -->
70
+ <button v-throttle-click:1000="handleClick">Submit</button>
51
71
 
52
- <!-- Interval 0: no throttle, modifiers still apply -->
53
- <button v-throttle-click:0="handleClick">Submit</button>
72
+ <!-- Async lock: blocks until the returned Promise settles -->
73
+ <button v-throttle-click.async="submitForm">Submit</button>
74
+
75
+ <!-- Async + trailing: re-fires the last blocked click after the Promise settles -->
76
+ <button v-throttle-click.async.trailing="submitForm">Submit</button>
54
77
  ```
55
78
 
56
- #### Modifiers
79
+ **Key modifiers:** `.once` · `.trailing` · `.async` · `.right` · `.stop` · `.prevent`
80
+
81
+ ---
57
82
 
58
- | Modifier | Description |
59
- |----------|-------------|
60
- | `.once` | Fire only once; all subsequent clicks are ignored |
61
- | `.trailing` | If a click is blocked during cooldown, it fires once after the cooldown ends |
62
- | `.async` | Async mode: new clicks are blocked until the previous callback's Promise resolves/rejects |
63
- | `.right` | Listen to `contextmenu` (right-click) instead of `click` |
64
- | `.capture` | Use capture phase for the event listener |
65
- | `.passive` | Use passive listener mode for better scroll performance |
66
- | `.stop` | Stop event propagation (passed to Vue's `withModifiers`) |
67
- | `.prevent` | Prevent default behavior (passed to Vue's `withModifiers`) |
68
- | `.self` | Only trigger when the event target is the element itself |
83
+ ## `v-debounce-click`
69
84
 
70
- #### Examples
85
+ Fires after the user stops clicking for the specified delay (trailing by default).
71
86
 
72
87
  ```vue
73
- <script setup lang="ts">
74
- async function submitForm() {
75
- await fetch('/api/submit', { method: 'POST' })
76
- }
77
- </script>
78
-
79
- <template>
80
- <!-- Async + trailing: blocks repeated clicks during async call,
81
- fires the last blocked click after the call completes -->
82
- <button v-throttle-click:1000.async.trailing="submitForm">
83
- Submit
84
- </button>
85
-
86
- <!-- Fire once only -->
87
- <button v-throttle-click.once="handleClick">Click once</button>
88
-
89
- <!-- Throttle right-click -->
90
- <div v-throttle-click.right="openMenu">Right-click area</div>
91
- </template>
92
- ```
88
+ <!-- Default: fires 300ms after the last click -->
89
+ <button v-debounce-click="handleClick">Search</button>
93
90
 
94
- #### Callback Signature
91
+ <!-- Custom delay -->
92
+ <button v-debounce-click:500="handleClick">Search</button>
95
93
 
96
- ```ts
97
- type ClickHandler = (event: MouseEvent, ...args: unknown[]) => any
94
+ <!-- Leading: fires immediately on first click, trailing if more clicks follow -->
95
+ <button v-debounce-click.leading="handleClick">Search</button>
98
96
  ```
99
97
 
100
- The binding value receives a `MouseEvent` as the first argument:
98
+ **Key modifiers:** `.once` · `.leading` · `.right` · `.stop` · `.prevent`
101
99
 
102
- ```ts
103
- function handleClick(e: MouseEvent) {
104
- console.log('clicked', e)
105
- }
106
- ```
100
+ ### Max-wait cap
107
101
 
108
- In async mode, return a `Promise` to activate the async lock:
102
+ By default there is no max-wait limit. Use `makeDebounceClick` to guarantee the handler fires within a fixed window even during continuous clicks:
109
103
 
110
104
  ```ts
111
- async function handleAsync(e: MouseEvent) {
112
- await fetch('/api/submit', { method: 'POST' })
113
- }
105
+ import { makeDebounceClick } from 'vuse-directive'
106
+
107
+ // Fires at most every 3000ms even if clicks never stop
108
+ app.directive('debounce-click', makeDebounceClick(3000))
114
109
  ```
110
+
111
+ ---
112
+
113
+ ## Issues
114
+
115
+ Found a bug or have a suggestion? Feel free to send an email to:
116
+
117
+ 📮 stafanhulk@gmail.com
@@ -0,0 +1,11 @@
1
+ import { Directive } from 'vue';
2
+
3
+ /**
4
+ * 创建防抖点击指令
5
+ * @param maxWait - 防抖最长等待时间,超过则强制触发一次;小于等于0时禁用该特性
6
+ * @returns
7
+ */
8
+ declare const makeDebounceClick: (maxWait: number) => Directive;
9
+ export { makeDebounceClick };
10
+ declare const debounceClickDirective: Directive;
11
+ export default debounceClickDirective;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import { App } from 'vue';
2
+ import { default as debounceClick, makeDebounceClick } from './directives/DebounceClick';
1
3
  import { default as throttleClick } from './directives/ThrottleClick';
2
4
 
3
- export { throttleClick };
5
+ export { throttleClick, debounceClick, makeDebounceClick };
6
+ declare const _default: {
7
+ install: (app: App) => void;
8
+ };
9
+ export default _default;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 兼容性处理,Symbol 在 IE 中不支持,使用字符串代替
3
+ * @param directiveName - 指令名称
4
+ * @returns - 指令唯一键
5
+ */
6
+ export declare const getDirectiveKey: (directiveName: string) => string | symbol;
@@ -1,63 +1,157 @@
1
- import { withModifiers as d } from "vue";
2
- const f = {
3
- mounted(t, e) {
4
- t._latestBinding = e, t._throttleTimer = null, t._hasCalledOnce = !1, t._trailingEvent = null, t._asyncPending = !1, t._unmounted = !1;
5
- const s = Object.getOwnPropertyNames(
6
- e.modifiers
1
+ import { withModifiers as f } from "vue";
2
+ const v = (r) => typeof Symbol == "function" ? Symbol(r) : `__${r}__`, d = v("debounceClick"), g = (r) => {
3
+ const n = r;
4
+ return {
5
+ mounted(s, i) {
6
+ s[d] = {
7
+ _latestBinding: i,
8
+ _debounceTimer: null,
9
+ _maxWaitTimer: null,
10
+ _hasCalledOnce: !1,
11
+ _leadingFired: !1,
12
+ _waiting: !1,
13
+ _lastEvent: null,
14
+ _eventName: "click",
15
+ _listenerOptions: {},
16
+ _debounceHandler: () => {
17
+ }
18
+ };
19
+ const e = s[d], a = Object.getOwnPropertyNames(
20
+ i.modifiers
21
+ );
22
+ e._debounceHandler = f((l) => {
23
+ const { modifiers: o, value: _ } = e._latestBinding;
24
+ if (o.once && e._hasCalledOnce)
25
+ return;
26
+ const u = typeof e._latestBinding.arg > "u" ? 300 : Number(e._latestBinding.arg);
27
+ e._lastEvent = l;
28
+ const c = (p) => {
29
+ e._hasCalledOnce = !0, _ == null || _(p);
30
+ };
31
+ if (u === 0) {
32
+ c(l);
33
+ return;
34
+ }
35
+ o.leading && !e._leadingFired ? (e._leadingFired = !0, c(l)) : e._waiting = !0, e._debounceTimer && clearTimeout(e._debounceTimer), n > 0 && e._maxWaitTimer === null && (e._maxWaitTimer = setTimeout(() => {
36
+ e._maxWaitTimer = null, e._debounceTimer && (clearTimeout(e._debounceTimer), e._debounceTimer = null), e._waiting && e._lastEvent && (e._waiting = !1, c(e._lastEvent)), e._leadingFired = !1;
37
+ }, n)), e._debounceTimer = setTimeout(() => {
38
+ e._debounceTimer = null, e._maxWaitTimer && (clearTimeout(e._maxWaitTimer), e._maxWaitTimer = null), e._waiting && (e._waiting = !1, c(l)), e._leadingFired = !1;
39
+ }, u);
40
+ }, a), i.modifiers.right && (e._eventName = "contextmenu"), e._listenerOptions = {
41
+ capture: i.modifiers.capture ?? !1,
42
+ passive: i.modifiers.passive ?? !1
43
+ }, s.addEventListener(
44
+ e._eventName,
45
+ e._debounceHandler,
46
+ e._listenerOptions
47
+ );
48
+ },
49
+ updated(s, i) {
50
+ const e = s[d], a = i.modifiers.capture ?? !1, l = i.modifiers.passive ?? !1, o = e._listenerOptions;
51
+ (a !== o.capture || l !== o.passive) && (s.removeEventListener(
52
+ e._eventName,
53
+ e._debounceHandler,
54
+ o
55
+ ), e._listenerOptions = {
56
+ capture: a,
57
+ passive: l
58
+ }, s.addEventListener(
59
+ e._eventName,
60
+ e._debounceHandler,
61
+ e._listenerOptions
62
+ )), e._latestBinding = i;
63
+ },
64
+ unmounted(s) {
65
+ const i = s[d];
66
+ s.removeEventListener(
67
+ i._eventName,
68
+ i._debounceHandler,
69
+ i._listenerOptions
70
+ ), i._debounceTimer && clearTimeout(i._debounceTimer), i._maxWaitTimer && clearTimeout(i._maxWaitTimer);
71
+ }
72
+ };
73
+ }, T = g(-1), m = v("throttleClick"), h = {
74
+ mounted(r, n) {
75
+ r[m] = {
76
+ _latestBinding: n,
77
+ _throttleTimer: null,
78
+ _hasCalledOnce: !1,
79
+ _trailingEvent: null,
80
+ _asyncPending: !1,
81
+ _unmounted: !1,
82
+ _eventName: "click",
83
+ _listenerOptions: {},
84
+ _throttleHandler: () => {
85
+ }
86
+ };
87
+ const t = r[m], s = Object.getOwnPropertyNames(
88
+ n.modifiers
7
89
  );
8
- t._throttleHandler = d((i) => {
9
- const { modifiers: n, value: r } = t._latestBinding;
10
- if (n.once && t._hasCalledOnce)
90
+ t._throttleHandler = f((i) => {
91
+ const { modifiers: e, value: a } = t._latestBinding;
92
+ if (e.once && t._hasCalledOnce)
11
93
  return;
12
- if (n.async && t._asyncPending) {
13
- n.trailing && (t._trailingEvent = i);
94
+ if (e.async && t._asyncPending) {
95
+ e.trailing && (t._trailingEvent = i);
14
96
  return;
15
97
  }
16
- const a = typeof t._latestBinding.arg > "u" ? 300 : Number(t._latestBinding.arg);
17
- if (a > 0 && t._throttleTimer) {
18
- n.trailing && (t._trailingEvent = i);
98
+ const l = typeof t._latestBinding.arg > "u" ? 300 : Number(t._latestBinding.arg);
99
+ if (l > 0 && t._throttleTimer) {
100
+ e.trailing && (t._trailingEvent = i);
19
101
  return;
20
102
  }
21
103
  const o = (_) => {
22
- t._hasCalledOnce = !0, t._trailingEvent = null, n.async ? (t._asyncPending = !0, Promise.resolve(r == null ? void 0 : r(_)).finally(() => {
23
- t._unmounted || (t._asyncPending = !1, n.trailing && t._trailingEvent && t._throttleHandler(t._trailingEvent));
24
- })) : r == null || r(_);
104
+ t._hasCalledOnce = !0, t._trailingEvent = null, e.async ? (t._asyncPending = !0, Promise.resolve(a == null ? void 0 : a(_)).finally(() => {
105
+ t._asyncPending = !1, !t._unmounted && e.trailing && t._trailingEvent && t._throttleHandler(t._trailingEvent);
106
+ })) : a == null || a(_);
25
107
  };
26
- if (a === 0) {
108
+ if (l === 0) {
27
109
  o(i);
28
110
  return;
29
111
  }
30
112
  o(i), t._throttleTimer = setTimeout(() => {
31
- t._throttleTimer = null, !n.async && n.trailing && t._trailingEvent && (t._throttleHandler(t._trailingEvent), t._trailingEvent = null);
32
- }, a);
33
- }, s), t._eventName = e.modifiers.right ? "contextmenu" : "click", t._listenerOptions = {
34
- capture: e.modifiers.capture ?? !1,
35
- passive: e.modifiers.passive ?? !1
36
- }, t.addEventListener(
113
+ t._throttleTimer = null, !e.async && e.trailing && t._trailingEvent && (t._throttleHandler(t._trailingEvent), t._trailingEvent = null);
114
+ }, l);
115
+ }, s), t._eventName = n.modifiers.right ? "contextmenu" : "click", t._listenerOptions = {
116
+ capture: n.modifiers.capture ?? !1,
117
+ passive: n.modifiers.passive ?? !1
118
+ }, r.addEventListener(
37
119
  t._eventName,
38
120
  t._throttleHandler,
39
121
  t._listenerOptions
40
122
  );
41
123
  },
42
- updated(t, e) {
43
- const s = e.modifiers.capture ?? !1, i = e.modifiers.passive ?? !1, n = t._listenerOptions;
44
- (s !== n.capture || i !== n.passive) && (t.removeEventListener(t._eventName, t._throttleHandler, n), t._listenerOptions = {
124
+ updated(r, n) {
125
+ const t = r[m], s = n.modifiers.capture ?? !1, i = n.modifiers.passive ?? !1, e = t._listenerOptions;
126
+ (s !== e.capture || i !== e.passive) && (r.removeEventListener(
127
+ t._eventName,
128
+ t._throttleHandler,
129
+ e
130
+ ), t._listenerOptions = {
45
131
  capture: s,
46
132
  passive: i
47
- }, t.addEventListener(
133
+ }, r.addEventListener(
48
134
  t._eventName,
49
135
  t._throttleHandler,
50
136
  t._listenerOptions
51
- )), t._latestBinding = e;
137
+ )), t._latestBinding = n;
52
138
  },
53
- unmounted(t) {
54
- t.removeEventListener(
55
- t._eventName,
56
- t._throttleHandler,
57
- t._listenerOptions
58
- ), t._throttleTimer && clearTimeout(t._throttleTimer), t._unmounted = !0, t._trailingEvent = null;
139
+ unmounted(r) {
140
+ const n = r[m];
141
+ r.removeEventListener(
142
+ n._eventName,
143
+ n._throttleHandler,
144
+ n._listenerOptions
145
+ ), n._throttleTimer && clearTimeout(n._throttleTimer), n._unmounted = !0, n._trailingEvent = null;
146
+ }
147
+ }, E = {
148
+ install: (r) => {
149
+ r.directive("throttle-click", h), r.directive("debounce-click", T);
59
150
  }
60
151
  };
61
152
  export {
62
- f as throttleClick
153
+ T as debounceClick,
154
+ E as default,
155
+ g as makeDebounceClick,
156
+ h as throttleClick
63
157
  };
@@ -1 +1 @@
1
- (function(i,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],a):(i=typeof globalThis<"u"?globalThis:i||self,a(i.VueDirective={},i.Vue))})(this,function(i,a){"use strict";const f={mounted(t,n){t._latestBinding=n,t._throttleTimer=null,t._hasCalledOnce=!1,t._trailingEvent=null,t._asyncPending=!1,t._unmounted=!1;const o=Object.getOwnPropertyNames(n.modifiers);t._throttleHandler=a.withModifiers(r=>{const{modifiers:e,value:s}=t._latestBinding;if(e.once&&t._hasCalledOnce)return;if(e.async&&t._asyncPending){e.trailing&&(t._trailingEvent=r);return}const d=typeof t._latestBinding.arg>"u"?300:Number(t._latestBinding.arg);if(d>0&&t._throttleTimer){e.trailing&&(t._trailingEvent=r);return}const _=u=>{t._hasCalledOnce=!0,t._trailingEvent=null,e.async?(t._asyncPending=!0,Promise.resolve(s==null?void 0:s(u)).finally(()=>{t._unmounted||(t._asyncPending=!1,e.trailing&&t._trailingEvent&&t._throttleHandler(t._trailingEvent))})):s==null||s(u)};if(d===0){_(r);return}_(r),t._throttleTimer=setTimeout(()=>{t._throttleTimer=null,!e.async&&e.trailing&&t._trailingEvent&&(t._throttleHandler(t._trailingEvent),t._trailingEvent=null)},d)},o),t._eventName=n.modifiers.right?"contextmenu":"click",t._listenerOptions={capture:n.modifiers.capture??!1,passive:n.modifiers.passive??!1},t.addEventListener(t._eventName,t._throttleHandler,t._listenerOptions)},updated(t,n){const o=n.modifiers.capture??!1,r=n.modifiers.passive??!1,e=t._listenerOptions;(o!==e.capture||r!==e.passive)&&(t.removeEventListener(t._eventName,t._throttleHandler,e),t._listenerOptions={capture:o,passive:r},t.addEventListener(t._eventName,t._throttleHandler,t._listenerOptions)),t._latestBinding=n},unmounted(t){t.removeEventListener(t._eventName,t._throttleHandler,t._listenerOptions),t._throttleTimer&&clearTimeout(t._throttleTimer),t._unmounted=!0,t._trailingEvent=null}};i.throttleClick=f,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})});
1
+ (function(o,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],d):(o=typeof globalThis<"u"?globalThis:o||self,d(o.VueDirective={},o.Vue))})(this,function(o,d){"use strict";const v=r=>typeof Symbol=="function"?Symbol(r):`__${r}__`,u=v("debounceClick"),p=r=>{const n=r;return{mounted(s,i){s[u]={_latestBinding:i,_debounceTimer:null,_maxWaitTimer:null,_hasCalledOnce:!1,_leadingFired:!1,_waiting:!1,_lastEvent:null,_eventName:"click",_listenerOptions:{},_debounceHandler:()=>{}};const e=s[u],a=Object.getOwnPropertyNames(i.modifiers);e._debounceHandler=d.withModifiers(l=>{const{modifiers:c,value:_}=e._latestBinding;if(c.once&&e._hasCalledOnce)return;const h=typeof e._latestBinding.arg>"u"?300:Number(e._latestBinding.arg);e._lastEvent=l;const f=y=>{e._hasCalledOnce=!0,_==null||_(y)};if(h===0){f(l);return}c.leading&&!e._leadingFired?(e._leadingFired=!0,f(l)):e._waiting=!0,e._debounceTimer&&clearTimeout(e._debounceTimer),n>0&&e._maxWaitTimer===null&&(e._maxWaitTimer=setTimeout(()=>{e._maxWaitTimer=null,e._debounceTimer&&(clearTimeout(e._debounceTimer),e._debounceTimer=null),e._waiting&&e._lastEvent&&(e._waiting=!1,f(e._lastEvent)),e._leadingFired=!1},n)),e._debounceTimer=setTimeout(()=>{e._debounceTimer=null,e._maxWaitTimer&&(clearTimeout(e._maxWaitTimer),e._maxWaitTimer=null),e._waiting&&(e._waiting=!1,f(l)),e._leadingFired=!1},h)},a),i.modifiers.right&&(e._eventName="contextmenu"),e._listenerOptions={capture:i.modifiers.capture??!1,passive:i.modifiers.passive??!1},s.addEventListener(e._eventName,e._debounceHandler,e._listenerOptions)},updated(s,i){const e=s[u],a=i.modifiers.capture??!1,l=i.modifiers.passive??!1,c=e._listenerOptions;(a!==c.capture||l!==c.passive)&&(s.removeEventListener(e._eventName,e._debounceHandler,c),e._listenerOptions={capture:a,passive:l},s.addEventListener(e._eventName,e._debounceHandler,e._listenerOptions)),e._latestBinding=i},unmounted(s){const i=s[u];s.removeEventListener(i._eventName,i._debounceHandler,i._listenerOptions),i._debounceTimer&&clearTimeout(i._debounceTimer),i._maxWaitTimer&&clearTimeout(i._maxWaitTimer)}}},g=p(-1),m=v("throttleClick"),T={mounted(r,n){r[m]={_latestBinding:n,_throttleTimer:null,_hasCalledOnce:!1,_trailingEvent:null,_asyncPending:!1,_unmounted:!1,_eventName:"click",_listenerOptions:{},_throttleHandler:()=>{}};const t=r[m],s=Object.getOwnPropertyNames(n.modifiers);t._throttleHandler=d.withModifiers(i=>{const{modifiers:e,value:a}=t._latestBinding;if(e.once&&t._hasCalledOnce)return;if(e.async&&t._asyncPending){e.trailing&&(t._trailingEvent=i);return}const l=typeof t._latestBinding.arg>"u"?300:Number(t._latestBinding.arg);if(l>0&&t._throttleTimer){e.trailing&&(t._trailingEvent=i);return}const c=_=>{t._hasCalledOnce=!0,t._trailingEvent=null,e.async?(t._asyncPending=!0,Promise.resolve(a==null?void 0:a(_)).finally(()=>{t._asyncPending=!1,!t._unmounted&&e.trailing&&t._trailingEvent&&t._throttleHandler(t._trailingEvent)})):a==null||a(_)};if(l===0){c(i);return}c(i),t._throttleTimer=setTimeout(()=>{t._throttleTimer=null,!e.async&&e.trailing&&t._trailingEvent&&(t._throttleHandler(t._trailingEvent),t._trailingEvent=null)},l)},s),t._eventName=n.modifiers.right?"contextmenu":"click",t._listenerOptions={capture:n.modifiers.capture??!1,passive:n.modifiers.passive??!1},r.addEventListener(t._eventName,t._throttleHandler,t._listenerOptions)},updated(r,n){const t=r[m],s=n.modifiers.capture??!1,i=n.modifiers.passive??!1,e=t._listenerOptions;(s!==e.capture||i!==e.passive)&&(r.removeEventListener(t._eventName,t._throttleHandler,e),t._listenerOptions={capture:s,passive:i},r.addEventListener(t._eventName,t._throttleHandler,t._listenerOptions)),t._latestBinding=n},unmounted(r){const n=r[m];r.removeEventListener(n._eventName,n._throttleHandler,n._listenerOptions),n._throttleTimer&&clearTimeout(n._throttleTimer),n._unmounted=!0,n._trailingEvent=null}},O={install:r=>{r.directive("throttle-click",T),r.directive("debounce-click",g)}};o.debounceClick=g,o.default=O,o.makeDebounceClick=p,o.throttleClick=T,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -0,0 +1,208 @@
1
+ # v-debounce-click
2
+
3
+ Debounces click events so the handler fires only after the user stops clicking for a specified duration. Supports custom delay, leading mode, max-wait cap, and Vue modifier pass-through.
4
+
5
+ ## Installation
6
+
7
+ ```ts
8
+ // Global registration (all directives)
9
+ import { createApp } from 'vue'
10
+ import VuseDirective from 'vuse-directive'
11
+
12
+ const app = createApp(App)
13
+ app.use(VuseDirective)
14
+ app.mount('#app')
15
+ ```
16
+
17
+ ```ts
18
+ // Single directive registration
19
+ import { createApp } from 'vue'
20
+ import { debounceClick } from 'vuse-directive'
21
+
22
+ const app = createApp(App)
23
+ app.directive('debounce-click', debounceClick)
24
+ app.mount('#app')
25
+ ```
26
+
27
+ ```ts
28
+ // On-demand import (local registration)
29
+ import { debounceClick } from 'vuse-directive'
30
+ ```
31
+
32
+ ## Basic Usage
33
+
34
+ ```vue
35
+ <!-- Default debounce delay: 300ms (trailing) -->
36
+ <button v-debounce-click="handleClick">Search</button>
37
+
38
+ <!-- Custom delay (in ms) -->
39
+ <button v-debounce-click:500="handleClick">Search</button>
40
+
41
+ <!-- Delay 0: no debounce, fires on every click -->
42
+ <button v-debounce-click:0="handleClick">Search</button>
43
+ ```
44
+
45
+ ## Arg
46
+
47
+ | Arg | Description |
48
+ |-------------|----------------------------------------------------------------------------------|
49
+ | _(omitted)_ | Default debounce delay: `300` ms |
50
+ | `0` | Disable debounce; callback fires on every click, modifiers still apply |
51
+ | _number_ | Custom debounce delay in milliseconds (e.g. `:500` → 500 ms) |
52
+
53
+ ## Modifiers
54
+
55
+ ### Debounce-specific
56
+
57
+ | Modifier | Description |
58
+ |------------|----------------------------------------------------------------------------------------------------------------------|
59
+ | `.leading` | Fire immediately on the first click of a debounce period; also fire trailing if more clicks happen during the delay |
60
+ | `.once` | Fire only once for the lifetime of the element; all subsequent clicks are ignored |
61
+
62
+ ### Event listener
63
+
64
+ | Modifier | Description |
65
+ |------------|---------------------------------------------------------------|
66
+ | `.right` | Listen to `contextmenu` (right-click) instead of `click` |
67
+ | `.capture` | Use capture phase for the event listener |
68
+ | `.passive` | Mark the listener as passive for better scroll performance |
69
+
70
+ ### Vue `withModifiers` pass-through
71
+
72
+ | Modifier | Description |
73
+ |-----------|---------------------------------------------------------------------------|
74
+ | `.stop` | Call `event.stopPropagation()` |
75
+ | `.prevent`| Call `event.preventDefault()` |
76
+ | `.self` | Only trigger when `event.target` is the element itself |
77
+ | `.ctrl` | Only trigger when the Ctrl key is held |
78
+ | `.shift` | Only trigger when the Shift key is held |
79
+ | `.alt` | Only trigger when the Alt / Option key is held |
80
+ | `.meta` | Only trigger when the Meta / Command key is held |
81
+ | `.left` | Only trigger on left mouse button clicks |
82
+ | `.middle` | Only trigger on middle mouse button clicks |
83
+ | `.exact` | Only trigger when exactly the specified modifier keys are pressed |
84
+
85
+ ## Callback Signature
86
+
87
+ ```ts
88
+ type ClickHandler = (event: MouseEvent, ...args: unknown[]) => void
89
+ ```
90
+
91
+ The binding value receives a `MouseEvent` as the first argument:
92
+
93
+ ```ts
94
+ function handleClick(e: MouseEvent) {
95
+ console.log('clicked', e)
96
+ }
97
+ ```
98
+
99
+ ## Behavior Details
100
+
101
+ ### Default (trailing)
102
+
103
+ The handler fires once after the user stops clicking for the full delay duration. Every new click resets the timer.
104
+
105
+ ```
106
+ click ── click ── click ── [silence 300ms]
107
+ ↓ fire
108
+ ```
109
+
110
+ ### `.leading`
111
+
112
+ The handler fires immediately on the **first** click of a debounce period. If more clicks occur during the delay, a trailing call is also made when the delay ends. If no clicks happen during the delay, no trailing call is made.
113
+
114
+ ```
115
+ // With subsequent clicks:
116
+ click ── click ── [delay ends]
117
+ ↓ fire (wait) ↓ fire (trailing)
118
+
119
+ // Without subsequent clicks:
120
+ click ── [delay ends]
121
+ ↓ fire (no trailing)
122
+ ```
123
+
124
+ ### `.once`
125
+
126
+ The handler fires at most once for the lifetime of the element. All later clicks are silently ignored.
127
+
128
+ ## Max-Wait Cap (`makeDebounceClick`)
129
+
130
+ The default `v-debounce-click` directive has no max-wait limit. If the user never stops clicking, the handler will never fire.
131
+
132
+ To set a max-wait cap — guaranteeing the handler fires at least once even during continuous clicks — use `makeDebounceClick`:
133
+
134
+ ```ts
135
+ import { makeDebounceClick } from 'vuse-directive'
136
+
137
+ // Creates a directive that fires at most every 3000ms even if clicks never stop
138
+ const debounceClickWithMaxWait = makeDebounceClick(3000)
139
+ ```
140
+
141
+ Register and use it like any custom directive:
142
+
143
+ ```ts
144
+ // Local registration
145
+ app.directive('debounce-click-capped', debounceClickWithMaxWait)
146
+ ```
147
+
148
+ ```vue
149
+ <button v-debounce-click-capped:500="handleClick">Search</button>
150
+ ```
151
+
152
+ ### `makeDebounceClick(maxWait)` parameter
153
+
154
+ | Value | Description |
155
+ |------------|----------------------------------------------------|
156
+ | `> 0` | Enable max-wait: force-fire after `maxWait` ms |
157
+ | `<= 0` | Disable max-wait (same as the default directive) |
158
+
159
+ The default exported `debounceClick` is equivalent to `makeDebounceClick(-1)`.
160
+
161
+ ## Examples
162
+
163
+ ```vue
164
+ <script setup lang="ts">
165
+ function search(e: MouseEvent) {
166
+ console.log('search triggered', e)
167
+ }
168
+ </script>
169
+
170
+ <template>
171
+ <!-- Basic: fires 300ms after the last click -->
172
+ <button v-debounce-click="search">Search</button>
173
+
174
+ <!-- Custom delay: fires 1000ms after the last click -->
175
+ <button v-debounce-click:1000="search">Search (1s)</button>
176
+
177
+ <!-- Leading: fires immediately on first click, trailing if more clicks follow -->
178
+ <button v-debounce-click.leading="search">Search (leading)</button>
179
+
180
+ <!-- Once: fires only the first time ever -->
181
+ <button v-debounce-click.once="search">Search (once)</button>
182
+
183
+ <!-- Debounce right-click -->
184
+ <div v-debounce-click.right="openMenu">Right-click area</div>
185
+
186
+ <!-- Stop propagation -->
187
+ <button v-debounce-click.stop="search">No bubble</button>
188
+ </template>
189
+ ```
190
+
191
+ ### With max-wait cap
192
+
193
+ ```ts
194
+ // main.ts
195
+ import { createApp } from 'vue'
196
+ import App from './App.vue'
197
+ import { makeDebounceClick } from 'vuse-directive'
198
+
199
+ const app = createApp(App)
200
+ app.directive('debounce-click', makeDebounceClick(3000))
201
+ app.mount('#app')
202
+ ```
203
+
204
+ ```vue
205
+ <!-- Fires after 500ms of silence, but guaranteed to fire within 3000ms
206
+ even if the user keeps clicking without stopping -->
207
+ <button v-debounce-click:500="search">Search</button>
208
+ ```
@@ -0,0 +1,177 @@
1
+ # v-throttle-click
2
+
3
+ Throttles click events to prevent repeated triggers within a short period. Supports custom delay, async lock, trailing call, and Vue modifier pass-through.
4
+
5
+ ## Installation
6
+
7
+ ```ts
8
+ // Global registration (all directives)
9
+ import { createApp } from 'vue'
10
+ import VuseDirective from 'vuse-directive'
11
+
12
+ const app = createApp(App)
13
+ app.use(VuseDirective)
14
+ app.mount('#app')
15
+ ```
16
+
17
+ ```ts
18
+ // Single directive registration
19
+ import { createApp } from 'vue'
20
+ import { throttleClick } from 'vuse-directive'
21
+
22
+ const app = createApp(App)
23
+ app.directive('throttle-click', throttleClick)
24
+ app.mount('#app')
25
+ ```
26
+
27
+ ```ts
28
+ // On-demand import (local registration)
29
+ import { throttleClick } from 'vuse-directive'
30
+ ```
31
+
32
+ ## Basic Usage
33
+
34
+ ```vue
35
+ <!-- Default throttle interval: 300ms -->
36
+ <button v-throttle-click="handleClick">Submit</button>
37
+
38
+ <!-- Custom interval (in ms) -->
39
+ <button v-throttle-click:500="handleClick">Submit</button>
40
+
41
+ <!-- Interval 0: no throttle, modifiers still apply -->
42
+ <button v-throttle-click:0="handleClick">Submit</button>
43
+ ```
44
+
45
+ ## Arg
46
+
47
+ | Arg | Description |
48
+ |------------|-----------------------------------------------------------------------------|
49
+ | _(omitted)_ | Default throttle interval: `300` ms |
50
+ | `0` | Disable throttle; callback fires on every click, modifiers still apply |
51
+ | _number_ | Custom throttle interval in milliseconds (e.g. `:500` → 500 ms) |
52
+
53
+ ## Modifiers
54
+
55
+ ### Throttle-specific
56
+
57
+ | Modifier | Description |
58
+ |------------|-------------------------------------------------------------------------------------------------|
59
+ | `.once` | Fire only once; all subsequent clicks are ignored |
60
+ | `.trailing`| If a click is blocked during cooldown, re-fire once after cooldown (or async call) ends |
61
+ | `.async` | Async mode: new clicks are blocked until the previous callback's Promise resolves/rejects |
62
+
63
+ ### Event listener
64
+
65
+ | Modifier | Description |
66
+ |------------|---------------------------------------------------------------|
67
+ | `.right` | Listen to `contextmenu` (right-click) instead of `click` |
68
+ | `.capture` | Use capture phase for the event listener |
69
+ | `.passive` | Mark the listener as passive for better scroll performance |
70
+
71
+ ### Vue `withModifiers` pass-through
72
+
73
+ | Modifier | Description |
74
+ |-----------|---------------------------------------------------------------------------|
75
+ | `.stop` | Call `event.stopPropagation()` |
76
+ | `.prevent`| Call `event.preventDefault()` |
77
+ | `.self` | Only trigger when `event.target` is the element itself |
78
+ | `.ctrl` | Only trigger when the Ctrl key is held |
79
+ | `.shift` | Only trigger when the Shift key is held |
80
+ | `.alt` | Only trigger when the Alt / Option key is held |
81
+ | `.meta` | Only trigger when the Meta / Command key is held |
82
+ | `.left` | Only trigger on left mouse button clicks |
83
+ | `.middle` | Only trigger on middle mouse button clicks |
84
+ | `.exact` | Only trigger when exactly the specified modifier keys are pressed |
85
+
86
+ ## Callback Signature
87
+
88
+ ```ts
89
+ type ClickHandler = (event: MouseEvent, ...args: unknown[]) => any
90
+ ```
91
+
92
+ The binding value receives a `MouseEvent` as the first argument.
93
+
94
+ In async mode, return a `Promise` to activate the async lock:
95
+
96
+ ```ts
97
+ async function handleAsync(e: MouseEvent) {
98
+ await fetch('/api/submit', { method: 'POST' })
99
+ }
100
+ ```
101
+
102
+ ## Behavior Details
103
+
104
+ ### Default (trailing only)
105
+
106
+ On each click the handler fires immediately. Subsequent clicks within the cooldown window are dropped.
107
+
108
+ ```
109
+ click ──── click ──── click ──── [cooldown ends]
110
+ ↓ fire (drop) (drop)
111
+ ```
112
+
113
+ ### `.trailing`
114
+
115
+ If any click is blocked during the cooldown, the handler re-fires once when the cooldown ends, using the most recent blocked event.
116
+
117
+ ```
118
+ click ──── click ──── click ──── [cooldown ends]
119
+ ↓ fire (save) (save) ↓ fire (last saved)
120
+ ```
121
+
122
+ ### `.async`
123
+
124
+ New clicks are blocked while the previous call's Promise is pending. The cooldown timer does not apply in async mode; the lock releases only when the Promise settles.
125
+
126
+ ```
127
+ click ──── click ──── [Promise resolves]
128
+ ↓ fire (block) ↓ unlocked
129
+ ```
130
+
131
+ ### `.async.trailing`
132
+
133
+ Same as `.async`, but if any click was blocked while the Promise was pending, the handler re-fires automatically after the Promise settles.
134
+
135
+ ```
136
+ click ──── click ──── [Promise resolves]
137
+ ↓ fire (save) ↓ fire (saved event)
138
+ ```
139
+
140
+ ### `.once`
141
+
142
+ The handler fires at most once for the lifetime of the element. All later clicks are silently ignored.
143
+
144
+ ## Examples
145
+
146
+ ```vue
147
+ <script setup lang="ts">
148
+ async function submitForm() {
149
+ await fetch('/api/submit', { method: 'POST' })
150
+ }
151
+
152
+ function handleClick(e: MouseEvent) {
153
+ console.log('clicked', e)
154
+ }
155
+ </script>
156
+
157
+ <template>
158
+ <!-- Basic: 300ms throttle -->
159
+ <button v-throttle-click="handleClick">Click me</button>
160
+
161
+ <!-- Custom interval: 1000ms -->
162
+ <button v-throttle-click:1000="handleClick">1s throttle</button>
163
+
164
+ <!-- Async + trailing: blocks repeated clicks during async call,
165
+ fires the last blocked click after the call completes -->
166
+ <button v-throttle-click.async.trailing="submitForm">Submit</button>
167
+
168
+ <!-- Fire once only -->
169
+ <button v-throttle-click.once="handleClick">Click once</button>
170
+
171
+ <!-- Throttle right-click -->
172
+ <div v-throttle-click.right="openMenu">Right-click area</div>
173
+
174
+ <!-- Stop propagation -->
175
+ <button v-throttle-click.stop="handleClick">No bubble</button>
176
+ </template>
177
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vuse-directive",
3
- "version": "0.0.3",
3
+ "version": "1.0.0",
4
4
  "description": "Vue 3 directives collection",
5
5
  "type": "module",
6
6
  "main": "./dist/vue-directive.umd.cjs",
@@ -14,14 +14,16 @@
14
14
  }
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "docs"
18
19
  ],
19
20
  "scripts": {
20
21
  "test": "jest",
21
22
  "build": "vite build",
22
23
  "dev": "vite",
23
24
  "format": "biome format --write .",
24
- "format:check": "biome format .",
25
+ "check": "biome check ./src",
26
+ "prepare": "husky",
25
27
  "prepublishOnly": "npm run build",
26
28
  "release": "node scripts/publish.js"
27
29
  },
@@ -29,10 +31,13 @@
29
31
  "vue": "^3.0.0"
30
32
  },
31
33
  "devDependencies": {
32
- "@biomejs/biome": "^2.0.0",
34
+ "@biomejs/biome": "^2.4.0",
33
35
  "@types/jest": "^30.0.0",
34
36
  "@types/node": "^25.3.0",
37
+ "@vitejs/plugin-vue": "^5.0.0",
35
38
  "@vue/test-utils": "^2.4.6",
39
+ "element-plus": "^2.13.3",
40
+ "husky": "^9.0.0",
36
41
  "jest": "^30.2.0",
37
42
  "jest-environment-jsdom": "^30.2.0",
38
43
  "ts-jest": "^29.4.6",
@@ -52,14 +57,6 @@
52
57
  "sideEffects": false,
53
58
  "author": "Stafan Hulk",
54
59
  "license": "MIT",
55
- "repository": {
56
- "type": "git",
57
- "url": "https://github.com/stafanhulk/vue-directive.git"
58
- },
59
- "homepage": "https://github.com/stafanhulk/vue-directive#readme",
60
- "bugs": {
61
- "url": "https://github.com/stafanhulk/vue-directive/issues"
62
- },
63
60
  "engines": {
64
61
  "node": ">=18"
65
62
  }