@musecat/functionkit 1.0.0 → 1.0.2
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/.agents/references/components/ScrolltoTop.md +36 -0
- package/.agents/references/components/SwitchCase.md +47 -0
- package/.agents/references/components/ViewportPortal.md +34 -0
- package/.agents/references/cookie/cookie.md +47 -0
- package/.agents/references/datetime/dateTime.client.md +42 -0
- package/.agents/references/datetime/dateTime.server.md +42 -0
- package/.agents/references/datetime/dateTime.shared.md +86 -0
- package/.agents/references/hooks/useAvoidKeyboard.md +29 -0
- package/.agents/references/hooks/useCheckInvisible.md +36 -0
- package/.agents/references/hooks/useCheckScroll.md +36 -0
- package/.agents/references/hooks/useClientDateTime.md +39 -0
- package/.agents/references/hooks/useDebounce.md +46 -0
- package/.agents/references/hooks/useDebouncedCallback.md +34 -0
- package/.agents/references/hooks/useDoubleClick.md +40 -0
- package/.agents/references/hooks/useGeolocation.md +37 -0
- package/.agents/references/hooks/useHasMounted.md +27 -0
- package/.agents/references/hooks/useIntersectionObserver.md +37 -0
- package/.agents/references/hooks/useInterval.md +40 -0
- package/.agents/references/hooks/useKeyboardHeight.md +33 -0
- package/.agents/references/hooks/useKeyboardListNavigation.md +58 -0
- package/.agents/references/hooks/useLongPress.md +48 -0
- package/.agents/references/hooks/usePreservedCallback.md +33 -0
- package/.agents/references/hooks/usePreservedReference.md +34 -0
- package/.agents/references/hooks/useRefEffect.md +35 -0
- package/.agents/references/hooks/useRelativeDateTime.md +35 -0
- package/.agents/references/hooks/useTimeout.md +42 -0
- package/.agents/references/hooks/useToggleState.md +39 -0
- package/.agents/references/hooks/useViewportHeight.md +26 -0
- package/.agents/references/hooks/useViewportMatch.md +32 -0
- package/.agents/references/utils/browserStorage.md +41 -0
- package/.agents/references/utils/buildContext.md +47 -0
- package/.agents/references/utils/clipboardShare.md +59 -0
- package/.agents/references/utils/floatingMotion.md +67 -0
- package/.agents/references/utils/getDeviceInfo.md +43 -0
- package/.agents/references/utils/isEditableKeyboardTarget.md +35 -0
- package/.agents/references/utils/mergeRefs.md +29 -0
- package/.agents/references/utils/seen.md +37 -0
- package/.agents/references/utils/subscribeKeyboardHeight.md +35 -0
- package/AGENTS.md +103 -396
- package/LICENSE +9 -0
- package/README.md +38 -0
- package/index.ts +97 -102
- package/package.json +27 -13
- package/{components → packages/components}/ScrolltoTop.tsx +3 -3
- package/{components → packages/components}/SwitchCase.tsx +7 -7
- package/packages/components/ViewportPortal.tsx +40 -0
- package/packages/cookie/cookie.shared.ts +138 -0
- package/packages/datetime/dateTime.client.ts +108 -0
- package/packages/datetime/dateTime.server.ts +108 -0
- package/packages/datetime/dateTime.shared.ts +358 -0
- package/packages/hooks/useAvoidKeyboard.ts +28 -0
- package/packages/hooks/useCheckInvisible.ts +25 -0
- package/packages/hooks/useCheckScroll.ts +19 -0
- package/packages/hooks/useClientDateTime.ts +49 -0
- package/packages/hooks/useDebounce.ts +118 -0
- package/packages/hooks/useDebouncedCallback.ts +69 -0
- package/packages/hooks/useDoubleClick.ts +53 -0
- package/packages/hooks/useGeolocation.ts +146 -0
- package/packages/hooks/useHasMounted.ts +13 -0
- package/packages/hooks/useIntersectionObserver.ts +34 -0
- package/packages/hooks/useInterval.ts +56 -0
- package/packages/hooks/useKeyboardHeight.ts +18 -0
- package/packages/hooks/useKeyboardListNavigation.ts +170 -0
- package/packages/hooks/useLongPress.ts +120 -0
- package/packages/hooks/usePreservedCallback.ts +21 -0
- package/packages/hooks/usePreservedReference.ts +21 -0
- package/packages/hooks/useRefEffect.ts +35 -0
- package/packages/hooks/useRelativeDateTime.ts +54 -0
- package/packages/hooks/useTimeout.ts +56 -0
- package/packages/hooks/useToggleState.ts +13 -0
- package/packages/hooks/useViewportHeight.ts +23 -0
- package/packages/hooks/useViewportMatch.ts +27 -0
- package/packages/utils/browserStorage.ts +59 -0
- package/packages/utils/buildContext.tsx +19 -0
- package/packages/utils/checkDevice.ts +74 -0
- package/packages/utils/clipboardShare.tsx +47 -0
- package/{utils → packages/utils}/clipboardShare.types.ts +7 -7
- package/packages/utils/floatingMotion.ts +99 -0
- package/packages/utils/keyboardTarget.ts +13 -0
- package/packages/utils/mergeRefs.ts +15 -0
- package/packages/utils/seen.ts +29 -0
- package/packages/utils/subscribeKeyboardHeight.ts +54 -0
- package/tests/components/ScrolltoTop.test.tsx +15 -0
- package/tests/components/SwitchCase.test.tsx +34 -0
- package/tests/components/ViewportPortal.test.tsx +42 -0
- package/tests/cookie/cookie.test.ts +156 -0
- package/tests/datetime/datetime.test.ts +250 -0
- package/tests/hooks/useAvoidKeyboard.test.ts +12 -0
- package/tests/hooks/useCheckInvisible.test.ts +28 -0
- package/tests/hooks/useCheckScroll.test.ts +23 -0
- package/tests/hooks/useClientDateTime.test.ts +14 -0
- package/tests/hooks/useDebounce.test.ts +71 -0
- package/tests/hooks/useDebouncedCallback.test.ts +34 -0
- package/tests/hooks/useDoubleClick.test.ts +31 -0
- package/tests/hooks/useGeolocation.test.ts +50 -0
- package/tests/hooks/useHasMounted.test.ts +10 -0
- package/tests/hooks/useIntersectionObserver.test.ts +13 -0
- package/tests/hooks/useInterval.test.ts +31 -0
- package/tests/hooks/useKeyboardHeight.test.ts +10 -0
- package/tests/hooks/useKeyboardListNavigation.test.ts +82 -0
- package/tests/hooks/useLongPress.test.ts +52 -0
- package/tests/hooks/usePreservedCallback.test.ts +35 -0
- package/tests/hooks/usePreservedReference.test.ts +35 -0
- package/tests/hooks/useRefEffect.test.ts +30 -0
- package/tests/hooks/useRelativeDateTime.test.ts +19 -0
- package/tests/hooks/useTimeout.test.ts +34 -0
- package/tests/hooks/useToggleState.test.ts +35 -0
- package/tests/hooks/useViewportHeight.test.ts +10 -0
- package/tests/hooks/useViewportMatch.test.ts +35 -0
- package/tests/setup.ts +9 -0
- package/tests/utils/browserStorage.test.ts +54 -0
- package/tests/utils/buildContext.test.tsx +27 -0
- package/tests/utils/checkDevice.test.ts +84 -0
- package/tests/utils/clipboardShare.test.ts +78 -0
- package/tests/utils/floatingMotion.test.ts +84 -0
- package/tests/utils/keyboardTarget.test.ts +44 -0
- package/tests/utils/mergeRefs.test.ts +18 -0
- package/tests/utils/seen.test.ts +31 -0
- package/tests/utils/subscribeKeyboardHeight.test.ts +50 -0
- package/tsconfig.json +16 -16
- package/vitest.config.ts +16 -0
- package/components/ViewportPortal.tsx +0 -40
- package/cookie/cookie.shared.ts +0 -142
- package/datetime/dateTime.client.ts +0 -108
- package/datetime/dateTime.server.ts +0 -108
- package/datetime/dateTime.shared.ts +0 -358
- package/hooks/useAvoidKeyboard.ts +0 -28
- package/hooks/useCheckInvisible.ts +0 -25
- package/hooks/useCheckScroll.ts +0 -19
- package/hooks/useClientDateTime.ts +0 -49
- package/hooks/useDebounce.ts +0 -118
- package/hooks/useDebouncedCallback.ts +0 -69
- package/hooks/useDoubleClick.ts +0 -53
- package/hooks/useGeolocation.ts +0 -146
- package/hooks/useHasMounted.ts +0 -13
- package/hooks/useIntersectionObserver.ts +0 -34
- package/hooks/useInterval.ts +0 -55
- package/hooks/useKeyboardHeight.ts +0 -18
- package/hooks/useKeyboardListNavigation.ts +0 -170
- package/hooks/useLongPress.ts +0 -120
- package/hooks/usePreservedCallback.ts +0 -21
- package/hooks/usePreservedReference.ts +0 -21
- package/hooks/useRefEffect.ts +0 -35
- package/hooks/useRelativeDateTime.ts +0 -54
- package/hooks/useTimeout.ts +0 -56
- package/hooks/useToggleState.ts +0 -13
- package/hooks/useViewportHeight.ts +0 -23
- package/hooks/useViewportMatch.ts +0 -33
- package/utils/browserStorage.ts +0 -59
- package/utils/buildContext.tsx +0 -19
- package/utils/checkDevice.ts +0 -74
- package/utils/clipboardShare.tsx +0 -47
- package/utils/floatingMotion.ts +0 -99
- package/utils/keyboardTarget.ts +0 -13
- package/utils/mergeRefs.ts +0 -15
- package/utils/seen.ts +0 -29
- package/utils/subscribeKeyboardHeight.ts +0 -54
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useMemo, useRef } from "react";
|
|
4
|
-
|
|
5
|
-
function areDeeplyEqual<T>(a: T, b: T): boolean {
|
|
6
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function usePreservedReference<T>(
|
|
10
|
-
value: T,
|
|
11
|
-
areValuesEqual: (a: T, b: T) => boolean = areDeeplyEqual,
|
|
12
|
-
): T {
|
|
13
|
-
const ref = useRef(value);
|
|
14
|
-
|
|
15
|
-
return useMemo(() => {
|
|
16
|
-
if (!areValuesEqual(ref.current, value)) {
|
|
17
|
-
ref.current = value;
|
|
18
|
-
}
|
|
19
|
-
return ref.current;
|
|
20
|
-
}, [areValuesEqual, value]);
|
|
21
|
-
}
|
package/hooks/useRefEffect.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { type DependencyList, useCallback, useRef } from "react";
|
|
4
|
-
|
|
5
|
-
import { usePreservedCallback } from "./usePreservedCallback";
|
|
6
|
-
|
|
7
|
-
export type CleanupCallback = () => void;
|
|
8
|
-
|
|
9
|
-
export function useRefEffect<RefElement extends HTMLElement = HTMLElement>(
|
|
10
|
-
callback: (element: RefElement) => CleanupCallback | undefined,
|
|
11
|
-
deps: DependencyList,
|
|
12
|
-
): (element: RefElement | null) => void {
|
|
13
|
-
const preservedCallback = usePreservedCallback(callback);
|
|
14
|
-
const cleanupCallbackRef = useRef<CleanupCallback>(() => {});
|
|
15
|
-
|
|
16
|
-
const effect = useCallback(
|
|
17
|
-
(element: RefElement | null) => {
|
|
18
|
-
cleanupCallbackRef.current();
|
|
19
|
-
cleanupCallbackRef.current = () => {};
|
|
20
|
-
|
|
21
|
-
if (element == null) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const cleanup = preservedCallback(element);
|
|
26
|
-
|
|
27
|
-
if (typeof cleanup === "function") {
|
|
28
|
-
cleanupCallbackRef.current = cleanup;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
[preservedCallback, ...deps],
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
return effect;
|
|
35
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { formatClientRelative } from "../datetime/dateTime.client";
|
|
5
|
-
import type { DateInput, DatePreset } from "../datetime/dateTime.shared";
|
|
6
|
-
import { toDate } from "../datetime/dateTime.shared";
|
|
7
|
-
|
|
8
|
-
type UseRelativeDateTimeOptions = {
|
|
9
|
-
locale?: string;
|
|
10
|
-
maxRelativeDays?: number;
|
|
11
|
-
fallbackDatePreset?: DatePreset;
|
|
12
|
-
updateIntervalMs?: number;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function useRelativeDateTime(
|
|
16
|
-
value: DateInput,
|
|
17
|
-
options?: UseRelativeDateTimeOptions,
|
|
18
|
-
) {
|
|
19
|
-
const [ready, setReady] = useState(false);
|
|
20
|
-
const [text, setText] = useState("");
|
|
21
|
-
const [isRelative, setIsRelative] = useState(false);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const update = () => {
|
|
25
|
-
const next = formatClientRelative(value, {
|
|
26
|
-
locale: options?.locale,
|
|
27
|
-
maxRelativeDays: options?.maxRelativeDays,
|
|
28
|
-
fallbackDatePreset: options?.fallbackDatePreset,
|
|
29
|
-
now: Date.now(),
|
|
30
|
-
});
|
|
31
|
-
setText(next.text);
|
|
32
|
-
setIsRelative(next.isRelative);
|
|
33
|
-
setReady(true);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
update();
|
|
37
|
-
|
|
38
|
-
const timer = window.setInterval(update, options?.updateIntervalMs ?? 1000);
|
|
39
|
-
return () => window.clearInterval(timer);
|
|
40
|
-
}, [
|
|
41
|
-
options?.fallbackDatePreset,
|
|
42
|
-
options?.locale,
|
|
43
|
-
options?.maxRelativeDays,
|
|
44
|
-
options?.updateIntervalMs,
|
|
45
|
-
value,
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
ready,
|
|
50
|
-
text,
|
|
51
|
-
isRelative,
|
|
52
|
-
date: toDate(value),
|
|
53
|
-
};
|
|
54
|
-
}
|
package/hooks/useTimeout.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
-
|
|
5
|
-
import { usePreservedCallback } from "./usePreservedCallback";
|
|
6
|
-
|
|
7
|
-
type UseTimeoutOptions = {
|
|
8
|
-
enabled?: boolean;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type UseTimeoutControls = {
|
|
12
|
-
start: (overrideDelay?: number) => void;
|
|
13
|
-
reset: (overrideDelay?: number) => void;
|
|
14
|
-
clear: () => void;
|
|
15
|
-
isPending: () => boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function useTimeout(
|
|
19
|
-
callback: () => void,
|
|
20
|
-
delay = 0,
|
|
21
|
-
options?: UseTimeoutOptions,
|
|
22
|
-
): UseTimeoutControls {
|
|
23
|
-
const { enabled = true } = options ?? {};
|
|
24
|
-
const preservedCallback = usePreservedCallback(callback);
|
|
25
|
-
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
26
|
-
|
|
27
|
-
const clear = useCallback(() => {
|
|
28
|
-
if (timeoutRef.current !== null) {
|
|
29
|
-
clearTimeout(timeoutRef.current);
|
|
30
|
-
timeoutRef.current = null;
|
|
31
|
-
}
|
|
32
|
-
}, []);
|
|
33
|
-
|
|
34
|
-
const start = useCallback(
|
|
35
|
-
(overrideDelay?: number) => {
|
|
36
|
-
clear();
|
|
37
|
-
timeoutRef.current = setTimeout(() => {
|
|
38
|
-
timeoutRef.current = null;
|
|
39
|
-
preservedCallback();
|
|
40
|
-
}, overrideDelay ?? delay);
|
|
41
|
-
},
|
|
42
|
-
[clear, delay, preservedCallback],
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const isPending = useCallback(() => timeoutRef.current !== null, []);
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (!enabled) return;
|
|
49
|
-
start();
|
|
50
|
-
return clear;
|
|
51
|
-
}, [enabled, start, clear]);
|
|
52
|
-
|
|
53
|
-
useEffect(() => clear, [clear]);
|
|
54
|
-
|
|
55
|
-
return { start, reset: start, clear, isPending };
|
|
56
|
-
}
|
package/hooks/useToggleState.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useCallback, useState } from "react";
|
|
4
|
-
|
|
5
|
-
export function useToggleState(initialValue = false) {
|
|
6
|
-
const [value, setValue] = useState(initialValue);
|
|
7
|
-
|
|
8
|
-
const setTrue = useCallback(() => setValue(true), []);
|
|
9
|
-
const setFalse = useCallback(() => setValue(false), []);
|
|
10
|
-
const toggle = useCallback(() => setValue((v) => !v), []);
|
|
11
|
-
|
|
12
|
-
return { value, setValue, setTrue, setFalse, toggle };
|
|
13
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
|
|
5
|
-
export function useViewportHeight() {
|
|
6
|
-
const [height, setHeight] = useState(0);
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const update = () =>
|
|
10
|
-
setHeight(window.visualViewport?.height ?? window.innerHeight);
|
|
11
|
-
|
|
12
|
-
update();
|
|
13
|
-
window.visualViewport?.addEventListener("resize", update);
|
|
14
|
-
window.addEventListener("resize", update);
|
|
15
|
-
|
|
16
|
-
return () => {
|
|
17
|
-
window.visualViewport?.removeEventListener("resize", update);
|
|
18
|
-
window.removeEventListener("resize", update);
|
|
19
|
-
};
|
|
20
|
-
}, []);
|
|
21
|
-
|
|
22
|
-
return { height };
|
|
23
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tracks whether the current viewport matches a media query.
|
|
7
|
-
*
|
|
8
|
-
* @param mediaQuery CSS media query string.
|
|
9
|
-
* @returns True when current viewport matches the given query.
|
|
10
|
-
*/
|
|
11
|
-
export function useViewportMatch(mediaQuery: string): boolean {
|
|
12
|
-
const [isMatched, setIsMatched] = useState(false);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (typeof window === "undefined") {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const mediaQueryList = window.matchMedia(mediaQuery);
|
|
20
|
-
const syncMatchState = () => {
|
|
21
|
-
setIsMatched(mediaQueryList.matches);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
syncMatchState();
|
|
25
|
-
mediaQueryList.addEventListener("change", syncMatchState);
|
|
26
|
-
|
|
27
|
-
return () => {
|
|
28
|
-
mediaQueryList.removeEventListener("change", syncMatchState);
|
|
29
|
-
};
|
|
30
|
-
}, [mediaQuery]);
|
|
31
|
-
|
|
32
|
-
return isMatched;
|
|
33
|
-
}
|
package/utils/browserStorage.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
type StorageKind = "localStorage" | "sessionStorage";
|
|
4
|
-
|
|
5
|
-
function getStorage(kind: StorageKind): Storage | null {
|
|
6
|
-
if (typeof window === "undefined") return null;
|
|
7
|
-
return window[kind];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function saveStorage(kind: StorageKind, key: string, value: unknown) {
|
|
11
|
-
const storage = getStorage(kind);
|
|
12
|
-
if (!storage) return;
|
|
13
|
-
try {
|
|
14
|
-
storage.setItem(key, JSON.stringify(value));
|
|
15
|
-
} catch {}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function loadStorage(kind: StorageKind, key: string): unknown {
|
|
19
|
-
const storage = getStorage(kind);
|
|
20
|
-
if (!storage) return null;
|
|
21
|
-
try {
|
|
22
|
-
const raw = storage.getItem(key);
|
|
23
|
-
return raw === null ? null : (JSON.parse(raw) as unknown);
|
|
24
|
-
} catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function removeStorage(kind: StorageKind, key: string) {
|
|
30
|
-
const storage = getStorage(kind);
|
|
31
|
-
if (!storage) return;
|
|
32
|
-
try {
|
|
33
|
-
storage.removeItem(key);
|
|
34
|
-
} catch {}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getLocalStorage(key: string): unknown {
|
|
38
|
-
return loadStorage("localStorage", key);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function updateLocalStorage(key: string, value: unknown) {
|
|
42
|
-
saveStorage("localStorage", key, value);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function removeLocalStorage(key: string) {
|
|
46
|
-
removeStorage("localStorage", key);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function getSessionStorage(key: string): unknown {
|
|
50
|
-
return loadStorage("sessionStorage", key);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function updateSessionStorage(key: string, value: unknown) {
|
|
54
|
-
saveStorage("sessionStorage", key, value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function removeSessionStorage(key: string) {
|
|
58
|
-
removeStorage("sessionStorage", key);
|
|
59
|
-
}
|
package/utils/buildContext.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { createContext, type ReactNode, useContext, useMemo } from "react";
|
|
4
|
-
|
|
5
|
-
export function buildContext<T>(defaultValue: T) {
|
|
6
|
-
const Ctx = createContext<T>(defaultValue);
|
|
7
|
-
|
|
8
|
-
function Provider({ value, children }: { value: T; children: ReactNode }) {
|
|
9
|
-
const stable = useMemo(() => value, [value]);
|
|
10
|
-
return <Ctx.Provider value={stable}>{children}</Ctx.Provider>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function useValue() {
|
|
14
|
-
const ctx = useContext(Ctx);
|
|
15
|
-
return ctx;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return [Provider, useValue] as const;
|
|
19
|
-
}
|
package/utils/checkDevice.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export type DeviceInfo = {
|
|
2
|
-
isMobile: boolean;
|
|
3
|
-
isIOS: boolean;
|
|
4
|
-
isAndroid: boolean;
|
|
5
|
-
isSafari: boolean;
|
|
6
|
-
isIOSSafari: boolean;
|
|
7
|
-
isMacSafari: boolean;
|
|
8
|
-
isSamsungBrowser: boolean;
|
|
9
|
-
isTouchDevice: boolean;
|
|
10
|
-
browser: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function getDeviceInfo(): DeviceInfo {
|
|
14
|
-
let isMobile = false;
|
|
15
|
-
let isIOS = false;
|
|
16
|
-
let isAndroid = false;
|
|
17
|
-
let isSafari = false;
|
|
18
|
-
let isIOSSafari = false;
|
|
19
|
-
let isMacSafari = false;
|
|
20
|
-
let isSamsungBrowser = false;
|
|
21
|
-
let isTouchDevice = false;
|
|
22
|
-
let browser = "unknown";
|
|
23
|
-
|
|
24
|
-
if (typeof window === "undefined") {
|
|
25
|
-
return {
|
|
26
|
-
isMobile,
|
|
27
|
-
isIOS,
|
|
28
|
-
isAndroid,
|
|
29
|
-
isSafari,
|
|
30
|
-
isIOSSafari,
|
|
31
|
-
isMacSafari,
|
|
32
|
-
isSamsungBrowser,
|
|
33
|
-
isTouchDevice,
|
|
34
|
-
browser,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const ua = navigator.userAgent;
|
|
39
|
-
|
|
40
|
-
isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(ua);
|
|
41
|
-
isIOS = /iPhone|iPad|iPod/i.test(ua);
|
|
42
|
-
isAndroid = /Android/i.test(ua);
|
|
43
|
-
isSafari = /Safari/i.test(ua) && !/Chrome/i.test(ua);
|
|
44
|
-
isIOSSafari = isIOS && isSafari;
|
|
45
|
-
isMacSafari = /Macintosh/i.test(ua) && isSafari && !isIOS;
|
|
46
|
-
isSamsungBrowser = /SamsungBrowser/i.test(ua);
|
|
47
|
-
isTouchDevice = "ontouchstart" in window;
|
|
48
|
-
|
|
49
|
-
if (isSamsungBrowser) {
|
|
50
|
-
browser = "samsung";
|
|
51
|
-
} else if (isSafari) {
|
|
52
|
-
browser = "safari";
|
|
53
|
-
} else if (/Chrome/i.test(ua)) {
|
|
54
|
-
browser = "chrome";
|
|
55
|
-
} else if (/Firefox/i.test(ua)) {
|
|
56
|
-
browser = "firefox";
|
|
57
|
-
} else if (/Edge/i.test(ua)) {
|
|
58
|
-
browser = "edge";
|
|
59
|
-
} else {
|
|
60
|
-
browser = "unknown";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
isMobile,
|
|
65
|
-
isIOS,
|
|
66
|
-
isAndroid,
|
|
67
|
-
isSafari,
|
|
68
|
-
isIOSSafari,
|
|
69
|
-
isMacSafari,
|
|
70
|
-
isSamsungBrowser,
|
|
71
|
-
isTouchDevice,
|
|
72
|
-
browser,
|
|
73
|
-
};
|
|
74
|
-
}
|
package/utils/clipboardShare.tsx
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
NavigatorClipboardProps,
|
|
5
|
-
NavigatorClipboardResult,
|
|
6
|
-
NavigatorShareProps,
|
|
7
|
-
NavigatorShareResult,
|
|
8
|
-
} from "./clipboardShare.types";
|
|
9
|
-
|
|
10
|
-
export function NavigatorClipboard({
|
|
11
|
-
text,
|
|
12
|
-
}: NavigatorClipboardProps): Promise<NavigatorClipboardResult> {
|
|
13
|
-
return navigator.clipboard.writeText(text).then(
|
|
14
|
-
() => ({ success: true }),
|
|
15
|
-
() => ({ success: false }),
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function NavigatorShare({
|
|
20
|
-
title,
|
|
21
|
-
text,
|
|
22
|
-
link,
|
|
23
|
-
}: NavigatorShareProps): Promise<NavigatorShareResult> {
|
|
24
|
-
if (navigator.share) {
|
|
25
|
-
return navigator.share({ title, text, url: link }).then(
|
|
26
|
-
() => ({ success: true, method: "share" }),
|
|
27
|
-
(error) => {
|
|
28
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
29
|
-
return { success: false, method: "share" };
|
|
30
|
-
}
|
|
31
|
-
return clipboardFallback(link);
|
|
32
|
-
},
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return clipboardFallback(link);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function clipboardFallback(link: string): Promise<NavigatorShareResult> {
|
|
40
|
-
if (navigator.clipboard) {
|
|
41
|
-
return navigator.clipboard.writeText(link).then(
|
|
42
|
-
() => ({ success: true, method: "clipboard" }),
|
|
43
|
-
() => ({ success: false, method: "unsupported" }),
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return Promise.resolve({ success: false, method: "unsupported" });
|
|
47
|
-
}
|
package/utils/floatingMotion.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export type FloatingMotionMode =
|
|
2
|
-
| "anchored"
|
|
3
|
-
| "center-selected"
|
|
4
|
-
| "modal-center"
|
|
5
|
-
| "mobile-sheet";
|
|
6
|
-
|
|
7
|
-
export type FloatingPlacement =
|
|
8
|
-
| "top-left"
|
|
9
|
-
| "top-center"
|
|
10
|
-
| "top-right"
|
|
11
|
-
| "middle-left"
|
|
12
|
-
| "middle-center"
|
|
13
|
-
| "middle-right"
|
|
14
|
-
| "bottom-left"
|
|
15
|
-
| "bottom-center"
|
|
16
|
-
| "bottom-right";
|
|
17
|
-
|
|
18
|
-
export interface FloatingMotionPreset {
|
|
19
|
-
enterMs: number;
|
|
20
|
-
exitMs: number;
|
|
21
|
-
ease: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const getFloatingMotionPreset = (
|
|
25
|
-
mode: FloatingMotionMode,
|
|
26
|
-
): FloatingMotionPreset => {
|
|
27
|
-
if (mode === "mobile-sheet") {
|
|
28
|
-
return {
|
|
29
|
-
enterMs: 360,
|
|
30
|
-
exitMs: 280,
|
|
31
|
-
ease: "cubic-bezier(0.2, 0.8, 0.2, 1)",
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (mode === "modal-center") {
|
|
36
|
-
return {
|
|
37
|
-
enterMs: 340,
|
|
38
|
-
exitMs: 260,
|
|
39
|
-
ease: "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (mode === "center-selected") {
|
|
44
|
-
return {
|
|
45
|
-
enterMs: 190,
|
|
46
|
-
exitMs: 140,
|
|
47
|
-
ease: "cubic-bezier(0.2, 0, 0, 1)",
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
enterMs: 230,
|
|
53
|
-
exitMs: 170,
|
|
54
|
-
ease: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const getFloatingTransformOrigin = (
|
|
59
|
-
placement?: FloatingPlacement,
|
|
60
|
-
): string => {
|
|
61
|
-
if (!placement) return "top center";
|
|
62
|
-
|
|
63
|
-
const [row, col] = placement.split("-") as [
|
|
64
|
-
"top" | "middle" | "bottom",
|
|
65
|
-
"left" | "center" | "right",
|
|
66
|
-
];
|
|
67
|
-
const y = row === "top" ? "bottom" : row === "bottom" ? "top" : "center";
|
|
68
|
-
const x = col === "right" ? "right" : col === "left" ? "left" : "center";
|
|
69
|
-
|
|
70
|
-
return `${y} ${x}`;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const getFloatingHiddenTransform = ({
|
|
74
|
-
mode,
|
|
75
|
-
placement,
|
|
76
|
-
}: {
|
|
77
|
-
mode: FloatingMotionMode;
|
|
78
|
-
placement?: FloatingPlacement;
|
|
79
|
-
}): string => {
|
|
80
|
-
if (mode === "mobile-sheet") return "translateY(1.8rem) scale(1)";
|
|
81
|
-
if (mode === "modal-center") return "translateY(.8rem) scale(.94)";
|
|
82
|
-
if (mode === "center-selected") return "translateY(.45rem) scale(.99)";
|
|
83
|
-
if (!placement) return "translateY(.4rem) scale(.975)";
|
|
84
|
-
|
|
85
|
-
const [row, col] = placement.split("-") as [
|
|
86
|
-
"top" | "middle" | "bottom",
|
|
87
|
-
"left" | "center" | "right",
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
if (row === "top") return "translateY(.4rem) scale(.975)";
|
|
91
|
-
if (row === "bottom") return "translateY(-.4rem) scale(.975)";
|
|
92
|
-
|
|
93
|
-
if (row === "middle") {
|
|
94
|
-
if (col === "left") return "translateX(.4rem) scale(.975)";
|
|
95
|
-
if (col === "right") return "translateX(-.4rem) scale(.975)";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return "translateY(.4rem) scale(.975)";
|
|
99
|
-
};
|
package/utils/keyboardTarget.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
export function isEditableKeyboardTarget(target: HTMLElement) {
|
|
4
|
-
return (
|
|
5
|
-
target.isContentEditable ||
|
|
6
|
-
target.tagName === "TEXTAREA" ||
|
|
7
|
-
target.tagName === "SELECT" ||
|
|
8
|
-
(target.tagName === "INPUT" &&
|
|
9
|
-
(target as HTMLInputElement).type !== "button" &&
|
|
10
|
-
(target as HTMLInputElement).type !== "checkbox" &&
|
|
11
|
-
(target as HTMLInputElement).type !== "radio")
|
|
12
|
-
);
|
|
13
|
-
}
|
package/utils/mergeRefs.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Ref, RefCallback } from "react";
|
|
2
|
-
|
|
3
|
-
type ReactRef<T> = Ref<T> | undefined | null;
|
|
4
|
-
|
|
5
|
-
export function mergeRefs<T>(...refs: ReactRef<T>[]): RefCallback<T> {
|
|
6
|
-
return (value: T | null) => {
|
|
7
|
-
for (const ref of refs) {
|
|
8
|
-
if (typeof ref === "function") {
|
|
9
|
-
ref(value);
|
|
10
|
-
} else if (ref && "current" in ref) {
|
|
11
|
-
(ref as { current: T | null }).current = value;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
}
|
package/utils/seen.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export const SEEN_STORAGE_KEY = "seen";
|
|
2
|
-
|
|
3
|
-
type SeenMap = Partial<Record<string, boolean>>;
|
|
4
|
-
|
|
5
|
-
export function parseSeen(raw: string | null): SeenMap {
|
|
6
|
-
if (!raw) return {};
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
10
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11
|
-
return {};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return parsed as SeenMap;
|
|
15
|
-
} catch {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function hasSeenKey(raw: string | null, key: string) {
|
|
21
|
-
const map = parseSeen(raw);
|
|
22
|
-
return map[key] === true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function buildSeenValue(raw: string | null, key: string): string {
|
|
26
|
-
const map = parseSeen(raw);
|
|
27
|
-
map[key] = true;
|
|
28
|
-
return JSON.stringify(map);
|
|
29
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
type SubscribeKeyboardHeightOptions = {
|
|
4
|
-
callback: (height: number) => void;
|
|
5
|
-
immediate?: boolean;
|
|
6
|
-
throttleMs?: number;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function subscribeKeyboardHeight({
|
|
10
|
-
callback,
|
|
11
|
-
immediate = false,
|
|
12
|
-
throttleMs = 16,
|
|
13
|
-
}: SubscribeKeyboardHeightOptions) {
|
|
14
|
-
let ticking = false;
|
|
15
|
-
let lastHeight = 0;
|
|
16
|
-
|
|
17
|
-
const notify = (height: number) => {
|
|
18
|
-
if (ticking) return;
|
|
19
|
-
ticking = true;
|
|
20
|
-
|
|
21
|
-
const run = () => {
|
|
22
|
-
if (height !== lastHeight) {
|
|
23
|
-
lastHeight = height;
|
|
24
|
-
callback(height);
|
|
25
|
-
}
|
|
26
|
-
ticking = false;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (throttleMs > 0) {
|
|
30
|
-
setTimeout(run, throttleMs);
|
|
31
|
-
} else {
|
|
32
|
-
run();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const handleResize = () => {
|
|
37
|
-
const vv = window.visualViewport;
|
|
38
|
-
if (!vv) return;
|
|
39
|
-
const diff = window.innerHeight - vv.height;
|
|
40
|
-
notify(diff > 0 ? diff : 0);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (immediate) handleResize();
|
|
44
|
-
|
|
45
|
-
window.visualViewport?.addEventListener("resize", handleResize);
|
|
46
|
-
window.visualViewport?.addEventListener("scroll", handleResize);
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
unsubscribe() {
|
|
50
|
-
window.visualViewport?.removeEventListener("resize", handleResize);
|
|
51
|
-
window.visualViewport?.removeEventListener("scroll", handleResize);
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|