vue-viewports 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2016 Marco 'Gatto' Boffo
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Marco 'Gatto' Boffo
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.
package/README.md CHANGED
@@ -1,73 +1,146 @@
1
- # vue-viewports
2
-
3
- > define your custom viewports and use them in your components
4
-
5
- ## Features
6
-
7
- - Uses [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
8
- - Exposes a fully updated viewport name
9
-
10
- ## Installation
11
-
12
- ### npm
13
-
14
- ```shell
15
- npm install vue-viewports --save-dev
16
- ```
17
-
18
- ### Vue's main.js
19
-
20
- ```js
21
- import VueViewports from 'vue-viewports'
22
-
23
- const options = [
24
- {
25
- rule: '320px',
26
- label: 'mobile'
27
- },
28
- {
29
- rule: '768px',
30
- label: 'tablet'
31
- },
32
- {
33
- rule: '1024px',
34
- label: 'desktop'
35
- },
36
- {
37
- rule: '1920px',
38
- label: 'hd-desktop'
39
- },
40
- {
41
- rule: '2560px',
42
- label: 'qhd-desktop'
43
- },
44
- {
45
- rule: '3840px',
46
- label: 'uhd-desktop'
47
- }
48
- ]
49
-
50
- Vue.use(VueViewports, options)
51
- ```
52
-
53
- ### Arguments
54
-
55
- - options [optional]: object defining a set of `{ rule: value, label: value }` where 'rule' is the number value where the viewport starts (included) and the 'label' is the viewport's name, **defaults** on previous example
56
-
57
- ## Example
58
-
59
- ```js
60
- {
61
- if (this.$currentViewport.label === 'tablet') {
62
- // from 768px (included) to 1024px (excluded)
63
- } else {
64
- // anything else
65
- }
66
- }
67
- ```
68
-
69
- ## Variables
70
-
71
- - `$currentViewport`: the current viewport object, defined by `rule`, `label`; `undefined` if no match.
72
-
73
- > Feel free to contribute and ask questions!
1
+ # vue-viewports
2
+
3
+ > Named, reactive, `matchMedia`-based viewport breakpoints for Vue 3 a tiny plugin **and** composable.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/vue-viewports.svg)](https://www.npmjs.com/package/vue-viewports)
6
+ [![CI](https://github.com/scaccogatto/vue-viewports/actions/workflows/ci.yml/badge.svg)](https://github.com/scaccogatto/vue-viewports/actions/workflows/ci.yml)
7
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/vue-viewports)](https://bundlephobia.com/package/vue-viewports)
8
+ [![license](https://img.shields.io/npm/l/vue-viewports.svg)](./LICENSE)
9
+
10
+ Define your breakpoints once, get the **current viewport** reactively in every component. No resize listeners, no debouncing — it is backed by the browser's [`matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) and updates only when a breakpoint is actually crossed.
11
+
12
+ - **Reactive everywhere** — the current viewport is a shared `ref`; templates, `computed`, and `watch` all update automatically.
13
+ - **Two APIs** — a Vue plugin (`$currentViewport` on every component) and a `useViewport()` composable.
14
+ - **Typed** — ships first-class TypeScript types and a single bundled `.d.ts`.
15
+ - **Tiny & zero-dependency** — < 1 kB gzipped, `vue` is the only (peer) dependency.
16
+ - **ESM + CJS** — works with Vite and bundlers.
17
+
18
+ ## Installation
19
+
20
+ ```shell
21
+ npm install vue-viewports
22
+ ```
23
+
24
+ Requires **Vue 3.3+**.
25
+
26
+ ## Usage
27
+
28
+ ### Composable (`<script setup>`)
29
+
30
+ ```vue
31
+ <script setup lang="ts">
32
+ import { useViewport } from 'vue-viewports'
33
+
34
+ const viewport = useViewport()
35
+ // viewport.value is { rule, label } | undefined
36
+ </script>
37
+
38
+ <template>
39
+ <p>Current viewport: {{ viewport?.label ?? 'unknown' }}</p>
40
+ <DesktopNav v-if="viewport?.label === 'desktop'" />
41
+ <MobileNav v-else />
42
+ </template>
43
+ ```
44
+
45
+ `useViewport()` returns a `readonly` ref. It lazily initializes the [default breakpoints](#default-breakpoints) on first use, so it works without the plugin. The value is `undefined` while no breakpoint matches (e.g. a width below the smallest `rule`).
46
+
47
+ ### Plugin
48
+
49
+ Install the plugin to expose `$currentViewport` on every component and to register your own breakpoints app-wide.
50
+
51
+ ```ts
52
+ import { createApp } from 'vue'
53
+ import VueViewports from 'vue-viewports'
54
+ import App from './App.vue'
55
+
56
+ createApp(App)
57
+ .use(VueViewports) // default breakpoints
58
+ .mount('#app')
59
+ ```
60
+
61
+ With custom breakpoints:
62
+
63
+ ```ts
64
+ createApp(App)
65
+ .use(VueViewports, [
66
+ { rule: '600px', label: 'small' },
67
+ { rule: '900px', label: 'medium' },
68
+ { rule: '1200px', label: 'large' },
69
+ ])
70
+ .mount('#app')
71
+ ```
72
+
73
+ Then, in any component:
74
+
75
+ ```vue
76
+ <template>
77
+ <header :class="$currentViewport?.label">…</header>
78
+ </template>
79
+ ```
80
+
81
+ The plugin is **authoritative**: installing it (re)configures the shared state, overriding any defaults a composable may have lazily set up.
82
+
83
+ ### Custom breakpoints with the composable
84
+
85
+ You can also configure breakpoints without the plugin by calling `setupViewports` once (e.g. in your entry file):
86
+
87
+ ```ts
88
+ import { setupViewports } from 'vue-viewports'
89
+
90
+ setupViewports([{ rule: '600px', label: 'small' }, { rule: '1200px', label: 'large' }])
91
+ ```
92
+
93
+ ## API
94
+
95
+ | Export | Description |
96
+ | --- | --- |
97
+ | `default` / `VueViewports` | Vue 3 plugin. `app.use(VueViewports, viewports?)`. |
98
+ | `useViewport()` | Composable returning `Readonly<Ref<ViewportMatch \| undefined>>`. |
99
+ | `setupViewports(viewports?)` | Imperatively (re)configure breakpoints; returns a teardown function. Idempotent. |
100
+ | `defaultViewports` | The built-in breakpoints. |
101
+ | `toMediaQuery(rule)` | `'768px'` → `'(min-width: 768px)'`. |
102
+ | `computeMatch(viewports)` | Pure-ish helper: the largest currently matching viewport. |
103
+ | `viewportInjectionKey` | `InjectionKey` for the readonly ref provided by the plugin. |
104
+ | `$currentViewport` | Component property added by the plugin: `ViewportMatch \| undefined`. |
105
+
106
+ ### Types
107
+
108
+ ```ts
109
+ interface ViewportConfig {
110
+ readonly rule: string // CSS length used as `min-width`, e.g. '768px'
111
+ readonly label: string // your name for the viewport, e.g. 'tablet'
112
+ }
113
+ type ViewportMatch = ViewportConfig
114
+ type ViewportConfigList = readonly ViewportConfig[]
115
+ ```
116
+
117
+ `rule` is the width at which the viewport **starts** (inclusive); the matching viewport is the largest one whose `min-width` is satisfied.
118
+
119
+ ### Default breakpoints
120
+
121
+ | label | starts at (`min-width`) |
122
+ | --- | --- |
123
+ | `mobile` | `320px` |
124
+ | `tablet` | `768px` |
125
+ | `desktop` | `1024px` |
126
+ | `hd-desktop` | `1920px` |
127
+ | `qhd-desktop` | `2560px` |
128
+ | `uhd-desktop` | `3840px` |
129
+
130
+ ## Migrating from v3 (Vue 2)
131
+
132
+ `v4` is a full Vue 3 + TypeScript rewrite. The old `v3.x` line (Vue 2) remains installable for legacy projects: `npm install vue-viewports@3`.
133
+
134
+ | v3.x (Vue 2) | v4 (Vue 3) |
135
+ | --- | --- |
136
+ | `Vue.use(VueViewports, options)` | `createApp(App).use(VueViewports, options)` |
137
+ | `this.$currentViewport` | `this.$currentViewport` (unchanged) or `useViewport()` |
138
+ | Object getters `{ rule, label }` | Plain reactive `{ rule, label }` object |
139
+ | Not reactive ([#6](https://github.com/scaccogatto/vue-viewports/issues/6)) | Fully reactive (`ref`-backed) |
140
+ | Bundled a `matchMedia` polyfill | Uses the native `matchMedia` API |
141
+
142
+ The `options` shape (`[{ rule, label }]`) is unchanged, so most apps only need to swap `Vue.use` for `createApp(...).use`.
143
+
144
+ ## License
145
+
146
+ [MIT](./LICENSE) © Marco Boffo
@@ -0,0 +1,16 @@
1
+ import { DeepReadonly, Ref } from 'vue';
2
+ import { ViewportMatch } from './types';
3
+ /**
4
+ * Reactive access to the current viewport from any component.
5
+ *
6
+ * ```ts
7
+ * const viewport = useViewport()
8
+ * watchEffect(() => console.log(viewport.value?.label))
9
+ * ```
10
+ *
11
+ * If the {@link VueViewports} plugin has been installed, this returns the state
12
+ * configured there. Otherwise it lazily initializes the default breakpoints on
13
+ * first call (no-op during SSR). The value is `undefined` until the first match
14
+ * is computed and whenever no viewport matches.
15
+ */
16
+ export declare const useViewport: () => DeepReadonly<Ref<ViewportMatch | undefined>>;
package/dist/core.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { DeepReadonly, Ref } from 'vue';
2
+ import { ViewportConfig, ViewportConfigList, ViewportMatch } from './types';
3
+ /**
4
+ * Sensible default breakpoints, ordered from smallest to largest.
5
+ * Override them by passing your own list to the plugin or composable.
6
+ */
7
+ export declare const defaultViewports: ViewportConfigList;
8
+ /** `'768px'` -> `'(min-width: 768px)'`. */
9
+ export declare const toMediaQuery: (rule: string) => string;
10
+ /**
11
+ * The largest viewport whose `min-width` currently matches, or `undefined`
12
+ * when none match. Pure with respect to its input: reads only `matchMedia`.
13
+ */
14
+ export declare const computeMatch: (viewports: ViewportConfigList) => ViewportMatch | undefined;
15
+ /**
16
+ * Register `matchMedia` listeners for every viewport and keep
17
+ * {@link currentViewport} in sync. Idempotent: tears down any previous setup
18
+ * first. Returns a teardown function that removes the listeners.
19
+ */
20
+ export declare const setupViewports: (viewports?: ViewportConfigList) => (() => void);
21
+ /**
22
+ * Lazily set up the default viewports on first read from a composable, but
23
+ * only if a plugin (which is authoritative) has not already done so and the
24
+ * environment exposes `matchMedia` (skipped during SSR).
25
+ */
26
+ export declare const ensureViewports: () => void;
27
+ /** Readonly handle to the shared reactive state, for composables. */
28
+ export declare const viewportRef: () => DeepReadonly<Ref<ViewportMatch | undefined>>;
29
+ /** Current value without subscribing reactively — used by the plugin getter. */
30
+ export declare const currentViewportValue: () => ViewportMatch | undefined;
31
+ /** Reset all state. Intended for tests. */
32
+ export declare const resetViewports: () => void;
33
+ export type { ViewportConfig, ViewportConfigList, ViewportMatch };
@@ -0,0 +1,4 @@
1
+ export { VueViewports, VueViewports as default, viewportInjectionKey } from './plugin';
2
+ export { useViewport } from './composable';
3
+ export { defaultViewports, setupViewports, toMediaQuery, computeMatch } from './core';
4
+ export type { ViewportConfig, ViewportConfigList, ViewportMatch } from './types';
@@ -0,0 +1,31 @@
1
+ import { InjectionKey, Plugin } from 'vue';
2
+ import { viewportRef } from './core';
3
+ import { ViewportConfigList, ViewportMatch } from './types';
4
+ /** Inject key for the readonly current-viewport ref. */
5
+ export declare const viewportInjectionKey: InjectionKey<ReturnType<typeof viewportRef>>;
6
+ /**
7
+ * Vue 3 plugin. Sets up `matchMedia` listeners for the given viewports and
8
+ * exposes the current one as:
9
+ *
10
+ * - `this.$currentViewport` on every component (Options & global), and
11
+ * - an injectable readonly ref via {@link viewportInjectionKey}.
12
+ *
13
+ * ```ts
14
+ * import { createApp } from 'vue'
15
+ * import VueViewports from 'vue-viewports'
16
+ *
17
+ * createApp(App).use(VueViewports).mount('#app')
18
+ * // or with custom breakpoints:
19
+ * createApp(App).use(VueViewports, [{ rule: '600px', label: 'small' }])
20
+ * ```
21
+ *
22
+ * The plugin is authoritative: installing it always (re)configures the shared
23
+ * state, overriding any lazy defaults a composable may have set up.
24
+ */
25
+ export declare const VueViewports: Plugin<[ViewportConfigList?]>;
26
+ declare module 'vue' {
27
+ interface ComponentCustomProperties {
28
+ /** The current viewport, or `undefined` when none matches. */
29
+ readonly $currentViewport: ViewportMatch | undefined;
30
+ }
31
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A single viewport definition.
3
+ *
4
+ * `rule` is the CSS length at which the viewport starts (used as `min-width`),
5
+ * e.g. `'768px'`. `label` is the human-readable name, e.g. `'tablet'`.
6
+ */
7
+ export interface ViewportConfig {
8
+ readonly rule: string;
9
+ readonly label: string;
10
+ }
11
+ /**
12
+ * The currently matching viewport, or `undefined` when no viewport matches
13
+ * (e.g. a width smaller than the smallest configured `rule`).
14
+ */
15
+ export type ViewportMatch = ViewportConfig;
16
+ export type ViewportConfigList = readonly ViewportConfig[];
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("vue");var t=[{rule:`320px`,label:`mobile`},{rule:`768px`,label:`tablet`},{rule:`1024px`,label:`desktop`},{rule:`1920px`,label:`hd-desktop`},{rule:`2560px`,label:`qhd-desktop`},{rule:`3840px`,label:`uhd-desktop`}],n=(0,e.ref)(void 0),r,i=!1,a=e=>`(min-width: ${e})`,o=e=>Number.parseFloat(e),s=e=>{let t=[...e].sort((e,t)=>o(e.rule)-o(t.rule)).filter(e=>window.matchMedia(a(e.rule)).matches).at(-1);return t?{rule:t.rule,label:t.label}:void 0},c=(e=t)=>{r?.();let o=e.map(e=>window.matchMedia(a(e.rule))),c=()=>{n.value=s(e)};o.forEach(e=>e.addEventListener(`change`,c)),c(),i=!0;let l=()=>{o.forEach(e=>e.removeEventListener(`change`,c)),r===l&&(r=void 0)};return r=l,l},l=()=>{i||typeof window>`u`||typeof window.matchMedia!=`function`||c(t)},u=()=>(0,e.readonly)(n),d=()=>n.value,f=Symbol(`vue-viewports`),p={install(e,n=t){c(n),Object.defineProperty(e.config.globalProperties,"$currentViewport",{configurable:!0,enumerable:!0,get:()=>d()}),e.provide(f,u())}},m=()=>(l(),u());exports.VueViewports=p,exports.default=p,exports.computeMatch=s,exports.defaultViewports=t,exports.setupViewports=c,exports.toMediaQuery=a,exports.useViewport=m,exports.viewportInjectionKey=f;
@@ -1 +1,54 @@
1
- !function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.VueViewports=n():t.VueViewports=n()}(this,function(){return function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=13)}([function(t,n){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){var e=t.exports={version:"2.5.1"};"number"==typeof __e&&(__e=e)},function(t,n,e){var r=e(34)("wks"),o=e(10),i=e(0).Symbol,u="function"==typeof i;(t.exports=function(t){return r[t]||(r[t]=u&&i[t]||(u?i:o)("Symbol."+t))}).store=r},function(t,n,e){var r=e(17),o=e(22);t.exports=e(5)?function(t,n,e){return r.f(t,n,o(1,e))}:function(t,n,e){return t[n]=e,t}},function(t,n,e){t.exports=!e(9)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n){var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=[{rule:"320px",label:"mobile"},{rule:"768px",label:"tablet"},{rule:"1024px",label:"desktop"},{rule:"1920px",label:"hd-desktop"},{rule:"2560px",label:"qhd-desktop"},{rule:"3840px",label:"uhd-desktop"}],o={sizes:void 0,matchMedia:[],currentMatch:void 0};n.defaultOptions=r,n.store=o},function(t,n,e){var r=e(0),o=e(2),i=e(4),u=e(23),c=e(11),f=function(t,n,e){var a,s,p,l,d=t&f.F,v=t&f.G,y=t&f.S,h=t&f.P,x=t&f.B,b=v?r:y?r[n]||(r[n]={}):(r[n]||{}).prototype,w=v?o:o[n]||(o[n]={}),m=w.prototype||(w.prototype={});v&&(e=n);for(a in e)s=!d&&b&&void 0!==b[a],p=(s?b:e)[a],l=x&&s?c(p,r):h&&"function"==typeof p?c(Function.call,p):p,b&&u(b,a,p,t&f.U),w[a]!=p&&i(w,a,l),h&&m[a]!=p&&(m[a]=p)};r.core=o,f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,t.exports=f},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){var e=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++e+r).toString(36))}},function(t,n,e){var r=e(25);t.exports=function(t,n,e){if(r(t),void 0===n)return t;switch(e){case 1:return function(e){return t.call(n,e)};case 2:return function(e,r){return t.call(n,e,r)};case 3:return function(e,r,o){return t.call(n,e,r,o)}}return function(){return t.apply(n,arguments)}}},function(t,n){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=e(7),o=e(14),i={install:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:r.defaultOptions;r.store.sizes=n;var e=r.store.sizes.map(o.toMatchMedia);r.store.matchMedia=e,r.store.matchMedia.forEach(function(t){return t.addListener(o.updateMatchStatus)}),r.store.matchMedia.forEach(o.updateMatchStatus),window.Object.defineProperty(t.prototype,"$currentViewport",{get:function(){return i._getPublicObject()}})},get currentViewport(){return r.store.currentMatch},_getPublicObject:function(){if(void 0!==i.currentViewport){var t=i.currentViewport,n=t.rule,e=t.label;return{get rule(){return n},get label(){return e}}}}};n.default=i,"undefined"!=typeof window&&window.Vue&&window.Vue.use(i)},function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.updateMatchStatus=n.toMediaQuery=n.toMatchMedia=void 0,e(15),e(36);var r=e(7),o=function(t){return window.matchMedia(i(t.rule))},i=function(t){return"(min-width: "+t+")"},u=function(t){var n=t.media,e=t.matches,o=r.store.sizes.find(function(t){return n.includes(t.rule)}).rule;r.store.currentMatch=e?r.store.sizes.find(function(t){return t.rule===o}):r.store.sizes.slice(0).reverse().find(function(t){return window.matchMedia(i(t.rule)).matches})};n.toMatchMedia=o,n.toMediaQuery=i,n.updateMatchStatus=u},function(t,n,e){e(16),t.exports=e(2).Array.find},function(t,n,e){"use strict";var r=e(8),o=e(26)(5),i=!0;"find"in[]&&Array(1).find(function(){i=!1}),r(r.P+r.F*i,"Array",{find:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),e(35)("find")},function(t,n,e){var r=e(18),o=e(19),i=e(21),u=Object.defineProperty;n.f=e(5)?Object.defineProperty:function(t,n,e){if(r(t),n=i(n,!0),r(e),o)try{return u(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported!");return"value"in e&&(t[n]=e.value),t}},function(t,n,e){var r=e(1);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,n,e){t.exports=!e(5)&&!e(9)(function(){return 7!=Object.defineProperty(e(20)("div"),"a",{get:function(){return 7}}).a})},function(t,n,e){var r=e(1),o=e(0).document,i=r(o)&&r(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},function(t,n,e){var r=e(1);t.exports=function(t,n){if(!r(t))return t;var e,o;if(n&&"function"==typeof(e=t.toString)&&!r(o=e.call(t)))return o;if("function"==typeof(e=t.valueOf)&&!r(o=e.call(t)))return o;if(!n&&"function"==typeof(e=t.toString)&&!r(o=e.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n,e){var r=e(0),o=e(4),i=e(24),u=e(10)("src"),c=Function.toString,f=(""+c).split("toString");e(2).inspectSource=function(t){return c.call(t)},(t.exports=function(t,n,e,c){var a="function"==typeof e;a&&(i(e,"name")||o(e,"name",n)),t[n]!==e&&(a&&(i(e,u)||o(e,u,t[n]?""+t[n]:f.join(String(n)))),t===r?t[n]=e:c?t[n]?t[n]=e:o(t,n,e):(delete t[n],o(t,n,e)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||c.call(this)})},function(t,n){var e={}.hasOwnProperty;t.exports=function(t,n){return e.call(t,n)}},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,e){var r=e(11),o=e(27),i=e(28),u=e(29),c=e(31);t.exports=function(t,n){var e=1==t,f=2==t,a=3==t,s=4==t,p=6==t,l=5==t||p,d=n||c;return function(n,c,v){for(var y,h,x=i(n),b=o(x),w=r(c,v,3),m=u(b.length),g=0,M=e?d(n,m):f?d(n,0):void 0;m>g;g++)if((l||g in b)&&(y=b[g],h=w(y,g,x),t))if(e)M[g]=h;else if(h)switch(t){case 3:return!0;case 5:return y;case 6:return g;case 2:M.push(y)}else if(s)return!1;return p?-1:a||s?s:M}}},function(t,n,e){var r=e(6);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,n,e){var r=e(12);t.exports=function(t){return Object(r(t))}},function(t,n,e){var r=e(30),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},function(t,n){var e=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:e)(t)}},function(t,n,e){var r=e(32);t.exports=function(t,n){return new(r(t))(n)}},function(t,n,e){var r=e(1),o=e(33),i=e(3)("species");t.exports=function(t){var n;return o(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!o(n.prototype)||(n=void 0),r(n)&&null===(n=n[i])&&(n=void 0)),void 0===n?Array:n}},function(t,n,e){var r=e(6);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,n,e){var r=e(0),o=r["__core-js_shared__"]||(r["__core-js_shared__"]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,n,e){var r=e(3)("unscopables"),o=Array.prototype;void 0==o[r]&&e(4)(o,r,{}),t.exports=function(t){o[r][t]=!0}},function(t,n,e){e(37),t.exports=e(2).String.includes},function(t,n,e){"use strict";var r=e(8),o=e(38);r(r.P+r.F*e(40)("includes"),"String",{includes:function(t){return!!~o(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,e){var r=e(39),o=e(12);t.exports=function(t,n,e){if(r(n))throw TypeError("String#"+e+" doesn't accept regex!");return String(o(t))}},function(t,n,e){var r=e(1),o=e(6),i=e(3)("match");t.exports=function(t){var n;return r(t)&&(void 0!==(n=t[i])?!!n:"RegExp"==o(t))}},function(t,n,e){var r=e(3)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(e){try{return n[r]=!1,!"/./"[t](n)}catch(t){}}return!0}}])});
1
+ import { readonly as e, ref as t } from "vue";
2
+ //#region src/core.ts
3
+ var n = [
4
+ {
5
+ rule: "320px",
6
+ label: "mobile"
7
+ },
8
+ {
9
+ rule: "768px",
10
+ label: "tablet"
11
+ },
12
+ {
13
+ rule: "1024px",
14
+ label: "desktop"
15
+ },
16
+ {
17
+ rule: "1920px",
18
+ label: "hd-desktop"
19
+ },
20
+ {
21
+ rule: "2560px",
22
+ label: "qhd-desktop"
23
+ },
24
+ {
25
+ rule: "3840px",
26
+ label: "uhd-desktop"
27
+ }
28
+ ], r = t(void 0), i, a = !1, o = (e) => `(min-width: ${e})`, s = (e) => Number.parseFloat(e), c = (e) => {
29
+ let t = [...e].sort((e, t) => s(e.rule) - s(t.rule)).filter((e) => window.matchMedia(o(e.rule)).matches).at(-1);
30
+ return t ? {
31
+ rule: t.rule,
32
+ label: t.label
33
+ } : void 0;
34
+ }, l = (e = n) => {
35
+ i?.();
36
+ let t = e.map((e) => window.matchMedia(o(e.rule))), s = () => {
37
+ r.value = c(e);
38
+ };
39
+ t.forEach((e) => e.addEventListener("change", s)), s(), a = !0;
40
+ let l = () => {
41
+ t.forEach((e) => e.removeEventListener("change", s)), i === l && (i = void 0);
42
+ };
43
+ return i = l, l;
44
+ }, u = () => {
45
+ a || typeof window > "u" || typeof window.matchMedia != "function" || l(n);
46
+ }, d = () => e(r), f = () => r.value, p = Symbol("vue-viewports"), m = { install(e, t = n) {
47
+ l(t), Object.defineProperty(e.config.globalProperties, "$currentViewport", {
48
+ configurable: !0,
49
+ enumerable: !0,
50
+ get: () => f()
51
+ }), e.provide(p, d());
52
+ } }, h = () => (u(), d());
53
+ //#endregion
54
+ export { m as VueViewports, m as default, c as computeMatch, n as defaultViewports, l as setupViewports, o as toMediaQuery, h as useViewport, p as viewportInjectionKey };
package/package.json CHANGED
@@ -1,35 +1,75 @@
1
1
  {
2
2
  "name": "vue-viewports",
3
- "version": "3.0.2",
4
- "description": "define your custom viewports and use them in your components",
3
+ "version": "4.0.0",
4
+ "description": "Named, reactive, matchMedia-based viewport breakpoints for Vue 3 a tiny plugin and composable.",
5
+ "type": "module",
5
6
  "author": {
6
7
  "name": "Marco Boffo",
7
8
  "email": "marcoboffo.waves@gmail.com"
8
9
  },
10
+ "license": "MIT",
9
11
  "repository": {
10
12
  "type": "git",
11
- "url": "https://github.com/scaccogatto/vue-viewports.git"
13
+ "url": "git+https://github.com/scaccogatto/vue-viewports.git"
12
14
  },
13
- "main": "dist/vue-viewports.js",
14
- "license": "MIT",
15
+ "homepage": "https://github.com/scaccogatto/vue-viewports#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/scaccogatto/vue-viewports/issues"
18
+ },
19
+ "keywords": [
20
+ "vue",
21
+ "vue3",
22
+ "viewport",
23
+ "viewports",
24
+ "responsive",
25
+ "breakpoints",
26
+ "media-query",
27
+ "matchmedia",
28
+ "composable",
29
+ "plugin"
30
+ ],
31
+ "sideEffects": false,
32
+ "main": "./dist/vue-viewports.cjs",
33
+ "module": "./dist/vue-viewports.js",
34
+ "types": "./dist/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/vue-viewports.js",
39
+ "require": "./dist/vue-viewports.cjs"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ],
15
46
  "scripts": {
16
- "build": "webpack -p --config webpack.config.js",
17
- "lint": "eslint src/**"
47
+ "dev": "vite",
48
+ "build": "vite build",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "coverage": "vitest run --coverage",
53
+ "lint": "eslint .",
54
+ "prepublishOnly": "npm run build"
18
55
  },
19
- "dependencies": {
20
- "vue": "^2.5.9"
56
+ "peerDependencies": {
57
+ "vue": "^3.3.0"
21
58
  },
22
59
  "devDependencies": {
23
- "babel-core": "^6.26.0",
24
- "babel-loader": "^7.1.2",
25
- "babel-polyfill": "^6.26.0",
26
- "babel-preset-env": "^1.6.1",
27
- "eslint": "^4.12.1",
28
- "eslint-config-standard": "^10.2.1",
29
- "eslint-plugin-import": "^2.8.0",
30
- "eslint-plugin-node": "^5.2.1",
31
- "eslint-plugin-promise": "^3.6.0",
32
- "eslint-plugin-standard": "^3.0.1",
33
- "webpack": "^3.8.1"
60
+ "@eslint/js": "^10.0.1",
61
+ "@vitest/coverage-v8": "^4.1.9",
62
+ "@vue/test-utils": "^2.4.11",
63
+ "eslint": "^10.6.0",
64
+ "jsdom": "^29.1.1",
65
+ "typescript": "^6.0.3",
66
+ "typescript-eslint": "^8.62.0",
67
+ "vite": "^8.1.0",
68
+ "vite-plugin-dts": "^5.0.3",
69
+ "vitest": "^4.1.9",
70
+ "vue": "^3.5.39"
71
+ },
72
+ "engines": {
73
+ "node": ">=20"
34
74
  }
35
75
  }
package/.babelrc DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "presets": [
3
- [
4
- "env",
5
- {
6
- "targets": {
7
- "browsers": [
8
- "last 2 versions",
9
- "ie >= 11"
10
- ]
11
- }
12
- }
13
- ]
14
- ],
15
- "comments": false
16
- }
package/.eslintrc.js DELETED
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- 'extends': 'standard'
3
- }
package/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - "node"
package/src/index.js DELETED
@@ -1,50 +0,0 @@
1
- import { store, defaultOptions } from './store'
2
- import { toMatchMedia, updateMatchStatus } from './matchMedia'
3
-
4
- const VueViewports = {
5
- install (Vue, options = defaultOptions) {
6
- // save sizes
7
- store.sizes = options
8
-
9
- // create matchMediaObjects https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
10
- const matchMedia = store.sizes.map(toMatchMedia)
11
-
12
- // save it
13
- store.matchMedia = matchMedia
14
-
15
- // add listeners
16
- store.matchMedia.forEach(matchMediaObj => matchMediaObj.addListener(updateMatchStatus))
17
-
18
- // first trigger
19
- store.matchMedia.forEach(updateMatchStatus)
20
-
21
- // global call
22
- window.Object.defineProperty(Vue.prototype, '$currentViewport', {
23
- get: () => VueViewports._getPublicObject()
24
- })
25
- },
26
- get currentViewport () {
27
- return store.currentMatch
28
- },
29
- _getPublicObject () {
30
- const { currentViewport } = VueViewports
31
- if (typeof currentViewport !== 'undefined') {
32
- const { rule, label } = VueViewports.currentViewport
33
- return {
34
- get rule () {
35
- return rule
36
- },
37
- get label () {
38
- return label
39
- }
40
- }
41
- }
42
- }
43
- }
44
-
45
- export default VueViewports
46
-
47
- // in-browser load
48
- if (typeof window !== 'undefined' && window.Vue) {
49
- window.Vue.use(VueViewports)
50
- }
package/src/matchMedia.js DELETED
@@ -1,22 +0,0 @@
1
- import 'core-js/fn/array/find'
2
- import 'core-js/fn/string/includes'
3
- import { store } from './store'
4
-
5
- const toMatchMedia = size => window.matchMedia(toMediaQuery(size.rule))
6
-
7
- const toMediaQuery = rule => `(min-width: ${rule})`
8
-
9
- const updateMatchStatus = ({ media, matches }) => {
10
- // find the store rule, matching the event
11
- const rule = store.sizes.find(size => media.includes(size.rule)).rule
12
-
13
- if (matches) {
14
- // if it matches, just set the rule
15
- store.currentMatch = store.sizes.find(size => size.rule === rule)
16
- } else {
17
- // if it does not,find the greater matching rule and apply
18
- store.currentMatch = store.sizes.slice(0).reverse().find(size => window.matchMedia(toMediaQuery(size.rule)).matches)
19
- }
20
- }
21
-
22
- export { toMatchMedia, toMediaQuery, updateMatchStatus }
package/src/store.js DELETED
@@ -1,34 +0,0 @@
1
- const defaultOptions = [
2
- {
3
- rule: '320px',
4
- label: 'mobile'
5
- },
6
- {
7
- rule: '768px',
8
- label: 'tablet'
9
- },
10
- {
11
- rule: '1024px',
12
- label: 'desktop'
13
- },
14
- {
15
- rule: '1920px',
16
- label: 'hd-desktop'
17
- },
18
- {
19
- rule: '2560px',
20
- label: 'qhd-desktop'
21
- },
22
- {
23
- rule: '3840px',
24
- label: 'uhd-desktop'
25
- }
26
- ]
27
-
28
- const store = {
29
- sizes: undefined,
30
- matchMedia: [],
31
- currentMatch: undefined
32
- }
33
-
34
- export { defaultOptions, store }
package/webpack.config.js DELETED
@@ -1,27 +0,0 @@
1
- const webpack = require('webpack')
2
- const path = require('path')
3
-
4
- const config = {
5
- entry: path.resolve(__dirname, 'src', 'index.js'),
6
- output: {
7
- path: path.resolve(__dirname, 'dist'),
8
- filename: 'vue-viewports.js',
9
- library: 'VueViewports',
10
- libraryTarget: 'umd'
11
- },
12
- module: {
13
- rules: [
14
- {
15
- test: /\.js$/,
16
- include: path.resolve(__dirname, 'src'),
17
- exclude: /node_modules/,
18
- use: 'babel-loader'
19
- }
20
- ]
21
- },
22
- plugins: [
23
- new webpack.optimize.UglifyJsPlugin()
24
- ]
25
- }
26
-
27
- module.exports = config