├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ └── lint-and-test.yaml ├── .gitignore ├── .prettierrc.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs ├── .vuepress │ ├── config.js │ └── enhanceApp.js ├── README.md └── functions │ ├── README.md │ ├── battery.md │ ├── clipboard.md │ ├── device-light.md │ ├── device-media.md │ ├── device-motion.md │ ├── device-orientation.md │ ├── document-visibility.md │ ├── event-listener.md │ ├── fetch.md │ ├── fullscreen.md │ ├── geolocation.md │ ├── hardware-concurrency.md │ ├── intersection-observer.md │ ├── local-storage.md │ ├── media-query.md │ ├── memory-status.md │ ├── mouse-position.md │ ├── network.md │ ├── notification.md │ ├── preferred-color-scheme.md │ ├── preferred-languages.md │ ├── script.md │ ├── scroll-position.md │ ├── websocket.md │ ├── window-size.md │ └── worker.md ├── jest.config.js ├── package.json ├── scripts ├── build.js ├── config.js ├── defs.sh └── deploy.sh ├── src ├── Battery.ts ├── Clipboard.ts ├── DeviceLight.ts ├── DeviceMedia.ts ├── DeviceMotion.ts ├── DeviceOrientation.ts ├── DocumentVisibility.ts ├── EventListener.ts ├── Fetch.ts ├── FullScreen.ts ├── Geolocation.ts ├── HardwareConcurrency.ts ├── IntersectionObserver.ts ├── LocalStorage.ts ├── Media.ts ├── MemoryStatus.ts ├── MousePosition.ts ├── Network.ts ├── Notification.ts ├── PreferredColorScheme.ts ├── PreferredLanguages.ts ├── Script.ts ├── WebSocket.ts ├── WindowScrollPosition.ts ├── WindowSize.ts ├── Worker.ts ├── index.ts └── utils.ts ├── test ├── Battery.test.ts ├── Clipboard.test.ts ├── Fetch.test.ts ├── FullScreen.test.ts ├── HardwareConcurrency.test.ts ├── LocalStorage.test.ts └── utils │ └── index.ts ├── tsconfig.json ├── vue-shims.d.ts └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "commonjs": true, 9 | "es6": true, 10 | "jest/globals": true 11 | }, 12 | "extends": [ 13 | "standard", 14 | "plugin:jest/recommended", 15 | "plugin:@typescript-eslint/recommended", 16 | "plugin:prettier/recommended", 17 | "prettier/@typescript-eslint" 18 | ], 19 | "plugins": ["jest", "prettier", "@typescript-eslint"], 20 | "rules": { 21 | "@typescript-eslint/camelcase": "off", 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "@typescript-eslint/explicit-function-return-type": "off", 24 | "@typescript-eslint/no-use-before-define": "off" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | target-branch: master 10 | ignore: 11 | - dependency-name: eslint-plugin-jest 12 | versions: 13 | - 24.1.3 14 | - 24.1.5 15 | - 24.1.8 16 | - 24.3.1 17 | - 24.3.2 18 | - 24.3.4 19 | - 24.3.5 20 | - dependency-name: uglify-js 21 | versions: 22 | - 3.12.5 23 | - 3.12.6 24 | - 3.12.7 25 | - 3.12.8 26 | - 3.13.0 27 | - 3.13.1 28 | - 3.13.2 29 | - 3.13.3 30 | - dependency-name: "@types/node" 31 | versions: 32 | - 14.14.22 33 | - 14.14.25 34 | - 14.14.28 35 | - 14.14.31 36 | - 14.14.32 37 | - 14.14.34 38 | - 14.14.35 39 | - 14.14.37 40 | - dependency-name: "@types/jest" 41 | versions: 42 | - 26.0.20 43 | - 26.0.21 44 | - dependency-name: "@vuepress/plugin-pwa" 45 | versions: 46 | - 1.8.0 47 | - 1.8.1 48 | - dependency-name: vuepress 49 | versions: 50 | - 1.8.0 51 | - 1.8.1 52 | - dependency-name: "@testing-library/vue" 53 | versions: 54 | - 5.6.1 55 | - dependency-name: rollup-plugin-typescript2 56 | versions: 57 | - 0.29.0 58 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and test 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Install dependencies 15 | run: npm install 16 | - name: Lint Code 17 | run: npm run lint 18 | - name: Unit test code 19 | run: npm run test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The usual 2 | node_modules 3 | dist 4 | 5 | # For typescript cache 6 | .rpt2_cache 7 | 8 | # For tests 9 | coverage 10 | .nyc_output 11 | 12 | # Editors meta files 13 | .idea 14 | .vscode 15 | .vs 16 | *.swp 17 | 18 | # logs 19 | 20 | *.log 21 | 22 | .DS_Store -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // prettier.config.js or .prettierrc.js 2 | module.exports = { 3 | trailingComma: 'none', 4 | printWidth: 120, 5 | tabWidth: 2, 6 | semi: true, 7 | singleQuote: true, 8 | bracketSpacing: true, 9 | arrowParens: 'avoid', 10 | endOfLine: 'lf' 11 | }; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at logaretm1@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, thanks for taking interest into contributing to this project, below is what you need to know about it. 4 | 5 | ### Getting Started 6 | 7 | Fork the repository, or clone it: 8 | 9 | ```bash 10 | git clone https://github.com/logaretm/vue-use-web.git 11 | ``` 12 | 13 | Install dependencies using [yarn](https://yarnpkg.com) 14 | 15 | ```bash 16 | yarn 17 | ``` 18 | 19 | ### Folder Structure 20 | 21 | As you can see we have: 22 | 23 | - `src` contains the working code for the repository: 24 | - `docs`: contains the src and built files for the documentation, we use [vuepress](https://vuepress.vuejs.org/) to generate the docs content. 25 | - `scripts`: has all our custom scripts used to bundle the project, release the docs and localization files generation. 26 | 27 | Currently we have no tests at the moment. 28 | 29 | ### Issues 30 | 31 | When creating issues, please provide as much details as possible. A clear explanation on the issue and a reliable production example can help us greatly in improving this project. Your issue may get closed if it cannot be easily reproduced so please provide a working example using either [Codesandbox](https://codesandbox.io/) or [jsfiddle](https://jsfiddle.net/). Your example should only focus on the issue, it should be minimal and clearly produces the issue. 32 | 33 | If your issue gets closed for not providing enough info or not responding to the maintainers' comments, do not consider it a hostile action. There are probably other issues that the maintainers are working on and must give priority to issues that are well investigated, you can always revisit the issue and address the reasons that it was closed and we will be happy to re-open it and address it properly. Sometimes a commit will close your issue without a response from the maintainers so make sure you read the issue timeline to prevent any misunderstandings. 34 | 35 | ### Code Style 36 | 37 | The code style is enforced with `eslint` and `prettier` and is checked automatically whenever you commit. Any violation of the code style may prevent merging your contribution so make sure you follow it. And yes we love our semi-colons. 38 | 39 | ### Commit Style 40 | 41 | Commit messages are enforced with `commitlint` which is configured to help you write a suitable commit message, the checks are run automatically when you commit. 42 | 43 | ### Contributing To The Docs 44 | 45 | If you want to contribute to the docs you can find it in the `docs` folder. 46 | 47 | The docs require `./dist/vue-use-web.esm` as dependency to run successfully in your local machine. You can generate this dependency by executing the following command from the root of the repository: 48 | 49 | ```bash 50 | yarn build 51 | 52 | # or 53 | npm run build 54 | ``` 55 | 56 | And then you can run vuepress local dev server by running: 57 | 58 | ```bash 59 | yarn docs:dev 60 | # or 61 | npm run docs:dev 62 | ``` 63 | 64 | ### Pull Requests 65 | 66 | - Make sure you fill the PR template provided. 67 | - PRs should have titles that are clear as possible. 68 | - Make sure that your PR is up to date with the branch you are targeting, use `git rebase` for this. 69 | - Unfinished/In-Progress PRs should have `[WIP]` prefix to them, and preferably a checklist for ongoing todos. 70 | - Make sure to mention which issues are being fixed by the PR so they can be closed properly. 71 | - Make sure to preview all pending PRs to make sure your work won't conflict with other ongoing pull-request. 72 | - Coordinate with ongoing conflicting PRs' authors to make it easier to merge both your PRs. 73 | 74 | ### Source Code 75 | 76 | Currently we are using TypeScript for the source code. So make sure you are familiar with it, we don't use classes anywhere so the code should be more JavaScripty. 77 | 78 | ### Testing 79 | 80 | Currently there are no tests, go nuts! 81 | 82 | ### Building 83 | 84 | Use this command to build all project bundles and localization files: 85 | 86 | ```bash 87 | yarn build 88 | # or 89 | npm run build 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Abdelrahman Awad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-use-web 2 | 3 |

4 | 5 | ![Github Actions](https://img.shields.io/github/workflow/status/TarekTouati/vue-use-web/Lint%20and%20test/master?style=flat-square) 6 | ![Codacy grade](https://img.shields.io/codacy/grade/866989b53305443f9b1cdeb646b33d4c?style=flat-square) 7 | ![npm](https://img.shields.io/npm/v/vue-use-web?style=flat-square) 8 | ![npm bundle size](https://img.shields.io/bundlephobia/minzip/vue-use-web?style=flat-square) 9 | ![npm](https://img.shields.io/npm/dm/vue-use-web?style=flat-square) 10 | [![GitHub license](https://img.shields.io/github/license/Tarektouati/vue-use-web?style=flat-square)](https://github.com/Tarektouati/vue-use-web/blob/master/LICENSE) 11 | 12 |

13 | 14 | Web APIs implemented as Vue.js composition functions. 15 | 16 | This is a collection of [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) exposed as Vue.js composition hooks that's upcoming in Vue 3.0 17 | 18 | You can use them with Vue 2.0 using [@vue/composition-api](https://github.com/vuejs/composition-api) until Vue 3.0 gets out. 19 | 20 | ## What and why 21 | 22 | Web APIs are ever changing, this library aims to provide to Vue.js developers a stable interface that integrates well into the ecosystem. Also an interface that degrades gracefully when browsers do not support said features. 23 | 24 | I initially was choosing to expose this as a [Stateful functional components](https://logaretm.com/blog/2019-06-29-stateful-functional-components/) but that isn't very handy and is not future proof. Implementing these APIs in Vue composition API (hooks) makes them ready for Vue 3.0 and beyond. Personally I think this is the perfect example to showcase the power of the Composition API. 25 | 26 | ## Installation 27 | 28 | ```bash 29 | # install with yarn 30 | yarn add @vue/composition-api vue-use-web 31 | 32 | # install with npm 33 | npm install @vue/composition-api vue-use-web 34 | ``` 35 | 36 | ## Usage 37 | 38 | [Kindly Check the documentation for examples](https://Tarektouati.github.io/vue-use-web/). 39 | 40 | ## APIs 41 | 42 | Each composition function is designed to degrade gracefully so you can safely use them, but you should use these as a progressive enhancements for your apps. Check browsers compatibilities for each API. 43 | 44 | - [Battery Status API](https://Tarektouati.github.io/vue-use-web/functions/battery.html). 45 | - [Clipboard API](https://Tarektouati.github.io/vue-use-web/functions/clipboard.html). 46 | - [Device Light](https://Tarektouati.github.io/vue-use-web/functions/device-light.html). 47 | - [Device Motion](https://Tarektouati.github.io/vue-use-web/functions/device-motion.html). 48 | - [Device Orientation](https://Tarektouati.github.io/vue-use-web/functions/device-orientation.html). 49 | - [Event Listener](https://Tarektouati.github.io/vue-use-web/functions/event-listener.html). 50 | - [Fetch API](https://Tarektouati.github.io/vue-use-web/functions/fetch.html). 51 | - [Full-screen](https://Tarektouati.github.io/vue-use-web/functions/fullscreen.html). 52 | - [Geo-location API](https://Tarektouati.github.io/vue-use-web/functions/geolocation.html). 53 | - [Hardware Concurrency](https://Tarektouati.github.io/vue-use-web/functions/hardware-concurrency.html) 54 | - [Intersection Observer](https://Tarektouati.github.io/vue-use-web/functions/intersection-observer.html). 55 | - [Localstorage API](https://Tarektouati.github.io/vue-use-web/functions/local-storage.html) 56 | - [Media Query](https://Tarektouati.github.io/vue-use-web/functions/media-query.html) 57 | - [Mouse Position](https://Tarektouati.github.io/vue-use-web/functions/mouse-position.html) 58 | - [Network Information API](https://Tarektouati.github.io/vue-use-web/functions/network.html). 59 | - [Preferred Color Scheme](https://Tarektouati.github.io/vue-use-web/functions/preferred-color-scheme.html). 60 | - [Preferred Languages](https://Tarektouati.github.io/vue-use-web/functions/preferred-languages.html). 61 | - [Script](https://Tarektouati.github.io/vue-use-web/functions/script.html). 62 | - [WebSocket](https://Tarektouati.github.io/vue-use-web/functions/websocket.html). 63 | - [Window Scroll Position](https://Tarektouati.github.io/vue-use-web/functions/scroll-position.html). 64 | - [Window Size](https://Tarektouati.github.io/vue-use-web/functions/window-size.html). 65 | - [Worker](https://Tarektouati.github.io/vue-use-web/functions/worker.html). 66 | - [Notification](https://Tarektouati.github.io/vue-use-web/functions/worker.html). 67 | - [Media Devices](https://Tarektouati.github.io/vue-use-web/functions/device-media.html). 68 | - Bluetooth (TODO). 69 | - Share (TODO). 70 | - ResizeObserver (WIP) 71 | 72 | ## Inspiration 73 | 74 | This library is inspired by [the-platform](https://github.com/palmerhq/the-platform) and [standard-hooks](https://github.com/kripod/standard-hooks) for React.js. 75 | 76 | ## License 77 | 78 | MIT 79 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'body-leading-blank': [1, 'always'], 4 | 'footer-leading-blank': [1, 'always'], 5 | 'header-max-length': [2, 'always', 72], 6 | 'scope-case': [2, 'always', 'lower-case'], 7 | 'subject-case': [ 8 | 2, 9 | 'never', 10 | ['sentence-case', 'start-case', 'pascal-case', 'upper-case'] 11 | ], 12 | 'subject-empty': [2, 'never'], 13 | 'subject-full-stop': [2, 'never', '.'], 14 | 'type-case': [2, 'always', 'lower-case'], 15 | 'type-empty': [2, 'never'], 16 | 'type-enum': [ 17 | 2, 18 | 'always', 19 | [ 20 | 'build', 21 | 'chore', 22 | 'ci', 23 | 'docs', 24 | 'feat', 25 | 'fix', 26 | 'perf', 27 | 'refactor', 28 | 'revert', 29 | 'style', 30 | 'test' 31 | ] 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'useWeb', 3 | description: 'Use the Web APIs as Vue.js composition functions', 4 | plugins: [ 5 | '@vuepress/back-to-top', 6 | [ 7 | '@vuepress/pwa', 8 | { 9 | serviceWorker: true, 10 | updatePopup: true 11 | } 12 | ], 13 | [ 14 | '@vuepress/google-analytics', 15 | { 16 | ga: '' 17 | } 18 | ] 19 | ], 20 | base: '/vue-use-web/', 21 | head: [ 22 | ['meta', { charset: 'utf-8' }], 23 | ['meta', { name: 'msapplication-TileColor', content: '#ffffff' }], 24 | ['meta', { name: 'msapplication-TileImage', content: '/img/ms-icon-144x144.png' }], 25 | ['meta', { name: 'theme-color', content: '#41b883' }], 26 | ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }], 27 | ['meta', { property: 'og:image', content: '' }], 28 | ['link', { rel: 'apple-touch-icon', sizes: '57x57', href: '/img/apple-icon-57x57.png' }], 29 | ['link', { rel: 'apple-touch-icon', sizes: '60x60', href: '/img/apple-icon-60x60.png' }], 30 | ['link', { rel: 'apple-touch-icon', sizes: '72x72', href: '/img/apple-icon-72x72.png' }], 31 | ['link', { rel: 'apple-touch-icon', sizes: '76x76', href: '/img/apple-icon-76x76.png' }], 32 | ['link', { rel: 'apple-touch-icon', sizes: '114x114', href: '/img/apple-icon-114x114.png' }], 33 | ['link', { rel: 'apple-touch-icon', sizes: '120x120', href: '/img/apple-icon-120x120.png' }], 34 | ['link', { rel: 'apple-touch-icon', sizes: '144x144', href: '/img/apple-icon-144x144.png' }], 35 | ['link', { rel: 'apple-touch-icon', sizes: '152x152', href: '/img/apple-icon-152x152.png' }], 36 | ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/img/apple-icon-180x180.png' }], 37 | ['link', { rel: 'icon', type: 'image/png', sizes: '192x192', href: '/img/android-icon-192x192.png' }], 38 | ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/img/favicon-32x32.png' }], 39 | ['link', { rel: 'icon', type: 'image/png', sizes: '96x96', href: '/img/favicon-96x96.png' }], 40 | ['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/img/favicon-16x16.png' }] 41 | ], 42 | locales: { 43 | '/': { 44 | lang: 'en-US', 45 | title: 'useWeb', 46 | description: 'Use the Web APIs as Vue.js composition functions' 47 | } 48 | }, 49 | themeConfig: { 50 | repo: 'logaretm/vue-use-web', 51 | docsRepo: 'logaretm/vue-use-web', 52 | docsDir: 'docs', 53 | editLinks: true, 54 | sidebarDepth: 2, 55 | sidebar: [ 56 | { 57 | title: 'Composition Functions', 58 | collapsable: false, 59 | children: [ 60 | '/functions/battery', 61 | '/functions/clipboard', 62 | '/functions/device-light', 63 | '/functions/device-motion', 64 | '/functions/device-orientation', 65 | '/functions/document-visibility', 66 | '/functions/fetch', 67 | '/functions/fullscreen', 68 | '/functions/geolocation', 69 | '/functions/hardware-concurrency', 70 | '/functions/intersection-observer', 71 | '/functions/local-storage', 72 | '/functions/media-query', 73 | '/functions/memory-status', 74 | '/functions/mouse-position', 75 | '/functions/network', 76 | '/functions/preferred-color-scheme', 77 | '/functions/preferred-languages', 78 | '/functions/script', 79 | '/functions/websocket', 80 | '/functions/scroll-position', 81 | '/functions/window-size', 82 | '/functions/notification', 83 | '/functions/device-media' 84 | ] 85 | }, 86 | { 87 | title: 'Cook Book (WIP)', 88 | collapsable: false, 89 | children: [] 90 | } 91 | ], 92 | locales: { 93 | '/': { 94 | label: 'English', 95 | selectText: 'Languages', 96 | editLinkText: 'Help us improve this page!', 97 | nav: [{ text: 'Functions', link: '/functions/' }] 98 | } 99 | } 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | import VueCompositionAPI from '@vue/composition-api'; 2 | 3 | export default ({ Vue }) => { 4 | Vue.use(VueCompositionAPI); 5 | }; 6 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useWeb 3 | lang: en-US 4 | home: true 5 | # heroImage: 6 | actionText: Get Started → 7 | actionLink: ./functions/ 8 | features: 9 | - title: Reactive 10 | details: All the APIs have been repurposed to be reactive so you can use them directly in your components. 11 | - title: Graceful Degradation 12 | details: Sensible fallbacks are in place if the browser does support an API or the user doesn't give the permission. 13 | - title: TypeScript Support 14 | details: Written in TypeScript. 15 | footer: MIT Licensed | Copyright © 2019-present Abdelrahman Awad 16 | description: Web APIs implemented as Vue.js composition functions 17 | meta: 18 | - name: og:title 19 | content: useWeb 20 | - name: og:description 21 | content: Web APIs implemented as Vue.js composition functions 22 | --- 23 | 24 | # Quick Setup 25 | 26 | Web APIs implemented as Vue.js composition functions. currently usable with the `@vue/composition-api` adapter for Vue.js 2.x. 27 | 28 | ## Installation 29 | 30 | ```bash 31 | # install with yarn 32 | yarn add @vue/composition-api vue-use-web 33 | 34 | # install with npm 35 | npm install @vue/composition-api vue-use-web 36 | ``` 37 | 38 | ## Web APIs 39 | 40 | - [Battery Status API](./functions/battery.md). 41 | - [Clipboard API](./functions/clipboard.md). 42 | - [Device Light](./functions/device-light.md). 43 | - [Device Motion](./functions/device-motion.md). 44 | - [Device Orientation](./functions/device-orientation.md). 45 | - [Document Visibility](./functions/document-visibility.md). 46 | - [Event Listener](./functions/event-listener). 47 | - [Fetch API](./functions/fetch.md). 48 | - [Full-screen](./functions/fullscreen.md). 49 | - [Geo-location API](./functions/geolocation.md). 50 | - [Hardware Concurrency](./functions/hardware-concurrency.md). 51 | - [Intersection Observer](./functions/intersection-observer.md). 52 | - [Local-storage API](./functions/local-storage.md) 53 | - [Media Query](./functions/media-query.md) 54 | - [Memory Status](./functions/memory-status.md). 55 | - [Mouse Position](./functions/mouse-position.md) 56 | - [Network Information API](./functions/network.md). 57 | - [Preferred Color Scheme](./functions/preferred-color-scheme.md). 58 | - [Preferred Languages](./functions/preferred-languages.md). 59 | - [Script](./functions/script.md). 60 | - [WebSocket](./functions/websocket.md). 61 | - [Window Scroll Position](./functions/scroll-position.md). 62 | - [Window Size](./functions/window-size.md). 63 | - [Worker](./functions/worker.md). 64 | - [Notification](./functions/notification.md). 65 | - Bluetooth . 66 | - Share . 67 | 68 | ## Usage 69 | 70 | ```vue 71 | 74 | 75 | 86 | ``` 87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/functions/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | `vue-use-web` is a collection of [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) exposed as Vue.js composition hooks that's upcoming in Vue 3.0 4 | 5 | You can use them with Vue 2.0 using [@vue/composition-api](https://github.com/vuejs/composition-api) until Vue 3.0 gets out. 6 | 7 | ## What and why 8 | 9 | Web APIs are ever changing, this library aims to provide to Vue.js developers a stable interface that integrates well into the ecosystem. Also an interface that degrades gracefully when browsers do not support said features. 10 | 11 | I initially was choosing to expose this as a [Stateful functional components](https://logaretm.com/blog/2019-06-29-stateful-functional-components/) but that isn't very handy and is not future proof. Implementing these APIs in Vue composition API (hooks) makes them ready for Vue 3.0 and beyond. Personally I think this is the perfect example to showcase the power of the Composition API. 12 | 13 | ## Features 14 | 15 | - Reactive Web APIs that are compatible with Vue.js. 16 | - Graceful degradation for old browsers and denied permissions. 17 | - Maintains similarities to the original APIs as close as possible. 18 | - Written in TypeScript. 19 | 20 | ## Available Functions 21 | 22 | These are the currently implemented Web APIs and the planned ones. 23 | 24 | - [Battery Status API](./battery.md). 25 | - [Clipboard API](./clipboard.md). 26 | - [Device Light](./device-light.md). 27 | - [Device Motion](./device-motion.md). 28 | - [Device Orientation](./device-orientation.md). 29 | - [Document Visibility](./document-visibility.md). 30 | - [Event Listener](./event-listener). 31 | - [Fetch API](./fetch.md). 32 | - [Full-screen](./fullscreen.md). 33 | - [Geo-location API](./geolocation.md). 34 | - [Hardware Concurrency](./hardware-concurrency.md). 35 | - [Intersection Observer](./intersection-observer.md). 36 | - [Local storage API](./local-storage.md). 37 | - [Media Query](./media-query.md). 38 | - [Memory Status](./memory-status.md) 39 | - [Mouse Position](./mouse-position.md). 40 | - [Network Information API](./network.md). 41 | - [Preferred Color Scheme](./preferred-color-scheme.md). 42 | - [Preferred Languages](./preferred-languages.md). 43 | - [Script](./script.md). 44 | - [WebSocket](./websocket.md). 45 | - [Window Scroll Position](./scroll-position.md). 46 | - [Window Size](./window-size.md). 47 | - [Worker](./worker.md). 48 | - [Media Devices](./device-media.md) 49 | - Bluetooth . 50 | - Notification . 51 | - Share . 52 | -------------------------------------------------------------------------------- /docs/functions/battery.md: -------------------------------------------------------------------------------- 1 | # Battery Status 2 | 3 | > The [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API), more often referred to as the Battery API, provides information about the system's battery charge level and lets you be notified by events that are sent when the battery level or charging status change. This can be used to adjust your app's resource usage to reduce battery drain when the battery is low, or to save changes before the battery runs out in order to prevent data loss. 4 | 5 | ## State 6 | 7 | The `useBattery` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useBattery } from 'vue-use-web'; 11 | 12 | const { isCharging, chargingTime, dischargingTime, level } = useBattery(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | --------------- | --------- | ----------------------------------------------------------------- | 17 | | isCharging | `Boolean` | If the device is currently charging. | 18 | | chargingTime | `Number` | The number of seconds until the device becomes fully charged. | 19 | | dischargingTime | `Number` | The number of seconds before the device becomes fully discharged. | 20 | | level | `Number` | A number between 0 and 1 representing the current charge level. | 21 | 22 | ## Use-cases 23 | 24 | Our applications normally are not empathetic to battery level, we can make a few adjustments to our applications that will be more friendly to low battery users. 25 | 26 | - Trigger a special "dark-mode" battery saver theme settings. 27 | - Stop auto playing videos in news feeds. 28 | - Disable some background workers that are not critical. 29 | - Limit network calls and reduce CPU/Memory consumption. 30 | 31 | ## Example 32 | 33 | Try the following example with a device that has batteries and chrome browser. 34 | 35 | ```vue 36 | 44 | 45 | 56 | ``` 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/functions/clipboard.md: -------------------------------------------------------------------------------- 1 | # Clipboard 2 | 3 | > The [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) provides the ability to respond to clipboard commands (cut, copy, and paste) as well as to asynchronously read from and write to the system clipboard. Access to the contents of the clipboard is gated behind the Permissions API; without user permission, reading or altering the clipboard contents is not permitted. 4 | 5 | :::tip Note! 6 | This API is designed to supersede accessing the clipboard using document.execCommand(). 7 | ::: 8 | 9 | ## State 10 | 11 | The `useClipboard` function exposes the following reactive state: 12 | 13 | ```js 14 | import { useClipboard } from 'vue-use-web'; 15 | 16 | const { text } = useClipboard(); 17 | ``` 18 | 19 | | State | Type | Description | 20 | | ----- | -------- | ---------------------------------- | 21 | | text | `string` | the current text in the clipboard. | 22 | 23 | ## Methods 24 | 25 | The `useClipboard` function exposes the following methods: 26 | 27 | ```js 28 | import { useClipboard } from 'vue-use-web'; 29 | 30 | const { write } = useClipboard(); 31 | 32 | write('hello world!'); 33 | ``` 34 | 35 | | Signature | Description | 36 | | -------------------- | -------------------------------------------- | 37 | | `write(str: string)` | Writes the given string it in the clipboard. | 38 | 39 | ## Use-cases 40 | 41 | I'm sure you can think of multiple situations where you would like to copy the clipboard text or write some data into them. Mainly stuff like copying code in snippets, referral links, recovery codes or really any hard to remember text. 42 | 43 | ## Example 44 | 45 | ```vue 46 | 52 | 53 | 64 | ``` 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/functions/device-light.md: -------------------------------------------------------------------------------- 1 | # Device Light 2 | 3 | > The [DeviceLightEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceLightEvent) provides web developers with information from photo sensors or similar detectors about ambient light levels near the device. For example this may be useful to adjust the screen's brightness based on the current ambient light level in order to save energy or provide better readability. 4 | 5 | ## State 6 | 7 | The `useDeviceLight` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useDeviceLight } from 'vue-use-web'; 11 | 12 | const { value } = useDeviceLight(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----- | -------- | --------------------------------------------------------------------------- | 17 | | value | `Number` | The level of the ambient light in [lux](https://en.wikipedia.org/wiki/Lux). | 18 | 19 | ## Use-cases 20 | 21 | Detecting the ambient light around user devices can have some applications: 22 | 23 | - Toggling between themes to adjust to the light level like using a dark theme in dark environments. 24 | 25 | ## Example 26 | 27 | You can test the following example on Firefox browser with an Andriod device with a light sensor. You should enable ambient light sensor in firefox's settings' `about:config`. 28 | 29 | ```vue 30 | 33 | 34 | 45 | ``` 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/functions/device-media.md: -------------------------------------------------------------------------------- 1 | # Device Media 2 | 3 | > The MediaDevices interface provides access to connected [media input devices](https://developer.mozilla.org/fr/docs/Web/API/MediaDevices) like cameras and microphones, as well as screen sharing. In essence, it lets you obtain access to any hardware source of media data.. 4 | 5 | ## State 6 | 7 | ```js 8 | import { useDeviceMedia } from 'vue-use-web'; 9 | 10 | const { stream } = useUserMedia({ audio: false, video: true }); 11 | ``` 12 | 13 | `useDeviceMedia` returns a 2 values: 14 | 15 | | State | Type | Description | 16 | | ------ | ------------------ | ----------------------------------------- | 17 | | stream | `Ref` | Audio and video stream. | 18 | | error | `Ref` | An error message in case mediaDevices API | 19 | 20 | ## Example 21 | 22 | ```vue 23 | 26 | 27 | 39 | ``` 40 | 41 | ## Demo 42 | 43 | TODO: Add cool live example! 44 | -------------------------------------------------------------------------------- /docs/functions/device-motion.md: -------------------------------------------------------------------------------- 1 | # Device Motion 2 | 3 | > The [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent) provides web developers with information about the speed of changes for the device's position and orientation. 4 | 5 | ## State 6 | 7 | The `useDeviceMotion` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useDeviceMotion } from 'vue-use-web'; 11 | 12 | const { acceleration, accelerationIncludingGravity, rotationRate, interval } = useDeviceMotion(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | 17 | | acceleration | `object` | An object giving the acceleration of the device on the three axis X, Y and Z. | 18 | | accelerationIncludingGravity | `object` | An object giving the acceleration of the device on the three axis X, Y and Z with the effect of gravity. | 19 | | rotationRate | `object` | An object giving the rate of change of the device's orientation on the three orientation axis alpha, beta and gamma. | 20 | | interval | `Number` | A number representing the interval of time, in milliseconds, at which data is obtained from the device.. | 21 | 22 | You can find [more information about the state on the MDN](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent#Properties). 23 | 24 | ## Config 25 | 26 | `useDeviceMotion` function takes an options object as an optional parameter. 27 | 28 | ```js 29 | import { useDeviceMotion } from 'vue-use-web'; 30 | 31 | const { acceleration } = useDeviceMotion({ 32 | throttleMs: 30 33 | }); 34 | ``` 35 | 36 | | Config | Type | Description | 37 | | ---------- | -------- | ---------------------------------------------- | 38 | | throttleMs | `Number` | The debounce rate of the updates to the state. | 39 | 40 | ## Example 41 | 42 | ```vue 43 | 55 | 56 | 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/functions/device-orientation.md: -------------------------------------------------------------------------------- 1 | # Device Orientation 2 | 3 | > The [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent) provides web developers with information from the physical orientation of the device running the web page. 4 | 5 | ## State 6 | 7 | The `useDeviceOrientation` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useDeviceOrientation } from 'vue-use-web'; 11 | 12 | const { isAbsolute, alpha, beta, gamma } = useDeviceOrientation(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | 17 | | isAbsolute | `object` | A boolean that indicates whether or not the device is providing orientation data absolutely. | 18 | | alpha | `object` | A number representing the motion of the device around the z axis, express in degrees with values ranging from 0 to 360. | 19 | | beta | `Number` | A number representing the motion of the device around the x axis, express in degrees with values ranging from -180 to 180. | 20 | | gamma | `object` | A number representing the motion of the device around the y axis, express in degrees with values ranging from -90 to 90. | 21 | 22 | You can find [more information about the state on the MDN](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent#Properties). 23 | 24 | ## Example 25 | 26 | ``` 27 | TODO 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/functions/document-visibility.md: -------------------------------------------------------------------------------- 1 | # Document Visibility 2 | 3 | > The [`Document.visibilityState`](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState) read-only property returns the visibility of the document, that is in which context this element is now visible. It is useful to know if the document is in the background or an invisible tab, or only loaded for pre-rendering. Possible values are: 4 | 5 | ## State 6 | 7 | ```js 8 | import { useDocumentVisibility } from 'vue-use-web'; 9 | 10 | const pageIsVisible = useDocumentVisibility(); 11 | ``` 12 | 13 | `useDocumentVisibility` only returns a single value which is a boolean. 14 | 15 | | State | Type | Description | 16 | | --------- | -------------- | ---------------------------------------------------------- | 17 | | isVisible | `Ref` | If the page is currently visible (not in a background tab) | 18 | 19 | ## Example 20 | 21 | ```vue 22 | 25 | 26 | 40 | ``` 41 | 42 | ## Demo 43 | 44 | TODO: Add cool live example! 45 | -------------------------------------------------------------------------------- /docs/functions/event-listener.md: -------------------------------------------------------------------------------- 1 | # Event Listeners 2 | 3 | > The [EventTarget method addEventListener()](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) sets up a function that will be called whenever the specified event is delivered to the target. Common targets are Element, Document, and Window, but the target may be any object that supports events (such as XMLHttpRequest). 4 | 5 | This composition function adds a **managed** event listener to the given object, meaning it will automatically remove the listener when the component unmounts. 6 | 7 | ## State 8 | 9 | `useEventListener` Doesn't return any state. 10 | 11 | ## Example 12 | 13 | ```vue 14 | 19 | 20 | 36 | ``` 37 | 38 | ## Demo 39 | 40 | TODO: Add cool live example! 41 | -------------------------------------------------------------------------------- /docs/functions/fetch.md: -------------------------------------------------------------------------------- 1 | # Fetch 2 | 3 | > The [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch). 4 | 5 | ## State 6 | 7 | The `useFetch` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useFetch } from 'vue-use-web'; 11 | 12 | const { isLoading, response, error, success } = useFetch('http://myurl.com'); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ---------- | ------------------------ | ----------------------------------------------------------------------------------------------- | 17 | | error | `Boolean` | If the request resulted in a non 200 status code. | 18 | | headers | `Record` | The response headers. | 19 | | isLoading | `Boolean` | If the request is pending. | 20 | | response | `any` | The response body as JSON or the raw response body as a string (depending on the content-type). | 21 | | status | `number` | The HTTP status code. | 22 | | statusText | `number` | The HTTP status text, eg: "OK" for 200. | 23 | | success | `Boolean` | If the request is successful. i.e resulted in 200 status code. | 24 | | type | `string` | [Response type](https://developer.mozilla.org/en-US/docs/Web/API/Response/type). | 25 | 26 | ## Methods 27 | 28 | The `useFetch` function exposes the following methods: 29 | 30 | ```js 31 | import { useFetch } from 'vue-use-web'; 32 | 33 | const { cancel } = useFetch(elem); 34 | ``` 35 | 36 | | Signature | Description | 37 | | --------- | ----------------------------------------------------------------------------------------------------------------------------------- | 38 | | `cancel` | Cancels the fetch request if browser supports `AbortController`, otherwise the request will complete but will not update the state. | 39 | 40 | ## Example 41 | 42 | ```vue 43 | 52 | 53 | 64 | ``` 65 | 66 | TODO: useFetch example 67 | -------------------------------------------------------------------------------- /docs/functions/fullscreen.md: -------------------------------------------------------------------------------- 1 | # Full Screen 2 | 3 | > The [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API) adds methods to present a specific Element (and its descendants) in full-screen mode, and to exit full-screen mode once it is no longer needed. This makes it possible to present desired content—such as an online game—using the user's entire screen, removing all browser user interface elements and other applications from the screen until full-screen mode is shut off. 4 | 5 | ## State 6 | 7 | The `useFullscreen` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useFullscreen } from 'vue-use-web'; 11 | import { ref } from '@vue/composition-api'; 12 | 13 | const elem = ref(null); 14 | 15 | const { isFullscreen } = useFullscreen(elem); 16 | ``` 17 | 18 | | State | Type | Description | 19 | | ------------ | --------- | ------------------------------------------------ | 20 | | isFullscreen | `Boolean` | If the element is currently in full screen mode. | 21 | 22 | ## Methods 23 | 24 | The `useFullscreen` function exposes the following methods: 25 | 26 | ```js 27 | import { ref } from '@vue/composition-api'; 28 | import { useFullscreen } from 'vue-use-web'; 29 | 30 | const elem = ref(null); 31 | 32 | const { enterFullscreen } = useFullscreen(elem); 33 | ``` 34 | 35 | | Signature | Description | 36 | | ----------------------- | ------------------------------------------------ | 37 | | `enterFullscreen(void)` | Enters the fullscreen mode for the element. | 38 | | `exitFullscreen(void)` | Exits the fullscreen mode from **all** elements. | 39 | 40 | ## Example 41 | 42 | ```vue 43 | 49 | 50 | 72 | ``` 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/functions/geolocation.md: -------------------------------------------------------------------------------- 1 | # Geolocation 2 | 3 | > The [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API) allows the user to provide their location to web applications if they so desire. For privacy reasons, the user is asked for permission to report location information. 4 | 5 | ## State 6 | 7 | The `useGeolocation` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useGeolocation } from 'vue-use-web'; 11 | 12 | const { coords, locatedAt, error } = useGeolocation(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | --------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------ | 17 | | coords | [Coordinates](https://developer.mozilla.org/en-US/docs/Web/API/Coordinates) | information about the position retrieved like the latitude and longitude | 18 | | locatedAt | `Date` | The time of the last geolocation call | 19 | | error | `string` | An error message in case geolocation API fails. | 20 | 21 | ## Config 22 | 23 | `useGeolocation` function takes [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object as an optional parameter. 24 | 25 | ```js 26 | import { useGeolocation } from 'vue-use-web'; 27 | 28 | const { coords } = useGeolocation({ 29 | enableHighAccuracy: true, 30 | maximumAge: 30000, 31 | timeout: 27000 32 | }); 33 | ``` 34 | 35 | ## Example 36 | 37 | ```vue 38 | 44 | 45 | 56 | ``` 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/functions/hardware-concurrency.md: -------------------------------------------------------------------------------- 1 | # Hardware Concurrency 2 | 3 | > The [Hardware Concurrency API](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency) The navigator.hardwareConcurrency read-only property returns the number of logical processors available to run threads on the user's computer. 4 | 5 | ## State 6 | 7 | The `useHardWareConcurrency` function exposes the following **readonly** reactive state: 8 | 9 | ```js 10 | import { useHardwareConcurrency } from 'vue-use-web'; 11 | 12 | const { logicalProcessors } = useHardwareConcurrency(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----------------- | --------- | ----------------------------------------------------- | 17 | | logicalProcessors | `Number` | Number of logical processors available on the device. | 18 | | unsupported | `Boolean` | If the API is unsupported. | 19 | 20 | ## Example 21 | 22 | TODO: Some cool example! 23 | -------------------------------------------------------------------------------- /docs/functions/intersection-observer.md: -------------------------------------------------------------------------------- 1 | # Intersection Observer 2 | 3 | > The [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. 4 | 5 | ## State 6 | 7 | The `useIntersectionObserver` function exposes the following reactive state: 8 | 9 | ```js 10 | import { ref } from '@vue/composition-api'; 11 | import { useIntersectionObserver } from 'vue-use-web'; 12 | 13 | // reference to the element being observed. 14 | const elem = ref(null); 15 | 16 | const { isIntersecting, isFullyInView, intersectionRatio } = useIntersectionObserver(elem); 17 | ``` 18 | 19 | | State | Type | Description | 20 | | ----------------- | --------- | --------------------------------------------------------------------------------- | 21 | | isIntersecting | `Boolean` | If the element or a part of it is in the view port. | 22 | | isFullyInView | `Boolean` | If the element is fully contained within the viewport. | 23 | | intersectionRatio | `Number` | A number between 0 and 1 representing how much of the element is in the viewport. | 24 | 25 | ## Methods 26 | 27 | The `useIntersectionObserver` function exposes the following methods to give you fine control over the observation state: 28 | 29 | ```js 30 | import { ref } from '@vue/composition-api'; 31 | import { useIntersectionObserver } from 'vue-use-web'; 32 | 33 | const elem = ref(null); 34 | 35 | const { observe, unobserve } = useIntersectionObserver(elem); 36 | ``` 37 | 38 | | Signature | Description | 39 | | ----------------- | ------------------------------------- | 40 | | `observe(void)` | Starts/Resumes observing the element. | 41 | | `unobserve(void)` | Stops/Pauses observing the element. | 42 | 43 | ## Config 44 | 45 | `useIntersectionObserver` function takes a required parameter that is a ref to the observed element and [optional config](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#Parameters). 46 | 47 | ```js 48 | import { ref } from '@vue/composition-api'; 49 | import { useIntersectionObserver } from 'vue-use-web'; 50 | const elem = ref(null); 51 | 52 | const { isIntersecting } = useIntersectionObserver(elem, { 53 | root: null, 54 | rootMargin: '0px 0px 0px 0px', 55 | threshold: [0] 56 | }); 57 | ``` 58 | 59 | ## Example 60 | 61 | ```vue 62 | 71 | 72 | 85 | 86 | 101 | ``` 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/functions/local-storage.md: -------------------------------------------------------------------------------- 1 | # Local Storage 2 | 3 | > The read-only [localStorage property](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions. localStorage is similar to sessionStorage, except that while data stored in localStorage has no expiration time, data stored in sessionStorage gets cleared when the page session ends — that is, when the page is closed. 4 | 5 | This composition function adds a bunch of features on top of the new API, for starters the value will be synced with changes from other tabs. And you can use any type as the value, the value will be automatically serialized using `JSON.stringify` and de-serialized when read using `JSON.parse`. 6 | 7 | The value will also be persisted automatically when the component is unmounted. 8 | 9 | ## State 10 | 11 | The `useLocalStorage` function exposes the following reactive state: 12 | 13 | ```js 14 | import { useLocalStorage } from 'vue-use-web'; 15 | 16 | const { value } = useLocalStorage('mykey'); 17 | ``` 18 | 19 | | State | Type | Description | 20 | | ----- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | 21 | | value | `any` | The value stored in the localstorage object, this value will be synced with any changes from other tabs and will be persisted once the component is umnounted. | 22 | 23 | ## Config 24 | 25 | `useLocalStorage` function takes a `key` which will be used as the storage key, and optionally accepts a default value in-case the key does not exist. 26 | 27 | ```js 28 | import { useLocalStorage } from 'vue-use-web'; 29 | 30 | const { value } = useLocalStorage('mykey', 'default_value'); 31 | ``` 32 | 33 | ## Example 34 | 35 | ```vue 36 | 42 | 43 | 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/functions/media-query.md: -------------------------------------------------------------------------------- 1 | # Media Query 2 | 3 | > the DOM provides features that can test the results of a media [query programmatically](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Testing_media_queries), via the MediaQueryList interface and its methods and properties. Once you've created a MediaQueryList object, you can check the result of the query or receive notifications when the result changes. 4 | 5 | ## State 6 | 7 | ```js 8 | import { useMedia } from 'vue-use-web'; 9 | 10 | const isLg = useMedia('(min-width: 1024px)'); 11 | ``` 12 | 13 | `useMedia` only returns a single value which is a boolean. 14 | 15 | | State | Type | Description | 16 | | ------- | -------------- | ---------------------------- | 17 | | matches | `Ref` | If the query matches or not. | 18 | 19 | ## Example 20 | 21 | ```vue 22 | 25 | 26 | 37 | ``` 38 | 39 | ## Demo 40 | 41 | TODO: Add cool live example! 42 | -------------------------------------------------------------------------------- /docs/functions/memory-status.md: -------------------------------------------------------------------------------- 1 | # Memory Status 2 | 3 | This composition function exposes the `navigator.deviceMemory` value as well as some additional properties from the `performance.memory` which is only available in chrome as they are a non-standard. 4 | 5 | ## State 6 | 7 | The `useMemoryStatus` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useMemoryStatus } from 'vue-use-web'; 11 | 12 | const { deviceMemory, unsupported, totalJSHeapSize, usedJSHeapSize, jsHeapSizeLimit } = useMemoryStatus(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | --------------- | -------------------- | ------------------------------------------------------------------------- | 17 | | deviceMemory | `number` | Approximate amount of device memory in gigabytes. | 18 | | unsupported | `Boolean` | True if the API is supported an `deviceMemory` can be used. | 19 | | totalJSHeapSize | `number | undefined` | The total allocated heap size, in bytes. | 20 | | usedJSHeapSize | `number | undefined` | usedJSHeapSize. | 21 | | jsHeapSizeLimit | `number | undefined` | The maximum size of the heap, in bytes, that is available to the context. | 22 | 23 | ## Example 24 | 25 | TODO: Probably a memory-intensive task and scale the algorithm depending on the memory available. 26 | -------------------------------------------------------------------------------- /docs/functions/mouse-position.md: -------------------------------------------------------------------------------- 1 | # Mouse Position 2 | 3 | This API provides access to the `x` and `y` of the mouse cursor position. 4 | 5 | ## State 6 | 7 | The `useMousePosition` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useMousePosition } from 'vue-use-web'; 11 | 12 | const { x, y } = useMousePosition(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----- | -------- | ------------------------------------------- | 17 | | x | `Number` | The mouse cursor position along the x-axis. | 18 | | y | `Number` | The mouse cursor position along the y-axis. | 19 | 20 | :::tip Note! 21 | By default the updates the state are throttled by 100ms to keep things snappy but you can configure that. 22 | ::: 23 | 24 | ## Config 25 | 26 | `useMousePosition` function takes an options object as an optional parameter. 27 | 28 | ```js 29 | import { useMousePosition } from 'vue-use-web'; 30 | 31 | const { x, y } = useMousePosition(); 32 | ``` 33 | 34 | ## Example 35 | 36 | ```vue 37 | 40 | 41 | 53 | ``` 54 | 55 | ### Demo 56 | 57 | 58 | 59 | > Credits: [Codepen by Chuloo](https://codepen.io/Chuloo/pen/RQYbvm) 60 | -------------------------------------------------------------------------------- /docs/functions/network.md: -------------------------------------------------------------------------------- 1 | # Network Information 2 | 3 | > The [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) provides information about the system's connection in terms of general connection type (e.g., 'wifi', 'cellular', etc.). This can be used to select high definition content or low definition content based on the user's connection. The entire API consists of the addition of the NetworkInformation interface and a single property to the Navigator interface: Navigator.connection. 4 | 5 | ## State 6 | 7 | The `useNetwork` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useNetwork } from 'vue-use-web'; 11 | 12 | const { isOnline, offlineAt, downlink, downlinkMax, effectiveType, saveData, type } = useNetwork(); 13 | ``` 14 | 15 | This is the TypeScript interface for the state: 16 | 17 | ```ts 18 | type NetworkType = 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown'; 19 | 20 | type NetworkEffectiveType = 'slow-2g' | '2g' | '3g' | '4g'; 21 | 22 | interface NetworkState { 23 | isOnline: boolean; 24 | offlineAt: Date | null; 25 | downlink?: number; 26 | downlinkMax?: number; 27 | effectiveType?: NetworkEffectiveType; 28 | saveData?: boolean; 29 | type?: NetworkType; 30 | } 31 | ``` 32 | 33 | | State | Type | Description | 34 | | ------------- | -------------------- | ------------------------------------------- | 35 | | isOnline | `boolean` | If the user is currently connected. | 36 | | offlineAt | `Date | undefined` | The time since the user was last connected. | 37 | | downlink | `Number | undefined` | The download speed in Mbps. | 38 | | downlinkMax | `Number | undefined` | The max reachable download speed in Mbps. | 39 | | effectiveType | `string | undefined` | The detected effective speed type. | 40 | | saveData | `boolean| undefined` | If the user activated data saver mode. | 41 | | type | `string | undefined` | The detected connection/network type. | 42 | 43 | ## Example 44 | 45 | ```vue 46 | 59 | 60 | 81 | ``` 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/functions/notification.md: -------------------------------------------------------------------------------- 1 | # Web Notification 2 | 3 | This functions provides simple web notifications that are displayed outside the page at the system level. 4 | 5 | ## Methods 6 | 7 | `useNotification` exposes the following methods: 8 | 9 | ```js 10 | import { useNotification } from 'vue-use-web'; 11 | 12 | const { showNotifcation } = useNotification('notification title', { 13 | icon: 'url of icon', 14 | body: 'body of the notification' 15 | }); 16 | ``` 17 | 18 | | Method | Signature | Description | 19 | | --------------- | ------------ | ------------------------ | 20 | | showNotifcation | `() => void` | toggle the notification. | 21 | 22 | ## Config 23 | 24 | `useNotification` function takes a required parameter that is the notification tilte and [optional config](https://developer.mozilla.org/en-US/docs/Web/API/Notification) 25 | 26 | ```js 27 | import { useNotification } from 'vue-use-web'; 28 | 29 | const { showNotifcation } = useNotification( 30 | 'notification title', 31 | { 32 | icon: 'url of icon', 33 | body: 'body of the notification' 34 | }, 35 | { 36 | onClick: () => alert('Notification clicked'), 37 | onClose: () => alert('Notification closed') 38 | } 39 | ); 40 | ``` 41 | 42 | ## Example 43 | 44 | ```vue 45 | 50 | 70 | ``` -------------------------------------------------------------------------------- /docs/functions/preferred-color-scheme.md: -------------------------------------------------------------------------------- 1 | # Preferred Color Scheme 2 | 3 | > The [matchMedia preferred-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) is used to detect the user's preference for a `light` or `dark` theme. This can be useful when you should adapt your UI depending on the user preference. 4 | 5 | ## State 6 | 7 | ```js 8 | import { usePreferredColorScheme } from 'vue-use-web'; 9 | 10 | const scheme = usePreferredColorScheme(); 11 | ``` 12 | 13 | | State | Type | Description | 14 | | ----- | ------------- | ---------------------------------------------------------------------------------------- | 15 | | theme | `Ref` | Current user color scheme preference, will be one of 'dark', 'light' and 'no-preference' | 16 | 17 | :::tip 18 | This composable function uses [`useMedia`](./media-query.md) internally. 19 | ::: 20 | 21 | ## Example 22 | 23 | ```vue 24 | 27 | 28 | 39 | ``` 40 | 41 | ## Demo 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/functions/preferred-languages.md: -------------------------------------------------------------------------------- 1 | # Navigator Languages 2 | 3 | > The [Navigator Languages](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages) provides web developers with information about the user's preferred languages. For example this may be useful to adjust the language of the user interface based on the user's preferred languages in order to provide better experience. 4 | 5 | ## State 6 | 7 | The `usePreferredLanguages` function exposes the following reactive state: 8 | 9 | ```js 10 | import { usePreferredLanguages } from 'vue-use-web'; 11 | 12 | const languages = usePreferredLanguages(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | --------- | ---------- | ------------------------------------ | 17 | | languages | `String[]` | An array of user preferred languages | 18 | 19 | ## Example 20 | 21 | ```vue 22 | 28 | 29 | 40 | ``` 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/functions/script.md: -------------------------------------------------------------------------------- 1 | # Script 2 | 3 | This function allows you to add a custom third-party script to the DOM. 4 | 5 | ## State 6 | 7 | The `useScript` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useScript } from 'vue-use-web'; 11 | 12 | const { isLoading, error } = useScript({ 13 | src: '{script_url}' 14 | }); 15 | ``` 16 | 17 | | State | Type | Description | 18 | | --------- | --------- | ---------------------------------------- | 19 | | isLoading | `Boolean` | If the script is still downloading. | 20 | | error | `Boolean` | If the script didn't download correctly. | 21 | 22 | ## Methods 23 | 24 | `useScript` exposes a `toPromise` method that returns a promise when the script loads successfully. 25 | 26 | ```js 27 | import { useScript } from 'vue-use-web'; 28 | 29 | useScript({ 30 | src: '{script_url}' 31 | }) 32 | .toPromise() 33 | .then(() => { 34 | // whatever you want after the script loads. 35 | }); 36 | ``` 37 | 38 | ## Config 39 | 40 | `useScript` function takes an options object, the object must contain a `src` property. 41 | 42 | ```js 43 | import { useScript } from 'vue-use-web'; 44 | 45 | const { isLoading, error } = useScript({ 46 | src: 'script_url' 47 | }); 48 | ``` 49 | 50 | | Config | Type | Description | 51 | | ------ | -------- | ------------------------- | 52 | | src | `String` | The 3rd party script URL. | 53 | 54 | ## Example 55 | 56 | This example implements a [Stripe elements](https://stripe.com/docs/payments/cards/collecting/web) example. 57 | 58 | ```vue 59 | 74 | 75 | 116 | ``` 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/functions/scroll-position.md: -------------------------------------------------------------------------------- 1 | # Window Scroll Position 2 | 3 | This API provides access to the `x` and `y` of the scroll position. 4 | 5 | ## State 6 | 7 | The `useWindowScrollPosition` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useWindowScrollPosition } from 'vue-use-web'; 11 | 12 | const { x, y } = useWindowScrollPosition(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----- | -------- | ------------------------------------- | 17 | | x | `Number` | The scroll position along the x-axis. | 18 | | y | `Number` | The scroll position along the y-axis. | 19 | 20 | :::tip Note! 21 | By default the updates the state are throttled by 100ms to keep things snappy but you can configure that. 22 | ::: 23 | 24 | ## Config 25 | 26 | `useWindowScrollPosition` function takes an options object as an optional parameter. 27 | 28 | ```js 29 | import { useWindowScrollPosition } from 'vue-use-web'; 30 | 31 | const { x, y } = useWindowScrollPosition({ 32 | throttleMs: 100 33 | }); 34 | ``` 35 | 36 | | Config | Type | Description | 37 | | ---------- | -------- | ---------------------------------------------- | 38 | | throttleMs | `Number` | The debounce rate of the updates to the state. | 39 | 40 | ## Example 41 | 42 | ```vue 43 | 51 | 52 | 66 | 67 | 79 | ``` 80 | 81 | 82 | -------------------------------------------------------------------------------- /docs/functions/websocket.md: -------------------------------------------------------------------------------- 1 | # Web Sockets 2 | 3 | This functions provides simple websocket client capabilities. 4 | 5 | ## State 6 | 7 | The `useWebSocket` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useWebSocket } from 'vue-use-web'; 11 | 12 | const { state, data } = useWebSocket('ws://websocketurl'); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----- | ------------- | ------------------------------------------------------------------------------------------------------- | 17 | | state | `Ref` | The current websocket state, can be only one of: 'OPEN', 'CONNECTING', 'CLOSING', 'CLOSED' | 18 | | data | `Ref` | Reference to the latest data received via the websocket, can be watched to respond to incoming messages | 19 | 20 | ## Methods 21 | 22 | `useWebSocket` exposes the following methods: 23 | 24 | ```js 25 | import { useWebSocket } from 'vue-use-web'; 26 | 27 | const { send, close } = useWebSocket('ws://websocketurl'); 28 | ``` 29 | 30 | | Method | Signature | Description | 31 | | ------ | ------------------------------------------ | -------------------------------------------- | 32 | | send | `(data: any) => void` | Sends data through the websocket connection. | 33 | | close | `(code?: number, reason?: string) => void` | Closes the websocket connection gracefully. | 34 | 35 | ## Example 36 | 37 | ```vue 38 | 48 | 49 | 70 | ``` 71 | 72 | ## Demo 73 | 74 | TODO: Cool Chat app maybe 75 | -------------------------------------------------------------------------------- /docs/functions/window-size.md: -------------------------------------------------------------------------------- 1 | # Window Size 2 | 3 | This API provides access to the `width` and `height` of the browser window. 4 | 5 | ## State 6 | 7 | The `useWindowSize` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useWindowSize } from 'vue-use-web'; 11 | 12 | const { width, height } = useWindowSize(); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ------ | -------- | ----------------------------------- | 17 | | width | `Number` | The client window width in pixels. | 18 | | height | `Number` | The client window height in pixels. | 19 | 20 | :::tip Note! 21 | By default the updates to the state are throttled by 100ms to keep things snappy but you can configure that. 22 | ::: 23 | 24 | ## Config 25 | 26 | `useWindowSize` function takes an options object as an optional parameter. 27 | 28 | ```js 29 | import { useWindowSize } from 'vue-use-web'; 30 | 31 | const { width, height } = useWindowSize({ 32 | throttleMs: 100 33 | }); 34 | ``` 35 | 36 | | Config | Type | Description | 37 | | ---------- | -------- | ---------------------------------------------- | 38 | | throttleMs | `Number` | The debounce rate of the updates to the state. | 39 | 40 | ## Example 41 | 42 | ```vue 43 | 51 | 52 | 66 | ``` 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/functions/worker.md: -------------------------------------------------------------------------------- 1 | # Web Worker 2 | 3 | This functions provides simple web worker registration and communication. 4 | 5 | ## State 6 | 7 | The `useWorker` function exposes the following reactive state: 8 | 9 | ```js 10 | import { useWorker } from 'vue-use-web'; 11 | 12 | const { data } = useWorker('/path/to/worker.js'); 13 | ``` 14 | 15 | | State | Type | Description | 16 | | ----- | ---------- | ---------------------------------------------------------------------------------------------------- | 17 | | data | `Ref` | Reference to the latest data received via the worker, can be watched to respond to incoming messages | 18 | 19 | ## Methods 20 | 21 | `useWorker` exposes the following methods: 22 | 23 | ```js 24 | import { useWorker } from 'vue-use-web'; 25 | 26 | const { post, terminate } = useWorker('/path/to/worker.js'); 27 | ``` 28 | 29 | | Method | Signature | Description | 30 | | --------- | --------------------- | -------------------------------- | 31 | | post | `(data: any) => void` | Sends data to the worker thread. | 32 | | terminate | `() => void` | Stops and terminates the worker. | 33 | 34 | ## Example 35 | 36 | ```vue 37 | 47 | 48 | 69 | ``` 70 | 71 | 72 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['/test/**.test.{ts,tsx}'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | '^.+\\.jsx?$': 'babel-jest', 6 | '.*\\.(vue)$': '/node_modules/vue-jest' 7 | }, 8 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue'], 9 | collectCoverageFrom: ['/test/**/*.{ts,js}'], 10 | coveragePathIgnorePatterns: ['/src.*/index.ts'], 11 | moduleNameMapper: { 12 | '^vue$': 'vue/dist/vue.common.js', 13 | '^@/(.*)$': '/src/$1' 14 | }, 15 | globals: { 16 | 'ts-jest': { 17 | diagnostics: false 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-use-web", 3 | "version": "1.0.1", 4 | "description": "Web APIs implemented as Vue.js composition functions", 5 | "module": "dist/vue-use-web.esm.js", 6 | "unpkg": "dist/vue-use-web.js", 7 | "main": "dist/vue-use-web.js", 8 | "types": "dist/types/index.d.ts", 9 | "homepage": "https://tarektouati.github.io/vue-use-web/", 10 | "repository": "https://github.com/Tarektouati/vue-use-web", 11 | "scripts": { 12 | "docs:dev": "vuepress dev docs", 13 | "docs:build": "vuepress build docs", 14 | "docs:deploy": "./scripts/deploy.sh", 15 | "test": "jest", 16 | "test:cover": "jest --coverage", 17 | "lint": "eslint '{src, test}/**.{js,jsx,ts,tsx}' --fix", 18 | "ts:defs": "./scripts/defs.sh", 19 | "build": "node ./scripts/build && npm run ts:defs", 20 | "prepublish": "npm run build" 21 | }, 22 | "author": "Abdelrahman Awad ", 23 | "license": "MIT", 24 | "files": [ 25 | "dist/*.js", 26 | "dist/**/*.d.ts" 27 | ], 28 | "devDependencies": { 29 | "@commitlint/cli": "^11.0.0", 30 | "@testing-library/vue": "^5.0.2", 31 | "@types/jest": "^26.0.13", 32 | "@types/node": "^14.0.5", 33 | "@typescript-eslint/eslint-plugin": "^2.14.0", 34 | "@typescript-eslint/parser": "^2.14.0", 35 | "@vue/composition-api": "^0.6.7", 36 | "@vue/test-utils": "^1.0.0-beta.29", 37 | "@vuepress/plugin-back-to-top": "^1.2.0", 38 | "@vuepress/plugin-google-analytics": "^1.2.0", 39 | "@vuepress/plugin-pwa": "^1.2.0", 40 | "babel-core": "^7.0.0-bridge.0", 41 | "babel-jest": "^26.0.1", 42 | "bundlesize": "^0.18.0", 43 | "chalk": "^4.0.0", 44 | "eslint": "^6.8.0", 45 | "eslint-config-prettier": "^6.9.0", 46 | "eslint-config-standard": "^14.1.0", 47 | "eslint-plugin-import": "^2.19.1", 48 | "eslint-plugin-jest": "^24.0.0", 49 | "eslint-plugin-node": "^11.0.0", 50 | "eslint-plugin-prettier": "^3.1.2", 51 | "eslint-plugin-promise": "^4.2.1", 52 | "eslint-plugin-standard": "^4.0.1", 53 | "filesize": "^6.0.1", 54 | "flush-promises": "^1.0.2", 55 | "gzip-size": "^5.1.1", 56 | "husky": "^4.2.3", 57 | "jest": "^25.1.0", 58 | "lint-staged": "^10.0.7", 59 | "mkdirp": "^1.0.3", 60 | "prettier": "^2.0.5", 61 | "rollup": "^2.0.6", 62 | "rollup-plugin-commonjs": "^10.1.0", 63 | "rollup-plugin-node-resolve": "^5.2.0", 64 | "rollup-plugin-replace": "^2.2.0", 65 | "rollup-plugin-typescript2": "^0.27.0", 66 | "ts-jest": "^25.2.1", 67 | "typescript": "^3.7.4", 68 | "uglify-js": "^3.7.3", 69 | "vue": "^2.6.10", 70 | "vue-jest": "^3.0.5", 71 | "vue-template-compiler": "^2.6.10", 72 | "vuepress": "^1.4.1" 73 | }, 74 | "husky": { 75 | "hooks": { 76 | "pre-commit": "lint-staged", 77 | "commit-msg": "commitlint --edit -E HUSKY_GIT_PARAMS" 78 | } 79 | }, 80 | "bundlesize": [ 81 | { 82 | "path": "./dist/*.min.js", 83 | "maxSize": "10 kB" 84 | } 85 | ], 86 | "eslintIgnore": [ 87 | "locale", 88 | "dist", 89 | "scripts" 90 | ], 91 | "lint-staged": { 92 | "*.ts": [ 93 | "eslint --fix", 94 | "prettier --write", 95 | "git add", 96 | "jest --maxWorkers=1 --bail --findRelatedTests" 97 | ], 98 | "*.js": [ 99 | "git add" 100 | ] 101 | }, 102 | "peerDependencies": { 103 | "@vue/composition-api": "^0.3.2" 104 | }, 105 | "dependencies": {} 106 | } 107 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const { promisify } = require('util'); 3 | const fs = require('fs'); 4 | const { configs, utils, paths } = require('./config'); 5 | 6 | const mkdir = promisify(fs.mkdir); 7 | 8 | async function build () { 9 | await mkdir(paths.dist, { recursive: true }) 10 | // eslint-disable-next-line 11 | console.log(chalk.cyan('Generating ESM build...')); 12 | await utils.writeBundle(configs.esm, 'vue-use-web.esm.js'); 13 | // eslint-disable-next-line 14 | console.log(chalk.cyan('Done!')); 15 | 16 | // eslint-disable-next-line 17 | console.log(chalk.cyan('Generating UMD build...')); 18 | await utils.writeBundle(configs.umd, 'vue-use-web.js', true); 19 | // eslint-disable-next-line 20 | console.log(chalk.cyan('Done!')); 21 | }; 22 | 23 | build(); 24 | -------------------------------------------------------------------------------- /scripts/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const { rollup } = require('rollup'); 4 | const chalk = require('chalk'); 5 | const uglify = require('uglify-js'); 6 | const gzipSize = require('gzip-size'); 7 | const filesize = require('filesize'); 8 | const typescript = require('rollup-plugin-typescript2'); 9 | const resolve = require('rollup-plugin-node-resolve'); 10 | const commonjs = require('rollup-plugin-commonjs'); 11 | const replace = require('rollup-plugin-replace'); 12 | const version = process.env.VERSION || require('../package.json').version; 13 | 14 | const commons = { 15 | banner: `/** 16 | * elweb v${version} 17 | * (c) ${new Date().getFullYear()} Abdelrahman Awad 18 | * @license MIT 19 | */`, 20 | outputFolder: path.join(__dirname, '..', 'dist'), 21 | uglifyOptions: { 22 | compress: true, 23 | mangle: true 24 | } 25 | }; 26 | 27 | const paths = { 28 | dist: commons.outputFolder 29 | }; 30 | 31 | const utils = { 32 | stats({ path, code }) { 33 | const { size } = fs.statSync(path); 34 | const gzipped = gzipSize.sync(code); 35 | 36 | return `| Size: ${filesize(size)} | Gzip: ${filesize(gzipped)}`; 37 | }, 38 | async writeBundle({ input, output }, fileName, minify = false) { 39 | const bundle = await rollup(input); 40 | const { 41 | output: [{ code }] 42 | } = await bundle.generate(output); 43 | 44 | let outputPath = path.join(paths.dist, fileName); 45 | fs.writeFileSync(outputPath, code); 46 | let stats = this.stats({ code, path: outputPath }); 47 | // eslint-disable-next-line 48 | console.log(`${chalk.green('Output File:')} ${fileName} ${stats}`); 49 | 50 | if (minify) { 51 | let minifiedFileName = fileName.replace('.js', '') + '.min.js'; 52 | outputPath = path.join(paths.dist, minifiedFileName); 53 | fs.writeFileSync(outputPath, uglify.minify(code, commons.uglifyOptions).code); 54 | stats = this.stats({ code, path: outputPath }); 55 | // eslint-disable-next-line 56 | console.log(`${chalk.green('Output File:')} ${minifiedFileName} ${stats}`); 57 | } 58 | 59 | return true; 60 | } 61 | }; 62 | 63 | const builds = { 64 | umd: { 65 | input: 'src/index.ts', 66 | format: 'umd', 67 | name: 'VueUseWeb', 68 | env: 'production' 69 | }, 70 | esm: { 71 | input: 'src/index.ts', 72 | format: 'es' 73 | } 74 | }; 75 | 76 | function genConfig(options) { 77 | const config = { 78 | input: { 79 | input: options.input, 80 | external: ['vue', '@vue/composition-api'], 81 | plugins: [ 82 | typescript({ useTsconfigDeclarationDir: true }), 83 | replace({ __VERSION__: version }), 84 | resolve(), 85 | commonjs() 86 | ] 87 | }, 88 | output: { 89 | banner: commons.banner, 90 | format: options.format, 91 | name: options.name, 92 | globals: { 93 | vue: 'Vue', 94 | '@vue/composition-api': 'vueCompositionApi' 95 | } 96 | } 97 | }; 98 | 99 | if (options.env) { 100 | config.input.plugins.unshift( 101 | replace({ 102 | 'process.env.NODE_ENV': JSON.stringify(options.env) 103 | }) 104 | ); 105 | } 106 | 107 | return config; 108 | } 109 | 110 | const configs = Object.keys(builds).reduce((prev, key) => { 111 | prev[key] = genConfig(builds[key]); 112 | 113 | return prev; 114 | }, {}); 115 | 116 | module.exports = { 117 | configs, 118 | utils, 119 | uglifyOptions: commons.uglifyOptions, 120 | paths 121 | }; 122 | -------------------------------------------------------------------------------- /scripts/defs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "\e[34mRemoving old declarations..." 4 | rm -rf ./dist/types 5 | mkdir -p ./dist/types 6 | echo "\e[92mDone" 7 | 8 | echo "\e[34mGenerating main declarations..." 9 | tsc ./src/*.ts --emitDeclarationOnly --skipLibCheck --declaration --outDir ./dist/types/ 10 | echo "\e[92mDone" 11 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # build 7 | npm run docs:build 8 | 9 | # navigate into the build output directory 10 | cd docs/.vuepress/dist 11 | 12 | git init 13 | git add -A 14 | git commit -m 'deploy' 15 | 16 | git push -f git@github.com:Tarektouati/vue-use-web.git master:gh-pages 17 | cd - 18 | -------------------------------------------------------------------------------- /src/Battery.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export interface BatteryManager extends EventTarget { 4 | charging: boolean; 5 | chargingTime: number; 6 | dischargingTime: number; 7 | level: number; 8 | } 9 | 10 | export type NavigatorWithBattery = Navigator & { 11 | getBattery: () => Promise; 12 | }; 13 | 14 | const events = ['chargingchange', 'chargingtimechange', 'dischargingtimechange', 'levelchange']; 15 | 16 | export function useBattery() { 17 | const isCharging = ref(false); 18 | const chargingTime = ref(0); 19 | const dischargingTime = ref(0); 20 | const level = ref(1); 21 | 22 | function updateBatteryInfo(this: BatteryManager) { 23 | isCharging.value = this.charging; 24 | chargingTime.value = this.chargingTime || 0; 25 | dischargingTime.value = this.dischargingTime || 0; 26 | level.value = this.level; 27 | } 28 | 29 | onMounted(() => { 30 | if (!('getBattery' in navigator)) { 31 | return; 32 | } 33 | 34 | (navigator as NavigatorWithBattery).getBattery().then(battery => { 35 | updateBatteryInfo.call(battery); 36 | events.forEach(evt => { 37 | battery.addEventListener(evt, updateBatteryInfo); 38 | }); 39 | }); 40 | }); 41 | 42 | onUnmounted(() => { 43 | if (!('getBattery' in navigator)) { 44 | return; 45 | } 46 | 47 | (navigator as NavigatorWithBattery).getBattery().then(battery => { 48 | events.forEach(evt => { 49 | battery.removeEventListener(evt, updateBatteryInfo); 50 | }); 51 | }); 52 | }); 53 | 54 | return { 55 | isCharging, 56 | chargingTime, 57 | dischargingTime, 58 | level 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/Clipboard.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export function useClipboard() { 4 | const text = ref(''); 5 | 6 | async function onCopy() { 7 | text.value = await navigator.clipboard.readText(); 8 | } 9 | 10 | onMounted(() => { 11 | window.addEventListener('copy', onCopy); 12 | }); 13 | 14 | onUnmounted(() => { 15 | window.removeEventListener('copy', onCopy); 16 | }); 17 | 18 | function write(txt: string) { 19 | text.value = txt; 20 | 21 | return navigator.clipboard.writeText(txt); 22 | } 23 | 24 | return { 25 | text, 26 | write 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/DeviceLight.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, Ref } from '@vue/composition-api'; 2 | 3 | export function useDeviceLight() { 4 | const value: Ref = ref(null); 5 | function handler(event: DeviceLightEvent) { 6 | value.value = event.value; 7 | } 8 | 9 | // TODO: Should we debounce/throttle the event? 10 | onMounted(() => { 11 | window.addEventListener('devicelight', handler); 12 | }); 13 | 14 | onUnmounted(() => { 15 | window.removeEventListener('devicelight', handler); 16 | }); 17 | 18 | return { 19 | value 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/DeviceMedia.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref, Ref } from '@vue/composition-api'; 2 | 3 | export function useDeviceMedia(constraints: MediaStreamConstraints) { 4 | const streamRef: Ref = ref(null); 5 | const errorRef: Ref = ref(null); 6 | 7 | onMounted(() => { 8 | navigator.mediaDevices 9 | .getUserMedia(constraints) 10 | .then(stream => (streamRef.value = stream)) 11 | .catch(err => { 12 | errorRef.value = err; 13 | }); 14 | }); 15 | 16 | return { 17 | stream: streamRef, 18 | error: errorRef 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/DeviceMotion.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref, onUnmounted, Ref } from '@vue/composition-api'; 2 | import { throttle } from './utils'; 3 | 4 | interface DeviceMotionOptions { 5 | throttleMs: 10; 6 | } 7 | 8 | export function useDeviceMotion(options: DeviceMotionOptions = { throttleMs: 10 }) { 9 | const acceleration: Ref = ref({ x: null, y: null, z: null }); 10 | const rotationRate: Ref = ref({ alpha: null, beta: null, gamma: null }); 11 | const interval = ref(0); 12 | const accelerationIncludingGravity: Ref = ref({ 13 | x: null, 14 | y: null, 15 | z: null 16 | }); 17 | 18 | function onDeviceMotion(event: DeviceMotionEvent) { 19 | acceleration.value = event.acceleration; 20 | accelerationIncludingGravity.value = event.accelerationIncludingGravity; 21 | rotationRate.value = event.rotationRate; 22 | interval.value = event.interval; 23 | } 24 | 25 | const handler = options.throttleMs ? throttle(options.throttleMs, onDeviceMotion) : onDeviceMotion; 26 | 27 | onMounted(() => { 28 | window.addEventListener('devicemotion', handler, false); 29 | }); 30 | 31 | onUnmounted(() => { 32 | window.removeEventListener('devicemotion', handler, false); 33 | }); 34 | 35 | return { 36 | acceleration, 37 | accelerationIncludingGravity, 38 | rotationRate, 39 | interval 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/DeviceOrientation.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, Ref } from '@vue/composition-api'; 2 | 3 | export function useDeviceOrientation() { 4 | const isAbsolute = ref(false); 5 | const alpha: Ref = ref(0); 6 | const beta: Ref = ref(0); 7 | const gamma: Ref = ref(0); 8 | 9 | function handler(event: DeviceOrientationEvent) { 10 | isAbsolute.value = event.absolute; 11 | alpha.value = event.alpha; 12 | beta.value = event.beta; 13 | gamma.value = event.gamma; 14 | } 15 | 16 | onMounted(() => { 17 | window.addEventListener('deviceorientation', handler); 18 | }); 19 | 20 | onUnmounted(() => { 21 | window.removeEventListener('deviceorientation', handler); 22 | }); 23 | 24 | return { 25 | isAbsolute, 26 | alpha, 27 | beta, 28 | gamma 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/DocumentVisibility.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from '@vue/composition-api'; 2 | import { useEventListener } from './EventListener'; 3 | import { hasWindow } from './utils'; 4 | 5 | /** 6 | * Tracks visibility of the page. 7 | * 8 | * @returns True if the document is currently visible. 9 | * 10 | * @example 11 | * setup() { 12 | * const isVisible = useDocumentVisibility() 13 | * watch(() => console.log(isVisible)) 14 | * } 15 | * 16 | */ 17 | export function useDocumentVisibility(): Ref { 18 | const documentIsVisible = () => document.visibilityState === 'visible'; 19 | 20 | const isVisible: Ref = ref(hasWindow ? documentIsVisible() : true); 21 | 22 | useEventListener(document, 'visibilitychange', () => { 23 | isVisible.value = documentIsVisible(); 24 | }); 25 | 26 | return isVisible; 27 | } 28 | -------------------------------------------------------------------------------- /src/EventListener.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onBeforeUnmount, Ref, isRef } from '@vue/composition-api'; 2 | 3 | export function useEventListener( 4 | target: T | Ref, 5 | type: string, 6 | handler: (this: T, evt: E) => void, 7 | options?: AddEventListenerOptions 8 | ) { 9 | onMounted(() => { 10 | const t = isRef(target) ? target.value : target; 11 | 12 | t.addEventListener(type, handler as (evt: Event) => void, options); 13 | }); 14 | 15 | onBeforeUnmount(() => { 16 | const t = isRef(target) ? target.value : target; 17 | 18 | t.removeEventListener(type, handler as (evt: Event) => void, options); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/Fetch.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, Ref } from '@vue/composition-api'; 2 | 3 | export function useFetch(url: RequestInfo, options: RequestInit) { 4 | const response: Ref = ref(null); 5 | const isLoading = ref(false); 6 | const success = ref(false); 7 | const error = ref(false); 8 | const cancelled = ref(false); 9 | const type: Ref = ref('unknown'); 10 | const status: Ref = ref(undefined); 11 | const statusText = ref(''); 12 | const headers: Ref> = ref({}); 13 | 14 | let signal: AbortSignal | undefined; 15 | let controller: AbortController; 16 | if (typeof AbortController !== 'undefined') { 17 | controller = new AbortController(); 18 | signal = controller.signal; 19 | } 20 | 21 | function execute() { 22 | isLoading.value = true; 23 | 24 | return window 25 | .fetch(url, { signal, ...options }) 26 | .then(res => { 27 | success.value = res.ok; 28 | error.value = !res.ok; 29 | isLoading.value = false; 30 | statusText.value = res.statusText; 31 | status.value = res.status; 32 | type.value = res.type; 33 | const responseHeaders: Record = {}; 34 | res.headers.forEach((value, key) => { 35 | responseHeaders[key] = value; 36 | }); 37 | 38 | headers.value = responseHeaders; 39 | const isContentTypeJson = 40 | res.headers.get('content-type')?.slice(0, res.headers.get('content-type')?.indexOf(';')) === 41 | 'application/json'; 42 | 43 | if (isContentTypeJson) { 44 | return res.json(); 45 | } 46 | 47 | return res.text(); 48 | }) 49 | .then(responseValue => { 50 | // Graceful degradation for cancellation. 51 | if (cancelled.value) { 52 | return; 53 | } 54 | 55 | response.value = responseValue; 56 | }) 57 | .catch(() => { 58 | error.value = true; 59 | isLoading.value = false; 60 | }); 61 | } 62 | 63 | onMounted(execute); 64 | 65 | function cancel() { 66 | if (success.value) { 67 | return; 68 | } 69 | 70 | cancelled.value = true; 71 | if (controller) { 72 | controller.abort(); 73 | } 74 | } 75 | 76 | return { 77 | response, 78 | status, 79 | statusText, 80 | headers, 81 | isLoading, 82 | cancelled, 83 | error, 84 | success, 85 | cancel, 86 | execute 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/FullScreen.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref } from '@vue/composition-api'; 2 | 3 | export function useFullscreen(target: Ref) { 4 | const isFullscreen = ref(false); 5 | function exitFullscreen() { 6 | if (document.fullscreenElement) { 7 | document.exitFullscreen(); 8 | } 9 | 10 | isFullscreen.value = false; 11 | } 12 | 13 | function enterFullscreen() { 14 | exitFullscreen(); 15 | 16 | target.value.requestFullscreen().then(() => { 17 | isFullscreen.value = true; 18 | }); 19 | } 20 | 21 | return { 22 | isFullscreen, 23 | enterFullscreen, 24 | exitFullscreen 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/Geolocation.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@vue/composition-api'; 2 | 3 | export function useGeolocation(options: PositionOptions) { 4 | const locatedAt: Ref = ref(null); 5 | const error = ref(''); 6 | const coords: Ref = ref({ 7 | accuracy: 0, 8 | latitude: 0, 9 | longitude: 0, 10 | altitude: null, 11 | altitudeAccuracy: null, 12 | heading: null, 13 | speed: null 14 | }); 15 | 16 | function updatePosition(position: Position) { 17 | locatedAt.value = position.timestamp; 18 | coords.value = position.coords; 19 | error.value = ''; 20 | } 21 | 22 | let watcher: number; 23 | onMounted(() => { 24 | if ('geolocation' in navigator) { 25 | watcher = window.navigator.geolocation.watchPosition(updatePosition, undefined, options); 26 | } 27 | }); 28 | 29 | onUnmounted(() => { 30 | if (watcher) { 31 | window.navigator.geolocation.clearWatch(watcher); 32 | } 33 | }); 34 | 35 | return { 36 | coords, 37 | locatedAt, 38 | error 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/HardwareConcurrency.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from '@vue/composition-api'; 2 | import { hasWindow } from './utils'; 3 | 4 | export function useHardwareConcurrency() { 5 | const logicalProcessors = ref(0); 6 | const unsupported = ref(false); 7 | 8 | function resolveConcurrency() { 9 | if (!hasWindow) { 10 | onMounted(resolveConcurrency); 11 | } 12 | 13 | if ('hardwareConcurrency' in window.navigator) { 14 | logicalProcessors.value = window.navigator.hardwareConcurrency; 15 | } 16 | 17 | unsupported.value = true; 18 | } 19 | 20 | resolveConcurrency(); 21 | 22 | return { logicalProcessors, unsupported }; 23 | } 24 | -------------------------------------------------------------------------------- /src/IntersectionObserver.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, Ref, ref, onUnmounted } from '@vue/composition-api'; 2 | 3 | export function useIntersectionObserver( 4 | target: Ref, 5 | options: IntersectionObserverInit = { root: null, rootMargin: '0px' } 6 | ) { 7 | const intersectionRatio = ref(0); 8 | const isIntersecting = ref(false); 9 | const isFullyInView = ref(false); 10 | 11 | function observe() { 12 | if (target.value) { 13 | observer.observe(target.value); 14 | } 15 | } 16 | 17 | let observer: IntersectionObserver; 18 | onMounted(() => { 19 | observer = new IntersectionObserver(([entry]) => { 20 | intersectionRatio.value = entry.intersectionRatio; 21 | if (entry.intersectionRatio > 0) { 22 | isIntersecting.value = true; 23 | isFullyInView.value = entry.intersectionRatio >= 1; 24 | return; 25 | } 26 | 27 | isIntersecting.value = false; 28 | }, options); 29 | 30 | observe(); 31 | }); 32 | 33 | function unobserve() { 34 | if (!observer) return; 35 | 36 | if (target.value) { 37 | observer.unobserve(target.value); 38 | } 39 | } 40 | 41 | onUnmounted(unobserve); 42 | 43 | return { 44 | intersectionRatio, 45 | isIntersecting, 46 | isFullyInView, 47 | observe, 48 | unobserve 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/LocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@vue/composition-api'; 2 | 3 | function parseValue(serializedVal: string) { 4 | let value = null; 5 | try { 6 | value = JSON.parse(serializedVal); 7 | } catch { 8 | value = serializedVal; 9 | } 10 | 11 | return value; 12 | } 13 | 14 | export function useLocalStorage(key: string, def?: any) { 15 | const value: Ref = ref(null); 16 | const init = () => { 17 | const serializedVal = localStorage.getItem(key); 18 | if (serializedVal !== null) { 19 | value.value = parseValue(serializedVal); 20 | return; 21 | } 22 | 23 | value.value = def; 24 | }; 25 | 26 | let initialized = false; 27 | 28 | // early init if possible. 29 | if (typeof window !== 'undefined') { 30 | init(); 31 | initialized = true; 32 | } 33 | 34 | function handler(e: StorageEvent) { 35 | if (e.key === key) { 36 | value.value = e.newValue ? parseValue(e.newValue) : null; 37 | } 38 | } 39 | 40 | onMounted(() => { 41 | if (!initialized) { 42 | init(); 43 | } 44 | 45 | window.addEventListener('storage', handler, true); 46 | }); 47 | 48 | onUnmounted(() => { 49 | localStorage.setItem(key, JSON.stringify(value.value)); 50 | window.removeEventListener('storage', handler); 51 | }); 52 | 53 | return { 54 | value 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/Media.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export function useMedia(query: string) { 4 | let mediaQuery!: MediaQueryList; 5 | 6 | // try to fetch initial value (avoid SSR issues) 7 | if (typeof window !== 'undefined') { 8 | mediaQuery = window.matchMedia(query); 9 | } 10 | 11 | const matches = ref(mediaQuery ? mediaQuery.matches : false); 12 | function handler(event: MediaQueryListEvent) { 13 | matches.value = event.matches; 14 | } 15 | 16 | onMounted(() => { 17 | if (!mediaQuery) { 18 | mediaQuery = window.matchMedia(query); 19 | } 20 | 21 | matches.value = mediaQuery.matches; 22 | mediaQuery.addListener(handler); 23 | }); 24 | 25 | onUnmounted(() => { 26 | mediaQuery.removeListener(handler); 27 | }); 28 | 29 | return matches; 30 | } 31 | -------------------------------------------------------------------------------- /src/MemoryStatus.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from '@vue/composition-api'; 2 | import { hasWindow } from './utils'; 3 | 4 | export function useMemoryStatus() { 5 | const deviceMemory = ref(0); 6 | const unsupported = ref(false); 7 | const totalJSHeapSize = ref(undefined); 8 | const usedJSHeapSize = ref(undefined); 9 | const jsHeapSizeLimit = ref(undefined); 10 | 11 | function resolveMemory() { 12 | if (!hasWindow) { 13 | onMounted(resolveMemory); 14 | } 15 | 16 | if (!('deviceMemory' in window.navigator)) { 17 | unsupported.value = true; 18 | return; 19 | } 20 | 21 | deviceMemory.value = (window.navigator as any).deviceMemory; 22 | 23 | if ('memory' in window.performance) { 24 | const memory = (window.performance as any).memory; 25 | totalJSHeapSize.value = memory.totalJSHeapSize; 26 | usedJSHeapSize.value = memory?.usedJSHeapSize; 27 | jsHeapSizeLimit.value = memory?.jsHeapSizeLimit; 28 | } 29 | } 30 | 31 | resolveMemory(); 32 | 33 | return { deviceMemory, unsupported }; 34 | } 35 | -------------------------------------------------------------------------------- /src/MousePosition.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from '@vue/composition-api'; 2 | 3 | export function useMousePosition() { 4 | const x = ref(0); 5 | const y = ref(0); 6 | 7 | function handler(e: MouseEvent) { 8 | x.value = e.clientX; 9 | y.value = e.clientY; 10 | } 11 | 12 | onMounted(() => { 13 | window.addEventListener('mousemove', handler, false); 14 | }); 15 | 16 | onUnmounted(() => { 17 | window.removeEventListener('mousemove', handler, false); 18 | }); 19 | 20 | return { 21 | x, 22 | y 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/Network.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, Ref } from '@vue/composition-api'; 2 | 3 | type NetworkType = 'bluetooth' | 'cellular' | 'ethernet' | 'none' | 'wifi' | 'wimax' | 'other' | 'unknown'; 4 | 5 | type NetworkEffectiveType = 'slow-2g' | '2g' | '3g' | '4g' | undefined; 6 | 7 | interface NetworkState { 8 | isOnline: boolean; 9 | offlineAt: number | undefined; 10 | downlink?: number; 11 | downlinkMax?: number; 12 | effectiveType?: NetworkEffectiveType; 13 | saveData?: boolean; 14 | type?: NetworkType; 15 | } 16 | 17 | export function useNetwork() { 18 | const isOnline = ref(true); 19 | const saveData = ref(false); 20 | const offlineAt: Ref = ref(undefined); 21 | const downlink: Ref = ref(undefined); 22 | const downlinkMax: Ref = ref(undefined); 23 | const effectiveType: Ref = ref(undefined); 24 | const type: Ref = ref('unknown'); 25 | 26 | function updateNetworkInformation() { 27 | isOnline.value = window.navigator.onLine; 28 | offlineAt.value = isOnline.value ? undefined : Date.now(); 29 | // skip for non supported browsers. 30 | if (!('connection' in window.navigator)) { 31 | return; 32 | } 33 | 34 | downlink.value = (window.navigator as any).connection.downlink; 35 | downlinkMax.value = (window.navigator as any).connection.downlinkMax; 36 | effectiveType.value = (window.navigator as any).connection.effectiveType; 37 | saveData.value = (window.navigator as any).connection.saveData; 38 | type.value = (window.navigator as any).connection.type; 39 | } 40 | 41 | const onOffline = () => { 42 | isOnline.value = false; 43 | offlineAt.value = Date.now(); 44 | }; 45 | 46 | const onOnline = () => { 47 | isOnline.value = true; 48 | }; 49 | 50 | onMounted(() => { 51 | updateNetworkInformation(); 52 | window.addEventListener('offline', onOffline); 53 | window.addEventListener('online', onOnline); 54 | if ('connection' in window.navigator) { 55 | (window.navigator as any).connection.addEventListener('change', updateNetworkInformation, false); 56 | } 57 | }); 58 | 59 | onUnmounted(() => { 60 | window.removeEventListener('offline', onOffline); 61 | window.removeEventListener('online', onOnline); 62 | if ('connection' in window.navigator) { 63 | (window.navigator as any).connection.removeEventListener('change', updateNetworkInformation, false); 64 | } 65 | }); 66 | 67 | return { 68 | isOnline, 69 | saveData, 70 | offlineAt, 71 | downlink, 72 | downlinkMax, 73 | effectiveType, 74 | type 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /src/Notification.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, Ref, ref, onUnmounted } from '@vue/composition-api'; 2 | 3 | type NotificationOptions = { 4 | dir?: 'auto' | 'ltr' | 'rtl'; 5 | lang?: string; 6 | body?: string; 7 | tag?: string; 8 | icon?: string; 9 | }; 10 | 11 | type NotificationMethods = { 12 | onClick: ((e: Event) => void) | null; 13 | onShow: ((e: Event) => void) | null; 14 | onError: ((e: Event) => void) | null; 15 | onClose: ((e: Event) => void) | null; 16 | }; 17 | 18 | const defaultNotificationOptions = { 19 | onClick: null, 20 | onShow: null, 21 | onError: null, 22 | onClose: null 23 | }; 24 | 25 | export function useNotification( 26 | title: string, 27 | options: NotificationOptions = {}, 28 | methods: NotificationMethods = defaultNotificationOptions 29 | ) { 30 | const notification: Ref = ref(null); 31 | const requestPermission = async () => { 32 | if ('permission' in Notification && Notification.permission !== 'denied') { 33 | await Notification.requestPermission(); 34 | } 35 | }; 36 | 37 | onMounted(requestPermission); 38 | 39 | onUnmounted(() => { 40 | notification.value = null; 41 | }); 42 | 43 | const showNotifcation = (): void => { 44 | notification.value = new Notification(title, options); 45 | notification.value.onclick = methods.onClick; 46 | notification.value.onshow = methods.onShow; 47 | notification.value.onerror = methods.onError; 48 | notification.value.onclose = methods.onClose; 49 | }; 50 | 51 | return { showNotifcation }; 52 | } 53 | -------------------------------------------------------------------------------- /src/PreferredColorScheme.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from '@vue/composition-api'; 2 | import { useMedia } from './Media'; 3 | 4 | export function usePreferredColorScheme(): Ref<'dark' | 'light' | 'no-preference'> { 5 | const queries = { 6 | light: '(prefers-color-scheme: light)', 7 | dark: '(prefers-color-scheme: dark)', 8 | 'no-preference': '(prefers-color-scheme: no-preference)' 9 | }; 10 | 11 | const isDark = useMedia(queries.dark); 12 | const isLight = useMedia(queries.light); 13 | 14 | const theme = computed(() => { 15 | if (isDark.value) { 16 | return 'dark'; 17 | } 18 | 19 | if (isLight.value) { 20 | return 'light'; 21 | } 22 | 23 | return 'no-preference'; 24 | }); 25 | 26 | return theme; 27 | } 28 | -------------------------------------------------------------------------------- /src/PreferredLanguages.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref } from '@vue/composition-api'; 2 | 3 | export function usePreferredLanguages() { 4 | const value = ref(navigator.languages); 5 | 6 | function handler() { 7 | value.value = navigator.languages; 8 | } 9 | 10 | onMounted(() => { 11 | window.addEventListener('languagechange', handler); 12 | }); 13 | 14 | onUnmounted(() => { 15 | window.removeEventListener('languagechange', handler); 16 | }); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /src/Script.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from '@vue/composition-api'; 2 | 3 | interface ScriptOptions { 4 | src: string; 5 | // async?: boolean; 6 | // defer?: boolean; 7 | // module?: boolean; 8 | } 9 | 10 | export function useScript(opts: ScriptOptions) { 11 | const isLoading = ref(false); 12 | const error = ref(false); 13 | const success = ref(false); 14 | 15 | const promise = new Promise((resolve, reject) => { 16 | onMounted(() => { 17 | const script = document.createElement('script'); 18 | // script.async = opts.async || true; 19 | // script.async = opts.defer || true; 20 | // script.noModule = !opts.module || false; 21 | script.onload = function() { 22 | isLoading.value = false; 23 | success.value = true; 24 | error.value = false; 25 | resolve(); 26 | }; 27 | 28 | script.onerror = function(err) { 29 | isLoading.value = false; 30 | success.value = false; 31 | error.value = true; 32 | reject(err); 33 | }; 34 | 35 | script.src = opts.src; 36 | document.head.appendChild(script); 37 | }); 38 | }); 39 | 40 | return { 41 | isLoading, 42 | error, 43 | success, 44 | toPromise: () => promise 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/WebSocket.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@vue/composition-api'; 2 | 3 | export function useWebSocket(url: string) { 4 | const data: Ref = ref(null); 5 | const state: Ref<'OPEN' | 'CONNECTING' | 'CLOSING' | 'CLOSED'> = ref('CONNECTING'); 6 | let ws: WebSocket; 7 | const close: typeof ws.close = function close(code, reason) { 8 | if (!ws) return; 9 | 10 | ws.close(code, reason); 11 | }; 12 | 13 | const send: typeof ws.send = function send(data) { 14 | if (!ws) return; 15 | 16 | ws.send(data); 17 | }; 18 | 19 | onMounted(() => { 20 | ws = new WebSocket(url); 21 | ws.onopen = () => { 22 | state.value = 'OPEN'; 23 | }; 24 | 25 | ws.onclose = ws.onerror = () => { 26 | state.value = 'CLOSED'; 27 | }; 28 | 29 | ws.onmessage = (e: MessageEvent) => { 30 | data.value = e.data; 31 | }; 32 | }); 33 | 34 | onUnmounted(() => { 35 | ws.close(); 36 | }); 37 | 38 | return { 39 | data, 40 | state, 41 | close, 42 | send 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/WindowScrollPosition.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, onBeforeMount } from '@vue/composition-api'; 2 | import { throttle } from './utils'; 3 | 4 | interface WindowScrollOptions { 5 | throttleMs: number; 6 | } 7 | 8 | export function useWindowScrollPosition(options: WindowScrollOptions = { throttleMs: 100 }) { 9 | const x = ref(0); 10 | const y = ref(0); 11 | 12 | function setScrollPos() { 13 | x.value = window.pageXOffset; 14 | y.value = window.pageYOffset; 15 | } 16 | 17 | const onScroll = throttle(options.throttleMs, setScrollPos); 18 | 19 | onBeforeMount(() => { 20 | setScrollPos(); 21 | }); 22 | 23 | onMounted(() => { 24 | window.addEventListener('scroll', onScroll, { passive: true }); 25 | }); 26 | 27 | onUnmounted(() => { 28 | window.removeEventListener('scroll', onScroll); 29 | }); 30 | 31 | return { 32 | x, 33 | y 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/WindowSize.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref, onBeforeMount } from '@vue/composition-api'; 2 | import { throttle } from './utils'; 3 | 4 | interface WindowSizeOptions { 5 | throttleMs: number; 6 | } 7 | 8 | export function useWindowSize(options: WindowSizeOptions = { throttleMs: 100 }) { 9 | const width = ref(0); 10 | const height = ref(0); 11 | 12 | function setSize() { 13 | width.value = window.innerWidth; 14 | height.value = window.innerHeight; 15 | } 16 | 17 | const onResize = throttle(options.throttleMs, setSize); 18 | onBeforeMount(() => { 19 | setSize(); 20 | }); 21 | 22 | onMounted(() => { 23 | window.addEventListener('resize', onResize, { passive: true }); 24 | }); 25 | 26 | onUnmounted(() => { 27 | window.removeEventListener('resize', onResize); 28 | }); 29 | 30 | return { 31 | height, 32 | width 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/Worker.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted, Ref } from '@vue/composition-api'; 2 | 3 | export function useWorker(url: string) { 4 | const data: Ref = ref(null); 5 | let worker: Worker; 6 | 7 | const post: typeof worker.postMessage = function post(val: any) { 8 | if (!worker) return; 9 | 10 | worker.postMessage(val); 11 | }; 12 | 13 | const terminate: typeof worker.terminate = function terminate() { 14 | if (!worker) return; 15 | 16 | worker.terminate(); 17 | }; 18 | 19 | onMounted(() => { 20 | worker = new Worker(url); 21 | 22 | worker.onmessage = (e: MessageEvent) => { 23 | data.value = e.data; 24 | }; 25 | }); 26 | 27 | onUnmounted(() => { 28 | worker.terminate(); 29 | }); 30 | 31 | return { 32 | data, 33 | post, 34 | terminate 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Battery'; 2 | export * from './Clipboard'; 3 | export * from './DeviceLight'; 4 | export * from './DeviceMotion'; 5 | export * from './DeviceOrientation'; 6 | export * from './DocumentVisibility'; 7 | export * from './EventListener'; 8 | export * from './Fetch'; 9 | export * from './FullScreen'; 10 | export * from './Geolocation'; 11 | export * from './HardwareConcurrency'; 12 | export * from './IntersectionObserver'; 13 | export * from './LocalStorage'; 14 | export * from './Media'; 15 | export * from './MemoryStatus'; 16 | export * from './MousePosition'; 17 | export * from './Network'; 18 | export * from './PreferredColorScheme'; 19 | export * from './PreferredLanguages'; 20 | export * from './Script'; 21 | export * from './WebSocket'; 22 | export * from './WindowScrollPosition'; 23 | export * from './WindowSize'; 24 | export * from './Worker'; 25 | export * from './Notification'; 26 | export * from './DeviceMedia'; 27 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function throttle void>(wait: number, fn: T): T { 2 | if (wait === 0) { 3 | return fn; 4 | } 5 | 6 | let timeout: number | undefined; 7 | 8 | return ((...args: any[]) => { 9 | const later = () => { 10 | timeout = undefined; 11 | 12 | // check if the fn call was cancelled. 13 | fn(...args); 14 | }; 15 | 16 | clearTimeout(timeout); 17 | timeout = setTimeout(later, wait) as any; 18 | }) as T; 19 | } 20 | 21 | export const hasWindow = typeof window !== 'undefined'; 22 | -------------------------------------------------------------------------------- /test/Battery.test.ts: -------------------------------------------------------------------------------- 1 | import { useBattery, NavigatorWithBattery, BatteryManager } from '../src/Battery'; 2 | import { mount, createLocalVue } from '@vue/test-utils'; 3 | import { mountCompositionFunc } from './utils'; 4 | import VueCompositionAPI from '@vue/composition-api'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const mockedBrowserResponse: BatteryManager = { 13 | charging: true, 14 | chargingTime: 250, 15 | dischargingTime: 6540, 16 | level: 4, 17 | addEventListener: jest.fn(), 18 | dispatchEvent: jest.fn(), 19 | removeEventListener: jest.fn() 20 | }; 21 | 22 | beforeEach(() => { 23 | (navigator as NavigatorWithBattery).getBattery = jest 24 | .fn() 25 | .mockImplementation(() => Promise.resolve(mockedBrowserResponse)); 26 | }); 27 | 28 | it('useBattery', async () => { 29 | const { vm } = mount( 30 | mountCompositionFunc(() => { 31 | const { isCharging, chargingTime, dischargingTime, level } = useBattery(); 32 | return { isCharging, chargingTime, dischargingTime, level }; 33 | }), 34 | { localVue } 35 | ); 36 | await localVue.nextTick(); 37 | expect(vm.isCharging).toBe(mockedBrowserResponse.charging); 38 | expect(vm.chargingTime).toBe(mockedBrowserResponse.chargingTime); 39 | expect(vm.dischargingTime).toBe(mockedBrowserResponse.dischargingTime); 40 | expect(vm.level).toBe(mockedBrowserResponse.level); 41 | vm.$destroy(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/Clipboard.test.ts: -------------------------------------------------------------------------------- 1 | import { useClipboard } from '../src/Clipboard'; 2 | import { mount, createLocalVue } from '@vue/test-utils'; 3 | import { mountCompositionFunc } from './utils'; 4 | import VueCompositionAPI from '@vue/composition-api'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const textInclipboard = 'sample text'; 13 | 14 | const ClipboardMock: Clipboard = { 15 | readText: jest.fn().mockImplementation(() => Promise.resolve(textInclipboard)), 16 | writeText: jest.fn(), 17 | addEventListener: jest.fn(), 18 | dispatchEvent: jest.fn(), 19 | removeEventListener: jest.fn() 20 | }; 21 | 22 | beforeEach(() => { 23 | Object.defineProperty(navigator, 'clipboard', { 24 | writable: false, 25 | value: ClipboardMock 26 | }); 27 | }); 28 | 29 | it('useClipboard: text', async () => { 30 | const { vm } = mount( 31 | mountCompositionFunc(() => { 32 | const { text } = useClipboard(); 33 | return { text }; 34 | }), 35 | { localVue } 36 | ); 37 | await window.dispatchEvent(new Event('copy')); 38 | await localVue.nextTick(); 39 | expect(vm.text).toBe(textInclipboard); 40 | expect(ClipboardMock.readText).toHaveBeenCalled(); 41 | vm.$destroy(); 42 | }); 43 | 44 | it('useClipboard: write', async () => { 45 | const { vm } = mount( 46 | mountCompositionFunc(() => { 47 | const { write } = useClipboard(); 48 | return { write }; 49 | }), 50 | { localVue } 51 | ); 52 | vm.write('test'); 53 | expect(ClipboardMock.writeText).toHaveBeenCalled(); 54 | vm.$destroy(); 55 | }); 56 | -------------------------------------------------------------------------------- /test/Fetch.test.ts: -------------------------------------------------------------------------------- 1 | import { render, waitFor } from '@testing-library/vue'; 2 | import { VueClass, createLocalVue } from '@vue/test-utils'; 3 | import VueCompositionAPI from '@vue/composition-api'; 4 | import { useFetch } from '../src/Fetch'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const MOCK_TXT_BODY = 'mocked response'; 13 | const MOCK_JSON_BODY: Record = { mock: true }; 14 | 15 | const MOCK_HEADERS_JSON: HeadersInit = { 16 | 'Content-Type': 'application/json; charset=utf-8' 17 | }; 18 | 19 | const MOCK_HEADERS_TEXT: HeadersInit = { 20 | 'Content-Type': 'text/plain' 21 | }; 22 | 23 | const headersMock = (setJsonContentType: boolean): Headers => 24 | new Headers(setJsonContentType ? MOCK_HEADERS_JSON : MOCK_HEADERS_TEXT); 25 | 26 | const mockResponse = (setJsonContentType: boolean): Response => ({ 27 | arrayBuffer: (): Promise => Promise.resolve((null as any) as ArrayBuffer), 28 | blob: (): Promise => Promise.resolve((null as any) as Blob), 29 | body: null, 30 | bodyUsed: true, 31 | clone: (): Response => mockResponse(setJsonContentType), 32 | formData: (): Promise => Promise.resolve((null as any) as FormData), 33 | headers: headersMock(setJsonContentType), 34 | json: (): Promise> => Promise.resolve(MOCK_JSON_BODY), 35 | ok: true, 36 | redirected: false, 37 | status: 200, 38 | statusText: 'ok', 39 | text: (): Promise => Promise.resolve(MOCK_TXT_BODY), 40 | trailer: Promise.resolve(headersMock(setJsonContentType)), 41 | type: (null as any) as ResponseType, 42 | url: 'http://endpoint.com/json' 43 | }); 44 | 45 | afterEach(() => { 46 | delete window.fetch; 47 | }); 48 | 49 | const renderWithCompositionApi = (component: VueClass, options?: any) => 50 | render(component, options, vue => vue.use(VueCompositionAPI)); 51 | 52 | const FetchComponent = localVue.component('use-fetch-component', { 53 | setup() { 54 | const { isLoading, success, response } = useFetch('http://endpoint.com/json', {}); 55 | return { isLoading, success, response }; 56 | }, 57 | template: ` 58 |
59 |
Loading...
60 |
{{ JSON.stringify(response) }}
61 |
62 | ` 63 | }); 64 | 65 | it('useFetch: response in json', async () => { 66 | window.fetch = jest.fn().mockResolvedValue(mockResponse(true)); 67 | const { getByText, getByTestId } = renderWithCompositionApi(FetchComponent); 68 | await waitFor(() => getByText('Loading...')); 69 | const response = getByTestId('response'); 70 | await waitFor(() => expect(JSON.parse(response.textContent as string)).toEqual(MOCK_JSON_BODY)); 71 | }); 72 | 73 | it('useFetch: response in text', async () => { 74 | window.fetch = jest.fn().mockResolvedValue(mockResponse(false)); 75 | const { getByText, getByTestId } = renderWithCompositionApi(FetchComponent); 76 | await waitFor(() => getByText('Loading...')); 77 | const response = getByTestId('response'); 78 | await waitFor(() => expect(JSON.parse(response.textContent as string)).toEqual(MOCK_TXT_BODY)); 79 | }); 80 | -------------------------------------------------------------------------------- /test/FullScreen.test.ts: -------------------------------------------------------------------------------- 1 | import { useFullscreen } from '../src/FullScreen'; 2 | import { mount, createLocalVue } from '@vue/test-utils'; 3 | import { mountCompositionFunc } from './utils'; 4 | import VueCompositionAPI from '@vue/composition-api'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const mockedElement: any = { value: { requestFullscreen: jest.fn().mockImplementation(() => Promise.resolve()) } }; 13 | const mockedExitFullscreen = jest.fn(); 14 | 15 | beforeEach(() => { 16 | Object.defineProperty(document, 'fullscreenElement', { 17 | writable: false, 18 | value: true 19 | }); 20 | document.exitFullscreen = mockedExitFullscreen; 21 | }); 22 | 23 | it('useFullscreen: isFullscreen', () => { 24 | const { vm } = mount( 25 | mountCompositionFunc(() => { 26 | const { isFullscreen } = useFullscreen(mockedElement); 27 | return { isFullscreen }; 28 | }), 29 | { localVue } 30 | ); 31 | localVue.nextTick(); 32 | expect(vm.isFullscreen).toBe(false); 33 | vm.$destroy(); 34 | }); 35 | 36 | it('useFullscreen: enterFullscreen', async () => { 37 | const { vm } = mount( 38 | mountCompositionFunc(() => { 39 | const { isFullscreen, enterFullscreen } = useFullscreen(mockedElement); 40 | return { isFullscreen, enterFullscreen }; 41 | }), 42 | { localVue } 43 | ); 44 | localVue.nextTick(); 45 | await vm.enterFullscreen(); 46 | expect(mockedElement.value.requestFullscreen).toHaveBeenCalled(); 47 | expect(vm.isFullscreen).toBe(true); 48 | vm.$destroy(); 49 | }); 50 | 51 | it('useFullscreen: exitFullscreen', () => { 52 | const { vm } = mount( 53 | mountCompositionFunc(() => { 54 | const { isFullscreen, exitFullscreen } = useFullscreen(mockedElement); 55 | return { isFullscreen, exitFullscreen }; 56 | }), 57 | { localVue } 58 | ); 59 | localVue.nextTick(); 60 | vm.exitFullscreen(); 61 | expect(vm.isFullscreen).toBe(false); 62 | expect(mockedExitFullscreen).toHaveBeenCalled(); 63 | vm.$destroy(); 64 | }); 65 | -------------------------------------------------------------------------------- /test/HardwareConcurrency.test.ts: -------------------------------------------------------------------------------- 1 | import { useHardwareConcurrency } from '../src/HardwareConcurrency'; 2 | import { mount, createLocalVue } from '@vue/test-utils'; 3 | import { mountCompositionFunc } from './utils'; 4 | import VueCompositionAPI from '@vue/composition-api'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const mockedhardwareConcurrencyValue = 12; 13 | 14 | beforeEach(() => { 15 | Object.defineProperty(window.navigator, 'hardwareConcurrency', { 16 | writable: false, 17 | value: mockedhardwareConcurrencyValue 18 | }); 19 | jest.mock('../src/utils', () => ({ 20 | hasWindow: jest.fn().mockReturnValue(true) 21 | })); 22 | }); 23 | 24 | it('useHardwareConcurrency', () => { 25 | const { vm } = mount( 26 | mountCompositionFunc(() => { 27 | const { logicalProcessors, unsupported } = useHardwareConcurrency(); 28 | return { logicalProcessors, unsupported }; 29 | }), 30 | { localVue } 31 | ); 32 | localVue.nextTick(); 33 | expect(vm.logicalProcessors).toEqual(mockedhardwareConcurrencyValue); 34 | expect(vm.unsupported).toBe(true); 35 | vm.$destroy(); 36 | }); 37 | -------------------------------------------------------------------------------- /test/LocalStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { useLocalStorage } from '../src/LocalStorage'; 2 | import { mount, createLocalVue } from '@vue/test-utils'; 3 | import { mountCompositionFunc } from './utils'; 4 | import VueCompositionAPI from '@vue/composition-api'; 5 | 6 | const localVue = createLocalVue(); 7 | localVue.use(VueCompositionAPI); 8 | localVue.config.silent = true; 9 | localVue.config.warnHandler = jest.fn(); 10 | window.console.error = jest.fn(); 11 | 12 | const mocklocalStorage = { 13 | key: 'test-key', 14 | value: 'testValue' 15 | }; 16 | 17 | beforeEach(() => { 18 | window.localStorage.clear(); 19 | }); 20 | 21 | it('useLocalStorage: Get localStorage value', () => { 22 | window.localStorage.setItem(mocklocalStorage.key, mocklocalStorage.value); 23 | const { vm } = mount( 24 | mountCompositionFunc(() => { 25 | const { value } = useLocalStorage(mocklocalStorage.key); 26 | return { value }; 27 | }), 28 | { localVue } 29 | ); 30 | localVue.nextTick(); 31 | expect(vm.value).toEqual(mocklocalStorage.value); 32 | vm.$destroy(); 33 | }); 34 | 35 | it('useLocalStorage: Set localStorage value', () => { 36 | const { vm } = mount( 37 | mountCompositionFunc(() => { 38 | const { value } = useLocalStorage(mocklocalStorage.key, mocklocalStorage.value); 39 | return { value }; 40 | }), 41 | { localVue } 42 | ); 43 | localVue.nextTick(); 44 | expect(vm.value).toEqual(mocklocalStorage.value); 45 | vm.$destroy(); 46 | }); 47 | -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { VNode, CreateElement } from 'vue'; 2 | 3 | export const mountCompositionFunc = (cb: () => any) => ({ 4 | setup() { 5 | return cb(); 6 | }, 7 | render(h: CreateElement): VNode { 8 | return h('div'); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "jsxFactory": "h", 5 | "moduleResolution": "node", 6 | "target": "es5", 7 | "module": "es2015", 8 | "lib": ["es2015", "es2016", "es2017", "dom"], 9 | "declaration": true, 10 | "declarationDir": "dist/types", 11 | "sourceMap": true, 12 | "outDir": "dist/lib", 13 | "noImplicitAny": true, 14 | "strict": true, 15 | "strictNullChecks": true, 16 | "strictBindCallApply": true, 17 | "strictFunctionTypes": true, 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | "typeRoots": ["node_modules/@types"], 21 | "esModuleInterop": true, 22 | "allowSyntheticDefaultImports": true 23 | }, 24 | "include": ["src", "test"], 25 | "files": ["./vue-shims.d.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | --------------------------------------------------------------------------------