responsive-media 1.0.7 → 1.2.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/README.md +868 -141
- package/dist/base-state.d.ts +182 -0
- package/dist/base-state.js +406 -0
- package/dist/container-state.d.ts +36 -0
- package/dist/container-state.js +107 -0
- package/dist/create-responsive.d.ts +46 -17
- package/dist/create-responsive.js +117 -54
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -1
- package/dist/media-query.d.ts +17 -0
- package/dist/media-query.js +30 -0
- package/dist/presets.d.ts +48 -0
- package/dist/presets.js +71 -0
- package/dist/react-responsive.d.ts +64 -0
- package/dist/react-responsive.js +95 -0
- package/dist/responsive.enum.d.ts +58 -2
- package/dist/responsive.enum.js +9 -9
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +3 -0
- package/dist/vue-responsive.d.ts +80 -6
- package/dist/vue-responsive.js +159 -24
- package/package.json +82 -49
package/README.md
CHANGED
|
@@ -1,141 +1,868 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
1
|
+
# responsive-media
|
|
2
|
+
|
|
3
|
+
A utility for creating reactive boolean state from CSS media queries and element dimensions. Useful when you need more than CSS — when you want to **imperatively react to viewport or container changes** in JavaScript.
|
|
4
|
+
|
|
5
|
+
- **Viewport breakpoints** — backed by `window.matchMedia`
|
|
6
|
+
- **Container queries** — backed by `ResizeObserver` (JS-side evaluation)
|
|
7
|
+
- **Vue 3** and **React 18+** adapters included
|
|
8
|
+
- **SSR-safe** — falls back to `false` on the server
|
|
9
|
+
- **Framework-agnostic** core — works with Vanilla JS, signals libraries, or any other framework
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm install responsive-media
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
1. [Quick Start](#quick-start)
|
|
22
|
+
2. [Config format — MediaQueryConfig](#config-format--mediaqueryconfig)
|
|
23
|
+
3. [Global singleton](#global-singleton)
|
|
24
|
+
4. [createResponsiveState — isolated instances](#createresponsivestate--isolated-instances)
|
|
25
|
+
5. [ContainerState — element container queries](#containerstate--element-container-queries)
|
|
26
|
+
6. [Subscription API](#subscription-api)
|
|
27
|
+
7. [Ordered breakpoint helpers](#ordered-breakpoint-helpers)
|
|
28
|
+
8. [Utilities](#utilities)
|
|
29
|
+
9. [Presets](#presets)
|
|
30
|
+
10. [Vue 3 integration](#vue-3-integration)
|
|
31
|
+
11. [React 18+ integration](#react-18-integration)
|
|
32
|
+
12. [TypeScript helpers](#typescript-helpers)
|
|
33
|
+
13. [SSR / hydration](#ssr--hydration)
|
|
34
|
+
14. [Exported API reference](#exported-api-reference)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { responsiveState, setResponsiveConfig } from 'responsive-media';
|
|
42
|
+
|
|
43
|
+
setResponsiveConfig({
|
|
44
|
+
mobile: [{ type: 'max-width', value: 767 }],
|
|
45
|
+
tablet: [{ type: 'min-width', value: 768 }, { type: 'max-width', value: 1023 }],
|
|
46
|
+
desktop: [{ type: 'min-width', value: 1024 }],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Read current state
|
|
50
|
+
console.log(responsiveState.proxy.mobile); // true / false
|
|
51
|
+
|
|
52
|
+
// Subscribe to changes
|
|
53
|
+
const stop = responsiveState.subscribe((state) => {
|
|
54
|
+
console.log('desktop:', state.desktop);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Cleanup
|
|
58
|
+
stop();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Config format — MediaQueryConfig
|
|
64
|
+
|
|
65
|
+
Each breakpoint is described by a `MediaQueryConfig` — an array of conditions that are combined with **AND**, or a nested array of groups combined with **OR**.
|
|
66
|
+
|
|
67
|
+
### AND (flat array)
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// (min-width: 768px) and (max-width: 1023px)
|
|
71
|
+
[
|
|
72
|
+
{ type: 'min-width', value: 768 },
|
|
73
|
+
{ type: 'max-width', value: 1023 },
|
|
74
|
+
]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### OR (nested array)
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// (max-width: 600px), (orientation: portrait) and (max-width: 1024px)
|
|
81
|
+
[
|
|
82
|
+
[{ type: 'max-width', value: 600 }],
|
|
83
|
+
[{ type: 'orientation', value: 'portrait' }, { type: 'max-width', value: 1024 }],
|
|
84
|
+
]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Raw media type
|
|
88
|
+
|
|
89
|
+
Use `type: 'raw'` to insert a value verbatim — useful for media types like `print` or `screen`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// Matches 'print' media type
|
|
93
|
+
[{ type: 'raw', value: 'print' }]
|
|
94
|
+
|
|
95
|
+
// screen and (max-width: 600px)
|
|
96
|
+
[{ type: 'raw', value: 'screen' }, { type: 'max-width', value: 600 }]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Supported condition types
|
|
100
|
+
|
|
101
|
+
| `type` | Example value | Generated query |
|
|
102
|
+
|-------------------------|-------------------|-----------------------------------|
|
|
103
|
+
| `min-width` | `768` | `(min-width: 768px)` |
|
|
104
|
+
| `max-width` | `1023` | `(max-width: 1023px)` |
|
|
105
|
+
| `min-height` | `600` | `(min-height: 600px)` |
|
|
106
|
+
| `max-height` | `900` | `(max-height: 900px)` |
|
|
107
|
+
| `orientation` | `'portrait'` | `(orientation: portrait)` |
|
|
108
|
+
| `aspect-ratio` | `'16/9'` | `(aspect-ratio: 16/9)` |
|
|
109
|
+
| `prefers-color-scheme` | `'dark'` | `(prefers-color-scheme: dark)` |
|
|
110
|
+
| `prefers-reduced-motion`| `'reduce'` | `(prefers-reduced-motion: reduce)`|
|
|
111
|
+
| `prefers-contrast` | `'more'` | `(prefers-contrast: more)` |
|
|
112
|
+
| `hover` | `'none'` | `(hover: none)` |
|
|
113
|
+
| `pointer` | `'coarse'` | `(pointer: coarse)` |
|
|
114
|
+
| `forced-colors` | `'active'` | `(forced-colors: active)` |
|
|
115
|
+
| `resolution` | `'2dppx'` | `(resolution: 2dppx)` |
|
|
116
|
+
| `display-mode` | `'standalone'` | `(display-mode: standalone)` |
|
|
117
|
+
| `raw` | `'print'` | `print` *(verbatim)* |
|
|
118
|
+
| … and more | | |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Global singleton
|
|
123
|
+
|
|
124
|
+
The library exports a pre-configured singleton `responsiveState` initialized with the default `ResponsiveConfig` (mobile / tablet / desktop).
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { responsiveState, setResponsiveConfig, getResponsiveMediaQueries } from 'responsive-media';
|
|
128
|
+
|
|
129
|
+
// Re-configure the singleton
|
|
130
|
+
setResponsiveConfig(
|
|
131
|
+
{
|
|
132
|
+
sm: [{ type: 'max-width', value: 767 }],
|
|
133
|
+
lg: [{ type: 'min-width', value: 1024 }],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
order: ['sm', 'lg'], // for isAbove/isBelow/between
|
|
137
|
+
debounce: 50, // ms, throttle subscribe() listeners
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Read the current state snapshot
|
|
142
|
+
const { sm, lg } = responsiveState.getState();
|
|
143
|
+
|
|
144
|
+
// Direct proxy access (live, non-debounced)
|
|
145
|
+
console.log(responsiveState.proxy.sm);
|
|
146
|
+
|
|
147
|
+
// Get the generated CSS strings
|
|
148
|
+
const mq = getResponsiveMediaQueries();
|
|
149
|
+
// { sm: '(max-width: 767px)', lg: '(min-width: 1024px)' }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Default config (`ResponsiveConfig`)
|
|
153
|
+
|
|
154
|
+
| Key | Range |
|
|
155
|
+
|-----------|--------------|
|
|
156
|
+
| `mobile` | ≤ 600px |
|
|
157
|
+
| `tablet` | 601 – 960px |
|
|
158
|
+
| `desktop` | ≥ 961px |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## createResponsiveState — isolated instances
|
|
163
|
+
|
|
164
|
+
Create independent instances — useful for per-request SSR, multiple independent contexts, or testing:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media';
|
|
168
|
+
|
|
169
|
+
const layoutState = createResponsiveState(TailwindPreset, {
|
|
170
|
+
order: [...TailwindOrder],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const themeState = createResponsiveState({
|
|
174
|
+
dark: [{ type: 'prefers-color-scheme', value: 'dark' }],
|
|
175
|
+
reducedMotion: [{ type: 'prefers-reduced-motion', value: 'reduce' }],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
layoutState.subscribe((s) => console.log('layout:', s));
|
|
179
|
+
themeState.subscribe((s) => console.log('theme:', s));
|
|
180
|
+
|
|
181
|
+
// Cleanup when done (e.g. per-request SSR)
|
|
182
|
+
layoutState.destroy();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## ContainerState — element container queries
|
|
188
|
+
|
|
189
|
+
`ContainerState` tracks an **element's dimensions** via `ResizeObserver` and evaluates breakpoint conditions in JavaScript — the same concept as CSS Container Queries, but in JS.
|
|
190
|
+
|
|
191
|
+
The API is identical to `ReactiveResponsiveState` — all subscription methods work the same way.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { createContainerState } from 'responsive-media/container';
|
|
195
|
+
// or: import { createContainerState } from 'responsive-media';
|
|
196
|
+
|
|
197
|
+
const card = document.querySelector('.card')!;
|
|
198
|
+
|
|
199
|
+
const cardState = createContainerState(card, {
|
|
200
|
+
compact: [{ type: 'max-width', value: 300 }],
|
|
201
|
+
normal: [{ type: 'min-width', value: 301 }, { type: 'max-width', value: 599 }],
|
|
202
|
+
wide: [{ type: 'min-width', value: 600 }],
|
|
203
|
+
}, {
|
|
204
|
+
order: ['compact', 'normal', 'wide'],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Reactive class toggling
|
|
208
|
+
cardState.on('compact', (v) => card.classList.toggle('card--compact', v));
|
|
209
|
+
|
|
210
|
+
// Sync CSS custom properties: --card-compact: 1; --card-wide: 0; …
|
|
211
|
+
cardState.syncCSSVars({ prefix: '--card-' });
|
|
212
|
+
|
|
213
|
+
// Get @container-compatible query strings
|
|
214
|
+
const strings = cardState.getMediaQueries();
|
|
215
|
+
// { compact: '(max-width: 300px)', wide: '(min-width: 600px)' }
|
|
216
|
+
|
|
217
|
+
// Cleanup
|
|
218
|
+
cardState.destroy();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Supported condition types for ContainerState
|
|
222
|
+
|
|
223
|
+
`max-width`, `min-width`, `max-height`, `min-height`, `orientation`, `aspect-ratio`
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Subscription API
|
|
228
|
+
|
|
229
|
+
All methods below are available on both `ReactiveResponsiveState` and `ContainerState`.
|
|
230
|
+
|
|
231
|
+
### `subscribe(listener)` → unsubscribe
|
|
232
|
+
|
|
233
|
+
Fires immediately with the current state, then on every change. Affected by `debounce`.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const stop = state.subscribe((s) => {
|
|
237
|
+
document.body.dataset.bp = Object.keys(s).filter(k => s[k]).join(' ');
|
|
238
|
+
});
|
|
239
|
+
stop(); // unsubscribe
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### `on(key, callback)` → unsubscribe
|
|
243
|
+
|
|
244
|
+
Fires immediately with the current value for `key`, then on every change. **Never debounced.**
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const off = state.on('mobile', (matches) => {
|
|
248
|
+
header.classList.toggle('header--mobile', matches);
|
|
249
|
+
});
|
|
250
|
+
off();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `onEnter(key, callback)` → unsubscribe
|
|
254
|
+
|
|
255
|
+
Fires only on `false → true` transitions. Skips the initial value. **Never debounced.**
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
state.onEnter('mobile', () => initMobileMenu());
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `onLeave(key, callback)` → unsubscribe
|
|
262
|
+
|
|
263
|
+
Fires only on `true → false` transitions. Skips the initial value. **Never debounced.**
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
state.onLeave('mobile', () => destroyMobileMenu());
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `once(key, callback)` → unsubscribe
|
|
270
|
+
|
|
271
|
+
Fires on the **next change** to `key`, then auto-unsubscribes. Does not fire for the current value. **Never debounced.**
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
state.once('mobile', (matches) => {
|
|
275
|
+
console.log('mobile changed to:', matches);
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `onNextChange(callback)` → unsubscribe
|
|
280
|
+
|
|
281
|
+
Fires on the **next global state change**, then auto-unsubscribes. Affected by `debounce`.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
state.onNextChange((s) => console.log('first change:', s));
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `onBreakpointChange(callback)` → unsubscribe
|
|
288
|
+
|
|
289
|
+
Fires when the **active breakpoint** changes (i.e. `current` changes), providing `from` and `to`. Affected by `debounce`.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
state.onBreakpointChange((from, to) => {
|
|
293
|
+
console.log(`breakpoint: ${from} → ${to}`);
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### `waitFor(key, expectedValue?)` → Promise
|
|
298
|
+
|
|
299
|
+
Returns a `Promise` that resolves when `key` reaches `expectedValue` (default `true`). Resolves immediately if already met. **Never debounced.**
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
await state.waitFor('desktop');
|
|
303
|
+
initDesktopChart();
|
|
304
|
+
|
|
305
|
+
// Wait for mobile to become false
|
|
306
|
+
await state.waitFor('mobile', false);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Ordered breakpoint helpers
|
|
312
|
+
|
|
313
|
+
These helpers require a breakpoint `order` — either set via `setConfig` / `createResponsiveState` options, or derived from config key insertion order.
|
|
314
|
+
|
|
315
|
+
### `state.current` — getter
|
|
316
|
+
|
|
317
|
+
Returns the first active breakpoint key in order, or `null`.
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
if (state.current === 'mobile') showDrawer();
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### `state.isAbove(key)` → boolean
|
|
324
|
+
|
|
325
|
+
`true` when the current breakpoint comes **after** `key` in the order.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
// order: ['xs', 'sm', 'md', 'lg', 'xl']
|
|
329
|
+
// current = 'lg'
|
|
330
|
+
state.isAbove('sm') // → true
|
|
331
|
+
state.isAbove('xl') // → false
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### `state.isBelow(key)` → boolean
|
|
335
|
+
|
|
336
|
+
`true` when the current breakpoint comes **before** `key` in the order.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
state.isBelow('md') // → true (current = 'sm')
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### `state.between(from, to)` → boolean
|
|
343
|
+
|
|
344
|
+
`true` when the current breakpoint is between `from` and `to` (inclusive).
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
state.between('sm', 'lg') // → true (current = 'md')
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Utilities
|
|
353
|
+
|
|
354
|
+
### `syncCSSVars(options?)` → stop
|
|
355
|
+
|
|
356
|
+
Syncs all breakpoint keys to CSS custom properties (`1` / `0`) on `document.documentElement` (or a custom element). Automatically removes properties for keys removed during a config change.
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
const stop = state.syncCSSVars({ element: document.body, prefix: '--bp-' });
|
|
360
|
+
// → --bp-mobile: 1; --bp-desktop: 0; …
|
|
361
|
+
stop(); // cleanup
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Options:**
|
|
365
|
+
|
|
366
|
+
| Option | Default | Description |
|
|
367
|
+
|-----------|--------------------------|----------------------------------|
|
|
368
|
+
| `element` | `document.documentElement` | Target HTML element |
|
|
369
|
+
| `prefix` | `'--responsive-'` | CSS custom property name prefix |
|
|
370
|
+
|
|
371
|
+
### `emitDOMEvents(target?, options?)` → stop
|
|
372
|
+
|
|
373
|
+
Dispatches DOM `CustomEvent`s on `target` whenever breakpoints change:
|
|
374
|
+
|
|
375
|
+
- `responsive:change` — fires on any state change; `event.detail` is the full state snapshot
|
|
376
|
+
- `responsive:mobile:enter` — fires when `mobile` becomes `true`
|
|
377
|
+
- `responsive:mobile:leave` — fires when `mobile` becomes `false`
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const stop = state.emitDOMEvents(document, { prefix: 'bp:' });
|
|
381
|
+
|
|
382
|
+
document.addEventListener('bp:change', (e) => console.log(e.detail));
|
|
383
|
+
document.addEventListener('bp:mobile:enter', () => initDrawer());
|
|
384
|
+
document.addEventListener('bp:desktop:leave', () => destroyDesktopChart());
|
|
385
|
+
|
|
386
|
+
stop();
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Options:**
|
|
390
|
+
|
|
391
|
+
| Option | Default | Description |
|
|
392
|
+
|----------|-----------------|--------------------------|
|
|
393
|
+
| `prefix` | `'responsive:'` | Custom event name prefix |
|
|
394
|
+
|
|
395
|
+
### `toSignal(key, factory)` → Signal
|
|
396
|
+
|
|
397
|
+
Binds a breakpoint key to a writable signal from any signals library. The signal is kept in sync via `on()`.
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
// @preact/signals-core
|
|
401
|
+
import { signal } from '@preact/signals-core';
|
|
402
|
+
const isMobile = state.toSignal('mobile', signal);
|
|
403
|
+
isMobile.value; // reactive boolean
|
|
404
|
+
|
|
405
|
+
// Angular signal
|
|
406
|
+
import { signal } from '@angular/core';
|
|
407
|
+
const isMobile = state.toSignal('mobile', signal);
|
|
408
|
+
|
|
409
|
+
// Vue ref
|
|
410
|
+
import { ref } from 'vue';
|
|
411
|
+
const isMobile = state.toSignal('mobile', ref);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### `getMediaQueries()` → Record\<string, string\>
|
|
415
|
+
|
|
416
|
+
Returns the generated CSS media query strings for each breakpoint key.
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
const mq = state.getMediaQueries();
|
|
420
|
+
// { mobile: '(max-width: 600px)', desktop: '(min-width: 961px)' }
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### `getState<T>()` → T
|
|
424
|
+
|
|
425
|
+
Returns a stable snapshot of the current state. Same reference between changes — safe for React's `useSyncExternalStore`.
|
|
426
|
+
|
|
427
|
+
### `getOrder()` → string[]
|
|
428
|
+
|
|
429
|
+
Returns the configured breakpoint order array (or empty array if not set).
|
|
430
|
+
|
|
431
|
+
### `hydrate(initialState)` — SSR hydration
|
|
432
|
+
|
|
433
|
+
Sets initial state from a server-side snapshot to prevent layout shift. Only updates keys that exist in the current config.
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
// On the server, serialize state and pass to the client:
|
|
437
|
+
state.hydrate({ mobile: false, tablet: false, desktop: true });
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### `destroy()`
|
|
441
|
+
|
|
442
|
+
Removes all `matchMedia` / `ResizeObserver` listeners, clears all subscribers, and cancels any pending debounce timer.
|
|
443
|
+
|
|
444
|
+
### `toMediaQueryString(conditions)` — standalone utility
|
|
445
|
+
|
|
446
|
+
Converts a `MediaQueryConfig` to a CSS media query string. Useful for CSS-in-JS or debugging.
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
import { toMediaQueryString } from 'responsive-media';
|
|
450
|
+
|
|
451
|
+
toMediaQueryString([{ type: 'min-width', value: 768 }, { type: 'max-width', value: 1024 }])
|
|
452
|
+
// → "(min-width: 768px) and (max-width: 1024px)"
|
|
453
|
+
|
|
454
|
+
toMediaQueryString([[{ type: 'max-width', value: 600 }], [{ type: 'orientation', value: 'portrait' }]])
|
|
455
|
+
// → "(max-width: 600px), (orientation: portrait)"
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### `match(state, map, fallback?)` — standalone utility
|
|
459
|
+
|
|
460
|
+
Returns the first value in `map` whose key is `true` in `state`. Priority follows `map` insertion order.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import { match } from 'responsive-media';
|
|
464
|
+
import { responsiveState } from 'responsive-media';
|
|
465
|
+
|
|
466
|
+
const cols = match(responsiveState.proxy, { mobile: 1, tablet: 2, desktop: 4 });
|
|
467
|
+
const View = match(responsiveState.proxy, { mobile: MobileMenu, desktop: DesktopNav });
|
|
468
|
+
const label = match(responsiveState.proxy, { sm: 'Compact', lg: 'Full' }, 'Default');
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### `subscribeMediaQuery(query, callback)` — standalone utility
|
|
472
|
+
|
|
473
|
+
Low-level reactive wrapper around a single raw CSS media query string. Framework-agnostic — the Vue and React adapters use this internally.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
import { subscribeMediaQuery } from 'responsive-media';
|
|
477
|
+
|
|
478
|
+
const off = subscribeMediaQuery('(prefers-color-scheme: dark)', (matches) => {
|
|
479
|
+
document.body.classList.toggle('dark', matches);
|
|
480
|
+
});
|
|
481
|
+
off(); // cleanup
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Presets
|
|
487
|
+
|
|
488
|
+
Import from `responsive-media/presets` or from the main entry point.
|
|
489
|
+
|
|
490
|
+
### `ResponsiveConfig` (default)
|
|
491
|
+
|
|
492
|
+
| Key | Range |
|
|
493
|
+
|-----------|--------------|
|
|
494
|
+
| `mobile` | ≤ 600px |
|
|
495
|
+
| `tablet` | 601 – 960px |
|
|
496
|
+
| `desktop` | ≥ 961px |
|
|
497
|
+
|
|
498
|
+
### `TailwindPreset` + `TailwindOrder`
|
|
499
|
+
|
|
500
|
+
Mutually exclusive Tailwind CSS v3/v4 breakpoints:
|
|
501
|
+
|
|
502
|
+
| Key | Range |
|
|
503
|
+
|-------|----------------|
|
|
504
|
+
| `xs` | ≤ 639px |
|
|
505
|
+
| `sm` | 640 – 767px |
|
|
506
|
+
| `md` | 768 – 1023px |
|
|
507
|
+
| `lg` | 1024 – 1279px |
|
|
508
|
+
| `xl` | 1280 – 1535px |
|
|
509
|
+
| `2xl` | ≥ 1536px |
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media';
|
|
513
|
+
|
|
514
|
+
const state = createResponsiveState(TailwindPreset, { order: [...TailwindOrder] });
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### `BootstrapPreset` + `BootstrapOrder`
|
|
518
|
+
|
|
519
|
+
Mutually exclusive Bootstrap 5 breakpoints:
|
|
520
|
+
|
|
521
|
+
| Key | Range |
|
|
522
|
+
|-------|----------------|
|
|
523
|
+
| `xs` | ≤ 575px |
|
|
524
|
+
| `sm` | 576 – 767px |
|
|
525
|
+
| `md` | 768 – 991px |
|
|
526
|
+
| `lg` | 992 – 1199px |
|
|
527
|
+
| `xl` | 1200 – 1399px |
|
|
528
|
+
| `xxl` | ≥ 1400px |
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
import { createResponsiveState, BootstrapPreset, BootstrapOrder } from 'responsive-media';
|
|
532
|
+
|
|
533
|
+
const state = createResponsiveState(BootstrapPreset, { order: [...BootstrapOrder] });
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### `AccessibilityPreset`
|
|
537
|
+
|
|
538
|
+
User-preference media queries. Multiple keys can be `true` simultaneously.
|
|
539
|
+
|
|
540
|
+
| Key | Matches when … |
|
|
541
|
+
|-----------------|-----------------------------------------|
|
|
542
|
+
| `dark` | `prefers-color-scheme: dark` |
|
|
543
|
+
| `light` | `prefers-color-scheme: light` |
|
|
544
|
+
| `reducedMotion` | `prefers-reduced-motion: reduce` |
|
|
545
|
+
| `highContrast` | `prefers-contrast: more` |
|
|
546
|
+
| `lowContrast` | `prefers-contrast: less` |
|
|
547
|
+
| `noHover` | `hover: none` (touch / stylus devices) |
|
|
548
|
+
| `coarsePointer` | `pointer: coarse` (finger-sized input) |
|
|
549
|
+
| `forcedColors` | `forced-colors: active` (Windows HCM) |
|
|
550
|
+
| `print` | `print` media type |
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { createResponsiveState, AccessibilityPreset } from 'responsive-media';
|
|
554
|
+
|
|
555
|
+
const a11y = createResponsiveState(AccessibilityPreset);
|
|
556
|
+
|
|
557
|
+
a11y.onEnter('dark', () => applyDarkTheme());
|
|
558
|
+
a11y.onEnter('reducedMotion', () => disableAnimations());
|
|
559
|
+
a11y.onEnter('print', () => hideNonPrintable());
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Vue 3 integration
|
|
565
|
+
|
|
566
|
+
Import from `responsive-media` (main entry) or `responsive-media` directly — Vue composables are included in the main bundle.
|
|
567
|
+
|
|
568
|
+
### Plugin registration
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
import { createApp } from 'vue';
|
|
572
|
+
import { ResponsivePlugin } from 'responsive-media';
|
|
573
|
+
|
|
574
|
+
const app = createApp(App);
|
|
575
|
+
|
|
576
|
+
app.use(ResponsivePlugin, {
|
|
577
|
+
sm: [{ type: 'max-width', value: 767 }],
|
|
578
|
+
lg: [{ type: 'min-width', value: 1024 }],
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
app.mount('#app');
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### `useResponsive<T>()` — reactive state object
|
|
585
|
+
|
|
586
|
+
Returns the Vue reactive responsive state. Reactive in templates and computed properties.
|
|
587
|
+
|
|
588
|
+
```vue
|
|
589
|
+
<script setup lang="ts">
|
|
590
|
+
import { useResponsive } from 'responsive-media';
|
|
591
|
+
|
|
592
|
+
type MyState = { sm: boolean; lg: boolean };
|
|
593
|
+
const state = useResponsive<MyState>();
|
|
594
|
+
</script>
|
|
595
|
+
|
|
596
|
+
<template>
|
|
597
|
+
<MobileNav v-if="state.sm" />
|
|
598
|
+
<DesktopNav v-else />
|
|
599
|
+
</template>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### `useBreakpoints()` — ordered helpers
|
|
603
|
+
|
|
604
|
+
Returns reactive ordered breakpoint helpers. All methods react to viewport changes in templates.
|
|
605
|
+
|
|
606
|
+
```vue
|
|
607
|
+
<script setup>
|
|
608
|
+
import { useBreakpoints } from 'responsive-media';
|
|
609
|
+
|
|
610
|
+
const { current, isAbove, isBelow, between } = useBreakpoints();
|
|
611
|
+
</script>
|
|
612
|
+
|
|
613
|
+
<template>
|
|
614
|
+
<span>Current: {{ current }}</span>
|
|
615
|
+
<DesktopNav v-if="isAbove('sm')" />
|
|
616
|
+
<MobileNav v-else />
|
|
617
|
+
<TabletOnly v-if="between('sm', 'lg')" />
|
|
618
|
+
</template>
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
> `current` is a `ComputedRef<string | null>`. `isAbove`, `isBelow`, `between` are plain functions — reactive because they read from the Vue reactive state.
|
|
622
|
+
|
|
623
|
+
### `useMediaQuery(query)` — single raw query
|
|
624
|
+
|
|
625
|
+
Returns a `Ref<boolean>` for a raw CSS media query string. Cleans up automatically on `onUnmounted`.
|
|
626
|
+
|
|
627
|
+
```vue
|
|
628
|
+
<script setup>
|
|
629
|
+
import { useMediaQuery } from 'responsive-media';
|
|
630
|
+
|
|
631
|
+
const isDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
632
|
+
const canHover = useMediaQuery('(hover: hover)');
|
|
633
|
+
</script>
|
|
634
|
+
|
|
635
|
+
<template>
|
|
636
|
+
<DarkTheme v-if="isDark" />
|
|
637
|
+
</template>
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### `useContainerState(elementRef, config, options?)` — container queries
|
|
641
|
+
|
|
642
|
+
Tracks an element's dimensions and returns a reactive state object. Sets up and tears down the `ResizeObserver` automatically via `watchEffect`.
|
|
643
|
+
|
|
644
|
+
```vue
|
|
645
|
+
<script setup>
|
|
646
|
+
import { useTemplateRef } from 'vue';
|
|
647
|
+
import { useContainerState } from 'responsive-media';
|
|
648
|
+
|
|
649
|
+
const cardRef = useTemplateRef('card');
|
|
650
|
+
const cardState = useContainerState(cardRef, {
|
|
651
|
+
compact: [{ type: 'max-width', value: 300 }],
|
|
652
|
+
wide: [{ type: 'min-width', value: 600 }],
|
|
653
|
+
});
|
|
654
|
+
</script>
|
|
655
|
+
|
|
656
|
+
<template>
|
|
657
|
+
<div ref="card">
|
|
658
|
+
<CompactLayout v-if="cardState.compact" />
|
|
659
|
+
<WideLayout v-else-if="cardState.wide" />
|
|
660
|
+
<DefaultLayout v-else />
|
|
661
|
+
</div>
|
|
662
|
+
</template>
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## React 18+ integration
|
|
668
|
+
|
|
669
|
+
Import from `responsive-media/react`.
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
import { useResponsive, useBreakpoints, useMediaQuery, useContainerState } from 'responsive-media/react';
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### `useResponsive<T>()` — reactive state
|
|
676
|
+
|
|
677
|
+
Returns the current responsive state. Re-renders only when state changes. Uses `useSyncExternalStore` internally.
|
|
678
|
+
|
|
679
|
+
```tsx
|
|
680
|
+
import { useResponsive } from 'responsive-media/react';
|
|
681
|
+
|
|
682
|
+
type MyState = { sm: boolean; lg: boolean };
|
|
683
|
+
|
|
684
|
+
function App() {
|
|
685
|
+
const { sm, lg } = useResponsive<MyState>();
|
|
686
|
+
return sm ? <MobileNav /> : <DesktopNav />;
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### `useBreakpoints()` — ordered helpers
|
|
691
|
+
|
|
692
|
+
Returns ordered breakpoint helpers. Re-renders when the responsive state changes.
|
|
693
|
+
|
|
694
|
+
```tsx
|
|
695
|
+
import { useBreakpoints } from 'responsive-media/react';
|
|
696
|
+
|
|
697
|
+
function Nav() {
|
|
698
|
+
const { current, isAbove, isBelow, between } = useBreakpoints();
|
|
699
|
+
return (
|
|
700
|
+
<>
|
|
701
|
+
<span>Current: {current}</span>
|
|
702
|
+
{isAbove('sm') ? <DesktopNav /> : <MobileNav />}
|
|
703
|
+
{between('sm', 'lg') && <TabletBanner />}
|
|
704
|
+
</>
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
> Unlike Vue, `current` is a plain `string | null` (not a ref). Re-renders are triggered by `useSyncExternalStore`.
|
|
710
|
+
|
|
711
|
+
### `useMediaQuery(query)` — single raw query
|
|
712
|
+
|
|
713
|
+
Returns a `boolean` that tracks a raw CSS media query string. SSR-safe (returns `false` on the server).
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
import { useMediaQuery } from 'responsive-media/react';
|
|
717
|
+
|
|
718
|
+
function ThemeToggle() {
|
|
719
|
+
const isDark = useMediaQuery('(prefers-color-scheme: dark)');
|
|
720
|
+
const canHover = useMediaQuery('(hover: hover)');
|
|
721
|
+
return <button className={isDark ? 'dark' : 'light'}>Toggle</button>;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### `useContainerState(ref, config, options?)` — container queries
|
|
726
|
+
|
|
727
|
+
Tracks an element's dimensions and returns a state object. Sets up and tears down `ResizeObserver` via `useEffect`.
|
|
728
|
+
|
|
729
|
+
```tsx
|
|
730
|
+
import { useRef } from 'react';
|
|
731
|
+
import { useContainerState } from 'responsive-media/react';
|
|
732
|
+
|
|
733
|
+
function Card() {
|
|
734
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
735
|
+
const { compact, wide } = useContainerState(ref, {
|
|
736
|
+
compact: [{ type: 'max-width', value: 300 }],
|
|
737
|
+
wide: [{ type: 'min-width', value: 600 }],
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
return (
|
|
741
|
+
<div ref={ref}>
|
|
742
|
+
{compact ? <CompactLayout /> : wide ? <WideLayout /> : <DefaultLayout />}
|
|
743
|
+
</div>
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
> `config` and `options` are treated as static after mount. Wrap in `useMemo` if they change.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## TypeScript helpers
|
|
753
|
+
|
|
754
|
+
### `ConfigToState<T>`
|
|
755
|
+
|
|
756
|
+
Derives a boolean-state type from a config object:
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
import type { ConfigToState, MediaQueryConfig } from 'responsive-media';
|
|
760
|
+
|
|
761
|
+
const config = {
|
|
762
|
+
sm: [{ type: 'min-width', value: 640 }],
|
|
763
|
+
lg: [{ type: 'min-width', value: 1024 }],
|
|
764
|
+
} satisfies Record<string, MediaQueryConfig>;
|
|
765
|
+
|
|
766
|
+
type MyState = ConfigToState<typeof config>;
|
|
767
|
+
// → { sm: boolean; lg: boolean }
|
|
768
|
+
|
|
769
|
+
const { sm, lg } = responsiveState.getState<MyState>();
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Generic `useResponsive<T>()`
|
|
773
|
+
|
|
774
|
+
Both Vue and React adapters accept a generic type parameter to narrow the returned state:
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
type AppState = { mobile: boolean; tablet: boolean; desktop: boolean };
|
|
778
|
+
const state = useResponsive<AppState>();
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## SSR / hydration
|
|
784
|
+
|
|
785
|
+
All APIs are SSR-safe — they check for `window` and `matchMedia` availability before use and fall back to `false` on the server.
|
|
786
|
+
|
|
787
|
+
For hydration (preventing layout shift):
|
|
788
|
+
|
|
789
|
+
```ts
|
|
790
|
+
// Server: serialize the expected initial state
|
|
791
|
+
const initialState = { mobile: false, tablet: false, desktop: true };
|
|
792
|
+
|
|
793
|
+
// Client: hydrate before the first render
|
|
794
|
+
import { responsiveState } from 'responsive-media';
|
|
795
|
+
responsiveState.hydrate(initialState);
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Exported API reference
|
|
801
|
+
|
|
802
|
+
### Main entry (`responsive-media`)
|
|
803
|
+
|
|
804
|
+
| Export | Type | Description |
|
|
805
|
+
|---------------------------|--------------------------|-------------------------------------------------------|
|
|
806
|
+
| `responsiveState` | `ReactiveResponsiveState`| Global singleton, default `ResponsiveConfig` |
|
|
807
|
+
| `setResponsiveConfig` | function | Reconfigure the global singleton |
|
|
808
|
+
| `getResponsiveState` | function | Get state snapshot from global singleton |
|
|
809
|
+
| `getResponsiveMediaQueries` | function | Get CSS query strings from global singleton |
|
|
810
|
+
| `createResponsiveState` | function | Create an isolated `ReactiveResponsiveState` instance |
|
|
811
|
+
| `createContainerState` | function | Create a `ContainerState` for an element |
|
|
812
|
+
| `toMediaQueryString` | function | Convert `MediaQueryConfig` to CSS string |
|
|
813
|
+
| `match` | function | Pick a value by first matching breakpoint key |
|
|
814
|
+
| `subscribeMediaQuery` | function | Subscribe to a raw CSS media query string |
|
|
815
|
+
| `ResponsiveConfig` | const | Default mobile/tablet/desktop breakpoints |
|
|
816
|
+
| `BaseResponsiveState` | class | Abstract base (for extension) |
|
|
817
|
+
| `ReactiveResponsiveState` | class | Viewport state (matchMedia-backed) |
|
|
818
|
+
| `ContainerState` | class | Element container state (ResizeObserver-backed) |
|
|
819
|
+
| `ResponsivePlugin` | Vue plugin | Vue app plugin |
|
|
820
|
+
| `useResponsive` | Vue composable | Reactive state object |
|
|
821
|
+
| `useBreakpoints` | Vue composable | Ordered breakpoint helpers |
|
|
822
|
+
| `useMediaQuery` | Vue composable | Single raw media query |
|
|
823
|
+
| `useContainerState` | Vue composable | Element container queries |
|
|
824
|
+
| `ConfigToState` | type | Derives state type from config |
|
|
825
|
+
| `MediaQueryConfig` | type | Config entry type |
|
|
826
|
+
| `MediaQueryCondition` | type | Single condition type |
|
|
827
|
+
| `ResponsiveState` | type | `Record<string, boolean>` |
|
|
828
|
+
| `SetConfigOptions` | type | Options for `setConfig` / `createResponsiveState` |
|
|
829
|
+
| `BreakpointHelpers` | type | Return type of `useBreakpoints` |
|
|
830
|
+
|
|
831
|
+
### React entry (`responsive-media/react`)
|
|
832
|
+
|
|
833
|
+
| Export | Description |
|
|
834
|
+
|---------------------|----------------------------------------------|
|
|
835
|
+
| `useResponsive` | State hook (useSyncExternalStore) |
|
|
836
|
+
| `useBreakpoints` | Ordered breakpoint helpers hook |
|
|
837
|
+
| `useMediaQuery` | Single raw media query hook |
|
|
838
|
+
| `useContainerState` | Element container queries hook |
|
|
839
|
+
| `BreakpointHelpers` | Type for `useBreakpoints` return value |
|
|
840
|
+
|
|
841
|
+
### Presets entry (`responsive-media/presets`)
|
|
842
|
+
|
|
843
|
+
| Export | Description |
|
|
844
|
+
|----------------------|-----------------------------------------|
|
|
845
|
+
| `TailwindPreset` | Tailwind CSS v3/v4 breakpoints |
|
|
846
|
+
| `TailwindOrder` | Ordered key array for `TailwindPreset` |
|
|
847
|
+
| `BootstrapPreset` | Bootstrap 5 breakpoints |
|
|
848
|
+
| `BootstrapOrder` | Ordered key array for `BootstrapPreset` |
|
|
849
|
+
| `AccessibilityPreset`| User-preference media queries |
|
|
850
|
+
|
|
851
|
+
### Container entry (`responsive-media/container`)
|
|
852
|
+
|
|
853
|
+
| Export | Description |
|
|
854
|
+
|-----------------------|----------------------------------------------|
|
|
855
|
+
| `ContainerState` | Class for element container queries |
|
|
856
|
+
| `createContainerState`| Factory function |
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## License
|
|
861
|
+
|
|
862
|
+
MIT
|
|
863
|
+
|
|
864
|
+
## Author
|
|
865
|
+
|
|
866
|
+
Danil Lisin aka Macrulez
|
|
867
|
+
|
|
868
|
+
GitHub: [macrulezru](https://github.com/macrulezru) · Website: [macrulez.ru](https://macrulez.ru/)
|