vue-viewports 3.1.2 → 4.0.1

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/README.md CHANGED
@@ -1,73 +1,146 @@
1
1
  # vue-viewports
2
2
 
3
- > define your custom viewports and use them in your components
3
+ > Named, reactive, `matchMedia`-based viewport breakpoints for Vue 3 a tiny plugin **and** composable.
4
4
 
5
- ## Features
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)
6
9
 
7
- - Uses [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
8
- - Exposes a fully updated viewport name
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.
9
11
 
10
- ## Installation
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.
11
17
 
12
- ### npm
18
+ ## Installation
13
19
 
14
20
  ```shell
15
- npm install vue-viewports --save-dev
21
+ npm install vue-viewports
16
22
  ```
17
23
 
18
- ### Vue's main.js
24
+ Requires **Vue 3.3+**.
25
+
26
+ ## Usage
27
+
28
+ ### Composable (`<script setup>`)
19
29
 
20
- ```js
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'
21
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
+ ```
22
60
 
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)
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>
51
79
  ```
52
80
 
53
- ### Arguments
81
+ The plugin is **authoritative**: installing it (re)configures the shared state, overriding any defaults a composable may have lazily set up.
54
82
 
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
83
+ ### Custom breakpoints with the composable
56
84
 
57
- ## Example
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
+ ```
58
92
 
59
- ```js
60
- {
61
- if (this.$currentViewport.label === 'tablet') {
62
- // from 768px (included) to 1024px (excluded)
63
- } else {
64
- // anything else
65
- }
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'
66
112
  }
113
+ type ViewportMatch = ViewportConfig
114
+ type ViewportConfigList = readonly ViewportConfig[]
67
115
  ```
68
116
 
69
- ## Variables
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`.
70
143
 
71
- - `$currentViewport`: the current viewport object, defined by `rule`, `label`; `undefined` if no match.
144
+ ## License
72
145
 
73
- > Feel free to contribute and ask questions!
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,78 @@
1
1
  {
2
2
  "name": "vue-viewports",
3
- "version": "3.1.2",
4
- "description": "define your custom viewports and use them in your components",
3
+ "version": "4.0.1",
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
+ "composition-api",
31
+ "typescript",
32
+ "vite"
33
+ ],
34
+ "sideEffects": false,
35
+ "main": "./dist/vue-viewports.cjs",
36
+ "module": "./dist/vue-viewports.js",
37
+ "types": "./dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/vue-viewports.js",
42
+ "require": "./dist/vue-viewports.cjs"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "files": [
47
+ "dist"
48
+ ],
15
49
  "scripts": {
16
- "start": "webpack -p --config webpack.config.js",
17
- "lint": "eslint src/**"
50
+ "dev": "vite",
51
+ "build": "vite build",
52
+ "typecheck": "tsc --noEmit",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "coverage": "vitest run --coverage",
56
+ "lint": "eslint .",
57
+ "prepublishOnly": "npm run build"
18
58
  },
19
- "dependencies": {
20
- "vue": "^2.5.9"
59
+ "peerDependencies": {
60
+ "vue": "^3.3.0"
21
61
  },
22
62
  "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"
63
+ "@eslint/js": "^10.0.1",
64
+ "@vitest/coverage-v8": "^4.1.9",
65
+ "@vue/test-utils": "^2.4.11",
66
+ "eslint": "^10.6.0",
67
+ "jsdom": "^29.1.1",
68
+ "typescript": "^6.0.3",
69
+ "typescript-eslint": "^8.62.0",
70
+ "vite": "^8.1.0",
71
+ "vite-plugin-dts": "^5.0.3",
72
+ "vitest": "^4.1.9",
73
+ "vue": "^3.5.39"
74
+ },
75
+ "engines": {
76
+ "node": ">=20"
34
77
  }
35
78
  }
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,24 +0,0 @@
1
- language: node_js
2
-
3
- node_js:
4
- - lts/carbon
5
-
6
- install:
7
- - npm install
8
-
9
- script:
10
- - npm run start
11
-
12
- cache:
13
- directories:
14
- - node_modules
15
-
16
- deploy:
17
- provider: npm
18
- email: marcoboffo.waves@gmail.com
19
- skip_cleanup: true
20
- api_key:
21
- secure: O232hq84yuYDU7vXTXc8Mu9UFdEL7rpA/9b+E8WDd4O7m/BWbeHdG1VTLK4FHq2jP27jekVGB1WpmrM+wiSg5TpYm0l0540iqadv6x1VBGeEkTdY44M3I93UbmDa6rM7w9vxdU3jm2LweS59PnXWeKAMDQ3FKlDKnIL/Rw1eKGlAVdRDFGY2lMpMmQQNvLXXMCchcceLDLAElcIRPi3CGpjYttpTlFbTayaRkObwHuZphCOB/9e3t/0ZtCM5W9iWBe6F+2+WoOLgL+qMVv7b1r458CWR3wS7FyLzT/jg9sl06Uqhcau6W29HfH2RnCYuS4uuNIITtDRKn6xOGLJr3XmJbhsqTDGVk6ybvnI20ltkOXZNDaMFYrWaEN+ibl/2c4itGUTsUg/EAMV7s37+W3rcB0Q3BPFXw3zFDT0AyVEpXpo6hvcti+leodZ2JA6CUco/KHPov31lfr6zh7B+TyFNQrFJeAVKTLng5TLJlkPQOe3fVFrZZmqKaD+s73EI/kGvgwclcoWqu2pVoEE/S7x6lwKFsT4nmw8Q0AGuJ2YTOTNw2Ky0S1siBeEFAlThST9HqaGL9qwDHOiO/4E1croGpuJjT6Xf472H43orCP+s9RFg506I9+Qy0cq2qI+ouBk7EIqpxdTAt/Rm55RnDVMjyj7moIbM2QzEgrWeR+8=
22
- on:
23
- tags: true
24
- repo: scaccogatto/vue-viewports
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