@musecat/functionkit 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +1 -1
- package/package.json +37 -2
- package/packages/components/ScrolltoTop.tsx +1 -1
- package/packages/components/ViewportPortal.tsx +1 -2
- package/packages/cookie/cookie.shared.ts +17 -35
- package/packages/cookie/cookieNames.shared.ts +9 -0
- package/packages/datetime/dateTime.client.ts +1 -3
- package/packages/datetime/dateTime.server.ts +1 -3
- package/packages/datetime/dateTime.shared.ts +9 -47
- package/packages/hooks/useCheckInvisible.ts +1 -1
- package/packages/hooks/useClientDateTime.ts +3 -16
- package/packages/hooks/useDebounce.ts +2 -7
- package/packages/hooks/useGeolocation.ts +1 -5
- package/packages/hooks/useInterval.ts +1 -2
- package/packages/hooks/useKeyboardListNavigation.ts +10 -38
- package/packages/hooks/useLongPress.ts +13 -24
- package/packages/hooks/useRelativeDateTime.ts +1 -4
- package/packages/hooks/useViewportHeight.ts +1 -2
- package/packages/hooks/useViewportMatch.ts +0 -4
- package/packages/utils/buildContext.tsx +2 -3
- package/packages/utils/floatingMotion.ts +15 -23
- package/packages/utils/subscribeKeyboardHeight.ts +3 -0
- package/.agents/references/components/ScrolltoTop.md +0 -36
- package/.agents/references/components/SwitchCase.md +0 -47
- package/.agents/references/components/ViewportPortal.md +0 -34
- package/.agents/references/cookie/cookie.md +0 -47
- package/.agents/references/datetime/dateTime.client.md +0 -42
- package/.agents/references/datetime/dateTime.server.md +0 -42
- package/.agents/references/datetime/dateTime.shared.md +0 -86
- package/.agents/references/hooks/useAvoidKeyboard.md +0 -29
- package/.agents/references/hooks/useCheckInvisible.md +0 -36
- package/.agents/references/hooks/useCheckScroll.md +0 -36
- package/.agents/references/hooks/useClientDateTime.md +0 -39
- package/.agents/references/hooks/useDebounce.md +0 -46
- package/.agents/references/hooks/useDebouncedCallback.md +0 -34
- package/.agents/references/hooks/useDoubleClick.md +0 -40
- package/.agents/references/hooks/useGeolocation.md +0 -37
- package/.agents/references/hooks/useHasMounted.md +0 -27
- package/.agents/references/hooks/useIntersectionObserver.md +0 -37
- package/.agents/references/hooks/useInterval.md +0 -40
- package/.agents/references/hooks/useKeyboardHeight.md +0 -33
- package/.agents/references/hooks/useKeyboardListNavigation.md +0 -58
- package/.agents/references/hooks/useLongPress.md +0 -48
- package/.agents/references/hooks/usePreservedCallback.md +0 -33
- package/.agents/references/hooks/usePreservedReference.md +0 -34
- package/.agents/references/hooks/useRefEffect.md +0 -35
- package/.agents/references/hooks/useRelativeDateTime.md +0 -35
- package/.agents/references/hooks/useTimeout.md +0 -42
- package/.agents/references/hooks/useToggleState.md +0 -39
- package/.agents/references/hooks/useViewportHeight.md +0 -26
- package/.agents/references/hooks/useViewportMatch.md +0 -32
- package/.agents/references/utils/browserStorage.md +0 -41
- package/.agents/references/utils/buildContext.md +0 -47
- package/.agents/references/utils/clipboardShare.md +0 -59
- package/.agents/references/utils/floatingMotion.md +0 -67
- package/.agents/references/utils/getDeviceInfo.md +0 -43
- package/.agents/references/utils/isEditableKeyboardTarget.md +0 -35
- package/.agents/references/utils/mergeRefs.md +0 -29
- package/.agents/references/utils/seen.md +0 -37
- package/.agents/references/utils/subscribeKeyboardHeight.md +0 -35
- package/.gitattributes +0 -2
- package/AGENTS.md +0 -105
- package/tests/components/ScrolltoTop.test.tsx +0 -15
- package/tests/components/SwitchCase.test.tsx +0 -34
- package/tests/components/ViewportPortal.test.tsx +0 -42
- package/tests/cookie/cookie.test.ts +0 -156
- package/tests/datetime/datetime.test.ts +0 -250
- package/tests/hooks/useAvoidKeyboard.test.ts +0 -12
- package/tests/hooks/useCheckInvisible.test.ts +0 -28
- package/tests/hooks/useCheckScroll.test.ts +0 -23
- package/tests/hooks/useClientDateTime.test.ts +0 -14
- package/tests/hooks/useDebounce.test.ts +0 -71
- package/tests/hooks/useDebouncedCallback.test.ts +0 -34
- package/tests/hooks/useDoubleClick.test.ts +0 -31
- package/tests/hooks/useGeolocation.test.ts +0 -50
- package/tests/hooks/useHasMounted.test.ts +0 -10
- package/tests/hooks/useIntersectionObserver.test.ts +0 -13
- package/tests/hooks/useInterval.test.ts +0 -31
- package/tests/hooks/useKeyboardHeight.test.ts +0 -10
- package/tests/hooks/useKeyboardListNavigation.test.ts +0 -82
- package/tests/hooks/useLongPress.test.ts +0 -52
- package/tests/hooks/usePreservedCallback.test.ts +0 -35
- package/tests/hooks/usePreservedReference.test.ts +0 -35
- package/tests/hooks/useRefEffect.test.ts +0 -30
- package/tests/hooks/useRelativeDateTime.test.ts +0 -19
- package/tests/hooks/useTimeout.test.ts +0 -34
- package/tests/hooks/useToggleState.test.ts +0 -35
- package/tests/hooks/useViewportHeight.test.ts +0 -10
- package/tests/hooks/useViewportMatch.test.ts +0 -35
- package/tests/setup.ts +0 -9
- package/tests/utils/browserStorage.test.ts +0 -54
- package/tests/utils/buildContext.test.tsx +0 -27
- package/tests/utils/checkDevice.test.ts +0 -84
- package/tests/utils/clipboardShare.test.ts +0 -78
- package/tests/utils/floatingMotion.test.ts +0 -84
- package/tests/utils/keyboardTarget.test.ts +0 -44
- package/tests/utils/mergeRefs.test.ts +0 -18
- package/tests/utils/seen.test.ts +0 -31
- package/tests/utils/subscribeKeyboardHeight.test.ts +0 -50
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -16
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# useDoubleClick
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Distinguishes between a single click and a double click on the same element. Fires `click` on a single click after a configurable delay, or `doubleClick` if a second click arrives within the delay window.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Maintains an internal timer. On the first click it waits for `delay` ms. If a second click arrives within that window, it fires `doubleClick` and clears the timer. Otherwise it fires `click` after the delay expires.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useDoubleClick(
|
|
15
|
-
handlers: {
|
|
16
|
-
click?: () => void;
|
|
17
|
-
doubleClick?: () => void;
|
|
18
|
-
},
|
|
19
|
-
delay?: number
|
|
20
|
-
): {
|
|
21
|
-
onClick: () => void;
|
|
22
|
-
};
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Returns an `onClick` handler to spread onto the target element.
|
|
26
|
-
|
|
27
|
-
## Example Code
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { useDoubleClick } from "@musecat/functionkit";
|
|
31
|
-
|
|
32
|
-
function FileIcon() {
|
|
33
|
-
const { onClick } = useDoubleClick({
|
|
34
|
-
click: () => console.log("select"),
|
|
35
|
-
doubleClick: () => console.log("open"),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return <div onClick={onClick}>📄</div>;
|
|
39
|
-
}
|
|
40
|
-
```
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# useGeolocation
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Wraps the Geolocation API (`navigator.geolocation`) for retrieving the user's current position or continuously watching position changes.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Supports two modes via the `action` option: `"GET"` calls `getCurrentPosition` once, `"WATCH"` uses `watchPosition` for continuous tracking. Provides imperative `startTracking` / `stopTracking` controls.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
interface UseGeolocationOptions {
|
|
15
|
-
action?: "GET" | "WATCH";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function useGeolocation(options?: UseGeolocationOptions): {
|
|
19
|
-
latitude: number | null;
|
|
20
|
-
longitude: number | null;
|
|
21
|
-
error: GeolocationPositionError | null;
|
|
22
|
-
startTracking: () => void;
|
|
23
|
-
stopTracking: () => void;
|
|
24
|
-
};
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Example Code
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { useGeolocation } from "@musecat/functionkit";
|
|
31
|
-
|
|
32
|
-
function LocationDisplay() {
|
|
33
|
-
const { latitude, longitude, error } = useGeolocation({ action: "GET" });
|
|
34
|
-
if (error) return <div>Location unavailable</div>;
|
|
35
|
-
return <div>{latitude}, {longitude}</div>;
|
|
36
|
-
}
|
|
37
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# useHasMounted
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Returns a boolean indicating whether the component has mounted on the client. A simple hydration guard for preventing server-client content mismatches.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Uses `useEffect` to set `true` after mount. Returns `false` on the server and during the first client render before hydration.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useHasMounted(): boolean;
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Example Code
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
import { useHasMounted } from "@musecat/functionkit";
|
|
21
|
-
|
|
22
|
-
function ClientOnly() {
|
|
23
|
-
const mounted = useHasMounted();
|
|
24
|
-
if (!mounted) return <Fallback />;
|
|
25
|
-
return <ClientContent />;
|
|
26
|
-
}
|
|
27
|
-
```
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# useIntersectionObserver
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Provides a convenient ref callback pattern for `IntersectionObserver`. Calls a callback when the observed element's intersection status changes.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Uses `useRefEffect` internally to attach the observer when the ref receives an element and clean up when it unmounts. Accepts standard `IntersectionObserverInit` options. The callback is stabilized with `usePreservedCallback`.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useIntersectionObserver(
|
|
15
|
-
callback: IntersectionObserverCallback,
|
|
16
|
-
options?: IntersectionObserverInit
|
|
17
|
-
): (node: HTMLElement | null) => void;
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Returns a callback ref to assign to the target element.
|
|
21
|
-
|
|
22
|
-
## Example Code
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
import { useIntersectionObserver } from "@musecat/functionkit";
|
|
26
|
-
|
|
27
|
-
function LazyImage() {
|
|
28
|
-
const ref = useIntersectionObserver(
|
|
29
|
-
([entry]) => {
|
|
30
|
-
if (entry.isIntersecting) loadImage();
|
|
31
|
-
},
|
|
32
|
-
{ threshold: 0.1 }
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return <div ref={ref}>...</div>;
|
|
36
|
-
}
|
|
37
|
-
```
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# useInterval
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
A `setInterval` wrapper hook that handles lifecycle cleanup and provides toggle control. Ideal for polling, timers, or any repeated execution.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Starts the interval immediately on mount (unless `enabled: false` is passed). The interval pauses when `delay` is `null` or when `enabled` is `false`. Supports `immediate` mode — fires the callback once immediately before the first interval tick.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useInterval(
|
|
15
|
-
callback: () => void,
|
|
16
|
-
delay: number | null,
|
|
17
|
-
options?: {
|
|
18
|
-
immediate?: boolean;
|
|
19
|
-
enabled?: boolean;
|
|
20
|
-
}
|
|
21
|
-
): void;
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Example Code
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
import { useInterval } from "@musecat/functionkit";
|
|
28
|
-
|
|
29
|
-
function PollingComponent() {
|
|
30
|
-
const [data, setData] = useState(null);
|
|
31
|
-
|
|
32
|
-
useInterval(
|
|
33
|
-
() => fetch("/api/status").then(setData),
|
|
34
|
-
5000,
|
|
35
|
-
{ immediate: true }
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return <div>{data}</div>;
|
|
39
|
-
}
|
|
40
|
-
```
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# useKeyboardHeight
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Returns the current virtual keyboard height in pixels by subscribing to `visualViewport` changes. Useful for adjusting layout when the keyboard opens on mobile.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Internally uses `subscribeKeyboardHeight` which listens to `visualViewport.resize` and `visualViewport.scroll` events. Computes the height as the difference between the full viewport and the visual viewport.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useKeyboardHeight(): number;
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Returns the keyboard height in pixels (0 when the keyboard is closed).
|
|
18
|
-
|
|
19
|
-
## Example Code
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
import { useKeyboardHeight } from "@musecat/functionkit";
|
|
23
|
-
|
|
24
|
-
function ChatLayout() {
|
|
25
|
-
const kbHeight = useKeyboardHeight();
|
|
26
|
-
return (
|
|
27
|
-
<div style={{ paddingBottom: kbHeight }}>
|
|
28
|
-
<MessageList />
|
|
29
|
-
<InputBar />
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
```
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# useKeyboardListNavigation
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Adds keyboard-based navigation (ArrowUp, ArrowDown, Home, End, Enter) to a list of items. Returns the active index, a ref setter, and a keydown handler.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Manages an `activeIndex` state updated by arrow keys. `Home`/`End` jump to the first/last item. `Enter` triggers the `onSelect` callback with the active index. Automatically skips navigation when the event target is an editable element (`isEditableKeyboardTarget`).
|
|
10
|
-
|
|
11
|
-
| Option | Effect |
|
|
12
|
-
|--------|--------|
|
|
13
|
-
| `loop` | When `true`, navigating past the last item wraps to the first |
|
|
14
|
-
| `disable` | When `true`, all keyboard handling is suspended |
|
|
15
|
-
|
|
16
|
-
## Type Signature
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
function useKeyboardListNavigation(options: {
|
|
20
|
-
itemCount: number;
|
|
21
|
-
onSelect: (index: number) => void;
|
|
22
|
-
disable?: boolean;
|
|
23
|
-
loop?: boolean;
|
|
24
|
-
}): {
|
|
25
|
-
activeIndex: number;
|
|
26
|
-
setItemRef: (index: number) => (el: HTMLElement | null) => void;
|
|
27
|
-
handleListKeyDown: (e: React.KeyboardEvent) => void;
|
|
28
|
-
};
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Example Code
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import { useKeyboardListNavigation } from "@musecat/functionkit";
|
|
35
|
-
|
|
36
|
-
function MenuList({ items }: { items: string[] }) {
|
|
37
|
-
const { activeIndex, setItemRef, handleListKeyDown } = useKeyboardListNavigation({
|
|
38
|
-
itemCount: items.length,
|
|
39
|
-
onSelect: (i) => console.log("selected:", items[i]),
|
|
40
|
-
loop: true,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<ul onKeyDown={handleListKeyDown}>
|
|
45
|
-
{items.map((item, i) => (
|
|
46
|
-
<li
|
|
47
|
-
key={i}
|
|
48
|
-
ref={setItemRef(i)}
|
|
49
|
-
className={i === activeIndex ? "active" : ""}
|
|
50
|
-
tabIndex={-1}
|
|
51
|
-
>
|
|
52
|
-
{item}
|
|
53
|
-
</li>
|
|
54
|
-
))}
|
|
55
|
-
</ul>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# useLongPress
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Detects a long press gesture on an element. Fires `onLongPress` after the user presses and holds for a configurable delay, with optional `onClick` for tap and `onLongPressEnd` for release.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Tracks both mouse and touch events. On `mousedown`/`touchstart`, starts a timer. If the pointer moves beyond `moveThreshold` pixels, the gesture is cancelled. If the timer expires without cancellation, `onLongPress` fires. On release, `onLongPressEnd` fires (if it was a long press), or `onClick` fires (if it was a short tap).
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useLongPress(
|
|
15
|
-
handlers: {
|
|
16
|
-
onLongPress: (e: MouseEvent | TouchEvent) => void;
|
|
17
|
-
onClick?: (e: MouseEvent | TouchEvent) => void;
|
|
18
|
-
onLongPressEnd?: (e: MouseEvent | TouchEvent) => void;
|
|
19
|
-
},
|
|
20
|
-
options?: {
|
|
21
|
-
delay?: number;
|
|
22
|
-
moveThreshold?: number;
|
|
23
|
-
}
|
|
24
|
-
): {
|
|
25
|
-
onMouseDown: (e: React.MouseEvent) => void;
|
|
26
|
-
onTouchStart: (e: React.TouchEvent) => void;
|
|
27
|
-
onMouseUp: (e: React.MouseEvent) => void;
|
|
28
|
-
onTouchEnd: (e: React.TouchEvent) => void;
|
|
29
|
-
onMouseMove: (e: React.MouseEvent) => void;
|
|
30
|
-
onTouchMove: (e: React.TouchEvent) => void;
|
|
31
|
-
};
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Example Code
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
import { useLongPress } from "@musecat/functionkit";
|
|
38
|
-
|
|
39
|
-
function LongPressButton() {
|
|
40
|
-
const handlers = useLongPress({
|
|
41
|
-
onLongPress: () => console.log("hold complete"),
|
|
42
|
-
onClick: () => console.log("tap"),
|
|
43
|
-
onLongPressEnd: () => console.log("released"),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
return <button {...handlers}>Press & hold</button>;
|
|
47
|
-
}
|
|
48
|
-
```
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# usePreservedCallback
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Returns a stable function identity while always calling the latest version of the provided callback. Eliminates the need to list callback dependencies in `useEffect` or `useMemo` dependency arrays.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Stores the latest callback in a `useRef`, then returns a `useCallback`-wrapped proxy that always invokes `ref.current`. The returned function never changes identity, preventing unnecessary re-renders of memoized children.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function usePreservedCallback<T extends (...args: any[]) => any>(
|
|
15
|
-
callback: T
|
|
16
|
-
): T;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Example Code
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
import { usePreservedCallback, useIntersectionObserver } from "@musecat/functionkit";
|
|
23
|
-
|
|
24
|
-
function InfiniteScroll({ onLoadMore }: { onLoadMore: () => void }) {
|
|
25
|
-
const stableLoadMore = usePreservedCallback(onLoadMore);
|
|
26
|
-
|
|
27
|
-
const ref = useIntersectionObserver(([entry]) => {
|
|
28
|
-
if (entry.isIntersecting) stableLoadMore();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
return <div ref={ref}>...</div>;
|
|
32
|
-
}
|
|
33
|
-
```
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# usePreservedReference
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Maintains a stable reference to a value, only updating it when the value meaningfully changes. Avoids unnecessary re-renders caused by new object/array identities on every render.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Compares the current and previous values using a custom comparator (defaults to `JSON.stringify`). If they are considered equal, the previous reference is returned instead of the new one.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function usePreservedReference<T>(
|
|
15
|
-
value: T,
|
|
16
|
-
compare?: (a: T, b: T) => boolean
|
|
17
|
-
): T;
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Example Code
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
import { usePreservedReference } from "@musecat/functionkit";
|
|
24
|
-
|
|
25
|
-
function Filters({ filters }: { filters: { sort: string; page: number } }) {
|
|
26
|
-
const stableFilters = usePreservedReference(filters);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
fetchResults(stableFilters);
|
|
30
|
-
}, [stableFilters]); // Only fires when actual values change
|
|
31
|
-
|
|
32
|
-
return <div>...</div>;
|
|
33
|
-
}
|
|
34
|
-
```
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# useRefEffect
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Provides a ref callback that runs an effect when the element mounts and optionally runs a cleanup when it unmounts or when dependencies change. Similar to `useEffect` but keyed to a DOM element's lifecycle.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Returns a callback ref. When React calls the ref with an element, the effect function runs. When it's called with `null` (unmount) or when `deps` change, the previous cleanup is called before running the new effect.
|
|
10
|
-
|
|
11
|
-
## Type Signatures
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
type CleanupCallback = () => void;
|
|
15
|
-
|
|
16
|
-
function useRefEffect(
|
|
17
|
-
effect: (el: HTMLElement) => CleanupCallback | void,
|
|
18
|
-
deps?: any[]
|
|
19
|
-
): (el: HTMLElement | null) => void;
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Example Code
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
import { useRefEffect } from "@musecat/functionkit";
|
|
26
|
-
|
|
27
|
-
function AutoFocus() {
|
|
28
|
-
const ref = useRefEffect((el) => {
|
|
29
|
-
el.focus();
|
|
30
|
-
return () => console.log("unmounted");
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
return <input ref={ref} />;
|
|
34
|
-
}
|
|
35
|
-
```
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# useRelativeDateTime
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Displays a date as a relative time string ("3 minutes ago", "2 hours ago") that updates automatically. Falls back to a formatted date string when the relative time exceeds a configurable threshold.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Computes the difference between now and the given date, then formats it using `formatRelativeText`. Recomputes on a configurable interval (`updateIntervalMs`, default 1000ms). If the difference exceeds `maxRelativeDays`, returns a standard date format instead.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useRelativeDateTime(
|
|
15
|
-
date: DateInput,
|
|
16
|
-
locale: AppLocale,
|
|
17
|
-
options?: {
|
|
18
|
-
updateIntervalMs?: number;
|
|
19
|
-
maxRelativeDays?: number;
|
|
20
|
-
}
|
|
21
|
-
): string;
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Example Code
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
import { useRelativeDateTime } from "@musecat/functionkit";
|
|
28
|
-
|
|
29
|
-
function Timestamp({ time }: { time: string }) {
|
|
30
|
-
const relative = useRelativeDateTime(time, "kr", {
|
|
31
|
-
maxRelativeDays: 7,
|
|
32
|
-
});
|
|
33
|
-
return <span>{relative}</span>;
|
|
34
|
-
}
|
|
35
|
-
```
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# useTimeout
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
A `setTimeout` wrapper hook that provides imperative controls: `start`, `reset`, `clear`, and an `isPending` state. Allows conditionally enabling the timeout.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Creates a timer on mount (unless `enabled: false`). The returned `start` begins the timeout, `clear` cancels it, and `reset` restarts it. `isPending` reflects whether a timeout is currently active.
|
|
10
|
-
|
|
11
|
-
## Type Signatures
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
interface UseTimeoutControls {
|
|
15
|
-
start: () => void;
|
|
16
|
-
reset: () => void;
|
|
17
|
-
clear: () => void;
|
|
18
|
-
isPending: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function useTimeout(
|
|
22
|
-
callback: () => void,
|
|
23
|
-
delay: number,
|
|
24
|
-
options?: { enabled?: boolean }
|
|
25
|
-
): UseTimeoutControls;
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Example Code
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { useTimeout } from "@musecat/functionkit";
|
|
32
|
-
|
|
33
|
-
function AutoDismiss({ onDismiss }: { onDismiss: () => void }) {
|
|
34
|
-
const { reset, isPending } = useTimeout(onDismiss, 5000);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div onMouseEnter={reset}>
|
|
38
|
-
Dismissing in {isPending ? "..." : "done"}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
```
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# useToggleState
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Simplifies boolean state management with explicit `setTrue`, `setFalse`, and `toggle` setters. A more expressive alternative to raw `useState(false)`.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Wraps `useState<boolean>` with named setter functions. Accepts an optional initial value (default `false`).
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useToggleState(
|
|
15
|
-
initial?: boolean | (() => boolean)
|
|
16
|
-
): {
|
|
17
|
-
value: boolean;
|
|
18
|
-
setTrue: () => void;
|
|
19
|
-
setFalse: () => void;
|
|
20
|
-
toggle: () => void;
|
|
21
|
-
};
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Example Code
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
import { useToggleState } from "@musecat/functionkit";
|
|
28
|
-
|
|
29
|
-
function Collapsible({ children }: { children: React.ReactNode }) {
|
|
30
|
-
const { value: isOpen, toggle } = useToggleState(false);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div>
|
|
34
|
-
<button onClick={toggle}>{isOpen ? "Hide" : "Show"}</button>
|
|
35
|
-
{isOpen && children}
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
```
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# useViewportHeight
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Returns the current viewport height in pixels, reacting to resize events. Uses `visualViewport` when available, falling back to `window.innerHeight`.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Subscribes to `visualViewport.resize` (or `window.resize`) events and updates state accordingly. Debounces updates to avoid excessive re-renders during rapid resize.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useViewportHeight(): number;
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Example Code
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
import { useViewportHeight } from "@musecat/functionkit";
|
|
21
|
-
|
|
22
|
-
function FullHeight() {
|
|
23
|
-
const height = useViewportHeight();
|
|
24
|
-
return <div style={{ height }}>Content</div>;
|
|
25
|
-
}
|
|
26
|
-
```
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# useViewportMatch
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Returns whether the current viewport matches a CSS media query string. Reactive — updates when the match status changes (e.g., window resize).
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Uses `window.matchMedia` to create a `MediaQueryList`, then subscribes to its `change` event to track live changes.
|
|
10
|
-
|
|
11
|
-
## Type Signature
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
function useViewportMatch(query: string): boolean;
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
| Parameter | Type | Example |
|
|
18
|
-
|-----------|------|---------|
|
|
19
|
-
| `query` | `string` | `"(min-width: 768px)"`, `"(prefers-color-scheme: dark)"` |
|
|
20
|
-
|
|
21
|
-
## Example Code
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { useViewportMatch } from "@musecat/functionkit";
|
|
25
|
-
|
|
26
|
-
function ResponsiveComponent() {
|
|
27
|
-
const isDesktop = useViewportMatch("(min-width: 1024px)");
|
|
28
|
-
const isDark = useViewportMatch("(prefers-color-scheme: dark)");
|
|
29
|
-
|
|
30
|
-
return <div className={isDesktop ? "desktop" : "mobile"}>...</div>;
|
|
31
|
-
}
|
|
32
|
-
```
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# browserStorage
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Provides typed JSON-serialized read/write/delete access to `localStorage` and `sessionStorage`. Gracefully handles missing `window` (SSR) by returning `null` silently.
|
|
6
|
-
|
|
7
|
-
## Usage Logic
|
|
8
|
-
|
|
9
|
-
Each function wraps the native Storage API with `JSON.stringify`/`JSON.parse`. SSR-safe at the call site — checks for `typeof window` before accessing storage.
|
|
10
|
-
|
|
11
|
-
## Type Signatures
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
// localStorage
|
|
15
|
-
function getLocalStorage<T = string>(key: string): T | null;
|
|
16
|
-
function updateLocalStorage<T>(key: string, value: T): void;
|
|
17
|
-
function removeLocalStorage(key: string): void;
|
|
18
|
-
|
|
19
|
-
// sessionStorage
|
|
20
|
-
function getSessionStorage<T = string>(key: string): T | null;
|
|
21
|
-
function updateSessionStorage<T>(key: string, value: T): void;
|
|
22
|
-
function removeSessionStorage(key: string): void;
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Example Code
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import { getLocalStorage, updateLocalStorage, removeLocalStorage } from "@musecat/functionkit";
|
|
29
|
-
|
|
30
|
-
function useDraft() {
|
|
31
|
-
const draft = getLocalStorage<{ text: string }>("draft") ?? { text: "" };
|
|
32
|
-
|
|
33
|
-
const saveDraft = (text: string) => {
|
|
34
|
-
updateLocalStorage("draft", { text });
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const clearDraft = () => removeLocalStorage("draft");
|
|
38
|
-
|
|
39
|
-
return { draft, saveDraft, clearDraft };
|
|
40
|
-
}
|
|
41
|
-
```
|