@journ/lift 0.2.1 → 0.3.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.
Files changed (48) hide show
  1. package/README.md +17 -37
  2. package/dist/prefers-reduced-motion.js +1 -1
  3. package/dist/use-debounce-state.js +3 -3
  4. package/dist/use-debounced-callback.js +6 -6
  5. package/package.json +3 -103
  6. package/dist/env-C0PNnbwt.js +0 -4
  7. package/dist/logger-Dse1660l.js +0 -33
  8. package/dist/try-catch-D4d3N9_W.js +0 -29
  9. package/dist/use-battery.d.ts +0 -12
  10. package/dist/use-battery.js +0 -64
  11. package/dist/use-browser.d.ts +0 -6
  12. package/dist/use-browser.js +0 -51
  13. package/dist/use-haptic.d.ts +0 -8
  14. package/dist/use-haptic.js +0 -84
  15. package/dist/use-headroom.d.ts +0 -18
  16. package/dist/use-headroom.js +0 -65
  17. package/dist/use-idle-queue.d.ts +0 -28
  18. package/dist/use-idle-queue.js +0 -221
  19. package/dist/use-intersection-observer.d.ts +0 -17
  20. package/dist/use-intersection-observer.js +0 -50
  21. package/dist/use-isomorphic-effect.d.ts +0 -5
  22. package/dist/use-isomorphic-effect.js +0 -5
  23. package/dist/use-keybindings.d.ts +0 -14
  24. package/dist/use-keybindings.js +0 -76
  25. package/dist/use-local-storage.d.ts +0 -13
  26. package/dist/use-local-storage.js +0 -76
  27. package/dist/use-logger.d.ts +0 -4
  28. package/dist/use-logger.js +0 -17
  29. package/dist/use-long-press.d.ts +0 -12
  30. package/dist/use-long-press.js +0 -43
  31. package/dist/use-measure.d.ts +0 -8
  32. package/dist/use-measure.js +0 -27
  33. package/dist/use-media-query.d.ts +0 -14
  34. package/dist/use-media-query.js +0 -24
  35. package/dist/use-mouse.d.ts +0 -18
  36. package/dist/use-mouse.js +0 -40
  37. package/dist/use-network.d.ts +0 -17
  38. package/dist/use-network.js +0 -38
  39. package/dist/use-page-leave.d.ts +0 -4
  40. package/dist/use-page-leave.js +0 -11
  41. package/dist/use-reduced-motion.d.ts +0 -4
  42. package/dist/use-reduced-motion.js +0 -5
  43. package/dist/use-scroll-direction.d.ts +0 -5
  44. package/dist/use-scroll-direction.js +0 -33
  45. package/dist/use-window-event.d.ts +0 -5
  46. package/dist/use-window-event.js +0 -11
  47. package/dist/use-window-scroll.d.ts +0 -10
  48. package/dist/use-window-scroll.js +0 -33
@@ -1,65 +0,0 @@
1
- import { useIsomorphicEffect } from "./use-isomorphic-effect.js";
2
- import { useScrollDirection } from "./use-scroll-direction.js";
3
- import { useWindowScroll } from "./use-window-scroll.js";
4
- import { useEffectEvent, useRef, useState } from "react";
5
- import { clamp } from "@journ/fitter-happier/clamp";
6
- //#region src/use-headroom.ts
7
- const isFixed = (current, fixedAt) => current <= fixedAt;
8
- const isPinned = (current, previous) => current <= previous;
9
- const isReleased = (current, previous, fixedAt) => !isPinned(current, previous) && !isFixed(current, fixedAt);
10
- const progressFrom = ({ isScrollingUp, scrollY, progress }, scrollPosition, scrollDistance) => {
11
- const delta = Math.abs(scrollPosition - scrollY) / scrollDistance;
12
- return clamp(progress + (isScrollingUp ? delta : -delta), 0, 1);
13
- };
14
- const advanceAnchor = (anchor, scrollPosition, previousPosition, fixedAt, isScrollingUp, scrollDistance) => {
15
- const fixed = isFixed(scrollPosition, fixedAt);
16
- if (anchor.fixed !== fixed) return {
17
- fixed,
18
- isScrollingUp,
19
- scrollY: fixed ? scrollPosition : fixedAt,
20
- progress: 1
21
- };
22
- if (!fixed && anchor.isScrollingUp !== isScrollingUp) return {
23
- fixed,
24
- isScrollingUp,
25
- scrollY: previousPosition,
26
- progress: progressFrom(anchor, previousPosition, scrollDistance)
27
- };
28
- return anchor;
29
- };
30
- const useHeadroom = ({ fixedAt = 0, scrollDistance = 100, onPin, onFix, onRelease } = {}) => {
31
- const isScrollingUp = useScrollDirection() === "up";
32
- const [{ y: scrollPosition }] = useWindowScroll();
33
- const fixed = isFixed(scrollPosition, fixedAt);
34
- const [anchor, setAnchor] = useState({
35
- fixed,
36
- isScrollingUp,
37
- scrollY: scrollPosition,
38
- progress: fixed ? 1 : 0
39
- });
40
- const [previousPosition, setPreviousPosition] = useState(scrollPosition);
41
- if (previousPosition !== scrollPosition) setPreviousPosition(scrollPosition);
42
- const currentAnchor = advanceAnchor(anchor, scrollPosition, previousPosition, fixedAt, isScrollingUp, scrollDistance);
43
- if (currentAnchor !== anchor) setAnchor(currentAnchor);
44
- const scrollProgress = fixed ? 1 : progressFrom(currentAnchor, scrollPosition, scrollDistance);
45
- const pinnedRef = useRef(false);
46
- const onPinEvent = useEffectEvent(() => onPin?.());
47
- const onReleaseEvent = useEffectEvent(() => onRelease?.());
48
- const onFixEvent = useEffectEvent(() => onFix?.());
49
- useIsomorphicEffect(() => {
50
- const shouldBePinned = fixed || isScrollingUp;
51
- if (shouldBePinned === pinnedRef.current) return;
52
- pinnedRef.current = shouldBePinned;
53
- if (shouldBePinned) onPinEvent();
54
- else onReleaseEvent();
55
- }, [fixed, isScrollingUp]);
56
- useIsomorphicEffect(() => {
57
- if (fixed) onFixEvent();
58
- }, [fixed]);
59
- return {
60
- pinned: scrollProgress > 0,
61
- scrollProgress
62
- };
63
- };
64
- //#endregion
65
- export { isFixed, isPinned, isReleased, useHeadroom };
@@ -1,28 +0,0 @@
1
- //#region src/lib/idle-queue/idle-queue.d.ts
2
- /** The state captured when a task was queued, passed to it when it runs. */
3
- interface TaskState {
4
- time: number;
5
- visibilityState: DocumentVisibilityState;
6
- }
7
- type Task = (state: TaskState) => void;
8
- interface TaskOptions {
9
- minTaskTime?: number;
10
- }
11
- //#endregion
12
- //#region src/use-idle-queue.d.ts
13
- type UseIdleQueueOptions = {
14
- ensureTasksRun?: boolean;
15
- minTaskTime?: number;
16
- onError?: (error: unknown) => void;
17
- };
18
- type UseIdleQueueReturnValue = {
19
- pushTask: (task: Task, options?: TaskOptions) => void;
20
- unshiftTask: (task: Task, options?: TaskOptions) => void;
21
- runTasksImmediately: () => void;
22
- hasPendingTasks: () => boolean;
23
- clearPendingTasks: () => void;
24
- getState: () => TaskState | null;
25
- };
26
- declare const useIdleQueue: ({ ensureTasksRun, minTaskTime, onError }?: UseIdleQueueOptions) => UseIdleQueueReturnValue;
27
- //#endregion
28
- export { type UseIdleQueueOptions, type UseIdleQueueReturnValue, useIdleQueue };
@@ -1,221 +0,0 @@
1
- import { useEffect, useEffectEvent, useMemo, useRef } from "react";
2
- //#region src/lib/idle-queue/idle-callback.ts
3
- /** The budget the shim reports, matching the spec's frame allowance. */
4
- const SHIM_BUDGET_MS = 50;
5
- const supportsRequestIdleCallback = () => typeof globalThis.requestIdleCallback === "function" && typeof globalThis.cancelIdleCallback === "function";
6
- const createIdleDeadline = (initTime) => ({
7
- didTimeout: false,
8
- timeRemaining: () => Math.max(0, SHIM_BUDGET_MS - (Date.now() - initTime))
9
- });
10
- const requestIdleCallbackShim = (callback) => {
11
- const deadline = createIdleDeadline(Date.now());
12
- return setTimeout(() => callback(deadline), 0);
13
- };
14
- const cancelIdleCallbackShim = (handle) => {
15
- clearTimeout(handle);
16
- };
17
- /**
18
- * Requests an idle callback, falling back to a timeout where the API is
19
- * missing. Support is checked per call so the module stays importable under
20
- * SSR, where no global exists at load time.
21
- */
22
- const rIC = (callback, options) => supportsRequestIdleCallback() ? globalThis.requestIdleCallback(callback, options) : requestIdleCallbackShim(callback);
23
- /** Cancels a callback scheduled by {@link rIC}. */
24
- const cIC = (handle) => {
25
- if (supportsRequestIdleCallback()) globalThis.cancelIdleCallback(handle);
26
- else cancelIdleCallbackShim(handle);
27
- };
28
- //#endregion
29
- //#region src/lib/idle-queue/queue-microtask.ts
30
- /** Rethrows asynchronously so a failing task cannot swallow the error. */
31
- const rethrowAsync = (error) => {
32
- setTimeout(() => {
33
- throw error;
34
- }, 0);
35
- };
36
- const viaNativeQueueMicrotask = () => (microtask) => {
37
- globalThis.queueMicrotask(() => {
38
- try {
39
- microtask();
40
- } catch (error) {
41
- rethrowAsync(error);
42
- }
43
- });
44
- };
45
- const viaPromise = () => (microtask) => {
46
- Promise.resolve().then(() => {
47
- try {
48
- microtask();
49
- } catch (error) {
50
- rethrowAsync(error);
51
- }
52
- });
53
- };
54
- /** Last resort for engines predating both native scheduling paths. */
55
- const viaMutationObserver = () => {
56
- let counter = 0;
57
- let pending = [];
58
- const node = document.createTextNode("");
59
- new MutationObserver(() => {
60
- const tasks = pending;
61
- pending = [];
62
- for (const microtask of tasks) try {
63
- microtask();
64
- } catch (error) {
65
- rethrowAsync(error);
66
- }
67
- }).observe(node, { characterData: true });
68
- return (microtask) => {
69
- pending.push(microtask);
70
- node.data = String(++counter % 2);
71
- };
72
- };
73
- const selectScheduler = () => {
74
- if (typeof globalThis.queueMicrotask === "function") return viaNativeQueueMicrotask();
75
- if (typeof Promise === "function") return viaPromise();
76
- return viaMutationObserver();
77
- };
78
- let scheduler = null;
79
- /**
80
- * Schedules a callback on the microtask queue, rethrowing failures instead of
81
- * turning them into unhandled rejections.
82
- */
83
- const queueMicrotask = (microtask) => {
84
- scheduler ??= selectScheduler();
85
- scheduler(microtask);
86
- };
87
- //#endregion
88
- //#region src/lib/idle-queue/idle-queue.ts
89
- const DEFAULT_MIN_TASK_TIME = 0;
90
- const isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
91
- const isSafari = () => isBrowser() && /^((?!chrome|android).)*safari/i.test(window.navigator.userAgent);
92
- const shouldYield = (deadline, minTaskTime) => !!deadline && deadline.timeRemaining() <= minTaskTime;
93
- const createTaskState = () => ({
94
- time: Date.now(),
95
- visibilityState: isBrowser() ? document.visibilityState : "visible"
96
- });
97
- const createIdleQueue = (options = {}) => {
98
- const { ensureTasksRun = false, minTaskTime: defaultMinTaskTime = DEFAULT_MIN_TASK_TIME, onError } = options;
99
- let taskQueue = [];
100
- let idleCallbackHandle = null;
101
- let isProcessing = false;
102
- let currentState = null;
103
- let isDestroyed = false;
104
- const cancelScheduledRun = () => {
105
- if (idleCallbackHandle !== null) {
106
- cIC(idleCallbackHandle);
107
- idleCallbackHandle = null;
108
- }
109
- };
110
- const runTasks = (deadline) => {
111
- cancelScheduledRun();
112
- if (isProcessing) return;
113
- isProcessing = true;
114
- try {
115
- while (taskQueue.length > 0 && !shouldYield(deadline, taskQueue[0]?.minTaskTime ?? 0)) {
116
- const nextTask = taskQueue.shift();
117
- if (!nextTask) break;
118
- currentState = nextTask.state;
119
- try {
120
- nextTask.task(nextTask.state);
121
- } catch (error) {
122
- if (onError) onError(error, nextTask.state);
123
- else setTimeout(() => {
124
- throw error;
125
- }, 0);
126
- } finally {
127
- currentState = null;
128
- }
129
- }
130
- } finally {
131
- isProcessing = false;
132
- }
133
- if (taskQueue.length > 0) scheduleTasksToRun();
134
- };
135
- const scheduleTasksToRun = () => {
136
- if (isDestroyed) return;
137
- if (ensureTasksRun && isBrowser() && document.visibilityState === "hidden") queueMicrotask(() => runTasks());
138
- else if (idleCallbackHandle === null) idleCallbackHandle = rIC(runTasks);
139
- };
140
- const enqueue = (task, taskOptions = {}, position) => {
141
- if (isDestroyed) return;
142
- const { minTaskTime = defaultMinTaskTime } = taskOptions;
143
- const queued = {
144
- state: createTaskState(),
145
- task,
146
- minTaskTime
147
- };
148
- if (position === "start") taskQueue.unshift(queued);
149
- else taskQueue.push(queued);
150
- scheduleTasksToRun();
151
- };
152
- const runTasksImmediately = () => {
153
- runTasks();
154
- };
155
- const onVisibilityChange = () => {
156
- if (document.visibilityState === "hidden") runTasksImmediately();
157
- };
158
- const listensForUnload = ensureTasksRun && isBrowser();
159
- const needsBeforeUnload = listensForUnload && isSafari();
160
- if (listensForUnload) {
161
- addEventListener("visibilitychange", onVisibilityChange, true);
162
- if (needsBeforeUnload) addEventListener("beforeunload", runTasksImmediately, true);
163
- }
164
- return {
165
- /** Queues a task to run after those already pending. */
166
- pushTask: (task, taskOptions) => {
167
- enqueue(task, taskOptions, "end");
168
- },
169
- /** Queues a task to run before those already pending. */
170
- unshiftTask: (task, taskOptions) => {
171
- enqueue(task, taskOptions, "start");
172
- },
173
- /** Runs every pending task now, ignoring the idle deadline. */
174
- runTasksImmediately,
175
- hasPendingTasks: () => taskQueue.length > 0,
176
- clearPendingTasks: () => {
177
- taskQueue = [];
178
- cancelScheduledRun();
179
- },
180
- /** The state of the task currently running, or `null` between tasks. */
181
- getState: () => currentState,
182
- /** Drops pending tasks and detaches listeners. The queue is unusable after. */
183
- destroy: () => {
184
- isDestroyed = true;
185
- taskQueue = [];
186
- cancelScheduledRun();
187
- if (listensForUnload) {
188
- removeEventListener("visibilitychange", onVisibilityChange, true);
189
- if (needsBeforeUnload) removeEventListener("beforeunload", runTasksImmediately, true);
190
- }
191
- }
192
- };
193
- };
194
- //#endregion
195
- //#region src/use-idle-queue.ts
196
- const useIdleQueue = ({ ensureTasksRun = true, minTaskTime = 0, onError } = {}) => {
197
- const queueRef = useRef(null);
198
- const reportError = useEffectEvent((error) => onError?.(error));
199
- useEffect(() => {
200
- const queue = createIdleQueue({
201
- ensureTasksRun,
202
- minTaskTime,
203
- onError: reportError
204
- });
205
- queueRef.current = queue;
206
- return () => {
207
- queue.destroy();
208
- queueRef.current = null;
209
- };
210
- }, [ensureTasksRun, minTaskTime]);
211
- return useMemo(() => ({
212
- pushTask: (task, options) => queueRef.current?.pushTask(task, options),
213
- unshiftTask: (task, options) => queueRef.current?.unshiftTask(task, options),
214
- runTasksImmediately: () => queueRef.current?.runTasksImmediately(),
215
- hasPendingTasks: () => queueRef.current?.hasPendingTasks() ?? false,
216
- clearPendingTasks: () => queueRef.current?.clearPendingTasks(),
217
- getState: () => queueRef.current?.getState() ?? null
218
- }), []);
219
- };
220
- //#endregion
221
- export { useIdleQueue };
@@ -1,17 +0,0 @@
1
- //#region src/use-intersection-observer.d.ts
2
- type UseIntersectionObserverOptions = {
3
- root?: Element | Document | null;
4
- rootMargin?: string;
5
- threshold?: number | number[];
6
- freezeOnceVisible?: boolean;
7
- onChange?: (isIntersecting: boolean, entry: IntersectionObserverEntry) => void;
8
- initialIsIntersecting?: boolean;
9
- };
10
- type IntersectionReturn = [(node?: Element | null) => void, boolean, IntersectionObserverEntry | undefined] & {
11
- ref: (node?: Element | null) => void;
12
- isIntersecting: boolean;
13
- entry?: IntersectionObserverEntry;
14
- };
15
- declare const useIntersectionObserver: ({ threshold, root, rootMargin, freezeOnceVisible, initialIsIntersecting, onChange }?: UseIntersectionObserverOptions) => IntersectionReturn;
16
- //#endregion
17
- export { useIntersectionObserver };
@@ -1,50 +0,0 @@
1
- import { useCallback, useEffect, useEffectEvent, useState } from "react";
2
- //#region src/use-intersection-observer.ts
3
- const checkIntersecting = (entry, thresholds) => entry.isIntersecting && thresholds.some((threshold) => entry.intersectionRatio >= threshold);
4
- const useIntersectionObserver = ({ threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false, initialIsIntersecting = false, onChange } = {}) => {
5
- const [node, setNode] = useState(null);
6
- const [state, setState] = useState({ isIntersecting: initialIsIntersecting });
7
- const notify = useEffectEvent((isIntersecting, entry) => onChange?.(isIntersecting, entry));
8
- const frozen = freezeOnceVisible && state.entry?.isIntersecting === true;
9
- const thresholdKey = [threshold].flat().join(",");
10
- useEffect(() => {
11
- if (!node || frozen || !("IntersectionObserver" in window)) return;
12
- const observer = new IntersectionObserver((entries) => {
13
- for (const entry of entries) {
14
- const isIntersecting = checkIntersecting(entry, observer.thresholds);
15
- setState({
16
- isIntersecting,
17
- entry
18
- });
19
- notify(isIntersecting, entry);
20
- }
21
- }, {
22
- threshold: thresholdKey.split(",").map(Number),
23
- root,
24
- rootMargin
25
- });
26
- observer.observe(node);
27
- return () => observer.disconnect();
28
- }, [
29
- node,
30
- frozen,
31
- thresholdKey,
32
- root,
33
- rootMargin
34
- ]);
35
- const ref = useCallback((next) => {
36
- setNode(next ?? null);
37
- if (!next && !freezeOnceVisible) setState({ isIntersecting: initialIsIntersecting });
38
- }, [freezeOnceVisible, initialIsIntersecting]);
39
- return Object.assign([
40
- ref,
41
- state.isIntersecting,
42
- state.entry
43
- ], {
44
- ref,
45
- isIntersecting: state.isIntersecting,
46
- entry: state.entry
47
- });
48
- };
49
- //#endregion
50
- export { useIntersectionObserver };
@@ -1,5 +0,0 @@
1
- import { useEffect } from "react";
2
- //#region src/use-isomorphic-effect.d.ts
3
- declare const useIsomorphicEffect: typeof useEffect;
4
- //#endregion
5
- export { useIsomorphicEffect };
@@ -1,5 +0,0 @@
1
- import { useEffect, useLayoutEffect } from "react";
2
- //#region src/use-isomorphic-effect.ts
3
- const useIsomorphicEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
4
- //#endregion
5
- export { useIsomorphicEffect };
@@ -1,14 +0,0 @@
1
- //#region src/lib/keybindings.d.ts
2
- type KeyBindingMap = Record<string, (event: KeyboardEvent) => void>;
3
- type KeyBindingHandlerOptions = {
4
- timeout?: number;
5
- };
6
- type KeyBindingOptions = KeyBindingHandlerOptions & {
7
- event?: "keydown" | "keyup";
8
- capture?: boolean;
9
- };
10
- //#endregion
11
- //#region src/use-keybindings.d.ts
12
- declare const useKeybindings: (keyBindingMap: KeyBindingMap, target?: Window | HTMLElement, { event, capture, timeout }?: KeyBindingOptions) => void;
13
- //#endregion
14
- export { useKeybindings };
@@ -1,76 +0,0 @@
1
- import { useEffect, useEffectEvent } from "react";
2
- //#region src/lib/keybindings.ts
3
- const KEYBINDING_MODIFIER_KEYS = [
4
- "Shift",
5
- "Meta",
6
- "Alt",
7
- "Control"
8
- ];
9
- const DEFAULT_TIMEOUT = 1e3;
10
- const DEFAULT_EVENT = "keydown";
11
- const PLATFORM = typeof navigator === "object" ? navigator.platform : "";
12
- const APPLE_DEVICE = /Mac|iPod|iPhone|iPad/.test(PLATFORM);
13
- const MOD = APPLE_DEVICE ? "Meta" : "Control";
14
- const ALT_GRAPH_ALIASES = PLATFORM === "Win32" ? ["Control", "Alt"] : APPLE_DEVICE ? ["Alt"] : [];
15
- const getModifierState = (event, mod) => typeof event.getModifierState === "function" ? event.getModifierState(mod) || ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState("AltGraph") : false;
16
- const parseKeybinding = (str) => str.trim().split(" ").map((press) => {
17
- const parts = press.split(/\b\+/);
18
- const rawKey = parts.at(-1);
19
- const mods = parts.slice(0, -1).map((mod) => mod === "$mod" ? MOD : mod);
20
- const match = rawKey.match(/^\((.+)\)$/);
21
- return [mods, match ? new RegExp(`^${match[1]}$`) : rawKey];
22
- });
23
- const matchesKey = (event, key) => key instanceof RegExp ? key.test(event.key) || key.test(event.code) : key.toUpperCase() === event.key.toUpperCase() || key === event.code;
24
- const hasRequiredModifiers = (event, mods) => mods.every((mod) => getModifierState(event, mod));
25
- const hasExtraModifiers = (event, mods, key) => KEYBINDING_MODIFIER_KEYS.some((mod) => !mods.includes(mod) && key !== mod && getModifierState(event, mod));
26
- const matchKeyBindingPress = (event, [mods, key]) => matchesKey(event, key) && hasRequiredModifiers(event, mods) && !hasExtraModifiers(event, mods, key);
27
- const createKeybindingsHandler = (keyBindingMap, { timeout = DEFAULT_TIMEOUT } = {}) => {
28
- const keyBindings = Object.entries(keyBindingMap).map(([binding, callback]) => [parseKeybinding(binding), callback]);
29
- const possibleMatches = /* @__PURE__ */ new Map();
30
- let timer = null;
31
- return (event) => {
32
- if (!(event instanceof KeyboardEvent)) return;
33
- for (const [sequence, callback] of keyBindings) {
34
- const remainingExpectedPresses = possibleMatches.get(sequence) ?? sequence;
35
- const currentExpectedPress = remainingExpectedPresses[0];
36
- if (!currentExpectedPress) continue;
37
- if (!matchKeyBindingPress(event, currentExpectedPress)) {
38
- if (!getModifierState(event, event.key)) possibleMatches.delete(sequence);
39
- } else if (remainingExpectedPresses.length > 1) possibleMatches.set(sequence, remainingExpectedPresses.slice(1));
40
- else {
41
- possibleMatches.delete(sequence);
42
- callback(event);
43
- }
44
- }
45
- if (timer) clearTimeout(timer);
46
- timer = setTimeout(() => possibleMatches.clear(), timeout);
47
- };
48
- };
49
- const keybindings = (target, keyBindingMap, { event = DEFAULT_EVENT, capture, timeout } = {}) => {
50
- const onKeyEvent = createKeybindingsHandler(keyBindingMap, { timeout });
51
- target.addEventListener(event, onKeyEvent, capture);
52
- return () => target.removeEventListener(event, onKeyEvent, capture);
53
- };
54
- //#endregion
55
- //#region src/use-keybindings.ts
56
- const useKeybindings = (keyBindingMap, target, { event, capture, timeout } = {}) => {
57
- const run = useEffectEvent((binding, keyboardEvent) => keyBindingMap[binding]?.(keyboardEvent));
58
- const bindingsKey = Object.keys(keyBindingMap).join("\n");
59
- useEffect(() => {
60
- const bindings = bindingsKey === "" ? [] : bindingsKey.split("\n");
61
- const map = Object.fromEntries(bindings.map((binding) => [binding, (keyboardEvent) => run(binding, keyboardEvent)]));
62
- return keybindings(target ?? window, map, {
63
- event,
64
- capture,
65
- timeout
66
- });
67
- }, [
68
- bindingsKey,
69
- target,
70
- event,
71
- capture,
72
- timeout
73
- ]);
74
- };
75
- //#endregion
76
- export { useKeybindings };
@@ -1,13 +0,0 @@
1
- //#region src/use-local-storage.d.ts
2
- type UseStorageOptions<T> = {
3
- key: string;
4
- defaultValue?: T;
5
- serialize?: (value: T) => string;
6
- deserialize?: (value: string) => T;
7
- };
8
- type UseStorageReturnValue<T> = [T, (val: T | ((prevState: T) => T)) => void, () => void];
9
- type UseStorage = <T>(options: UseStorageOptions<T>) => UseStorageReturnValue<T>;
10
- declare const useLocalStorage: UseStorage;
11
- declare const useSessionStorage: UseStorage;
12
- //#endregion
13
- export { useLocalStorage, useSessionStorage };
@@ -1,76 +0,0 @@
1
- import { n as tryCatchSync } from "./try-catch-D4d3N9_W.js";
2
- import { t as logger } from "./logger-Dse1660l.js";
3
- import { useCallback, useMemo, useSyncExternalStore } from "react";
4
- //#region src/use-local-storage.ts
5
- const serializeJSON = (value, hookName) => {
6
- const result = tryCatchSync(() => JSON.stringify(value));
7
- if (!result.success) throw new Error(`${hookName}: Failed to serialize the value`);
8
- return result.data;
9
- };
10
- const deserializeJSON = (value) => {
11
- const result = tryCatchSync(() => JSON.parse(value));
12
- return result.success ? result.data : value;
13
- };
14
- const createStorageHandler = (type, hookName) => {
15
- const attempt = (fn, failure, fallback) => {
16
- const result = tryCatchSync(fn);
17
- if (result.success) return result.data;
18
- logger.warn(`${hookName}: ${failure}`);
19
- return fallback;
20
- };
21
- const getItem = (key) => attempt(() => window[type].getItem(key), "Failed to get value from storage", null);
22
- const setItem = (key, value) => attempt(() => window[type].setItem(key, value), "Failed to set value to storage", void 0);
23
- const removeItem = (key) => attempt(() => window[type].removeItem(key), "Failed to remove value from storage", void 0);
24
- return {
25
- getItem,
26
- setItem,
27
- removeItem
28
- };
29
- };
30
- const getServerSnapshot = () => null;
31
- const createUseStorage = (type, hookName) => {
32
- const eventName = type === "localStorage" ? "local-storage" : "session-storage";
33
- const { getItem, setItem, removeItem } = createStorageHandler(type, hookName);
34
- const subscribe = (callback) => {
35
- window.addEventListener("storage", callback);
36
- window.addEventListener(eventName, callback);
37
- return () => {
38
- window.removeEventListener("storage", callback);
39
- window.removeEventListener(eventName, callback);
40
- };
41
- };
42
- const defaultSerialize = (value) => serializeJSON(value, hookName);
43
- const notify = () => window.dispatchEvent(new Event(eventName));
44
- return ({ key, defaultValue, deserialize = deserializeJSON, serialize = defaultSerialize }) => {
45
- const getSnapshot = useCallback(() => getItem(key), [key]);
46
- const raw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
47
- const stored = useMemo(() => raw === null ? void 0 : deserialize(raw), [raw, deserialize]);
48
- const setStorageValue = useCallback((val) => {
49
- let next;
50
- if (val instanceof Function) {
51
- const current = getItem(key);
52
- next = val(current === null ? defaultValue : deserialize(current));
53
- } else next = val;
54
- setItem(key, serialize(next));
55
- notify();
56
- }, [
57
- key,
58
- defaultValue,
59
- deserialize,
60
- serialize
61
- ]);
62
- const removeStorageValue = useCallback(() => {
63
- removeItem(key);
64
- notify();
65
- }, [key]);
66
- return [
67
- raw === null ? defaultValue : stored,
68
- setStorageValue,
69
- removeStorageValue
70
- ];
71
- };
72
- };
73
- const useLocalStorage = createUseStorage("localStorage", "use-local-storage");
74
- const useSessionStorage = createUseStorage("sessionStorage", "use-session-storage");
75
- //#endregion
76
- export { useLocalStorage, useSessionStorage };
@@ -1,4 +0,0 @@
1
- //#region src/use-logger.d.ts
2
- declare const useLogger: (componentName: string, props: unknown[]) => void;
3
- //#endregion
4
- export { useLogger };
@@ -1,17 +0,0 @@
1
- import { useDidUpdate } from "./use-did-update.js";
2
- import { t as logger } from "./logger-Dse1660l.js";
3
- import { useEffect, useEffectEvent } from "react";
4
- //#region src/use-logger.ts
5
- const useLogger = (componentName, props) => {
6
- const logMount = useEffectEvent(() => logger.log(`${componentName} mounted`, ...props));
7
- const logUnmount = useEffectEvent(() => logger.log(`${componentName} unmounted`));
8
- useEffect(() => {
9
- logMount();
10
- return () => logUnmount();
11
- }, []);
12
- useDidUpdate(() => {
13
- logger.log(`${componentName} updated`, ...props);
14
- }, props);
15
- };
16
- //#endregion
17
- export { useLogger };
@@ -1,12 +0,0 @@
1
- import { PointerEvent, SyntheticEvent } from "react";
2
- //#region src/use-long-press.d.ts
3
- type LongPressHandlers = {
4
- readonly onPointerDown: (event: PointerEvent) => void;
5
- readonly onPointerMove: (event: PointerEvent) => void;
6
- readonly onPointerUp: () => void;
7
- readonly onPointerCancel: () => void;
8
- readonly onContextMenu: (event: SyntheticEvent) => void;
9
- };
10
- declare const useLongPress: (onLongPress: () => void, disabled?: boolean) => LongPressHandlers;
11
- //#endregion
12
- export { useLongPress };
@@ -1,43 +0,0 @@
1
- import { useCallback, useEffect, useRef } from "react";
2
- //#region src/use-long-press.ts
3
- const HOLD_MS = 500;
4
- const SLOP_PX = 10;
5
- const useLongPress = (onLongPress, disabled = false) => {
6
- const timer = useRef(null);
7
- const origin = useRef(null);
8
- const cancel = useCallback(() => {
9
- if (timer.current !== null) {
10
- clearTimeout(timer.current);
11
- timer.current = null;
12
- }
13
- origin.current = null;
14
- }, []);
15
- useEffect(() => cancel, [cancel]);
16
- return {
17
- onPointerDown: useCallback((event) => {
18
- if (disabled) return;
19
- cancel();
20
- origin.current = {
21
- x: event.clientX,
22
- y: event.clientY
23
- };
24
- timer.current = setTimeout(() => {
25
- timer.current = null;
26
- onLongPress();
27
- }, HOLD_MS);
28
- }, [
29
- disabled,
30
- onLongPress,
31
- cancel
32
- ]),
33
- onPointerMove: useCallback((event) => {
34
- if (!origin.current) return;
35
- if (Math.abs(event.clientX - origin.current.x) > SLOP_PX || Math.abs(event.clientY - origin.current.y) > SLOP_PX) cancel();
36
- }, [cancel]),
37
- onPointerUp: cancel,
38
- onPointerCancel: cancel,
39
- onContextMenu: useCallback((event) => event.preventDefault(), [])
40
- };
41
- };
42
- //#endregion
43
- export { useLongPress };
@@ -1,8 +0,0 @@
1
- //#region src/use-measure.d.ts
2
- type Dimensions = {
3
- width: number | null;
4
- height: number | null;
5
- };
6
- declare const useMeasure: () => [(node: Element | null) => void, Dimensions];
7
- //#endregion
8
- export { useMeasure };