├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.3 2 | 3 | * Fix incorrect condition 4 | 5 | ## 1.0.2 6 | 7 | * Fix missing scrollbar width on simple breakpoint setup 8 | 9 | ## 1.0.1 10 | 11 | * Simpler getter setup -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stanislav Lashmanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This library is no longer maintained. Please refer to [`vue-component-media-queries`](https://github.com/CyberAP/vue-component-media-queries) instead.** 2 | 3 | 4 | # Vue-provide-responsive 5 | A Vue plugin that provides responsive design helpers, based on breakpoints. 6 | 7 | [![npm](https://img.shields.io/npm/v/vue-provide-responsive)](https://www.npmjs.com/package/vue-provide-responsive) 8 | 9 | [Demo](https://codesandbox.io/s/magical-shockley-wc287) 10 | 11 | * **Performant**. All the data is retrieved in a single pass. 12 | * **Zero collisions**. Uses [Provide\Inject](https://vuejs.org/v2/api/index.html#provide-inject), accepts any string or a symbol as a provision name. 13 | * **[SSR\Nuxt Ready](#ssr)**. Has [fallback width](#defaultwidth) for server-side rendering. 14 | * **Easy setup**. Nothing is required, minimal breakpoint configuration. 15 | * **Fully customizable**. Execute any arbitrary code in the resize callback, [provide your own helpers\values](#beforeprovide), customize existing helpers. 16 | * **Human readable**. No manual equality checks, easy to understand helper names. 17 | 18 | 19 | ```html 20 | 31 | 32 | 43 | ``` 44 | 45 | ## Usage 46 | 47 | Install from npm 48 | 49 | `npm i vue-provide-responsive` 50 | 51 | Activate the plugin inside your app: 52 | 53 | ```js 54 | import Vue from 'vue'; 55 | import VueResponsiveProvide from 'vue-provide-responsive'; 56 | 57 | Vue.use(VueResponsiveProvide, { 58 | breakpoints: { 59 | mobile: 768 60 | // will be converted to responsive.isMobile helper (works exactly like a computed) 61 | // will be true if the viewport width is less or equals 768 pixels 62 | } 63 | }); 64 | ``` 65 | 66 | Use responsive helpers in any of your components: 67 | 68 | ```html 69 | 76 | 77 | 90 | ``` 91 | 92 | ## Plugin configuration 93 | 94 | ### **`breakpoints`** 95 | 96 | An object listing resolution breakpoints that would be converted into responsive helpers. 97 | 98 | Could be either a breakpoint value (`number`) or a breakpoint configuration: `{ value: number, ignoreScrollbar: boolean }`. 99 | 100 | Breakpoints are Media Query\CSS compliant, in the sence that they do not include scrollbar width. So in result your breakpoint value will be redued by the scrollbar width (they same way as Media Queries work). You can disable that behaviour with `ingnoreScrollbar: true`. 101 | 102 | ```js 103 | import Vue from 'vue'; 104 | import VueProvideResponsive from 'vue-provide-responsive'; 105 | 106 | Vue.use(VueProvideResponsive, { 107 | breakpoints: { 108 | mobile: 768, 109 | desktop: { 110 | value: 1024, 111 | ignoreScrollbar: true 112 | } 113 | } 114 | }); 115 | ``` 116 | 117 | #### breakpoint configuration 118 | 119 | * `value` 120 | 121 | **Type**: `number` 122 | 123 | Maximum width 124 | 125 | * `ignoreScrollbar` 126 | 127 | **Type**: `boolean` 128 | 129 | **Default**: `false` 130 | 131 | Controls whether scrollbar width should be included in the breakpoint calculation. 132 | If not set your breakpoint value would be substracted by the scrollbar width. 133 | 134 | Scrollbar width is always zero outside browser enviroment (SSR on nodejs for example). 135 | 136 | 137 | ### **`name`** 138 | 139 | **Type**: `string` or `symbol` 140 | 141 | **Default**: `responsive` 142 | 143 | Used as a provision name. 144 | 145 | ```js 146 | // constants.js 147 | export const RESPONSIVE_SYMBOL = Symbol(); 148 | ``` 149 | 150 | ```js 151 | import Vue from 'vue'; 152 | import VueProvideResponsive from 'vue-provide-responsive'; 153 | import { RESPONSIVE_SYMBOL } from 'constants.js'; 154 | 155 | Vue.use(VueProvideResponsive, { 156 | name: RESPONSIVE_SYMBOL 157 | }); 158 | ``` 159 | 160 | ```html 161 | 173 | ``` 174 | 175 | ### **`beforeProvide`** 176 | 177 | **Type**: `function` 178 | 179 | **Arguments**: 180 | 181 | * `responsive`: Non-reactive responsive object 182 | 183 | Callback to extend responsive object before it becomes reactive. Useful for adding your own helpers. 184 | 185 | ```js 186 | import Vue from 'vue'; 187 | import VueProvideResponsive from 'vue-provide-responsive'; 188 | 189 | Vue.use(VueProvideResponsive, { 190 | beforeProvide(responsive) { 191 | // to behave like a computed we define a getter: responsive.isSmallHeight 192 | // you can define a function if you want and call it explicitly: responsive.myFunc() 193 | Object.defineProperty(responsive, 'isSmallHeight', { 194 | configurable: true, 195 | enumerable: true, 196 | // will become reactive on the component's context 197 | get() { 198 | return this.height < 500; 199 | } 200 | }) 201 | }, 202 | }); 203 | ``` 204 | 205 | You could also use this to create reactive properties in advance. 206 | 207 | ```js 208 | import Vue from 'vue'; 209 | import VueProvideResponsive from 'vue-provide-responsive'; 210 | 211 | Vue.use(VueProvideResponsive, { 212 | beforeProvide(responsive) { 213 | // this will be useful if you want height to be reactive 214 | responsive.height = 0; 215 | }, 216 | }); 217 | ``` 218 | 219 | ### **`onResize`** 220 | 221 | **Type**: `function` 222 | 223 | **Arguments**: 224 | 225 | * `event`: Resize UIEvent 226 | * `responsive`: Reactive responsive object 227 | 228 | Callback that's called on every resize event. Useful to update properties on the `responsive` object. 229 | 230 | ```js 231 | import Vue from 'vue'; 232 | import VueProvideResponsive from 'vue-provide-responsive'; 233 | 234 | Vue.use(VueProvideResponsive, { 235 | onResize(event, responsive) { 236 | Vue.set(responsive, 'height', window.innerHeight); 237 | } 238 | }); 239 | ``` 240 | 241 | If you defined `height` beforehand then you don't need `Vue.set`: 242 | 243 | ```js 244 | import Vue from 'vue'; 245 | import VueProvideResponsive from 'vue-provide-responsive'; 246 | 247 | Vue.use(VueProvideResponsive, { 248 | beforeProvide(responsive) { 249 | responsive.height = 0; 250 | }, 251 | onResize(event, responsive) { 252 | responsive.height = window.innerHeight; 253 | } 254 | }); 255 | ``` 256 | 257 | ### **`defaultWidth`** 258 | 259 | **Type**: `number` 260 | 261 | **Default**: `1024` 262 | 263 | Width that will be used when `window` is not available. 264 | SRR support relies on that value, so you can control what the default width will be when the exact device is unkown. 265 | 266 | ```js 267 | import Vue from 'vue'; 268 | import VueProvideResponsive from 'vue-provide-responsive'; 269 | 270 | Vue.use(VueProvideResponsive, { 271 | breakpoints: { 272 | mobile: 768 273 | }, 274 | defaultWidth: 768 275 | // responsive.isMobile will be true on SSR context 276 | }); 277 | ``` 278 | 279 | #### Predictive rendering 280 | Could also be used with a prediction tool to determine layout in SSR context. 281 | 282 | Nuxt.js example using [UAParser.js](https://github.com/faisalman/ua-parser-js): 283 | 284 | ```js 285 | // plugins/responsive.js 286 | import Vue from 'vue'; 287 | import VueProvideResponsive from 'vue-provide-responsive'; 288 | 289 | export default ({ req }) => { 290 | 291 | const breakpoints = { 292 | mobile: 768, 293 | tablet: 1024, 294 | } 295 | 296 | const devices = { 297 | wearable: breakpoints.mobile, 298 | mobile: breakpoints.mobile, 299 | tablet: breakpoints.tablet, 300 | } 301 | 302 | const config = { 303 | breakpoints, 304 | defaultWidth: 1920, 305 | } 306 | 307 | if (req) { 308 | const uaparser = require('ua-parser-js'); 309 | const { device } = uaparser(req.headers['user-agent']); 310 | const width = devices[device.type]; 311 | if (width) config.defaultWidth = width; 312 | } 313 | 314 | Vue.use({ 315 | // forces plugin to install on every request 316 | install: VueProvideResponsive.install 317 | }, config); 318 | } 319 | ``` 320 | 321 | ### **`ssr`** 322 | 323 | **Type**: `boolean` 324 | 325 | **Default**: `false` 326 | 327 | Triggers supports for hydration on the client. Set this to `true` if you're using a custom SSR. Nuxt users will get this out of the box. 328 | 329 | ### **`window`** 330 | 331 | **Type**: `window` instance 332 | 333 | A `window` substitute, could be useful if you don't want to work with global window instance. 334 | 335 | 336 | ## Responsive object 337 | 338 | ### **`width`** 339 | 340 | **Type**: `number` 341 | 342 | Current viewport width, equals to `defaultWidth` outside browser context (for example in SSR). 343 | 344 | ```html 345 | 350 | 351 | 357 | ``` 358 | 359 | ### **`is%Breakpoint%`** 360 | 361 | **Type**: `boolean` 362 | 363 | Reactive helper based on breakpoint value. 364 | 365 | ```js 366 | import Vue from 'vue'; 367 | import VueProvideResponsive from 'vue-provide-responsive'; 368 | 369 | Vue.use(VueProvideResponsive, { 370 | breakpoints: { 371 | mobile: 768 372 | }, 373 | }); 374 | ``` 375 | 376 | ```html 377 | 382 | 383 | 389 | ``` 390 | 391 | #### Performance 392 | `resize` is usually not a frequent event, but in case you have a lot of heavy resize handling in your app you could use `debounce` to reduce performance strain in your watchers or computeds. 393 | 394 | ```html 395 | 416 | ``` 417 | 418 | 419 | ## Typescript support 420 | 421 | You would need to declare typings yourself, since they can not be determined beforehand. 422 | 423 | ```ts 424 | // main.ts or plugin.ts 425 | declare module 'vue/types/vue' { 426 | interface Vue { 427 | responsive: { 428 | width: number, 429 | isMobile: boolean 430 | isTablet: boolean 431 | } 432 | } 433 | } 434 | ``` 435 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-provide-responsive", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.5.5", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", 10 | "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.0.0" 14 | } 15 | }, 16 | "@babel/highlight": { 17 | "version": "7.5.0", 18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", 19 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", 20 | "dev": true, 21 | "requires": { 22 | "chalk": "^2.0.0", 23 | "esutils": "^2.0.2", 24 | "js-tokens": "^4.0.0" 25 | } 26 | }, 27 | "@types/estree": { 28 | "version": "0.0.41", 29 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.41.tgz", 30 | "integrity": "sha512-rIAmXyJlqw4KEBO7+u9gxZZSQHaCNnIzYrnNmYVpgfJhxTqO0brCX0SYpqUTkVI5mwwUwzmtspLBGBKroMeynA==", 31 | "dev": true 32 | }, 33 | "@types/node": { 34 | "version": "13.1.1", 35 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.1.tgz", 36 | "integrity": "sha512-hx6zWtudh3Arsbl3cXay+JnkvVgCKzCWKv42C9J01N2T2np4h8w5X8u6Tpz5mj38kE3M9FM0Pazx8vKFFMnjLQ==", 37 | "dev": true 38 | }, 39 | "acorn": { 40 | "version": "7.1.0", 41 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 42 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", 43 | "dev": true 44 | }, 45 | "ansi-styles": { 46 | "version": "3.2.1", 47 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 48 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 49 | "dev": true, 50 | "requires": { 51 | "color-convert": "^1.9.0" 52 | } 53 | }, 54 | "buffer-from": { 55 | "version": "1.1.1", 56 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 57 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 58 | "dev": true 59 | }, 60 | "chalk": { 61 | "version": "2.4.2", 62 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 63 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 64 | "dev": true, 65 | "requires": { 66 | "ansi-styles": "^3.2.1", 67 | "escape-string-regexp": "^1.0.5", 68 | "supports-color": "^5.3.0" 69 | } 70 | }, 71 | "color-convert": { 72 | "version": "1.9.3", 73 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 74 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 75 | "dev": true, 76 | "requires": { 77 | "color-name": "1.1.3" 78 | } 79 | }, 80 | "color-name": { 81 | "version": "1.1.3", 82 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 83 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 84 | "dev": true 85 | }, 86 | "commondir": { 87 | "version": "1.0.1", 88 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 89 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", 90 | "dev": true 91 | }, 92 | "escape-string-regexp": { 93 | "version": "1.0.5", 94 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 95 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 96 | "dev": true 97 | }, 98 | "estree-walker": { 99 | "version": "0.6.1", 100 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 101 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 102 | "dev": true 103 | }, 104 | "esutils": { 105 | "version": "2.0.3", 106 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 107 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 108 | "dev": true 109 | }, 110 | "find-cache-dir": { 111 | "version": "3.2.0", 112 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", 113 | "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", 114 | "dev": true, 115 | "requires": { 116 | "commondir": "^1.0.1", 117 | "make-dir": "^3.0.0", 118 | "pkg-dir": "^4.1.0" 119 | } 120 | }, 121 | "find-up": { 122 | "version": "4.1.0", 123 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 124 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 125 | "dev": true, 126 | "requires": { 127 | "locate-path": "^5.0.0", 128 | "path-exists": "^4.0.0" 129 | } 130 | }, 131 | "fs-extra": { 132 | "version": "8.1.0", 133 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 134 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 135 | "dev": true, 136 | "requires": { 137 | "graceful-fs": "^4.2.0", 138 | "jsonfile": "^4.0.0", 139 | "universalify": "^0.1.0" 140 | } 141 | }, 142 | "graceful-fs": { 143 | "version": "4.2.3", 144 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 145 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", 146 | "dev": true 147 | }, 148 | "has-flag": { 149 | "version": "3.0.0", 150 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 151 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 152 | "dev": true 153 | }, 154 | "jest-worker": { 155 | "version": "24.9.0", 156 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", 157 | "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", 158 | "dev": true, 159 | "requires": { 160 | "merge-stream": "^2.0.0", 161 | "supports-color": "^6.1.0" 162 | }, 163 | "dependencies": { 164 | "supports-color": { 165 | "version": "6.1.0", 166 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", 167 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", 168 | "dev": true, 169 | "requires": { 170 | "has-flag": "^3.0.0" 171 | } 172 | } 173 | } 174 | }, 175 | "js-tokens": { 176 | "version": "4.0.0", 177 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 178 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 179 | "dev": true 180 | }, 181 | "jsonfile": { 182 | "version": "4.0.0", 183 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 184 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 185 | "dev": true, 186 | "requires": { 187 | "graceful-fs": "^4.1.6" 188 | } 189 | }, 190 | "locate-path": { 191 | "version": "5.0.0", 192 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 193 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 194 | "dev": true, 195 | "requires": { 196 | "p-locate": "^4.1.0" 197 | } 198 | }, 199 | "make-dir": { 200 | "version": "3.0.0", 201 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", 202 | "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", 203 | "dev": true, 204 | "requires": { 205 | "semver": "^6.0.0" 206 | } 207 | }, 208 | "merge-stream": { 209 | "version": "2.0.0", 210 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 211 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 212 | "dev": true 213 | }, 214 | "p-limit": { 215 | "version": "2.2.1", 216 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", 217 | "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", 218 | "dev": true, 219 | "requires": { 220 | "p-try": "^2.0.0" 221 | } 222 | }, 223 | "p-locate": { 224 | "version": "4.1.0", 225 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 226 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 227 | "dev": true, 228 | "requires": { 229 | "p-limit": "^2.2.0" 230 | } 231 | }, 232 | "p-try": { 233 | "version": "2.2.0", 234 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 235 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 236 | "dev": true 237 | }, 238 | "path-exists": { 239 | "version": "4.0.0", 240 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 241 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 242 | "dev": true 243 | }, 244 | "path-parse": { 245 | "version": "1.0.6", 246 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 247 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 248 | "dev": true 249 | }, 250 | "pkg-dir": { 251 | "version": "4.2.0", 252 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 253 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 254 | "dev": true, 255 | "requires": { 256 | "find-up": "^4.0.0" 257 | } 258 | }, 259 | "rollup": { 260 | "version": "1.27.14", 261 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.14.tgz", 262 | "integrity": "sha512-DuDjEyn8Y79ALYXMt+nH/EI58L5pEw5HU9K38xXdRnxQhvzUTI/nxAawhkAHUQeudANQ//8iyrhVRHJBuR6DSQ==", 263 | "dev": true, 264 | "requires": { 265 | "@types/estree": "*", 266 | "@types/node": "*", 267 | "acorn": "^7.1.0" 268 | } 269 | }, 270 | "rollup-plugin-terser": { 271 | "version": "5.1.3", 272 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.1.3.tgz", 273 | "integrity": "sha512-FuFuXE5QUJ7snyxHLPp/0LFXJhdomKlIx/aK7Tg88Yubsx/UU/lmInoJafXJ4jwVVNcORJ1wRUC5T9cy5yk0wA==", 274 | "dev": true, 275 | "requires": { 276 | "@babel/code-frame": "^7.0.0", 277 | "jest-worker": "^24.6.0", 278 | "rollup-pluginutils": "^2.8.1", 279 | "serialize-javascript": "^2.1.2", 280 | "terser": "^4.1.0" 281 | } 282 | }, 283 | "rollup-plugin-typescript2": { 284 | "version": "0.25.3", 285 | "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.25.3.tgz", 286 | "integrity": "sha512-ADkSaidKBovJmf5VBnZBZe+WzaZwofuvYdzGAKTN/J4hN7QJCFYAq7IrH9caxlru6T5qhX41PNFS1S4HqhsGQg==", 287 | "dev": true, 288 | "requires": { 289 | "find-cache-dir": "^3.0.0", 290 | "fs-extra": "8.1.0", 291 | "resolve": "1.12.0", 292 | "rollup-pluginutils": "2.8.1", 293 | "tslib": "1.10.0" 294 | }, 295 | "dependencies": { 296 | "resolve": { 297 | "version": "1.12.0", 298 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", 299 | "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", 300 | "dev": true, 301 | "requires": { 302 | "path-parse": "^1.0.6" 303 | } 304 | } 305 | } 306 | }, 307 | "rollup-pluginutils": { 308 | "version": "2.8.1", 309 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", 310 | "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", 311 | "dev": true, 312 | "requires": { 313 | "estree-walker": "^0.6.1" 314 | } 315 | }, 316 | "semver": { 317 | "version": "6.3.0", 318 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 319 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 320 | "dev": true 321 | }, 322 | "serialize-javascript": { 323 | "version": "2.1.2", 324 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", 325 | "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", 326 | "dev": true 327 | }, 328 | "source-map": { 329 | "version": "0.6.1", 330 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 331 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 332 | "dev": true 333 | }, 334 | "source-map-support": { 335 | "version": "0.5.16", 336 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 337 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 338 | "dev": true, 339 | "requires": { 340 | "buffer-from": "^1.0.0", 341 | "source-map": "^0.6.0" 342 | } 343 | }, 344 | "supports-color": { 345 | "version": "5.5.0", 346 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 347 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 348 | "dev": true, 349 | "requires": { 350 | "has-flag": "^3.0.0" 351 | } 352 | }, 353 | "terser": { 354 | "version": "4.4.3", 355 | "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.3.tgz", 356 | "integrity": "sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==", 357 | "dev": true, 358 | "requires": { 359 | "commander": "^2.20.0", 360 | "source-map": "~0.6.1", 361 | "source-map-support": "~0.5.12" 362 | }, 363 | "dependencies": { 364 | "commander": { 365 | "version": "2.20.3", 366 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 367 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 368 | "dev": true 369 | } 370 | } 371 | }, 372 | "tslib": { 373 | "version": "1.10.0", 374 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 375 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 376 | "dev": true 377 | }, 378 | "typescript": { 379 | "version": "3.7.4", 380 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz", 381 | "integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==", 382 | "dev": true 383 | }, 384 | "universalify": { 385 | "version": "0.1.2", 386 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 387 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 388 | "dev": true 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-provide-responsive", 3 | "version": "1.1.0", 4 | "description": "Responsive design plugin for Vue using Provide\\Inject", 5 | "main": "dist/index.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/src/index.d.ts", 8 | "scripts": { 9 | "build": "rollup -c" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/CyberAP/vue-provide-responsive.git" 14 | }, 15 | "keywords": [ 16 | "vue", 17 | "responsive", 18 | "provide", 19 | "inject" 20 | ], 21 | "author": "Stas Lashmanov ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/CyberAP/vue-provide-responsive/issues" 25 | }, 26 | "homepage": "https://github.com/CyberAP/vue-provide-responsive#readme", 27 | "devDependencies": { 28 | "rollup": "^1.27.14", 29 | "rollup-plugin-terser": "^5.1.3", 30 | "rollup-plugin-typescript2": "^0.25.3", 31 | "tslib": "^1.10.0", 32 | "typescript": "^3.7.4" 33 | }, 34 | "peerDependencies": { 35 | "vue": "^2.6.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import { terser } from "rollup-plugin-terser"; 3 | import pkg from './package.json'; 4 | 5 | export default { 6 | input: 'src/index.ts', 7 | output: [ 8 | { 9 | file: pkg.main, 10 | format: 'cjs', 11 | }, 12 | { 13 | file: pkg.module, 14 | format: 'es', 15 | }, 16 | ], 17 | external: [ 18 | ...Object.keys(pkg.dependencies || {}), 19 | ...Object.keys(pkg.peerDependencies || {}), 20 | ], plugins: [ 21 | terser(), 22 | typescript() 23 | ], 24 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | const getWindow = () => typeof window === 'undefined' ? null : window; 2 | 3 | type Responsive = { width: number } & Record; 4 | type Breakpoint = number | { value: number, ignoreScrollbar: boolean }; 5 | type Breakpoints = Record; 6 | 7 | const install = (Vue: any, { 8 | breakpoints = {}, 9 | defaultWidth = 1024, 10 | window = getWindow(), 11 | beforeProvide, 12 | onResize, 13 | name = 'responsive', 14 | ssr = false, 15 | }: { 16 | breakpoints?: Breakpoints, 17 | defaultWidth?: number, 18 | window?: Window | null, 19 | beforeProvide?: (responsive: Responsive) => void, 20 | onResize?: (event: UIEvent, responsive: Responsive) => void, 21 | name?: string | symbol, 22 | ssr?: boolean, 23 | } = {}) => { 24 | 25 | const scrollbarWidth = (() => { 26 | if (!window) return 0; 27 | return window.innerWidth - window.document.body.clientWidth; 28 | })(); 29 | 30 | const helpers = Object.keys(breakpoints) 31 | .reduce((acc, key) => { 32 | const breakpoint = breakpoints[key]; 33 | const name = 'is' + key.charAt(0).toUpperCase() + key.substring(1); // mobile → isMobile 34 | let value: number; 35 | if (typeof breakpoint === 'object') { 36 | value = breakpoint.value; 37 | if (!breakpoint.ignoreScrollbar) value -= scrollbarWidth; 38 | } else { 39 | value = breakpoint - scrollbarWidth; 40 | } 41 | Object.defineProperty(acc, name, { 42 | configurable: true, 43 | enumerable: true, 44 | get() { 45 | return this.width <= value; 46 | } 47 | }); 48 | return acc; 49 | }, {}); 50 | 51 | const nonReactiveResponsive = Object.assign(helpers, { width: getCurrentWidth() }); 52 | 53 | if (beforeProvide) { 54 | beforeProvide(nonReactiveResponsive); 55 | } 56 | 57 | const responsive = Vue.observable(nonReactiveResponsive); 58 | 59 | function getCurrentWidth() { 60 | if (!window) return defaultWidth; 61 | return window.document.body.clientWidth; 62 | } 63 | 64 | if (window) { 65 | // do not break hydration in SSR 66 | if (ssr || '__NUXT__' in window) { 67 | responsive.width = defaultWidth; 68 | requestAnimationFrame(() => { 69 | responsive.width = getCurrentWidth(); 70 | }); 71 | } 72 | window.addEventListener('resize', (event: UIEvent) => { 73 | responsive.width = getCurrentWidth(); 74 | if (onResize) onResize(event, responsive); 75 | }); 76 | } 77 | 78 | Vue.mixin({ 79 | provide: { 80 | [name]: responsive, 81 | } 82 | }); 83 | }; 84 | 85 | export default { install }; 86 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "dist", 5 | "target": "es5", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "removeComments": false, 11 | "preserveConstEnums": true, 12 | "sourceMap": true, 13 | "outDir": "dist", 14 | "rootDir": ".", 15 | }, 16 | "include": [ 17 | "src/**/*" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "**/*.spec.ts" 22 | ] 23 | } --------------------------------------------------------------------------------