@akhil-saxena/design-system 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/dist/chunk-34YNJKI2.js +231 -0
- package/dist/chunk-34YNJKI2.js.map +1 -0
- package/dist/hooks/index.d.ts +19 -1
- package/dist/hooks/index.js +22 -124
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +907 -64
- package/dist/index.js +3378 -335
- package/dist/index.js.map +1 -1
- package/dist/primitives.css +1197 -40
- package/dist/tokens.css +118 -32
- package/package.json +4 -2
- package/dist/chunk-FUXR6QZ3.js +0 -108
- package/dist/chunk-FUXR6QZ3.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Accessible React primitives with semantic tokens. Full dark mode, cream + ink + amber design language.
|
|
4
4
|
|
|
5
|
-
**v1.
|
|
5
|
+
**v1.5.0 — 79 components across 9 categories.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@akhil-saxena/design-system)
|
|
8
8
|
|
|
@@ -57,41 +57,41 @@ import { ChevronDown, Search } from "@akhil-saxena/design-system/icons";
|
|
|
57
57
|
|
|
58
58
|
## Components
|
|
59
59
|
|
|
60
|
-
### Inputs (
|
|
60
|
+
### Inputs (22)
|
|
61
61
|
|
|
62
|
-
Button, TextInput, Textarea, Badge, Chip, Checkbox, Radio, Toggle, NumberStepper, RangeSlider, StarRating, Autocomplete, DatePicker, DateRangePicker, MultiSelect, Select
|
|
62
|
+
Button, OAuthButton, TextInput, Textarea, Badge, Chip, Kbd, Checkbox, Radio, Toggle, NumberStepper, RangeSlider, StarRating, StatusPill, Autocomplete, ColorPicker, DatePicker, DateRangePicker, FileInput, InlineEditField, MultiSelect, Select
|
|
63
63
|
|
|
64
|
-
### Overlays (
|
|
64
|
+
### Overlays (11)
|
|
65
65
|
|
|
66
|
-
Popover, Modal, BottomSheet, Tooltip, Sheet, HoverCard, Card, StickyNote, Lightbox
|
|
66
|
+
Popover, Modal, ConfirmDialog, CommandPalette, BottomSheet, Tooltip, Sheet, HoverCard, Card, StickyNote, Lightbox
|
|
67
67
|
|
|
68
|
-
### Data Display (
|
|
68
|
+
### Data Display (11)
|
|
69
69
|
|
|
70
|
-
Table, Tabs, Accordion, Carousel, Timeline, InfiniteList, Calendar, Breadcrumbs, SegmentedControl
|
|
70
|
+
Table, DataGrid, Tabs, Accordion, Carousel, Timeline, InfiniteList, Calendar, Breadcrumbs, Pagination, SegmentedControl
|
|
71
71
|
|
|
72
|
-
### Feedback (
|
|
72
|
+
### Feedback (7)
|
|
73
73
|
|
|
74
|
-
AlertBanner, Toast, Skeleton, ProgressBar, InlineConfirm, EmptyState
|
|
74
|
+
AlertBanner, Toast, Snackbar, Skeleton, ProgressBar, InlineConfirm, EmptyState
|
|
75
75
|
|
|
76
|
-
### Interaction (
|
|
76
|
+
### Interaction (7)
|
|
77
77
|
|
|
78
|
-
CopyToClipboard, InlineEdit, RichText, SearchAndFilters, Sortable, SplitButton
|
|
78
|
+
CopyToClipboard, RelativeTime, InlineEdit, RichText, SearchAndFilters, Sortable, SplitButton
|
|
79
79
|
|
|
80
|
-
### Layout (
|
|
80
|
+
### Layout (4)
|
|
81
81
|
|
|
82
|
-
AppShell, AppBar, Footer
|
|
82
|
+
AppShell, AppBar, Footer, SplitHero
|
|
83
83
|
|
|
84
|
-
### Display (
|
|
84
|
+
### Display (6)
|
|
85
85
|
|
|
86
|
-
Avatar, RollingNumber
|
|
86
|
+
Avatar, RollingNumber, StatCard, Sparkline, MiniDonut, MiniBar
|
|
87
87
|
|
|
88
88
|
### Patterns (3)
|
|
89
89
|
|
|
90
90
|
Wizard, FormValidation, Coachmark
|
|
91
91
|
|
|
92
|
-
### Foundation (
|
|
92
|
+
### Foundation (6)
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Heading, Text, Eyebrow, Link, Divider, DotGrid
|
|
95
95
|
|
|
96
96
|
## Hooks
|
|
97
97
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/hooks/useComposedRefs.ts
|
|
4
|
+
function setRef(ref, node) {
|
|
5
|
+
if (typeof ref === "function") ref(node);
|
|
6
|
+
else if (ref && "current" in ref) ref.current = node;
|
|
7
|
+
}
|
|
8
|
+
function useComposedRefs(...refs) {
|
|
9
|
+
return useCallback((node) => {
|
|
10
|
+
for (const ref of refs) setRef(ref, node);
|
|
11
|
+
}, refs);
|
|
12
|
+
}
|
|
13
|
+
function useReducedMotion() {
|
|
14
|
+
const [reduced, setReduced] = useState(() => {
|
|
15
|
+
if (typeof window === "undefined") return false;
|
|
16
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
17
|
+
});
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (typeof window === "undefined") return;
|
|
20
|
+
const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
21
|
+
const handler = (e) => setReduced(e.matches);
|
|
22
|
+
mql.addEventListener("change", handler);
|
|
23
|
+
return () => mql.removeEventListener("change", handler);
|
|
24
|
+
}, []);
|
|
25
|
+
return reduced;
|
|
26
|
+
}
|
|
27
|
+
function useClickOutside(ref, handler, enabled = true) {
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!enabled) return;
|
|
30
|
+
function listener(e) {
|
|
31
|
+
const el = ref.current;
|
|
32
|
+
if (!el || el.contains(e.target)) return;
|
|
33
|
+
handler(e);
|
|
34
|
+
}
|
|
35
|
+
document.addEventListener("mousedown", listener);
|
|
36
|
+
document.addEventListener("touchstart", listener);
|
|
37
|
+
return () => {
|
|
38
|
+
document.removeEventListener("mousedown", listener);
|
|
39
|
+
document.removeEventListener("touchstart", listener);
|
|
40
|
+
};
|
|
41
|
+
}, [ref, handler, enabled]);
|
|
42
|
+
}
|
|
43
|
+
var FOCUSABLE_SELECTOR = "a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type='hidden']), select:not([disabled]), [tabindex]:not([tabindex='-1'])";
|
|
44
|
+
function useFocusTrap(container, active) {
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!active || !container) return;
|
|
47
|
+
const c = container;
|
|
48
|
+
const previouslyFocused = document.activeElement;
|
|
49
|
+
const focusables = () => Array.from(c.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
50
|
+
(el) => !el.hasAttribute("inert")
|
|
51
|
+
);
|
|
52
|
+
const initial = focusables()[0];
|
|
53
|
+
if (initial) {
|
|
54
|
+
initial.focus();
|
|
55
|
+
} else {
|
|
56
|
+
c.focus();
|
|
57
|
+
}
|
|
58
|
+
function handleKeyDown(e) {
|
|
59
|
+
if (e.key !== "Tab") return;
|
|
60
|
+
const list = focusables();
|
|
61
|
+
const activeEl = document.activeElement;
|
|
62
|
+
const activeInside = activeEl ? c.contains(activeEl) : false;
|
|
63
|
+
if (list.length === 0) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (!activeInside) c.focus();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const firstEl = list[0];
|
|
69
|
+
const lastEl = list.at(-1);
|
|
70
|
+
if (!activeInside) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
(e.shiftKey ? lastEl : firstEl).focus();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (e.shiftKey && activeEl === firstEl) {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
lastEl.focus();
|
|
78
|
+
} else if (!e.shiftKey && activeEl === lastEl) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
firstEl.focus();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
84
|
+
return () => {
|
|
85
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
86
|
+
previouslyFocused?.focus?.();
|
|
87
|
+
};
|
|
88
|
+
}, [active, container]);
|
|
89
|
+
}
|
|
90
|
+
function useMatchMedia(query) {
|
|
91
|
+
const [matches, setMatches] = useState(() => {
|
|
92
|
+
if (typeof window === "undefined") return false;
|
|
93
|
+
return window.matchMedia(query).matches;
|
|
94
|
+
});
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (typeof window === "undefined") return;
|
|
97
|
+
const mql = window.matchMedia(query);
|
|
98
|
+
const handler = (e) => setMatches(e.matches);
|
|
99
|
+
setMatches(mql.matches);
|
|
100
|
+
mql.addEventListener("change", handler);
|
|
101
|
+
return () => mql.removeEventListener("change", handler);
|
|
102
|
+
}, [query]);
|
|
103
|
+
return matches;
|
|
104
|
+
}
|
|
105
|
+
function useResizableColumns(initialWidths, options) {
|
|
106
|
+
const minWidth = options?.minWidth ?? 60;
|
|
107
|
+
const [widths, setWidths] = useState(initialWidths);
|
|
108
|
+
const widthsRef = useRef(widths);
|
|
109
|
+
widthsRef.current = widths;
|
|
110
|
+
const onWidthsChangeRef = useRef(options?.onWidthsChange);
|
|
111
|
+
onWidthsChangeRef.current = options?.onWidthsChange;
|
|
112
|
+
const setWidth = useCallback(
|
|
113
|
+
(col, w) => {
|
|
114
|
+
const clamped = Math.max(minWidth, w);
|
|
115
|
+
setWidths((prev) => {
|
|
116
|
+
const next = { ...prev, [col]: clamped };
|
|
117
|
+
widthsRef.current = next;
|
|
118
|
+
return next;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
[minWidth]
|
|
122
|
+
);
|
|
123
|
+
const startResize = useCallback(
|
|
124
|
+
(col, e) => {
|
|
125
|
+
const startX = e.clientX;
|
|
126
|
+
const startW = widthsRef.current[col] ?? 0;
|
|
127
|
+
try {
|
|
128
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
const onMove = (ev) => {
|
|
132
|
+
const delta = ev.clientX - startX;
|
|
133
|
+
const next = Math.max(minWidth, startW + delta);
|
|
134
|
+
setWidths((prev) => {
|
|
135
|
+
const updated = { ...prev, [col]: next };
|
|
136
|
+
widthsRef.current = updated;
|
|
137
|
+
return updated;
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
const onUp = () => {
|
|
141
|
+
document.removeEventListener("pointermove", onMove);
|
|
142
|
+
document.removeEventListener("pointerup", onUp);
|
|
143
|
+
onWidthsChangeRef.current?.({ ...widthsRef.current });
|
|
144
|
+
};
|
|
145
|
+
document.addEventListener("pointermove", onMove);
|
|
146
|
+
document.addEventListener("pointerup", onUp);
|
|
147
|
+
},
|
|
148
|
+
[minWidth]
|
|
149
|
+
);
|
|
150
|
+
return { widths, setWidth, startResize };
|
|
151
|
+
}
|
|
152
|
+
function useSortableTable(rows, options) {
|
|
153
|
+
const [sortCol, setSortCol] = useState(options?.defaultCol ?? null);
|
|
154
|
+
const [sortDir, setSortDir] = useState(options?.defaultDir ?? "asc");
|
|
155
|
+
const toggleSort = useCallback((col) => {
|
|
156
|
+
setSortCol((prev) => {
|
|
157
|
+
if (prev === col) {
|
|
158
|
+
setSortDir((d) => d === "asc" ? "desc" : "asc");
|
|
159
|
+
return prev;
|
|
160
|
+
}
|
|
161
|
+
setSortDir("asc");
|
|
162
|
+
return col;
|
|
163
|
+
});
|
|
164
|
+
}, []);
|
|
165
|
+
const customComparator = options?.comparator;
|
|
166
|
+
const sorted = useMemo(() => {
|
|
167
|
+
if (sortCol == null) return rows;
|
|
168
|
+
const cmp = customComparator ?? ((a, b, col) => {
|
|
169
|
+
const av = a[col];
|
|
170
|
+
const bv = b[col];
|
|
171
|
+
if (av == null && bv == null) return 0;
|
|
172
|
+
if (av == null) return -1;
|
|
173
|
+
if (bv == null) return 1;
|
|
174
|
+
if (av < bv) return -1;
|
|
175
|
+
if (av > bv) return 1;
|
|
176
|
+
return 0;
|
|
177
|
+
});
|
|
178
|
+
const dirMul = sortDir === "asc" ? 1 : -1;
|
|
179
|
+
return [...rows].sort((a, b) => cmp(a, b, sortCol) * dirMul);
|
|
180
|
+
}, [rows, sortCol, sortDir, customComparator]);
|
|
181
|
+
return { sorted, sortCol, sortDir, toggleSort };
|
|
182
|
+
}
|
|
183
|
+
function useTableSelection(ids, options) {
|
|
184
|
+
const mode = options?.mode ?? "multi";
|
|
185
|
+
const isControlled = options?.selectedIds !== void 0;
|
|
186
|
+
const [uncontrolled, setUncontrolled] = useState(options?.defaultSelected ?? []);
|
|
187
|
+
const selectedIds = isControlled ? options.selectedIds : uncontrolled;
|
|
188
|
+
const setSelected = useCallback(
|
|
189
|
+
(next) => {
|
|
190
|
+
if (!isControlled) setUncontrolled(next);
|
|
191
|
+
options?.onSelectionChange?.(next);
|
|
192
|
+
},
|
|
193
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
|
+
[isControlled, options?.onSelectionChange]
|
|
195
|
+
);
|
|
196
|
+
const isSelected = useCallback((id) => selectedIds.includes(id), [selectedIds]);
|
|
197
|
+
const toggle = useCallback(
|
|
198
|
+
(id) => {
|
|
199
|
+
if (mode === "single") {
|
|
200
|
+
setSelected(selectedIds.includes(id) ? [] : [id]);
|
|
201
|
+
} else {
|
|
202
|
+
setSelected(
|
|
203
|
+
selectedIds.includes(id) ? selectedIds.filter((x) => x !== id) : [...selectedIds, id]
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[mode, selectedIds, setSelected]
|
|
208
|
+
);
|
|
209
|
+
const toggleAll = useCallback(() => {
|
|
210
|
+
if (mode === "single") return;
|
|
211
|
+
if (selectedIds.length === ids.length) {
|
|
212
|
+
setSelected([]);
|
|
213
|
+
} else {
|
|
214
|
+
setSelected([...ids]);
|
|
215
|
+
}
|
|
216
|
+
}, [mode, selectedIds, ids, setSelected]);
|
|
217
|
+
const clear = useCallback(() => setSelected([]), [setSelected]);
|
|
218
|
+
const isAllSelected = useMemo(
|
|
219
|
+
() => selectedIds.length === ids.length && ids.length > 0,
|
|
220
|
+
[selectedIds.length, ids.length]
|
|
221
|
+
);
|
|
222
|
+
const isIndeterminate = useMemo(
|
|
223
|
+
() => selectedIds.length > 0 && !isAllSelected,
|
|
224
|
+
[selectedIds.length, isAllSelected]
|
|
225
|
+
);
|
|
226
|
+
return { selectedIds, isAllSelected, isIndeterminate, isSelected, toggle, toggleAll, clear };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { useClickOutside, useComposedRefs, useFocusTrap, useMatchMedia, useReducedMotion, useResizableColumns, useSortableTable, useTableSelection };
|
|
230
|
+
//# sourceMappingURL=chunk-34YNJKI2.js.map
|
|
231
|
+
//# sourceMappingURL=chunk-34YNJKI2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useComposedRefs.ts","../src/hooks/useReducedMotion.ts","../src/hooks/useClickOutside.ts","../src/hooks/useFocusTrap.ts","../src/hooks/useMatchMedia.ts","../src/hooks/useResizableColumns.ts","../src/hooks/useSortableTable.ts","../src/hooks/useTableSelection.ts"],"names":["useEffect","useState","useCallback","useMemo"],"mappings":"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","file":"chunk-34YNJKI2.js","sourcesContent":["import { type Ref, useCallback } from \"react\";\n\nfunction setRef<T>(ref: Ref<T> | undefined, node: T | null): void {\n\tif (typeof ref === \"function\") ref(node);\n\telse if (ref && \"current\" in ref) (ref as { current: T | null }).current = node;\n}\n\n/**\n * Combine multiple refs (function or object) into one callback ref.\n * Used by every primitive that does forwardRef + needs an internal ref\n * for a hook (Popover trigger, Modal container, etc.).\n */\nexport function useComposedRefs<T>(...refs: Array<Ref<T> | undefined>): (node: T | null) => void {\n\treturn useCallback((node: T | null) => {\n\t\tfor (const ref of refs) setRef(ref, node);\n\t}, refs);\n}\n","import { useEffect, useState } from \"react\";\n\n/**\n * Returns true when the user has set OS-level \"Reduce Motion\" preference.\n * Watches matchMedia for changes (e.g., user toggles preference at runtime).\n * SSR-safe - returns false on the server.\n * Used by Carousel (DS-65) autoplay gating, Accordion expand transition, etc.\n */\nexport function useReducedMotion(): boolean {\n\tconst [reduced, setReduced] = useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\t});\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return;\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tconst handler = (e: MediaQueryListEvent) => setReduced(e.matches);\n\t\tmql.addEventListener(\"change\", handler);\n\t\treturn () => mql.removeEventListener(\"change\", handler);\n\t}, []);\n\treturn reduced;\n}\n","import { type RefObject, useEffect } from \"react\";\n\n/**\n * Calls `handler` when a mousedown/touchstart fires outside of `ref`'s element.\n * Used by Popover (Phase 14), Modal close-on-backdrop, Select dropdown dismiss.\n */\nexport function useClickOutside<T extends HTMLElement>(\n\tref: RefObject<T | null>,\n\thandler: (e: MouseEvent | TouchEvent) => void,\n\tenabled = true,\n): void {\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\t\tfunction listener(e: MouseEvent | TouchEvent) {\n\t\t\tconst el = ref.current;\n\t\t\tif (!el || el.contains(e.target as Node)) return;\n\t\t\thandler(e);\n\t\t}\n\t\tdocument.addEventListener(\"mousedown\", listener);\n\t\tdocument.addEventListener(\"touchstart\", listener);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousedown\", listener);\n\t\t\tdocument.removeEventListener(\"touchstart\", listener);\n\t\t};\n\t}, [ref, handler, enabled]);\n}\n","import { useEffect } from \"react\";\n\nconst FOCUSABLE_SELECTOR =\n\t\"a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), \" +\n\t\"input:not([disabled]):not([type='hidden']), select:not([disabled]), \" +\n\t\"[tabindex]:not([tabindex='-1'])\";\n\n/**\n * Trap Tab/Shift+Tab focus inside `container` while `active` is true.\n *\n * Pass the actual DOM node (use a callback ref + useState pattern in the\n * caller). This guarantees the effect re-runs as soon as the node attaches,\n * which is necessary for portal-mounted containers (Modal, Sheet, BottomSheet)\n * where the node materializes one tick after the parent renders.\n *\n * On activation: focuses the first focusable child, OR the container itself\n * when it has no focusable descendants (container must have `tabIndex={-1}`).\n * On deactivation: restores focus to the previously-focused element.\n *\n * Listens at `document` level so the trap engages even when focus is currently\n * outside the container (e.g., focus leaked to background, or content has no\n * focusables and initial focus didn't land inside).\n */\nexport function useFocusTrap<T extends HTMLElement>(container: T | null, active: boolean): void {\n\tuseEffect(() => {\n\t\tif (!active || !container) return;\n\t\t// Capture as non-null local - TypeScript loses narrowing across closure boundaries.\n\t\tconst c = container;\n\t\tconst previouslyFocused = document.activeElement as HTMLElement | null;\n\n\t\tconst focusables = () =>\n\t\t\tArray.from(c.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n\t\t\t\t(el) => !el.hasAttribute(\"inert\"),\n\t\t\t);\n\n\t\tconst initial = focusables()[0];\n\t\tif (initial) {\n\t\t\tinitial.focus();\n\t\t} else {\n\t\t\tc.focus();\n\t\t}\n\n\t\tfunction handleKeyDown(e: KeyboardEvent) {\n\t\t\tif (e.key !== \"Tab\") return;\n\t\t\tconst list = focusables();\n\t\t\tconst activeEl = document.activeElement as HTMLElement | null;\n\t\t\tconst activeInside = activeEl ? c.contains(activeEl) : false;\n\n\t\t\tif (list.length === 0) {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (!activeInside) c.focus();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst firstEl = list[0]!;\n\t\t\tconst lastEl = list.at(-1)!;\n\n\t\t\tif (!activeInside) {\n\t\t\t\te.preventDefault();\n\t\t\t\t(e.shiftKey ? lastEl : firstEl).focus();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (e.shiftKey && activeEl === firstEl) {\n\t\t\t\te.preventDefault();\n\t\t\t\tlastEl.focus();\n\t\t\t} else if (!e.shiftKey && activeEl === lastEl) {\n\t\t\t\te.preventDefault();\n\t\t\t\tfirstEl.focus();\n\t\t\t}\n\t\t}\n\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\tpreviouslyFocused?.focus?.();\n\t\t};\n\t}, [active, container]);\n}\n","import { useEffect, useState } from \"react\";\n\n/**\n * Returns the `matches` boolean for an arbitrary CSS media query, reactive\n * to viewport changes (resize, rotation, prefers-* toggles at runtime).\n * SSR-safe - returns false on the server.\n * Used by Calendar (DS-68) for the mobile breakpoint switch between Popover\n * and BottomSheet at `(max-width: 640px)`. Generic over any media query string.\n */\nexport function useMatchMedia(query: string): boolean {\n\tconst [matches, setMatches] = useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.matchMedia(query).matches;\n\t});\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return;\n\t\tconst mql = window.matchMedia(query);\n\t\tconst handler = (e: MediaQueryListEvent) => setMatches(e.matches);\n\t\t// Sync once after subscription in case query changed between mount and effect.\n\t\tsetMatches(mql.matches);\n\t\tmql.addEventListener(\"change\", handler);\n\t\treturn () => mql.removeEventListener(\"change\", handler);\n\t}, [query]);\n\treturn matches;\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type React from \"react\";\n\n/**\n * Column-resize state + Pointer Events drag handle (DS-61, D-17-10).\n *\n * Attach `startResize(col, event)` to a drag handle element's onPointerDown.\n * Pointer is captured via setPointerCapture for reliable tracking on fast drags.\n * onWidthsChange fires on pointerup with the final widths snapshot.\n * Consumer owns persistence (e.g. localStorage) - this hook only emits the event.\n *\n * @example\n * const { widths, startResize } = useResizableColumns({ name: 120, role: 100 });\n * <Table.HeaderCell width={widths.name}>\n * Name\n * <span onPointerDown={(e) => startResize(\"name\", e)} />\n * </Table.HeaderCell>\n */\nexport function useResizableColumns(\n\tinitialWidths: Record<string, number>,\n\toptions?: {\n\t\t/** Minimum column width in px. Default: 60. */\n\t\tminWidth?: number;\n\t\t/** Called on pointerup with the final widths record. */\n\t\tonWidthsChange?: (widths: Record<string, number>) => void;\n\t},\n): {\n\twidths: Record<string, number>;\n\tsetWidth: (col: string, w: number) => void;\n\tstartResize: (col: string, e: React.PointerEvent) => void;\n} {\n\tconst minWidth = options?.minWidth ?? 60;\n\tconst [widths, setWidths] = useState<Record<string, number>>(initialWidths);\n\n\t// Ref mirrors widths state so closure-captured handlers always see fresh value.\n\tconst widthsRef = useRef(widths);\n\twidthsRef.current = widths;\n\n\tconst onWidthsChangeRef = useRef(options?.onWidthsChange);\n\tonWidthsChangeRef.current = options?.onWidthsChange;\n\n\tconst setWidth = useCallback(\n\t\t(col: string, w: number) => {\n\t\t\tconst clamped = Math.max(minWidth, w);\n\t\t\tsetWidths((prev) => {\n\t\t\t\tconst next = { ...prev, [col]: clamped };\n\t\t\t\twidthsRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\t[minWidth],\n\t);\n\n\tconst startResize = useCallback(\n\t\t(col: string, e: React.PointerEvent) => {\n\t\t\tconst startX = e.clientX;\n\t\t\tconst startW = widthsRef.current[col] ?? 0;\n\n\t\t\t// Capture pointer so we track movement even if cursor leaves the element.\n\t\t\ttry {\n\t\t\t\t(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);\n\t\t\t} catch {\n\t\t\t\t// jsdom and some older browsers may throw; safe to ignore.\n\t\t\t}\n\n\t\t\tconst onMove = (ev: PointerEvent) => {\n\t\t\t\tconst delta = ev.clientX - startX;\n\t\t\t\tconst next = Math.max(minWidth, startW + delta);\n\t\t\t\tsetWidths((prev) => {\n\t\t\t\t\tconst updated = { ...prev, [col]: next };\n\t\t\t\t\twidthsRef.current = updated;\n\t\t\t\t\treturn updated;\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst onUp = () => {\n\t\t\t\tdocument.removeEventListener(\"pointermove\", onMove);\n\t\t\t\tdocument.removeEventListener(\"pointerup\", onUp);\n\t\t\t\tonWidthsChangeRef.current?.({ ...widthsRef.current });\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"pointermove\", onMove);\n\t\t\tdocument.addEventListener(\"pointerup\", onUp);\n\t\t},\n\t\t[minWidth],\n\t);\n\n\treturn { widths, setWidth, startResize };\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport interface SortState<T> {\n\tcol: keyof T | null;\n\tdir: \"asc\" | \"desc\";\n}\n\n/**\n * Sortable table state + derived sorted rows.\n * Used by Table primitive (DS-61, D-17-07) header click handlers.\n * Stable sort via Array.prototype.sort (ES2019 guarantee).\n *\n * Usage:\n * const { sorted, sortCol, sortDir, toggleSort } = useSortableTable(rows, { defaultCol: \"name\" });\n * <Table.HeaderCell sortable sortDir={sortCol === \"name\" ? sortDir : null} onToggleSort={() => toggleSort(\"name\")}>\n * Name\n * </Table.HeaderCell>\n *\n * @param rows - The source data array (reference-stable recommended for memo perf).\n * @param options.defaultCol - Initial sort column (default: null = unsorted).\n * @param options.defaultDir - Initial sort direction (default: \"asc\").\n * @param options.comparator - Custom compare fn `(a, b, col) => number`.\n */\nexport function useSortableTable<T>(\n\trows: T[],\n\toptions?: {\n\t\tdefaultCol?: keyof T;\n\t\tdefaultDir?: \"asc\" | \"desc\";\n\t\tcomparator?: (a: T, b: T, col: keyof T) => number;\n\t},\n): {\n\tsorted: T[];\n\tsortCol: keyof T | null;\n\tsortDir: \"asc\" | \"desc\";\n\ttoggleSort: (col: keyof T) => void;\n} {\n\tconst [sortCol, setSortCol] = useState<keyof T | null>(options?.defaultCol ?? null);\n\tconst [sortDir, setSortDir] = useState<\"asc\" | \"desc\">(options?.defaultDir ?? \"asc\");\n\n\tconst toggleSort = useCallback((col: keyof T) => {\n\t\tsetSortCol((prev) => {\n\t\t\tif (prev === col) {\n\t\t\t\tsetSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\n\t\t\t\treturn prev;\n\t\t\t}\n\t\t\tsetSortDir(\"asc\");\n\t\t\treturn col;\n\t\t});\n\t}, []);\n\n\tconst customComparator = options?.comparator;\n\n\tconst sorted = useMemo(() => {\n\t\tif (sortCol == null) return rows;\n\t\tconst cmp =\n\t\t\tcustomComparator ??\n\t\t\t((a: T, b: T, col: keyof T) => {\n\t\t\t\tconst av = a[col];\n\t\t\t\tconst bv = b[col];\n\t\t\t\tif (av == null && bv == null) return 0;\n\t\t\t\tif (av == null) return -1;\n\t\t\t\tif (bv == null) return 1;\n\t\t\t\tif (av < bv) return -1;\n\t\t\t\tif (av > bv) return 1;\n\t\t\t\treturn 0;\n\t\t\t});\n\t\tconst dirMul = sortDir === \"asc\" ? 1 : -1;\n\t\treturn [...rows].sort((a, b) => cmp(a, b, sortCol) * dirMul);\n\t}, [rows, sortCol, sortDir, customComparator]);\n\n\treturn { sorted, sortCol, sortDir, toggleSort };\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport type SelectionMode = \"single\" | \"multi\";\n\n/**\n * Selection state for Table.SelectAllCell + Table.SelectCell (DS-61, D-17-09).\n * Modes: \"single\" (at most 1 selected) | \"multi\" (default).\n * Controlled via selectedIds + onSelectionChange; uncontrolled via defaultSelected.\n *\n * @example\n * const { selectedIds, isAllSelected, isIndeterminate, toggle, toggleAll, clear } =\n * useTableSelection(rowIds, { mode: \"multi\", defaultSelected: [] });\n */\nexport function useTableSelection<Id extends string | number>(\n\tids: Id[],\n\toptions?: {\n\t\tmode?: SelectionMode;\n\t\tdefaultSelected?: Id[];\n\t\t/** Controlled selected ids - takes priority over internal state. */\n\t\tselectedIds?: Id[];\n\t\tonSelectionChange?: (ids: Id[]) => void;\n\t},\n): {\n\tselectedIds: Id[];\n\tisAllSelected: boolean;\n\tisIndeterminate: boolean;\n\tisSelected: (id: Id) => boolean;\n\ttoggle: (id: Id) => void;\n\ttoggleAll: () => void;\n\tclear: () => void;\n} {\n\tconst mode = options?.mode ?? \"multi\";\n\tconst isControlled = options?.selectedIds !== undefined;\n\tconst [uncontrolled, setUncontrolled] = useState<Id[]>(options?.defaultSelected ?? []);\n\tconst selectedIds = isControlled ? (options!.selectedIds as Id[]) : uncontrolled;\n\n\tconst setSelected = useCallback(\n\t\t(next: Id[]) => {\n\t\t\tif (!isControlled) setUncontrolled(next);\n\t\t\toptions?.onSelectionChange?.(next);\n\t\t},\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[isControlled, options?.onSelectionChange],\n\t);\n\n\tconst isSelected = useCallback((id: Id) => selectedIds.includes(id), [selectedIds]);\n\n\tconst toggle = useCallback(\n\t\t(id: Id) => {\n\t\t\tif (mode === \"single\") {\n\t\t\t\tsetSelected(selectedIds.includes(id) ? [] : [id]);\n\t\t\t} else {\n\t\t\t\tsetSelected(\n\t\t\t\t\tselectedIds.includes(id) ? selectedIds.filter((x) => x !== id) : [...selectedIds, id],\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, selectedIds, setSelected],\n\t);\n\n\tconst toggleAll = useCallback(() => {\n\t\tif (mode === \"single\") return; // no-op in single mode\n\t\tif (selectedIds.length === ids.length) {\n\t\t\tsetSelected([]);\n\t\t} else {\n\t\t\tsetSelected([...ids]);\n\t\t}\n\t}, [mode, selectedIds, ids, setSelected]);\n\n\tconst clear = useCallback(() => setSelected([]), [setSelected]);\n\n\tconst isAllSelected = useMemo(\n\t\t() => selectedIds.length === ids.length && ids.length > 0,\n\t\t[selectedIds.length, ids.length],\n\t);\n\n\tconst isIndeterminate = useMemo(\n\t\t() => selectedIds.length > 0 && !isAllSelected,\n\t\t[selectedIds.length, isAllSelected],\n\t);\n\n\treturn { selectedIds, isAllSelected, isIndeterminate, isSelected, toggle, toggleAll, clear };\n}\n"]}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -42,6 +42,24 @@ interface ShortcutOptions {
|
|
|
42
42
|
*/
|
|
43
43
|
declare function useKeyboardShortcut(combo: string | string[], handler: (e: KeyboardEvent) => void, options?: ShortcutOptions): void;
|
|
44
44
|
|
|
45
|
+
interface LongPressHandlers {
|
|
46
|
+
onPointerDown: (e: React.PointerEvent) => void;
|
|
47
|
+
onPointerUp: (e: React.PointerEvent) => void;
|
|
48
|
+
onPointerCancel: (e: React.PointerEvent) => void;
|
|
49
|
+
onPointerLeave: (e: React.PointerEvent) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fires `onLongPress` when a pointer is held for `ms` (default 600ms). Mouse
|
|
53
|
+
* users release well under the threshold, so it stays inert on desktop — pair
|
|
54
|
+
* it with a hover-reveal affordance there and a long-press → ActionSheet on
|
|
55
|
+
* touch. Spread the returned handlers onto the target element.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const lp = useLongPress(() => setSheetOpen(true));
|
|
59
|
+
* return <div {...lp}>…</div>;
|
|
60
|
+
*/
|
|
61
|
+
declare function useLongPress(onLongPress: () => void, ms?: number): LongPressHandlers;
|
|
62
|
+
|
|
45
63
|
/**
|
|
46
64
|
* Returns the `matches` boolean for an arbitrary CSS media query, reactive
|
|
47
65
|
* to viewport changes (resize, rotation, prefers-* toggles at runtime).
|
|
@@ -142,4 +160,4 @@ declare function useResizableColumns(initialWidths: Record<string, number>, opti
|
|
|
142
160
|
startResize: (col: string, e: react__default.PointerEvent) => void;
|
|
143
161
|
};
|
|
144
162
|
|
|
145
|
-
export { type SelectionMode, type SortState, useClickOutside, useComposedRefs, useFocusTrap, useKeyboardShortcut, useMatchMedia, useReducedMotion, useResizableColumns, useSortableTable, useTableSelection };
|
|
163
|
+
export { type LongPressHandlers, type SelectionMode, type SortState, useClickOutside, useComposedRefs, useFocusTrap, useKeyboardShortcut, useLongPress, useMatchMedia, useReducedMotion, useResizableColumns, useSortableTable, useTableSelection };
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { useClickOutside, useComposedRefs, useFocusTrap, useMatchMedia, useReducedMotion } from '../chunk-
|
|
2
|
-
import { useEffect,
|
|
1
|
+
export { useClickOutside, useComposedRefs, useFocusTrap, useMatchMedia, useReducedMotion, useResizableColumns, useSortableTable, useTableSelection } from '../chunk-34YNJKI2.js';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
3
|
|
|
4
4
|
var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
|
|
5
5
|
function parseCombo(combo) {
|
|
@@ -48,130 +48,28 @@ function useKeyboardShortcut(combo, handler, options = {}) {
|
|
|
48
48
|
};
|
|
49
49
|
}, [combo, handler, enabled, preventDefault, target]);
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}, []);
|
|
64
|
-
const customComparator = options?.comparator;
|
|
65
|
-
const sorted = useMemo(() => {
|
|
66
|
-
if (sortCol == null) return rows;
|
|
67
|
-
const cmp = customComparator ?? ((a, b, col) => {
|
|
68
|
-
const av = a[col];
|
|
69
|
-
const bv = b[col];
|
|
70
|
-
if (av == null && bv == null) return 0;
|
|
71
|
-
if (av == null) return -1;
|
|
72
|
-
if (bv == null) return 1;
|
|
73
|
-
if (av < bv) return -1;
|
|
74
|
-
if (av > bv) return 1;
|
|
75
|
-
return 0;
|
|
76
|
-
});
|
|
77
|
-
const dirMul = sortDir === "asc" ? 1 : -1;
|
|
78
|
-
return [...rows].sort((a, b) => cmp(a, b, sortCol) * dirMul);
|
|
79
|
-
}, [rows, sortCol, sortDir, customComparator]);
|
|
80
|
-
return { sorted, sortCol, sortDir, toggleSort };
|
|
81
|
-
}
|
|
82
|
-
function useTableSelection(ids, options) {
|
|
83
|
-
const mode = options?.mode ?? "multi";
|
|
84
|
-
const isControlled = options?.selectedIds !== void 0;
|
|
85
|
-
const [uncontrolled, setUncontrolled] = useState(options?.defaultSelected ?? []);
|
|
86
|
-
const selectedIds = isControlled ? options.selectedIds : uncontrolled;
|
|
87
|
-
const setSelected = useCallback(
|
|
88
|
-
(next) => {
|
|
89
|
-
if (!isControlled) setUncontrolled(next);
|
|
90
|
-
options?.onSelectionChange?.(next);
|
|
91
|
-
},
|
|
92
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
-
[isControlled, options?.onSelectionChange]
|
|
94
|
-
);
|
|
95
|
-
const isSelected = useCallback((id) => selectedIds.includes(id), [selectedIds]);
|
|
96
|
-
const toggle = useCallback(
|
|
97
|
-
(id) => {
|
|
98
|
-
if (mode === "single") {
|
|
99
|
-
setSelected(selectedIds.includes(id) ? [] : [id]);
|
|
100
|
-
} else {
|
|
101
|
-
setSelected(
|
|
102
|
-
selectedIds.includes(id) ? selectedIds.filter((x) => x !== id) : [...selectedIds, id]
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
[mode, selectedIds, setSelected]
|
|
107
|
-
);
|
|
108
|
-
const toggleAll = useCallback(() => {
|
|
109
|
-
if (mode === "single") return;
|
|
110
|
-
if (selectedIds.length === ids.length) {
|
|
111
|
-
setSelected([]);
|
|
112
|
-
} else {
|
|
113
|
-
setSelected([...ids]);
|
|
51
|
+
function useLongPress(onLongPress, ms = 600) {
|
|
52
|
+
const timer = useRef(null);
|
|
53
|
+
const start = () => {
|
|
54
|
+
if (timer.current) clearTimeout(timer.current);
|
|
55
|
+
timer.current = setTimeout(() => {
|
|
56
|
+
onLongPress();
|
|
57
|
+
}, ms);
|
|
58
|
+
};
|
|
59
|
+
const cancel = () => {
|
|
60
|
+
if (timer.current) {
|
|
61
|
+
clearTimeout(timer.current);
|
|
62
|
+
timer.current = null;
|
|
114
63
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
() => selectedIds.length > 0 && !isAllSelected,
|
|
123
|
-
[selectedIds.length, isAllSelected]
|
|
124
|
-
);
|
|
125
|
-
return { selectedIds, isAllSelected, isIndeterminate, isSelected, toggle, toggleAll, clear };
|
|
126
|
-
}
|
|
127
|
-
function useResizableColumns(initialWidths, options) {
|
|
128
|
-
const minWidth = options?.minWidth ?? 60;
|
|
129
|
-
const [widths, setWidths] = useState(initialWidths);
|
|
130
|
-
const widthsRef = useRef(widths);
|
|
131
|
-
widthsRef.current = widths;
|
|
132
|
-
const onWidthsChangeRef = useRef(options?.onWidthsChange);
|
|
133
|
-
onWidthsChangeRef.current = options?.onWidthsChange;
|
|
134
|
-
const setWidth = useCallback(
|
|
135
|
-
(col, w) => {
|
|
136
|
-
const clamped = Math.max(minWidth, w);
|
|
137
|
-
setWidths((prev) => {
|
|
138
|
-
const next = { ...prev, [col]: clamped };
|
|
139
|
-
widthsRef.current = next;
|
|
140
|
-
return next;
|
|
141
|
-
});
|
|
142
|
-
},
|
|
143
|
-
[minWidth]
|
|
144
|
-
);
|
|
145
|
-
const startResize = useCallback(
|
|
146
|
-
(col, e) => {
|
|
147
|
-
const startX = e.clientX;
|
|
148
|
-
const startW = widthsRef.current[col] ?? 0;
|
|
149
|
-
try {
|
|
150
|
-
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
151
|
-
} catch {
|
|
152
|
-
}
|
|
153
|
-
const onMove = (ev) => {
|
|
154
|
-
const delta = ev.clientX - startX;
|
|
155
|
-
const next = Math.max(minWidth, startW + delta);
|
|
156
|
-
setWidths((prev) => {
|
|
157
|
-
const updated = { ...prev, [col]: next };
|
|
158
|
-
widthsRef.current = updated;
|
|
159
|
-
return updated;
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
const onUp = () => {
|
|
163
|
-
document.removeEventListener("pointermove", onMove);
|
|
164
|
-
document.removeEventListener("pointerup", onUp);
|
|
165
|
-
onWidthsChangeRef.current?.({ ...widthsRef.current });
|
|
166
|
-
};
|
|
167
|
-
document.addEventListener("pointermove", onMove);
|
|
168
|
-
document.addEventListener("pointerup", onUp);
|
|
169
|
-
},
|
|
170
|
-
[minWidth]
|
|
171
|
-
);
|
|
172
|
-
return { widths, setWidth, startResize };
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
onPointerDown: start,
|
|
67
|
+
onPointerUp: cancel,
|
|
68
|
+
onPointerCancel: cancel,
|
|
69
|
+
onPointerLeave: cancel
|
|
70
|
+
};
|
|
173
71
|
}
|
|
174
72
|
|
|
175
|
-
export { useKeyboardShortcut,
|
|
73
|
+
export { useKeyboardShortcut, useLongPress };
|
|
176
74
|
//# sourceMappingURL=index.js.map
|
|
177
75
|
//# sourceMappingURL=index.js.map
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/useKeyboardShortcut.ts","../../src/hooks/useSortableTable.ts","../../src/hooks/useTableSelection.ts","../../src/hooks/useResizableColumns.ts"],"names":["useState","useCallback","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect } from \"react\";\n\ninterface ShortcutOptions {\n\tenabled?: boolean;\n\tpreventDefault?: boolean;\n\ttarget?: HTMLElement | Window;\n}\n\nconst isMac = typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform);\n\ninterface ParsedCombo {\n\tkey: string;\n\tmod: boolean;\n\tctrl: boolean;\n\tshift: boolean;\n\talt: boolean;\n}\n\nfunction parseCombo(combo: string): ParsedCombo {\n\tconst parts = combo\n\t\t.toLowerCase()\n\t\t.split(\"+\")\n\t\t.map((p) => p.trim());\n\tconst out: ParsedCombo = {\n\t\tkey: \"\",\n\t\tmod: false,\n\t\tctrl: false,\n\t\tshift: false,\n\t\talt: false,\n\t};\n\tfor (const p of parts) {\n\t\tif (p === \"mod\") out.mod = true;\n\t\telse if (p === \"ctrl\") out.ctrl = true;\n\t\telse if (p === \"shift\") out.shift = true;\n\t\telse if (p === \"alt\" || p === \"opt\") out.alt = true;\n\t\telse out.key = p;\n\t}\n\treturn out;\n}\n\nfunction matches(e: KeyboardEvent, combo: ParsedCombo): boolean {\n\tif (e.key.toLowerCase() !== combo.key.toLowerCase()) return false;\n\tconst modPressed = isMac ? e.metaKey : e.ctrlKey;\n\tif (combo.mod && !modPressed) return false;\n\tif (combo.ctrl && !e.ctrlKey) return false;\n\tif (combo.shift !== e.shiftKey) return false;\n\tif (combo.alt !== e.altKey) return false;\n\treturn true;\n}\n\n/**\n * Bind a keyboard shortcut. Use \"mod+k\" for Cmd-on-Mac, Ctrl-on-Win/Linux.\n * Used by CommandPalette (Phase 17 - ⌘K), Modal Esc-to-close (Phase 14).\n */\nexport function useKeyboardShortcut(\n\tcombo: string | string[],\n\thandler: (e: KeyboardEvent) => void,\n\toptions: ShortcutOptions = {},\n): void {\n\tconst { enabled = true, preventDefault = false, target } = options;\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\t\tconst combos = (Array.isArray(combo) ? combo : [combo]).map(parseCombo);\n\t\tconst t: EventTarget = target ?? (typeof window !== \"undefined\" ? window : ({} as EventTarget));\n\t\tfunction listener(ev: Event) {\n\t\t\tconst e = ev as KeyboardEvent;\n\t\t\tif (combos.some((c) => matches(e, c))) {\n\t\t\t\tif (preventDefault) e.preventDefault();\n\t\t\t\thandler(e);\n\t\t\t}\n\t\t}\n\t\tt.addEventListener(\"keydown\", listener);\n\t\treturn () => {\n\t\t\tt.removeEventListener(\"keydown\", listener);\n\t\t};\n\t}, [combo, handler, enabled, preventDefault, target]);\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport interface SortState<T> {\n\tcol: keyof T | null;\n\tdir: \"asc\" | \"desc\";\n}\n\n/**\n * Sortable table state + derived sorted rows.\n * Used by Table primitive (DS-61, D-17-07) header click handlers.\n * Stable sort via Array.prototype.sort (ES2019 guarantee).\n *\n * Usage:\n * const { sorted, sortCol, sortDir, toggleSort } = useSortableTable(rows, { defaultCol: \"name\" });\n * <Table.HeaderCell sortable sortDir={sortCol === \"name\" ? sortDir : null} onToggleSort={() => toggleSort(\"name\")}>\n * Name\n * </Table.HeaderCell>\n *\n * @param rows - The source data array (reference-stable recommended for memo perf).\n * @param options.defaultCol - Initial sort column (default: null = unsorted).\n * @param options.defaultDir - Initial sort direction (default: \"asc\").\n * @param options.comparator - Custom compare fn `(a, b, col) => number`.\n */\nexport function useSortableTable<T>(\n\trows: T[],\n\toptions?: {\n\t\tdefaultCol?: keyof T;\n\t\tdefaultDir?: \"asc\" | \"desc\";\n\t\tcomparator?: (a: T, b: T, col: keyof T) => number;\n\t},\n): {\n\tsorted: T[];\n\tsortCol: keyof T | null;\n\tsortDir: \"asc\" | \"desc\";\n\ttoggleSort: (col: keyof T) => void;\n} {\n\tconst [sortCol, setSortCol] = useState<keyof T | null>(options?.defaultCol ?? null);\n\tconst [sortDir, setSortDir] = useState<\"asc\" | \"desc\">(options?.defaultDir ?? \"asc\");\n\n\tconst toggleSort = useCallback((col: keyof T) => {\n\t\tsetSortCol((prev) => {\n\t\t\tif (prev === col) {\n\t\t\t\tsetSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\n\t\t\t\treturn prev;\n\t\t\t}\n\t\t\tsetSortDir(\"asc\");\n\t\t\treturn col;\n\t\t});\n\t}, []);\n\n\tconst customComparator = options?.comparator;\n\n\tconst sorted = useMemo(() => {\n\t\tif (sortCol == null) return rows;\n\t\tconst cmp =\n\t\t\tcustomComparator ??\n\t\t\t((a: T, b: T, col: keyof T) => {\n\t\t\t\tconst av = a[col];\n\t\t\t\tconst bv = b[col];\n\t\t\t\tif (av == null && bv == null) return 0;\n\t\t\t\tif (av == null) return -1;\n\t\t\t\tif (bv == null) return 1;\n\t\t\t\tif (av < bv) return -1;\n\t\t\t\tif (av > bv) return 1;\n\t\t\t\treturn 0;\n\t\t\t});\n\t\tconst dirMul = sortDir === \"asc\" ? 1 : -1;\n\t\treturn [...rows].sort((a, b) => cmp(a, b, sortCol) * dirMul);\n\t}, [rows, sortCol, sortDir, customComparator]);\n\n\treturn { sorted, sortCol, sortDir, toggleSort };\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport type SelectionMode = \"single\" | \"multi\";\n\n/**\n * Selection state for Table.SelectAllCell + Table.SelectCell (DS-61, D-17-09).\n * Modes: \"single\" (at most 1 selected) | \"multi\" (default).\n * Controlled via selectedIds + onSelectionChange; uncontrolled via defaultSelected.\n *\n * @example\n * const { selectedIds, isAllSelected, isIndeterminate, toggle, toggleAll, clear } =\n * useTableSelection(rowIds, { mode: \"multi\", defaultSelected: [] });\n */\nexport function useTableSelection<Id extends string | number>(\n\tids: Id[],\n\toptions?: {\n\t\tmode?: SelectionMode;\n\t\tdefaultSelected?: Id[];\n\t\t/** Controlled selected ids - takes priority over internal state. */\n\t\tselectedIds?: Id[];\n\t\tonSelectionChange?: (ids: Id[]) => void;\n\t},\n): {\n\tselectedIds: Id[];\n\tisAllSelected: boolean;\n\tisIndeterminate: boolean;\n\tisSelected: (id: Id) => boolean;\n\ttoggle: (id: Id) => void;\n\ttoggleAll: () => void;\n\tclear: () => void;\n} {\n\tconst mode = options?.mode ?? \"multi\";\n\tconst isControlled = options?.selectedIds !== undefined;\n\tconst [uncontrolled, setUncontrolled] = useState<Id[]>(options?.defaultSelected ?? []);\n\tconst selectedIds = isControlled ? (options!.selectedIds as Id[]) : uncontrolled;\n\n\tconst setSelected = useCallback(\n\t\t(next: Id[]) => {\n\t\t\tif (!isControlled) setUncontrolled(next);\n\t\t\toptions?.onSelectionChange?.(next);\n\t\t},\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[isControlled, options?.onSelectionChange],\n\t);\n\n\tconst isSelected = useCallback((id: Id) => selectedIds.includes(id), [selectedIds]);\n\n\tconst toggle = useCallback(\n\t\t(id: Id) => {\n\t\t\tif (mode === \"single\") {\n\t\t\t\tsetSelected(selectedIds.includes(id) ? [] : [id]);\n\t\t\t} else {\n\t\t\t\tsetSelected(\n\t\t\t\t\tselectedIds.includes(id) ? selectedIds.filter((x) => x !== id) : [...selectedIds, id],\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\t[mode, selectedIds, setSelected],\n\t);\n\n\tconst toggleAll = useCallback(() => {\n\t\tif (mode === \"single\") return; // no-op in single mode\n\t\tif (selectedIds.length === ids.length) {\n\t\t\tsetSelected([]);\n\t\t} else {\n\t\t\tsetSelected([...ids]);\n\t\t}\n\t}, [mode, selectedIds, ids, setSelected]);\n\n\tconst clear = useCallback(() => setSelected([]), [setSelected]);\n\n\tconst isAllSelected = useMemo(\n\t\t() => selectedIds.length === ids.length && ids.length > 0,\n\t\t[selectedIds.length, ids.length],\n\t);\n\n\tconst isIndeterminate = useMemo(\n\t\t() => selectedIds.length > 0 && !isAllSelected,\n\t\t[selectedIds.length, isAllSelected],\n\t);\n\n\treturn { selectedIds, isAllSelected, isIndeterminate, isSelected, toggle, toggleAll, clear };\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport type React from \"react\";\n\n/**\n * Column-resize state + Pointer Events drag handle (DS-61, D-17-10).\n *\n * Attach `startResize(col, event)` to a drag handle element's onPointerDown.\n * Pointer is captured via setPointerCapture for reliable tracking on fast drags.\n * onWidthsChange fires on pointerup with the final widths snapshot.\n * Consumer owns persistence (e.g. localStorage) - this hook only emits the event.\n *\n * @example\n * const { widths, startResize } = useResizableColumns({ name: 120, role: 100 });\n * <Table.HeaderCell width={widths.name}>\n * Name\n * <span onPointerDown={(e) => startResize(\"name\", e)} />\n * </Table.HeaderCell>\n */\nexport function useResizableColumns(\n\tinitialWidths: Record<string, number>,\n\toptions?: {\n\t\t/** Minimum column width in px. Default: 60. */\n\t\tminWidth?: number;\n\t\t/** Called on pointerup with the final widths record. */\n\t\tonWidthsChange?: (widths: Record<string, number>) => void;\n\t},\n): {\n\twidths: Record<string, number>;\n\tsetWidth: (col: string, w: number) => void;\n\tstartResize: (col: string, e: React.PointerEvent) => void;\n} {\n\tconst minWidth = options?.minWidth ?? 60;\n\tconst [widths, setWidths] = useState<Record<string, number>>(initialWidths);\n\n\t// Ref mirrors widths state so closure-captured handlers always see fresh value.\n\tconst widthsRef = useRef(widths);\n\twidthsRef.current = widths;\n\n\tconst onWidthsChangeRef = useRef(options?.onWidthsChange);\n\tonWidthsChangeRef.current = options?.onWidthsChange;\n\n\tconst setWidth = useCallback(\n\t\t(col: string, w: number) => {\n\t\t\tconst clamped = Math.max(minWidth, w);\n\t\t\tsetWidths((prev) => {\n\t\t\t\tconst next = { ...prev, [col]: clamped };\n\t\t\t\twidthsRef.current = next;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\t[minWidth],\n\t);\n\n\tconst startResize = useCallback(\n\t\t(col: string, e: React.PointerEvent) => {\n\t\t\tconst startX = e.clientX;\n\t\t\tconst startW = widthsRef.current[col] ?? 0;\n\n\t\t\t// Capture pointer so we track movement even if cursor leaves the element.\n\t\t\ttry {\n\t\t\t\t(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);\n\t\t\t} catch {\n\t\t\t\t// jsdom and some older browsers may throw; safe to ignore.\n\t\t\t}\n\n\t\t\tconst onMove = (ev: PointerEvent) => {\n\t\t\t\tconst delta = ev.clientX - startX;\n\t\t\t\tconst next = Math.max(minWidth, startW + delta);\n\t\t\t\tsetWidths((prev) => {\n\t\t\t\t\tconst updated = { ...prev, [col]: next };\n\t\t\t\t\twidthsRef.current = updated;\n\t\t\t\t\treturn updated;\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst onUp = () => {\n\t\t\t\tdocument.removeEventListener(\"pointermove\", onMove);\n\t\t\t\tdocument.removeEventListener(\"pointerup\", onUp);\n\t\t\t\tonWidthsChangeRef.current?.({ ...widthsRef.current });\n\t\t\t};\n\n\t\t\tdocument.addEventListener(\"pointermove\", onMove);\n\t\t\tdocument.addEventListener(\"pointerup\", onUp);\n\t\t},\n\t\t[minWidth],\n\t);\n\n\treturn { widths, setWidth, startResize };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useKeyboardShortcut.ts","../../src/hooks/useLongPress.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect } from \"react\";\n\ninterface ShortcutOptions {\n\tenabled?: boolean;\n\tpreventDefault?: boolean;\n\ttarget?: HTMLElement | Window;\n}\n\nconst isMac = typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform);\n\ninterface ParsedCombo {\n\tkey: string;\n\tmod: boolean;\n\tctrl: boolean;\n\tshift: boolean;\n\talt: boolean;\n}\n\nfunction parseCombo(combo: string): ParsedCombo {\n\tconst parts = combo\n\t\t.toLowerCase()\n\t\t.split(\"+\")\n\t\t.map((p) => p.trim());\n\tconst out: ParsedCombo = {\n\t\tkey: \"\",\n\t\tmod: false,\n\t\tctrl: false,\n\t\tshift: false,\n\t\talt: false,\n\t};\n\tfor (const p of parts) {\n\t\tif (p === \"mod\") out.mod = true;\n\t\telse if (p === \"ctrl\") out.ctrl = true;\n\t\telse if (p === \"shift\") out.shift = true;\n\t\telse if (p === \"alt\" || p === \"opt\") out.alt = true;\n\t\telse out.key = p;\n\t}\n\treturn out;\n}\n\nfunction matches(e: KeyboardEvent, combo: ParsedCombo): boolean {\n\tif (e.key.toLowerCase() !== combo.key.toLowerCase()) return false;\n\tconst modPressed = isMac ? e.metaKey : e.ctrlKey;\n\tif (combo.mod && !modPressed) return false;\n\tif (combo.ctrl && !e.ctrlKey) return false;\n\tif (combo.shift !== e.shiftKey) return false;\n\tif (combo.alt !== e.altKey) return false;\n\treturn true;\n}\n\n/**\n * Bind a keyboard shortcut. Use \"mod+k\" for Cmd-on-Mac, Ctrl-on-Win/Linux.\n * Used by CommandPalette (Phase 17 - ⌘K), Modal Esc-to-close (Phase 14).\n */\nexport function useKeyboardShortcut(\n\tcombo: string | string[],\n\thandler: (e: KeyboardEvent) => void,\n\toptions: ShortcutOptions = {},\n): void {\n\tconst { enabled = true, preventDefault = false, target } = options;\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\t\tconst combos = (Array.isArray(combo) ? combo : [combo]).map(parseCombo);\n\t\tconst t: EventTarget = target ?? (typeof window !== \"undefined\" ? window : ({} as EventTarget));\n\t\tfunction listener(ev: Event) {\n\t\t\tconst e = ev as KeyboardEvent;\n\t\t\tif (combos.some((c) => matches(e, c))) {\n\t\t\t\tif (preventDefault) e.preventDefault();\n\t\t\t\thandler(e);\n\t\t\t}\n\t\t}\n\t\tt.addEventListener(\"keydown\", listener);\n\t\treturn () => {\n\t\t\tt.removeEventListener(\"keydown\", listener);\n\t\t};\n\t}, [combo, handler, enabled, preventDefault, target]);\n}\n","import { useRef } from \"react\";\n\nexport interface LongPressHandlers {\n\tonPointerDown: (e: React.PointerEvent) => void;\n\tonPointerUp: (e: React.PointerEvent) => void;\n\tonPointerCancel: (e: React.PointerEvent) => void;\n\tonPointerLeave: (e: React.PointerEvent) => void;\n}\n\n/**\n * Fires `onLongPress` when a pointer is held for `ms` (default 600ms). Mouse\n * users release well under the threshold, so it stays inert on desktop — pair\n * it with a hover-reveal affordance there and a long-press → ActionSheet on\n * touch. Spread the returned handlers onto the target element.\n *\n * @example\n * const lp = useLongPress(() => setSheetOpen(true));\n * return <div {...lp}>…</div>;\n */\nexport function useLongPress(onLongPress: () => void, ms = 600): LongPressHandlers {\n\tconst timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\tconst start = () => {\n\t\tif (timer.current) clearTimeout(timer.current);\n\t\ttimer.current = setTimeout(() => {\n\t\t\tonLongPress();\n\t\t}, ms);\n\t};\n\tconst cancel = () => {\n\t\tif (timer.current) {\n\t\t\tclearTimeout(timer.current);\n\t\t\ttimer.current = null;\n\t\t}\n\t};\n\n\treturn {\n\t\tonPointerDown: start,\n\t\tonPointerUp: cancel,\n\t\tonPointerCancel: cancel,\n\t\tonPointerLeave: cancel,\n\t};\n}\n"]}
|