├── .babelrc ├── .circleci └── config.yml ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── question.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── _internals.js ├── debounce.js ├── directive.js └── index.js ├── tests └── test.js └── types └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": "defaults" 7 | } 8 | ] 9 | ], 10 | "exclude": [ 11 | "node_modules/**", 12 | "dist" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:12 6 | 7 | working_directory: ~/vue-debounce 8 | 9 | steps: 10 | - checkout 11 | 12 | - run: 13 | name: install-deps 14 | command: npm ci 15 | 16 | # lint 17 | - run: 18 | name: lint 19 | command: npm run lint 20 | # run tests and report coverage 21 | - run: 22 | name: test 23 | command: npm t 24 | workflows: 25 | version: 2 26 | main: 27 | jobs: 28 | - build: 29 | filters: 30 | branches: 31 | only: 32 | - master 33 | - development 34 | ignore: gh-pages 35 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dustinh17@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | I am open to, and grateful for, any contributions made to this project. 4 | 5 | Please abide by the linter setup which uses [Standardjs](http://standardjs.com) rules 6 | 7 | Please make sure all PRs are pointed at and processed through the main Development branch 8 | 9 | ## Commit Messages 10 | 11 | Please write and use meaningful and helpful commit messages for your contributions and changes. 12 | 13 | ## Testing 14 | 15 | All changes are expected to continue to pass the tests in place. 16 | 17 | If you are adding new functionality to the library you are expected to also unit test and create appropriate testing for your additions 18 | 19 | To run all tests use `npm t` 20 | 21 | If you want to test only your functionality feel free to change the test script to your `.js` file but **please** remember to change it back to `*.js` and re run `npm t` afterwards! 22 | 23 | ## Documentation 24 | 25 | If you are editing or adding functionality please make sure the readme reflects youyr changes accordingly. 26 | 27 | ## Developing 28 | 29 | - Run unit tests with `npm test` 30 | - You can use `npm run watch` to run the rollup watcher for the source code as you use the html file for testing 31 | - Before opening a PR make sure you do `npm run build` to generate new build files 32 | 33 | ## Releasing 34 | 35 | Currently the CHANGELOG is updated manually before each release. You can document your changes here, or I will document them using your provided commit messages. 36 | 37 | Releases follow standard [semantic versioning](https://semver.org/). 38 | 39 | I will handle publishing to npm for now after your PR is merged in 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help vue-debounce improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Version of Vue** 11 | Which version of vue are you on? (2 or 3) 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Pass it '...' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Desktop (please complete the following information):** 23 | - Browser [e.g. chrome, safari, node] 24 | 25 | **Screenshots** 26 | If applicable/needed, add screenshots to help explain your problem. 27 | 28 | **Additional context** 29 | Add any other context about the problem here. (If none needed feel free to remove this section) 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Unsure of how to use something or just need general help? 4 | 5 | --- 6 | 7 | **Version of Vue** 8 | Which version of vue are you using? 9 | 10 | **Browser** 11 | What browser are you using? 12 | 13 | Type out the question to the best of your ability! 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > Delete any section you're not using and please delete this line too 2 | 3 | ## Breaking Changes 4 | 5 | - List of breaking changes if any 6 | 7 | ## New 8 | 9 | - List of new changes if any 10 | 11 | ## Improved 12 | 13 | - List of improved changes if any 14 | 15 | ## Fixed 16 | 17 | - List of bug or functionality fixes if any 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Stupid Mac Files 2 | 3 | .DS_Store 4 | 5 | test.html 6 | test-func.html 7 | test-vue3.html 8 | test-vuetify.html 9 | 10 | dist/ 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | 74 | exp.js 75 | 76 | # IDE # 77 | .idea/ 78 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Stupid Mac Files 2 | 3 | .DS_Store 4 | 5 | test.html 6 | test-vue3.html 7 | 8 | rollup.config.js 9 | exp.js 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional REPL history 59 | .node_repl_history 60 | 61 | # Output of 'npm pack' 62 | *.tgz 63 | 64 | # Yarn Integrity file 65 | .yarn-integrity 66 | 67 | # dotenv environment variables file 68 | .env 69 | 70 | # next.js build output 71 | .next 72 | 73 | exp.js 74 | 75 | # IDE # 76 | .idea/ 77 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v5.0.1 4 | 5 | ### Fixed 6 | 7 | - Package.json now contains a `main` and fixes #89 ([@andreclemente](https://github.com/andreclemente)) 8 | 9 | ## v5.0.0 10 | 11 | ### Breaking Changes 12 | 13 | - Remove support for Vue 2 14 | - If you need to continue supporting Vue2 please switch to the [vue2-debounce](https://github.com/dhershman1/vue2-debounce) package or stay of v4 of this one 15 | - vue-debounce is now registered as a ES `module` instead of a cjs package 16 | - This helps the transition over to Vue3 17 | - This will help keep the typing system clean 18 | 19 | ### Fixed 20 | 21 | - README now reflects how to use vue-debounce with only vue3 22 | 23 | ## v4.0.1 24 | 25 | ### Improved 26 | 27 | - Added PR #79 for export support 28 | - Added PR #82 for fixed typings 29 | 30 | ### Chores 31 | 32 | - Dependency updates 33 | 34 | ## v4.0.0 35 | 36 | ### Breaking Changes 37 | 38 | - Removed `getDirective` flow there is now a dedicated import for vue 2, and vue 3 39 | 40 | ### Improved 41 | 42 | - Made usage easier, instead of trying to support complex backwards compatability, I simply separated the two into their own imports 43 | - Usage should be less convoluted 44 | 45 | ### Fixed 46 | 47 | - Vue 3 compatability should be stabalized and working again 48 | 49 | ## v3.1.1 50 | 51 | ### Fixed 52 | 53 | - Erroring out when there were no attributes on your element tag [#71](https://github.com/dhershman1/vue-debounce/issues/71) 54 | - Potential issue if passed events was just an empty array or string would cause debounce to stop listening all together 55 | - This falls back on the value of `listenTo` 56 | 57 | ## v3.1.0 58 | 59 | ### Improved 60 | 61 | - Now using vNode instead of raw events, this allows us to use events no matter how deep [#66](https://github.com/dhershman1/vue-debounce/issues/66) 62 | - The above improves compatability with libraries like Vuetify 63 | 64 | ### Chore 65 | 66 | - Dependency Updates and Audit fixes 67 | 68 | ## v3.0.2 69 | 70 | ### New 71 | 72 | - The build will now push out a esm module as well with the other minified versions check the readme on how to use it 73 | 74 | ### Chores 75 | 76 | - Updated dependencies 77 | - Ran Audit on dev deps (and fixed) 78 | 79 | ### Fixed 80 | 81 | - Some documentation mis communications 82 | 83 | ## v3.0.1 84 | 85 | ### Improved 86 | 87 | - Types for debounce (thanks to [hrobertson](https://github.com/hrobertson)) 88 | - Placement of the readme for `getDirective` 89 | 90 | ## v3.0.0 91 | 92 | ### BREAKING CHANGES 93 | 94 | - Lots of code condencing, shouldn't break anything but just in case 95 | - Changed how debouncing `fireonempty` works. It had a bug so I made it a bit more strict to fix this 96 | - Please open issues ASAP if this functionality is not working as expected 97 | 98 | ### New 99 | 100 | - Added new `trim` option and modifier, this allows you to trim inputs that come through 101 | - The value given to your function will **NOT** be the trimmed value, trim is only used for logic to see if things should be ran 102 | - Added `getDirective` function which allows you to create a directive instance at lower levels other than global 103 | 104 | ### Improved 105 | 106 | - Some small code cleanup 107 | - Updated dependencies 108 | 109 | ## v2.6.0 110 | 111 | ### New 112 | 113 | - Added Vue 3 Compatibility :tada: 114 | - This involves the backwards compatibility change I brought up in discussions 115 | 116 | ### Improved 117 | 118 | - Dropped `dist/` from the repo 119 | - This is so it doesnt bog down PRs 120 | - dist is still available on npm as its built before deploy 121 | - Your CDN (if using) should _NOT_ be affected 122 | 123 | ## v2.5.8 124 | 125 | ### Improved 126 | 127 | - Updated all dev dependencies 128 | - Started planning vue 3 compatibility 129 | 130 | ### Fixed 131 | 132 | - Removed david-md badges from readme since it seems to be down for good 133 | - Linting fixes in the test files 134 | 135 | ## v2.5.7 136 | 137 | ### Fixed 138 | 139 | - Took out Kyanite for compatibility reasons 140 | 141 | ## v2.5.6 142 | 143 | ### Fixed 144 | 145 | - Restored IE11 support by updating kyanite to its latest version 146 | 147 | ## v2.5.5 148 | 149 | ### Improved 150 | 151 | - Converted over to `Terser` instead of `uglify` 152 | - Converted over to `babel` instead of `buble` 153 | - Switched over to my library `kyanite` for some of the utility work, removing some excess code 154 | 155 | ## v2.5.4 156 | 157 | ### Improved 158 | 159 | - Caveats section added [#36](https://github.com/dhershman1/vue-debounce/issues/36) 160 | 161 | ### Fixed 162 | 163 | - Unclear documentation for `modifiers` [#37](https://github.com/dhershman1/vue-debounce/issues/37) 164 | 165 | ## v2.5.3 166 | 167 | ### Fixed 168 | 169 | - Unclear documentation on using the `debounce` function [#34](https://github.com/dhershman1/vue-debounce/issues/34) 170 | 171 | ## v2.5.2 172 | 173 | ### Improved 174 | 175 | - Added support for scope inheritance for `debounce` utility function this might address [#28](https://github.com/dhershman1/vue-debounce/issues/28) [ilyasfoo](https://github.com/ilyasfoo) 176 | - Dependencies updated 177 | 178 | ## v2.5.1 179 | 180 | ### Fixed 181 | 182 | - typescript typing for the return on the debounce function [@bobvandevijver](https://github.com/bobvandevijver) 183 | 184 | ## v2.5.0 185 | 186 | ### New 187 | 188 | - Added `fireOnEmpty` modifier, which allow you to choose individual inputs you want to have fireOnEmpty 189 | - Added `cancelOnEmpty` modifier which cancels the debounce all together if the input value is empty 190 | 191 | ### Improved 192 | 193 | - Drastic code cleanup 194 | 195 | ### Fixed 196 | 197 | - Bug with `fireOnEmpty` where debounce function would fire twice even when input was empty 198 | 199 | ## v2.4.0 200 | 201 | ### New 202 | 203 | - Added the `event` Object itself to the function call, it is passed as the 2nd parameter 204 | 205 | ## v2.3.0 206 | 207 | ### New 208 | 209 | - Added a new `fireOnEmpty` option that you can enable to fire the debounce function immediately if the input is empty 210 | 211 | ## v2.2.1 212 | 213 | ### Fixed 214 | 215 | - Added Typescript section to the table of contents 216 | - Fixed author data to have my email 217 | 218 | ## v2.2.0 219 | 220 | ### New 221 | 222 | - Added Typescript Support thanks to @itmayziii 223 | 224 | ## v2.1.0 225 | 226 | ### New 227 | 228 | - Added instructions on how to just use the debounce api 229 | - Added ability to send the debounce function just a number and it will get treated as miliseconds 230 | 231 | ## v2.0.0 232 | 233 | ### BREAKING CHANGES 234 | 235 | - Now using the `addEventListener` method attached to the given el to properly set the event rather than overriding the native callback 236 | 237 | ## v1.3.0 238 | 239 | - Added ability to set default timeout within the options of the plugin 240 | 241 | ## v1.2.0 242 | 243 | - Added ability to send an array of events to have the elments listen to 244 | - Removed deprecated `keyCode` in favor of just `key` 245 | - `listenTo` is no longer case sensitive 246 | - Added ability to listen for the `debounce-events` attribute 247 | 248 | ## v1.1.0 249 | 250 | - Tweaked the Syntax of the debounce function 251 | - Added additional tests 252 | - Updated dependencies 253 | - Added ability to set the event to listen to in options 254 | 255 | ## v1.0.0 256 | 257 | - Some small tweaks to code nothing breaking 258 | - Removed the long deleted `min` from the regex check 259 | - Removed unused code 260 | - Mainly to just get it to v1.0.0 no breaking changes added 261 | 262 | ## v0.2.0 263 | 264 | - Slight Tweaks to optimization 265 | - Removed unused data pieces 266 | - Switched the event to `keyup` instead of `input` for key events 267 | - Added some options and modifiers support (See next tick) 268 | - Pressing enter on a debounced input now automatically fires the desired function 269 | - You can disable this by using the `.lock` modifier on the directive 270 | - You can also disable it by passing `lock` to the directive as an option, however this will disable it for _ALL_ debounced inputs 271 | - If you are using the lock option and want to make an exception for an input you can use the `.unlock` modifier to do so 272 | - Removed support for minutes 273 | 274 | ## v0.1.2 275 | 276 | - Replaced `null` type with a `false` Boolean 277 | - Removed empty config object 278 | - Added `Vue` as a peer dependency 279 | - Removed `Vue` as a dev dependency 280 | - Created the changelog md 281 | 282 | ## v0.1.1 283 | 284 | - Fixed the Build files 285 | 286 | ## v0.1.0 287 | 288 | - Initial Release 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dustin Hershman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-debounce 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-debounce.svg?style=flat-square)](https://www.npmjs.com/package/vue-debounce) 4 | [![Downloads](https://img.shields.io/npm/dm/vue-debounce.svg?style=flat-square)](https://www.npmjs.com/package/vue-debounce) 5 | 6 | A simple to use directive for debounce solutions 7 | 8 | It attaches itself to an event for actions 9 | 10 | [![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard) 11 | 12 | ## Important 13 | 14 | As of now [vue2-debounce](https://github.com/dhershman1/vue2-debounce) is published on npm, if you are using vue2 I highly recommend migrating to this package as this one is going to start focusing on vue3 from v5 onward. Consider vue2 support deprecated for this module. 15 | 16 | ## Content 17 | 18 | - [Features](#features) 19 | - [Installation](#installation) 20 | - [Modifiers](#modifiers) 21 | - [Options](#options) 22 | - [Option Defaults](#option-defaults) 23 | - [CDN Support](#cdn-support) 24 | - [Setup](#setup) 25 | - [Use Just Debounce](#using-just-debounce) 26 | - [Usage](#usage) 27 | - [Modifier Usage](#modifier-usage) 28 | - [Overwriting Events](#overwriting-events) 29 | - [Typescript Support](#typescript-support) 30 | - [Caveats](#caveats) 31 | 32 | ## Features 33 | 34 | - Dynamic debouncing for input based requests 35 | - Easy to use, just place it into your vue instance and attach it to your inputs/components 36 | - Self regulating no need to worry about it, set it and forget it 37 | - Multiple time formats supported (miliseconds and seconds) 38 | - Enter key support to automatically fire the desired function when the user hits the enter key in the desired input (Can also be disabled) 39 | - Supports the ability to have multiple event listeners, and specify events at the element level 40 | 41 | ## Installation 42 | ``` 43 | npm i vue-debounce 44 | ``` 45 | 46 | ## Modifiers 47 | 48 | - `lock` : Used to lock the debounce and prevent the enter key from triggering the function when pressed 49 | - Example: `v-debounce:400ms.lock="cb"` 50 | - `unlock` : Used to unlock the enter key on a debounced input, useful if you want to use the `lock` option and only want a few debounced inputs unlocked 51 | - `fireonempty` : Use to signify that when that specific input is emptied, you want the function to fire right away 52 | - `cancelonempty` : Use this to specify that when the input is emptied you **DO NOT** want your debounced function to trigger at all 53 | - `trim` : `Boolean` - Tells debounce to trim out white space using the `String.prototype.trim()` function 54 | 55 | ## Options 56 | 57 | - `lock` : `Boolean` - This works the same way as the modifier does, however using the option will lock _ALL_ of the debounced inputs within that vue instance, where as using the modifer only locks the one it's attached to 58 | - `listenTo` : `String|Array` - Allows you to set a custom event attached to an element like `input` for example 59 | - This is given to the `addEventListener` method attached to the element 60 | - `defaultTime` : `String` - Set the default timer for debounce directives that you don't give a time to 61 | - `fireOnEmpty` : `Boolean` - Tells debounce that if the input is empty, then fire the function immediately 62 | - `trim` : `Boolean` - Tells debounce to trim out white space using the `String.prototype.trim()` function 63 | 64 | ## Option Defaults 65 | 66 | ```js 67 | { 68 | lock: false, 69 | listenTo: 'keyup', 70 | defaultTime: '300ms', 71 | fireOnEmpty: false, 72 | trim: false 73 | } 74 | ``` 75 | 76 | ## CDN Support 77 | 78 | You can use vue debounce via CDN like so: (It is recommended that you don't use `@latest` however) 79 | 80 | ```html 81 | 85 | ``` 86 | 87 | ## Setup 88 | 89 | With vue3 we simply need to import the new directive function `vueDebounce` this function takes in an object of options (found above) 90 | 91 | Using `vue-debounce` Globally: 92 | ```js 93 | import vueDebounce from 'vue-debounce' 94 | import { createApp } from 'vue'; 95 | import App from './App.vue'; 96 | 97 | const app = createApp(App) 98 | app 99 | .directive('debounce', vueDebounce({ lock: true })) 100 | .mount('#app'); 101 | ``` 102 | 103 | Using the setup API at the component level: 104 | ```vue 105 | 110 | ``` 111 | 112 | Using `vue-debounce` at a component level using the option API: 113 | ```js 114 | import vueDebounce from 'vue-debounce' 115 | 116 | export default { 117 | directives: { 118 | debounce: vueDebounce({ lock: true }) 119 | } 120 | } 121 | ``` 122 | 123 | ## Using Just Debounce 124 | 125 | With Vue-debounce you're also able to just use the debouncing function. 126 | 127 | Simply require the debounce file. 128 | 129 | ```js 130 | import { debounce } from 'vue-debounce' 131 | ``` 132 | 133 | The `debounce` function returns a function back which in turn is debounced, so you can set them up ahead of time: 134 | 135 | ```js 136 | const dFn = debounce(val => console.log('normal format', val), '400ms') 137 | 138 | dFn(10) // => 'normal format' 10 139 | // Or 140 | debounce(val => console.log('just a number!'), 400)(10) // => 'just a number!' 10 141 | ``` 142 | 143 | 144 | ## Usage 145 | 146 | Then attach a time:format to the directive, and set the value to the function you want to call and attach it to your input element 147 | 148 | Example: 149 | 150 | ```vue 151 | 152 | ``` 153 | 154 | If no wait timer is passed in, then the directive will default to whatever you set `defaultTime` to, **OR** `300ms` if that isn't set. 155 | 156 | You can pass the time in multiple formats: 157 | 158 | ```vue 159 | 160 | 161 | 162 | 163 | 164 | ``` 165 | 166 | The value of the input is passed along to your function as the first parameter, and the 2nd parameter is the event object itself. 167 | 168 | ## Modifier Usage 169 | 170 | Using modifiers works just like normal Vue directives. You can chain them to the timeout value and each other. Some examples include: 171 | 172 | > **IMPORTANT NOTE**: Modifiers WILL overwrite options you have set, for example if you set the `fireOnEmpty` option set to true and then tag a input with the `cancelonempty` modifier then the debounced function will cancel when **THAT** input is empty instead of fire. 173 | 174 | ```vue 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | ``` 188 | 189 | ## Overwriting Events 190 | 191 | As of Version 1.2.0 you can assign specific event listeners to specific inputs. Doing so overwrites **ANY** of the listed events set with `listenTo` 192 | 193 | Example: 194 | ```vue 195 | // This can accept an array or a single string when using the bind `:` syntax 196 | 197 | 198 | 199 | // You can also just use it as an attribute, though if passing multiple events binding it is preferred 200 | 201 | ``` 202 | 203 | A full example: 204 | 205 | ```vue 206 | 210 | 220 | ``` 221 | 222 | ## Typescript Support 223 | While this project is not written in typescript, we do define types in the `types` directory. Unfortunately the way Vue is currently typed 224 | the only type support you will get is when you `Vue.use(vueDebounce)`. 225 | 226 | i.e. 227 | 228 | ```typescript 229 | import Vue from 'vue' 230 | import vueDebounce, { PluginConfig, debounce } from 'vue-debounce' 231 | 232 | debounce(() => console.log('just a number!'), 400) 233 | debounce(() => console.log('normal format'), '400ms') 234 | 235 | Vue.use(vueDebounce, { lock: true, defaultTime: '400ms', listenTo: 'keyup' }) 236 | ``` 237 | 238 | Hopefully in the future Vue will allow directives to type the modifiers and values that are accepted. 239 | 240 | ## Caveats 241 | 242 | If a library you are using such as `Vueftify` is already using a specified event, it will block vue debounce from being able to listen to that event. 243 | 244 | As of `v3.1.0` I have significantly improved compatability with these kinds of libraries, however this problem still remains. 245 | 246 | For example, Vuetify makes pretty heavy use of the `onblur` event for a lot of it's styles/animatons, so I'd recommend telling vue-debounce to listen for `focusout` instead, if you want debounce to trigger on a blur like event. 247 | 248 | I will keep doing research into a better way to solve this little issue, but for now the improved compatability should help a lot! 249 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-debounce", 3 | "version": "5.0.1", 4 | "description": "A simple vue directive for debounce", 5 | "module": "src/index.js", 6 | "main": "src/index.js", 7 | "types": "types/index.d.ts", 8 | "type": "module", 9 | "unpkg": "dist/vueDebounce.min.js", 10 | "jsdelivr": "dist/vueDebounce.min.js", 11 | "scripts": { 12 | "test": "tape tests/test.js | tap-on", 13 | "prepack": "npm run lint && npm run build", 14 | "build": "rollup -c", 15 | "watch": "rollup -c -w", 16 | "lint": "standard src/*.js", 17 | "lint:fix": "standard --fix src/*.js" 18 | }, 19 | "standard": { 20 | "ignore": [ 21 | "dist/*", 22 | "types/*" 23 | ] 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/dhershman1/vue-debounce.git" 28 | }, 29 | "keywords": [ 30 | "vue", 31 | "vue3", 32 | "debounce", 33 | "directive", 34 | "simple", 35 | "v-debounce", 36 | "events" 37 | ], 38 | "author": "Dustin Hershman ", 39 | "contributors": [ 40 | "Tommy May III " 41 | ], 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/dhershman1/vue-debounce/issues" 45 | }, 46 | "homepage": "https://github.com/dhershman1/vue-debounce#readme", 47 | "devDependencies": { 48 | "@babel/core": "7.23.5", 49 | "@babel/preset-env": "7.23.5", 50 | "@rollup/plugin-babel": "6.0.4", 51 | "@rollup/plugin-terser": "0.4.4", 52 | "rollup": "^4.6.1", 53 | "rollup-plugin-filesize": "9.1.2", 54 | "standard": "^17.1.0", 55 | "tap-on": "0.3.1", 56 | "tape": "^5.7.2" 57 | }, 58 | "peerDependencies": { 59 | "vue": ">= 3.0.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import filesize from 'rollup-plugin-filesize' 3 | import terser from '@rollup/plugin-terser' 4 | 5 | export default [{ 6 | input: './src/index.js', 7 | plugins: [ 8 | babel({ babelHelpers: 'bundled' }), 9 | terser(), 10 | filesize({ 11 | showMinifiedSize: false 12 | }) 13 | ], 14 | output: { 15 | format: 'umd', 16 | name: 'vueDebounce', 17 | file: 'dist/vue-debounce.min.js' 18 | } 19 | }, { 20 | input: './src/debounce.js', 21 | plugins: [ 22 | babel({ babelHelpers: 'bundled' }), 23 | terser(), 24 | filesize({ 25 | showMinifiedSize: false 26 | }) 27 | ], 28 | output: { 29 | format: 'umd', 30 | name: 'debounce', 31 | file: 'dist/debounce.min.js' 32 | } 33 | }] 34 | -------------------------------------------------------------------------------- /src/_internals.js: -------------------------------------------------------------------------------- 1 | // Helper Functions 2 | /** 3 | * Maps through an array of strings and lowercases all of them 4 | * @param {Array} list an array of strings to map through 5 | */ 6 | export function toLowerMap (list) { 7 | return list.map(x => x.toLowerCase()) 8 | } 9 | 10 | /** 11 | * Takes in a value and ensures its wrapped within an array 12 | * @param {Any} value The value to ensure is an array 13 | */ 14 | export function ensureArray (value) { 15 | if (Array.isArray(value)) { 16 | return value 17 | } 18 | 19 | if (value == null) { 20 | return [] 21 | } 22 | 23 | return [value] 24 | } 25 | 26 | // Figures out the event we are using with the bound element 27 | export function mapOutListeningEvents (attrs, listenTo) { 28 | // Make sure attributes exist on the element 29 | const elEvents = attrs ? attrs['debounce-events'] : [] 30 | // If they set an events attribute that overwrites everything 31 | if (elEvents && elEvents.length > 0) { 32 | // Since they can send in an array or a string we need to be prepared for both 33 | if (Array.isArray(elEvents)) { 34 | return toLowerMap(elEvents) 35 | } 36 | return toLowerMap(elEvents.split(',')) 37 | } 38 | 39 | return toLowerMap(ensureArray(listenTo)) 40 | } 41 | 42 | export function isEmpty (str) { 43 | return str === '' 44 | } 45 | 46 | export function isLocked (key, modifiers) { 47 | return key === 'Enter' && (!modifiers.lock || modifiers.unlock) 48 | } 49 | 50 | export function shouldFireOnEmpty (value, key, modifiers) { 51 | return isEmpty(value) && modifiers.fireonempty && (key === 'Enter' || key === ' ') 52 | } 53 | -------------------------------------------------------------------------------- /src/debounce.js: -------------------------------------------------------------------------------- 1 | function convertTime (time) { 2 | const [amt, t = 'ms'] = String(time).split(/(ms|s)/i) 3 | const types = { 4 | ms: 1, 5 | s: 1000 6 | } 7 | 8 | return Number(amt) * types[t] 9 | } 10 | 11 | function debounce (fn, wait) { 12 | let timeout = null 13 | const timer = typeof wait === 'number' ? wait : convertTime(wait) 14 | 15 | const debounced = function (...args) { 16 | const later = () => { 17 | timeout = null 18 | 19 | fn.apply(this, args) 20 | } 21 | 22 | clearTimeout(timeout) 23 | timeout = setTimeout(later, timer) 24 | 25 | if (!timeout) { 26 | fn.apply(this, args) 27 | } 28 | } 29 | 30 | debounced.cancel = () => { 31 | clearTimeout(timeout) 32 | timeout = null 33 | } 34 | 35 | return debounced 36 | } 37 | 38 | export default debounce 39 | -------------------------------------------------------------------------------- /src/directive.js: -------------------------------------------------------------------------------- 1 | import debounce from './debounce.js' 2 | import { mapOutListeningEvents, isEmpty, isLocked, shouldFireOnEmpty } from './_internals.js' 3 | 4 | export default function ({ 5 | lock = false, 6 | listenTo = 'keyup', 7 | defaultTime = '300ms', 8 | fireOnEmpty = false, 9 | cancelOnEmpty = false, 10 | trim = false 11 | } = {}) { 12 | return { 13 | created (el, { 14 | value: debouncedFn, 15 | arg: timer = defaultTime, 16 | modifiers 17 | }, vnode) { 18 | const combinedRules = Object.assign({ 19 | lock, 20 | trim, 21 | fireonempty: fireOnEmpty, 22 | cancelonempty: cancelOnEmpty 23 | }, modifiers) 24 | const events = mapOutListeningEvents(vnode.props, listenTo) 25 | const fn = debounce(e => { 26 | debouncedFn(e.target.value, e) 27 | }, timer) 28 | 29 | function handler (event) { 30 | const value = combinedRules.trim ? event.target.value.trim() : event.target.value 31 | 32 | if (isEmpty(value) && combinedRules.cancelonempty) { 33 | fn.cancel() 34 | } else if (isLocked(event.key, combinedRules) || shouldFireOnEmpty(value, event.key, combinedRules)) { 35 | fn.cancel() 36 | debouncedFn(event.target.value, event) 37 | } else { 38 | fn(event) 39 | } 40 | } 41 | 42 | events.forEach(e => { 43 | el.addEventListener(e, handler) 44 | }) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import debounce from './debounce.js' 2 | import vueDebounce from './directive.js' 3 | 4 | export { 5 | debounce, 6 | vueDebounce 7 | } 8 | 9 | export default vueDebounce 10 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import debounce from '../src/debounce.js' 3 | 4 | test('Handles defaults', t => { 5 | const runner = debounce((val) => { 6 | t.ok(val) 7 | t.same(val, 'testing') 8 | t.end() 9 | }) 10 | 11 | runner('testing') 12 | }) 13 | 14 | test('Handles number type', t => { 15 | const runner = debounce(val => { 16 | t.ok(val) 17 | t.same(val, 'testing') 18 | t.end() 19 | }, 400) 20 | 21 | runner('testing') 22 | }) 23 | 24 | test('Handles no format set', t => { 25 | const runner = debounce(val => { 26 | t.ok(val) 27 | t.same(val, 'testing') 28 | t.end() 29 | }, '500') 30 | 31 | runner('testing') 32 | }) 33 | 34 | test('Handles sepcific time', t => { 35 | const runner = debounce((val) => { 36 | t.ok(val) 37 | t.same(val, 'testing') 38 | t.end() 39 | }, '500ms') 40 | 41 | runner('testing') 42 | }) 43 | 44 | test('Handles seconds', t => { 45 | const runner = debounce((val) => { 46 | t.ok(val) 47 | t.same(val, 'testing') 48 | t.end() 49 | }, '1s') 50 | 51 | runner('testing') 52 | }) 53 | 54 | test('Inherits parent scope', t => { 55 | const runner = function (val) { 56 | const Someclass = function ($options) { 57 | this.somevalue = '' 58 | this.$options = $options 59 | 60 | this.bind = function (fn, ctx) { 61 | return function (a) { 62 | const l = arguments.length 63 | return l 64 | ? l > 1 65 | ? fn.apply(ctx, arguments) 66 | : fn.call(ctx, a) 67 | : fn.call(ctx) 68 | } 69 | } 70 | 71 | this._initMethods = function () { 72 | const methods = this.$options.methods 73 | if (methods) { 74 | for (const key in methods) { 75 | this[key] = this.bind(methods[key], this) 76 | } 77 | } 78 | } 79 | 80 | this._initMethods() 81 | } 82 | 83 | const vm = new Someclass({ 84 | methods: { 85 | somefunction: debounce(function () { 86 | t.ok(val) 87 | t.same(this.somevalue, 'testing') 88 | t.end() 89 | }) 90 | } 91 | }) 92 | 93 | vm.somevalue = val 94 | vm.somefunction() 95 | } 96 | 97 | runner('testing') 98 | }) 99 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * TODO 12/1/2019 - It would be nice if we could add types to the modifiers and expected callback value of the directive but this does not seem to be possible right now. 3 | * 4 | * Both of the types are marked as any without any generic support so not much we can do. This might change in the future when Vue is actually written in typescript as per the road map for Vue 3 https://github.com/vuejs/roadmap#3x 5 | * "value" type - https://github.com/vuejs/vue/blob/fd0eaf92948bb5a4882d538362091fb287d642e3/types/vnode.d.ts#L70 6 | * "modifiers" type - https://github.com/vuejs/vue/blob/fd0eaf92948bb5a4882d538362091fb287d642e3/types/options.d.ts#L184 7 | */ 8 | 9 | export interface PluginConfig { 10 | lock?: boolean 11 | listenTo?: string | string[] 12 | defaultTime?: string | number 13 | fireOnEmpty?: boolean, 14 | cancelOnEmpty?: boolean, 15 | trim?: boolean 16 | } 17 | 18 | interface DirectiveObject { 19 | [key: string]: (el: HTMLElement, binding: any, vnode: any) => void 20 | } 21 | 22 | interface DebounceInstance { 23 | (...args: A): void | Promise, 24 | cancel(): void, 25 | } 26 | 27 | interface Debounce { 28 | (fn: (...args: A) => void | Promise, wait: number | string): DebounceInstance 29 | } 30 | 31 | declare const debounce: Debounce 32 | declare const vueDebounce: (opts: PluginConfig) => DirectiveObject 33 | 34 | export { debounce, vueDebounce, DebounceInstance, DirectiveObject } 35 | export default vueDebounce 36 | --------------------------------------------------------------------------------