@musecat/functionkit 1.2.0 → 1.2.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 +53 -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 +41 -0
- package/.agents/references/hooks/useInterval.md +44 -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 +37 -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 +128 -0
- package/CLAUDE.md +1 -0
- package/README.md +4 -0
- package/package.json +4 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# floatingMotion
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Provides animation presets and transform utilities for floating UI elements: tooltips, popovers, modals, and mobile sheets. Calculates `initial`, `animate`, and `exit` states for Framer Motion.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
**`getFloatingMotionPreset(mode, placement?)`**: Returns a complete Framer Motion preset object based on the mode and optional placement.
|
|
10
|
+
|
|
11
|
+
**`getFloatingTransformOrigin(placement)`**: Returns the correct `transformOrigin` string for a given placement.
|
|
12
|
+
**`getFloatingHiddenTransform(placement)`**: Returns the hidden-state transform offset for a given placement.
|
|
13
|
+
|
|
14
|
+
### Modes
|
|
15
|
+
| Mode | Description | Animation |
|
|
16
|
+
|------|-------------|-----------|
|
|
17
|
+
| `anchored` | Tooltips, popovers | Scale + fade from placement origin |
|
|
18
|
+
| `center-selected` | Center-positioned selection | Scale in place |
|
|
19
|
+
| `modal-center` | Modal windows | Scale + fade in center |
|
|
20
|
+
| `mobile-sheet` | Bottom sheets | Slide up from bottom |
|
|
21
|
+
|
|
22
|
+
## Type Signatures
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
type FloatingMotionMode = "anchored" | "center-selected" | "modal-center" | "mobile-sheet";
|
|
26
|
+
type FloatingPlacement = "top" | "bottom" | "left" | "right" | "top-start" | "top-end" | ...;
|
|
27
|
+
type FloatingMotionPreset = {
|
|
28
|
+
initial: Variants;
|
|
29
|
+
animate: Variants;
|
|
30
|
+
exit: Variants;
|
|
31
|
+
transition: Transition;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getFloatingMotionPreset(
|
|
35
|
+
mode: FloatingMotionMode,
|
|
36
|
+
placement?: FloatingPlacement
|
|
37
|
+
): FloatingMotionPreset;
|
|
38
|
+
|
|
39
|
+
function getFloatingTransformOrigin(placement: FloatingPlacement): string;
|
|
40
|
+
function getFloatingHiddenTransform(placement: FloatingPlacement): { x?: number; y?: number };
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Example Code
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { getFloatingMotionPreset } from "@musecat/functionkit";
|
|
47
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
48
|
+
|
|
49
|
+
function Tooltip({ open, children }: { open: boolean; children: ReactNode }) {
|
|
50
|
+
const preset = getFloatingMotionPreset("anchored", "top");
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<AnimatePresence>
|
|
54
|
+
{open && (
|
|
55
|
+
<motion.div
|
|
56
|
+
variants={preset}
|
|
57
|
+
initial="initial"
|
|
58
|
+
animate="animate"
|
|
59
|
+
exit="exit"
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</motion.div>
|
|
63
|
+
)}
|
|
64
|
+
</AnimatePresence>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# getDeviceInfo (checkDevice)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Parses the User-Agent string to detect the user's device type, operating system, and browser. Callable at module scope — no `window` required.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
Analyzes `navigator.userAgent` with a series of regex checks. Returns a `DeviceInfo` object with boolean flags. SSR-safe — can be called in Server Components, RSC, or module initialization.
|
|
10
|
+
|
|
11
|
+
## Type Signatures
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface DeviceInfo {
|
|
15
|
+
isMobile: boolean;
|
|
16
|
+
isIOS: boolean;
|
|
17
|
+
isAndroid: boolean;
|
|
18
|
+
isSafari: boolean;
|
|
19
|
+
isIOSSafari: boolean;
|
|
20
|
+
isMacSafari: boolean;
|
|
21
|
+
isSamsungBrowser: boolean;
|
|
22
|
+
isTouchDevice: boolean;
|
|
23
|
+
browser: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getDeviceInfo(): DeviceInfo;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Example Code
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { getDeviceInfo } from "@musecat/functionkit";
|
|
33
|
+
|
|
34
|
+
const { isMobile, isIOS, isIOSSafari } = getDeviceInfo();
|
|
35
|
+
|
|
36
|
+
// Call at module scope
|
|
37
|
+
const platform = isIOS ? "ios" : isAndroid ? "android" : "web";
|
|
38
|
+
|
|
39
|
+
export function detectBrowser() {
|
|
40
|
+
const { isSafari, browser } = getDeviceInfo();
|
|
41
|
+
return { isSafari, browser };
|
|
42
|
+
}
|
|
43
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# isEditableKeyboardTarget (keyboardTarget)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Checks if a keyboard event target is an editable element (`input`, `textarea`, `select`, or `contentEditable`). Used to prevent keyboard navigation hooks from interfering with native input editing.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
Examines the target element's tag name and `isContentEditable` property. Excludes buttons, checkboxes, and radio inputs from the editable set.
|
|
10
|
+
|
|
11
|
+
## Type Signature
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
function isEditableKeyboardTarget(target: EventTarget | null): boolean;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Example Code
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { isEditableKeyboardTarget, useKeyboardListNavigation } from "@musecat/functionkit";
|
|
21
|
+
|
|
22
|
+
function SearchableList({ items }: { items: string[] }) {
|
|
23
|
+
const { handleListKeyDown } = useKeyboardListNavigation({
|
|
24
|
+
itemCount: items.length,
|
|
25
|
+
onSelect: (i) => console.log(items[i]),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const onKeyDown = (e: React.KeyboardEvent) => {
|
|
29
|
+
if (isEditableKeyboardTarget(e.target)) return; // Don't steal from inputs
|
|
30
|
+
handleListKeyDown(e);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return <ul onKeyDown={onKeyDown}>...</ul>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# mergeRefs
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Combines multiple React refs (callback refs and/or object refs) into a single callback ref. Useful when forwarding refs from parent components while keeping internal refs.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
Returns a callback ref that, when called with a DOM node, sets `ref.current` on all object refs and invokes all callback refs with the node. Filters out `undefined` refs.
|
|
10
|
+
|
|
11
|
+
## Type Signature
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
function mergeRefs<T>(
|
|
15
|
+
...refs: (React.Ref<T> | undefined)[]
|
|
16
|
+
): (node: T) => void;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Example Code
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { mergeRefs } from "@musecat/functionkit";
|
|
23
|
+
import { forwardRef, useRef } from "react";
|
|
24
|
+
|
|
25
|
+
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
|
|
26
|
+
const internalRef = useRef<HTMLInputElement>(null);
|
|
27
|
+
return <input ref={mergeRefs(ref, internalRef)} {...props} />;
|
|
28
|
+
});
|
|
29
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# seen
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Manages a "user has already seen this" state as a JSON string. Useful for marking items as read, dismissing onboarding prompts, or tracking feature discovery without server state.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
Stores an array of string keys serialized as JSON. `parseSeen` deserializes, `hasSeenKey` checks a specific key, and `buildSeenValue` appends a new key and returns the updated JSON string. SSR-safe — pure functions with no side effects.
|
|
10
|
+
|
|
11
|
+
## Type Signatures
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const SEEN_STORAGE_KEY = "seen";
|
|
15
|
+
|
|
16
|
+
function parseSeen(seen: string | null): string[];
|
|
17
|
+
function hasSeenKey(seen: string | null, key: string): boolean;
|
|
18
|
+
function buildSeenValue(prev: string | null, key: string): string;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Example Code
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { hasSeenKey, buildSeenValue, SEEN_STORAGE_KEY } from "@musecat/functionkit";
|
|
25
|
+
import { getLocalStorage, updateLocalStorage } from "@musecat/functionkit";
|
|
26
|
+
|
|
27
|
+
function useOnboarding() {
|
|
28
|
+
const stored = getLocalStorage(SEEN_STORAGE_KEY);
|
|
29
|
+
const hasSeenWalkthrough = hasSeenKey(stored, "walkthrough-v1");
|
|
30
|
+
|
|
31
|
+
const dismissWalkthrough = () => {
|
|
32
|
+
updateLocalStorage(SEEN_STORAGE_KEY, buildSeenValue(stored, "walkthrough-v1"));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return { hasSeenWalkthrough, dismissWalkthrough };
|
|
36
|
+
}
|
|
37
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# subscribeKeyboardHeight
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A low-level utility that subscribes to `visualViewport` resize/scroll events and reports the estimated keyboard height. Used internally by `useKeyboardHeight`.
|
|
6
|
+
|
|
7
|
+
## Usage Logic
|
|
8
|
+
|
|
9
|
+
Attaches `resize` and `scroll` event listeners to `window.visualViewport`. Computes keyboard height as `window.innerHeight - visualViewport.height`. Throttles callbacks at the specified interval (default 16ms). Returns an unsubscribe function.
|
|
10
|
+
|
|
11
|
+
## Type Signature
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
function subscribeKeyboardHeight(
|
|
15
|
+
handler: (height: number) => void,
|
|
16
|
+
throttleMs?: number
|
|
17
|
+
): { unsubscribe: () => void };
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Example Code
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { subscribeKeyboardHeight } from "@musecat/functionkit";
|
|
24
|
+
import { useEffect } from "react";
|
|
25
|
+
|
|
26
|
+
function useCustomKeyboardHandler() {
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const { unsubscribe } = subscribeKeyboardHeight(
|
|
29
|
+
(height) => console.log("Keyboard height:", height),
|
|
30
|
+
100
|
|
31
|
+
);
|
|
32
|
+
return unsubscribe;
|
|
33
|
+
}, []);
|
|
34
|
+
}
|
|
35
|
+
```
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# FunctionKit (@musecat/functionkit)
|
|
2
|
+
|
|
3
|
+
React 19 frontend utility library. Single package, ESM.
|
|
4
|
+
|
|
5
|
+
## Import Convention Enforcement (Mandatory)
|
|
6
|
+
|
|
7
|
+
- **Barrel imports are the rule:** `import { useDebounce } from "@musecat/functionkit"`.
|
|
8
|
+
- **Subpath imports are only allowed when:** the export is not in the barrel (`useCheckInvisible`, `SwitchCase`, `ScrolltoTop`) or tree-shaking is absolutely required.
|
|
9
|
+
- **Type imports also go through the barrel:** `import type { AppLocale, DateInput } from "@musecat/functionkit"`.
|
|
10
|
+
- **No reimplementation:** If FunctionKit already provides a feature, reuse it. Never write a duplicate.
|
|
11
|
+
|
|
12
|
+
## SSR Safety Classification
|
|
13
|
+
|
|
14
|
+
| Category | Condition | Examples |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **SSR-safe** | No `"use client"`, pure function | `formatLongDate`, `mergeRefs`, `SwitchCase`, `getDeviceInfo`, `floatingMotion`, `seen`, `parseClientCookieNames`, `debounce` |
|
|
17
|
+
| **Client-only** | `"use client"` or `window` access | All hooks, `ViewportPortal`, `ScrolltoTop`, `browserStorage`, `cookie` (except `parseClientCookieNames`), `clipboardShare`, `isEditableKeyboardTarget` |
|
|
18
|
+
|
|
19
|
+
SSR-safe functions can be used freely in Server Components, RSC, and Client Components. Client-only items must only be used in `"use client"` components.
|
|
20
|
+
|
|
21
|
+
## Barrel Export Mapping
|
|
22
|
+
|
|
23
|
+
The barrel (`index.ts`) exports 63 named exports. **Items NOT in the barrel (require subpath imports):**
|
|
24
|
+
- `SwitchCase` — component subpath
|
|
25
|
+
- `ScrolltoTop` — component subpath
|
|
26
|
+
- `useCheckInvisible` — `@musecat/functionkit/hooks/useCheckInvisible`
|
|
27
|
+
- `useCheckScroll` — `@musecat/functionkit/hooks/useCheckScroll`
|
|
28
|
+
|
|
29
|
+
When adding new features, register them in the barrel.
|
|
30
|
+
|
|
31
|
+
## Hook Selection Guidelines
|
|
32
|
+
|
|
33
|
+
| Scenario | Hook to Use |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Debounce rapid state changes | `useDebounce` or `useDebouncedCallback` |
|
|
36
|
+
| Polling / repeated execution | `useInterval` |
|
|
37
|
+
| Delayed single execution | `useTimeout` |
|
|
38
|
+
| Keyboard list navigation | `useKeyboardListNavigation` |
|
|
39
|
+
| Long press gesture | `useLongPress` |
|
|
40
|
+
| Single/double click distinction | `useDoubleClick` |
|
|
41
|
+
| Geolocation tracking | `useGeolocation` |
|
|
42
|
+
| Client date formatting | `useClientDateTime` |
|
|
43
|
+
| Relative time ("3 minutes ago") | `useRelativeDateTime` |
|
|
44
|
+
| IntersectionObserver | `useIntersectionObserver` |
|
|
45
|
+
| Keyboard avoidance (mobile) | `useAvoidKeyboard` |
|
|
46
|
+
| Viewport height | `useViewportHeight` |
|
|
47
|
+
| Media query matching | `useViewportMatch` |
|
|
48
|
+
| Scroll position detection | `useCheckScroll` (subpath) |
|
|
49
|
+
| Element visibility detection | `useCheckInvisible` (subpath) |
|
|
50
|
+
| Toggle state | `useToggleState` |
|
|
51
|
+
| Hydration guard | `useHasMounted` |
|
|
52
|
+
| Stable callback reference | `usePreservedCallback` |
|
|
53
|
+
| Deep equality reference | `usePreservedReference` |
|
|
54
|
+
| Ref lifecycle | `useRefEffect` |
|
|
55
|
+
|
|
56
|
+
## Component Selection Guidelines
|
|
57
|
+
|
|
58
|
+
- **SwitchCase** — Prefer over inline &&/ternary for multi-branch rendering. SSR-safe.
|
|
59
|
+
- **ViewportPortal** — Fixed-position overlays, modals, toasts. Client-only.
|
|
60
|
+
- **ScrolltoTop** — Scroll reset on page navigation. Client-only.
|
|
61
|
+
|
|
62
|
+
Never write raw `createContext` + `Provider` + `useContext` boilerplate. Use `buildContext` instead.
|
|
63
|
+
|
|
64
|
+
## DateTime Formatting Guidelines
|
|
65
|
+
|
|
66
|
+
| Context | Use | SSR-safe |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| Server static date | `formatServerDate` / `formatServerDateTime` | Yes |
|
|
69
|
+
| Client date | `formatClientDate` / `formatClientDateTime` | No |
|
|
70
|
+
| Relative time ("3분 전") | `formatServerRelative` (server) / `formatClientRelative` (client) | Mixed |
|
|
71
|
+
| Remaining time ("2일 3시간 남음") | `formatRemainingText` | Yes |
|
|
72
|
+
| UTC key ("YYYY-MM-DD") | `formatUtcDateKey` / `parseUtcDateInput` | Yes |
|
|
73
|
+
| Raw formatters | `formatLongDate`, `formatDotDate`, `formatKoreanTime` etc. | Yes |
|
|
74
|
+
|
|
75
|
+
**Locale handling:** `DateInput` accepts `string | number | Date`. Use `normalizeAppLocale("ko")` → `"kr"`, `toIntlLocale("kr")` → `"ko-KR"`.
|
|
76
|
+
|
|
77
|
+
## Cookie & Storage API Guidelines
|
|
78
|
+
|
|
79
|
+
| Operation | API | SSR-safe |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| Read cookie | `getClientCookie(name)` | No |
|
|
82
|
+
| Write cookie | `setClientCookie(name, value, days?)` | No |
|
|
83
|
+
| Delete cookie | `clearClientCookie(name)` / `clearAllClientCookies()` | No |
|
|
84
|
+
| Parse cookie names | `parseClientCookieNames(cookieString)` | **Yes** |
|
|
85
|
+
| localStorage | `getLocalStorage` / `updateLocalStorage` / `removeLocalStorage` | No |
|
|
86
|
+
| sessionStorage | `getSessionStorage` / `updateSessionStorage` / `removeSessionStorage` | No |
|
|
87
|
+
|
|
88
|
+
`clearAllClientCookies` tries all domain/path combinations — use with caution.
|
|
89
|
+
|
|
90
|
+
## Context & Utility Patterns
|
|
91
|
+
|
|
92
|
+
- **`buildContext`:** Creates `createContext` + Provider + `useContext` in one call. `const [useMyCtx, MyProvider] = buildContext<MyType>()`.
|
|
93
|
+
- **`mergeRefs`:** Merges multiple refs into a single callback ref. Use when forwarding refs.
|
|
94
|
+
- **`getDeviceInfo`:** UA-based device detection. Callable at module scope. Returns `isMobile`, `isIOS`, `isAndroid`, etc.
|
|
95
|
+
- **`isEditableKeyboardTarget`:** Checks if a key event target is an editable element. Use before keyboard navigation.
|
|
96
|
+
- **`NavigatorClipboard` / `NavigatorShare`:** Clipboard copy/share with automatic fallback on failure.
|
|
97
|
+
- **`floatingMotion`:** `getFloatingMotionPreset` for tooltip/popover/modal animation configs. 4 modes (anchored, center-selected, modal-center, mobile-sheet).
|
|
98
|
+
- **`seen`:** `hasSeenKey` / `buildSeenValue` for "user has already seen" state management. SSR-safe.
|
|
99
|
+
- **`subscribeKeyboardHeight`:** Low-level keyboard height change detection. Used internally by `useKeyboardHeight`.
|
|
100
|
+
|
|
101
|
+
## Test Failure Resolution
|
|
102
|
+
|
|
103
|
+
When a test fails, do not blindly patch it to turn green. Reason about intent:
|
|
104
|
+
1. Understand the hook/component's design contract — what it should do vs. what it actually does.
|
|
105
|
+
2. If the test correctly captures the intended behavior and the code violates it, fix the code.
|
|
106
|
+
3. If the code correctly implements the intended behavior and the test reflects outdated assumptions, fix the test.
|
|
107
|
+
4. If neither is clearly right, treat the source as the source of truth and align the test unless you have explicit user instruction to change the behavior.
|
|
108
|
+
|
|
109
|
+
## Documentation Maintenance
|
|
110
|
+
|
|
111
|
+
- **Documentation MUST Be Updated Immediately:** Every time you add, modify, or remove a hook, component, util, or any exported API/type, you MUST update the corresponding `.agents/references/` doc in the **same batch of tool calls** — not later, not after tests, not when asked. You must check what changed and update refs before moving to the next task.
|
|
112
|
+
- When the barrel (`index.ts`) export list changes, update the Barrel Export Mapping section above.
|
|
113
|
+
- Before using any feature, read the corresponding `.agents/references/` doc first.
|
|
114
|
+
- If references have drifted from the actual code due to manual changes, analyze the source code and git history to align them.
|
|
115
|
+
|
|
116
|
+
### Reference File Mapping
|
|
117
|
+
|
|
118
|
+
| Category | File(s) |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Components | `.agents/references/components/SwitchCase.md`, `ScrolltoTop.md`, `ViewportPortal.md` |
|
|
121
|
+
| Hooks | `.agents/references/hooks/usePreservedCallback.md`, `usePreservedReference.md`, `useRefEffect.md`, `useDebounce.md`, `useDebouncedCallback.md`, `useInterval.md`, `useTimeout.md`, `useKeyboardListNavigation.md`, `useLongPress.md`, `useDoubleClick.md`, `useIntersectionObserver.md`, `useCheckInvisible.md`, `useCheckScroll.md`, `useViewportHeight.md`, `useViewportMatch.md`, `useAvoidKeyboard.md`, `useToggleState.md`, `useHasMounted.md`, `useGeolocation.md`, `useKeyboardHeight.md`, `useClientDateTime.md`, `useRelativeDateTime.md` |
|
|
122
|
+
| Cookie | `.agents/references/cookie/cookie.md` |
|
|
123
|
+
| DateTime | `.agents/references/datetime/dateTime.shared.md`, `dateTime.client.md`, `dateTime.server.md` |
|
|
124
|
+
| Utils | `.agents/references/utils/buildContext.md`, `mergeRefs.md`, `floatingMotion.md`, `browserStorage.md`, `getDeviceInfo.md`, `clipboardShare.md`, `isEditableKeyboardTarget.md`, `seen.md`, `subscribeKeyboardHeight.md` |
|
|
125
|
+
|
|
126
|
+
### usePreservedCallback Trade-off
|
|
127
|
+
|
|
128
|
+
`usePreservedCallback` uses `useRef` + `useEffect` to keep a stable callback reference that always calls the latest version. Because the ref is synced via `useEffect` (after paint), the returned callback may hold a **stale value** during the first render or in the gap between a re-render and the effect flush. In practice this is never an issue — the callback is only invoked in event handlers that fire post-paint. This is the same pattern used by react-hook-form, Radix UI, and others. If synchronous invocation during render is required, use a regular `useCallback` with inline deps instead.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ export default function Example() {
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## References
|
|
33
|
+
|
|
34
|
+
This package includes `.agents/references/` directory with detailed documentation for every hook, component, and utility. Use it alongside TypeScript definitions as a guide when building with `@musecat/functionkit`.
|
|
35
|
+
|
|
32
36
|
## Acknowledgements
|
|
33
37
|
|
|
34
38
|
- [toss/react-simplikit](https://github.com/toss/react-simplikit/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@musecat/functionkit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "React 19 + TypeScript frontend utility library — hooks, components, datetime, cookie, and utils",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"index.ts",
|
|
20
20
|
"packages/",
|
|
21
|
+
".agents/",
|
|
22
|
+
"AGENTS.md",
|
|
23
|
+
"CLAUDE.md",
|
|
21
24
|
"README.md",
|
|
22
25
|
"LICENSE"
|
|
23
26
|
],
|