responsive-media 1.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 +21 -0
- package/README.md +123 -0
- package/dist/create-responsive.d.ts +17 -0
- package/dist/create-responsive.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/responsive.enum.d.ts +7 -0
- package/dist/responsive.enum.js +11 -0
- package/dist/vue-responsive.d.ts +8 -0
- package/dist/vue-responsive.js +38 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
npm install responsive-media
|
|
2
|
+
|
|
3
|
+
# responsive-media
|
|
4
|
+
|
|
5
|
+
A utility for reactive state based on CSS media queries. Includes integration with Vue 3 (Composition API).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install responsive-media
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage without Vue
|
|
14
|
+
|
|
15
|
+
### Getting the state
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { responsiveState } from 'responsive-media';
|
|
19
|
+
|
|
20
|
+
// Get the current state:
|
|
21
|
+
const { mobile, tablet, desktop } = responsiveState.proxy;
|
|
22
|
+
|
|
23
|
+
console.log('isMobile:', mobile);
|
|
24
|
+
console.log('isTablet:', tablet);
|
|
25
|
+
console.log('isDesktop:', desktop);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Subscribing to changes
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Subscribe to state changes:
|
|
32
|
+
const unsubscribe = responsiveState.subscribe((state) => {
|
|
33
|
+
console.log('State changed:', state);
|
|
34
|
+
// state.mobile, state.tablet, state.desktop
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// To unsubscribe:
|
|
38
|
+
unsubscribe();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage with Vue 3 (Composition API)
|
|
42
|
+
|
|
43
|
+
### Plugin registration
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { createApp } from 'vue';
|
|
47
|
+
import { ResponsivePlugin, ResponsiveConfig } from 'responsive-media';
|
|
48
|
+
import App from './App.vue';
|
|
49
|
+
|
|
50
|
+
const app = createApp(App);
|
|
51
|
+
|
|
52
|
+
// Use default breakpoints:
|
|
53
|
+
app.use(ResponsivePlugin);
|
|
54
|
+
|
|
55
|
+
// Or provide your own breakpoints (now you can combine conditions):
|
|
56
|
+
app.use(ResponsivePlugin, {
|
|
57
|
+
...ResponsiveConfig, // keep default breakpoints
|
|
58
|
+
myCustom: [
|
|
59
|
+
{ type: 'min-width', value: 1200 },
|
|
60
|
+
{ type: 'aspect-ratio', value: '16/9' },
|
|
61
|
+
], // add your own with multiple conditions
|
|
62
|
+
mobile: [
|
|
63
|
+
{ type: 'max-width', value: 500 },
|
|
64
|
+
{ type: 'orientation', value: 'portrait' },
|
|
65
|
+
], // override default with multiple conditions
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
app.mount('#app');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Usage in a component
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
<script setup lang="ts">
|
|
75
|
+
import { useResponsive } from 'responsive-media';
|
|
76
|
+
|
|
77
|
+
const responsive = useResponsive();
|
|
78
|
+
|
|
79
|
+
// responsive.mobile, responsive.tablet, responsive.desktop, etc.
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Customizing breakpoints
|
|
84
|
+
|
|
85
|
+
You can override or add your own breakpoints using the setResponsiveConfig function. Now each breakpoint can be an array of conditions (they will be combined with and):
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { setResponsiveConfig, ResponsiveConfig } from 'responsive-media';
|
|
89
|
+
|
|
90
|
+
setResponsiveConfig({
|
|
91
|
+
...ResponsiveConfig, // keep default
|
|
92
|
+
myCustom: [
|
|
93
|
+
{ type: 'min-width', value: 1200 },
|
|
94
|
+
{ type: 'aspect-ratio', value: '16/9' },
|
|
95
|
+
], // add your own breakpoint with multiple conditions
|
|
96
|
+
mobile: [
|
|
97
|
+
{ type: 'max-width', value: 500 },
|
|
98
|
+
{ type: 'orientation', value: 'portrait' },
|
|
99
|
+
], // override default with multiple conditions
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Exported entities
|
|
104
|
+
- responsiveState
|
|
105
|
+
- ResponsiveConfig
|
|
106
|
+
- setResponsiveConfig
|
|
107
|
+
- ResponsivePlugin (Vue)
|
|
108
|
+
- useResponsive (Vue)
|
|
109
|
+
|
|
110
|
+
## Breakpoint config format
|
|
111
|
+
|
|
112
|
+
Each breakpoint is now described by an array of conditions (MediaQueryConfig = MediaQueryCondition[]), where each condition is an object with type and value. All conditions within a breakpoint are combined with and (e.g., `(max-width: 600px) and (aspect-ratio: 16/9)`).
|
|
113
|
+
|
|
114
|
+
## Author
|
|
115
|
+
|
|
116
|
+
Danil Lisin Vladimirovich aka Macrulez
|
|
117
|
+
|
|
118
|
+
GitHub: [macrulezru](https://github.com/macrulezru)
|
|
119
|
+
|
|
120
|
+
Website: [macrulez.ru](https://macrulez.ru/)
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Breakpoint, type MediaQueryConfig } from './responsive.enum';
|
|
2
|
+
export type { MediaQueryConfig };
|
|
3
|
+
type ResponsiveState = Record<Breakpoint, boolean>;
|
|
4
|
+
type ResponsiveListener = (state: ResponsiveState) => void;
|
|
5
|
+
declare class ReactiveResponsiveState {
|
|
6
|
+
private state;
|
|
7
|
+
private listeners;
|
|
8
|
+
proxy: ResponsiveState;
|
|
9
|
+
private queries;
|
|
10
|
+
constructor();
|
|
11
|
+
private applyConfig;
|
|
12
|
+
setConfig(config: Record<string, MediaQueryConfig>): void;
|
|
13
|
+
subscribe(listener: ResponsiveListener): () => boolean;
|
|
14
|
+
private notify;
|
|
15
|
+
}
|
|
16
|
+
export declare const responsiveState: ReactiveResponsiveState;
|
|
17
|
+
export declare function setResponsiveConfig(config: Record<string, MediaQueryConfig>): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ResponsiveConfig } from './responsive.enum';
|
|
2
|
+
class ReactiveResponsiveState {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.listeners = new Set();
|
|
5
|
+
this.queries = {};
|
|
6
|
+
this.state = {};
|
|
7
|
+
this.proxy = new Proxy(this.state, {
|
|
8
|
+
set: (target, prop, value) => {
|
|
9
|
+
if (target[prop] !== value) {
|
|
10
|
+
target[prop] = value;
|
|
11
|
+
this.notify();
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
},
|
|
15
|
+
get: (target, prop) => {
|
|
16
|
+
return target[prop];
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
this.applyConfig(ResponsiveConfig);
|
|
20
|
+
}
|
|
21
|
+
applyConfig(config) {
|
|
22
|
+
Object.entries(this.queries).forEach(([key, query]) => {
|
|
23
|
+
query.onchange = null;
|
|
24
|
+
});
|
|
25
|
+
this.queries = {};
|
|
26
|
+
Object.keys(this.state).forEach(key => delete this.state[key]);
|
|
27
|
+
Object.entries(config).forEach(([key, conditions]) => {
|
|
28
|
+
// Формируем строку media-запроса из массива условий
|
|
29
|
+
const mq = conditions
|
|
30
|
+
.map(cond => {
|
|
31
|
+
const val = typeof cond.value === 'number' && !String(cond.type).includes('aspect-ratio')
|
|
32
|
+
? cond.value + 'px'
|
|
33
|
+
: cond.value;
|
|
34
|
+
return `(${cond.type}: ${val})`;
|
|
35
|
+
})
|
|
36
|
+
.join(' and ');
|
|
37
|
+
const query = window.matchMedia(mq);
|
|
38
|
+
this.queries[key] = query;
|
|
39
|
+
this.proxy[key] = query.matches;
|
|
40
|
+
query.addEventListener('change', (e) => {
|
|
41
|
+
this.proxy[key] = e.matches;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
this.notify();
|
|
45
|
+
}
|
|
46
|
+
setConfig(config) {
|
|
47
|
+
this.applyConfig(config);
|
|
48
|
+
}
|
|
49
|
+
subscribe(listener) {
|
|
50
|
+
this.listeners.add(listener);
|
|
51
|
+
listener(this.proxy);
|
|
52
|
+
return () => this.listeners.delete(listener);
|
|
53
|
+
}
|
|
54
|
+
notify() {
|
|
55
|
+
this.listeners.forEach(listener => listener(this.proxy));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export const responsiveState = new ReactiveResponsiveState();
|
|
59
|
+
export function setResponsiveConfig(config) {
|
|
60
|
+
responsiveState.setConfig(config);
|
|
61
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type Breakpoint = 'mobile' | 'tablet' | 'desktop';
|
|
2
|
+
export interface MediaQueryCondition {
|
|
3
|
+
type: 'width' | 'min-width' | 'max-width' | 'height' | 'min-height' | 'max-height' | 'aspect-ratio' | 'min-aspect-ratio' | 'max-aspect-ratio' | 'orientation' | 'resolution' | 'min-resolution' | 'max-resolution' | 'color' | 'min-color' | 'max-color' | 'color-index' | 'min-color-index' | 'max-color-index' | 'monochrome' | 'min-monochrome' | 'max-monochrome' | 'scan' | 'grid';
|
|
4
|
+
value: number | string;
|
|
5
|
+
}
|
|
6
|
+
export type MediaQueryConfig = MediaQueryCondition[];
|
|
7
|
+
export declare const ResponsiveConfig: Record<Breakpoint, MediaQueryConfig>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { App } from '@vue/runtime-core';
|
|
2
|
+
import type { MediaQueryConfig } from './create-responsive';
|
|
3
|
+
export declare function useResponsive(): any;
|
|
4
|
+
type ResponsiveConfigType = Record<string, MediaQueryConfig>;
|
|
5
|
+
export declare const ResponsivePlugin: {
|
|
6
|
+
install(app: App, config?: ResponsiveConfigType): void;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { inject, readonly, reactive } from '@vue/runtime-core';
|
|
2
|
+
import { responsiveState, setResponsiveConfig } from './create-responsive';
|
|
3
|
+
const RESPONSIVE_KEY = Symbol('responsiveState');
|
|
4
|
+
let vueReactiveState = null;
|
|
5
|
+
export function useResponsive() {
|
|
6
|
+
// Получаем реактивный объект из provide/inject или создаём локально
|
|
7
|
+
const injected = inject(RESPONSIVE_KEY);
|
|
8
|
+
if (injected)
|
|
9
|
+
return injected;
|
|
10
|
+
if (!vueReactiveState) {
|
|
11
|
+
vueReactiveState = readonly(reactive({ ...responsiveState.proxy }));
|
|
12
|
+
// Синхронизируем с изменениями responsiveState
|
|
13
|
+
responsiveState.subscribe((state) => {
|
|
14
|
+
Object.keys(state).forEach(key => {
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
vueReactiveState[key] = state[key];
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return vueReactiveState;
|
|
21
|
+
}
|
|
22
|
+
export const ResponsivePlugin = {
|
|
23
|
+
install(app, config) {
|
|
24
|
+
if (config) {
|
|
25
|
+
setResponsiveConfig(config);
|
|
26
|
+
}
|
|
27
|
+
if (!vueReactiveState) {
|
|
28
|
+
vueReactiveState = readonly(reactive({ ...responsiveState.proxy }));
|
|
29
|
+
responsiveState.subscribe((state) => {
|
|
30
|
+
Object.keys(state).forEach(key => {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
vueReactiveState[key] = state[key];
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
app.provide(RESPONSIVE_KEY, vueReactiveState);
|
|
37
|
+
},
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "responsive-media",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Утилита для реактивного состояния на основе CSS media queries без зависимостей.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/yourusername/responsive-media.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"responsive",
|
|
27
|
+
"media-query",
|
|
28
|
+
"typescript",
|
|
29
|
+
"reactive",
|
|
30
|
+
"utility"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/vue": "^1.0.31",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
38
|
+
"eslint": "^9.39.2",
|
|
39
|
+
"eslint-plugin-vue": "^10.7.0",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"vue": "^3.5.27"
|
|
44
|
+
}
|
|
45
|
+
}
|