responsive-media 1.0.7 → 1.2.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.
@@ -0,0 +1,182 @@
1
+ import type { MediaQueryConfig } from './responsive.enum';
2
+ export type ResponsiveState = Record<string, boolean>;
3
+ export type ResponsiveListener = (state: ResponsiveState) => void;
4
+ export interface SetConfigOptions {
5
+ /**
6
+ * Debounce delay in milliseconds for `subscribe` listeners.
7
+ * `on()`, `onEnter()`, `onLeave()`, `once()`, and `waitFor()` are never debounced.
8
+ * Pass `0` to disable (default).
9
+ */
10
+ debounce?: number;
11
+ /**
12
+ * Explicit breakpoint order for `isAbove()`, `isBelow()`, and `between()`.
13
+ * If omitted, config key insertion order is used.
14
+ * @example ['xs', 'sm', 'md', 'lg', 'xl', '2xl']
15
+ */
16
+ order?: string[];
17
+ }
18
+ export interface SyncCSSVarsOptions {
19
+ /** Target element. Defaults to `document.documentElement`. */
20
+ element?: HTMLElement;
21
+ /** CSS custom property prefix. Defaults to `'--responsive-'`. */
22
+ prefix?: string;
23
+ }
24
+ export interface EmitDOMEventsOptions {
25
+ /** Custom event prefix. Defaults to `'responsive:'`. */
26
+ prefix?: string;
27
+ }
28
+ export interface WritableSignal<T> {
29
+ value: T;
30
+ }
31
+ export type SignalFactory<T> = (initialValue: T) => WritableSignal<T>;
32
+ export declare abstract class BaseResponsiveState {
33
+ protected state: ResponsiveState;
34
+ protected listeners: Set<ResponsiveListener>;
35
+ protected keyListeners: Map<string, Set<(matches: boolean) => void>>;
36
+ proxy: ResponsiveState;
37
+ protected mediaQueries: Record<string, string>;
38
+ protected snapshot: ResponsiveState;
39
+ protected batching: boolean;
40
+ protected pendingKeyChanges: Map<string, boolean>;
41
+ protected debounceMs: number;
42
+ protected debounceTimer: ReturnType<typeof setTimeout> | null;
43
+ protected order: string[];
44
+ constructor();
45
+ /** Subclass implements the source-specific setup (matchMedia / ResizeObserver). */
46
+ protected abstract setupSources(config: Record<string, MediaQueryConfig>): void;
47
+ /** Subclass cleans up source listeners (matchMedia handlers / ResizeObserver). */
48
+ protected abstract cleanupSources(): void;
49
+ protected beginApplyConfig(): void;
50
+ protected clearStateKeys(newConfig: Record<string, MediaQueryConfig>): void;
51
+ protected endApplyConfig(): void;
52
+ protected applyConfig(config: Record<string, MediaQueryConfig>): void;
53
+ protected notifyKey(key: string, value: boolean): void;
54
+ protected flushNotify(): void;
55
+ protected notify(): void;
56
+ /**
57
+ * Returns a stable snapshot of the current state.
58
+ * Same reference between changes — safe for React's `useSyncExternalStore`.
59
+ */
60
+ getState<T extends Record<string, boolean> = ResponsiveState>(): T;
61
+ /** Returns the CSS media query strings for each breakpoint key. */
62
+ getMediaQueries(): Record<string, string>;
63
+ /** Returns the configured breakpoint order (or empty array if not set). */
64
+ getOrder(): string[];
65
+ private effectiveOrder;
66
+ /**
67
+ * The first active breakpoint key, or `null`.
68
+ * Reads live state — not affected by debounce.
69
+ *
70
+ * @example
71
+ * if (state.current === 'mobile') { ... }
72
+ */
73
+ get current(): string | null;
74
+ /**
75
+ * Returns `true` when the current breakpoint comes **after** `key` in the order.
76
+ * Requires an `order` option or relies on config key insertion order.
77
+ *
78
+ * @example
79
+ * // current = 'lg', order = ['xs','sm','md','lg','xl']
80
+ * state.isAbove('sm') // → true
81
+ */
82
+ isAbove(key: string): boolean;
83
+ /**
84
+ * Returns `true` when the current breakpoint comes **before** `key` in the order.
85
+ */
86
+ isBelow(key: string): boolean;
87
+ /**
88
+ * Returns `true` when the current breakpoint is between `from` and `to` (inclusive).
89
+ */
90
+ between(from: string, to: string): boolean;
91
+ setConfig(config: Record<string, MediaQueryConfig>, options?: SetConfigOptions): void;
92
+ /** Subscribes to all state changes. Fires immediately with current state. Affected by `debounce`. */
93
+ subscribe(listener: ResponsiveListener): () => void;
94
+ /** Subscribes to a single key. Fires immediately. Never debounced. */
95
+ on(key: string, callback: (matches: boolean) => void): () => void;
96
+ /**
97
+ * Fires `callback` only on `false → true` transitions. Skips initial state.
98
+ * Never debounced.
99
+ */
100
+ onEnter(key: string, callback: () => void): () => void;
101
+ /**
102
+ * Fires `callback` only on `true → false` transitions. Skips initial state.
103
+ * Never debounced.
104
+ */
105
+ onLeave(key: string, callback: () => void): () => void;
106
+ /**
107
+ * Fires `callback` on the **next change** to `key`, then auto-unsubscribes.
108
+ * Does NOT fire for the current value. Never debounced.
109
+ *
110
+ * @example
111
+ * state.once('mobile', (matches) => console.log('mobile changed to', matches));
112
+ */
113
+ once(key: string, callback: (matches: boolean) => void): () => void;
114
+ /**
115
+ * Fires `callback` on the **next global state change**, then auto-unsubscribes.
116
+ * Affected by `debounce`.
117
+ *
118
+ * @example
119
+ * state.onNextChange((s) => console.log('first change:', s));
120
+ */
121
+ onNextChange(callback: (state: ResponsiveState) => void): () => void;
122
+ /**
123
+ * Fires whenever the **active breakpoint** changes (i.e. `current` changes).
124
+ * Provides `from` and `to` for transition-aware logic. Affected by `debounce`.
125
+ */
126
+ onBreakpointChange(callback: (from: string | null, to: string | null) => void): () => void;
127
+ /**
128
+ * Returns a Promise that resolves when `key` reaches `expectedValue`.
129
+ * Resolves immediately if condition is already met. Never debounced.
130
+ */
131
+ waitFor(key: string, expectedValue?: boolean): Promise<void>;
132
+ /**
133
+ * Syncs breakpoint state to CSS custom properties (`1` / `0`).
134
+ * Removes properties for keys deleted by a config change.
135
+ * Returns a stop / cleanup function.
136
+ *
137
+ * @example
138
+ * const stop = state.syncCSSVars({ prefix: '--bp-' });
139
+ * // → --bp-mobile: 1; --bp-desktop: 0; …
140
+ */
141
+ syncCSSVars(options?: SyncCSSVarsOptions): () => void;
142
+ /**
143
+ * Sets initial state from a server-side snapshot to prevent SSR layout shift.
144
+ * Only updates keys that exist in the current config.
145
+ */
146
+ hydrate(initialState: Record<string, boolean>): void;
147
+ /**
148
+ * Binds a breakpoint key to a writable signal from any signals library
149
+ * (`@preact/signals-core`, Angular `signal()`, Vue `ref()`, etc.).
150
+ * The signal is kept in sync via `on()`.
151
+ *
152
+ * @example
153
+ * import { signal } from '@preact/signals-core';
154
+ * const mobile = state.toSignal('mobile', signal);
155
+ * mobile.value; // reactive boolean
156
+ *
157
+ * @example
158
+ * // Vue ref
159
+ * import { ref } from 'vue';
160
+ * const mobile = state.toSignal('mobile', ref);
161
+ */
162
+ toSignal<T extends WritableSignal<boolean>>(key: string, factory: SignalFactory<boolean>): T;
163
+ /**
164
+ * Dispatches DOM `CustomEvent`s on `target` whenever breakpoints change.
165
+ *
166
+ * Events fired:
167
+ * - `responsive:change` — on any state change (`detail` = full state snapshot)
168
+ * - `responsive:mobile:enter` / `responsive:mobile:leave` — per-key transitions
169
+ *
170
+ * Returns a cleanup / stop function.
171
+ *
172
+ * @example
173
+ * const stop = state.emitDOMEvents(document, { prefix: 'bp:' });
174
+ * document.addEventListener('bp:mobile:enter', () => initDrawer());
175
+ */
176
+ emitDOMEvents(target?: EventTarget, options?: EmitDOMEventsOptions): () => void;
177
+ /**
178
+ * Removes all `matchMedia` / `ResizeObserver` listeners, clears all
179
+ * subscribers, and cancels any pending debounce timer.
180
+ */
181
+ destroy(): void;
182
+ }
@@ -0,0 +1,406 @@
1
+ import { isSSR } from './utils';
2
+ // ---------------------------------------------------------------------------
3
+ // Abstract base — shared between ReactiveResponsiveState and ContainerState
4
+ // ---------------------------------------------------------------------------
5
+ export class BaseResponsiveState {
6
+ constructor() {
7
+ this.state = {};
8
+ this.listeners = new Set();
9
+ this.keyListeners = new Map();
10
+ this.mediaQueries = {};
11
+ this.snapshot = {};
12
+ this.batching = false;
13
+ this.pendingKeyChanges = new Map();
14
+ this.debounceMs = 0;
15
+ this.debounceTimer = null;
16
+ this.order = [];
17
+ this.proxy = new Proxy(this.state, {
18
+ set: (target, prop, value) => {
19
+ if (target[prop] !== value) {
20
+ target[prop] = value;
21
+ if (this.batching) {
22
+ this.pendingKeyChanges.set(prop, value);
23
+ }
24
+ else {
25
+ this.notifyKey(prop, value);
26
+ this.notify();
27
+ }
28
+ }
29
+ return true;
30
+ },
31
+ get: (target, prop) => target[prop],
32
+ });
33
+ }
34
+ beginApplyConfig() {
35
+ this.batching = true;
36
+ if (this.debounceTimer !== null) {
37
+ clearTimeout(this.debounceTimer);
38
+ this.debounceTimer = null;
39
+ }
40
+ this.mediaQueries = {};
41
+ }
42
+ clearStateKeys(newConfig) {
43
+ Object.keys(this.state).forEach(key => {
44
+ if (!(key in newConfig))
45
+ this.notifyKey(key, false);
46
+ delete this.state[key];
47
+ });
48
+ }
49
+ endApplyConfig() {
50
+ this.batching = false;
51
+ this.pendingKeyChanges.forEach((v, k) => this.notifyKey(k, v));
52
+ this.pendingKeyChanges.clear();
53
+ this.flushNotify();
54
+ }
55
+ applyConfig(config) {
56
+ this.beginApplyConfig();
57
+ this.cleanupSources();
58
+ this.clearStateKeys(config);
59
+ this.setupSources(config);
60
+ this.endApplyConfig();
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Notification helpers
64
+ // ---------------------------------------------------------------------------
65
+ notifyKey(key, value) {
66
+ var _a;
67
+ (_a = this.keyListeners.get(key)) === null || _a === void 0 ? void 0 : _a.forEach(cb => cb(value));
68
+ }
69
+ flushNotify() {
70
+ this.snapshot = { ...this.state };
71
+ this.listeners.forEach(l => l(this.snapshot));
72
+ }
73
+ notify() {
74
+ if (this.debounceMs > 0) {
75
+ if (this.debounceTimer !== null)
76
+ clearTimeout(this.debounceTimer);
77
+ this.debounceTimer = setTimeout(() => {
78
+ this.debounceTimer = null;
79
+ this.flushNotify();
80
+ }, this.debounceMs);
81
+ }
82
+ else {
83
+ this.flushNotify();
84
+ }
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Public read API
88
+ // ---------------------------------------------------------------------------
89
+ /**
90
+ * Returns a stable snapshot of the current state.
91
+ * Same reference between changes — safe for React's `useSyncExternalStore`.
92
+ */
93
+ getState() {
94
+ return this.snapshot;
95
+ }
96
+ /** Returns the CSS media query strings for each breakpoint key. */
97
+ getMediaQueries() {
98
+ return { ...this.mediaQueries };
99
+ }
100
+ /** Returns the configured breakpoint order (or empty array if not set). */
101
+ getOrder() {
102
+ return [...this.order];
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Ordered breakpoint helpers
106
+ // ---------------------------------------------------------------------------
107
+ effectiveOrder() {
108
+ return this.order.length ? this.order : Object.keys(this.state);
109
+ }
110
+ /**
111
+ * The first active breakpoint key, or `null`.
112
+ * Reads live state — not affected by debounce.
113
+ *
114
+ * @example
115
+ * if (state.current === 'mobile') { ... }
116
+ */
117
+ get current() {
118
+ const ord = this.effectiveOrder();
119
+ for (const key of ord) {
120
+ if (this.state[key])
121
+ return key;
122
+ }
123
+ return null;
124
+ }
125
+ /**
126
+ * Returns `true` when the current breakpoint comes **after** `key` in the order.
127
+ * Requires an `order` option or relies on config key insertion order.
128
+ *
129
+ * @example
130
+ * // current = 'lg', order = ['xs','sm','md','lg','xl']
131
+ * state.isAbove('sm') // → true
132
+ */
133
+ isAbove(key) {
134
+ const ord = this.effectiveOrder();
135
+ const cur = this.current;
136
+ return ord.indexOf(cur !== null && cur !== void 0 ? cur : '') > ord.indexOf(key);
137
+ }
138
+ /**
139
+ * Returns `true` when the current breakpoint comes **before** `key` in the order.
140
+ */
141
+ isBelow(key) {
142
+ const ord = this.effectiveOrder();
143
+ const cur = this.current;
144
+ const curIdx = ord.indexOf(cur !== null && cur !== void 0 ? cur : '');
145
+ const keyIdx = ord.indexOf(key);
146
+ return curIdx !== -1 && curIdx < keyIdx;
147
+ }
148
+ /**
149
+ * Returns `true` when the current breakpoint is between `from` and `to` (inclusive).
150
+ */
151
+ between(from, to) {
152
+ var _a;
153
+ const ord = this.effectiveOrder();
154
+ const idx = ord.indexOf((_a = this.current) !== null && _a !== void 0 ? _a : '');
155
+ return idx !== -1 && idx >= ord.indexOf(from) && idx <= ord.indexOf(to);
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Configuration
159
+ // ---------------------------------------------------------------------------
160
+ setConfig(config, options) {
161
+ if ((options === null || options === void 0 ? void 0 : options.debounce) !== undefined)
162
+ this.debounceMs = options.debounce;
163
+ if ((options === null || options === void 0 ? void 0 : options.order) !== undefined)
164
+ this.order = options.order;
165
+ this.applyConfig(config);
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Subscription API
169
+ // ---------------------------------------------------------------------------
170
+ /** Subscribes to all state changes. Fires immediately with current state. Affected by `debounce`. */
171
+ subscribe(listener) {
172
+ this.listeners.add(listener);
173
+ listener(this.snapshot);
174
+ return () => this.listeners.delete(listener);
175
+ }
176
+ /** Subscribes to a single key. Fires immediately. Never debounced. */
177
+ on(key, callback) {
178
+ var _a;
179
+ if (!this.keyListeners.has(key))
180
+ this.keyListeners.set(key, new Set());
181
+ const set = this.keyListeners.get(key);
182
+ set.add(callback);
183
+ callback((_a = this.state[key]) !== null && _a !== void 0 ? _a : false);
184
+ return () => set.delete(callback);
185
+ }
186
+ /**
187
+ * Fires `callback` only on `false → true` transitions. Skips initial state.
188
+ * Never debounced.
189
+ */
190
+ onEnter(key, callback) {
191
+ var _a;
192
+ let prev = (_a = this.state[key]) !== null && _a !== void 0 ? _a : false;
193
+ return this.on(key, (matches) => {
194
+ const changed = prev !== matches;
195
+ prev = matches;
196
+ if (changed && matches)
197
+ callback();
198
+ });
199
+ }
200
+ /**
201
+ * Fires `callback` only on `true → false` transitions. Skips initial state.
202
+ * Never debounced.
203
+ */
204
+ onLeave(key, callback) {
205
+ var _a;
206
+ let prev = (_a = this.state[key]) !== null && _a !== void 0 ? _a : false;
207
+ return this.on(key, (matches) => {
208
+ const changed = prev !== matches;
209
+ prev = matches;
210
+ if (changed && !matches)
211
+ callback();
212
+ });
213
+ }
214
+ /**
215
+ * Fires `callback` on the **next change** to `key`, then auto-unsubscribes.
216
+ * Does NOT fire for the current value. Never debounced.
217
+ *
218
+ * @example
219
+ * state.once('mobile', (matches) => console.log('mobile changed to', matches));
220
+ */
221
+ once(key, callback) {
222
+ let cleanup = () => { };
223
+ let skippedInit = false;
224
+ cleanup = this.on(key, (matches) => {
225
+ if (!skippedInit) {
226
+ skippedInit = true;
227
+ return;
228
+ }
229
+ cleanup();
230
+ callback(matches);
231
+ });
232
+ return cleanup;
233
+ }
234
+ /**
235
+ * Fires `callback` on the **next global state change**, then auto-unsubscribes.
236
+ * Affected by `debounce`.
237
+ *
238
+ * @example
239
+ * state.onNextChange((s) => console.log('first change:', s));
240
+ */
241
+ onNextChange(callback) {
242
+ let cleanup = () => { };
243
+ let skippedInit = false;
244
+ cleanup = this.subscribe((s) => {
245
+ if (!skippedInit) {
246
+ skippedInit = true;
247
+ return;
248
+ }
249
+ cleanup();
250
+ callback(s);
251
+ });
252
+ return cleanup;
253
+ }
254
+ /**
255
+ * Fires whenever the **active breakpoint** changes (i.e. `current` changes).
256
+ * Provides `from` and `to` for transition-aware logic. Affected by `debounce`.
257
+ */
258
+ onBreakpointChange(callback) {
259
+ let prev = this.current;
260
+ return this.subscribe(() => {
261
+ const next = this.current;
262
+ if (prev !== next) {
263
+ callback(prev, next);
264
+ prev = next;
265
+ }
266
+ });
267
+ }
268
+ /**
269
+ * Returns a Promise that resolves when `key` reaches `expectedValue`.
270
+ * Resolves immediately if condition is already met. Never debounced.
271
+ */
272
+ waitFor(key, expectedValue = true) {
273
+ var _a;
274
+ if (((_a = this.state[key]) !== null && _a !== void 0 ? _a : false) === expectedValue)
275
+ return Promise.resolve();
276
+ return new Promise((resolve) => {
277
+ const off = this.on(key, (matches) => {
278
+ if (matches === expectedValue) {
279
+ off();
280
+ resolve();
281
+ }
282
+ });
283
+ });
284
+ }
285
+ // ---------------------------------------------------------------------------
286
+ // Utilities
287
+ // ---------------------------------------------------------------------------
288
+ /**
289
+ * Syncs breakpoint state to CSS custom properties (`1` / `0`).
290
+ * Removes properties for keys deleted by a config change.
291
+ * Returns a stop / cleanup function.
292
+ *
293
+ * @example
294
+ * const stop = state.syncCSSVars({ prefix: '--bp-' });
295
+ * // → --bp-mobile: 1; --bp-desktop: 0; …
296
+ */
297
+ syncCSSVars(options) {
298
+ var _a, _b;
299
+ if (isSSR())
300
+ return () => { };
301
+ const el = (_a = options === null || options === void 0 ? void 0 : options.element) !== null && _a !== void 0 ? _a : document.documentElement;
302
+ const prefix = (_b = options === null || options === void 0 ? void 0 : options.prefix) !== null && _b !== void 0 ? _b : '--responsive-';
303
+ let prevKeys = new Set();
304
+ return this.subscribe((s) => {
305
+ const cur = new Set(Object.keys(s));
306
+ prevKeys.forEach(k => { if (!cur.has(k))
307
+ el.style.removeProperty(`${prefix}${k}`); });
308
+ Object.entries(s).forEach(([k, v]) => el.style.setProperty(`${prefix}${k}`, v ? '1' : '0'));
309
+ prevKeys = cur;
310
+ });
311
+ }
312
+ /**
313
+ * Sets initial state from a server-side snapshot to prevent SSR layout shift.
314
+ * Only updates keys that exist in the current config.
315
+ */
316
+ hydrate(initialState) {
317
+ let changed = false;
318
+ Object.entries(initialState).forEach(([key, value]) => {
319
+ if (key in this.state && this.state[key] !== value) {
320
+ this.state[key] = value;
321
+ this.notifyKey(key, value);
322
+ changed = true;
323
+ }
324
+ });
325
+ if (changed)
326
+ this.flushNotify();
327
+ }
328
+ /**
329
+ * Binds a breakpoint key to a writable signal from any signals library
330
+ * (`@preact/signals-core`, Angular `signal()`, Vue `ref()`, etc.).
331
+ * The signal is kept in sync via `on()`.
332
+ *
333
+ * @example
334
+ * import { signal } from '@preact/signals-core';
335
+ * const mobile = state.toSignal('mobile', signal);
336
+ * mobile.value; // reactive boolean
337
+ *
338
+ * @example
339
+ * // Vue ref
340
+ * import { ref } from 'vue';
341
+ * const mobile = state.toSignal('mobile', ref);
342
+ */
343
+ toSignal(key, factory) {
344
+ var _a;
345
+ const sig = factory((_a = this.state[key]) !== null && _a !== void 0 ? _a : false);
346
+ this.on(key, (v) => { sig.value = v; });
347
+ return sig;
348
+ }
349
+ /**
350
+ * Dispatches DOM `CustomEvent`s on `target` whenever breakpoints change.
351
+ *
352
+ * Events fired:
353
+ * - `responsive:change` — on any state change (`detail` = full state snapshot)
354
+ * - `responsive:mobile:enter` / `responsive:mobile:leave` — per-key transitions
355
+ *
356
+ * Returns a cleanup / stop function.
357
+ *
358
+ * @example
359
+ * const stop = state.emitDOMEvents(document, { prefix: 'bp:' });
360
+ * document.addEventListener('bp:mobile:enter', () => initDrawer());
361
+ */
362
+ emitDOMEvents(target = document, options) {
363
+ var _a;
364
+ if (isSSR())
365
+ return () => { };
366
+ const prefix = (_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : 'responsive:';
367
+ let prev = {};
368
+ let initialized = false;
369
+ return this.subscribe((state) => {
370
+ if (!initialized) {
371
+ initialized = true;
372
+ prev = { ...state };
373
+ return;
374
+ }
375
+ target.dispatchEvent(new CustomEvent(`${prefix}change`, {
376
+ detail: { ...state },
377
+ bubbles: true,
378
+ }));
379
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(state)]);
380
+ allKeys.forEach(key => {
381
+ const was = prev[key] === true;
382
+ const now = state[key] === true;
383
+ if (!was && now) {
384
+ target.dispatchEvent(new CustomEvent(`${prefix}${key}:enter`, { bubbles: true }));
385
+ }
386
+ else if (was && !now) {
387
+ target.dispatchEvent(new CustomEvent(`${prefix}${key}:leave`, { bubbles: true }));
388
+ }
389
+ });
390
+ prev = { ...state };
391
+ });
392
+ }
393
+ /**
394
+ * Removes all `matchMedia` / `ResizeObserver` listeners, clears all
395
+ * subscribers, and cancels any pending debounce timer.
396
+ */
397
+ destroy() {
398
+ if (this.debounceTimer !== null) {
399
+ clearTimeout(this.debounceTimer);
400
+ this.debounceTimer = null;
401
+ }
402
+ this.cleanupSources();
403
+ this.listeners.clear();
404
+ this.keyListeners.clear();
405
+ }
406
+ }
@@ -0,0 +1,36 @@
1
+ import type { MediaQueryConfig } from './responsive.enum';
2
+ import { BaseResponsiveState, type SetConfigOptions } from './base-state';
3
+ export declare class ContainerState extends BaseResponsiveState {
4
+ private element;
5
+ private observer;
6
+ private configSnapshot;
7
+ constructor(element: Element, config: Record<string, MediaQueryConfig>, options?: SetConfigOptions);
8
+ protected setupSources(config: Record<string, MediaQueryConfig>): void;
9
+ protected cleanupSources(): void;
10
+ }
11
+ /**
12
+ * Creates a `ContainerState` that tracks an element's dimensions via
13
+ * `ResizeObserver` and evaluates breakpoint conditions in JavaScript.
14
+ *
15
+ * The API is identical to `ReactiveResponsiveState` — all subscription
16
+ * methods (`subscribe`, `on`, `onEnter`, `onLeave`, `waitFor`, etc.) work
17
+ * the same way.
18
+ *
19
+ * The `getMediaQueries()` method returns CSS `@container` compatible strings
20
+ * that can be used in stylesheets alongside the JS reactive state.
21
+ *
22
+ * @example
23
+ * const card = document.querySelector('.card')!;
24
+ *
25
+ * const cardState = createContainerState(card, {
26
+ * compact: [{ type: 'max-width', value: 300 }],
27
+ * wide: [{ type: 'min-width', value: 600 }],
28
+ * });
29
+ *
30
+ * cardState.on('compact', (v) => card.classList.toggle('card--compact', v));
31
+ * cardState.syncCSSVars({ prefix: '--card-' });
32
+ *
33
+ * // Cleanup when no longer needed:
34
+ * cardState.destroy();
35
+ */
36
+ export declare function createContainerState(element: Element, config: Record<string, MediaQueryConfig>, options?: SetConfigOptions): ContainerState;