solid-drift 0.21.0 → 0.22.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 CHANGED
@@ -1390,6 +1390,61 @@ const scratch = createScratch(() => foil, {
1390
1390
 
1391
1391
  Options: `threshold` (fraction cleared to complete, default `0.45`), `brush` (eraser radius in px, default `26`), `paint(ctx, w, h)` (custom cover art), `onComplete`. Returns `{ cleared, done, reset }`: `cleared()` is the 0..1 fraction erased, `reset()` repaints the cover. Scratching is direct manipulation, so it works identically under reduced motion. SSR-safe: `cleared()` stays 0.
1392
1392
 
1393
+ ### DOM utilities
1394
+
1395
+ Everyday DOM glue, signal-native: debounced and throttled signal transforms, a persisted signal, a live media query, outside-press dismissal, body scroll locking, and infinite scroll. All SSR-safe.
1396
+
1397
+ ```tsx
1398
+ import {
1399
+ createDebounced,
1400
+ createThrottled,
1401
+ createLocalStorage,
1402
+ createMediaQuery,
1403
+ createClickOutside,
1404
+ createScrollLock,
1405
+ createInfiniteScroll,
1406
+ } from "solid-drift"
1407
+
1408
+ // Debounced search: the query waits for a 300ms pause before firing.
1409
+ const [query, setQuery] = createSignal("")
1410
+ const debounced = createDebounced(query, 300)
1411
+ createEffect(() => { if (debounced()) search(debounced()) })
1412
+
1413
+ // Throttled scroll position: at most one update per 100ms.
1414
+ const throttledY = createThrottled(scrollY, 100)
1415
+
1416
+ // Persisted theme, synced across tabs.
1417
+ const theme = createLocalStorage<"light" | "dark">("theme", "light")
1418
+ theme.set("dark")
1419
+
1420
+ // Live media query.
1421
+ const wide = createMediaQuery("(min-width: 1024px)")
1422
+
1423
+ // Dismiss a menu on outside press.
1424
+ let menu!: HTMLDivElement
1425
+ createClickOutside(() => menu, () => setOpen(false))
1426
+
1427
+ // Lock body scroll while a modal is open (nested locks stack).
1428
+ const scroll = createScrollLock()
1429
+ createEffect(() => { modalOpen() ? scroll.lock() : scroll.unlock() })
1430
+
1431
+ // Infinite scroll: prefetch as the sentinel approaches.
1432
+ let sentinel!: HTMLDivElement
1433
+ createInfiniteScroll(() => sentinel, {
1434
+ onLoadMore: () => loadPage(),
1435
+ disabled: () => !hasMore(),
1436
+ })
1437
+ <div ref={sentinel} />
1438
+ ```
1439
+
1440
+ - `createDebounced(source, delay)` returns an `Accessor<T>` that follows the source after it stops changing for `delay` ms (trailing edge).
1441
+ - `createThrottled(source, interval)` returns an `Accessor<T>` that updates at most once per `interval` ms: leading change applies immediately, the rest collapse into one trailing update.
1442
+ - `createLocalStorage<T>(key, initialValue, options?)` returns `{ value, set, remove }`: reads the stored value on creation (falling back on missing or corrupt JSON), writes through on every set, and stays in sync across tabs via the `storage` event (`sync: true` default). Custom `serialize`/`deserialize` supported. Behaves like a plain signal where storage is unavailable.
1443
+ - `createMediaQuery(query)` returns an `Accessor<boolean>` that tracks the query live (`false` on the server).
1444
+ - `createClickOutside(ref, handler, options?)` calls `handler` on `pointerdown` (default, configurable via `events`) outside the element. Shadow-DOM aware via `composedPath`. No-op on the server.
1445
+ - `createScrollLock()` returns `{ locked, lock, unlock }`: sets `document.body.style.overflow = "hidden"`, restores the previous value when the last lock releases, and reference-counts nested locks so stacked modals cannot unlock each other early. Unmounting releases the locks.
1446
+ - `createInfiniteScroll(ref, options)` observes a sentinel with IntersectionObserver and calls `onLoadMore` as it approaches the viewport (`threshold` px prefetch via `rootMargin`, default `200`). `disabled` is a reactive kill switch (e.g. `() => !hasMore()`).
1447
+
1393
1448
  ### Easings
1394
1449
 
1395
1450
  Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
package/dist/dom.d.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { type Accessor, type Setter } from "solid-js";
2
+ type MaybeElement = () => Element | null | undefined;
3
+ /**
4
+ * A signal that follows the source after it stops changing for `delay`
5
+ * ms. The classic search-input pattern: the UI stays live while the
6
+ * query signal waits for a pause before firing network requests.
7
+ *
8
+ * Trailing edge only. The pending update is cancelled on cleanup.
9
+ * SSR-safe: holds the source's initial value on the server.
10
+ *
11
+ * ```tsx
12
+ * const [query, setQuery] = createSignal("")
13
+ * const debounced = createDebounced(query, 300)
14
+ * createEffect(() => {
15
+ * const q = debounced()
16
+ * if (q) search(q) // fires only after a 300ms pause
17
+ * })
18
+ * ```
19
+ */
20
+ export declare function createDebounced<T>(source: Accessor<T>, delay: number): Accessor<T>;
21
+ /**
22
+ * A signal that follows the source at most once per `interval` ms.
23
+ * The first change applies immediately (leading edge); changes inside
24
+ * the window collapse into one trailing update. For scroll or resize
25
+ * handlers that feed expensive work.
26
+ *
27
+ * SSR-safe: holds the source's initial value on the server.
28
+ *
29
+ * ```tsx
30
+ * const throttledY = createThrottled(scrollY, 100)
31
+ * ```
32
+ */
33
+ export declare function createThrottled<T>(source: Accessor<T>, interval: number): Accessor<T>;
34
+ export interface LocalStorageOptions<T> {
35
+ /** Serialize a value. Default JSON.stringify. */
36
+ serialize?: (value: T) => string;
37
+ /** Deserialize a stored string. Default JSON.parse. */
38
+ deserialize?: (raw: string) => T;
39
+ /**
40
+ * React to `storage` events from other tabs. Default true: the
41
+ * signal stays in sync across tabs.
42
+ */
43
+ sync?: boolean;
44
+ }
45
+ export interface LocalStorageControls<T> {
46
+ value: Accessor<T>;
47
+ set: Setter<T>;
48
+ /** Remove the key from storage and reset to the initial value. */
49
+ remove: () => void;
50
+ }
51
+ /**
52
+ * A signal persisted to `localStorage`. Reads the stored value on
53
+ * creation (falling back to `initialValue` when missing or corrupt),
54
+ * writes through on every set, and stays in sync across tabs via the
55
+ * `storage` event.
56
+ *
57
+ * SSR-safe and storage-less-safe: without `window.localStorage` it
58
+ * behaves like a plain signal.
59
+ *
60
+ * ```tsx
61
+ * const theme = createLocalStorage<"light" | "dark">("theme", "light")
62
+ * theme.set("dark") // localStorage.theme = '"dark"'
63
+ * ```
64
+ */
65
+ export declare function createLocalStorage<T>(key: string, initialValue: T, options?: LocalStorageOptions<T>): LocalStorageControls<T>;
66
+ /**
67
+ * A boolean signal tracking a CSS media query, updating live when the
68
+ * query starts or stops matching. The primitive behind
69
+ * `usePrefersReducedMotion`, generalized.
70
+ *
71
+ * SSR-safe (and safe where `matchMedia` is missing): constant `false`.
72
+ *
73
+ * ```tsx
74
+ * const wide = createMediaQuery("(min-width: 1024px)")
75
+ * const columns = () => (wide() ? 4 : 2)
76
+ * ```
77
+ */
78
+ export declare function createMediaQuery(query: string): Accessor<boolean>;
79
+ export interface ClickOutsideOptions {
80
+ /**
81
+ * Events that count as "outside". Default `["pointerdown"]`: fires
82
+ * before `click`, so menus dismiss without a visible flash, and
83
+ * covers touch via Pointer Events.
84
+ */
85
+ events?: string[];
86
+ }
87
+ /**
88
+ * Call `handler` when the user interacts outside the element:
89
+ * dropdowns, popovers, and menus that dismiss on outside press.
90
+ *
91
+ * Listens on `window` (so it works even when the press lands on an
92
+ * overlaying element), ignores presses inside the ref, and cleans up
93
+ * on unmount. SSR-safe: no-op on the server.
94
+ *
95
+ * ```tsx
96
+ * let menu!: HTMLDivElement
97
+ * const [open, setOpen] = createSignal(false)
98
+ * createClickOutside(() => menu, () => setOpen(false))
99
+ * ```
100
+ */
101
+ export declare function createClickOutside(ref: MaybeElement, handler: (event: Event) => void, options?: ClickOutsideOptions): void;
102
+ export interface ScrollLockControls {
103
+ /** Whether the body scroll is currently locked. */
104
+ locked: Accessor<boolean>;
105
+ /** Lock body scroll. Nested locks stack; all must unlock. */
106
+ lock: () => void;
107
+ /** Release one lock. Restores the original overflow when empty. */
108
+ unlock: () => void;
109
+ }
110
+ /**
111
+ * Lock body scroll while overlays are open: modals, drawers, the
112
+ * bottom sheet. `overflow: hidden` goes on `document.body`, the
113
+ * previous value is restored when the last lock releases, and nested
114
+ * locks are reference-counted so two stacked modals cannot unlock
115
+ * each other early. Unmounting while locked releases the lock.
116
+ *
117
+ * SSR-safe: no-op on the server.
118
+ *
119
+ * ```tsx
120
+ * const scroll = createScrollLock()
121
+ * createEffect(() => {
122
+ * if (modalOpen()) scroll.lock()
123
+ * else scroll.unlock()
124
+ * })
125
+ * ```
126
+ */
127
+ export declare function createScrollLock(): ScrollLockControls;
128
+ export interface InfiniteScrollOptions {
129
+ /** Called when the sentinel approaches the viewport. */
130
+ onLoadMore: () => void;
131
+ /**
132
+ * Trigger distance in px before the sentinel enters: the observer's
133
+ * rootMargin. Default 200.
134
+ */
135
+ threshold?: number;
136
+ /**
137
+ * Reactive kill switch, e.g. `() => !hasMore()`. While true the
138
+ * sentinel is unobserved.
139
+ */
140
+ disabled?: Accessor<boolean>;
141
+ }
142
+ /**
143
+ * Infinite scroll: observe a sentinel element at the end of a list
144
+ * and call `onLoadMore` as it approaches the viewport, prefetching
145
+ * before the user hits the bottom.
146
+ *
147
+ * Built on IntersectionObserver with a `rootMargin` prefetch zone.
148
+ * SSR-safe (and safe without IntersectionObserver): never fires.
149
+ *
150
+ * ```tsx
151
+ * let sentinel!: HTMLDivElement
152
+ * const [items, setItems] = createSignal<string[]>([])
153
+ * const [hasMore, setHasMore] = createSignal(true)
154
+ * createInfiniteScroll(() => sentinel, {
155
+ * onLoadMore: () => loadPage(),
156
+ * disabled: () => !hasMore(),
157
+ * })
158
+ * <For each={items()}>{(item) => <Row item={item} />}</For>
159
+ * <div ref={sentinel} />
160
+ * ```
161
+ */
162
+ export declare function createInfiniteScroll(ref: MaybeElement, options: InfiniteScrollOptions): void;
163
+ export {};
package/dist/dom.js ADDED
@@ -0,0 +1,316 @@
1
+ import { createEffect, createSignal, onCleanup, untrack, } from "solid-js";
2
+ /* ------------------------------------------------------------------ */
3
+ /* createDebounced */
4
+ /* ------------------------------------------------------------------ */
5
+ /**
6
+ * A signal that follows the source after it stops changing for `delay`
7
+ * ms. The classic search-input pattern: the UI stays live while the
8
+ * query signal waits for a pause before firing network requests.
9
+ *
10
+ * Trailing edge only. The pending update is cancelled on cleanup.
11
+ * SSR-safe: holds the source's initial value on the server.
12
+ *
13
+ * ```tsx
14
+ * const [query, setQuery] = createSignal("")
15
+ * const debounced = createDebounced(query, 300)
16
+ * createEffect(() => {
17
+ * const q = debounced()
18
+ * if (q) search(q) // fires only after a 300ms pause
19
+ * })
20
+ * ```
21
+ */
22
+ export function createDebounced(source, delay) {
23
+ const [value, setValue] = createSignal(untrack(source));
24
+ if (typeof window === "undefined")
25
+ return value;
26
+ let timer;
27
+ createEffect(() => {
28
+ const next = source();
29
+ clearTimeout(timer);
30
+ timer = setTimeout(() => setValue(() => next), Math.max(0, delay));
31
+ });
32
+ onCleanup(() => clearTimeout(timer));
33
+ return value;
34
+ }
35
+ /* ------------------------------------------------------------------ */
36
+ /* createThrottled */
37
+ /* ------------------------------------------------------------------ */
38
+ /**
39
+ * A signal that follows the source at most once per `interval` ms.
40
+ * The first change applies immediately (leading edge); changes inside
41
+ * the window collapse into one trailing update. For scroll or resize
42
+ * handlers that feed expensive work.
43
+ *
44
+ * SSR-safe: holds the source's initial value on the server.
45
+ *
46
+ * ```tsx
47
+ * const throttledY = createThrottled(scrollY, 100)
48
+ * ```
49
+ */
50
+ export function createThrottled(source, interval) {
51
+ const [value, setValue] = createSignal(untrack(source));
52
+ if (typeof window === "undefined")
53
+ return value;
54
+ const wait = Math.max(0, interval);
55
+ let last = 0;
56
+ let timer;
57
+ createEffect(() => {
58
+ const next = source();
59
+ const t = Date.now();
60
+ clearTimeout(timer);
61
+ timer = undefined;
62
+ if (t - last >= wait) {
63
+ last = t;
64
+ setValue(() => next);
65
+ }
66
+ else {
67
+ timer = setTimeout(() => {
68
+ last = Date.now();
69
+ timer = undefined;
70
+ setValue(() => next);
71
+ }, wait - (t - last));
72
+ }
73
+ });
74
+ onCleanup(() => clearTimeout(timer));
75
+ return value;
76
+ }
77
+ /**
78
+ * A signal persisted to `localStorage`. Reads the stored value on
79
+ * creation (falling back to `initialValue` when missing or corrupt),
80
+ * writes through on every set, and stays in sync across tabs via the
81
+ * `storage` event.
82
+ *
83
+ * SSR-safe and storage-less-safe: without `window.localStorage` it
84
+ * behaves like a plain signal.
85
+ *
86
+ * ```tsx
87
+ * const theme = createLocalStorage<"light" | "dark">("theme", "light")
88
+ * theme.set("dark") // localStorage.theme = '"dark"'
89
+ * ```
90
+ */
91
+ export function createLocalStorage(key, initialValue, options = {}) {
92
+ const { serialize = JSON.stringify, deserialize = JSON.parse, sync = true, } = options;
93
+ const storage = () => {
94
+ if (typeof window === "undefined")
95
+ return null;
96
+ try {
97
+ return window.localStorage;
98
+ }
99
+ catch {
100
+ return null; // private mode / blocked storage
101
+ }
102
+ };
103
+ const read = () => {
104
+ const store = storage();
105
+ if (!store)
106
+ return initialValue;
107
+ try {
108
+ const raw = store.getItem(key);
109
+ return raw === null ? initialValue : deserialize(raw);
110
+ }
111
+ catch {
112
+ return initialValue; // corrupt JSON: fall back, don't crash
113
+ }
114
+ };
115
+ const [value, setValue] = createSignal(read());
116
+ const set = ((next) => {
117
+ const resolved = typeof next === "function"
118
+ ? next(untrack(value))
119
+ : next;
120
+ setValue(() => resolved);
121
+ try {
122
+ storage()?.setItem(key, serialize(resolved));
123
+ }
124
+ catch {
125
+ // Quota exceeded or blocked: the signal still updates.
126
+ }
127
+ return resolved;
128
+ });
129
+ const remove = () => {
130
+ try {
131
+ storage()?.removeItem(key);
132
+ }
133
+ catch {
134
+ // ignore
135
+ }
136
+ setValue(() => initialValue);
137
+ };
138
+ if (typeof window !== "undefined" && sync) {
139
+ const onStorage = (event) => {
140
+ if (event.key !== key)
141
+ return;
142
+ try {
143
+ setValue(() => event.newValue === null ? initialValue : deserialize(event.newValue));
144
+ }
145
+ catch {
146
+ // ignore corrupt cross-tab writes
147
+ }
148
+ };
149
+ window.addEventListener("storage", onStorage);
150
+ onCleanup(() => window.removeEventListener("storage", onStorage));
151
+ }
152
+ return { value, set, remove };
153
+ }
154
+ /* ------------------------------------------------------------------ */
155
+ /* createMediaQuery */
156
+ /* ------------------------------------------------------------------ */
157
+ /**
158
+ * A boolean signal tracking a CSS media query, updating live when the
159
+ * query starts or stops matching. The primitive behind
160
+ * `usePrefersReducedMotion`, generalized.
161
+ *
162
+ * SSR-safe (and safe where `matchMedia` is missing): constant `false`.
163
+ *
164
+ * ```tsx
165
+ * const wide = createMediaQuery("(min-width: 1024px)")
166
+ * const columns = () => (wide() ? 4 : 2)
167
+ * ```
168
+ */
169
+ export function createMediaQuery(query) {
170
+ const no = () => false;
171
+ if (typeof window === "undefined" ||
172
+ typeof window.matchMedia !== "function") {
173
+ return no;
174
+ }
175
+ const mql = window.matchMedia(query);
176
+ const [matches, setMatches] = createSignal(mql.matches);
177
+ const onChange = (event) => {
178
+ setMatches(event.matches);
179
+ };
180
+ mql.addEventListener("change", onChange);
181
+ onCleanup(() => mql.removeEventListener("change", onChange));
182
+ return matches;
183
+ }
184
+ /**
185
+ * Call `handler` when the user interacts outside the element:
186
+ * dropdowns, popovers, and menus that dismiss on outside press.
187
+ *
188
+ * Listens on `window` (so it works even when the press lands on an
189
+ * overlaying element), ignores presses inside the ref, and cleans up
190
+ * on unmount. SSR-safe: no-op on the server.
191
+ *
192
+ * ```tsx
193
+ * let menu!: HTMLDivElement
194
+ * const [open, setOpen] = createSignal(false)
195
+ * createClickOutside(() => menu, () => setOpen(false))
196
+ * ```
197
+ */
198
+ export function createClickOutside(ref, handler, options = {}) {
199
+ if (typeof window === "undefined")
200
+ return;
201
+ const { events = ["pointerdown"] } = options;
202
+ const onEvent = (event) => {
203
+ const el = ref();
204
+ if (!el)
205
+ return;
206
+ const target = event.target;
207
+ // Shadow-DOM aware: composedPath pierces shadow roots.
208
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
209
+ const inside = (target !== null && el.contains(target)) || path.includes(el);
210
+ if (!inside)
211
+ handler(event);
212
+ };
213
+ for (const name of events) {
214
+ window.addEventListener(name, onEvent);
215
+ }
216
+ onCleanup(() => {
217
+ for (const name of events) {
218
+ window.removeEventListener(name, onEvent);
219
+ }
220
+ });
221
+ }
222
+ let lockCount = 0;
223
+ let savedOverflow = "";
224
+ /**
225
+ * Lock body scroll while overlays are open: modals, drawers, the
226
+ * bottom sheet. `overflow: hidden` goes on `document.body`, the
227
+ * previous value is restored when the last lock releases, and nested
228
+ * locks are reference-counted so two stacked modals cannot unlock
229
+ * each other early. Unmounting while locked releases the lock.
230
+ *
231
+ * SSR-safe: no-op on the server.
232
+ *
233
+ * ```tsx
234
+ * const scroll = createScrollLock()
235
+ * createEffect(() => {
236
+ * if (modalOpen()) scroll.lock()
237
+ * else scroll.unlock()
238
+ * })
239
+ * ```
240
+ */
241
+ export function createScrollLock() {
242
+ const [locked, setLocked] = createSignal(false);
243
+ if (typeof window === "undefined" || typeof document === "undefined") {
244
+ return { locked, lock: () => { }, unlock: () => { } };
245
+ }
246
+ let held = 0;
247
+ const lock = () => {
248
+ if (lockCount === 0) {
249
+ savedOverflow = document.body.style.overflow;
250
+ document.body.style.overflow = "hidden";
251
+ }
252
+ lockCount++;
253
+ held++;
254
+ setLocked(true);
255
+ };
256
+ const unlock = () => {
257
+ if (held === 0)
258
+ return;
259
+ held--;
260
+ lockCount--;
261
+ if (lockCount === 0) {
262
+ document.body.style.overflow = savedOverflow;
263
+ setLocked(false);
264
+ }
265
+ else if (held === 0) {
266
+ setLocked(false);
267
+ }
268
+ };
269
+ onCleanup(() => {
270
+ while (held > 0)
271
+ unlock();
272
+ });
273
+ return { locked, lock, unlock };
274
+ }
275
+ /**
276
+ * Infinite scroll: observe a sentinel element at the end of a list
277
+ * and call `onLoadMore` as it approaches the viewport, prefetching
278
+ * before the user hits the bottom.
279
+ *
280
+ * Built on IntersectionObserver with a `rootMargin` prefetch zone.
281
+ * SSR-safe (and safe without IntersectionObserver): never fires.
282
+ *
283
+ * ```tsx
284
+ * let sentinel!: HTMLDivElement
285
+ * const [items, setItems] = createSignal<string[]>([])
286
+ * const [hasMore, setHasMore] = createSignal(true)
287
+ * createInfiniteScroll(() => sentinel, {
288
+ * onLoadMore: () => loadPage(),
289
+ * disabled: () => !hasMore(),
290
+ * })
291
+ * <For each={items()}>{(item) => <Row item={item} />}</For>
292
+ * <div ref={sentinel} />
293
+ * ```
294
+ */
295
+ export function createInfiniteScroll(ref, options) {
296
+ const { onLoadMore, threshold = 200, disabled } = options;
297
+ if (typeof window === "undefined" ||
298
+ typeof IntersectionObserver === "undefined") {
299
+ return;
300
+ }
301
+ createEffect(() => {
302
+ const el = ref();
303
+ if (!el)
304
+ return;
305
+ if (disabled?.() === true)
306
+ return;
307
+ const observer = new IntersectionObserver((entries) => {
308
+ for (const entry of entries) {
309
+ if (entry.isIntersecting)
310
+ onLoadMore();
311
+ }
312
+ }, { rootMargin: `${threshold}px` });
313
+ observer.observe(el);
314
+ onCleanup(() => observer.disconnect());
315
+ });
316
+ }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion
14
14
  export { isLowPowerMode, useLowPowerMode, type LowPowerOptions, } from "./power.js";
15
15
  export { createSlotMachine, type SlotMachineControls, type SlotMachineOptions, type SlotMachineStatus, createRedPacket, type RedPacketCoin, type RedPacketControls, type RedPacketOptions, type RedPacketStatus, createConfetti, type ConfettiControls, type ConfettiOptions, createEmojiBurst, type EmojiBurstControls, type EmojiBurstOptions, createScratch, type ScratchControls, type ScratchOptions, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
+ export { createDebounced, createThrottled, createLocalStorage, type LocalStorageOptions, type LocalStorageControls, createMediaQuery, createClickOutside, type ClickOutsideOptions, createScrollLock, type ScrollLockControls, createInfiniteScroll, type InfiniteScrollOptions, } from "./dom.js";
17
18
  export { createHorizontalScroll, type HorizontalScrollOptions, type HorizontalScrollResult, } from "./horizontal.js";
18
19
  export { createScrub, type ScrubKeyframe, type ScrubOptions } from "./scrub.js";
19
20
  export { createScrollColor, type ScrollColorStop, type ScrollColorOptions, type ScrollColorFormat, createScrollTracking, type ScrollTrackingOptions, createScrollLine, type ScrollLineOptions, type ScrollLineStyle, type ScrollLineAxis, type ScrollLineOrigin, } from "./scrollfx.js";
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion
14
14
  export { isLowPowerMode, useLowPowerMode, } from "./power.js";
15
15
  export { createSlotMachine, createRedPacket, createConfetti, createEmojiBurst, createScratch, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
+ export { createDebounced, createThrottled, createLocalStorage, createMediaQuery, createClickOutside, createScrollLock, createInfiniteScroll, } from "./dom.js";
17
18
  export { createHorizontalScroll, } from "./horizontal.js";
18
19
  export { createScrub } from "./scrub.js";
19
20
  export { createScrollColor, createScrollTracking, createScrollLine, } from "./scrollfx.js";
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  },
44
44
  "type": "module",
45
45
  "types": "./dist/index.d.ts",
46
- "version": "0.21.0"
46
+ "version": "0.22.0"
47
47
  }