├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── npmpublish.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── vue-wait-next.js └── vue-wait-v2.js ├── examples ├── transition-example │ ├── index.js │ └── main.vue ├── vue-example │ ├── index.js │ └── main.vue ├── vuex-example │ ├── index.js │ └── main.vue └── wrap-example │ ├── index.js │ └── main.vue ├── index.js ├── nuxt ├── index.js └── vue-wait-plugin.template.js ├── package.json ├── resources ├── logo.png ├── logo.sketch ├── vue-ui-install.png ├── vue-wait-2.gif ├── vue-wait.gif └── vuex-dev-tools.png ├── src ├── components │ └── v-wait.vue ├── directives │ └── wait.js ├── helpers.js ├── lib │ └── matcher.js ├── types │ ├── index.d.ts │ └── vue.d.ts ├── utils.js ├── vue-wait.js └── vuex │ └── store.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | "@babel/plugin-transform-runtime" 5 | ], 6 | "comments": false 7 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/.eslintignore -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | 7 | extends: 'standard', 8 | // required to lint *.vue files 9 | plugins: [ 10 | 'html' 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: fka 2 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12 19 | - run: yarn 20 | - run: yarn test 21 | 22 | publish-npm: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v1 27 | - uses: actions/setup-node@v1 28 | with: 29 | node-version: 12 30 | registry-url: https://registry.npmjs.org/ 31 | - run: yarn publish 32 | if: github.event == 'push' 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 35 | 36 | publish-gpr: 37 | needs: build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v1 41 | - uses: actions/setup-node@v1 42 | with: 43 | node-version: 12 44 | registry-url: https://npm.pkg.github.com/ 45 | scope: '@f' 46 | - run: yarn publish 47 | if: github.event == 'push' 48 | env: 49 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 10 5 | 6 | cache: yarn 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vue-wait changelog 2 | 3 | ## v1.2.0 4 | - Rename `isWaiting` to `is` to make the code less crowded. 5 | - Better array matching 6 | 7 | ## v1.1.0 8 | - Rename package to `vue-wait` 9 | - `anyLoading` to `any` 10 | 11 | ## v1.0.0 12 | 13 | - A complete rewrite, more extensible. 14 | - Readable and better code. 15 | - Update to Webpack 4 16 | - Remove built-in loaders. Maybe we can create another repository including custom spinners. 17 | - Remove `width` and `height` props. 18 | - Strict props. 19 | - `isWaiting` supports matchers now `creating.*`, `!creating` etc. Please see [/sindresorhus/matcher](/sindresorhus/matcher). 20 | - Rename `registerComponents` to `registerComponent` 21 | - Added `accessorName` option to change `$wait` key. 22 | - Removed `createActionHelpers`, use `mapWaitingActions` or `waitFor` instead. 23 | - Added `v-loading:visible`, `v-loading:hidden`, `v-loading:disabled`, `v-loading:enabled`, `v-loading:click` directives. 24 | 25 | ## v0.4.0 26 | 27 | - rename v-loading slot `spinner` to `loading` #30 28 | - added `waitFor` helper function for easy integration of vue-wait in vue component methods #30 29 | 30 | ## v0.3.0 31 | 32 | - Rename `$vuexLoading` to `$wait` to be consistent with class name #25 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fatih Kadir Akın 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 |

2 | 3 |

4 |

5 | Multiple Process Loader Management for Vue and (optionally) Vuex. 6 |

7 | 8 |

9 | Read the Medium post "Managing Complex Waiting Experiences on Web UIs". 10 |

11 | 12 | [![npm version](https://badge.fury.io/js/vue-wait.svg)](https://badge.fury.io/js/vue-wait) 13 | 14 | --- 15 | 16 | ![vue-wait](https://user-images.githubusercontent.com/196477/42170484-4d91e36a-7e1f-11e8-9cee-816bfe857db2.gif) 17 | 18 | > [Play with demo above](https://f.github.io/vue-wait/). 19 | 20 | **vue-wait** helps to manage multiple loading states on the page without any conflict. It's based on a **very simple idea** that manages an array (or Vuex store optionally) with multiple loading states. The **built-in loader component** listens its registered loader and immediately become loading state. 21 | 22 | # ⏩Quick Start 23 | 24 | If you are a **try and learn** developer, you can start trying the **vue-wait** now using [codesandbox.io](https://codesandbox.io). 25 | 26 | [![Edit VueWait Sandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/85q3vpm42?autoresize=1&hidenavigation=1&module=%2Fsrc%2Fcomponents%2FMyList.vue) 27 | 28 | ### 1. Install: 29 | ```bash 30 | yarn add vue-wait 31 | ``` 32 | 33 | ### 2. Require: 34 | #### For Vue 2.x 35 | ```js 36 | import VueWait from 'vue-wait' 37 | 38 | Vue.use(VueWait) 39 | 40 | new Vue({ 41 | // your vue config 42 | wait: new VueWait(), 43 | }) 44 | ``` 45 | 46 | #### For Vue 3.x 47 | ```js 48 | import { createApp } from 'vue' 49 | import { createVueWait } from 'vue-wait' 50 | import App from './App.vue' 51 | 52 | const VueWait = createVueWait() 53 | 54 | createApp(App) // Create app with root component 55 | .use(VueWait) // Register vue-wait 56 | .mount('#app') 57 | ``` 58 | 59 | ### 3. Use in Your Components 60 | 61 | ```vue 62 | 75 | 76 | 94 | ``` 95 | 96 | > **vue-wait has more abilities to make the management easier, please read the complete documentation.** 97 | 98 | # ▶️Detailed Start 99 | 100 | ## 📦 Requirements 101 | 102 | - [Vue.js](https://vuejs.org) (v2.0.0+) 103 | 104 | ## 🚀 Power Supplies 105 | - [Vuex](http://vuex.vuejs.org), optionally (v2.0.0+) 106 | 107 | ## 🔧 Installation 108 | 109 | via CLI: 110 | 111 | ```bash 112 | $ yarn add vue-wait 113 | # or if you using npm 114 | $ npm install vue-wait 115 | ``` 116 | 117 | via Vue UI: 118 | 119 | 120 | 121 | ## 📖 Usage 122 | 123 | ```js 124 | import VueWait from 'vue-wait' 125 | 126 | Vue.use(VueWait) // add VueWait as Vue plugin 127 | ``` 128 | 129 | Then you should register `wait` property (`VueWait` instance) to the Vue instance: 130 | 131 | ```js 132 | new Vue({ 133 | el: '#app', 134 | store, 135 | wait: new VueWait({ 136 | // Defaults values are following: 137 | useVuex: false, // Uses Vuex to manage wait state 138 | vuexModuleName: 'wait', // Vuex module name 139 | 140 | registerComponent: true, // Registers `v-wait` component 141 | componentName: 'v-wait', // component name, you can set `my-loader` etc. 142 | 143 | registerDirective: true, // Registers `v-wait` directive 144 | directiveName: 'wait', // directive name, you can set `my-loader` etc. 145 | 146 | }), 147 | }); 148 | ``` 149 | 150 | ## ♻️ Usage with Vuex 151 | 152 | Simply set `useVuex` parameter to `true` and optionally override 153 | `vuexModuleName` 154 | 155 | ```js 156 | import VueWait from 'vue-wait' 157 | 158 | Vue.use(Vuex) 159 | Vue.use(VueWait) // add VueWait as Vue plugin 160 | ``` 161 | 162 | Then you should register `VueWait` module: 163 | 164 | ```js 165 | new Vue({ 166 | el: '#app', 167 | store, 168 | wait: new VueWait({ 169 | useVuex: true, // You must pass this option `true` to use Vuex 170 | vuexModuleName: 'vuex-example-module' // It's optional, `wait` by default. 171 | }), 172 | }); 173 | ``` 174 | 175 | Now `VueWait` will use `Vuex` store for data management which can be traced in `Vue DevTools > Vuex` 176 | 177 | ## ♻️ Usage with Nuxt.js 178 | 179 | Add `vue-wait/nuxt` to modules section of `nuxt.config.js` 180 | 181 | ```js 182 | { 183 | modules: [ 184 | // Simple usage 185 | 'vue-wait/nuxt' 186 | 187 | // Optionally passing options in module configuration 188 | ['vue-wait/nuxt', { useVuex: true }] 189 | ], 190 | 191 | // Optionally passing options in module top level configuration 192 | wait: { useVuex: true } 193 | } 194 | ``` 195 | 196 | ## 🔁 `VueWait` Options 197 | 198 | You can use this options for customize VueWait behavior. 199 | 200 | | Option Name | Type | Default | Description | 201 | | ----------- | ---- | ------- | ----------- | 202 | | `accessorName` | `String` | `"$wait"` | You can change this value to rename the accessor. E.g. if you rename this to `$w`, your `VueWait` methods will be accessible by `$w.waits(..)` etc. | 203 | | `useVuex` | `Boolean` | `false` | Use this value for enabling integration with `Vuex` store. When this value is true `VueWait` will store data in `Vuex` store and all changes to this data will be made by dispatching actions to store | 204 | | `vuexModuleName` | `String` | `"wait"` | Name for `Vuex` store if `useVuex` set to true, otherwise not used. | 205 | | `registerComponent` | `Boolean` | `true` | Registers `v-wait` component. | 206 | | `componentName` | `String` | `"v-wait"` | Changes `v-wait` component name. | 207 | | `registerDirective` | `Boolean` | `true` | Registers `v-wait` directive. | 208 | | `directiveName` | `String` | `"v-wait"` | Changes `v-wait` directive name. | 209 | 210 | ## 🌈 Global Template Helpers 211 | 212 | **vue-wait** provides some helpers to you to use in your templates. 213 | All features can be obtained from $wait property in Vue components. 214 | 215 | #### `.any` 216 | 217 | Returns boolean value if any loader exists in page. 218 | 219 | ```vue 220 | 223 | ``` 224 | 225 | #### `.is(loader String | Matcher)` or `.waiting(loader String | Matcher)` 226 | 227 | Returns boolean value if given loader exists in page. 228 | 229 | ```vue 230 | 233 | ``` 234 | 235 | You can use **`waiting`** alias instead of **`is`**. 236 | 237 | ```vue 238 | 243 | ``` 244 | 245 | Also you can use matcher to make it more flexible: 246 | 247 | Please see [matcher](https://github.com/sindresorhus/matcher/) library to see how to use matchers. 248 | 249 | ```vue 250 | 253 | ``` 254 | 255 | #### `.is(loaders Array)` or `.waiting(loaders Array)` 256 | 257 | Returns boolean value if some of given loaders exists in page. 258 | 259 | ```vue 260 | 263 | ``` 264 | 265 | #### `.start(loader String)` 266 | 267 | Starts the given loader. 268 | 269 | ```vue 270 | 273 | ``` 274 | 275 | #### `.end(loader String)` 276 | 277 | Stops the given loader. 278 | 279 | ```vue 280 | 283 | ``` 284 | 285 | #### `.progress(loader String, current [, total = 100])` 286 | 287 | Sets the progress of the given loader. 288 | 289 | ```vue 290 | 296 | ``` 297 | 298 | ##### Completing the Progress 299 | 300 | To complete the progress, `current` value should be set bigger than `100`. 301 | If you `total` is given, `current` must be bigger than `total`. 302 | 303 | ```vue 304 | 305 | ``` 306 | 307 | or 308 | 309 | ```vue 310 | 311 | ``` 312 | 313 | #### `.percent(loader String)` 314 | 315 | Returns the percentage of the given loader. 316 | 317 | ```vue 318 | 321 | ``` 322 | 323 | ## 🏹 Directives 324 | 325 | You can use directives to make your template cleaner. 326 | 327 | #### `v-wait:visible='"loader name"'` 328 | 329 | Shows if the given loader is loading. 330 | 331 | ```vue 332 | 335 | ``` 336 | 337 | #### `v-wait:hidden='"loader name"'` or `v-wait:visible.not='"loader name"'` 338 | 339 | Hides if the given loader is loading. 340 | 341 | ```vue 342 | 345 | ``` 346 | 347 | #### `v-wait:disabled='"loader name"'` 348 | 349 | Sets `disabled="disabled"` attribute to element if the given loader is loading. 350 | 351 | ```vue 352 | 356 | ``` 357 | 358 | #### `v-wait:enabled='"loader name"'` or `v-wait:disabled.not='"loader name"'` 359 | 360 | Removes `disabled="disabled"` attribute to element if the given loader is loading. 361 | 362 | ```vue 363 | 366 | ``` 367 | 368 | #### `v-wait:click.start='"loader name"'` 369 | 370 | Starts given loader on click. 371 | 372 | ```vue 373 | 376 | ``` 377 | 378 | #### `v-wait:click.end='"loader name"'` 379 | 380 | Ends given loader on click. 381 | 382 | ```vue 383 | 386 | ``` 387 | 388 | #### `v-wait:toggle='"loader name"'` 389 | 390 | Toggles given loader on click. 391 | 392 | ```vue 393 | 396 | ``` 397 | 398 | #### `v-wait:click.progress='["loader name", 80]'` 399 | 400 | Sets the progress of given loader on click. 401 | 402 | ```vue 403 | 406 | ``` 407 | 408 | ## 🔌 Loading Action and Getter Mappers 409 | 410 | **vue-wait** provides `mapWaitingActions` and `mapWaitingGetters` mapper to be used with your Vuex stores. 411 | 412 | Let's assume you have a store and async **action**s called `createUser` and `updateUser`. 413 | It will call the methods you map and will start loaders while action is resolved. 414 | 415 | ```js 416 | import { mapWaitingActions, mapWaitingGetters } from 'vue-wait' 417 | 418 | // ... 419 | methods: { 420 | ...mapWaitingActions('users', { 421 | getUsers: 'loading users', 422 | createUser: 'creating user', 423 | updateUser: 'updating user', 424 | }), 425 | }, 426 | computed: { 427 | ...mapWaitingGetters({ 428 | somethingWithUsers: [ 429 | 'loading users', 430 | 'creating user', 431 | 'updating user', 432 | ], 433 | deletingUser: 'deleting user', 434 | }), 435 | } 436 | // ... 437 | ``` 438 | 439 | You can also map **action** to custom method and customize loader name like in example below: 440 | 441 | ```js 442 | import { mapWaitingActions, mapWaitingGetters } from 'vue-wait' 443 | 444 | // ... 445 | methods: { 446 | ...mapWaitingActions('users', { 447 | getUsers: { action: 'getUsers', loader: 'loading users' }, 448 | createUser: { action: 'createUser', loader: 'creating user'}, 449 | createSuperUser: { action: 'createUser', loader: 'creating super user' }, 450 | }), 451 | }, 452 | // ... 453 | ``` 454 | 455 | There is also possibility to use array as a second argument to mapWaitingActions: 456 | ```js 457 | // ... 458 | methods: { 459 | ...mapWaitingActions('users', [ 460 | 'getUsers', 461 | { method: 'createUser', action: 'createUser', loader: 'creating user'}, 462 | { method: 'createSuperUser', action: 'createUser', loader: 'creating super user' }, 463 | ]), 464 | }, 465 | // ... 466 | 467 | 468 | ``` 469 | 470 | ### ☢️Advanced Getters and Actions Usage 471 | 472 | > The Vuex module name is `wait` by default. If you've changed on config, you should get it by `rootGetters['/is']` or `rootGetters['/any']`. 473 | 474 | You can access `vue-wait`'s Vuex getters using `rootGetters` in Vuex. 475 | 476 | ```js 477 | getters: { 478 | cartOperationInProgress(state, getters, rootState, rootGetters) { 479 | return rootGetters['wait/is']('cart.*'); 480 | } 481 | }, 482 | ``` 483 | 484 | And you can start and end loaders using `wait` actions. You must pass `root: true` option to the `dispatch` method. 485 | 486 | ```js 487 | actions: { 488 | async addItemToCart({ dispatch }, item) { 489 | dispatch('wait/start', 'cart.addItem', { root: true }); 490 | await CartService.addItem(item); 491 | dispatch('wait/end', 'cart.addItem', { root: true }); 492 | } 493 | }, 494 | ``` 495 | 496 | #### `waitFor(loader String, func Function [,forceSync = false])` 497 | 498 | Decorator that wraps function, will trigger a loading and will end loader after the original function (`func` argument) is finished. 499 | 500 | By default `waitFor` return async function, if you want to wrap default sync function pass `true` in last argument 501 | 502 | _Example using with async function_ 503 | 504 | ```js 505 | import { waitFor } from 'vue-wait'; 506 | 507 | ... 508 | methods: { 509 | fetchDataFromApi: waitFor('fetch data', async function () { 510 | function sleep(ms) { 511 | return new Promise(resolve => setTimeout(resolve, ms)); 512 | } 513 | // do work here 514 | await sleep(3000); 515 | // simulate some api call 516 | this.fetchResponse = Math.random() 517 | }) 518 | } 519 | ... 520 | ``` 521 | 522 | See also `examples/wrap-example` 523 | 524 | ## 💧 Using `v-wait` Component 525 | 526 | If you disable `registerComponent` option then import and add `v-wait` into components 527 | 528 | ```js 529 | import vLoading from 'vue-wait/src/components/v-wait.vue' 530 | components: { 531 | 'v-wait': vLoading 532 | } 533 | ``` 534 | 535 | In template, you should wrap your content with `v-wait` component to show loading on it. 536 | 537 | ```vue 538 | 539 | 542 | 543 | This will be shown when "fetching data" loader ends. 544 | 545 | ``` 546 | 547 | Better example for a `button` with loading state: 548 | 549 | ```vue 550 | 556 | ``` 557 | 558 | ## 🔁 Transitions 559 | 560 | You can use transitions with `v-wait` component. 561 | 562 | Just pass `` props and listeners to the `v-wait` with `transition` prop. 563 | 564 | ```vue 565 | 572 | 575 | My content 576 | 577 | ``` 578 | 579 | ## ⚡️ Making Reusable Loader Components 580 | 581 | With reusable loader components, you will be able to use custom loader components as example below. This will allow you to create better **user loading experience**. 582 | 583 | 584 | 585 | In this example above, the **tab gets data from back-end**, and the **table loads data from back-end at the same time**. With **vue-wait**, you will be able to manage these two seperated loading processes easily: 586 | 587 | ```vue 588 | 616 | ``` 617 | 618 | You may want to design your own reusable loader for your project. You better create a wrapper component called `my-waiter`: 619 | 620 | ```vue 621 | 622 | 623 | tr: 624 | loading: Yükleniyor... 625 | en: 626 | loading: Loading... 627 | 628 | 629 | 635 | 636 | 647 | ``` 648 | 649 | Now you can use your spinner everywhere using `slot='waiting'` attribute: 650 | 651 | ```vue 652 | 660 | ``` 661 | 662 | ## 📦 Using with external spinner libraries 663 | 664 | You can use `vue-wait` with another spinner libraries like [epic-spinners](https://github.com/epicmaxco/epic-spinners) or other libraries. You just need to add `slot="waiting"` to the component and Vue handles rest of the work. 665 | 666 | First register the component, 667 | ```js 668 | import { OrbitSpinner } from 'epic-spinners'; 669 | Vue.component('orbit-spinner', OrbitSpinner); 670 | ``` 671 | 672 | Then use it in your as a `v-wait`'s `waiting` slot. 673 | ```vue 674 | 675 | 681 | 682 | ``` 683 | 684 | ... and done! 685 | 686 | For other libraries you can use, please see [Loaders section of **vuejs/awesome-vue**](https://github.com/vuejs/awesome-vue#loader). 687 | 688 | ## 🚌 Run example 689 | 690 | Use `npm run dev-vuex`, `npm run dev-vue` or `npm run dev-wrap` commands. 691 | for running examples locally. 692 | 693 | ## ✔ Testing components 694 | 695 | You can test components using `vue-wait` but it requires configuration. Let's take a basic component for instance: 696 | 697 | ```vue 698 | 699 | 700 |
    701 |
  • {{ suggestion.Name }}
  • 702 |
703 |
704 | ``` 705 | 706 | ```js 707 | const localVue = createLocalVue(); 708 | localVue.use(Vuex); // optionally when you use Vuex integration 709 | 710 | it('uses vue-wait component', () => { 711 | const wrapper = shallowMount(Suggestions, { localVue }); 712 | expect(wrapper.find('.suggestions').exists()).toBe(true); 713 | }); 714 | ``` 715 | 716 | `vue-test-utils` will replace `v-wait` component with an empty `div`, making it difficult to test correctly. 717 | 718 | First, make your local Vue instance use `vue-wait`, 719 | 720 | ```js 721 | const localVue = createLocalVue(); 722 | localVue.use(Vuex); // optionally when you use Vuex integration 723 | localVue.use(VueWait); 724 | ``` 725 | 726 | Then inject the `wait` property using `VueWait` constructor, 727 | 728 | ```js 729 | it('uses vue-wait component', () => { 730 | const wrapper = shallowMount(SuggestedAddresses, { 731 | localVue, 732 | wait: new VueWait() 733 | }); 734 | expect(wrapper.find('.suggestions').exists()).toBe(true); // it works! 735 | }); 736 | ``` 737 | 738 | ## For Development on vue-wait 739 | Install packages 740 | ```bash 741 | $ yarn install 742 | # or if you using npm 743 | $ npm install 744 | ``` 745 | 746 | Bundle it 747 | ```bash 748 | $ yarn bundle 749 | # or if you using npm 750 | $ npm run bundle 751 | ``` 752 | 753 | ## 🎯 Contributors 754 | 755 | - Fatih Kadir Akın, (creator) 756 | - Igor, (maintainer, made Vuex-free) 757 | 758 | ## 🔗 Other Implementations 759 | 760 | Since **vue-wait** based on a very simple idea, it can be implemented on other frameworks. 761 | 762 | - [react-wait](https://github.com/f/react-wait): Multiple Process Loader Management for React. 763 | - [dom-wait](https://github.com/f/dom-wait): Multiple Process Loader Management for vanilla JavaScript. 764 | 765 | ## 🔑 License 766 | 767 | MIT © [Fatih Kadir Akın](https://github.com/f) 768 | -------------------------------------------------------------------------------- /dist/vue-wait-next.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("vue")):"function"==typeof define&&define.amd?define(["vue"],e):"object"==typeof exports?exports.VueWait=e(require("vue")):t.VueWait=e(t.vue)}("undefined"!=typeof self?self:this,(function(t){return(()=>{var e={757:(t,e,r)=>{t.exports=r(666)},150:t=>{"use strict";var e=/[|\\{}()[\]^$+*?.]/g;t.exports=function(t){if("string"!=typeof t)throw new TypeError("Expected a string");return t.replace(e,"\\$&")}},666:t=>{var e=function(t){"use strict";var e,r=Object.prototype,n=r.hasOwnProperty,i="function"==typeof Symbol?Symbol:{},o=i.iterator||"@@iterator",a=i.asyncIterator||"@@asyncIterator",s=i.toStringTag||"@@toStringTag";function c(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{c({},"")}catch(t){c=function(t,e,r){return t[e]=r}}function u(t,e,r,n){var i=e&&e.prototype instanceof y?e:y,o=Object.create(i.prototype),a=new L(n||[]);return o._invoke=function(t,e,r){var n=f;return function(i,o){if(n===h)throw new Error("Generator is already running");if(n===v){if("throw"===i)throw o;return A()}for(r.method=i,r.arg=o;;){var a=r.delegate;if(a){var s=S(a,r);if(s){if(s===d)continue;return s}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(n===f)throw n=v,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n=h;var c=l(t,e,r);if("normal"===c.type){if(n=r.done?v:p,c.arg===d)continue;return{value:c.arg,done:r.done}}"throw"===c.type&&(n=v,r.method="throw",r.arg=c.arg)}}}(t,r,a),o}function l(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}t.wrap=u;var f="suspendedStart",p="suspendedYield",h="executing",v="completed",d={};function y(){}function g(){}function m(){}var w={};c(w,o,(function(){return this}));var b=Object.getPrototypeOf,O=b&&b(b(P([])));O&&O!==r&&n.call(O,o)&&(w=O);var x=m.prototype=y.prototype=Object.create(w);function k(t){["next","throw","return"].forEach((function(e){c(t,e,(function(t){return this._invoke(e,t)}))}))}function j(t,e){function r(i,o,a,s){var c=l(t[i],t,o);if("throw"!==c.type){var u=c.arg,f=u.value;return f&&"object"==typeof f&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){r("next",t,a,s)}),(function(t){r("throw",t,a,s)})):e.resolve(f).then((function(t){u.value=t,a(u)}),(function(t){return r("throw",t,a,s)}))}s(c.arg)}var i;this._invoke=function(t,n){function o(){return new e((function(e,i){r(t,n,e,i)}))}return i=i?i.then(o,o):o()}}function S(t,r){var n=t.iterator[r.method];if(n===e){if(r.delegate=null,"throw"===r.method){if(t.iterator.return&&(r.method="return",r.arg=e,S(t,r),"throw"===r.method))return d;r.method="throw",r.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var i=l(n,t.iterator,r.arg);if("throw"===i.type)return r.method="throw",r.arg=i.arg,r.delegate=null,d;var o=i.arg;return o?o.done?(r[t.resultName]=o.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,d):o:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,d)}function _(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function E(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function L(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(_,this),this.reset(!0)}function P(t){if(t){var r=t[o];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var i=-1,a=function r(){for(;++i=0;--o){var a=this.tryEntries[o],s=a.completion;if("root"===a.tryLoc)return i("end");if(a.tryLoc<=this.prev){var c=n.call(a,"catchLoc"),u=n.call(a,"finallyLoc");if(c&&u){if(this.prev=0;--r){var i=this.tryEntries[r];if(i.tryLoc<=this.prev&&n.call(i,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),E(r),d}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var i=n.arg;E(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:P(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),d}},t}(t.exports);try{regeneratorRuntime=e}catch(t){"object"==typeof globalThis?globalThis.regeneratorRuntime=e:Function("r","regeneratorRuntime = r")(e)}},103:e=>{"use strict";e.exports=t}},r={};function n(t){var i=r[t];if(void 0!==i)return i.exports;var o=r[t]={exports:{}};return e[t](o,o.exports,n),o.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};return(()=>{"use strict";function t(t,e,r,n,i,o,a){try{var s=t[o](a),c=s.value}catch(t){return void r(t)}s.done?e(c):Promise.resolve(c).then(n,i)}function e(e){return function(){var r=this,n=arguments;return new Promise((function(i,o){var a=e.apply(r,n);function s(e){t(a,i,o,s,c,"next",e)}function c(e){t(a,i,o,s,c,"throw",e)}s(void 0)}))}}function r(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){for(var r=0;rY,default:()=>z,install:()=>q,mapWaitingActions:()=>E,mapWaitingGetters:()=>L,waitFor:()=>P});var s=n(757),c=n.n(s);function u(t){return(u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0:Array.isArray(e)?e.some((function(e){return b(t,e)})):t.includes(e)}function O(t){return t.length>0}function x(t,e){return w([].concat(p(t),[e]))}function k(t,e){return w(t).filter((function(t){return t!==e}))}function j(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:100;return n>i?S(t,e):m(m({},t),{},r({},e,{current:n,total:i,percent:100*n/i}))}function S(t,e){return t[e],function(t,e){if(null==t)return{};var r,n,i=function(t,e){if(null==t)return{};var r,n,i={},o=Object.keys(t);for(n=0;n=0||(i[r]=t[r]);return i}(t,e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(i[r]=t[r])}return i}(t,[e].map(y))}function _(t,e){var r=t[e];return r?r.percent:0}function E(t,r){var n={};"object"===u(t)&&(r=t,t=null);for(var i=Array.isArray(r),o=function(){var r,o,u=(r=s[a],o=2,function(t){if(Array.isArray(t))return t}(r)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,i,o=[],a=!0,s=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(o.push(n.value),!e||o.length!==e);a=!0);}catch(t){s=!0,i=t}finally{try{a||null==r.return||r.return()}finally{if(s)throw i}}return o}}(r,o)||f(r,o)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),l=u[0],p=u[1],h=void 0,v=void 0,d=void 0;p===Object(p)?(i?(h=p.action,void 0!==p.method&&(h=p.method)):h=l,v=p.action,d=p.loader):i?(h=v=p,d=p):(h=v=l,d=p),d||(d=v),v&&(n[h]=e(c().mark((function e(){var r,n,i,o,a=arguments;return c().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:for(e.prev=0,this.__$waitInstance.start(d),n=a.length,i=new Array(n),o=0;o2&&void 0!==arguments[2]&&arguments[2];return"function"!=typeof r?(console.warn("[vue-wait] waitFor second argument must be a function"),r):!0===n?function(){try{this.__$waitInstance.start(t);for(var e=arguments.length,n=new Array(e),i=0;i0&&void 0!==arguments[0]?arguments[0]:{};o(this,t);var r={accessorName:"$wait",useVuex:!1,vuexModuleName:"wait",registerComponent:!0,componentName:"v-wait",registerDirective:!0,directiveName:"wait"};this.options=R(R({},r),e),this.initialized=!1}var e,r,i;return e=t,i=[{key:"getVueVersion",value:function(t){return parseFloat((t.version||"").split(".")[0]||0)}}],(r=[{key:"init",value:function(e,r){if(!this.initialized){if(this.options.registerComponent&&e.component(this.options.componentName,G),this.options.registerDirective&&e.directive(this.options.directiveName,M),this.options.useVuex){var i=this.options.vuexModuleName;if(!r)throw new Error("[vue-wait] Vuex not initialized.");this.store=r,r._modules.get([i])||r.registerModule(i,C);var o={computed:{is:function(){return function(t){return r.getters["".concat(i,"/is")](t)}},any:function(){return r.getters["".concat(i,"/any")]},percent:function(){return function(t){return r.getters["".concat(i,"/percent")](t)}}}};if(t.getVueVersion(e)>2){var a=n(103).createApp;this.stateHandler=a(o).mount(document.createElement("div"))}else this.stateHandler=new e(o)}else{var s={data:function(){return{waitingFor:[],progresses:{}}},computed:{is:function(){var t=this;return function(e){return b(t.waitingFor,e)}},any:function(){return O(this.waitingFor)},percent:function(){var t=this;return function(e){return _(t.progresses,e)}}},methods:{start:function(t){this.waitingFor=x(this.waitingFor,t)},end:function(t){this.waitingFor=k(this.waitingFor,t),this.progresses=S(this.progresses,t)},progress:function(t){var e=t.waiter,r=t.current,n=t.total;this.progresses=j(this.progresses,e,r,n)}}};if(t.getVueVersion(e)>2){var c=n(103).createApp;this.stateHandler=c(s).mount(document.createElement("div"))}else this.stateHandler=new e(s)}this.initialized=!0}}},{key:"any",get:function(){return this.stateHandler.any}},{key:"is",value:function(t){return this.stateHandler.is(t)}},{key:"waiting",value:function(t){return this.is(t)}},{key:"percent",value:function(t){return this.stateHandler.percent(t)}},{key:"dispatchWaitingAction",value:function(t,e){var r=this.options.vuexModuleName;this.store.dispatch("".concat(r,"/").concat(t),e,{root:!0})}},{key:"start",value:function(t){this.options.useVuex?this.dispatchWaitingAction("start",t):this.stateHandler.start(t)}},{key:"end",value:function(t){this.options.useVuex?this.dispatchWaitingAction("end",t):this.stateHandler.end(t)}},{key:"progress",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:100;this.is(t)||this.start(t),e>r?this.end(t):this.options.useVuex?this.dispatchWaitingAction("progress",{waiter:t,current:e,total:r}):this.stateHandler.progress({waiter:t,current:e,total:r})}}])&&a(e.prototype,r),i&&a(e,i),t}();function q(t){q.installed&&t||(t.mixin({beforeCreate:function(){var e=this.$options,r=e.wait,n=e.store,i=e.parent,o=null;r?(o="function"==typeof r?new r:r).init(t,n):i&&i.__$waitInstance&&(o=i.__$waitInstance).init(t,i.$store),o&&(this.__$waitInstance=o,this[o.options.accessorName]=o)}}),q.installed=!0)}function Y(t){return{instance:null,install:function(r){var n=this;return e(c().mark((function e(){var i;return c().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!n.installed||!r){e.next=3;break}return e.abrupt("return",n.instance);case 3:return(i=new z(t)).init(r,r.config.globalProperties.$store),r.config.globalProperties[i.options.accessorName]=i,r.mixin({beforeCreate:function(){this.__$waitInstance=i}}),n.instance=i,n.installed=!0,e.abrupt("return",i);case 10:case"end":return e.stop()}}),e)})))()}}}z.install=q})(),i})()})); -------------------------------------------------------------------------------- /dist/vue-wait-v2.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("vue")):"function"==typeof define&&define.amd?define(["vue"],e):"object"==typeof exports?exports.VueWait=e(require("vue")):t.VueWait=e(t.vue)}("undefined"!=typeof self?self:this,(function(t){return(()=>{var e={757:(t,e,r)=>{t.exports=r(666)},150:t=>{"use strict";var e=/[|\\{}()[\]^$+*?.]/g;t.exports=function(t){if("string"!=typeof t)throw new TypeError("Expected a string");return t.replace(e,"\\$&")}},666:t=>{var e=function(t){"use strict";var e,r=Object.prototype,n=r.hasOwnProperty,i="function"==typeof Symbol?Symbol:{},o=i.iterator||"@@iterator",a=i.asyncIterator||"@@asyncIterator",s=i.toStringTag||"@@toStringTag";function c(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{c({},"")}catch(t){c=function(t,e,r){return t[e]=r}}function u(t,e,r,n){var i=e&&e.prototype instanceof y?e:y,o=Object.create(i.prototype),a=new P(n||[]);return o._invoke=function(t,e,r){var n=f;return function(i,o){if(n===h)throw new Error("Generator is already running");if(n===v){if("throw"===i)throw o;return A()}for(r.method=i,r.arg=o;;){var a=r.delegate;if(a){var s=S(a,r);if(s){if(s===d)continue;return s}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(n===f)throw n=v,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n=h;var c=l(t,e,r);if("normal"===c.type){if(n=r.done?v:p,c.arg===d)continue;return{value:c.arg,done:r.done}}"throw"===c.type&&(n=v,r.method="throw",r.arg=c.arg)}}}(t,r,a),o}function l(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}t.wrap=u;var f="suspendedStart",p="suspendedYield",h="executing",v="completed",d={};function y(){}function g(){}function m(){}var b={};c(b,o,(function(){return this}));var w=Object.getPrototypeOf,O=w&&w(w(L([])));O&&O!==r&&n.call(O,o)&&(b=O);var _=m.prototype=y.prototype=Object.create(b);function x(t){["next","throw","return"].forEach((function(e){c(t,e,(function(t){return this._invoke(e,t)}))}))}function j(t,e){function r(i,o,a,s){var c=l(t[i],t,o);if("throw"!==c.type){var u=c.arg,f=u.value;return f&&"object"==typeof f&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){r("next",t,a,s)}),(function(t){r("throw",t,a,s)})):e.resolve(f).then((function(t){u.value=t,a(u)}),(function(t){return r("throw",t,a,s)}))}s(c.arg)}var i;this._invoke=function(t,n){function o(){return new e((function(e,i){r(t,n,e,i)}))}return i=i?i.then(o,o):o()}}function S(t,r){var n=t.iterator[r.method];if(n===e){if(r.delegate=null,"throw"===r.method){if(t.iterator.return&&(r.method="return",r.arg=e,S(t,r),"throw"===r.method))return d;r.method="throw",r.arg=new TypeError("The iterator does not provide a 'throw' method")}return d}var i=l(n,t.iterator,r.arg);if("throw"===i.type)return r.method="throw",r.arg=i.arg,r.delegate=null,d;var o=i.arg;return o?o.done?(r[t.resultName]=o.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,d):o:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,d)}function E(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function k(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function P(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(E,this),this.reset(!0)}function L(t){if(t){var r=t[o];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var i=-1,a=function r(){for(;++i=0;--o){var a=this.tryEntries[o],s=a.completion;if("root"===a.tryLoc)return i("end");if(a.tryLoc<=this.prev){var c=n.call(a,"catchLoc"),u=n.call(a,"finallyLoc");if(c&&u){if(this.prev=0;--r){var i=this.tryEntries[r];if(i.tryLoc<=this.prev&&n.call(i,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),k(r),d}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var i=n.arg;k(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:L(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),d}},t}(t.exports);try{regeneratorRuntime=e}catch(t){"object"==typeof globalThis?globalThis.regeneratorRuntime=e:Function("r","regeneratorRuntime = r")(e)}},103:e=>{"use strict";e.exports=t}},r={};function n(t){var i=r[t];if(void 0!==i)return i.exports;var o=r[t]={exports:{}};return e[t](o,o.exports,n),o.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};return(()=>{"use strict";function t(t,e,r,n,i,o,a){try{var s=t[o](a),c=s.value}catch(t){return void r(t)}s.done?e(c):Promise.resolve(c).then(n,i)}function e(e){return function(){var r=this,n=arguments;return new Promise((function(i,o){var a=e.apply(r,n);function s(e){t(a,i,o,s,c,"next",e)}function c(e){t(a,i,o,s,c,"throw",e)}s(void 0)}))}}function r(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){for(var r=0;rH,default:()=>D,install:()=>G,mapWaitingActions:()=>k,mapWaitingGetters:()=>P,waitFor:()=>L});var s=n(757),c=n.n(s);function u(t){return(u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0:Array.isArray(e)?e.some((function(e){return w(t,e)})):t.includes(e)}function O(t){return t.length>0}function _(t,e){return b([].concat(p(t),[e]))}function x(t,e){return b(t).filter((function(t){return t!==e}))}function j(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:100;return n>i?S(t,e):m(m({},t),{},r({},e,{current:n,total:i,percent:100*n/i}))}function S(t,e){return t[e],function(t,e){if(null==t)return{};var r,n,i=function(t,e){if(null==t)return{};var r,n,i={},o=Object.keys(t);for(n=0;n=0||(i[r]=t[r]);return i}(t,e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(i[r]=t[r])}return i}(t,[e].map(y))}function E(t,e){var r=t[e];return r?r.percent:0}function k(t,r){var n={};"object"===u(t)&&(r=t,t=null);for(var i=Array.isArray(r),o=function(){var r,o,u=(r=s[a],o=2,function(t){if(Array.isArray(t))return t}(r)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,i,o=[],a=!0,s=!1;try{for(r=r.call(t);!(a=(n=r.next()).done)&&(o.push(n.value),!e||o.length!==e);a=!0);}catch(t){s=!0,i=t}finally{try{a||null==r.return||r.return()}finally{if(s)throw i}}return o}}(r,o)||f(r,o)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),l=u[0],p=u[1],h=void 0,v=void 0,d=void 0;p===Object(p)?(i?(h=p.action,void 0!==p.method&&(h=p.method)):h=l,v=p.action,d=p.loader):i?(h=v=p,d=p):(h=v=l,d=p),d||(d=v),v&&(n[h]=e(c().mark((function e(){var r,n,i,o,a=arguments;return c().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:for(e.prev=0,this.__$waitInstance.start(d),n=a.length,i=new Array(n),o=0;o2&&void 0!==arguments[2]&&arguments[2];return"function"!=typeof r?(console.warn("[vue-wait] waitFor second argument must be a function"),r):!0===n?function(){try{this.__$waitInstance.start(t);for(var e=arguments.length,n=new Array(e),i=0;i0&&void 0!==arguments[0]?arguments[0]:{};o(this,t);var r={accessorName:"$wait",useVuex:!1,vuexModuleName:"wait",registerComponent:!0,componentName:"v-wait",registerDirective:!0,directiveName:"wait"};this.options=V(V({},r),e),this.initialized=!1}var e,r,i;return e=t,i=[{key:"getVueVersion",value:function(t){return parseFloat((t.version||"").split(".")[0]||0)}}],(r=[{key:"init",value:function(e,r){if(!this.initialized){if(this.options.registerComponent&&e.component(this.options.componentName,F),this.options.registerDirective&&e.directive(this.options.directiveName,N),this.options.useVuex){var i=this.options.vuexModuleName;if(!r)throw new Error("[vue-wait] Vuex not initialized.");this.store=r,r._modules.get([i])||r.registerModule(i,C);var o={computed:{is:function(){return function(t){return r.getters["".concat(i,"/is")](t)}},any:function(){return r.getters["".concat(i,"/any")]},percent:function(){return function(t){return r.getters["".concat(i,"/percent")](t)}}}};if(t.getVueVersion(e)>2){var a=n(103).createApp;this.stateHandler=a(o).mount(document.createElement("div"))}else this.stateHandler=new e(o)}else{var s={data:function(){return{waitingFor:[],progresses:{}}},computed:{is:function(){var t=this;return function(e){return w(t.waitingFor,e)}},any:function(){return O(this.waitingFor)},percent:function(){var t=this;return function(e){return E(t.progresses,e)}}},methods:{start:function(t){this.waitingFor=_(this.waitingFor,t)},end:function(t){this.waitingFor=x(this.waitingFor,t),this.progresses=S(this.progresses,t)},progress:function(t){var e=t.waiter,r=t.current,n=t.total;this.progresses=j(this.progresses,e,r,n)}}};if(t.getVueVersion(e)>2){var c=n(103).createApp;this.stateHandler=c(s).mount(document.createElement("div"))}else this.stateHandler=new e(s)}this.initialized=!0}}},{key:"any",get:function(){return this.stateHandler.any}},{key:"is",value:function(t){return this.stateHandler.is(t)}},{key:"waiting",value:function(t){return this.is(t)}},{key:"percent",value:function(t){return this.stateHandler.percent(t)}},{key:"dispatchWaitingAction",value:function(t,e){var r=this.options.vuexModuleName;this.store.dispatch("".concat(r,"/").concat(t),e,{root:!0})}},{key:"start",value:function(t){this.options.useVuex?this.dispatchWaitingAction("start",t):this.stateHandler.start(t)}},{key:"end",value:function(t){this.options.useVuex?this.dispatchWaitingAction("end",t):this.stateHandler.end(t)}},{key:"progress",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:100;this.is(t)||this.start(t),e>r?this.end(t):this.options.useVuex?this.dispatchWaitingAction("progress",{waiter:t,current:e,total:r}):this.stateHandler.progress({waiter:t,current:e,total:r})}}])&&a(e.prototype,r),i&&a(e,i),t}();function G(t){G.installed&&t||(t.mixin({beforeCreate:function(){var e=this.$options,r=e.wait,n=e.store,i=e.parent,o=null;r?(o="function"==typeof r?new r:r).init(t,n):i&&i.__$waitInstance&&(o=i.__$waitInstance).init(t,i.$store),o&&(this.__$waitInstance=o,this[o.options.accessorName]=o)}}),G.installed=!0)}function H(t){return{instance:null,install:function(r){var n=this;return e(c().mark((function e(){var i;return c().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!n.installed||!r){e.next=3;break}return e.abrupt("return",n.instance);case 3:return(i=new D(t)).init(r,r.config.globalProperties.$store),r.config.globalProperties[i.options.accessorName]=i,r.mixin({beforeCreate:function(){this.__$waitInstance=i}}),n.instance=i,n.installed=!0,e.abrupt("return",i);case 10:case"end":return e.stop()}}),e)})))()}}}D.install=G})(),i})()})); -------------------------------------------------------------------------------- /examples/transition-example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueWait from '../../src/vue-wait'; 3 | 4 | import main from './main.vue'; 5 | 6 | Vue.use(VueWait); 7 | 8 | new Vue({ 9 | el: '#app', 10 | wait: new VueWait({ registerComponents: false }), 11 | render: function(createElement) { 12 | return createElement(main); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /examples/transition-example/main.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 60 | 61 | 83 | -------------------------------------------------------------------------------- /examples/vue-example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueWait from '../../src/vue-wait'; 3 | import { OrbitSpinner } from 'epic-spinners'; 4 | 5 | import main from './main.vue'; 6 | 7 | Vue.use(VueWait); 8 | 9 | Vue.component('orbit-spinner', OrbitSpinner); 10 | 11 | new Vue({ 12 | el: '#app', 13 | wait: new VueWait({ registerComponents: false }), 14 | render: function(createElement) { 15 | return createElement(main); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /examples/vue-example/main.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 76 | 77 | 155 | -------------------------------------------------------------------------------- /examples/vuex-example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import VueWait from '../../src/vue-wait'; 4 | 5 | import main from './main.vue'; 6 | 7 | Vue.use(VueWait); 8 | Vue.use(Vuex); 9 | 10 | const store = new Vuex.Store({ 11 | state: { 12 | counter: 1 13 | }, 14 | getters: { 15 | count: state => state.counter 16 | }, 17 | actions: { 18 | incrementAsync({ commit }) { 19 | return new Promise(resolve => { 20 | setTimeout(() => { 21 | commit('increment'); 22 | resolve(); 23 | }, 3000); 24 | }); 25 | } 26 | }, 27 | mutations: { 28 | increment(state) { 29 | state.counter += 1; 30 | } 31 | } 32 | }); 33 | 34 | const wait = new VueWait({ 35 | useVuex: true, 36 | vuexModuleName: 'vuex-example-module', 37 | accessorName: '$l' 38 | }); 39 | 40 | new Vue({ 41 | el: '#app', 42 | store, 43 | wait, 44 | render: function(createElement) { 45 | return createElement(main); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /examples/vuex-example/main.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 127 | 128 | 225 | -------------------------------------------------------------------------------- /examples/wrap-example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueWait from '../../src/vue-wait'; 3 | 4 | import main from './main.vue'; 5 | 6 | Vue.use(VueWait); 7 | 8 | new Vue({ 9 | el: '#app', 10 | wait: new VueWait({ 11 | registerComponents: false, 12 | componentName: 'my-waiter' 13 | }), 14 | render: function(createElement) { 15 | return createElement(main); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /examples/wrap-example/main.vue: -------------------------------------------------------------------------------- 1 | 30 | 61 | 62 | 63 | 141 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("vue")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["vue"], factory); 6 | else if(typeof exports === 'object') 7 | exports["VueWait"] = factory(require("vue")); 8 | else 9 | root["VueWait"] = factory(root["vue"]); 10 | })(typeof self !== 'undefined' ? self : this, function(vue) { 11 | if (vue.createApp) { 12 | const v3 = require('./dist/vue-wait-next'); 13 | return v3 14 | } 15 | const v2 = require('./dist/vue-wait-v2'); 16 | return v2 17 | }) 18 | -------------------------------------------------------------------------------- /nuxt/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Nuxt.js module for vue-wait 3 | 4 | Usage: 5 | - Install vue-wait package 6 | - Add this into your nuxt.config.js file: 7 | { 8 | modules: [ 9 | // Simple usage 10 | 'vue-wait/nuxt' 11 | 12 | // Optionally passing options in module configuration 13 | ['vue-wait/nuxt', { useVuex: true }] 14 | ], 15 | 16 | // Optionally passing options in module top level configuration 17 | wait: { useVuex: true } 18 | } 19 | */ 20 | 21 | const {resolve} = require('path'); 22 | 23 | module.exports = function nuxtVueWaitModule(moduleOptions) { 24 | const options = Object.assign({}, this.options.wait, moduleOptions); 25 | 26 | // Register plugin 27 | this.addPlugin({ 28 | src: resolve(__dirname, 'vue-wait-plugin.template.js'), 29 | fileName: 'vue-wait-plugin.js', 30 | options: options 31 | }) 32 | }; 33 | 34 | // required by nuxt 35 | module.exports.meta = require('../package.json'); 36 | -------------------------------------------------------------------------------- /nuxt/vue-wait-plugin.template.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueWait from 'vue-wait'; 3 | 4 | Vue.use(VueWait); // add VueLoading as Vue plugin 5 | 6 | export default ({app}) => { 7 | // inject options from module 8 | const pluginOptions = [<%= serialize(options) %>][0] 9 | app.wait = new VueWait(pluginOptions) 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-wait", 3 | "description": "Vue Plugin for Global Loading Management", 4 | "version": "1.5.3", 5 | "license": "MIT", 6 | "author": "Fatih Kadir Akın ", 7 | "keywords": [ 8 | "vue", 9 | "vuex", 10 | "nuxt", 11 | "plugin", 12 | "loading", 13 | "loader" 14 | ], 15 | "homepage": "https://github.com/f/vue-wait#readme", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/f/vue-wait.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/f/vue-wait/issues" 22 | }, 23 | "main": "index.js", 24 | "types": "src/types/index.d.ts", 25 | "files": [ 26 | "dist", 27 | "src", 28 | "nuxt" 29 | ], 30 | "scripts": { 31 | "test": "exit 0;", 32 | "precommit": "lint-staged", 33 | "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", 34 | "bundle": "npm run build; npm run build -- --env=v3", 35 | "dev-transition": "poi examples/transition-example/index.js", 36 | "dev-vuex": "poi examples/vuex-example/index.js", 37 | "dev-vue": "poi examples/vue-example/index.js", 38 | "dev-wrap": "poi examples/wrap-example/index.js", 39 | "build-gh-pages": "poi build --out-dir=/tmp/gh-pages examples/vuex-example/index.js" 40 | }, 41 | "lint-staged": { 42 | "src/**/*.js": [ 43 | "prettier --single-quote --write", 44 | "git add" 45 | ], 46 | "examples/**/*.js": [ 47 | "prettier --single-quote --write", 48 | "git add" 49 | ] 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "^7.14.8", 53 | "@babel/plugin-transform-runtime": "^7.14.5", 54 | "@babel/preset-env": "^7.14.8", 55 | "@vue/compiler-sfc": "^3.1.5", 56 | "babel-loader": "^8.2.2", 57 | "cross-env": "^5.1.3", 58 | "css-loader": "^0.28.11", 59 | "epic-spinners": "^1.0.3", 60 | "escape-string-regexp": "^1.0.5", 61 | "eslint": "^4.2.0", 62 | "husky": "^0.14.3", 63 | "lint-staged": "^7.1.3", 64 | "poi": "^10.2.3", 65 | "prettier": "^1.1.0", 66 | "uglifyjs-webpack-plugin": "^1.2.5", 67 | "vue-html-loader": "^1.2.4", 68 | "vue-loader": "^16.3.3", 69 | "vue-loader-old": "npm:vue-loader@^15.9.6", 70 | "vue-style-loader": "^4.1.3", 71 | "vue-template-compiler": "^2.6.14", 72 | "vuex": "^3.6.2", 73 | "webpack": "^5.47.0", 74 | "webpack-cli": "^4.7.2" 75 | }, 76 | "peerDependencies": { 77 | "vue": "^2.5.16" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/logo.png -------------------------------------------------------------------------------- /resources/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/logo.sketch -------------------------------------------------------------------------------- /resources/vue-ui-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/vue-ui-install.png -------------------------------------------------------------------------------- /resources/vue-wait-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/vue-wait-2.gif -------------------------------------------------------------------------------- /resources/vue-wait.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/vue-wait.gif -------------------------------------------------------------------------------- /resources/vuex-dev-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-wait/c2e093fabbc08c2d5bf3ba79a74583232583fbc8/resources/vuex-dev-tools.png -------------------------------------------------------------------------------- /src/components/v-wait.vue: -------------------------------------------------------------------------------- 1 | 36 | 91 | -------------------------------------------------------------------------------- /src/directives/wait.js: -------------------------------------------------------------------------------- 1 | function bind(el, binding, vNode, oldVNode) { 2 | const { arg, modifiers, value } = binding; 3 | const instance = vNode.context.__$waitInstance; 4 | switch (arg) { 5 | case 'click': 6 | if (modifiers.start) { 7 | el.addEventListener('click', () => instance.start(value), false); 8 | break; 9 | } 10 | if (modifiers.end) { 11 | el.addEventListener('click', () => instance.end(value), false); 12 | break; 13 | } 14 | if (modifiers.progress) { 15 | el.addEventListener('click', () => instance.progress(...value), false); 16 | break; 17 | } 18 | break; 19 | case 'toggle': 20 | el.addEventListener( 21 | 'click', 22 | () => { 23 | const isWaiting = instance.is(value); 24 | if (!isWaiting) { 25 | instance.start(value); 26 | } else { 27 | instance.end(value); 28 | } 29 | }, 30 | false 31 | ); 32 | break; 33 | } 34 | 35 | update(el, binding, vNode, oldVNode); 36 | } 37 | 38 | function update(el, binding, vNode, oldVNode) { 39 | const { arg, modifiers, value } = binding; 40 | const instance = vNode.context.__$waitInstance; 41 | 42 | let isWaiting = instance.is(value); 43 | if (modifiers.not || ['hidden', 'enabled'].includes(arg)) { 44 | isWaiting = !isWaiting; 45 | } 46 | 47 | const originalDisplay = el.style.display === 'none' ? '' : el.style.display; 48 | switch (arg) { 49 | case 'visible': 50 | case 'hidden': 51 | el.style.display = !isWaiting ? 'none' : originalDisplay; 52 | break; 53 | case 'enabled': 54 | case 'disabled': 55 | isWaiting 56 | ? el.setAttribute('disabled', true) 57 | : el.removeAttribute('disabled'); 58 | break; 59 | } 60 | } 61 | 62 | export default { 63 | bind, 64 | update 65 | }; 66 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | export function mapWaitingActions(vuexModuleName, actions) { 2 | const mappings = {}; 3 | if (typeof vuexModuleName === 'object') { 4 | actions = vuexModuleName; 5 | vuexModuleName = null; 6 | } 7 | const isActionsArray = Array.isArray(actions); 8 | 9 | for (let [key, entry] of Object.entries(actions)) { 10 | let method, action, waiter; 11 | if (entry === Object(entry)) { 12 | if (isActionsArray) { 13 | method = entry.action; 14 | if (entry.method !== undefined) { 15 | method = entry.method; 16 | } 17 | } else { 18 | method = key; 19 | } 20 | action = entry.action; 21 | waiter = entry.loader; 22 | } else { 23 | if (isActionsArray) { 24 | method = action = entry; 25 | waiter = entry; 26 | } else { 27 | method = action = key; 28 | waiter = entry; 29 | } 30 | } 31 | if (!waiter) { 32 | waiter = action; 33 | } 34 | if (action) { 35 | mappings[method] = async function(...args) { 36 | try { 37 | this.__$waitInstance.start(waiter); 38 | return await this.$store.dispatch( 39 | vuexModuleName ? `${vuexModuleName}/${action}` : action, 40 | ...args 41 | ); 42 | } finally { 43 | this.__$waitInstance.end(waiter); 44 | } 45 | }; 46 | } 47 | } 48 | return mappings; 49 | } 50 | 51 | export function mapWaitingGetters(getters) { 52 | const mappings = {}; 53 | Object.keys(getters).forEach(getter => { 54 | const waiter = getters[getter]; 55 | mappings[getter] = function() { 56 | return this.__$waitInstance.is(waiter); 57 | }; 58 | }); 59 | return mappings; 60 | } 61 | 62 | export function waitFor(waiter, func, forceSync = false) { 63 | if (typeof func !== 'function') { 64 | console.warn('[vue-wait] waitFor second argument must be a function'); 65 | return func; 66 | } 67 | 68 | if (forceSync === true) { 69 | return function(...args) { 70 | try { 71 | this.__$waitInstance.start(waiter); 72 | return func.apply(this, args); 73 | } finally { 74 | this.__$waitInstance.end(waiter); 75 | } 76 | }; 77 | } 78 | return async function(...args) { 79 | try { 80 | this.__$waitInstance.start(waiter); 81 | return await func.apply(this, args); 82 | } finally { 83 | this.__$waitInstance.end(waiter); 84 | } 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/lib/matcher.js: -------------------------------------------------------------------------------- 1 | // clone of sindresorhus/matcher 2 | 'use strict'; 3 | const escapeStringRegexp = require('escape-string-regexp'); 4 | 5 | const reCache = new Map(); 6 | 7 | function makeRe(pattern, options) { 8 | const opts = Object.assign( 9 | { 10 | caseSensitive: false 11 | }, 12 | options 13 | ); 14 | 15 | const cacheKey = pattern + JSON.stringify(opts); 16 | 17 | if (reCache.has(cacheKey)) { 18 | return reCache.get(cacheKey); 19 | } 20 | 21 | const negated = pattern[0] === '!'; 22 | 23 | if (negated) { 24 | pattern = pattern.slice(1); 25 | } 26 | 27 | pattern = escapeStringRegexp(pattern).replace(/\\\*/g, '.*'); 28 | 29 | const re = new RegExp(`^${pattern}$`, opts.caseSensitive ? '' : 'i'); 30 | re.negated = negated; 31 | reCache.set(cacheKey, re); 32 | 33 | return re; 34 | } 35 | 36 | export const isMatch = (input, pattern, options) => { 37 | const re = makeRe(pattern, options); 38 | const matches = re.test(input); 39 | return re.negated ? !matches : matches; 40 | }; 41 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { PluginFunction } from 'vue'; 2 | import { Store } from 'vuex'; 3 | import './vue'; 4 | 5 | 6 | type AsyncFunction = ((arg0: any) => Promise) | Promise; 7 | 8 | export default class VueWait extends VueWaitInstance { 9 | constructor(options?: VueWaitOptions); 10 | 11 | static install(): PluginFunction; 12 | 13 | static init(Vue: Vue, store: Store): void; 14 | } 15 | 16 | /** 17 | * The vue-wait Instance 18 | */ 19 | 20 | export class VueWaitInstance { 21 | /** 22 | * Returns boolean value if any loader exists in page. 23 | * 24 | * @returns {boolean} 25 | * @memberof VueWaitInstance 26 | */ 27 | any(): boolean; 28 | 29 | /** 30 | * Returns boolean value if some of given loaders exists in page. 31 | * 32 | * @param {(string|string[])} waiter 33 | * @returns {boolean} 34 | * @memberof VueWaitInstance 35 | */ 36 | is(waiter: string|string[]): boolean; 37 | 38 | /** 39 | * Returns boolean value if some of given loaders exists in page. 40 | * 41 | * @param {(string|string[])} waiter 42 | * @returns {boolean} 43 | * @memberof VueWaitInstance 44 | */ 45 | waiting(waiter: string|string[]): boolean; 46 | 47 | /** 48 | * Returns the percentage of the given loader. 49 | * 50 | * @param {string} waiter 51 | * @returns {number} 52 | * @memberof VueWaitInstance 53 | */ 54 | percent(waiter: string): number; 55 | 56 | /** 57 | * Starts the given loader. 58 | * 59 | * @param {string} waiter 60 | * @returns {void} 61 | * @memberof VueWaitInstance 62 | */ 63 | start(waiter: string): void; 64 | 65 | /** 66 | * Stops the given loader. 67 | * 68 | * @param {string} waiter 69 | * @returns {void} 70 | * @memberof VueWaitInstance 71 | */ 72 | end(waiter: string): void; 73 | 74 | /** 75 | * Sets the progress of the given loader. 76 | * 77 | * @param {string} waiter 78 | * @param {number} current 79 | * @param {number} [total] 80 | * @memberof VueWaitInstance 81 | */ 82 | progress(waiter: string, current: number, total?: number): Promise; 83 | 84 | waitFor(waiter: string, callback: T, forceSync?: false): T; 85 | } 86 | 87 | export interface VueWaitOptions{ 88 | /** 89 | * You can change this value to rename the accessor. E.g. if you rename this to $w, your VueWait methods will be accessible by $w.waits(..) etc. 90 | */ 91 | accessorName?: string, 92 | /** 93 | * Use this value for enabling integration with Vuex store. When this value is true VueWait will store data in Vuex store and all changes to this data will be made by dispatching actions to store 94 | */ 95 | useVuex?: boolean, 96 | /** 97 | * Name for Vuex store if useVuex set to true, otherwise not used. 98 | */ 99 | vuexModuleName?: string, 100 | /** 101 | * Registers v-wait component. 102 | */ 103 | registerComponent?: boolean, 104 | /** 105 | * Changes v-wait component name. 106 | */ 107 | componentName?: string; 108 | /** 109 | * Registers v-wait directive. 110 | */ 111 | registerDirective?: boolean; 112 | /** 113 | * Changes v-wait directive name. 114 | */ 115 | directiveName?: string; 116 | } 117 | 118 | export interface CreateVueWaitResponse{ 119 | /** 120 | * Can accessible VueWait instance after registration 121 | */ 122 | instance?: VueWaitInstance | null; 123 | 124 | /** 125 | * Install function for registration 126 | */ 127 | install(options: object): Promise; 128 | } 129 | 130 | export function mapWaitingGetters(getters: any): any; 131 | 132 | export function mapWaitingActions(vuexModuleName: any, actions?: any): any; 133 | 134 | export function createVueWait(options?: VueWaitOptions): CreateVueWaitResponse; 135 | -------------------------------------------------------------------------------- /src/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extends interfaces in Vue.js 3 | */ 4 | 5 | import Vue from 'vue'; 6 | import VueWait from './index'; 7 | 8 | declare module 'vue/types/vue' { 9 | interface Vue { 10 | $wait: VueWait; 11 | } 12 | } 13 | 14 | declare module 'vue/types/options' { 15 | interface ComponentOptions { 16 | wait?: VueWait; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { isMatch } from './lib/matcher'; 2 | 3 | function uniqArray(array) { 4 | return array.filter((el, index, arr) => index == arr.indexOf(el)); 5 | } 6 | 7 | export function is(waitingFor, waiter) { 8 | if (typeof waiter === 'string' && waiter.match(/[\*\!]/)) { 9 | return waitingFor.filter(w => isMatch(w, waiter)).length > 0; 10 | } 11 | return Array.isArray(waiter) 12 | ? waiter.some(w => is(waitingFor, w)) 13 | : waitingFor.includes(waiter); 14 | } 15 | 16 | export function any(waitingFor) { 17 | return waitingFor.length > 0; 18 | } 19 | 20 | export function start(waitingFor, waiter) { 21 | return uniqArray([...waitingFor, waiter]); 22 | } 23 | 24 | export function end(waitingFor, waiter) { 25 | return uniqArray(waitingFor).filter(l => l !== waiter); 26 | } 27 | 28 | export function progress(progresses, waiter, current, total = 100) { 29 | if (current > total) { 30 | return endProgress(progresses, waiter); 31 | } 32 | 33 | return { 34 | ...progresses, 35 | [waiter]: { 36 | current, 37 | total, 38 | percent: (100 * current) / total 39 | } 40 | }; 41 | } 42 | 43 | export function endProgress(progresses, waiter) { 44 | const { [waiter]: omit, ...omittedProgresses } = progresses; 45 | return omittedProgresses; 46 | } 47 | 48 | export function percent(progresses, waiter) { 49 | const progress = progresses[waiter]; 50 | if (!progress) return 0; 51 | 52 | return progress.percent; 53 | } 54 | 55 | export function nodeIsDebug() { 56 | return process.env.NODE_ENV !== 'production'; 57 | } 58 | -------------------------------------------------------------------------------- /src/vue-wait.js: -------------------------------------------------------------------------------- 1 | import { 2 | is, 3 | any, 4 | start, 5 | end, 6 | progress, 7 | percent, 8 | endProgress, 9 | nodeIsDebug 10 | } from './utils'; 11 | 12 | // Import to export 13 | import { mapWaitingActions, mapWaitingGetters, waitFor } from './helpers'; 14 | 15 | import vuexStore from './vuex/store'; 16 | import vWaitComponent from './components/v-wait.vue'; 17 | import vWaitDirective from './directives/wait.js'; 18 | 19 | export default class VueWait { 20 | constructor(options = {}) { 21 | const defaults = { 22 | accessorName: '$wait', 23 | // Vuex Options 24 | useVuex: false, 25 | vuexModuleName: 'wait', 26 | // Components 27 | registerComponent: true, 28 | componentName: 'v-wait', 29 | // Directives 30 | registerDirective: true, 31 | directiveName: 'wait' 32 | }; 33 | this.options = { 34 | ...defaults, 35 | ...options 36 | }; 37 | this.initialized = false; 38 | } 39 | 40 | init(App, store) { 41 | if (nodeIsDebug() && !install.installed && VueWait.getVueVersion(App) < 3) { 42 | console.warn( 43 | `[vue-wait] not installed. Make sure to call \`Vue.use(VueWait)\` before init root instance.` 44 | ); 45 | } 46 | 47 | if (this.initialized) { 48 | return; 49 | } 50 | 51 | if (this.options.registerComponent) { 52 | App.component(this.options.componentName, vWaitComponent); 53 | } 54 | 55 | if (this.options.registerDirective) { 56 | App.directive(this.options.directiveName, vWaitDirective); 57 | } 58 | 59 | if (this.options.useVuex) { 60 | const { vuexModuleName } = this.options; 61 | if (!store) { 62 | throw new Error('[vue-wait] Vuex not initialized.'); 63 | } 64 | this.store = store; 65 | 66 | if (!store._modules.get([vuexModuleName])) { 67 | store.registerModule(vuexModuleName, vuexStore); 68 | } 69 | 70 | const config = { 71 | computed: { 72 | is: () => waiter => store.getters[`${vuexModuleName}/is`](waiter), 73 | any: () => store.getters[`${vuexModuleName}/any`], 74 | percent: () => waiter => 75 | store.getters[`${vuexModuleName}/percent`](waiter) 76 | } 77 | }; 78 | 79 | if (VueWait.getVueVersion(App) > 2) { 80 | const { createApp } = require('vue'); 81 | this.stateHandler = createApp(config).mount( 82 | document.createElement('div') 83 | ); 84 | } else { 85 | this.stateHandler = new App(config); 86 | } 87 | } else { 88 | const config = { 89 | data() { 90 | return { 91 | waitingFor: [], 92 | progresses: {} 93 | }; 94 | }, 95 | computed: { 96 | is() { 97 | return waiter => is(this.waitingFor, waiter); 98 | }, 99 | any() { 100 | return any(this.waitingFor); 101 | }, 102 | percent() { 103 | return waiter => percent(this.progresses, waiter); 104 | } 105 | }, 106 | methods: { 107 | start(waiter) { 108 | this.waitingFor = start(this.waitingFor, waiter); 109 | }, 110 | end(waiter) { 111 | this.waitingFor = end(this.waitingFor, waiter); 112 | this.progresses = endProgress(this.progresses, waiter); 113 | }, 114 | progress({ waiter, current, total }) { 115 | this.progresses = progress(this.progresses, waiter, current, total); 116 | } 117 | } 118 | }; 119 | 120 | if (VueWait.getVueVersion(App) > 2) { 121 | const { createApp } = require('vue'); 122 | this.stateHandler = createApp(config).mount( 123 | document.createElement('div') 124 | ); 125 | } else { 126 | this.stateHandler = new App(config); 127 | } 128 | } 129 | 130 | this.initialized = true; 131 | } 132 | 133 | get any() { 134 | return this.stateHandler.any; 135 | } 136 | 137 | static getVueVersion(app) { 138 | return parseFloat((app.version || '').split('.')[0] || 0); 139 | } 140 | 141 | is(waiter) { 142 | return this.stateHandler.is(waiter); 143 | } 144 | 145 | // alias for `is` 146 | waiting(waiter) { 147 | return this.is(waiter); 148 | } 149 | 150 | percent(waiter) { 151 | return this.stateHandler.percent(waiter); 152 | } 153 | 154 | dispatchWaitingAction(action, waiter) { 155 | const { vuexModuleName } = this.options; 156 | this.store.dispatch(`${vuexModuleName}/${action}`, waiter, { 157 | root: true 158 | }); 159 | } 160 | 161 | start(waiter) { 162 | if (this.options.useVuex) { 163 | this.dispatchWaitingAction('start', waiter); 164 | return; 165 | } 166 | this.stateHandler.start(waiter); 167 | } 168 | 169 | end(waiter) { 170 | if (this.options.useVuex) { 171 | this.dispatchWaitingAction('end', waiter); 172 | return; 173 | } 174 | this.stateHandler.end(waiter); 175 | } 176 | 177 | progress(waiter, current, total = 100) { 178 | if (!this.is(waiter)) { 179 | this.start(waiter); 180 | } 181 | 182 | if (current > total) { 183 | this.end(waiter); 184 | return; 185 | } 186 | 187 | if (this.options.useVuex) { 188 | this.dispatchWaitingAction('progress', { waiter, current, total }); 189 | return; 190 | } 191 | this.stateHandler.progress({ waiter, current, total }); 192 | } 193 | } 194 | 195 | export function install(App) { 196 | if (install.installed && App) { 197 | if (nodeIsDebug()) { 198 | console.warn( 199 | '[vue-wait] already installed. Vue.use(VueWait) should be called only once.' 200 | ); 201 | } 202 | return; 203 | } 204 | 205 | App.mixin({ 206 | /** 207 | * VueWait init hook, injected into each instances init hooks list. 208 | */ 209 | beforeCreate() { 210 | const { wait, store, parent } = this.$options; 211 | 212 | let instance = null; 213 | if (wait) { 214 | instance = typeof wait === 'function' ? new wait() : wait; 215 | // Inject store 216 | instance.init(App, store); 217 | } else if (parent && parent.__$waitInstance) { 218 | instance = parent.__$waitInstance; 219 | instance.init(App, parent.$store); 220 | } 221 | 222 | if (instance) { 223 | // Store helper for internal use 224 | this.__$waitInstance = instance; 225 | this[instance.options.accessorName] = instance; 226 | } 227 | } 228 | }); 229 | 230 | install.installed = true; 231 | } 232 | 233 | function createVueWait(options) { 234 | const Wait = { 235 | instance: null, 236 | async install(app) { 237 | if (this.installed && app) { 238 | if (nodeIsDebug()) { 239 | console.warn('[vue-wait] already installed'); 240 | } 241 | return this.instance; 242 | } 243 | 244 | const instance = new VueWait(options); 245 | instance.init(app, app.config.globalProperties.$store); 246 | app.config.globalProperties[instance.options.accessorName] = instance; 247 | 248 | app.mixin({ 249 | beforeCreate() { 250 | this.__$waitInstance = instance; 251 | } 252 | }); 253 | 254 | this.instance = instance; 255 | this.installed = true; 256 | return instance; 257 | } 258 | }; 259 | 260 | return Wait; 261 | } 262 | 263 | // Export which are imported to export 264 | export { mapWaitingActions, mapWaitingGetters, waitFor, createVueWait }; 265 | 266 | VueWait.install = install; 267 | -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import { is, any, start, end, progress, percent, endProgress } from '../utils'; 2 | 3 | const mutations = { 4 | START: 'START', 5 | END: 'END', 6 | PROGRESS: 'PROGRESS' 7 | }; 8 | 9 | export default { 10 | namespaced: true, 11 | state: { 12 | waitingFor: [], 13 | progresses: {} 14 | }, 15 | getters: { 16 | is: state => waiter => is(state.waitingFor, waiter), 17 | any: state => any(state.waitingFor), 18 | percent: state => waiter => percent(state.progresses, waiter) 19 | }, 20 | actions: { 21 | start: ({ commit }, waiter) => commit(mutations.START, waiter), 22 | end: ({ commit }, waiter) => commit(mutations.END, waiter), 23 | progress: ({ commit }, progress) => commit(mutations.PROGRESS, progress) 24 | }, 25 | mutations: { 26 | [mutations.START](state, waiter) { 27 | state.waitingFor = start(state.waitingFor, waiter); 28 | }, 29 | [mutations.END](state, waiter) { 30 | state.waitingFor = end(state.waitingFor, waiter); 31 | state.progresses = endProgress(state.progresses, waiter); 32 | }, 33 | [mutations.PROGRESS](state, { waiter, current, total }) { 34 | state.progresses = progress(state.progresses, waiter, current, total); 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = env => { 2 | const isV3 = !!env.v3 3 | const loaderName = `vue-loader${isV3 ? '': '-old'}`; 4 | const { VueLoaderPlugin } = require(loaderName); 5 | 6 | return { 7 | mode: process.env.NODE_ENV, 8 | entry: ["./src/vue-wait.js"], 9 | output: { 10 | library: "VueWait", 11 | libraryTarget: "umd", 12 | globalObject: 'typeof self !== \'undefined\' ? self : this', 13 | filename: `vue-wait${isV3 ? '-next': '-v2'}.js` 14 | }, 15 | externals: { vue: 'vue' }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.vue$/, 20 | loader: loaderName 21 | }, 22 | { 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | use: { 26 | loader: "babel-loader" 27 | } 28 | } 29 | ] 30 | }, 31 | plugins: [new VueLoaderPlugin()] 32 | } 33 | }; 34 | --------------------------------------------------------------------------------