├── .eslintrc ├── .gitignore ├── .storybook ├── addons.js ├── config.js ├── configureStorybook.js ├── styles.css └── webpack.config.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── STARTER_DOCS.md ├── code-of-conduct.md ├── package-lock.json ├── package.json ├── public ├── branding │ ├── logo-storybook.png │ ├── logo.png │ └── vue-use-kit.psd └── demo │ ├── muybridge.png │ ├── video1.mp4 │ └── video2.mp4 ├── rollup.config.ts ├── src ├── api.ts ├── functions │ ├── getQuery │ │ ├── getQuery.spec.ts │ │ ├── getQuery.ts │ │ ├── index.ts │ │ └── stories │ │ │ ├── GetQueryDemo.vue │ │ │ ├── getQuery.md │ │ │ └── getQuery.story.ts │ ├── useBeforeUnload │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseBeforeUnloadDemo.vue │ │ │ ├── useBeforeUnload.md │ │ │ └── useBeforeUnload.story.ts │ │ ├── useBeforeUnload.spec.ts │ │ └── useBeforeUnload.ts │ ├── useClickAway │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseClickAwayDemo.vue │ │ │ ├── useClickAway.md │ │ │ └── useClickAway.story.ts │ │ ├── useClickAway.spec.ts │ │ └── useClickAway.ts │ ├── useCookie │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseCookieDemo.vue │ │ │ ├── useCookie.md │ │ │ └── useCookie.story.ts │ │ ├── useCookie.spec.ts │ │ └── useCookie.ts │ ├── useFetch │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseFetchDemo.vue │ │ │ ├── UseFetchDemoTable.vue │ │ │ ├── useFetch.md │ │ │ └── useFetch.story.ts │ │ ├── useFetch.spec.ts │ │ └── useFetch.ts │ ├── useFullscreen │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseFullscreenDemo.vue │ │ │ ├── useFullscreen.md │ │ │ └── useFullscreen.story.ts │ │ ├── useFullscreen.spec.ts │ │ └── useFullscreen.ts │ ├── useGeolocation │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseGeolocationDemo.vue │ │ │ ├── useGeolocation.md │ │ │ └── useGeolocation.story.ts │ │ ├── useGeolocation.spec.ts │ │ └── useGeolocation.ts │ ├── useHover │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseHoverDemo.vue │ │ │ ├── useHover.md │ │ │ └── useHover.story.ts │ │ ├── useHover.spec.ts │ │ └── useHover.ts │ ├── useIdle │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseIdleDemo.vue │ │ │ ├── useIdle.md │ │ │ └── useIdle.story.ts │ │ ├── useIdle.spec.ts │ │ └── useIdle.ts │ ├── useIntersection │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseIntersectionAdvancedDemo.vue │ │ │ ├── UseIntersectionDemo.vue │ │ │ ├── UseIntersectionElementDemo.vue │ │ │ ├── useIntersection.md │ │ │ └── useIntersection.story.ts │ │ ├── useIntersection.spec.ts │ │ └── useIntersection.ts │ ├── useInterval │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseIntervalDemo.vue │ │ │ ├── useInterval.md │ │ │ └── useInterval.story.ts │ │ ├── useInterval.spec.ts │ │ └── useInterval.ts │ ├── useIntervalFn │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseIntervalFnDemo.vue │ │ │ ├── useIntervalFn.md │ │ │ └── useIntervalFn.story.ts │ │ ├── useIntervalFn.spec.ts │ │ └── useIntervalFn.ts │ ├── useKey │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseKeyDemo.vue │ │ │ ├── useKey.md │ │ │ └── useKey.story.ts │ │ ├── useKey.spec.ts │ │ └── useKey.ts │ ├── useLocalStorage │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseLocalStorageDemo.vue │ │ │ ├── useLocalStorage.md │ │ │ └── useLocalStorage.story.ts │ │ ├── useLocalStorage.spec.ts │ │ └── useLocalStorage.ts │ ├── useLocation │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseLocationDemo.vue │ │ │ ├── useLocation.md │ │ │ └── useLocation.story.ts │ │ ├── useLocation.spec.ts │ │ └── useLocation.ts │ ├── useMedia │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseMediaAdvancedDemo.vue │ │ │ ├── UseMediaDemo.vue │ │ │ ├── useMedia.md │ │ │ └── useMedia.story.ts │ │ ├── useMedia.spec.ts │ │ └── useMedia.ts │ ├── useMediaDevices │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseMediaDevicesDemo.vue │ │ │ ├── useMediaDevices.md │ │ │ └── useMediaDevices.story.ts │ │ ├── useMediaDevices.spec.ts │ │ └── useMediaDevices.ts │ ├── useMouse │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseMouseAdvancedDemo.vue │ │ │ ├── UseMouseDemo.vue │ │ │ ├── useMouse.md │ │ │ └── useMouse.story.ts │ │ ├── useMouse.spec.ts │ │ └── useMouse.ts │ ├── useMouseElement │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseMouseElementDemo.vue │ │ │ ├── useMouseElement.md │ │ │ └── useMouseElement.story.ts │ │ ├── useMouseElement.spec.ts │ │ └── useMouseElement.ts │ ├── useMouseLeavePage │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseMouseLeavePageDemo.vue │ │ │ ├── useMouseLeavePage.md │ │ │ └── useMouseLeavePage.story.ts │ │ ├── useMouseLeavePage.spec.ts │ │ └── useMouseLeavePage.ts │ ├── useOrientation │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseOrientationDemo.vue │ │ │ ├── useOrientation.md │ │ │ └── useOrientation.story.ts │ │ ├── useOrientation.spec.ts │ │ └── useOrientation.ts │ ├── useRaf │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseRafDemo.vue │ │ │ ├── useRaf.md │ │ │ └── useRaf.story.ts │ │ ├── useRaf.spec.ts │ │ └── useRaf.ts │ ├── useRafFn │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseRafFnAdvancedDemo.vue │ │ │ ├── UseRafFnDemo.vue │ │ │ ├── useRafFn.md │ │ │ └── useRafFn.story.ts │ │ ├── useRafFn.spec.ts │ │ └── useRafFn.ts │ ├── useSampleComponent │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseSampleComponentDemo.vue │ │ │ ├── useSampleComponent.md │ │ │ └── useSampleComponent.storySample.ts │ │ ├── useSampleComponent.spec.ts │ │ └── useSampleComponent.ts │ ├── useScroll │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseScrollDemo.vue │ │ │ ├── useScroll.md │ │ │ └── useScroll.story.ts │ │ ├── useScroll.spec.ts │ │ └── useScroll.ts │ ├── useScrollbarWidth │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseScrollbarWidthDemo.vue │ │ │ ├── useScrollbarWidth.md │ │ │ └── useScrollbarWidth.story.ts │ │ ├── useScrollbarWidth.spec.ts │ │ └── useScrollbarWidth.ts │ ├── useSearchParams │ │ ├── index.ts │ │ ├── stories │ │ │ ├── Field.vue │ │ │ ├── UseSearchParamsDemo.vue │ │ │ ├── useSearchParams.md │ │ │ └── useSearchParams.story.ts │ │ ├── useSearchParams.spec.ts │ │ └── useSearchParams.ts │ ├── useSessionStorage │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseSessionStorageDemo.vue │ │ │ ├── useSessionStorage.md │ │ │ └── useSessionStorage.story.ts │ │ ├── useSessionStorage.spec.ts │ │ └── useSessionStorage.ts │ ├── useSize │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseSizeDemo.vue │ │ │ ├── useSize.md │ │ │ └── useSize.story.ts │ │ ├── useSize.spec.ts │ │ └── useSize.ts │ ├── useTimeout │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseTimeoutDemo.vue │ │ │ ├── useTimeout.md │ │ │ └── useTimeout.story.ts │ │ ├── useTimeout.spec.ts │ │ └── useTimeout.ts │ ├── useTimeoutFn │ │ ├── index.ts │ │ ├── stories │ │ │ ├── UseTimeoutFnDemo.vue │ │ │ ├── useTimeoutFn.md │ │ │ └── useTimeoutFn.story.ts │ │ ├── useTimeoutFn.spec.ts │ │ └── useTimeoutFn.ts │ └── useWindowSize │ │ ├── index.ts │ │ ├── stories │ │ ├── UseWindowSizeDemo.vue │ │ ├── useWindowSize.md │ │ └── useWindowSize.story.ts │ │ ├── useWindowSize.spec.ts │ │ └── useWindowSize.ts ├── helpers │ ├── StoryTitle.vue │ ├── config.ts │ └── test.ts ├── shared │ ├── cookies.ts │ ├── createStorage.ts │ └── utils.ts ├── types │ └── ResizeObserver.ts ├── vue-shims.d.ts └── vue-use-kit.ts ├── tools ├── build-clean.ts ├── config.ts ├── gh-pages-publish.ts └── rollup.config.ts ├── tsconfig.build.json ├── tsconfig.eslint.json └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "jest", "prettier"], 4 | "extends": [ 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "parserOptions": { 11 | "project": "./tsconfig.eslint.json" 12 | }, 13 | "rules": { 14 | "prettier/prettier": "error", 15 | "@typescript-eslint/explicit-function-return-type": "off", 16 | "@typescript-eslint/no-explicit-any": "off", 17 | "@typescript-eslint/unbound-method": "off", 18 | "@typescript-eslint/no-unused-vars": ["warn", { 19 | "vars": "all", 20 | "args": "after-used", 21 | "ignoreRestSiblings": false, 22 | "varsIgnorePattern": "Ref" 23 | }] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .DS_Store 5 | *.log 6 | .vscode 7 | .idea 8 | dist 9 | compiled 10 | .awcache 11 | .rpt2_cache 12 | docs 13 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-viewport/register'; 2 | import '@storybook/addon-notes/register-panel'; 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/vue' 2 | import { loadStories } from './configureStorybook' 3 | import Vue from 'vue' 4 | import VueCompositionApi from '@vue/composition-api' 5 | 6 | Vue.use(VueCompositionApi) 7 | 8 | configure(loadStories, module) 9 | -------------------------------------------------------------------------------- /.storybook/configureStorybook.js: -------------------------------------------------------------------------------- 1 | import { addParameters } from '@storybook/vue' 2 | import { create } from '@storybook/theming' 3 | import pkg from '../package.json' 4 | // Global styling and icons 5 | import './styles.css' 6 | import 'bulma/css/bulma.css' 7 | import '@fortawesome/fontawesome-free/css/all.css' 8 | 9 | const basicTheme = create({ 10 | base: 'light', 11 | brandTitle: '🛠️ Vue use kit', 12 | brandUrl: pkg.repository.url, 13 | brandImage: 'https://raw.githubusercontent.com/microcipcip/vue-use-kit/master/public/branding/logo-storybook.png', 14 | }) 15 | 16 | addParameters({ 17 | options: { 18 | showPanel: true, 19 | panelPosition: 'right', 20 | theme: basicTheme 21 | } 22 | }) 23 | 24 | export function loadStories() { 25 | const req = require.context('../src', true, /\.story\.ts$/) 26 | req.keys().forEach(mod => req(mod)) 27 | } 28 | -------------------------------------------------------------------------------- /.storybook/styles.css: -------------------------------------------------------------------------------- 1 | .sb-show-main { 2 | padding: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const rootDir = path.resolve(__dirname, '..') 3 | 4 | module.exports = ({ config }) => { 5 | // Enable typescript support 6 | config.module.rules.push({ 7 | test: /\.tsx?$/, 8 | use: [ 9 | { 10 | loader: require.resolve('babel-loader'), 11 | options: { 12 | compact: true 13 | } 14 | }, 15 | { 16 | loader: require.resolve('ts-loader'), 17 | options: { 18 | appendTsSuffixTo: [/\.vue$/] 19 | } 20 | } 21 | ] 22 | }) 23 | 24 | config.resolve.alias = Object.assign(config.resolve.alias, { 25 | '@src': path.resolve(rootDir, 'src') 26 | }) 27 | 28 | config.node = { 29 | __dirname: true 30 | } 31 | 32 | config.resolve.extensions.push('.ts', '.tsx') 33 | return config 34 | } 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - ~/.npm 5 | notifications: 6 | email: false 7 | node_js: 8 | - '10' 9 | script: 10 | - npm run test:prod && npm run build 11 | after_success: 12 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run deploy-docs"; fi 13 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run semantic-release"; fi 14 | branches: 15 | except: 16 | - /^v\d+\.\d+\.\d+$/ 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We're really glad you're reading this, because we need volunteer developers to help this project come to fruition. 👏 2 | 3 | ## Instructions 4 | 5 | These steps will guide you through contributing to this project: 6 | 7 | - Fork the repo 8 | - Clone it and install dependencies 9 | 10 | git clone https://github.com/YOUR-USERNAME/typescript-library-starter 11 | npm install 12 | 13 | Keep in mind that after running `npm install` the git repo is reset. So a good way to cope with this is to have a copy of the folder to push the changes, and the other to try them. 14 | 15 | Make and commit your changes. Make sure the commands npm run build and npm run test:prod are working. 16 | 17 | Finally send a [GitHub Pull Request](https://github.com/alexjoverm/typescript-library-starter/compare?expand=1) with a clear list of what you've done (read more [about pull requests](https://help.github.com/articles/about-pull-requests/)). Make sure all of your commits are atomic (one feature per commit). 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Salvatore Tedde 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /public/branding/logo-storybook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/branding/logo-storybook.png -------------------------------------------------------------------------------- /public/branding/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/branding/logo.png -------------------------------------------------------------------------------- /public/branding/vue-use-kit.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/branding/vue-use-kit.psd -------------------------------------------------------------------------------- /public/demo/muybridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/demo/muybridge.png -------------------------------------------------------------------------------- /public/demo/video1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/demo/video1.mp4 -------------------------------------------------------------------------------- /public/demo/video2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microcipcip/vue-use-kit/584bc1a3a050fcb82fc8cc3d825c3b8611edbbb0/public/demo/video2.mp4 -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import resolve from 'rollup-plugin-node-resolve' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | import camelCase from 'lodash.camelcase' 5 | import typescript from 'rollup-plugin-typescript2' 6 | import json from 'rollup-plugin-json' 7 | import ttypescript from 'ttypescript' 8 | 9 | const pkg = require('./package.json') 10 | 11 | const libraryName = 'vue-use-kit' 12 | 13 | const formatDate = () => { 14 | const pad = s => (s < 10 ? '0' + s : s) 15 | const d = new Date() 16 | return [pad(d.getDate()), pad(d.getMonth() + 1), d.getFullYear()].join('/') 17 | } 18 | 19 | const banner = `/** 20 | * Name: Vue use kit 21 | * Author: ${pkg.author} 22 | * Website: ${pkg.repository.url} 23 | * Date: ${formatDate()} 24 | */` 25 | 26 | export default { 27 | input: `src/${libraryName}.ts`, 28 | output: [ 29 | { 30 | banner, 31 | file: pkg.main, 32 | name: camelCase(libraryName), 33 | format: 'umd', 34 | globals: { 35 | vue: 'Vue', 36 | '@vue/composition-api': 'vueCompositionApi' 37 | } 38 | }, 39 | { banner, file: pkg.module, format: 'es' } 40 | ], 41 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 42 | external: ['vue', '@vue/composition-api'], 43 | watch: { include: 'src/**' }, 44 | plugins: [ 45 | // Allow json resolution 46 | json(), 47 | // Compile TypeScript files 48 | typescript({ 49 | // Transform typescript aliases because otherwise type definitions 50 | // will have the wrong import path 51 | typescript: ttypescript, 52 | tsconfigDefaults: { 53 | compilerOptions: { 54 | plugins: [ 55 | { transform: 'typescript-transform-paths' }, 56 | { transform: 'typescript-transform-paths', afterDeclarations: true } 57 | ] 58 | } 59 | }, 60 | useTsconfigDeclarationDir: true 61 | }), 62 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 63 | commonjs(), 64 | // Allow node_modules resolution, so you can use 'external' to control 65 | // which external modules to include in the bundle 66 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 67 | resolve() 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | export * from '@vue/composition-api' 2 | export { default } from '@vue/composition-api' 3 | -------------------------------------------------------------------------------- /src/functions/getQuery/getQuery.spec.ts: -------------------------------------------------------------------------------- 1 | import { getQuery } from '@src/vue-use-kit' 2 | 3 | describe('getQuery', () => { 4 | it('should throw when no min or max value is provided', () => { 5 | const t = () => getQuery() 6 | expect(t).toThrow(Error) 7 | }) 8 | 9 | it('should return the expected queries', () => { 10 | // max width check 11 | const maxWidthQuery = getQuery(0, 500) 12 | expect(maxWidthQuery).not.toContain('min-width') 13 | expect(maxWidthQuery).toContain('max-width') 14 | 15 | // min and max width check 16 | const minAndMaxWidthQuery = getQuery(500, 1024) 17 | expect(minAndMaxWidthQuery).toContain('min-width') 18 | expect(minAndMaxWidthQuery).toContain('max-width') 19 | 20 | // min width check 21 | const minWidthQuery = getQuery(1024) 22 | expect(minWidthQuery).toContain('min-width') 23 | expect(minWidthQuery).not.toContain('max-width') 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/functions/getQuery/getQuery.ts: -------------------------------------------------------------------------------- 1 | export function getQuery(min = 0, max = 0): string { 2 | if (!min && !max) { 3 | throw new Error(`Please specify at least one value, either 'min' or 'max'`) 4 | } else if (min && !max) { 5 | return `only screen and (min-width: ${min}px)` 6 | } else if (!min && max) { 7 | return `only screen and (max-width: ${max - 1}px)` 8 | } else { 9 | return `only screen and (min-width: ${min}px) and (max-width: ${max - 1}px)` 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/functions/getQuery/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getQuery' 2 | -------------------------------------------------------------------------------- /src/functions/getQuery/stories/GetQueryDemo.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /src/functions/getQuery/stories/getQuery.md: -------------------------------------------------------------------------------- 1 | # getQuery 2 | 3 | getQuery is an utility function that helps you build a min max media query. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function getQuery( 9 | min?: number, 10 | max?: number 11 | ): string 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `min: number` the min value to use to build the media query 17 | - `max: number` the max value to use to build the media query 18 | 19 | ### Returns 20 | 21 | - `query: string` the media query value 22 | 23 | ## Usage 24 | 25 | ```typescript 26 | import { getQuery } from 'vue-use-kit' 27 | 28 | const query = getQuery(0, 300) 29 | // only screen and (max-width: 299px) 30 | 31 | const query = getQuery(300, 1024) 32 | // only screen and (min-width: 300px) and (max-width: 1023px) 33 | 34 | const query = getQuery(1024) 35 | // only screen and (min-width: 1024px) 36 | ``` 37 | -------------------------------------------------------------------------------- /src/functions/getQuery/stories/getQuery.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import GetQueryDemo from './GetQueryDemo.vue' 5 | 6 | const functionName = 'getQuery' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: GetQueryDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('utils|getQuery', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useBeforeUnload' 2 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/stories/UseBeforeUnloadDemo.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 52 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/stories/useBeforeUnload.md: -------------------------------------------------------------------------------- 1 | # useBeforeUnload 2 | 3 | Vue function that shows browser alert when user try to reload or close the page. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useBeforeUnload( 9 | isPageDirty: Ref, 10 | runOnMount?: boolean 11 | ): { 12 | isTracking: Ref 13 | start: () => void 14 | stop: () => void 15 | } 16 | ``` 17 | 18 | ### Parameters 19 | 20 | - `isPageDirty: Ref` when this value is `true` value, it will show the browser alert on page change 21 | - `runOnMount: boolean` whether to listen to the 'beforeunload' event on mount, `true` by default 22 | 23 | ### Returns 24 | 25 | - `isTracking: Ref` whether this function events are running or not 26 | - `start: Function` the function used to start tracking page change or reload 27 | - `stop: Function` the function used to stop tracking page change or reload 28 | 29 | ## Usage 30 | 31 | ```html 32 | 45 | 46 | 60 | ``` 61 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/stories/useBeforeUnload.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseBeforeUnloadDemo from './UseBeforeUnloadDemo.vue' 5 | 6 | const functionName = 'useBeforeUnload' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseBeforeUnloadDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 30 | 31 | 32 |
` 33 | }) 34 | 35 | storiesOf('side effects|useBeforeUnload', module) 36 | .addParameters({ notes }) 37 | .add('Demo', basicDemo) 38 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/useBeforeUnload.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkElementExistenceOnMount, 3 | checkOnMountAndUnmountEvents, 4 | checkOnStartEvents, 5 | checkOnStopEvents 6 | } from '@src/helpers/test' 7 | import { ref } from '@src/api' 8 | import { useBeforeUnload } from '@src/vue-use-kit' 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks() 12 | }) 13 | 14 | const testComponent = (onMount = true) => ({ 15 | template: ` 16 |
17 |
18 | 19 | 20 |
21 | `, 22 | setup() { 23 | const isDirty = ref(false) 24 | const { isTracking, start, stop } = useBeforeUnload(isDirty, onMount) 25 | return { isTracking, start, stop } 26 | } 27 | }) 28 | 29 | describe('useBeforeUnload', () => { 30 | const events = ['beforeunload'] 31 | 32 | it('should add events on mounted and remove them on unmounted', async () => { 33 | await checkOnMountAndUnmountEvents(window, events, testComponent) 34 | }) 35 | 36 | it('should add events again when start is called', async () => { 37 | await checkOnStartEvents(window, events, testComponent) 38 | }) 39 | 40 | it('should remove events when stop is called', async () => { 41 | await checkOnStopEvents(window, events, testComponent) 42 | }) 43 | 44 | it('should show #isTracking when runOnMount is true', async () => { 45 | await checkElementExistenceOnMount(true, testComponent(true)) 46 | }) 47 | 48 | it('should not show #isTracking when runOnMount is false', async () => { 49 | await checkElementExistenceOnMount(false, testComponent(false)) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /src/functions/useBeforeUnload/useBeforeUnload.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useBeforeUnload(isPageDirty: Ref, runOnMount = true) { 4 | const isTracking = ref(false) 5 | 6 | const handleBeforeUnload = (e: BeforeUnloadEvent) => { 7 | // Show alert message only when isPageDirty is true 8 | if (isPageDirty.value) e.preventDefault() 9 | } 10 | 11 | const start = () => { 12 | if (isTracking.value) return 13 | window.addEventListener('beforeunload', handleBeforeUnload) 14 | isTracking.value = true 15 | } 16 | 17 | const stop = () => { 18 | if (!isTracking.value) return 19 | window.removeEventListener('beforeunload', handleBeforeUnload) 20 | isTracking.value = false 21 | } 22 | 23 | onMounted(() => runOnMount && start()) 24 | onUnmounted(stop) 25 | 26 | return { isTracking, start, stop } 27 | } 28 | -------------------------------------------------------------------------------- /src/functions/useClickAway/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useClickAway' 2 | -------------------------------------------------------------------------------- /src/functions/useClickAway/stories/UseClickAwayDemo.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 59 | -------------------------------------------------------------------------------- /src/functions/useClickAway/stories/useClickAway.md: -------------------------------------------------------------------------------- 1 | # useClickAway 2 | 3 | Vue function that triggers a callback when the user clicks outside of the target area. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useClickAway( 9 | elRef: Ref, 10 | callback: (e: Event) => void, 11 | events?: string[] 12 | ): void; 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `elRef: Ref` the element to check for click away events 18 | - `callback: Function` the callback to run when triggering a click away 19 | - `events: string[]` list of events to listen to, defaults to `['mousedown', 'touchstart']` 20 | 21 | ## Usage 22 | 23 | ```html 24 | 30 | 31 | 50 | ``` 51 | -------------------------------------------------------------------------------- /src/functions/useClickAway/stories/useClickAway.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseClickAwayDemo from './UseClickAwayDemo.vue' 5 | 6 | const functionName = 'useClickAway' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseClickAwayDemo }, 12 | template: ` 13 |
14 | 15 | 16 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('ui|useClickAway', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useClickAway/useClickAway.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkOnMountAndUnmountEvents } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useClickAway } from '@src/vue-use-kit' 4 | 5 | afterEach(() => { 6 | jest.clearAllMocks() 7 | }) 8 | 9 | const testComponent = () => ({ 10 | template: ` 11 |
12 | 13 | 14 |
15 | `, 16 | setup() { 17 | const dropdownRef = ref(null) 18 | const isDropdownOpen = ref(false) 19 | 20 | useClickAway(dropdownRef, () => { 21 | isDropdownOpen.value = true 22 | }) 23 | 24 | return { dropdownRef, isDropdownOpen } 25 | } 26 | }) 27 | 28 | describe('useClickAway', () => { 29 | const events = ['mousedown', 'touchstart'] 30 | 31 | it('should call addEventListener on mount and unmount', async () => { 32 | await checkOnMountAndUnmountEvents(document, events, testComponent) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/functions/useClickAway/useClickAway.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | const defaultEvents = ['mousedown', 'touchstart'] 4 | 5 | export function useClickAway( 6 | elRef: Ref, 7 | callback: (e: Event) => void, 8 | events = defaultEvents 9 | ) { 10 | const handler = (e: Event) => { 11 | if (elRef.value && !elRef.value.contains(e.target as Node)) { 12 | callback(e) 13 | } 14 | } 15 | 16 | onMounted(() => { 17 | events.forEach(evtName => document.addEventListener(evtName, handler)) 18 | }) 19 | 20 | onUnmounted(() => { 21 | events.forEach(evtName => document.removeEventListener(evtName, handler)) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/useCookie/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCookie' 2 | -------------------------------------------------------------------------------- /src/functions/useCookie/stories/UseCookieDemo.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 85 | -------------------------------------------------------------------------------- /src/functions/useCookie/stories/useCookie.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseCookieDemo from './UseCookieDemo.vue' 5 | 6 | const functionName = 'useCookie' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseCookieDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('side effects|useCookie', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useCookie/useCookie.ts: -------------------------------------------------------------------------------- 1 | import { CookieSerializeOptions } from 'cookie' 2 | import { Cookies } from '@src/shared/cookies' 3 | import { 4 | createSerializer, 5 | createDeserializer, 6 | SerializerFunction, 7 | DeserializerFunction, 8 | trySerialize, 9 | tryDeserialize, 10 | isNullOrUndefined 11 | } from '@src/shared/utils' 12 | import { ref, onMounted, Ref } from '@src/api' 13 | 14 | export interface UseCookieOptions { 15 | isParsing: boolean 16 | serializer?: SerializerFunction 17 | deserializer?: DeserializerFunction 18 | } 19 | 20 | const defaultOptions = { 21 | isParsing: false 22 | } 23 | 24 | export function useCookie( 25 | cookieName: string, 26 | options?: UseCookieOptions, 27 | runOnMount = true 28 | ) { 29 | const { isParsing, ...opts } = Object.assign({}, defaultOptions, options) 30 | const serializer = createSerializer(opts.serializer) 31 | const deserializer = createDeserializer(opts.deserializer) 32 | 33 | const cookieLib = Cookies() 34 | const cookie: Ref = ref(null) 35 | 36 | const getCookie = () => { 37 | const cookieVal = tryDeserialize( 38 | cookieLib.get(cookieName), 39 | deserializer, 40 | isParsing 41 | ) 42 | if (!isNullOrUndefined(cookieVal)) cookie.value = cookieVal 43 | } 44 | 45 | const setCookie = ( 46 | // The user may pass a 'string', a 'number', a valid JSON object/array 47 | // or even a custom object when serializer/deserializer are defined 48 | // so it is better to set allowed cookie value as 'any' 49 | newVal: any, 50 | options?: CookieSerializeOptions 51 | ) => { 52 | const newCookieVal = trySerialize(newVal, serializer, isParsing) 53 | cookieLib.set(cookieName, newCookieVal, options) 54 | cookie.value = tryDeserialize(newCookieVal, deserializer, isParsing) 55 | } 56 | 57 | const removeCookie = (options?: CookieSerializeOptions) => { 58 | cookieLib.remove(cookieName, options) 59 | cookie.value = null 60 | } 61 | 62 | onMounted(() => runOnMount && getCookie()) 63 | 64 | return { cookie, getCookie, setCookie, removeCookie } 65 | } 66 | -------------------------------------------------------------------------------- /src/functions/useFetch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useFetch' 2 | -------------------------------------------------------------------------------- /src/functions/useFetch/stories/UseFetchDemoTable.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/functions/useFetch/stories/useFetch.md: -------------------------------------------------------------------------------- 1 | # useFetch 2 | 3 | Vue function that fetch resources asynchronously across the network. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | type TUseFetchUrl = RequestInfo | Ref 9 | ``` 10 | 11 | ```typescript 12 | function useFetch( 13 | url: TUseFetchUrl, 14 | options?: RequestInit, 15 | runOnMount?: boolean 16 | ): { 17 | data: Ref 18 | status: Ref 19 | statusText: Ref 20 | isLoading: Ref 21 | isFailed: Ref 22 | isAborted: Ref 23 | start: () => Promise 24 | stop: () => void 25 | } 26 | ``` 27 | 28 | ### Parameters 29 | 30 | - `url: TUseFetchUrl` the fetch url value, can be type string or type `RequestInfo`. 31 | - `options: RequestInit` the fetch url options. 32 | - `runOnMount: boolean` whether to fetch on mount, `true` by default. 33 | 34 | ### Returns 35 | 36 | - `data: Ref` the response data, has to be of JSON type otherwise will return an error 37 | - `status: Ref` the status code of the response 38 | - `statusText: Ref` the status text of the response 39 | - `isLoading: Ref` whether fetch request is loading or not 40 | - `isFailed: Ref` whether fetch request has failed or not 41 | - `isAborted: Ref` whether fetch request has been aborted or not 42 | - `start: Function` the function used for starting fetch request 43 | - `stop: Function` the function used for aborting fetch request 44 | 45 | ## Usage 46 | 47 | ```html 48 | 57 | 58 | 71 | ``` 72 | -------------------------------------------------------------------------------- /src/functions/useFetch/stories/useFetch.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseFetchDemo from './UseFetchDemo.vue' 5 | 6 | const functionName = 'useFetch' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseFetchDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('side effects|useFetch', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useFetch/useFetch.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref, isRef } from '@src/api' 2 | 3 | export type TUseFetchUrl = RequestInfo | Ref 4 | 5 | const abortError = 'AbortError' 6 | 7 | const isContentTypeJson = (res: Response) => { 8 | const contentType = res.headers.get('content-type') 9 | return contentType && contentType.includes('application/json') 10 | } 11 | 12 | const getUrl = (url: TUseFetchUrl) => (isRef(url) ? url.value : url) 13 | 14 | const fetchWrapper = async (url: TUseFetchUrl, opts: RequestInit) => { 15 | const res = await fetch(getUrl(url), opts) 16 | const resData = isContentTypeJson(res) && (await res.json()) 17 | return { resData, res } 18 | } 19 | 20 | export function useFetch( 21 | url: TUseFetchUrl, 22 | options: RequestInit = {}, 23 | runOnMount = true 24 | ) { 25 | const data: Ref = ref(null) 26 | const status: Ref = ref(null) 27 | const statusText: Ref = ref(null) 28 | const isLoading = ref(false) 29 | const isFailed = ref(false) 30 | const isAborted = ref(false) 31 | 32 | let controller: AbortController 33 | 34 | const start = async () => { 35 | try { 36 | controller = new AbortController() 37 | const signal = controller.signal 38 | 39 | isLoading.value = true 40 | isFailed.value = false 41 | isAborted.value = false 42 | 43 | const { resData, res } = await fetchWrapper(getUrl(url), { 44 | signal, 45 | ...options 46 | }) 47 | data.value = resData 48 | 49 | isLoading.value = false 50 | isFailed.value = !res.ok 51 | status.value = res.status 52 | statusText.value = res.statusText 53 | } catch (err) { 54 | if (err.name === abortError) isAborted.value = true 55 | isLoading.value = false 56 | isFailed.value = true 57 | status.value = 500 58 | statusText.value = err.message || err.name 59 | } 60 | } 61 | 62 | const stop = () => { 63 | if (controller) controller.abort() 64 | } 65 | 66 | onMounted(() => runOnMount && start()) 67 | onUnmounted(stop) 68 | 69 | return { 70 | data, 71 | status, 72 | statusText, 73 | isLoading, 74 | isFailed, 75 | isAborted, 76 | start, 77 | stop 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useFullscreen' 2 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/stories/UseFullscreenDemo.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 52 | 53 | 66 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/stories/useFullscreen.md: -------------------------------------------------------------------------------- 1 | # useFullscreen 2 | 3 | Vue function used for displaying an element in fullscreen mode. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useFullscreen( 9 | elRef: Ref 10 | ): { 11 | isFullscreen: Ref; 12 | start: () => void; 13 | stop: () => void; 14 | } 15 | ``` 16 | 17 | ### Parameters 18 | 19 | - `elRef: Ref` the element to display fullscreen 20 | 21 | ### Returns 22 | 23 | - `isFullscreen: Ref` it is `true` when the element is fullscreen, `false` otherwise 24 | - `start: Function` the function used for starting the fullscreen mode 25 | - `stop: Function` the function used for stopping the fullscreen mode 26 | 27 | ## Usage 28 | 29 | ```html 30 | 37 | 38 | 52 | ``` 53 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/stories/useFullscreen.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseFullscreenDemo from './UseFullscreenDemo.vue' 5 | 6 | const functionName = 'useFullscreen' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseFullscreenDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('ui|useFullscreen', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/useFullscreen.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useFullscreen } from '@src/vue-use-kit' 4 | 5 | // This feature is difficult to test therefore 6 | // I've only written a simple test 7 | 8 | afterEach(() => { 9 | jest.clearAllMocks() 10 | }) 11 | 12 | const testComponent = () => ({ 13 | template: ` 14 |
15 |
16 | 17 | 18 |
19 | `, 20 | setup() { 21 | const divRef = ref(null) 22 | const { isFullscreen, start, stop } = useFullscreen(divRef) 23 | return { isFullscreen, start, stop, divRef } 24 | } 25 | }) 26 | 27 | describe('useFullscreen', () => { 28 | it('should not be fullscreen onMounted', () => { 29 | const wrapper = mount(testComponent()) 30 | expect(wrapper.find('#isFullscreen').exists()).toBe(false) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/functions/useFullscreen/useFullscreen.ts: -------------------------------------------------------------------------------- 1 | import { ref, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useFullscreen(elRef: Ref) { 4 | const isFullscreen = ref(false) 5 | 6 | const handleFullscreenChange = () => { 7 | if (!isFullscreen.value) return 8 | const isCurrentlyFullscreen = !!document.fullscreenElement 9 | isFullscreen.value = isCurrentlyFullscreen 10 | } 11 | 12 | const startTracking = () => { 13 | document.addEventListener('fullscreenchange', handleFullscreenChange) 14 | } 15 | 16 | const stopTracking = () => { 17 | document.removeEventListener('fullscreenchange', handleFullscreenChange) 18 | } 19 | 20 | const start = () => { 21 | if (!elRef.value) return 22 | if (isFullscreen.value) return 23 | elRef.value.requestFullscreen().then(() => { 24 | isFullscreen.value = true 25 | startTracking() 26 | }) 27 | } 28 | 29 | const stop = () => { 30 | if (!isFullscreen.value) return 31 | document.exitFullscreen().then(() => { 32 | isFullscreen.value = false 33 | stopTracking() 34 | }) 35 | } 36 | 37 | onUnmounted(stopTracking) 38 | 39 | return { isFullscreen, start, stop } 40 | } 41 | -------------------------------------------------------------------------------- /src/functions/useGeolocation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useGeolocation' 2 | -------------------------------------------------------------------------------- /src/functions/useGeolocation/stories/UseGeolocationDemo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /src/functions/useGeolocation/stories/useGeolocation.md: -------------------------------------------------------------------------------- 1 | # useGeolocation 2 | 3 | Vue function that tracks geolocation state of user's device, based on the [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API). 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | interface UseGeolocation { 9 | loading: boolean 10 | accuracy: number | null 11 | altitude: number | null 12 | altitudeAccuracy: number | null 13 | heading: number | null 14 | latitude: number | null 15 | longitude: number | null 16 | speed: number | null 17 | timestamp: number | null 18 | error?: Error | PositionError 19 | } 20 | ``` 21 | 22 | ```typescript 23 | function useGeolocation( 24 | options?: PositionOptions, 25 | runOnMount?: boolean 26 | ): { 27 | isTracking: Ref; 28 | geo: Ref; 29 | start: () => void; 30 | stop: () => void; 31 | } 32 | ``` 33 | 34 | ### Parameters 35 | 36 | - `options: PositionOptions` the [geolocation position options](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) 37 | - `runOnMount: boolean` whether to run the geolocation tracking on mount, `true` by default 38 | 39 | ### Returns 40 | 41 | - `geo: Ref` the geolocation object 42 | - `isTracking: Ref` whether this function events are running or not 43 | - `start: Function` the function used for starting the geolocation tracking 44 | - `stop: Function` the function used for stopping the geolocation tracking 45 | 46 | ## Usage 47 | 48 | ```html 49 | 61 | 62 | 74 | ``` 75 | -------------------------------------------------------------------------------- /src/functions/useGeolocation/stories/useGeolocation.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseGeolocationDemo from './UseGeolocationDemo.vue' 5 | 6 | const functionName = 'useGeolocation' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseGeolocationDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('sensors|useGeolocation', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useHover/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useHover' 2 | -------------------------------------------------------------------------------- /src/functions/useHover/stories/UseHoverDemo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /src/functions/useHover/stories/useHover.md: -------------------------------------------------------------------------------- 1 | # useHover 2 | 3 | Vue function that tracks mouse hover state of a given element. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useHover(elRef: Ref): Ref 9 | ``` 10 | 11 | ### Parameters 12 | 13 | - `elRef: Ref` the element used for tracking the mouse hover state 14 | 15 | ### Returns 16 | 17 | - `isHovered: Ref` whether the element is currently hovered or not 18 | 19 | ## Usage 20 | 21 | ```html 22 | 28 | 29 | 43 | ``` 44 | -------------------------------------------------------------------------------- /src/functions/useHover/stories/useHover.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseHoverDemo from './UseHoverDemo.vue' 5 | 6 | const functionName = 'useHover' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseHoverDemo }, 12 | template: ` 13 |
14 | 15 | 16 | 19 | 20 | 21 |
` 22 | }) 23 | 24 | storiesOf('sensors|useHover', module) 25 | .addParameters({ notes }) 26 | .add('Demo', basicDemo) 27 | -------------------------------------------------------------------------------- /src/functions/useHover/useHover.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkOnMountAndUnmountEvents, mount } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useHover } from '@src/vue-use-kit' 4 | 5 | afterEach(() => { 6 | jest.clearAllMocks() 7 | }) 8 | 9 | const testComponent = () => ({ 10 | template: ` 11 |
12 | `, 13 | setup() { 14 | const elRef = ref(document.body as any) 15 | const isHovered = useHover(elRef) 16 | return { isHovered } 17 | } 18 | }) 19 | 20 | describe('useHover', () => { 21 | const events = ['mouseenter', 'mouseleave'] 22 | 23 | it('should add events on mounted and remove them on unmounted', async () => { 24 | await checkOnMountAndUnmountEvents(document.body, events, testComponent) 25 | }) 26 | 27 | it('should return isHovered false by default', () => { 28 | const wrapper = mount(testComponent()) 29 | expect(wrapper.find('#isHovered').exists()).toBe(false) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/functions/useHover/useHover.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useHover(elRef: Ref) { 4 | const isHovered = ref(false) 5 | 6 | const mouseEnterHandler = () => { 7 | isHovered.value = true 8 | } 9 | 10 | const mouseLeaveHandler = () => { 11 | isHovered.value = false 12 | } 13 | 14 | onMounted(() => { 15 | if (!elRef.value) return 16 | elRef.value.addEventListener('mouseenter', mouseEnterHandler) 17 | elRef.value.addEventListener('mouseleave', mouseLeaveHandler) 18 | }) 19 | 20 | onUnmounted(() => { 21 | if (!elRef.value) return 22 | elRef.value.removeEventListener('mouseenter', mouseEnterHandler) 23 | elRef.value.removeEventListener('mouseleave', mouseLeaveHandler) 24 | }) 25 | 26 | return isHovered 27 | } 28 | -------------------------------------------------------------------------------- /src/functions/useIdle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useIdle' 2 | -------------------------------------------------------------------------------- /src/functions/useIdle/stories/UseIdleDemo.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 45 | 46 | 57 | -------------------------------------------------------------------------------- /src/functions/useIdle/stories/useIdle.md: -------------------------------------------------------------------------------- 1 | # useIdle 2 | 3 | Vue function that tracks whether user is being inactive. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useIdle( 9 | ms?: number, 10 | events?: string[], 11 | runOnMount?: boolean 12 | ): { 13 | isIdle: Ref; 14 | isTracking: Ref; 15 | start: () => void; 16 | stop: () => void; 17 | } 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `ms: number` milliseconds to wait before deciding whether the user is being idle 23 | - `events: string[]` list of events to track the user for 24 | - `runOnMount: boolean` whether to start tracking idle state on mount, `true` by default 25 | 26 | ### Returns 27 | 28 | - `isIdle: Ref` it is `true` when the user is idle, `false` otherwise 29 | - `isTracking: Ref` whether this function events are running or not 30 | - `start: Function` the function used for start tracking the user's idle state 31 | - `stop: Function` the function used for stop tracking the user's idle state 32 | 33 | ## Usage 34 | 35 | ```html 36 | 43 | 44 | 56 | ``` 57 | -------------------------------------------------------------------------------- /src/functions/useIdle/stories/useIdle.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseIdleDemo from './UseIdleDemo.vue' 5 | 6 | const functionName = 'useIdle' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseIdleDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('sensors|useIdle', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useIdle/useIdle.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkOnMountAndUnmountEvents, 3 | checkOnStartEvents, 4 | checkOnStopEvents 5 | } from '@src/helpers/test' 6 | import { useIdle, idleEventsList } from '@src/vue-use-kit' 7 | 8 | afterEach(() => { 9 | jest.clearAllMocks() 10 | }) 11 | 12 | const testComponent = () => ({ 13 | template: ` 14 |
15 |
16 | 17 | 18 |
19 | `, 20 | setup() { 21 | const { isIdle, stop, start } = useIdle(3000) 22 | return { isIdle, stop, start } 23 | } 24 | }) 25 | 26 | describe('useIdle', () => { 27 | const events = [...idleEventsList, 'visibilitychange'] 28 | 29 | it('should add events on mounted and remove them on unmounted', async () => { 30 | await checkOnMountAndUnmountEvents(document, events, testComponent) 31 | }) 32 | 33 | it('should add events on mounted and remove them on unmounted', async () => { 34 | await checkOnMountAndUnmountEvents(document, events, testComponent) 35 | }) 36 | 37 | it('should add events again when start is called', async () => { 38 | await checkOnStartEvents(document, events, testComponent) 39 | }) 40 | 41 | it('should remove events when stop is called', async () => { 42 | await checkOnStopEvents(document, events, testComponent) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /src/functions/useIdle/useIdle.ts: -------------------------------------------------------------------------------- 1 | import { throttle } from 'throttle-debounce' 2 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 3 | 4 | export const idleEventsList = [ 5 | 'mousemove', 6 | 'mousedown', 7 | 'resize', 8 | 'keydown', 9 | 'touchstart', 10 | 'wheel' 11 | ] 12 | const oneMinute = 60e3 13 | 14 | export function useIdle( 15 | ms = oneMinute, 16 | events = idleEventsList, 17 | runOnMount = true 18 | ) { 19 | let timeout: any = null 20 | const isIdle = ref(false) 21 | const isTracking = ref(false) 22 | 23 | const handleChange = throttle(50, () => { 24 | isIdle.value = false 25 | 26 | if (timeout) clearTimeout(timeout) 27 | timeout = setTimeout(() => { 28 | isIdle.value = true 29 | }, ms) 30 | }) 31 | 32 | const handleVisibility = () => { 33 | if (!document.hidden) return 34 | handleChange() 35 | } 36 | 37 | const start = () => { 38 | if (isTracking.value) return 39 | events.forEach(evtName => document.addEventListener(evtName, handleChange)) 40 | document.addEventListener('visibilitychange', handleVisibility) 41 | 42 | // Initialize it since the events above may not run immediately 43 | handleChange() 44 | isTracking.value = true 45 | } 46 | 47 | const stop = () => { 48 | if (!isTracking.value) return 49 | events.forEach(evtName => 50 | document.removeEventListener(evtName, handleChange) 51 | ) 52 | document.removeEventListener('visibilitychange', handleVisibility) 53 | 54 | // Stop timer if it is still running 55 | if (timeout) clearTimeout(timeout) 56 | 57 | // Restore initial status 58 | timeout = null 59 | isIdle.value = false 60 | isTracking.value = false 61 | } 62 | 63 | onMounted(() => runOnMount && start()) 64 | onUnmounted(stop) 65 | 66 | return { isIdle, isTracking, start, stop } 67 | } 68 | -------------------------------------------------------------------------------- /src/functions/useIntersection/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useIntersection' 2 | -------------------------------------------------------------------------------- /src/functions/useIntersection/stories/UseIntersectionElementDemo.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 70 | -------------------------------------------------------------------------------- /src/functions/useIntersection/useIntersection.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useIntersection } from '@src/vue-use-kit' 4 | 5 | let observe: any 6 | let unobserve: any 7 | let disconnect: any 8 | beforeEach(() => { 9 | observe = jest.fn() 10 | unobserve = jest.fn() 11 | disconnect = jest.fn() 12 | ;(window as any).IntersectionObserver = jest.fn(() => ({ 13 | observe, 14 | unobserve, 15 | disconnect 16 | })) 17 | }) 18 | 19 | afterEach(() => { 20 | jest.clearAllMocks() 21 | }) 22 | 23 | const testComponent = (onMount = true) => ({ 24 | template: ` 25 |
26 | 27 | 28 |
29 | `, 30 | setup() { 31 | const elRef = ref(null) 32 | const { start, stop } = useIntersection(elRef, {}, onMount) 33 | return { start, stop, elRef } 34 | } 35 | }) 36 | 37 | describe('useIntersection', () => { 38 | it('should call IntersectionObserver on mounted', () => { 39 | expect(observe).toHaveBeenCalledTimes(0) 40 | mount(testComponent()) 41 | expect(observe).toHaveBeenCalledTimes(1) 42 | }) 43 | 44 | it('should call IntersectionObserver again when start is called', async () => { 45 | expect(observe).toHaveBeenCalledTimes(0) 46 | const wrapper = mount(testComponent()) 47 | expect(observe).toHaveBeenCalledTimes(1) 48 | wrapper.find('#stop').trigger('click') 49 | wrapper.find('#start').trigger('click') 50 | await wrapper.vm.$nextTick() 51 | expect(observe).toHaveBeenCalledTimes(2) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /src/functions/useIntersection/useIntersection.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | const errorMsg = 4 | 'IntersectionObserver is not supported, please install a polyfill' 5 | 6 | export function useIntersection( 7 | elRef: Ref, 8 | options: IntersectionObserverInit = {}, 9 | runOnMount = true 10 | ) { 11 | const observedEntry: Ref = ref(null) 12 | 13 | const handleObserver = (entries: IntersectionObserverEntry[]) => { 14 | observedEntry.value = entries[0] 15 | } 16 | 17 | let observer: IntersectionObserver | null = null 18 | 19 | const start = () => { 20 | if (!('IntersectionObserver' in window)) throw new Error(errorMsg) 21 | 22 | // Do not start if the observer is already initialized 23 | // or the elRef does not exist 24 | if (observer || !elRef.value) return 25 | observer = new IntersectionObserver(handleObserver, options) 26 | observer.observe(elRef.value) 27 | } 28 | 29 | const stop = () => { 30 | if (!observer) return 31 | observer.disconnect() 32 | observer = null 33 | } 34 | 35 | onMounted(() => runOnMount && start()) 36 | onUnmounted(stop) 37 | 38 | return { observedEntry, start, stop } 39 | } 40 | -------------------------------------------------------------------------------- /src/functions/useInterval/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useInterval' 2 | -------------------------------------------------------------------------------- /src/functions/useInterval/stories/UseIntervalDemo.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 40 | -------------------------------------------------------------------------------- /src/functions/useInterval/stories/useInterval.md: -------------------------------------------------------------------------------- 1 | # useInterval 2 | 3 | Vue function that updates the `counter` value repeatedly on a fixed time delay. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useInterval( 9 | ms?: number, 10 | runOnMount?: boolean 11 | ): { 12 | isRunning: Ref; 13 | counter: Ref; 14 | start: () => void; 15 | stop: () => void; 16 | }; 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `ms: number` how many milliseconds to wait before updating the counter 22 | - `runOnMount: boolean` whether to run the interval on mount, `true` by default 23 | 24 | ### Returns 25 | 26 | - `isRunning: Ref` this value is `true` if the interval is running, `false` otherwise 27 | - `counter: Ref` the number of times the interval has run 28 | - `start: Function` the function used for starting the interval 29 | - `stop: Function` the function used for stopping the interval 30 | 31 | ## Usage 32 | 33 | ```html 34 | 42 | 43 | 58 | ``` 59 | -------------------------------------------------------------------------------- /src/functions/useInterval/stories/useInterval.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseIntervalDemo from './UseIntervalDemo.vue' 5 | 6 | const functionName = 'useInterval' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseIntervalDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('animations|useInterval', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useInterval/useInterval.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { useInterval } from '@src/vue-use-kit' 3 | 4 | beforeEach(() => { 5 | jest.useFakeTimers() 6 | }) 7 | 8 | afterEach(() => { 9 | jest.clearAllTimers() 10 | jest.clearAllMocks() 11 | }) 12 | 13 | const testComponent = () => ({ 14 | template: ` 15 |
16 |
17 |
18 | `, 19 | setup() { 20 | const { isRunning } = useInterval(1000) 21 | return { isRunning } 22 | } 23 | }) 24 | 25 | describe('useInterval', () => { 26 | it('should show #isRunning when the intervals are called', async () => { 27 | const wrapper = mount(testComponent()) 28 | jest.advanceTimersByTime(1500) 29 | 30 | // Wait for Vue to append #isReady in the DOM 31 | await wrapper.vm.$nextTick() 32 | expect(wrapper.find('#isRunning').exists()).toBe(true) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/functions/useInterval/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from '@src/api' 2 | import { useIntervalFn } from '@src/functions/useIntervalFn' 3 | 4 | export function useInterval(ms = 0, runOnMount = true) { 5 | const counter = ref(0) 6 | const animHandler = () => { 7 | counter.value = counter.value + 1 8 | } 9 | const { isRunning, start, stop } = useIntervalFn(animHandler, ms, runOnMount) 10 | return { isRunning, counter, start, stop } 11 | } 12 | -------------------------------------------------------------------------------- /src/functions/useIntervalFn/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useIntervalFn' 2 | -------------------------------------------------------------------------------- /src/functions/useIntervalFn/stories/UseIntervalFnDemo.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 48 | -------------------------------------------------------------------------------- /src/functions/useIntervalFn/stories/useIntervalFn.md: -------------------------------------------------------------------------------- 1 | # useIntervalFn 2 | 3 | Vue function that calls given callback repeatedly on a fixed time delay. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useIntervalFn( 9 | callback: Function, 10 | ms?: number, 11 | runOnMount?: boolean 12 | ): { 13 | isRunning: Ref; 14 | start: () => void; 15 | stop: () => void; 16 | }; 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `callback: Function` the function to call for each interval finishes 22 | - `ms: number` how many milliseconds to wait before running the callback function 23 | - `runOnMount: boolean` whether to run the interval on mount, `true` by default 24 | 25 | ### Returns 26 | 27 | - `isRunning: Ref` this value is `true` if the interval is running, `false` otherwise 28 | - `start: Function` the function used for starting the interval 29 | - `stop: Function` the function used for stopping the interval 30 | 31 | ## Usage 32 | 33 | ```html 34 | 42 | 43 | 63 | ``` 64 | -------------------------------------------------------------------------------- /src/functions/useIntervalFn/stories/useIntervalFn.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseIntervalFnDemo from './UseIntervalFnDemo.vue' 5 | 6 | const functionName = 'useIntervalFn' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseIntervalFnDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('animations|useIntervalFn', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useIntervalFn/useIntervalFn.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, Ref } from '@src/api' 2 | 3 | export function useIntervalFn(callback: Function, ms = 0, runOnMount = true) { 4 | const isRunning = ref(false) 5 | let interval: any = null 6 | 7 | const start = () => { 8 | if (interval) return 9 | isRunning.value = true 10 | interval = setInterval(callback, ms) 11 | } 12 | 13 | const stop = () => { 14 | clearInterval(interval) 15 | isRunning.value = false 16 | interval = null 17 | } 18 | 19 | onMounted(() => runOnMount && start()) 20 | onUnmounted(stop) 21 | 22 | return { isRunning, start, stop } 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/useKey/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useKey' 2 | -------------------------------------------------------------------------------- /src/functions/useKey/stories/UseKeyDemo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 61 | 62 | 78 | -------------------------------------------------------------------------------- /src/functions/useKey/stories/useKey.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseKeyDemo from './UseKeyDemo.vue' 5 | 6 | const functionName = 'useKey' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseKeyDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 26 | 27 | 28 |
` 29 | }) 30 | 31 | storiesOf('sensors|useKey', module) 32 | .addParameters({ notes }) 33 | .add('Demo', basicDemo) 34 | -------------------------------------------------------------------------------- /src/functions/useKey/useKey.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mount, 3 | checkOnMountAndUnmountEvents, 4 | checkOnStartEvents, 5 | checkOnStopEvents, 6 | checkElementExistenceOnMount 7 | } from '@src/helpers/test' 8 | import { useKey } from '@src/vue-use-kit' 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks() 12 | }) 13 | 14 | const testComponent = (filter = 'a', callback = () => ``, onMount = true) => ({ 15 | template: ` 16 |
17 |
18 |
19 | 20 | 21 |
22 | `, 23 | setup() { 24 | const { isPressed, isTracking, start, stop } = useKey( 25 | filter, 26 | callback, 27 | onMount 28 | ) 29 | return { isPressed, isTracking, start, stop } 30 | } 31 | }) 32 | 33 | describe('useKey', () => { 34 | const noop = () => `` 35 | const events = ['keyup', 'keydown'] 36 | 37 | it('should add events on mounted and remove them on unmounted', async () => { 38 | await checkOnMountAndUnmountEvents(document, events, testComponent) 39 | }) 40 | 41 | it('should add events again when start is called', async () => { 42 | await checkOnStartEvents(document, events, testComponent) 43 | }) 44 | 45 | it('should remove events when stop is called', async () => { 46 | await checkOnStopEvents(document, events, testComponent) 47 | }) 48 | 49 | it('should show #isTracking when runOnMount is true', async () => { 50 | await checkElementExistenceOnMount(true, testComponent('a', noop, true)) 51 | }) 52 | 53 | it('should not show #isTracking when runOnMount is false', async () => { 54 | await checkElementExistenceOnMount(false, testComponent('a', noop, false)) 55 | }) 56 | 57 | it('should not show #isPressed when no key has been pressed', async () => { 58 | const wrapper = mount(testComponent()) 59 | await wrapper.vm.$nextTick() 60 | expect(wrapper.find('#isPressed').exists()).toBe(false) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /src/functions/useKey/useKey.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | type UseKeyFilter = string | ((event: KeyboardEvent) => boolean) 4 | 5 | export function useKey( 6 | filter: UseKeyFilter, 7 | callback: any = () => ``, 8 | runOnMount = true 9 | ) { 10 | const isTracking = ref(false) 11 | const isPressed = ref(false) 12 | 13 | const getFilter = () => { 14 | if (typeof filter === 'function') return filter 15 | return (event: KeyboardEvent) => event.key === filter 16 | } 17 | 18 | const handleKeyDown = (event: KeyboardEvent) => { 19 | const filterFn = getFilter() 20 | if (!filterFn(event)) return 21 | 22 | isPressed.value = true 23 | callback(event) 24 | } 25 | 26 | const handleKeyUp = (event: KeyboardEvent) => { 27 | const filterFn = getFilter() 28 | if (!filterFn(event)) return 29 | 30 | isPressed.value = false 31 | callback(event) 32 | } 33 | 34 | const start = () => { 35 | if (isTracking.value) return 36 | document.addEventListener('keydown', handleKeyDown) 37 | document.addEventListener('keyup', handleKeyUp) 38 | isTracking.value = true 39 | } 40 | 41 | const stop = () => { 42 | if (!isTracking.value) return 43 | document.removeEventListener('keydown', handleKeyDown) 44 | document.removeEventListener('keyup', handleKeyUp) 45 | isTracking.value = false 46 | } 47 | 48 | onMounted(() => runOnMount && start()) 49 | onUnmounted(stop) 50 | 51 | return { isPressed, isTracking, start, stop } 52 | } 53 | -------------------------------------------------------------------------------- /src/functions/useLocalStorage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useLocalStorage' 2 | -------------------------------------------------------------------------------- /src/functions/useLocalStorage/stories/UseLocalStorageDemo.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 85 | -------------------------------------------------------------------------------- /src/functions/useLocalStorage/stories/useLocalStorage.md: -------------------------------------------------------------------------------- 1 | # useLocalStorage 2 | 3 | Vue function that provides way to read, update and delete a localStorage key 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | interface StorageOptions { 9 | isParsing: boolean 10 | serializer?: SerializerFunction 11 | deserializer?: DeserializerFunction 12 | } 13 | ``` 14 | 15 | ```typescript 16 | function useLocalStorage( 17 | key: string, 18 | options?: StorageOptions, 19 | runOnMount?: boolean 20 | ): { 21 | item: Ref 22 | getItem: () => void 23 | setItem: (newVal: any) => void 24 | removeItem: () => void 25 | } 26 | ``` 27 | 28 | ### Parameters 29 | 30 | - `key: string` the localStorage key you wish to get/set/remove 31 | - `options: StorageOptions` 32 | - `isParsing: boolean` whether to enable parsing the localStorage key value or not, `false` by default 33 | - `serializer: SerializerFunction` a custom serializer, `JSON.stringify` by default 34 | - `deserializer: DeserializerFunction` a custom deserializer, `JSON.parse` by default 35 | - `runOnMount: boolean` whether to get the localStorage key on mount or not, `true` by default 36 | 37 | ### Returns 38 | 39 | - `item: Ref` the localStorage key value, it can be null, a string or a JSON object/array 40 | - `getItem: Function` get the localStorage key value 41 | - `setItem: Function` set the localStorage key value 42 | - `newVal: any`: the value to set, can be a string or an object/array 43 | - `removeItem: Function` delete the localStorage key 44 | 45 | ## Usage 46 | 47 | ```html 48 | 56 | 57 | 77 | ``` 78 | -------------------------------------------------------------------------------- /src/functions/useLocalStorage/stories/useLocalStorage.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseLocalStorageDemo from './UseLocalStorageDemo.vue' 5 | 6 | const functionName = 'useLocalStorage' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseLocalStorageDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('side effects|useLocalStorage', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useLocalStorage/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { createStorage, StorageOptions } from '@src/shared/createStorage' 2 | import { onMounted, Ref } from '@src/api' 3 | 4 | export function useLocalStorage( 5 | key: string, 6 | options?: StorageOptions, 7 | runOnMount = true 8 | ) { 9 | const { item, getItem, setItem, removeItem } = createStorage( 10 | localStorage, 11 | key, 12 | options 13 | ) 14 | 15 | onMounted(() => runOnMount && getItem()) 16 | 17 | return { 18 | item, 19 | getItem, 20 | setItem, 21 | removeItem 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/useLocation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useLocation' 2 | -------------------------------------------------------------------------------- /src/functions/useLocation/stories/UseLocationDemo.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 63 | -------------------------------------------------------------------------------- /src/functions/useLocation/stories/useLocation.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseLocationDemo from './UseLocationDemo.vue' 5 | 6 | const functionName = 'useLocation' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseLocationDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 31 | 32 | 33 |
` 34 | }) 35 | 36 | storiesOf('sensors|useLocation', module) 37 | .addParameters({ notes }) 38 | .add('Demo', basicDemo) 39 | -------------------------------------------------------------------------------- /src/functions/useLocation/useLocation.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkElementExistenceOnMount, 3 | checkOnMountAndUnmountEvents, 4 | checkOnStartEvents, 5 | checkOnStopEvents, 6 | mount 7 | } from '@src/helpers/test' 8 | import { useLocation } from '@src/vue-use-kit' 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks() 12 | }) 13 | 14 | const testComponent = (onMount = true) => ({ 15 | template: ` 16 |
17 |
18 |
{{JSON.stringify(locationState)}}
19 | 20 | 21 |
22 | `, 23 | setup() { 24 | const { locationState, isTracking, start, stop } = useLocation(onMount) 25 | return { locationState, isTracking, start, stop } 26 | } 27 | }) 28 | 29 | describe('useLocation', () => { 30 | const locationStateKeys = [ 31 | 'trigger', 32 | 'state', 33 | 'length', 34 | 'hash', 35 | 'host', 36 | 'hostname', 37 | 'href', 38 | 'origin', 39 | 'pathname', 40 | 'port', 41 | 'protocol', 42 | 'search' 43 | ] 44 | 45 | const events = ['popstate', 'pushstate', 'replacestate'] 46 | 47 | it('should add events on mounted and remove them on unmounted', async () => { 48 | await checkOnMountAndUnmountEvents(window, events, testComponent) 49 | }) 50 | 51 | it('should add events again when start is called', async () => { 52 | await checkOnStartEvents(window, events, testComponent) 53 | }) 54 | 55 | it('should remove events when stop is called', async () => { 56 | await checkOnStopEvents(window, events, testComponent) 57 | }) 58 | 59 | it('should show #isTracking when runOnMount is true', async () => { 60 | await checkElementExistenceOnMount(true, testComponent(true)) 61 | }) 62 | 63 | it('should not show #isTracking when runOnMount is false', async () => { 64 | await checkElementExistenceOnMount(false, testComponent(false)) 65 | }) 66 | 67 | it('should display the locationState object', async () => { 68 | const wrapper = mount(testComponent(true)) 69 | await wrapper.vm.$nextTick() 70 | locationStateKeys.forEach(locationKey => { 71 | expect(wrapper.text().includes(locationKey)).toBe(true) 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /src/functions/useLocation/useLocation.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | import { patchHistoryMethodsOnce } from '@src/shared/utils' 3 | 4 | export interface UseLocationState { 5 | trigger: string 6 | state: any 7 | length: number 8 | hash: string 9 | host: string 10 | hostname: string 11 | href: string 12 | origin: string 13 | pathname: string 14 | port: string 15 | protocol: string 16 | search: string 17 | } 18 | 19 | export function useLocation(runOnMount = true) { 20 | const buildState = (trigger: string) => { 21 | const { state, length } = history 22 | 23 | const { 24 | hash, 25 | host, 26 | hostname, 27 | href, 28 | origin, 29 | pathname, 30 | port, 31 | protocol, 32 | search 33 | } = location 34 | 35 | return { 36 | trigger, 37 | state, 38 | length, 39 | hash, 40 | host, 41 | hostname, 42 | href, 43 | origin, 44 | pathname, 45 | port, 46 | protocol, 47 | search 48 | } 49 | } 50 | const isTracking = ref(false) 51 | const locationState: Ref = ref(buildState('load')) 52 | 53 | const popState = () => (locationState.value = buildState('popstate')) 54 | const pushState = () => (locationState.value = buildState('pushstate')) 55 | const replaceState = () => (locationState.value = buildState('replacestate')) 56 | 57 | const start = () => { 58 | if (isTracking.value) return 59 | patchHistoryMethodsOnce() 60 | locationState.value = buildState('start') 61 | window.addEventListener('popstate', popState) 62 | window.addEventListener('pushstate', pushState) 63 | window.addEventListener('replacestate', replaceState) 64 | isTracking.value = true 65 | } 66 | 67 | const stop = () => { 68 | if (!isTracking.value) return 69 | window.removeEventListener('popstate', popState) 70 | window.removeEventListener('pushstate', pushState) 71 | window.removeEventListener('replacestate', replaceState) 72 | isTracking.value = false 73 | } 74 | 75 | onMounted(() => runOnMount && start()) 76 | onUnmounted(stop) 77 | 78 | return { locationState, isTracking, start, stop } 79 | } 80 | -------------------------------------------------------------------------------- /src/functions/useMedia/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMedia' 2 | -------------------------------------------------------------------------------- /src/functions/useMedia/stories/UseMediaDemo.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 33 | 34 | 45 | -------------------------------------------------------------------------------- /src/functions/useMedia/stories/useMedia.md: -------------------------------------------------------------------------------- 1 | # useMedia 2 | 3 | Vue function that tracks the state of a CSS media query. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useMedia( 9 | query: string, 10 | defaultState?: boolean 11 | ): Ref 12 | ``` 13 | 14 | ### Parameters 15 | 16 | - `query: string` the media query to use, for example '(min-width: 1024px)' 17 | - `defaultState?: boolean` the value used as fallback for SSR 18 | 19 | ### Returns 20 | 21 | - `isQueryMatching: Ref` whether the query matches or not 22 | 23 | ## Usage 24 | 25 | ```html 26 | 29 | 30 | 43 | ``` 44 | -------------------------------------------------------------------------------- /src/functions/useMedia/stories/useMedia.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseMediaDemo from './UseMediaDemo.vue' 5 | import UseMediaAdvancedDemo from './UseMediaAdvancedDemo.vue' 6 | 7 | const functionName = 'useMedia' 8 | const functionPath = path.resolve(__dirname, '..') 9 | const notes = require(`./${functionName}.md`).default 10 | 11 | const basicDemo = () => ({ 12 | components: { StoryTitle, demo: UseMediaDemo }, 13 | template: ` 14 |
15 | 16 | 17 | 22 | 23 | 24 |
` 25 | }) 26 | 27 | const advancedDemo = () => ({ 28 | components: { StoryTitle, demo: UseMediaAdvancedDemo }, 29 | template: ` 30 |
31 | 32 | 33 | 38 | 39 | 40 |
` 41 | }) 42 | 43 | storiesOf('sensors|useMedia', module) 44 | .addParameters({ notes }) 45 | .add('Demo', basicDemo) 46 | .add('Advanced demo', advancedDemo) 47 | -------------------------------------------------------------------------------- /src/functions/useMedia/useMedia.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useMedia(query: string, defaultState = false) { 4 | let mql: MediaQueryList 5 | const matches = ref(defaultState) 6 | 7 | const updateMatchValue = () => (matches.value = mql.matches) 8 | 9 | onMounted(() => { 10 | mql = window.matchMedia(query) 11 | mql.addListener(updateMatchValue) 12 | updateMatchValue() 13 | }) 14 | 15 | onUnmounted(() => { 16 | mql.removeListener(updateMatchValue) 17 | }) 18 | 19 | return matches 20 | } 21 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMediaDevices' 2 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/stories/UseMediaDevicesDemo.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 52 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/stories/useMediaDevices.md: -------------------------------------------------------------------------------- 1 | # useMediaDevices 2 | 3 | Vue function that tracks connected hardware devices. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | interface UseMediaDevicesState { 9 | deviceId: string 10 | groupId: string 11 | kind: string 12 | label: string 13 | } 14 | 15 | function useMediaDevices( 16 | runOnMount?: boolean 17 | ): { 18 | devicesState: Ref 19 | isTracking: Ref 20 | isTracked: Ref 21 | start: () => void 22 | stop: () => void 23 | } 24 | ``` 25 | 26 | ### Parameters 27 | 28 | - `runOnMount: boolean` whether to run the connected media devices tracking on mount, `true` by default 29 | 30 | ### Returns 31 | 32 | - `devicesState: Ref` the list of connected media devices 33 | - `isTracking: Ref` whether this function events are running or not 34 | - `isTracked: Ref` whether the connected devices have been successfully tracked 35 | - `start: Function` the function used to start tracking the connected media devices 36 | - `stop: Function` the function used to stop tracking the connected media devices 37 | 38 | ## Usage 39 | 40 | ```html 41 | 58 | 59 | 71 | ``` 72 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/stories/useMediaDevices.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseMediaDevicesDemo from './UseMediaDevicesDemo.vue' 5 | 6 | const functionName = 'useMediaDevices' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseMediaDevicesDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('sensors|useMediaDevices', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/useMediaDevices.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkElementExistenceOnMount, 3 | checkOnMountAndUnmountEvents, 4 | checkOnStartEvents, 5 | checkOnStopEvents 6 | } from '@src/helpers/test' 7 | import { useMediaDevices } from '@src/vue-use-kit' 8 | 9 | const mediaDeviceInfo = { 10 | deviceId: 'string', 11 | groupId: 'string', 12 | kind: 'string', 13 | label: 'string' 14 | } 15 | const mediaDevices = [mediaDeviceInfo, mediaDeviceInfo] 16 | let enumerateDevices: any 17 | beforeEach(() => { 18 | enumerateDevices = () => Promise.resolve(mediaDevices) 19 | ;(navigator as any).mediaDevices = { 20 | enumerateDevices, 21 | addEventListener: jest.fn(), 22 | removeEventListener: jest.fn() 23 | } 24 | }) 25 | 26 | afterEach(() => { 27 | jest.clearAllMocks() 28 | }) 29 | 30 | const testComponent = (onMount = true) => ({ 31 | template: ` 32 |
33 |
34 |
{{JSON.stringify(devicesState)}}
35 | 36 | 37 |
38 | `, 39 | setup() { 40 | const { devicesState, isTracking, start, stop } = useMediaDevices(onMount) 41 | return { devicesState, isTracking, start, stop } 42 | } 43 | }) 44 | 45 | describe('useMediaDevices', () => { 46 | const events = ['devicechange'] 47 | 48 | it('should add events on mounted and remove them on unmounted', async () => { 49 | await checkOnMountAndUnmountEvents( 50 | navigator.mediaDevices, 51 | events, 52 | testComponent 53 | ) 54 | }) 55 | 56 | it('should add events again when start is called', async () => { 57 | await checkOnStartEvents(navigator.mediaDevices, events, testComponent) 58 | }) 59 | 60 | it('should remove events when stop is called', async () => { 61 | await checkOnStopEvents(navigator.mediaDevices, events, testComponent) 62 | }) 63 | 64 | it('should show #isTracking when runOnMount is true', async () => { 65 | await checkElementExistenceOnMount(true, testComponent(true)) 66 | }) 67 | 68 | it('should not show #isTracking when runOnMount is false', async () => { 69 | await checkElementExistenceOnMount(false, testComponent(false)) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /src/functions/useMediaDevices/useMediaDevices.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export interface UseMediaDevicesState { 4 | deviceId: string 5 | groupId: string 6 | kind: string 7 | label: string 8 | } 9 | 10 | export function useMediaDevices(runOnMount = true) { 11 | const devicesState: Ref = ref([]) 12 | const isTracking = ref(false) 13 | const isTracked = ref(false) 14 | 15 | const deviceMap = ({ 16 | deviceId, 17 | groupId, 18 | kind, 19 | label 20 | }: UseMediaDevicesState) => ({ 21 | deviceId, 22 | groupId, 23 | kind, 24 | label 25 | }) 26 | 27 | const handleDeviceChange = () => { 28 | navigator.mediaDevices 29 | .enumerateDevices() 30 | .then(deviceList => { 31 | if (!isTracking.value) return 32 | isTracked.value = true 33 | devicesState.value = deviceList.map(deviceMap) 34 | }) 35 | .catch(() => { 36 | isTracked.value = false 37 | }) 38 | } 39 | 40 | const start = () => { 41 | if (isTracking.value) return 42 | handleDeviceChange() 43 | navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange) 44 | isTracking.value = true 45 | } 46 | 47 | const stop = () => { 48 | if (!isTracking.value) return 49 | navigator.mediaDevices.removeEventListener( 50 | 'devicechange', 51 | handleDeviceChange 52 | ) 53 | isTracking.value = false 54 | } 55 | 56 | onMounted(() => runOnMount && start()) 57 | onUnmounted(stop) 58 | 59 | return { devicesState, isTracking, isTracked, start, stop } 60 | } 61 | -------------------------------------------------------------------------------- /src/functions/useMouse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMouse' 2 | -------------------------------------------------------------------------------- /src/functions/useMouse/stories/UseMouseDemo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /src/functions/useMouse/stories/useMouse.md: -------------------------------------------------------------------------------- 1 | # useMouse 2 | 3 | Vue function that tracks the mouse `x` and `y` position in the document. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useMouse(): { 9 | docX: Ref 10 | docY: Ref 11 | } 12 | ``` 13 | 14 | ### Returns 15 | 16 | - `docX: Ref` the mouse `x` position relative to the document 17 | - `docY: Ref` the mouse `y` position relative to the document 18 | 19 | ## Usage 20 | 21 | ```html 22 | 25 | 26 | 38 | ``` 39 | -------------------------------------------------------------------------------- /src/functions/useMouse/stories/useMouse.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseMouseDemo from './UseMouseDemo.vue' 5 | import UseMouseAdvancedDemo from './UseMouseAdvancedDemo.vue' 6 | 7 | const functionName = 'useMouse' 8 | const functionPath = path.resolve(__dirname, '..') 9 | const notes = require(`./${functionName}.md`).default 10 | 11 | const basicDemo = () => ({ 12 | components: { StoryTitle, demo: UseMouseDemo }, 13 | template: ` 14 |
15 | 16 | 17 | 18 | 19 | 20 |
` 21 | }) 22 | 23 | const advancedDemo = () => ({ 24 | components: { StoryTitle, demo: UseMouseAdvancedDemo }, 25 | template: ` 26 |
27 | 28 | 29 | 35 | 36 | 37 |
` 38 | }) 39 | 40 | storiesOf('sensors|useMouse', module) 41 | .addParameters({ notes }) 42 | .add('Demo', basicDemo) 43 | .add('Advanced Demo', advancedDemo) 44 | -------------------------------------------------------------------------------- /src/functions/useMouse/useMouse.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkOnMountAndUnmountEvents, mount } from '@src/helpers/test' 2 | import { useMouse } from '@src/vue-use-kit' 3 | 4 | afterEach(() => { 5 | jest.clearAllMocks() 6 | }) 7 | 8 | const testComponent = () => ({ 9 | template: ` 10 |
11 |
12 |
13 |
14 | `, 15 | setup() { 16 | const { docX, docY } = useMouse() 17 | return { docX, docY } 18 | } 19 | }) 20 | 21 | describe('useMouse', () => { 22 | const events = ['mousemove'] 23 | 24 | it('should add events on mounted and remove them on unmounted', async () => { 25 | await checkOnMountAndUnmountEvents(document, events, testComponent) 26 | }) 27 | 28 | it('should not render any document before mounted since all values are 0 by default', () => { 29 | const wrapper = mount(testComponent()) 30 | expect(wrapper.find('#docX').exists()).toBe(false) 31 | expect(wrapper.find('#docY').exists()).toBe(false) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/functions/useMouse/useMouse.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useMouse() { 4 | const docX = ref(0) 5 | const docY = ref(0) 6 | 7 | const mouseMoveHandler = (e: MouseEvent) => { 8 | docX.value = e.pageX 9 | docY.value = e.pageY 10 | } 11 | 12 | onMounted(() => { 13 | document.addEventListener('mousemove', mouseMoveHandler) 14 | }) 15 | 16 | onUnmounted(() => { 17 | document.removeEventListener('mousemove', mouseMoveHandler) 18 | }) 19 | 20 | return { 21 | docX, 22 | docY 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/functions/useMouseElement/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMouseElement' 2 | -------------------------------------------------------------------------------- /src/functions/useMouseElement/stories/useMouseElement.md: -------------------------------------------------------------------------------- 1 | # useMouseElement 2 | 3 | Vue function that tracks the mouse `x` and `y` position, 4 | and optionally tracks the mouse position relative to a given element. 5 | 6 | ## Reference 7 | 8 | ```typescript 9 | function useMouseElement(elRef: Ref): { 10 | docX: Ref; 11 | docY: Ref; 12 | elX: Ref; 13 | elY: Ref; 14 | elInfoX: Ref; 15 | elInfoY: Ref; 16 | elInfoW: Ref; 17 | elInfoH: Ref; 18 | } 19 | ``` 20 | 21 | ### Parameters 22 | 23 | - `elRef: Ref` used for getting the `x` and `y` mouse position relative to the position of the given element 24 | 25 | ### Returns 26 | 27 | - `docX: Ref` the mouse `x` position relative to the document 28 | - `docY: Ref` the mouse `y` position relative to the document 29 | - `elX: Ref` the mouse `x` position relative to the given element 30 | - `elY: Ref` the mouse `y` position relative to the given element 31 | - `elInfoX: Ref` the element `x` position 32 | - `elInfoY: Ref` the element `y` position 33 | - `elInfoW: Ref` the element `width` value 34 | - `elInfoH: Ref` the element `height` value 35 | 36 | ## Usage 37 | 38 | ```html 39 | 42 | 43 | 55 | ``` 56 | -------------------------------------------------------------------------------- /src/functions/useMouseElement/stories/useMouseElement.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseMouseElementDemo from './UseMouseElementDemo.vue' 5 | 6 | const functionName = 'useMouseElement' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseMouseElementDemo }, 12 | template: ` 13 |
14 | 15 | 16 | 22 | 23 | 24 |
` 25 | }) 26 | 27 | storiesOf('sensors|useMouseElement', module) 28 | .addParameters({ notes }) 29 | .add('Demo', basicDemo) 30 | -------------------------------------------------------------------------------- /src/functions/useMouseElement/useMouseElement.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkOnMountAndUnmountEvents, mount } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useMouseElement } from '@src/vue-use-kit' 4 | 5 | afterEach(() => { 6 | jest.clearAllMocks() 7 | }) 8 | 9 | const testComponent = () => ({ 10 | template: ` 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | `, 22 | setup() { 23 | const divRef = ref(null) 24 | const { 25 | docX, 26 | docY, 27 | elX, 28 | elY, 29 | elInfoX, 30 | elInfoY, 31 | elInfoW, 32 | elInfoH 33 | } = useMouseElement(divRef) 34 | return { docX, docY, elX, elY, elInfoX, elInfoY, elInfoW, elInfoH, divRef } 35 | } 36 | }) 37 | 38 | describe('useMouseElement', () => { 39 | const events = ['mousemove'] 40 | 41 | it('should add events on mounted and remove them on unmounted', async () => { 42 | await checkOnMountAndUnmountEvents(document, events, testComponent) 43 | }) 44 | 45 | it('should not render any document before onMount since all values are 0 by default', () => { 46 | const wrapper = mount(testComponent()) 47 | expect(wrapper.find('#docX').exists()).toBe(false) 48 | expect(wrapper.find('#docY').exists()).toBe(false) 49 | expect(wrapper.find('#elX').exists()).toBe(false) 50 | expect(wrapper.find('#elY').exists()).toBe(false) 51 | expect(wrapper.find('#elInfoX').exists()).toBe(false) 52 | expect(wrapper.find('#elInfoY').exists()).toBe(false) 53 | expect(wrapper.find('#elInfoW').exists()).toBe(false) 54 | expect(wrapper.find('#elInfoH').exists()).toBe(false) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /src/functions/useMouseElement/useMouseElement.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useMouseElement(elRef: Ref) { 4 | const docX = ref(0) 5 | const docY = ref(0) 6 | const elX = ref(0) 7 | const elY = ref(0) 8 | const elInfoX = ref(0) 9 | const elInfoY = ref(0) 10 | const elInfoW = ref(0) 11 | const elInfoH = ref(0) 12 | 13 | const mouseMoveHandler = (e: MouseEvent) => { 14 | if (!elRef.value) return 15 | docX.value = e.pageX 16 | docY.value = e.pageY 17 | const { left, top, height, width } = elRef.value.getBoundingClientRect() 18 | elInfoX.value = left + window.pageXOffset 19 | elInfoY.value = top + window.pageYOffset 20 | elInfoW.value = width 21 | elInfoH.value = height 22 | elX.value = e.pageX - elInfoX.value 23 | elY.value = e.pageY - elInfoY.value 24 | } 25 | 26 | onMounted(() => { 27 | document.addEventListener('mousemove', mouseMoveHandler) 28 | }) 29 | 30 | onUnmounted(() => { 31 | document.removeEventListener('mousemove', mouseMoveHandler) 32 | }) 33 | 34 | return { 35 | docX, 36 | docY, 37 | elX, 38 | elY, 39 | elInfoX, 40 | elInfoY, 41 | elInfoW, 42 | elInfoH 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMouseLeavePage' 2 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/stories/UseMouseLeavePageDemo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | 43 | 48 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/stories/useMouseLeavePage.md: -------------------------------------------------------------------------------- 1 | # useMouseLeavePage 2 | 3 | Vue function that tracks when mouse leaves page boundaries. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useMouseLeavePage( 9 | runOnMount?: boolean 10 | ): { 11 | hasLeftPage: Ref 12 | isTracking: Ref 13 | start: () => void 14 | stop: () => void 15 | } 16 | ``` 17 | 18 | ### Parameters 19 | 20 | - `runOnMount: boolean` whether to check mouse leaves page boundaries tracking on mount, `true` by default 21 | 22 | ### Returns 23 | 24 | - `hasLeftPage: Ref` whether the mouse has left the page or not 25 | - `isTracking: Ref` whether this function events are running or not 26 | - `start: Function` the function used to start tracking when the mouse leaves the page boundaries 27 | - `stop: Function` the function used to stop tracking when the mouse leaves the page boundaries 28 | 29 | ## Usage 30 | 31 | ```html 32 | 46 | 47 | 59 | ``` 60 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/stories/useMouseLeavePage.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseMouseLeavePageDemo from './UseMouseLeavePageDemo.vue' 5 | 6 | const functionName = 'useMouseLeavePage' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseMouseLeavePageDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 32 | 33 | 34 |
` 35 | }) 36 | 37 | storiesOf('sensors|useMouseLeavePage', module) 38 | .addParameters({ notes }) 39 | .add('Demo', basicDemo) 40 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/useMouseLeavePage.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkElementExistenceOnMount, 3 | checkOnMountAndUnmountEvents, 4 | checkOnStartEvents, 5 | checkOnStopEvents 6 | } from '@src/helpers/test' 7 | import { useMouseLeavePage } from '@src/vue-use-kit' 8 | 9 | afterEach(() => { 10 | jest.clearAllMocks() 11 | }) 12 | 13 | const testComponent = (onMount = true) => ({ 14 | template: ` 15 |
16 |
17 |
18 | 19 | 20 |
21 | `, 22 | setup() { 23 | const { hasLeftPage, isTracking, start, stop } = useMouseLeavePage(onMount) 24 | return { hasLeftPage, isTracking, start, stop } 25 | } 26 | }) 27 | 28 | describe('useMouseLeavePage', () => { 29 | const events = ['mouseout'] 30 | 31 | it('should add events on mounted and remove them on unmounted', async () => { 32 | await checkOnMountAndUnmountEvents(document, events, testComponent) 33 | }) 34 | 35 | it('should add events again when start is called', async () => { 36 | await checkOnStartEvents(document, events, testComponent) 37 | }) 38 | 39 | it('should remove events when stop is called', async () => { 40 | await checkOnStopEvents(document, events, testComponent) 41 | }) 42 | 43 | it('should show #isTracking when runOnMount is true', async () => { 44 | await checkElementExistenceOnMount(true, testComponent(true)) 45 | }) 46 | 47 | it('should not show #isTracking when runOnMount is false', async () => { 48 | await checkElementExistenceOnMount(false, testComponent(false)) 49 | }) 50 | 51 | it('should not show #hasLeftPage when runOnMount is false', async () => { 52 | await checkElementExistenceOnMount( 53 | false, 54 | testComponent(false), 55 | '#hasLeftPage' 56 | ) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /src/functions/useMouseLeavePage/useMouseLeavePage.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useMouseLeavePage(runOnMount = true) { 4 | const isTracking = ref(false) 5 | const hasLeftPage = ref(false) 6 | 7 | const handleMouseOut = (e: any) => { 8 | const from = e.relatedTarget || e.toElement 9 | const mouseHasLeftPage = !from || from.nodeName === 'HTML' 10 | hasLeftPage.value = mouseHasLeftPage 11 | } 12 | 13 | const start = () => { 14 | if (isTracking.value) return 15 | document.addEventListener('mouseout', handleMouseOut) 16 | isTracking.value = true 17 | } 18 | 19 | const stop = () => { 20 | if (!isTracking.value) return 21 | document.removeEventListener('mouseout', handleMouseOut) 22 | isTracking.value = false 23 | } 24 | 25 | onMounted(() => runOnMount && start()) 26 | onUnmounted(stop) 27 | 28 | return { hasLeftPage, isTracking, start, stop } 29 | } 30 | -------------------------------------------------------------------------------- /src/functions/useOrientation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useOrientation' 2 | -------------------------------------------------------------------------------- /src/functions/useOrientation/stories/UseOrientationDemo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /src/functions/useOrientation/stories/useOrientation.md: -------------------------------------------------------------------------------- 1 | # useOrientation 2 | 3 | Vue function that tracks state of device's screen orientation. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | interface UseOrientationState { 9 | angle: number; 10 | type: string; 11 | } 12 | ``` 13 | 14 | ```typescript 15 | function useOrientation( 16 | initialState?: UseOrientationState, 17 | runOnMount?: boolean 18 | ): { 19 | orientation: Ref<{ 20 | angle: number; 21 | type: string; 22 | }>; 23 | isTracking: Ref; 24 | start: () => void; 25 | stop: () => void; 26 | }; 27 | ``` 28 | 29 | ### Parameters 30 | 31 | - `initialState: UseOrientationState` the initial state to use before the event listener is fired 32 | - `runOnMount: boolean` whether to track orientation on mount, `true` by default 33 | 34 | ### Returns 35 | 36 | - `orientation: Ref` 37 | - `angle: number`: the possible values for the window.orientation angle are: -90, 0, 90, 180. 38 | - `type: string`: the type can be `landscape-primary` `landscape-secondary` or `portrait-primary`, `portrait-secondary` 39 | - `isTracking: Ref` whether this function events are running or not 40 | - `start: Function` the function used to start tracking the device's screen orientation 41 | - `stop: Function` the function used to stop tracking the device's screen orientation 42 | 43 | ## Usage 44 | 45 | ```html 46 | 56 | 57 | 69 | ``` 70 | -------------------------------------------------------------------------------- /src/functions/useOrientation/stories/useOrientation.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseOrientationDemo from './UseOrientationDemo.vue' 5 | 6 | const functionName = 'useOrientation' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseOrientationDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 26 | 27 | 28 |
` 29 | }) 30 | 31 | storiesOf('sensors|useOrientation', module) 32 | .addParameters({ notes }) 33 | .add('Demo', basicDemo) 34 | -------------------------------------------------------------------------------- /src/functions/useOrientation/useOrientation.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export interface UseOrientationState { 4 | angle: number 5 | type: string 6 | } 7 | 8 | const defaultState: UseOrientationState = { 9 | angle: 0, 10 | type: 'landscape-primary' 11 | } 12 | 13 | export function useOrientation( 14 | initialState: UseOrientationState = defaultState, 15 | runOnMount = true 16 | ) { 17 | const isTracking = ref(false) 18 | const orientation = ref(initialState) 19 | 20 | const handleOrientationChange = () => { 21 | if (screen.orientation) { 22 | const { angle, type } = screen.orientation 23 | orientation.value = { angle, type } 24 | } else if (window.orientation) { 25 | orientation.value = { 26 | angle: typeof window.orientation === 'number' ? window.orientation : 0, 27 | type: '' 28 | } 29 | } else { 30 | orientation.value = initialState 31 | } 32 | } 33 | 34 | const start = () => { 35 | if (isTracking.value) return 36 | window.addEventListener('orientationchange', handleOrientationChange) 37 | handleOrientationChange() 38 | isTracking.value = true 39 | } 40 | 41 | const stop = () => { 42 | if (!isTracking.value) return 43 | window.removeEventListener('orientationchange', handleOrientationChange) 44 | isTracking.value = false 45 | } 46 | 47 | onMounted(() => runOnMount && start()) 48 | onUnmounted(stop) 49 | 50 | return { orientation, isTracking, start, stop } 51 | } 52 | -------------------------------------------------------------------------------- /src/functions/useRaf/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useRaf' 2 | -------------------------------------------------------------------------------- /src/functions/useRaf/stories/UseRafDemo.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 69 | 70 | 75 | -------------------------------------------------------------------------------- /src/functions/useRaf/stories/useRaf.md: -------------------------------------------------------------------------------- 1 | # useRaf 2 | 3 | Vue function that returns the total elapsed time excluding paused intervals. 4 | 5 | You can optionally specify how many times in a second should the elapsed 6 | time update by specifying the `fps` value. 7 | 8 | ## Reference 9 | 10 | ```typescript 11 | type TFps = number | Ref; 12 | 13 | function useRaf( 14 | fps?: TFps, 15 | runOnMount?: boolean 16 | ): { 17 | isRunning: Ref; 18 | elapsed: Ref; 19 | start: () => void; 20 | stop: () => void; 21 | } 22 | ``` 23 | 24 | ### Parameters 25 | 26 | - `fps: number | Ref` the amount of times per second that the elapsed time should update. 27 | Please note that when a value greater or equal to `60` is defined, the `fps` logic will be skipped completely 28 | therefore the elapsed time will update at full speed. By default the value is set to `60` so it will indeed 29 | update at full speed 30 | - `runOnMount: boolean` whether to run the Raf loop on mount, `true` by default. 31 | If `false`, you'll have to start the Raf with the `start` function 32 | 33 | ### Returns 34 | 35 | - `isRunning: Ref` the Raf status 36 | - `false` when the Raf loop is paused 37 | - `true` when the Raf loop is running 38 | - `elapsed: Ref` the total elapsed time excluding paused intervals 39 | - `start: Function` the function used for starting the Raf loop 40 | - `stop: Function` the function used for stopping the Raf loop 41 | 42 | ## Usage 43 | 44 | ```html 45 | 52 | 53 | 70 | ``` 71 | -------------------------------------------------------------------------------- /src/functions/useRaf/stories/useRaf.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseRafDemo from './UseRafDemo.vue' 5 | 6 | const functionName = 'useRaf' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseRafDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 26 | 27 | 28 |
` 29 | }) 30 | 31 | storiesOf('animations|useRaf', module) 32 | .addParameters({ notes }) 33 | .add('Demo', basicDemo) 34 | -------------------------------------------------------------------------------- /src/functions/useRaf/useRaf.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { useRaf } from '@src/vue-use-kit' 3 | 4 | let rafSpy: any = null 5 | // Logic to avoid infinite loop 6 | const totCalls = 4 7 | let count = 0 8 | beforeEach(() => { 9 | window.requestAnimationFrame = (cb: any) => { 10 | if (count >= totCalls) return 11 | count++ 12 | return cb() 13 | } 14 | rafSpy = jest.spyOn(window, 'requestAnimationFrame') 15 | }) 16 | 17 | afterEach(() => { 18 | count = 0 19 | jest.clearAllMocks() 20 | }) 21 | 22 | const testComponent = (onMount = false) => ({ 23 | template: ` 24 |
25 |
26 |
27 | 28 | 29 |
30 | `, 31 | setup() { 32 | const { isRunning, elapsed, start, stop } = useRaf(60, onMount) 33 | return { isRunning, elapsed, start, stop } 34 | } 35 | }) 36 | 37 | describe('useRaf', () => { 38 | it('should not show #isRunning when runOnMount is false', async () => { 39 | const wrapper = mount(testComponent(false)) 40 | await wrapper.vm.$nextTick() 41 | expect(rafSpy).not.toHaveBeenCalled() 42 | expect(wrapper.find('#isRunning').exists()).toBe(false) 43 | expect(wrapper.find('#elapsed').text()).toBe('0') 44 | }) 45 | 46 | it('should show #isRunning when runOnMount is true', async () => { 47 | const wrapper = mount(testComponent(true)) 48 | await wrapper.vm.$nextTick() 49 | expect(rafSpy).toHaveBeenCalled() 50 | expect(wrapper.find('#isRunning').exists()).toBe(true) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /src/functions/useRaf/useRaf.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from '@src/api' 2 | import { TFps, useRafFn } from '@src/functions/useRafFn' 3 | 4 | const fpsLimit = 60 5 | export function useRaf(fps: TFps = fpsLimit, runOnMount = true) { 6 | const elapsed = ref(0) 7 | const animHandler = (elapsedTime: number) => { 8 | elapsed.value = elapsedTime 9 | } 10 | 11 | const { isRunning, start, stop } = useRafFn(animHandler, fps, runOnMount) 12 | 13 | return { 14 | isRunning, 15 | elapsed, 16 | start, 17 | stop 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/functions/useRafFn/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useRafFn' 2 | -------------------------------------------------------------------------------- /src/functions/useRafFn/stories/useRafFn.md: -------------------------------------------------------------------------------- 1 | # useRafFn 2 | 3 | Vue function that calls given callback inside the RAF loop. 4 | 5 | You can optionally specify how many times the callback should 6 | run in a second by specifying the `fps` value. 7 | 8 | ## Reference 9 | 10 | ```typescript 11 | type TFps = number | Ref; 12 | 13 | function useRafFn( 14 | callback: Function, 15 | fps?: TFps, 16 | runOnMount?: boolean 17 | ): { 18 | isRunning: Ref; 19 | start: () => void; 20 | stop: () => void; 21 | } 22 | ``` 23 | 24 | ### Parameters 25 | 26 | - `callback: Function` the function to call inside the Raf loop 27 | - `fps: number | Ref` the amount of times per second that the callback should run. 28 | Please note that when a value greater or equal to `60` is defined, the `fps` logic will be skipped completely 29 | therefore the callback will run at full speed. By default the value is set to `60` so it will indeed 30 | run at full speed 31 | - `runOnMount: boolean` whether to run the Raf loop on mount, `true` by default. 32 | If `false`, you'll have to start the Raf with the `start` function 33 | 34 | ### Returns 35 | 36 | - `isRunning: Ref` the Raf status 37 | - `false` when the Raf loop is paused 38 | - `true` when the Raf loop is running 39 | - `start: Function` the function used for starting the Raf loop 40 | - `stop: Function` the function used for stopping the Raf loop 41 | 42 | ## Usage 43 | 44 | ```html 45 | 52 | 53 | 77 | ``` 78 | -------------------------------------------------------------------------------- /src/functions/useRafFn/useRafFn.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { ref } from '@src/api' 3 | import { useRafFn } from '@src/vue-use-kit' 4 | 5 | let rafSpy: any = null 6 | // Logic to avoid infinite loop 7 | const totCalls = 4 8 | let count = 0 9 | beforeEach(() => { 10 | window.requestAnimationFrame = (cb: any) => { 11 | if (count >= totCalls) return 12 | count++ 13 | return cb() 14 | } 15 | rafSpy = jest.spyOn(window, 'requestAnimationFrame') 16 | }) 17 | 18 | afterEach(() => { 19 | count = 0 20 | jest.clearAllMocks() 21 | }) 22 | 23 | const testComponent = (onMount = false) => ({ 24 | template: ` 25 |
26 |
27 |
28 | 29 | 30 |
31 | `, 32 | setup() { 33 | const elapsedTime = ref(0) 34 | 35 | const { isRunning, start, stop } = useRafFn( 36 | () => { 37 | elapsedTime.value = elapsedTime.value + 1 38 | }, 39 | 60, 40 | onMount 41 | ) 42 | 43 | return { isRunning, elapsedTime, start, stop } 44 | } 45 | }) 46 | 47 | describe('useRafFn', () => { 48 | it('should not show #isRunning when runOnMount is false', async () => { 49 | const wrapper = mount(testComponent(false)) 50 | await wrapper.vm.$nextTick() 51 | expect(rafSpy).not.toHaveBeenCalled() 52 | expect(wrapper.find('#isRunning').exists()).toBe(false) 53 | expect(wrapper.find('#elapsedTime').text()).toBe('0') 54 | }) 55 | 56 | it('should show #isRunning when runOnMount is true', async () => { 57 | const wrapper = mount(testComponent(true)) 58 | await wrapper.vm.$nextTick() 59 | expect(rafSpy).toHaveBeenCalled() 60 | expect(wrapper.find('#isRunning').exists()).toBe(true) 61 | expect(wrapper.find('#elapsedTime').text()).toBe(`${totCalls}`) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSampleComponent' 2 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/stories/UseSampleComponentDemo.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/stories/useSampleComponent.md: -------------------------------------------------------------------------------- 1 | # useSampleComponent 2 | 3 | Vue function that... 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | // function useSampleComponent() 9 | ``` 10 | 11 | ### Parameters 12 | 13 | - `value: string` lorem ipsa 14 | 15 | ### Returns 16 | 17 | - `value: Ref` lorem ipsa 18 | 19 | ## Usage 20 | 21 | ```html 22 | 23 | ``` 24 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/stories/useSampleComponent.storySample.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseSampleComponentDemo from './UseSampleComponentDemo.vue' 5 | 6 | const functionName = 'useSampleComponent' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseSampleComponentDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('category|useSampleComponent', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/useSampleComponent.spec.ts: -------------------------------------------------------------------------------- 1 | // import { mount } from '@src/helpers/test' 2 | // import { useSampleComponent } from '@src/vue-use-kit' 3 | 4 | afterEach(() => { 5 | jest.clearAllMocks() 6 | }) 7 | 8 | describe('useSampleComponent', () => { 9 | it('should do something', () => { 10 | // Add test here 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/functions/useSampleComponent/useSampleComponent.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useSampleComponent() { 4 | const isMounted = ref(false) 5 | 6 | onMounted(() => { 7 | isMounted.value = true 8 | }) 9 | 10 | onUnmounted(() => { 11 | isMounted.value = false 12 | }) 13 | 14 | return isMounted 15 | } 16 | -------------------------------------------------------------------------------- /src/functions/useScroll/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useScroll' 2 | -------------------------------------------------------------------------------- /src/functions/useScroll/stories/UseScrollDemo.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 52 | 53 | 70 | -------------------------------------------------------------------------------- /src/functions/useScroll/stories/useScroll.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseScrollDemo from './UseScrollDemo.vue' 5 | 6 | const functionName = 'useScroll' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseScrollDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('sensors|useScroll', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useScroll/useScroll.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | 3 | export function useScroll( 4 | elRef: Ref, 5 | ms = 150, 6 | runOnMount = true 7 | ) { 8 | const isTracking = ref(false) 9 | const isScrolling = ref(false) 10 | 11 | const x = ref(0) 12 | const y = ref(0) 13 | 14 | let scrollingTimeout: any = null 15 | const updateScrollStatus = () => { 16 | isScrolling.value = true 17 | clearTimeout(scrollingTimeout) 18 | scrollingTimeout = setTimeout(() => (isScrolling.value = false), ms) 19 | } 20 | 21 | const updateWindowElement = () => { 22 | x.value = window.pageXOffset 23 | y.value = window.pageYOffset 24 | } 25 | 26 | const updateHTMLElement = () => { 27 | x.value = (elRef.value as HTMLElement).scrollLeft 28 | y.value = (elRef.value as HTMLElement).scrollTop 29 | } 30 | 31 | const updateElScrollPos = () => { 32 | if (!elRef.value) return 33 | elRef.value === window ? updateWindowElement() : updateHTMLElement() 34 | } 35 | 36 | const handleScroll = () => { 37 | updateElScrollPos() 38 | updateScrollStatus() 39 | } 40 | 41 | const start = () => { 42 | if (isTracking.value) return 43 | if (elRef.value) { 44 | elRef.value.addEventListener('scroll', handleScroll, { 45 | capture: false, 46 | passive: true 47 | }) 48 | updateElScrollPos() 49 | } 50 | isTracking.value = true 51 | } 52 | 53 | const stop = () => { 54 | if (!isTracking.value) return 55 | if (elRef.value) elRef.value.removeEventListener('scroll', handleScroll) 56 | isTracking.value = false 57 | } 58 | 59 | onMounted(() => runOnMount && start()) 60 | onUnmounted(stop) 61 | 62 | return { x, y, isTracking, isScrolling, start, stop } 63 | } 64 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useScrollbarWidth' 2 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/stories/UseScrollbarWidthDemo.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/stories/useScrollbarWidth.md: -------------------------------------------------------------------------------- 1 | # useScrollbarWidth 2 | 3 | Vue function that gets current browser's scrollbar width. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useScrollbarWidth( 9 | runOnMount?: boolean 10 | ): { 11 | scrollbarWidth: Ref 12 | getScrollbarWidth: () => void 13 | } 14 | ``` 15 | 16 | ### Parameters 17 | 18 | - `runOnMount: boolean` whether to get scrollbar width on mount, `true` by default 19 | 20 | ### Returns 21 | 22 | - `scrollbarWidth: Ref` the scrollbar width 23 | - `getScrollbarWidth: Function` the function to call to get the scrollbar width 24 | 25 | ## Usage 26 | 27 | ```html 28 | 33 | 34 | 46 | ``` 47 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/stories/useScrollbarWidth.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseScrollbarWidthDemo from './UseScrollbarWidthDemo.vue' 5 | 6 | const functionName = 'useScrollbarWidth' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseScrollbarWidthDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('sensors|useScrollbarWidth', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/useScrollbarWidth.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { useScrollbarWidth } from '@src/vue-use-kit' 3 | 4 | afterEach(() => { 5 | jest.clearAllMocks() 6 | }) 7 | 8 | const testComponent = (onMount = true) => ({ 9 | template: ` 10 |
11 |
{{ scrollbarWidth }}
12 | 13 |
14 | `, 15 | setup() { 16 | const { scrollbarWidth, getScrollbarWidth } = useScrollbarWidth(onMount) 17 | return { scrollbarWidth, getScrollbarWidth } 18 | } 19 | }) 20 | 21 | describe('useScrollbarWidth', () => { 22 | it('should return a scrollbarWidth as a numeric value', async () => { 23 | const wrapper = mount(testComponent()) 24 | await wrapper.vm.$nextTick() 25 | expect(wrapper.find('#scrollbarWidth').text()).toBe('0') 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/functions/useScrollbarWidth/useScrollbarWidth.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, Ref } from '@src/api' 2 | 3 | export function useScrollbarWidth(runOnMount = true) { 4 | const scrollbarWidth = ref(0) 5 | 6 | const getScrollbarWidth = () => { 7 | // https://stackoverflow.com/a/13382873/974107 8 | // Creating invisible container 9 | const outer = document.createElement('div') 10 | outer.style.visibility = 'hidden' 11 | outer.style.overflow = 'scroll' // forcing scrollbar to appear 12 | outer.style.msOverflowStyle = 'scrollbar' // needed for WinJS apps 13 | document.body.appendChild(outer) 14 | 15 | // Creating inner element and placing it in the container 16 | const inner = document.createElement('div') 17 | outer.appendChild(inner) 18 | 19 | // Calculating difference between container's full width and the child width 20 | const scrollbarWidthValue = outer.offsetWidth - inner.offsetWidth 21 | 22 | // Removing temporary elements from the DOM 23 | if (outer.parentNode) outer.parentNode.removeChild(outer) 24 | 25 | scrollbarWidth.value = scrollbarWidthValue 26 | } 27 | 28 | onMounted(() => runOnMount && getScrollbarWidth()) 29 | 30 | return { scrollbarWidth, getScrollbarWidth } 31 | } 32 | -------------------------------------------------------------------------------- /src/functions/useSearchParams/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSearchParams' 2 | -------------------------------------------------------------------------------- /src/functions/useSearchParams/stories/Field.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /src/functions/useSearchParams/stories/useSearchParams.md: -------------------------------------------------------------------------------- 1 | # useSearchParams 2 | 3 | Vue function that tracks browser's location search parameters. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useSearchParams( 9 | parameters: string[], 10 | runOnMount?: boolean 11 | ): { 12 | searchParams: Ref 13 | isTracking: Ref 14 | start: () => void 15 | stop: () => void 16 | } 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `parameters: string[]` the list of parameters you wish to track 22 | - `runOnMount: boolean` whether to run the location search parameters tracking on mount, `true` by default 23 | 24 | ### Returns 25 | 26 | - `searchParams: Ref` the object containing the search parameters as key value pairs 27 | - `isTracking: Ref` whether this function events are running or not 28 | - `start: Function` the function used to start tracking the location search parameters 29 | - `stop: Function` the function used to stop tracking the location search parameters 30 | 31 | ## Usage 32 | 33 | ```html 34 | 49 | 50 | 70 | ``` 71 | -------------------------------------------------------------------------------- /src/functions/useSearchParams/stories/useSearchParams.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseSearchParamsDemo from './UseSearchParamsDemo.vue' 5 | 6 | const functionName = 'useSearchParams' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseSearchParamsDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 31 | 32 | 33 |
` 34 | }) 35 | 36 | storiesOf('sensors|useSearchParams', module) 37 | .addParameters({ notes }) 38 | .add('Demo', basicDemo) 39 | -------------------------------------------------------------------------------- /src/functions/useSearchParams/useSearchParams.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | import { 3 | patchHistoryMethodsOnce, 4 | normalizeEntriesData 5 | } from '@src/shared/utils' 6 | 7 | const normalizeParams = (urlParamsObj: { [key: string]: string }) => ( 8 | paramAcc: any, 9 | param: string 10 | ) => { 11 | paramAcc[param] = param in urlParamsObj ? urlParamsObj[param] : null 12 | return paramAcc 13 | } 14 | 15 | const getUrlParams = (parameters: string[]) => { 16 | const urlParamsObj = normalizeEntriesData( 17 | Array.from(new URLSearchParams(location.search).entries()) 18 | ) 19 | return parameters.reduce(normalizeParams(urlParamsObj), {}) 20 | } 21 | 22 | export function useSearchParams( 23 | parameters: T[], 24 | runOnMount = true 25 | ) { 26 | const searchParams = ref( 27 | parameters.reduce(normalizeParams({}), {} as { [key in T]: any }) 28 | ) 29 | const isTracking = ref(false) 30 | 31 | const handleParamsChange = () => 32 | (searchParams.value = getUrlParams(parameters)) 33 | 34 | const start = () => { 35 | if (isTracking.value) return 36 | patchHistoryMethodsOnce() 37 | handleParamsChange() 38 | window.addEventListener('popstate', handleParamsChange) 39 | window.addEventListener('pushstate', handleParamsChange) 40 | window.addEventListener('replacestate', handleParamsChange) 41 | isTracking.value = true 42 | } 43 | 44 | const stop = () => { 45 | if (!isTracking.value) return 46 | window.removeEventListener('popstate', handleParamsChange) 47 | window.removeEventListener('pushstate', handleParamsChange) 48 | window.removeEventListener('replacestate', handleParamsChange) 49 | isTracking.value = false 50 | } 51 | 52 | onMounted(() => runOnMount && start()) 53 | onUnmounted(stop) 54 | 55 | return { searchParams, isTracking, start, stop } 56 | } 57 | -------------------------------------------------------------------------------- /src/functions/useSessionStorage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSessionStorage' 2 | -------------------------------------------------------------------------------- /src/functions/useSessionStorage/stories/UseSessionStorageDemo.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 85 | -------------------------------------------------------------------------------- /src/functions/useSessionStorage/stories/useSessionStorage.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseSessionStorageDemo from './UseSessionStorageDemo.vue' 5 | 6 | const functionName = 'useSessionStorage' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseSessionStorageDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 21 | 22 | 23 |
` 24 | }) 25 | 26 | storiesOf('side effects|useSessionStorage', module) 27 | .addParameters({ notes }) 28 | .add('Demo', basicDemo) 29 | -------------------------------------------------------------------------------- /src/functions/useSessionStorage/useSessionStorage.ts: -------------------------------------------------------------------------------- 1 | import { createStorage, StorageOptions } from '@src/shared/createStorage' 2 | import { onMounted, Ref } from '@src/api' 3 | 4 | export function useSessionStorage( 5 | key: string, 6 | options?: StorageOptions, 7 | runOnMount = true 8 | ) { 9 | const { item, getItem, setItem, removeItem } = createStorage( 10 | sessionStorage, 11 | key, 12 | options 13 | ) 14 | 15 | onMounted(() => runOnMount && getItem()) 16 | 17 | return { 18 | item, 19 | getItem, 20 | setItem, 21 | removeItem 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/useSize/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSize' 2 | -------------------------------------------------------------------------------- /src/functions/useSize/stories/UseSizeDemo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | 44 | 78 | -------------------------------------------------------------------------------- /src/functions/useSize/stories/useSize.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseSizeDemo from './UseSizeDemo.vue' 5 | 6 | const functionName = 'useSize' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseSizeDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 26 | 27 | 28 |
` 29 | }) 30 | 31 | storiesOf('sensors|useSize', module) 32 | .addParameters({ notes }) 33 | .add('Demo', basicDemo) 34 | -------------------------------------------------------------------------------- /src/functions/useSize/useSize.spec.ts: -------------------------------------------------------------------------------- 1 | import { ref } from '@src/api' 2 | import { mount } from '@src/helpers/test' 3 | import { useSize } from '@src/vue-use-kit' 4 | 5 | afterEach(() => { 6 | jest.clearAllMocks() 7 | }) 8 | 9 | let observe: any 10 | let unobserve: any 11 | let disconnect: any 12 | beforeEach(() => { 13 | observe = jest.fn() 14 | unobserve = jest.fn() 15 | disconnect = jest.fn() 16 | ;(window as any).ResizeObserver = jest.fn(() => ({ 17 | observe, 18 | unobserve, 19 | disconnect 20 | })) 21 | }) 22 | 23 | const testComponent = (onMount = true) => ({ 24 | template: ` 25 |
26 | 27 | 28 |
29 | `, 30 | setup() { 31 | const elRef = ref(null) 32 | const { start, stop } = useSize(elRef, {}, onMount) 33 | return { start, stop, elRef } 34 | } 35 | }) 36 | 37 | describe('useSize', () => { 38 | it('should call ResizeObserver on mounted', () => { 39 | expect(observe).toHaveBeenCalledTimes(0) 40 | mount(testComponent()) 41 | expect(observe).toHaveBeenCalledTimes(1) 42 | }) 43 | 44 | it('should call ResizeObserver again when start is called', async () => { 45 | expect(observe).toHaveBeenCalledTimes(0) 46 | const wrapper = mount(testComponent()) 47 | expect(observe).toHaveBeenCalledTimes(1) 48 | wrapper.find('#stop').trigger('click') 49 | wrapper.find('#start').trigger('click') 50 | await wrapper.vm.$nextTick() 51 | expect(observe).toHaveBeenCalledTimes(2) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /src/functions/useSize/useSize.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | import { 3 | ResizeObserver as ResizeObserverInterface, 4 | ResizeObserverEntry, 5 | ResizeObserverObserveOptions 6 | } from '@src/types/ResizeObserver' 7 | 8 | const errorMsg = 'ResizeObserver is not supported, please install a polyfill' 9 | 10 | export function useSize( 11 | elRef: Ref, 12 | options: ResizeObserverObserveOptions = {}, 13 | runOnMount = true 14 | ) { 15 | const isTracking = ref(false) 16 | const observedEntry: Ref = ref(null) 17 | 18 | const handleObserver = (entries: ResizeObserverEntry[]) => { 19 | observedEntry.value = entries[0] 20 | } 21 | 22 | let observer: ResizeObserverInterface | null = null 23 | 24 | const start = () => { 25 | if (isTracking.value) return 26 | if (!('ResizeObserver' in window)) throw new Error(errorMsg) 27 | 28 | // Do not start if the observer is already initialized 29 | // or the elRef does not exist 30 | if (!elRef.value) return 31 | observer = new (window as any).ResizeObserver( 32 | handleObserver 33 | ) as ResizeObserverInterface 34 | observer.observe(elRef.value, options) 35 | isTracking.value = true 36 | } 37 | 38 | const stop = () => { 39 | if (!isTracking.value) return 40 | if (!observer) return 41 | observer.disconnect() 42 | isTracking.value = false 43 | } 44 | 45 | onMounted(() => runOnMount && start()) 46 | onUnmounted(stop) 47 | 48 | return { observedEntry, isTracking, start, stop } 49 | } 50 | -------------------------------------------------------------------------------- /src/functions/useTimeout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useTimeout' 2 | -------------------------------------------------------------------------------- /src/functions/useTimeout/stories/UseTimeoutDemo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 58 | -------------------------------------------------------------------------------- /src/functions/useTimeout/stories/useTimeout.md: -------------------------------------------------------------------------------- 1 | # useTimeout 2 | 3 | Vue function that returns `isReady` value as `true` after a specified `ms` amount of time. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useTimeout( 9 | ms?: number, 10 | runOnMount?: boolean 11 | ): { 12 | isReady: Ref; 13 | start: () => void; 14 | stop: () => void; 15 | } 16 | ``` 17 | 18 | ### Parameters 19 | 20 | - `ms: number` how many milliseconds to wait before the timer is completed 21 | - `runOnMount: boolean` whether to run the timeout on mount, `true` by default 22 | 23 | ### Returns 24 | 25 | - `isReady: Ref` the timer status 26 | - `false` when the timer is executing 27 | - `true` when the timer is completed 28 | - `null` when the timer is idle 29 | - `start: Function` the function used for starting or resetting the timer 30 | - `stop: Function` the function used to stop the timer 31 | 32 | ## Usage 33 | 34 | ```html 35 | 43 | 44 | 57 | ``` 58 | -------------------------------------------------------------------------------- /src/functions/useTimeout/stories/useTimeout.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseTimeoutDemo from './UseTimeoutDemo.vue' 5 | 6 | const functionName = 'useTimeout' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseTimeoutDemo }, 12 | template: ` 13 |
14 | 15 | 16 | 17 | 18 | 19 |
` 20 | }) 21 | 22 | storiesOf('animations|useTimeout', module) 23 | .addParameters({ notes }) 24 | .add('Demo', basicDemo) 25 | -------------------------------------------------------------------------------- /src/functions/useTimeout/useTimeout.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@src/helpers/test' 2 | import { computed } from '@src/api' 3 | import { useTimeout } from '@src/vue-use-kit' 4 | 5 | beforeEach(() => { 6 | jest.useFakeTimers() 7 | }) 8 | 9 | afterEach(() => { 10 | jest.clearAllTimers() 11 | jest.clearAllMocks() 12 | }) 13 | 14 | const testComponent = () => ({ 15 | template: ` 16 |
17 |
18 |
19 |
20 | `, 21 | setup() { 22 | const { isReady } = useTimeout(1000) 23 | const isIdle = computed(() => isReady.value === null) 24 | return { isReady, isIdle } 25 | } 26 | }) 27 | 28 | describe('useTimeout', () => { 29 | it('should show #isReady when the timers are called, but not #isIdle', async () => { 30 | const wrapper = mount(testComponent()) 31 | jest.runAllTimers() 32 | 33 | // Wait for Vue to append #isReady in the DOM 34 | await wrapper.vm.$nextTick() 35 | expect(wrapper.find('#isReady').exists()).toBe(true) 36 | expect(wrapper.find('#isIdle').exists()).toBe(false) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /src/functions/useTimeout/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from '@src/api' 2 | import { useTimeoutFn } from '@src/functions/useTimeoutFn' 3 | 4 | const noop = () => null 5 | export function useTimeout(ms = 0, runOnMount = true) { 6 | const { isReady, start, stop } = useTimeoutFn(noop, ms, runOnMount) 7 | return { isReady, start, stop } 8 | } 9 | -------------------------------------------------------------------------------- /src/functions/useTimeoutFn/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useTimeoutFn' 2 | -------------------------------------------------------------------------------- /src/functions/useTimeoutFn/stories/UseTimeoutFnDemo.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 78 | -------------------------------------------------------------------------------- /src/functions/useTimeoutFn/stories/useTimeoutFn.md: -------------------------------------------------------------------------------- 1 | # useTimeoutFn 2 | 3 | Vue function that calls given callback after a specified `ms` amount of time. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useTimeoutFn( 9 | callback: Function, 10 | ms?: number, 11 | runOnMount?: boolean 12 | ): { 13 | isReady: Ref; 14 | start: () => void; 15 | stop: () => void; 16 | } 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `callback: Function` the function to call when the timer finishes 22 | - `ms: number` how many milliseconds to wait before running the callback function 23 | - `runOnMount: boolean` whether to run the timeout on mount, `true` by default 24 | 25 | ### Returns 26 | 27 | - `isReady: Ref` the timer status 28 | - `false` when the timer is executing 29 | - `true` when the timer is completed 30 | - `null` when the timer is idle 31 | - `start: Function` the function used for starting or resetting the timer 32 | - `stop: Function` the function used for stopping the timer 33 | 34 | ## Usage 35 | 36 | ```html 37 | 46 | 47 | 70 | ``` 71 | -------------------------------------------------------------------------------- /src/functions/useTimeoutFn/stories/useTimeoutFn.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseTimeoutFnDemo from './UseTimeoutFnDemo.vue' 5 | 6 | const functionName = 'useTimeoutFn' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseTimeoutFnDemo }, 12 | template: ` 13 |
14 | 15 | 16 | 17 | 18 | 19 |
` 20 | }) 21 | 22 | storiesOf('animations|useTimeoutFn', module) 23 | .addParameters({ notes }) 24 | .add('Demo', basicDemo) 25 | -------------------------------------------------------------------------------- /src/functions/useTimeoutFn/useTimeoutFn.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, Ref } from '@src/api' 2 | 3 | export function useTimeoutFn(callback: Function, ms = 0, runOnMount = true) { 4 | const isReady = ref(null) 5 | let timeout: any = null 6 | 7 | const start = () => { 8 | isReady.value = false 9 | if (timeout) clearTimeout(timeout) 10 | 11 | timeout = setTimeout(() => { 12 | isReady.value = true 13 | callback() 14 | timeout = null 15 | }, ms) 16 | } 17 | 18 | const stop = () => { 19 | isReady.value = null 20 | if (timeout) { 21 | clearTimeout(timeout) 22 | timeout = null 23 | } 24 | } 25 | 26 | onMounted(() => runOnMount && start()) 27 | onUnmounted(stop) 28 | 29 | return { isReady, start, stop } 30 | } 31 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useWindowSize' 2 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/stories/UseWindowSizeDemo.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 40 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/stories/useWindowSize.md: -------------------------------------------------------------------------------- 1 | # useWindowSize 2 | 3 | Vue function that tracks `Window` scroll position. 4 | 5 | ## Reference 6 | 7 | ```typescript 8 | function useWindowSize( 9 | runOnMount?: boolean 10 | ): { 11 | width: Ref 12 | height: Ref 13 | isTracking: Ref 14 | start: () => void 15 | stop: () => void 16 | } 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `runOnMount: boolean` whether to track the window resize event on mount, `true` by default 22 | 23 | ### Returns 24 | 25 | - `width: Ref` the current window's `width` 26 | - `height: Ref` the current window's `height` 27 | - `isTracking: Ref` whether this function observer is running or not 28 | - `start: Function` the function used for start tracking window's resize event 29 | - `stop: Function` the function used for stop tracking window's resize event 30 | 31 | ## Usage 32 | 33 | ```html 34 | 45 | 46 | 58 | ``` 59 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/stories/useWindowSize.story.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | import path from 'path' 3 | import StoryTitle from '@src/helpers/StoryTitle.vue' 4 | import UseWindowSizeDemo from './UseWindowSizeDemo.vue' 5 | 6 | const functionName = 'useWindowSize' 7 | const functionPath = path.resolve(__dirname, '..') 8 | const notes = require(`./${functionName}.md`).default 9 | 10 | const basicDemo = () => ({ 11 | components: { StoryTitle, demo: UseWindowSizeDemo }, 12 | template: ` 13 |
14 | 19 | 20 | 25 | 26 | 27 |
` 28 | }) 29 | 30 | storiesOf('sensors|useWindowSize', module) 31 | .addParameters({ notes }) 32 | .add('Demo', basicDemo) 33 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/useWindowSize.spec.ts: -------------------------------------------------------------------------------- 1 | import { useWindowSize } from '@src/vue-use-kit' 2 | import { 3 | checkElementExistenceOnMount, 4 | checkOnMountAndUnmountEvents, 5 | checkOnStartEvents, 6 | checkOnStopEvents, 7 | mount 8 | } from '@src/helpers/test' 9 | 10 | afterEach(() => { 11 | jest.clearAllMocks() 12 | }) 13 | 14 | const testComponent = (onMount = true) => ({ 15 | template: ` 16 |
17 |
18 |
{{width}}
19 |
{{height}}
20 | 21 | 22 |
23 | `, 24 | setup() { 25 | const { width, height, isTracking, start, stop } = useWindowSize(onMount) 26 | return { width, height, isTracking, start, stop } 27 | } 28 | }) 29 | 30 | describe('useWindowSize', () => { 31 | const events = ['resize'] 32 | 33 | it('should add events on mounted and remove them on unmounted', async () => { 34 | await checkOnMountAndUnmountEvents(window, events, testComponent) 35 | }) 36 | 37 | it('should add events again when start is called', async () => { 38 | await checkOnStartEvents(window, events, testComponent) 39 | }) 40 | 41 | it('should remove events when stop is called', async () => { 42 | await checkOnStopEvents(window, events, testComponent) 43 | }) 44 | 45 | it('should show #isTracking when runOnMount is true', async () => { 46 | await checkElementExistenceOnMount(true, testComponent(true)) 47 | }) 48 | 49 | it('should not show #isTracking when runOnMount is false', async () => { 50 | await checkElementExistenceOnMount(false, testComponent(false)) 51 | }) 52 | 53 | it('should display the width and height values', async () => { 54 | const wrapper = mount(testComponent(true)) 55 | await wrapper.vm.$nextTick() 56 | expect(wrapper.find('#width').text()).toEqual(`${window.innerWidth}`) 57 | expect(wrapper.find('#height').text()).toEqual(`${window.innerHeight}`) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/functions/useWindowSize/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@src/api' 2 | import { isClient } from '@src/shared/utils' 3 | 4 | export function useWindowSize(runOnMount = true) { 5 | const width = ref(isClient ? window.innerWidth : Infinity) 6 | const height = ref(isClient ? window.innerHeight : Infinity) 7 | const isTracking = ref(false) 8 | 9 | const handleWindowResize = () => { 10 | width.value = window.innerWidth 11 | height.value = window.innerHeight 12 | } 13 | 14 | const start = () => { 15 | if (isTracking.value) return 16 | window.addEventListener('resize', handleWindowResize) 17 | handleWindowResize() 18 | isTracking.value = true 19 | } 20 | 21 | const stop = () => { 22 | if (!isTracking.value) return 23 | window.removeEventListener('resize', handleWindowResize) 24 | isTracking.value = false 25 | } 26 | 27 | onMounted(() => runOnMount && start()) 28 | onUnmounted(stop) 29 | 30 | return { width, height, isTracking, start, stop } 31 | } 32 | -------------------------------------------------------------------------------- /src/helpers/StoryTitle.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 60 | 61 | 82 | -------------------------------------------------------------------------------- /src/helpers/config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const pkg = require('../../package.json') 3 | 4 | /** 5 | * Removes extra forward slashes so that we always get a valid url 6 | */ 7 | const cleanUrl = (str: string) => str.replace(/([^:]\/)\/+/g, '$1') 8 | 9 | export const url = pkg.repository.url 10 | export const storiesFolder = 'stories' 11 | export const absUrl = `${url}/blob/master/` 12 | export const getDemoUrl = (functionPath: string, filePath: string) => { 13 | const absoluteUrl = cleanUrl( 14 | `${absUrl}/${functionPath}/${storiesFolder}/${filePath}` 15 | ) 16 | return absoluteUrl 17 | } 18 | export const getSourceUrl = (functionPath: string, filePath: string) => { 19 | const absoluteUrl = cleanUrl(`${absUrl}/${functionPath}/${filePath}.ts`) 20 | return absoluteUrl 21 | } 22 | -------------------------------------------------------------------------------- /src/shared/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookie from 'cookie' 2 | 3 | export const Cookies = () => { 4 | const state: any = { 5 | set(name = '', value = '', opts = { path: '/' }) { 6 | value = typeof value === 'object' ? JSON.stringify(value) : value 7 | document.cookie = Cookie.serialize(name, value, opts) 8 | }, 9 | 10 | get(name = '') { 11 | const cookies = Cookie.parse(document.cookie) 12 | const cookie = cookies[name] 13 | return cookie 14 | }, 15 | 16 | remove(name = '', opts = { path: '/', expires: new Date(0) }) { 17 | const cookie = state.get(name) 18 | opts.expires = new Date(0) 19 | if (typeof cookie !== 'undefined') state.set(name, '', opts) 20 | } 21 | } 22 | 23 | return state 24 | } 25 | -------------------------------------------------------------------------------- /src/types/ResizeObserver.ts: -------------------------------------------------------------------------------- 1 | export declare class ResizeObserver { 2 | constructor(callback: ResizeObserverCallback) 3 | disconnect: () => void 4 | observe: (target: Element, options?: ResizeObserverObserveOptions) => void 5 | unobserve: (target: Element) => void 6 | } 7 | 8 | export interface ResizeObserverEntry { 9 | readonly borderBoxSize: ResizeObserverEntryBoxSize 10 | readonly contentBoxSize: ResizeObserverEntryBoxSize 11 | readonly contentRect: DOMRectReadOnly 12 | readonly target: Element 13 | } 14 | 15 | export interface ResizeObserverObserveOptions { 16 | box?: 'content-box' | 'border-box' 17 | } 18 | 19 | export type ResizeObserverCallback = ( 20 | entries: ResizeObserverEntry[], 21 | observer: ResizeObserver 22 | ) => void 23 | 24 | export interface ResizeObserverEntryBoxSize { 25 | blockSize: number 26 | inlineSize: number 27 | } 28 | -------------------------------------------------------------------------------- /src/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/vue-use-kit.ts: -------------------------------------------------------------------------------- 1 | // Utility 2 | export * from './functions/getQuery' 3 | // UI 4 | export * from './functions/useClickAway' 5 | export * from './functions/useFullscreen' 6 | // Sensors 7 | export * from './functions/useGeolocation' 8 | export * from './functions/useHover' 9 | export * from './functions/useIdle' 10 | export * from './functions/useIntersection' 11 | export * from './functions/useKey' 12 | export * from './functions/useLocation' 13 | export * from './functions/useMedia' 14 | export * from './functions/useMediaDevices' 15 | export * from './functions/useMouse' 16 | export * from './functions/useMouseElement' 17 | export * from './functions/useMouseLeavePage' 18 | export * from './functions/useOrientation' 19 | export * from './functions/useSize' 20 | export * from './functions/useSearchParams' 21 | export * from './functions/useScroll' 22 | export * from './functions/useScrollbarWidth' 23 | export * from './functions/useWindowSize' 24 | // Animations 25 | export * from './functions/useIntervalFn' 26 | export * from './functions/useInterval' 27 | export * from './functions/useRafFn' 28 | export * from './functions/useRaf' 29 | export * from './functions/useTimeoutFn' 30 | export * from './functions/useTimeout' 31 | // Site Effects 32 | export * from './functions/useBeforeUnload' 33 | export * from './functions/useCookie' 34 | export * from './functions/useFetch' 35 | export * from './functions/useLocalStorage' 36 | export * from './functions/useSessionStorage' 37 | -------------------------------------------------------------------------------- /tools/build-clean.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const rimraf = require('rimraf') 3 | const { distDir } = require('./config') 4 | 5 | const handleError = (err: Error) => { 6 | if (err) return console.log(err) 7 | } 8 | 9 | // Remove lib folder 10 | rimraf(`${distDir}/lib`, handleError) 11 | 12 | // Remove stories folder 13 | rimraf(`${distDir}/**/stories/**`, handleError) 14 | 15 | // Remove test files 16 | rimraf(`${distDir}/**/*.spec.*`, handleError) 17 | 18 | // Remove build helpers files 19 | rimraf(`${distDir}/**/helpers/**`, handleError) 20 | 21 | // Remove tools 22 | rimraf(`${distDir}/**/tools/**`, handleError) 23 | 24 | // Remove useSampleComponent 25 | rimraf(`${distDir}/**/useSampleComponent/**`, handleError) 26 | -------------------------------------------------------------------------------- /tools/config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path') 3 | 4 | module.exports = { 5 | rootDir: path.resolve(__dirname, '..'), 6 | srcDir: path.resolve(__dirname, '..', 'src'), 7 | distDir: path.resolve(__dirname, '..', 'dist') 8 | } 9 | -------------------------------------------------------------------------------- /tools/gh-pages-publish.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { cd, exec, echo, touch } = require('shelljs') 3 | const { readFileSync } = require('fs') 4 | const url = require('url') 5 | 6 | const pkg = JSON.parse(readFileSync('package.json')) 7 | const repoUrl = pkg.repository.url 8 | const parsedUrl = url.parse(repoUrl) 9 | const repository = `${parsedUrl.host || ''}${parsedUrl.path || ''}` 10 | const ghToken = process.env.GH_TOKEN 11 | 12 | echo('Deploying docs!!!') 13 | cd('docs') 14 | touch('.nojekyll') 15 | exec('git init') 16 | exec('git add .') 17 | exec('git config user.name "Salvatore Tedde"') 18 | exec('git config user.email "microcipcip@gmail.com"') 19 | exec('git commit -m "docs(docs): update gh-pages"') 20 | exec( 21 | `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages` 22 | ) 23 | echo('Docs deployed!!') 24 | -------------------------------------------------------------------------------- /tools/rollup.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const resolve = require('rollup-plugin-node-resolve') 3 | const commonjs = require('rollup-plugin-commonjs') 4 | const camelCase = require('lodash.camelcase') 5 | const typescript = require('rollup-plugin-typescript2') 6 | const json = require('rollup-plugin-json') 7 | const { rootDir, srcDir } = require('./config') 8 | 9 | const pkg = require(`${rootDir}/package.json`) 10 | 11 | const libraryName = pkg.name 12 | 13 | const formatDate = () => { 14 | const pad = s => (s < 10 ? '0' + s : s) 15 | const d = new Date() 16 | return [pad(d.getDate()), pad(d.getMonth() + 1), d.getFullYear()].join('/') 17 | } 18 | 19 | const banner = `/** 20 | * Name: ${libraryName} 21 | * Author: ${pkg.author} 22 | * Website: ${pkg.repository.url} 23 | * Date: ${formatDate()} 24 | */` 25 | 26 | export default { 27 | input: `${srcDir}/${libraryName}.ts`, 28 | output: [ 29 | { 30 | banner, 31 | file: pkg.main, 32 | name: camelCase(libraryName), 33 | format: 'umd', 34 | globals: { 35 | vue: 'Vue', 36 | '@vue/composition-api': 'vueCompositionApi' 37 | } 38 | }, 39 | { file: pkg.module, format: 'es' } 40 | ], 41 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 42 | external: ['vue', '@vue/composition-api'], 43 | watch: { 44 | include: `${srcDir}/**` 45 | }, 46 | plugins: [ 47 | // Allow json resolution 48 | json(), 49 | // Compile TypeScript files 50 | typescript({ 51 | useTsconfigDeclarationDir: true 52 | }), 53 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 54 | commonjs(), 55 | // Allow node_modules resolution, so you can use 'external' to control 56 | // which external modules to include in the bundle 57 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 58 | resolve() 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src", "tools"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "module": "es2015", 7 | "lib": ["es2015", "es2016", "es2017", "dom", "dom.iterable"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "declaration": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "experimentalDecorators": true, 14 | "emitDecoratorMetadata": true, 15 | "declarationDir": "dist/types", 16 | "outDir": "dist/lib", 17 | "typeRoots": ["node_modules/@types"], 18 | "baseUrl": ".", 19 | "paths": { 20 | "@src/*": ["./src/*"] 21 | } 22 | }, 23 | "include": ["src"] 24 | } 25 | --------------------------------------------------------------------------------