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.
@@ -0,0 +1,107 @@
1
+ import { BaseResponsiveState } from './base-state';
2
+ import { buildMediaQuery } from './create-responsive';
3
+ import { hasResizeObserver } from './utils';
4
+ // ---------------------------------------------------------------------------
5
+ // Condition evaluator (JS-side, no matchMedia)
6
+ // ---------------------------------------------------------------------------
7
+ function evaluateSingle(cond, w, h) {
8
+ const val = typeof cond.value === 'number' ? cond.value : parseFloat(String(cond.value));
9
+ switch (cond.type) {
10
+ case 'max-width': return w <= val;
11
+ case 'min-width': return w >= val;
12
+ case 'max-height': return h <= val;
13
+ case 'min-height': return h >= val;
14
+ case 'orientation': return cond.value === 'landscape' ? w > h : w <= h;
15
+ case 'aspect-ratio': {
16
+ const [rw, rh] = String(cond.value).split('/').map(Number);
17
+ return rh ? Math.abs(w / h - rw / rh) < 0.01 : false;
18
+ }
19
+ default: return false;
20
+ }
21
+ }
22
+ function evaluateConditions(conditions, w, h) {
23
+ if (Array.isArray(conditions[0])) {
24
+ // OR mode — any group must fully match
25
+ return conditions.some(group => group.every(c => evaluateSingle(c, w, h)));
26
+ }
27
+ // AND mode — all conditions must match
28
+ return conditions.every(c => evaluateSingle(c, w, h));
29
+ }
30
+ // ---------------------------------------------------------------------------
31
+ // ContainerState — ResizeObserver-based
32
+ // ---------------------------------------------------------------------------
33
+ export class ContainerState extends BaseResponsiveState {
34
+ constructor(element, config, options) {
35
+ super();
36
+ this.observer = null;
37
+ this.configSnapshot = {};
38
+ this.element = element;
39
+ if ((options === null || options === void 0 ? void 0 : options.debounce) !== undefined)
40
+ this.debounceMs = options.debounce;
41
+ if ((options === null || options === void 0 ? void 0 : options.order) !== undefined)
42
+ this.order = options.order;
43
+ this.applyConfig(config);
44
+ }
45
+ setupSources(config) {
46
+ this.configSnapshot = config;
47
+ // Generate CSS @container-compatible strings for each breakpoint
48
+ Object.entries(config).forEach(([key, conditions]) => {
49
+ this.mediaQueries[key] = buildMediaQuery(conditions);
50
+ });
51
+ if (!hasResizeObserver()) {
52
+ // SSR or unsupported — default to false
53
+ Object.keys(config).forEach(key => { this.state[key] = false; });
54
+ return;
55
+ }
56
+ // Synchronous initial evaluation using the element's current size
57
+ const rect = this.element.getBoundingClientRect();
58
+ Object.entries(config).forEach(([key, conditions]) => {
59
+ this.state[key] = evaluateConditions(conditions, rect.width, rect.height);
60
+ });
61
+ this.observer = new ResizeObserver((entries) => {
62
+ const { width, height } = entries[0].contentRect;
63
+ Object.entries(this.configSnapshot).forEach(([key, conditions]) => {
64
+ this.proxy[key] = evaluateConditions(conditions, width, height);
65
+ });
66
+ });
67
+ this.observer.observe(this.element);
68
+ }
69
+ cleanupSources() {
70
+ if (this.observer) {
71
+ this.observer.disconnect();
72
+ this.observer = null;
73
+ }
74
+ this.configSnapshot = {};
75
+ }
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Factory
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Creates a `ContainerState` that tracks an element's dimensions via
82
+ * `ResizeObserver` and evaluates breakpoint conditions in JavaScript.
83
+ *
84
+ * The API is identical to `ReactiveResponsiveState` — all subscription
85
+ * methods (`subscribe`, `on`, `onEnter`, `onLeave`, `waitFor`, etc.) work
86
+ * the same way.
87
+ *
88
+ * The `getMediaQueries()` method returns CSS `@container` compatible strings
89
+ * that can be used in stylesheets alongside the JS reactive state.
90
+ *
91
+ * @example
92
+ * const card = document.querySelector('.card')!;
93
+ *
94
+ * const cardState = createContainerState(card, {
95
+ * compact: [{ type: 'max-width', value: 300 }],
96
+ * wide: [{ type: 'min-width', value: 600 }],
97
+ * });
98
+ *
99
+ * cardState.on('compact', (v) => card.classList.toggle('card--compact', v));
100
+ * cardState.syncCSSVars({ prefix: '--card-' });
101
+ *
102
+ * // Cleanup when no longer needed:
103
+ * cardState.destroy();
104
+ */
105
+ export function createContainerState(element, config, options) {
106
+ return new ContainerState(element, config, options);
107
+ }
@@ -1,20 +1,49 @@
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;
1
+ import { type MediaQueryConfig } from './responsive.enum';
2
+ import { BaseResponsiveState, type ResponsiveState, type SetConfigOptions } from './base-state';
3
+ export type { MediaQueryConfig, ResponsiveState, SetConfigOptions };
4
+ export { BaseResponsiveState };
5
+ export declare function buildMediaQuery(conditions: MediaQueryConfig): string;
6
+ export declare class ReactiveResponsiveState extends BaseResponsiveState {
9
7
  private queries;
10
- private mediaQueries;
11
- constructor();
12
- private applyConfig;
13
- getMediaQueries(): Record<string, string>;
14
- setConfig(config: Record<string, MediaQueryConfig>): void;
15
- subscribe(listener: ResponsiveListener): () => boolean;
16
- private notify;
8
+ private handlers;
9
+ constructor(config?: Record<string, MediaQueryConfig>, options?: SetConfigOptions);
10
+ protected setupSources(config: Record<string, MediaQueryConfig>): void;
11
+ protected cleanupSources(): void;
17
12
  }
18
- export declare function getResponsiveMediaQueries(): Record<string, string>;
19
13
  export declare const responsiveState: ReactiveResponsiveState;
20
- export declare function setResponsiveConfig(config: Record<string, MediaQueryConfig>): void;
14
+ /**
15
+ * Creates an isolated `ReactiveResponsiveState` instance — useful for tests,
16
+ * SSR (per-request instances), or multiple independent responsive contexts.
17
+ *
18
+ * @example
19
+ * const layoutState = createResponsiveState(TailwindPreset, {
20
+ * order: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'],
21
+ * });
22
+ * const themeState = createResponsiveState(AccessibilityPreset);
23
+ */
24
+ export declare function createResponsiveState(config?: Record<string, MediaQueryConfig>, options?: SetConfigOptions): ReactiveResponsiveState;
25
+ /**
26
+ * Converts a `MediaQueryConfig` array to a CSS media query string.
27
+ * Useful for CSS-in-JS, `@container` rules, or debugging.
28
+ *
29
+ * @example
30
+ * toMediaQueryString([{ type: 'min-width', value: 768 }, { type: 'max-width', value: 1024 }])
31
+ * // → "(min-width: 768px) and (max-width: 1024px)"
32
+ *
33
+ * toMediaQueryString([[{ type: 'max-width', value: 600 }], [{ type: 'orientation', value: 'portrait' }]])
34
+ * // → "(max-width: 600px), (orientation: portrait)"
35
+ */
36
+ export declare function toMediaQueryString(conditions: MediaQueryConfig): string;
37
+ export declare function getResponsiveState<T extends Record<string, boolean> = ResponsiveState>(): T;
38
+ export declare function getResponsiveMediaQueries(): Record<string, string>;
39
+ export declare function setResponsiveConfig(config: Record<string, MediaQueryConfig>, options?: SetConfigOptions): void;
40
+ /**
41
+ * Returns the first value in `map` whose key is `true` in `state`,
42
+ * or `fallback` if none match. Priority follows `map` insertion order.
43
+ *
44
+ * @example
45
+ * const cols = match(state, { mobile: 1, tablet: 2, desktop: 4 });
46
+ * const View = match(state, { mobile: MobileMenu, desktop: DesktopNav });
47
+ * const label = match(state, { sm: 'Compact', lg: 'Full' }, 'Default');
48
+ */
49
+ export declare function match<T>(state: Record<string, boolean>, map: Record<string, T>, fallback?: T): T | undefined;
@@ -1,69 +1,132 @@
1
1
  import { ResponsiveConfig } from './responsive.enum';
2
- class ReactiveResponsiveState {
3
- constructor() {
4
- this.listeners = new Set();
5
- this.queries = {};
6
- this.mediaQueries = {};
7
- this.state = {};
8
- this.proxy = new Proxy(this.state, {
9
- set: (target, prop, value) => {
10
- if (target[prop] !== value) {
11
- target[prop] = value;
12
- this.notify();
13
- }
14
- return true;
15
- },
16
- get: (target, prop) => {
17
- return target[prop];
18
- },
19
- });
20
- this.applyConfig(ResponsiveConfig);
2
+ import { BaseResponsiveState } from './base-state';
3
+ import { hasMatchMedia } from './utils';
4
+ export { BaseResponsiveState };
5
+ // ---------------------------------------------------------------------------
6
+ // Media query string builder (also used by ContainerState for @container strings)
7
+ // ---------------------------------------------------------------------------
8
+ const PIXEL_TYPES = new Set([
9
+ 'width', 'min-width', 'max-width',
10
+ 'height', 'min-height', 'max-height',
11
+ ]);
12
+ function formatValue(cond) {
13
+ if (typeof cond.value === 'number' && PIXEL_TYPES.has(cond.type)) {
14
+ return `${cond.value}px`;
21
15
  }
22
- applyConfig(config) {
23
- Object.entries(this.queries).forEach(([key, query]) => {
24
- query.onchange = null;
25
- });
16
+ return String(cond.value);
17
+ }
18
+ function conditionToString(cond) {
19
+ // 'raw' inserts the value verbatim (e.g. 'print', 'screen')
20
+ return cond.type === 'raw' ? String(cond.value) : `(${cond.type}: ${formatValue(cond)})`;
21
+ }
22
+ export function buildMediaQuery(conditions) {
23
+ if (conditions.length === 0)
24
+ return '';
25
+ if (Array.isArray(conditions[0])) {
26
+ return conditions
27
+ .map(group => group.map(conditionToString).join(' and '))
28
+ .join(', ');
29
+ }
30
+ return conditions.map(conditionToString).join(' and ');
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ // ReactiveResponsiveState — viewport-based (matchMedia)
34
+ // ---------------------------------------------------------------------------
35
+ export class ReactiveResponsiveState extends BaseResponsiveState {
36
+ constructor(config, options) {
37
+ super();
26
38
  this.queries = {};
27
- this.mediaQueries = {};
28
- Object.keys(this.state).forEach(key => delete this.state[key]);
39
+ this.handlers = {};
40
+ if ((options === null || options === void 0 ? void 0 : options.debounce) !== undefined)
41
+ this.debounceMs = options.debounce;
42
+ if ((options === null || options === void 0 ? void 0 : options.order) !== undefined)
43
+ this.order = options.order;
44
+ this.applyConfig(config !== null && config !== void 0 ? config : ResponsiveConfig);
45
+ }
46
+ setupSources(config) {
29
47
  Object.entries(config).forEach(([key, conditions]) => {
30
- const mq = conditions
31
- .map(cond => {
32
- const val = typeof cond.value === 'number' && !String(cond.type).includes('aspect-ratio')
33
- ? cond.value + 'px'
34
- : cond.value;
35
- return `(${cond.type}: ${val})`;
36
- })
37
- .join(' and ');
48
+ const mq = buildMediaQuery(conditions);
38
49
  this.mediaQueries[key] = mq;
50
+ if (!hasMatchMedia()) {
51
+ this.state[key] = false;
52
+ return;
53
+ }
39
54
  const query = window.matchMedia(mq);
40
55
  this.queries[key] = query;
41
- this.proxy[key] = query.matches;
42
- query.addEventListener('change', (e) => {
43
- this.proxy[key] = e.matches;
44
- });
56
+ const handler = (e) => { this.proxy[key] = e.matches; };
57
+ this.handlers[key] = handler;
58
+ query.addEventListener('change', handler);
59
+ this.state[key] = query.matches;
45
60
  });
46
- this.notify();
47
- }
48
- getMediaQueries() {
49
- return { ...this.mediaQueries };
50
- }
51
- setConfig(config) {
52
- this.applyConfig(config);
53
61
  }
54
- subscribe(listener) {
55
- this.listeners.add(listener);
56
- listener(this.proxy);
57
- return () => this.listeners.delete(listener);
58
- }
59
- notify() {
60
- this.listeners.forEach(listener => listener(this.proxy));
62
+ cleanupSources() {
63
+ Object.entries(this.handlers).forEach(([key, handler]) => {
64
+ var _a;
65
+ (_a = this.queries[key]) === null || _a === void 0 ? void 0 : _a.removeEventListener('change', handler);
66
+ });
67
+ this.queries = {};
68
+ this.handlers = {};
61
69
  }
62
70
  }
71
+ // ---------------------------------------------------------------------------
72
+ // Singleton
73
+ // ---------------------------------------------------------------------------
74
+ export const responsiveState = new ReactiveResponsiveState();
75
+ // ---------------------------------------------------------------------------
76
+ // Public factory
77
+ // ---------------------------------------------------------------------------
78
+ /**
79
+ * Creates an isolated `ReactiveResponsiveState` instance — useful for tests,
80
+ * SSR (per-request instances), or multiple independent responsive contexts.
81
+ *
82
+ * @example
83
+ * const layoutState = createResponsiveState(TailwindPreset, {
84
+ * order: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'],
85
+ * });
86
+ * const themeState = createResponsiveState(AccessibilityPreset);
87
+ */
88
+ export function createResponsiveState(config, options) {
89
+ return new ReactiveResponsiveState(config, options);
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Standalone helpers
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Converts a `MediaQueryConfig` array to a CSS media query string.
96
+ * Useful for CSS-in-JS, `@container` rules, or debugging.
97
+ *
98
+ * @example
99
+ * toMediaQueryString([{ type: 'min-width', value: 768 }, { type: 'max-width', value: 1024 }])
100
+ * // → "(min-width: 768px) and (max-width: 1024px)"
101
+ *
102
+ * toMediaQueryString([[{ type: 'max-width', value: 600 }], [{ type: 'orientation', value: 'portrait' }]])
103
+ * // → "(max-width: 600px), (orientation: portrait)"
104
+ */
105
+ export function toMediaQueryString(conditions) {
106
+ return buildMediaQuery(conditions);
107
+ }
108
+ export function getResponsiveState() {
109
+ return responsiveState.getState();
110
+ }
63
111
  export function getResponsiveMediaQueries() {
64
112
  return responsiveState.getMediaQueries();
65
113
  }
66
- export const responsiveState = new ReactiveResponsiveState();
67
- export function setResponsiveConfig(config) {
68
- responsiveState.setConfig(config);
114
+ export function setResponsiveConfig(config, options) {
115
+ responsiveState.setConfig(config, options);
116
+ }
117
+ /**
118
+ * Returns the first value in `map` whose key is `true` in `state`,
119
+ * or `fallback` if none match. Priority follows `map` insertion order.
120
+ *
121
+ * @example
122
+ * const cols = match(state, { mobile: 1, tablet: 2, desktop: 4 });
123
+ * const View = match(state, { mobile: MobileMenu, desktop: DesktopNav });
124
+ * const label = match(state, { sm: 'Compact', lg: 'Full' }, 'Default');
125
+ */
126
+ export function match(state, map, fallback) {
127
+ for (const [key, value] of Object.entries(map)) {
128
+ if (state[key])
129
+ return value;
130
+ }
131
+ return fallback;
69
132
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  export * from './responsive.enum';
2
+ export * from './base-state';
2
3
  export * from './create-responsive';
3
- export { ResponsivePlugin, useResponsive } from './vue-responsive';
4
+ export * from './container-state';
5
+ export * from './media-query';
6
+ export * from './presets';
7
+ export { ResponsivePlugin, useResponsive, useMediaQuery, useBreakpoints, useContainerState } from './vue-responsive';
8
+ export type { BreakpointHelpers } from './vue-responsive';
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
1
  export * from './responsive.enum';
2
+ export * from './base-state';
2
3
  export * from './create-responsive';
3
- export { ResponsivePlugin, useResponsive } from './vue-responsive';
4
+ export * from './container-state';
5
+ export * from './media-query';
6
+ export * from './presets';
7
+ export { ResponsivePlugin, useResponsive, useMediaQuery, useBreakpoints, useContainerState } from './vue-responsive';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Low-level reactive wrapper around a single raw CSS media query string.
3
+ * Framework-agnostic — Vue and React adapters live in their own files.
4
+ */
5
+ /**
6
+ * Subscribes to a raw CSS media query string. The callback is called immediately
7
+ * with the current match state and again whenever it changes.
8
+ * Returns an unsubscribe / cleanup function.
9
+ *
10
+ * @example
11
+ * const off = subscribeMediaQuery('(prefers-color-scheme: dark)', (matches) => {
12
+ * document.body.classList.toggle('dark', matches);
13
+ * });
14
+ * // Later:
15
+ * off();
16
+ */
17
+ export declare function subscribeMediaQuery(query: string, callback: (matches: boolean) => void): () => void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Low-level reactive wrapper around a single raw CSS media query string.
3
+ * Framework-agnostic — Vue and React adapters live in their own files.
4
+ */
5
+ function isSSR() {
6
+ return typeof window === 'undefined' || typeof window.matchMedia !== 'function';
7
+ }
8
+ /**
9
+ * Subscribes to a raw CSS media query string. The callback is called immediately
10
+ * with the current match state and again whenever it changes.
11
+ * Returns an unsubscribe / cleanup function.
12
+ *
13
+ * @example
14
+ * const off = subscribeMediaQuery('(prefers-color-scheme: dark)', (matches) => {
15
+ * document.body.classList.toggle('dark', matches);
16
+ * });
17
+ * // Later:
18
+ * off();
19
+ */
20
+ export function subscribeMediaQuery(query, callback) {
21
+ if (isSSR()) {
22
+ callback(false);
23
+ return () => { };
24
+ }
25
+ const mql = window.matchMedia(query);
26
+ const handler = (e) => callback(e.matches);
27
+ mql.addEventListener('change', handler);
28
+ callback(mql.matches);
29
+ return () => mql.removeEventListener('change', handler);
30
+ }
@@ -0,0 +1,48 @@
1
+ import type { MediaQueryConfig } from './responsive.enum';
2
+ /**
3
+ * Tailwind CSS v3/v4 breakpoints — mutually exclusive ranges.
4
+ *
5
+ * | Key | Range |
6
+ * |------|--------------------|
7
+ * | xs | ≤ 639px |
8
+ * | sm | 640 – 767px |
9
+ * | md | 768 – 1023px |
10
+ * | lg | 1024 – 1279px |
11
+ * | xl | 1280 – 1535px |
12
+ * | 2xl | ≥ 1536px |
13
+ */
14
+ export declare const TailwindPreset: Record<string, MediaQueryConfig>;
15
+ /** Ordered key array matching `TailwindPreset` — pass as `order` option. */
16
+ export declare const TailwindOrder: readonly ["xs", "sm", "md", "lg", "xl", "2xl"];
17
+ /**
18
+ * Bootstrap 5 breakpoints — mutually exclusive ranges.
19
+ *
20
+ * | Key | Range |
21
+ * |------|--------------------|
22
+ * | xs | ≤ 575px |
23
+ * | sm | 576 – 767px |
24
+ * | md | 768 – 991px |
25
+ * | lg | 992 – 1199px |
26
+ * | xl | 1200 – 1399px |
27
+ * | xxl | ≥ 1400px |
28
+ */
29
+ export declare const BootstrapPreset: Record<string, MediaQueryConfig>;
30
+ /** Ordered key array matching `BootstrapPreset` — pass as `order` option. */
31
+ export declare const BootstrapOrder: readonly ["xs", "sm", "md", "lg", "xl", "xxl"];
32
+ /**
33
+ * Accessibility & user-preference media queries.
34
+ * Keys are mutually independent — multiple can be `true` at the same time.
35
+ *
36
+ * | Key | Matches when … |
37
+ * |----------------|---------------------------------------------|
38
+ * | dark | `prefers-color-scheme: dark` |
39
+ * | light | `prefers-color-scheme: light` |
40
+ * | reducedMotion | `prefers-reduced-motion: reduce` |
41
+ * | highContrast | `prefers-contrast: more` |
42
+ * | lowContrast | `prefers-contrast: less` |
43
+ * | noHover | `hover: none` (touch / stylus devices) |
44
+ * | coarsePointer | `pointer: coarse` (finger-sized input) |
45
+ * | forcedColors | `forced-colors: active` (Windows HCM) |
46
+ * | print | `print` media type |
47
+ */
48
+ export declare const AccessibilityPreset: Record<string, MediaQueryConfig>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tailwind CSS v3/v4 breakpoints — mutually exclusive ranges.
3
+ *
4
+ * | Key | Range |
5
+ * |------|--------------------|
6
+ * | xs | ≤ 639px |
7
+ * | sm | 640 – 767px |
8
+ * | md | 768 – 1023px |
9
+ * | lg | 1024 – 1279px |
10
+ * | xl | 1280 – 1535px |
11
+ * | 2xl | ≥ 1536px |
12
+ */
13
+ export const TailwindPreset = {
14
+ xs: [{ type: 'max-width', value: 639 }],
15
+ sm: [{ type: 'min-width', value: 640 }, { type: 'max-width', value: 767 }],
16
+ md: [{ type: 'min-width', value: 768 }, { type: 'max-width', value: 1023 }],
17
+ lg: [{ type: 'min-width', value: 1024 }, { type: 'max-width', value: 1279 }],
18
+ xl: [{ type: 'min-width', value: 1280 }, { type: 'max-width', value: 1535 }],
19
+ '2xl': [{ type: 'min-width', value: 1536 }],
20
+ };
21
+ /** Ordered key array matching `TailwindPreset` — pass as `order` option. */
22
+ export const TailwindOrder = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
23
+ /**
24
+ * Bootstrap 5 breakpoints — mutually exclusive ranges.
25
+ *
26
+ * | Key | Range |
27
+ * |------|--------------------|
28
+ * | xs | ≤ 575px |
29
+ * | sm | 576 – 767px |
30
+ * | md | 768 – 991px |
31
+ * | lg | 992 – 1199px |
32
+ * | xl | 1200 – 1399px |
33
+ * | xxl | ≥ 1400px |
34
+ */
35
+ export const BootstrapPreset = {
36
+ xs: [{ type: 'max-width', value: 575 }],
37
+ sm: [{ type: 'min-width', value: 576 }, { type: 'max-width', value: 767 }],
38
+ md: [{ type: 'min-width', value: 768 }, { type: 'max-width', value: 991 }],
39
+ lg: [{ type: 'min-width', value: 992 }, { type: 'max-width', value: 1199 }],
40
+ xl: [{ type: 'min-width', value: 1200 }, { type: 'max-width', value: 1399 }],
41
+ xxl: [{ type: 'min-width', value: 1400 }],
42
+ };
43
+ /** Ordered key array matching `BootstrapPreset` — pass as `order` option. */
44
+ export const BootstrapOrder = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
45
+ /**
46
+ * Accessibility & user-preference media queries.
47
+ * Keys are mutually independent — multiple can be `true` at the same time.
48
+ *
49
+ * | Key | Matches when … |
50
+ * |----------------|---------------------------------------------|
51
+ * | dark | `prefers-color-scheme: dark` |
52
+ * | light | `prefers-color-scheme: light` |
53
+ * | reducedMotion | `prefers-reduced-motion: reduce` |
54
+ * | highContrast | `prefers-contrast: more` |
55
+ * | lowContrast | `prefers-contrast: less` |
56
+ * | noHover | `hover: none` (touch / stylus devices) |
57
+ * | coarsePointer | `pointer: coarse` (finger-sized input) |
58
+ * | forcedColors | `forced-colors: active` (Windows HCM) |
59
+ * | print | `print` media type |
60
+ */
61
+ export const AccessibilityPreset = {
62
+ dark: [{ type: 'prefers-color-scheme', value: 'dark' }],
63
+ light: [{ type: 'prefers-color-scheme', value: 'light' }],
64
+ reducedMotion: [{ type: 'prefers-reduced-motion', value: 'reduce' }],
65
+ highContrast: [{ type: 'prefers-contrast', value: 'more' }],
66
+ lowContrast: [{ type: 'prefers-contrast', value: 'less' }],
67
+ noHover: [{ type: 'hover', value: 'none' }],
68
+ coarsePointer: [{ type: 'pointer', value: 'coarse' }],
69
+ forcedColors: [{ type: 'forced-colors', value: 'active' }],
70
+ print: [{ type: 'raw', value: 'print' }],
71
+ };
@@ -0,0 +1,64 @@
1
+ import type { RefObject } from 'react';
2
+ import type { MediaQueryConfig, ResponsiveState, SetConfigOptions } from './create-responsive';
3
+ /**
4
+ * Returns the current responsive state. Re-renders only when state changes.
5
+ * Requires React 18+. Generic `T` narrows the type for custom configs.
6
+ *
7
+ * @example
8
+ * type MyState = { sm: boolean; lg: boolean };
9
+ * const { sm, lg } = useResponsive<MyState>();
10
+ */
11
+ export declare function useResponsive<T extends Record<string, boolean> = ResponsiveState>(): T;
12
+ export interface BreakpointHelpers {
13
+ /** First active breakpoint key, or `null`. */
14
+ current: string | null;
15
+ /** `true` when the current breakpoint is after `key` in the order. */
16
+ isAbove: (key: string) => boolean;
17
+ /** `true` when the current breakpoint is before `key` in the order. */
18
+ isBelow: (key: string) => boolean;
19
+ /** `true` when the current breakpoint is between `from` and `to` (inclusive). */
20
+ between: (from: string, to: string) => boolean;
21
+ }
22
+ /**
23
+ * Returns ordered breakpoint helpers. Re-renders when the responsive state
24
+ * changes, so all helpers always reflect the current breakpoint.
25
+ *
26
+ * Requires a breakpoint `order` set via `setResponsiveConfig` or
27
+ * `createResponsiveState`. Falls back to config key insertion order.
28
+ *
29
+ * @example
30
+ * const { current, isAbove, isBelow, between } = useBreakpoints();
31
+ * return isAbove('sm') ? <DesktopNav /> : <MobileNav />;
32
+ */
33
+ export declare function useBreakpoints(): BreakpointHelpers;
34
+ /**
35
+ * Returns a boolean that tracks a raw CSS media query string.
36
+ * Compatible with React 18+ SSR (returns `false` on the server).
37
+ *
38
+ * @example
39
+ * const isDark = useMediaQuery('(prefers-color-scheme: dark)');
40
+ * const canHover = useMediaQuery('(hover: hover)');
41
+ */
42
+ export declare function useMediaQuery(query: string): boolean;
43
+ /**
44
+ * Tracks an element's dimensions with `ResizeObserver` and evaluates
45
+ * breakpoint conditions in JavaScript (Container Queries).
46
+ *
47
+ * Pass a `ref` created with `useRef<Element>(null)` — the hook sets up the
48
+ * observer after mount and cleans up on unmount.
49
+ *
50
+ * @example
51
+ * function Card() {
52
+ * const ref = useRef<HTMLDivElement>(null);
53
+ * const { compact, wide } = useContainerState(ref, {
54
+ * compact: [{ type: 'max-width', value: 300 }],
55
+ * wide: [{ type: 'min-width', value: 600 }],
56
+ * });
57
+ * return (
58
+ * <div ref={ref}>
59
+ * {compact ? <CompactLayout /> : wide ? <WideLayout /> : <DefaultLayout />}
60
+ * </div>
61
+ * );
62
+ * }
63
+ */
64
+ export declare function useContainerState(ref: RefObject<Element | null>, config: Record<string, MediaQueryConfig>, options?: SetConfigOptions): Record<string, boolean>;