@petrarca/sonnet-core 0.2.0 → 0.4.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 +18 -6
- package/dist/hooks/index.d.ts +9 -1
- package/dist/hooks/index.js +43 -25
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
# @petrarca/sonnet-core
|
|
2
2
|
|
|
3
|
-
Foundation utilities for the Petrarca Sonnet component library.
|
|
3
|
+
Foundation utilities for the Petrarca Sonnet component library. Leaf package —
|
|
4
|
+
all other Sonnet packages depend on it.
|
|
4
5
|
|
|
5
6
|
## Entry points
|
|
6
7
|
|
|
7
8
|
| Import | What |
|
|
8
9
|
|---|---|
|
|
9
|
-
| `@petrarca/sonnet-core` |
|
|
10
|
+
| `@petrarca/sonnet-core` | `cn`, `isEqual`, `formatDateTime`, `normalizeError`, logging |
|
|
10
11
|
| `@petrarca/sonnet-core/schema` | JSON Schema types, resolution, validation, diffing |
|
|
11
|
-
| `@petrarca/sonnet-core/search` | Filter expression types
|
|
12
|
-
| `@petrarca/sonnet-core/hooks` | React hooks (
|
|
13
|
-
| `@petrarca/sonnet-core/entityOptions` | Entity option fetcher context
|
|
12
|
+
| `@petrarca/sonnet-core/search` | Filter expression types (`FilterExpr`, `buildListParams`) |
|
|
13
|
+
| `@petrarca/sonnet-core/hooks` | React hooks (see below) |
|
|
14
|
+
| `@petrarca/sonnet-core/entityOptions` | Entity option fetcher context for async select/autocomplete |
|
|
15
|
+
|
|
16
|
+
## Hooks
|
|
17
|
+
|
|
18
|
+
| Hook | Purpose |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `useClipboard` | Copy text to clipboard with copied-state timeout |
|
|
21
|
+
| `useDisclosure` | Boolean open/close/toggle state |
|
|
22
|
+
| `useFormState` | Generic form state with dirty tracking |
|
|
23
|
+
| `useEntityOptions` | Async entity fetching for select/autocomplete components |
|
|
24
|
+
| `useResizablePanel` | Drag-to-resize panel width |
|
|
25
|
+
| `useTreeExpansion` | Expand/collapse state for `TreeView` components |
|
|
14
26
|
|
|
15
27
|
## Install
|
|
16
28
|
|
|
@@ -20,4 +32,4 @@ pnpm add @petrarca/sonnet-core
|
|
|
20
32
|
|
|
21
33
|
## License
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
See [LICENSE.md](../../LICENSE.md).
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ interface UseClipboardReturn {
|
|
|
29
29
|
*/
|
|
30
30
|
declare function useClipboard({ timeout, }?: UseClipboardOptions): UseClipboardReturn;
|
|
31
31
|
|
|
32
|
+
interface UseTreeExpansionReturn {
|
|
33
|
+
expandedIds: Set<string>;
|
|
34
|
+
onExpand: (id: string) => void;
|
|
35
|
+
onCollapse: (id: string) => void;
|
|
36
|
+
}
|
|
37
|
+
/** Manages expand/collapse state for a TreeView. */
|
|
38
|
+
declare function useTreeExpansion(): UseTreeExpansionReturn;
|
|
39
|
+
|
|
32
40
|
interface UseDisclosureReturn {
|
|
33
41
|
open: () => void;
|
|
34
42
|
close: () => void;
|
|
@@ -226,4 +234,4 @@ interface QueryHookOptions {
|
|
|
226
234
|
enabled?: boolean;
|
|
227
235
|
}
|
|
228
236
|
|
|
229
|
-
export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, useResizablePanel };
|
|
237
|
+
export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, type UseTreeExpansionReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, useResizablePanel, useTreeExpansion };
|
package/dist/hooks/index.js
CHANGED
|
@@ -30,18 +30,35 @@ function useClipboard({
|
|
|
30
30
|
return { copy, copied, error, reset };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// src/hooks/
|
|
33
|
+
// src/hooks/useTreeExpansion.ts
|
|
34
34
|
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
35
|
+
function useTreeExpansion() {
|
|
36
|
+
const [expandedIds, setExpandedIds] = useState2(/* @__PURE__ */ new Set());
|
|
37
|
+
const onExpand = useCallback2((id) => {
|
|
38
|
+
setExpandedIds((prev) => /* @__PURE__ */ new Set([...prev, id]));
|
|
39
|
+
}, []);
|
|
40
|
+
const onCollapse = useCallback2((id) => {
|
|
41
|
+
setExpandedIds((prev) => {
|
|
42
|
+
const next = new Set(prev);
|
|
43
|
+
next.delete(id);
|
|
44
|
+
return next;
|
|
45
|
+
});
|
|
46
|
+
}, []);
|
|
47
|
+
return { expandedIds, onExpand, onCollapse };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/hooks/useDisclosure.ts
|
|
51
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
35
52
|
function useDisclosure(initialState = false) {
|
|
36
|
-
const [opened, setOpened] =
|
|
37
|
-
const open =
|
|
38
|
-
const close =
|
|
39
|
-
const toggle =
|
|
53
|
+
const [opened, setOpened] = useState3(initialState);
|
|
54
|
+
const open = useCallback3(() => setOpened(true), []);
|
|
55
|
+
const close = useCallback3(() => setOpened(false), []);
|
|
56
|
+
const toggle = useCallback3(() => setOpened((prev) => !prev), []);
|
|
40
57
|
return [opened, { open, close, toggle }];
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
// src/hooks/useResizablePanel.ts
|
|
44
|
-
import { useState as
|
|
61
|
+
import { useState as useState4, useRef, useCallback as useCallback4, useEffect } from "react";
|
|
45
62
|
function useResizablePanel(options = {}) {
|
|
46
63
|
const {
|
|
47
64
|
defaultWidth = 320,
|
|
@@ -49,20 +66,20 @@ function useResizablePanel(options = {}) {
|
|
|
49
66
|
maxWidth = 640,
|
|
50
67
|
direction = "left-edge"
|
|
51
68
|
} = options;
|
|
52
|
-
const [panelWidth, setPanelWidth] =
|
|
69
|
+
const [panelWidth, setPanelWidth] = useState4(defaultWidth);
|
|
53
70
|
const resizingRef = useRef(false);
|
|
54
71
|
const startXRef = useRef(0);
|
|
55
72
|
const startWidthRef = useRef(0);
|
|
56
|
-
const clamp =
|
|
73
|
+
const clamp = useCallback4(
|
|
57
74
|
(v) => Math.min(Math.max(v, minWidth), maxWidth),
|
|
58
75
|
[minWidth, maxWidth]
|
|
59
76
|
);
|
|
60
|
-
const finishResize =
|
|
77
|
+
const finishResize = useCallback4(() => {
|
|
61
78
|
if (!resizingRef.current) return;
|
|
62
79
|
resizingRef.current = false;
|
|
63
80
|
document.body.classList.remove("select-none");
|
|
64
81
|
}, []);
|
|
65
|
-
const handlePointerMove =
|
|
82
|
+
const handlePointerMove = useCallback4(
|
|
66
83
|
(e) => {
|
|
67
84
|
if (!resizingRef.current) return;
|
|
68
85
|
const delta = e.clientX - startXRef.current;
|
|
@@ -71,7 +88,7 @@ function useResizablePanel(options = {}) {
|
|
|
71
88
|
},
|
|
72
89
|
[clamp, direction]
|
|
73
90
|
);
|
|
74
|
-
const handlePointerDown =
|
|
91
|
+
const handlePointerDown = useCallback4(
|
|
75
92
|
(e) => {
|
|
76
93
|
resizingRef.current = true;
|
|
77
94
|
startXRef.current = e.clientX;
|
|
@@ -80,7 +97,7 @@ function useResizablePanel(options = {}) {
|
|
|
80
97
|
},
|
|
81
98
|
[panelWidth]
|
|
82
99
|
);
|
|
83
|
-
const handleDoubleClick =
|
|
100
|
+
const handleDoubleClick = useCallback4(
|
|
84
101
|
() => setPanelWidth(defaultWidth),
|
|
85
102
|
[defaultWidth]
|
|
86
103
|
);
|
|
@@ -117,27 +134,27 @@ function useResizablePanel(options = {}) {
|
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
// src/hooks/useFormState.ts
|
|
120
|
-
import { useState as
|
|
137
|
+
import { useState as useState5, useCallback as useCallback5, useMemo } from "react";
|
|
121
138
|
function useFormState(initialData) {
|
|
122
|
-
const [data, setDataState] =
|
|
123
|
-
const [original, setOriginal] =
|
|
139
|
+
const [data, setDataState] = useState5(initialData);
|
|
140
|
+
const [original, setOriginal] = useState5(initialData);
|
|
124
141
|
const isDirty = useMemo(() => {
|
|
125
142
|
return !isEqual(data, original);
|
|
126
143
|
}, [data, original]);
|
|
127
|
-
const setData =
|
|
144
|
+
const setData = useCallback5((newData) => {
|
|
128
145
|
setDataState(newData);
|
|
129
146
|
}, []);
|
|
130
|
-
const setField =
|
|
147
|
+
const setField = useCallback5((field, value) => {
|
|
131
148
|
setDataState((prev) => ({
|
|
132
149
|
...prev,
|
|
133
150
|
[field]: value
|
|
134
151
|
}));
|
|
135
152
|
}, []);
|
|
136
|
-
const reset =
|
|
153
|
+
const reset = useCallback5((newData) => {
|
|
137
154
|
setDataState(newData);
|
|
138
155
|
setOriginal(newData);
|
|
139
156
|
}, []);
|
|
140
|
-
const discard =
|
|
157
|
+
const discard = useCallback5(() => {
|
|
141
158
|
setDataState(original);
|
|
142
159
|
}, [original]);
|
|
143
160
|
return {
|
|
@@ -152,7 +169,7 @@ function useFormState(initialData) {
|
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
// src/hooks/useEntityOptions.ts
|
|
155
|
-
import { useState as
|
|
172
|
+
import { useState as useState6, useEffect as useEffect2, useCallback as useCallback6 } from "react";
|
|
156
173
|
import { useQuery } from "@tanstack/react-query";
|
|
157
174
|
function resolveEnabled(enabled, mode, activeSearch, minChars, committedSearch) {
|
|
158
175
|
if (!enabled) return false;
|
|
@@ -169,15 +186,15 @@ function useEntityOptions({
|
|
|
169
186
|
limit = 50,
|
|
170
187
|
enabled = true
|
|
171
188
|
}) {
|
|
172
|
-
const [search, setSearch] =
|
|
173
|
-
const [debouncedSearch, setDebouncedSearch] =
|
|
174
|
-
const [committedSearch, setCommittedSearch] =
|
|
189
|
+
const [search, setSearch] = useState6("");
|
|
190
|
+
const [debouncedSearch, setDebouncedSearch] = useState6("");
|
|
191
|
+
const [committedSearch, setCommittedSearch] = useState6(null);
|
|
175
192
|
useEffect2(() => {
|
|
176
193
|
if (mode === "explicit") return;
|
|
177
194
|
const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);
|
|
178
195
|
return () => clearTimeout(timer);
|
|
179
196
|
}, [search, debounceMs, mode]);
|
|
180
|
-
const submit =
|
|
197
|
+
const submit = useCallback6(() => {
|
|
181
198
|
setCommittedSearch(search);
|
|
182
199
|
}, [search]);
|
|
183
200
|
const activeSearch = mode === "explicit" ? committedSearch ?? "" : debouncedSearch;
|
|
@@ -202,6 +219,7 @@ export {
|
|
|
202
219
|
useDisclosure,
|
|
203
220
|
useEntityOptions,
|
|
204
221
|
useFormState,
|
|
205
|
-
useResizablePanel
|
|
222
|
+
useResizablePanel,
|
|
223
|
+
useTreeExpansion
|
|
206
224
|
};
|
|
207
225
|
//# sourceMappingURL=index.js.map
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/useClipboard.ts","../../src/hooks/useDisclosure.ts","../../src/hooks/useResizablePanel.ts","../../src/hooks/useFormState.ts","../../src/hooks/useEntityOptions.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\n\nexport interface UseClipboardOptions {\n timeout?: number;\n}\n\nexport interface UseClipboardReturn {\n copy: (text: string) => Promise<void>;\n copied: boolean;\n error: Error | null;\n reset: () => void;\n}\n\n/**\n * Custom hook for clipboard operations\n *\n * Replaces Mantine's useClipboard hook with native Clipboard API\n *\n * @param options - Configuration options\n * @param options.timeout - Time in ms before copied state resets (default: 2000)\n * @returns Object with copy function, copied state, error, and reset function\n *\n * @example\n * ```tsx\n * const { copy, copied } = useClipboard({ timeout: 3000 });\n *\n * <button onClick={() => copy('Hello World')}>\n * {copied ? 'Copied!' : 'Copy'}\n * </button>\n * ```\n */\nexport function useClipboard({\n timeout = 2000,\n}: UseClipboardOptions = {}): UseClipboardReturn {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const copy = useCallback(\n async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setError(null);\n\n // Reset copied state after timeout\n setTimeout(() => setCopied(false), timeout);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setCopied(false);\n }\n },\n [timeout],\n );\n\n const reset = useCallback(() => {\n setCopied(false);\n setError(null);\n }, []);\n\n return { copy, copied, error, reset };\n}\n","import { useState, useCallback } from \"react\";\n\nexport interface UseDisclosureReturn {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Custom hook for managing open/close state\n *\n * Replaces Mantine's useDisclosure hook with simple useState wrapper\n *\n * @param initialState - Initial open state (default: false)\n * @returns Tuple of [opened, handlers] where handlers contains open, close, toggle functions\n *\n * @example\n * ```tsx\n * const [opened, { open, close, toggle }] = useDisclosure();\n *\n * <button onClick={open}>Open Modal</button>\n * <Modal opened={opened} onClose={close}>\n * <button onClick={toggle}>Toggle</button>\n * </Modal>\n * ```\n */\nexport function useDisclosure(\n initialState = false,\n): readonly [boolean, UseDisclosureReturn] {\n const [opened, setOpened] = useState(initialState);\n\n const open = useCallback(() => setOpened(true), []);\n const close = useCallback(() => setOpened(false), []);\n const toggle = useCallback(() => setOpened((prev) => !prev), []);\n\n return [opened, { open, close, toggle }] as const;\n}\n","/**\n * Custom hook for resizable panel functionality.\n * Extracted from GraphViewer to be reusable across components.\n *\n * @module hooks/useResizablePanel\n */\n\nimport { useState, useRef, useCallback, useEffect } from \"react\";\n\ninterface UseResizablePanelOptions {\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /**\n * Which edge the drag handle sits on relative to the panel content.\n *\n * - \"left-edge\" — handle is on the panel's left; dragging right shrinks the\n * panel (default, matches the right-side action panel in\n * GraphViewer where the separator is left of the panel).\n * - \"right-edge\" — handle is on the panel's right; dragging right grows the\n * panel (matches a left-side SidePane where the separator is\n * right of the panel).\n */\n direction?: \"left-edge\" | \"right-edge\";\n}\n\ninterface UseResizablePanelReturn {\n panelWidth: number;\n handlePointerDown: (e: React.PointerEvent) => void;\n handleDoubleClick: () => void;\n separatorProps: {\n role: string;\n \"aria-orientation\": \"vertical\";\n \"aria-valuenow\": number;\n \"aria-valuemin\": number;\n \"aria-valuemax\": number;\n };\n}\n\n/**\n * Hook for managing resizable panel state and interactions.\n * Handles pointer events, constraints, and keyboard shortcuts (Escape to cancel).\n */\nexport function useResizablePanel(\n options: UseResizablePanelOptions = {},\n): UseResizablePanelReturn {\n const {\n defaultWidth = 320,\n minWidth = 260,\n maxWidth = 640,\n direction = \"left-edge\",\n } = options;\n\n const [panelWidth, setPanelWidth] = useState<number>(defaultWidth);\n const resizingRef = useRef(false);\n const startXRef = useRef(0);\n const startWidthRef = useRef(0);\n\n const clamp = useCallback(\n (v: number) => Math.min(Math.max(v, minWidth), maxWidth),\n [minWidth, maxWidth],\n );\n\n const finishResize = useCallback(() => {\n if (!resizingRef.current) return;\n resizingRef.current = false;\n document.body.classList.remove(\"select-none\");\n }, []);\n\n const handlePointerMove = useCallback(\n (e: PointerEvent) => {\n if (!resizingRef.current) return;\n const delta = e.clientX - startXRef.current;\n // left-edge: separator left of panel, drag right shrinks (subtract delta)\n // right-edge: separator right of panel, drag right grows (add delta)\n const newWidth =\n direction === \"right-edge\"\n ? startWidthRef.current + delta\n : startWidthRef.current - delta;\n setPanelWidth(clamp(newWidth));\n },\n [clamp, direction],\n );\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n resizingRef.current = true;\n startXRef.current = e.clientX;\n startWidthRef.current = panelWidth;\n document.body.classList.add(\"select-none\");\n },\n [panelWidth],\n );\n\n const handleDoubleClick = useCallback(\n () => setPanelWidth(defaultWidth),\n [defaultWidth],\n );\n\n useEffect(() => {\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", finishResize);\n window.addEventListener(\"pointercancel\", finishResize);\n\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && resizingRef.current) {\n setPanelWidth(startWidthRef.current);\n finishResize();\n }\n };\n window.addEventListener(\"keydown\", handleKey);\n\n return () => {\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", finishResize);\n window.removeEventListener(\"pointercancel\", finishResize);\n window.removeEventListener(\"keydown\", handleKey);\n };\n }, [handlePointerMove, finishResize]);\n\n return {\n panelWidth,\n handlePointerDown,\n handleDoubleClick,\n separatorProps: {\n role: \"separator\",\n \"aria-orientation\": \"vertical\",\n \"aria-valuenow\": panelWidth,\n \"aria-valuemin\": minWidth,\n \"aria-valuemax\": maxWidth,\n },\n };\n}\n","/**\n * useFormState Hook\n *\n * Manages form state with automatic dirty tracking and common operations.\n * Tracks changes against original data to determine if form has been modified.\n *\n * @module hooks/useFormState\n */\n\nimport { useState, useCallback, useMemo } from \"react\";\nimport { isEqual } from \"../utils\";\n\n/**\n * Return type for useFormState hook\n */\nexport interface UseFormStateReturn<T> {\n /** Current form data */\n data: T;\n /** Update form data (partial or full) */\n setData: (data: T | ((prev: T) => T)) => void;\n /** Update a single field */\n setField: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Whether form has been modified */\n isDirty: boolean;\n /** Load new data and mark as clean (for switching items) */\n reset: (newData: T) => void;\n /** Revert to original data */\n discard: () => void;\n /** Original data for comparison */\n original: T;\n}\n\n/**\n * Hook for managing form state with dirty tracking\n *\n * Provides state management for forms with automatic change detection.\n * Compares current data against original to determine if form has been modified.\n *\n * @template T - Type of the form data\n * @param initialData - Initial form data\n * @returns Form state and control functions\n *\n * @example\n * ```tsx\n * interface FormData {\n * name: string;\n * email: string;\n * }\n *\n * function MyForm() {\n * const form = useFormState<FormData>({\n * name: '',\n * email: '',\n * });\n *\n * // Update field\n * form.setField('name', 'John');\n *\n * // Check if dirty\n * if (form.isDirty) {\n * console.log('Form has unsaved changes');\n * }\n *\n * // Load new data (e.g., from API)\n * form.reset({ name: 'Jane', email: 'jane@example.com' });\n *\n * // Revert changes\n * form.discard();\n * }\n * ```\n */\nexport function useFormState<T>(initialData: T): UseFormStateReturn<T> {\n const [data, setDataState] = useState<T>(initialData);\n const [original, setOriginal] = useState<T>(initialData);\n\n /**\n * Compute dirty state by comparing current data with original\n */\n const isDirty = useMemo(() => {\n return !isEqual(data, original);\n }, [data, original]);\n\n /**\n * Update form data\n */\n const setData = useCallback((newData: T | ((prev: T) => T)) => {\n setDataState(newData);\n }, []);\n\n /**\n * Update a single field\n */\n const setField = useCallback(<K extends keyof T>(field: K, value: T[K]) => {\n setDataState((prev) => ({\n ...prev,\n [field]: value,\n }));\n }, []);\n\n /**\n * Load new data and mark as clean\n * Use when loading data from API or switching between items\n */\n const reset = useCallback((newData: T) => {\n setDataState(newData);\n setOriginal(newData);\n }, []);\n\n /**\n * Revert to original data\n * Use when user clicks \"Cancel\" or \"Discard Changes\"\n */\n const discard = useCallback(() => {\n setDataState(original);\n }, [original]);\n\n return {\n data,\n setData,\n setField,\n isDirty,\n reset,\n discard,\n original,\n };\n}\n","/**\n * useEntityOptions -- fetch selectable options from any data source.\n *\n * Transport-agnostic: receives an EntityOptionFetcher and returns\n * TanStack Query state. Supports three search modes:\n *\n * eager -- fetches on mount, refetches on debounced search (default)\n * typeahead -- fetches only after the user types >= minChars (debounced)\n * explicit -- fetches only when submit() is called\n *\n * Extractable to @petrarca/sonnet-core/hooks -- no app-specific imports.\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { EntityOption, EntityOptionFetcher } from \"../entityOptions\";\n\n/** Search mode for the entity options hook and EntitySelect component. */\nexport type EntitySelectMode = \"eager\" | \"typeahead\" | \"explicit\";\n\nexport interface UseEntityOptionsConfig {\n /** The fetcher function (created by a fetcher factory or provided directly). */\n fetcher: EntityOptionFetcher;\n /** TanStack Query cache key prefix. Must be stable. */\n queryKey: string;\n /**\n * Search mode. Default: \"eager\".\n * eager -- fetch on mount, refetch on debounced typing\n * typeahead -- fetch only after minChars are typed (debounced)\n * explicit -- fetch only when submit() is called\n */\n mode?: EntitySelectMode;\n /** Debounce delay in ms for the search input (default 300). */\n debounceMs?: number;\n /** Minimum characters before typeahead fires (default 1). Ignored in other modes. */\n minChars?: number;\n /** Maximum options to fetch (default 50). */\n limit?: number;\n /** Disable fetching (e.g. when a dependency is not yet satisfied). */\n enabled?: boolean;\n}\n\nexport interface UseEntityOptionsReturn {\n /** Current options from the server. */\n options: EntityOption[];\n /** Whether a fetch is in progress. */\n isLoading: boolean;\n /** Whether the fetch errored. */\n isError: boolean;\n /** The current search string (set by the combobox input). */\n search: string;\n /** Update the search string (drives debounced refetch). */\n setSearch: (value: string) => void;\n /** Trigger a fetch with the current search string. Only meaningful in explicit mode. */\n submit: () => void;\n}\n\n// Determine whether the query should be enabled based on mode and state.\nfunction resolveEnabled(\n enabled: boolean,\n mode: EntitySelectMode,\n activeSearch: string,\n minChars: number,\n committedSearch: string | null,\n): boolean {\n if (!enabled) return false;\n if (mode === \"typeahead\") return activeSearch.length >= minChars;\n if (mode === \"explicit\") return committedSearch !== null;\n return true;\n}\n\nexport function useEntityOptions({\n fetcher,\n queryKey,\n mode = \"eager\",\n debounceMs = 300,\n minChars = 1,\n limit = 50,\n enabled = true,\n}: UseEntityOptionsConfig): UseEntityOptionsReturn {\n const [search, setSearch] = useState(\"\");\n\n // Debounced search drives eager and typeahead modes.\n const [debouncedSearch, setDebouncedSearch] = useState(\"\");\n // Committed search drives explicit mode -- only updated on submit().\n const [committedSearch, setCommittedSearch] = useState<string | null>(null);\n\n useEffect(() => {\n if (mode === \"explicit\") return;\n const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);\n return () => clearTimeout(timer);\n }, [search, debounceMs, mode]);\n\n const submit = useCallback(() => {\n setCommittedSearch(search);\n }, [search]);\n\n const activeSearch =\n mode === \"explicit\" ? (committedSearch ?? \"\") : debouncedSearch;\n\n const queryEnabled = resolveEnabled(\n enabled,\n mode,\n activeSearch,\n minChars,\n committedSearch,\n );\n\n const { data, isLoading, isError } = useQuery({\n queryKey: [queryKey, \"options\", { search: activeSearch, limit }],\n queryFn: () => fetcher({ search: activeSearch || undefined, limit }),\n enabled: queryEnabled,\n staleTime: 5 * 60 * 1000,\n });\n\n const options: EntityOption[] = data ?? [];\n\n return { options, isLoading, isError, search, setSearch, submit };\n}\n"],"mappings":"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","names":["useState","useCallback","useState","useCallback","useState","useCallback","useState","useCallback","useState","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useClipboard.ts","../../src/hooks/useTreeExpansion.ts","../../src/hooks/useDisclosure.ts","../../src/hooks/useResizablePanel.ts","../../src/hooks/useFormState.ts","../../src/hooks/useEntityOptions.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\n\nexport interface UseClipboardOptions {\n timeout?: number;\n}\n\nexport interface UseClipboardReturn {\n copy: (text: string) => Promise<void>;\n copied: boolean;\n error: Error | null;\n reset: () => void;\n}\n\n/**\n * Custom hook for clipboard operations\n *\n * Replaces Mantine's useClipboard hook with native Clipboard API\n *\n * @param options - Configuration options\n * @param options.timeout - Time in ms before copied state resets (default: 2000)\n * @returns Object with copy function, copied state, error, and reset function\n *\n * @example\n * ```tsx\n * const { copy, copied } = useClipboard({ timeout: 3000 });\n *\n * <button onClick={() => copy('Hello World')}>\n * {copied ? 'Copied!' : 'Copy'}\n * </button>\n * ```\n */\nexport function useClipboard({\n timeout = 2000,\n}: UseClipboardOptions = {}): UseClipboardReturn {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const copy = useCallback(\n async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setError(null);\n\n // Reset copied state after timeout\n setTimeout(() => setCopied(false), timeout);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setCopied(false);\n }\n },\n [timeout],\n );\n\n const reset = useCallback(() => {\n setCopied(false);\n setError(null);\n }, []);\n\n return { copy, copied, error, reset };\n}\n","import { useState, useCallback } from \"react\";\n\nexport interface UseTreeExpansionReturn {\n expandedIds: Set<string>;\n onExpand: (id: string) => void;\n onCollapse: (id: string) => void;\n}\n\n/** Manages expand/collapse state for a TreeView. */\nexport function useTreeExpansion(): UseTreeExpansionReturn {\n const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());\n\n const onExpand = useCallback((id: string) => {\n setExpandedIds((prev) => new Set([...prev, id]));\n }, []);\n\n const onCollapse = useCallback((id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n return { expandedIds, onExpand, onCollapse };\n}\n","import { useState, useCallback } from \"react\";\n\nexport interface UseDisclosureReturn {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Custom hook for managing open/close state\n *\n * Replaces Mantine's useDisclosure hook with simple useState wrapper\n *\n * @param initialState - Initial open state (default: false)\n * @returns Tuple of [opened, handlers] where handlers contains open, close, toggle functions\n *\n * @example\n * ```tsx\n * const [opened, { open, close, toggle }] = useDisclosure();\n *\n * <button onClick={open}>Open Modal</button>\n * <Modal opened={opened} onClose={close}>\n * <button onClick={toggle}>Toggle</button>\n * </Modal>\n * ```\n */\nexport function useDisclosure(\n initialState = false,\n): readonly [boolean, UseDisclosureReturn] {\n const [opened, setOpened] = useState(initialState);\n\n const open = useCallback(() => setOpened(true), []);\n const close = useCallback(() => setOpened(false), []);\n const toggle = useCallback(() => setOpened((prev) => !prev), []);\n\n return [opened, { open, close, toggle }] as const;\n}\n","/**\n * Custom hook for resizable panel functionality.\n * Extracted from GraphViewer to be reusable across components.\n *\n * @module hooks/useResizablePanel\n */\n\nimport { useState, useRef, useCallback, useEffect } from \"react\";\n\ninterface UseResizablePanelOptions {\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /**\n * Which edge the drag handle sits on relative to the panel content.\n *\n * - \"left-edge\" — handle is on the panel's left; dragging right shrinks the\n * panel (default, matches the right-side action panel in\n * GraphViewer where the separator is left of the panel).\n * - \"right-edge\" — handle is on the panel's right; dragging right grows the\n * panel (matches a left-side SidePane where the separator is\n * right of the panel).\n */\n direction?: \"left-edge\" | \"right-edge\";\n}\n\ninterface UseResizablePanelReturn {\n panelWidth: number;\n handlePointerDown: (e: React.PointerEvent) => void;\n handleDoubleClick: () => void;\n separatorProps: {\n role: string;\n \"aria-orientation\": \"vertical\";\n \"aria-valuenow\": number;\n \"aria-valuemin\": number;\n \"aria-valuemax\": number;\n };\n}\n\n/**\n * Hook for managing resizable panel state and interactions.\n * Handles pointer events, constraints, and keyboard shortcuts (Escape to cancel).\n */\nexport function useResizablePanel(\n options: UseResizablePanelOptions = {},\n): UseResizablePanelReturn {\n const {\n defaultWidth = 320,\n minWidth = 260,\n maxWidth = 640,\n direction = \"left-edge\",\n } = options;\n\n const [panelWidth, setPanelWidth] = useState<number>(defaultWidth);\n const resizingRef = useRef(false);\n const startXRef = useRef(0);\n const startWidthRef = useRef(0);\n\n const clamp = useCallback(\n (v: number) => Math.min(Math.max(v, minWidth), maxWidth),\n [minWidth, maxWidth],\n );\n\n const finishResize = useCallback(() => {\n if (!resizingRef.current) return;\n resizingRef.current = false;\n document.body.classList.remove(\"select-none\");\n }, []);\n\n const handlePointerMove = useCallback(\n (e: PointerEvent) => {\n if (!resizingRef.current) return;\n const delta = e.clientX - startXRef.current;\n // left-edge: separator left of panel, drag right shrinks (subtract delta)\n // right-edge: separator right of panel, drag right grows (add delta)\n const newWidth =\n direction === \"right-edge\"\n ? startWidthRef.current + delta\n : startWidthRef.current - delta;\n setPanelWidth(clamp(newWidth));\n },\n [clamp, direction],\n );\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n resizingRef.current = true;\n startXRef.current = e.clientX;\n startWidthRef.current = panelWidth;\n document.body.classList.add(\"select-none\");\n },\n [panelWidth],\n );\n\n const handleDoubleClick = useCallback(\n () => setPanelWidth(defaultWidth),\n [defaultWidth],\n );\n\n useEffect(() => {\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", finishResize);\n window.addEventListener(\"pointercancel\", finishResize);\n\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && resizingRef.current) {\n setPanelWidth(startWidthRef.current);\n finishResize();\n }\n };\n window.addEventListener(\"keydown\", handleKey);\n\n return () => {\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", finishResize);\n window.removeEventListener(\"pointercancel\", finishResize);\n window.removeEventListener(\"keydown\", handleKey);\n };\n }, [handlePointerMove, finishResize]);\n\n return {\n panelWidth,\n handlePointerDown,\n handleDoubleClick,\n separatorProps: {\n role: \"separator\",\n \"aria-orientation\": \"vertical\",\n \"aria-valuenow\": panelWidth,\n \"aria-valuemin\": minWidth,\n \"aria-valuemax\": maxWidth,\n },\n };\n}\n","/**\n * useFormState Hook\n *\n * Manages form state with automatic dirty tracking and common operations.\n * Tracks changes against original data to determine if form has been modified.\n *\n * @module hooks/useFormState\n */\n\nimport { useState, useCallback, useMemo } from \"react\";\nimport { isEqual } from \"../utils\";\n\n/**\n * Return type for useFormState hook\n */\nexport interface UseFormStateReturn<T> {\n /** Current form data */\n data: T;\n /** Update form data (partial or full) */\n setData: (data: T | ((prev: T) => T)) => void;\n /** Update a single field */\n setField: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Whether form has been modified */\n isDirty: boolean;\n /** Load new data and mark as clean (for switching items) */\n reset: (newData: T) => void;\n /** Revert to original data */\n discard: () => void;\n /** Original data for comparison */\n original: T;\n}\n\n/**\n * Hook for managing form state with dirty tracking\n *\n * Provides state management for forms with automatic change detection.\n * Compares current data against original to determine if form has been modified.\n *\n * @template T - Type of the form data\n * @param initialData - Initial form data\n * @returns Form state and control functions\n *\n * @example\n * ```tsx\n * interface FormData {\n * name: string;\n * email: string;\n * }\n *\n * function MyForm() {\n * const form = useFormState<FormData>({\n * name: '',\n * email: '',\n * });\n *\n * // Update field\n * form.setField('name', 'John');\n *\n * // Check if dirty\n * if (form.isDirty) {\n * console.log('Form has unsaved changes');\n * }\n *\n * // Load new data (e.g., from API)\n * form.reset({ name: 'Jane', email: 'jane@example.com' });\n *\n * // Revert changes\n * form.discard();\n * }\n * ```\n */\nexport function useFormState<T>(initialData: T): UseFormStateReturn<T> {\n const [data, setDataState] = useState<T>(initialData);\n const [original, setOriginal] = useState<T>(initialData);\n\n /**\n * Compute dirty state by comparing current data with original\n */\n const isDirty = useMemo(() => {\n return !isEqual(data, original);\n }, [data, original]);\n\n /**\n * Update form data\n */\n const setData = useCallback((newData: T | ((prev: T) => T)) => {\n setDataState(newData);\n }, []);\n\n /**\n * Update a single field\n */\n const setField = useCallback(<K extends keyof T>(field: K, value: T[K]) => {\n setDataState((prev) => ({\n ...prev,\n [field]: value,\n }));\n }, []);\n\n /**\n * Load new data and mark as clean\n * Use when loading data from API or switching between items\n */\n const reset = useCallback((newData: T) => {\n setDataState(newData);\n setOriginal(newData);\n }, []);\n\n /**\n * Revert to original data\n * Use when user clicks \"Cancel\" or \"Discard Changes\"\n */\n const discard = useCallback(() => {\n setDataState(original);\n }, [original]);\n\n return {\n data,\n setData,\n setField,\n isDirty,\n reset,\n discard,\n original,\n };\n}\n","/**\n * useEntityOptions -- fetch selectable options from any data source.\n *\n * Transport-agnostic: receives an EntityOptionFetcher and returns\n * TanStack Query state. Supports three search modes:\n *\n * eager -- fetches on mount, refetches on debounced search (default)\n * typeahead -- fetches only after the user types >= minChars (debounced)\n * explicit -- fetches only when submit() is called\n *\n * Extractable to @petrarca/sonnet-core/hooks -- no app-specific imports.\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { EntityOption, EntityOptionFetcher } from \"../entityOptions\";\n\n/** Search mode for the entity options hook and EntitySelect component. */\nexport type EntitySelectMode = \"eager\" | \"typeahead\" | \"explicit\";\n\nexport interface UseEntityOptionsConfig {\n /** The fetcher function (created by a fetcher factory or provided directly). */\n fetcher: EntityOptionFetcher;\n /** TanStack Query cache key prefix. Must be stable. */\n queryKey: string;\n /**\n * Search mode. Default: \"eager\".\n * eager -- fetch on mount, refetch on debounced typing\n * typeahead -- fetch only after minChars are typed (debounced)\n * explicit -- fetch only when submit() is called\n */\n mode?: EntitySelectMode;\n /** Debounce delay in ms for the search input (default 300). */\n debounceMs?: number;\n /** Minimum characters before typeahead fires (default 1). Ignored in other modes. */\n minChars?: number;\n /** Maximum options to fetch (default 50). */\n limit?: number;\n /** Disable fetching (e.g. when a dependency is not yet satisfied). */\n enabled?: boolean;\n}\n\nexport interface UseEntityOptionsReturn {\n /** Current options from the server. */\n options: EntityOption[];\n /** Whether a fetch is in progress. */\n isLoading: boolean;\n /** Whether the fetch errored. */\n isError: boolean;\n /** The current search string (set by the combobox input). */\n search: string;\n /** Update the search string (drives debounced refetch). */\n setSearch: (value: string) => void;\n /** Trigger a fetch with the current search string. Only meaningful in explicit mode. */\n submit: () => void;\n}\n\n// Determine whether the query should be enabled based on mode and state.\nfunction resolveEnabled(\n enabled: boolean,\n mode: EntitySelectMode,\n activeSearch: string,\n minChars: number,\n committedSearch: string | null,\n): boolean {\n if (!enabled) return false;\n if (mode === \"typeahead\") return activeSearch.length >= minChars;\n if (mode === \"explicit\") return committedSearch !== null;\n return true;\n}\n\nexport function useEntityOptions({\n fetcher,\n queryKey,\n mode = \"eager\",\n debounceMs = 300,\n minChars = 1,\n limit = 50,\n enabled = true,\n}: UseEntityOptionsConfig): UseEntityOptionsReturn {\n const [search, setSearch] = useState(\"\");\n\n // Debounced search drives eager and typeahead modes.\n const [debouncedSearch, setDebouncedSearch] = useState(\"\");\n // Committed search drives explicit mode -- only updated on submit().\n const [committedSearch, setCommittedSearch] = useState<string | null>(null);\n\n useEffect(() => {\n if (mode === \"explicit\") return;\n const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);\n return () => clearTimeout(timer);\n }, [search, debounceMs, mode]);\n\n const submit = useCallback(() => {\n setCommittedSearch(search);\n }, [search]);\n\n const activeSearch =\n mode === \"explicit\" ? (committedSearch ?? \"\") : debouncedSearch;\n\n const queryEnabled = resolveEnabled(\n enabled,\n mode,\n activeSearch,\n minChars,\n committedSearch,\n );\n\n const { data, isLoading, isError } = useQuery({\n queryKey: [queryKey, \"options\", { search: activeSearch, limit }],\n queryFn: () => fetcher({ search: activeSearch || undefined, limit }),\n enabled: queryEnabled,\n staleTime: 5 * 60 * 1000,\n });\n\n const options: EntityOption[] = data ?? [];\n\n return { options, isLoading, isError, search, setSearch, submit };\n}\n"],"mappings":"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","names":["useState","useCallback","useState","useCallback","useState","useCallback","useState","useCallback","useState","useCallback","useState","useEffect","useCallback"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -136,5 +136,14 @@ interface PageResult<T> {
|
|
|
136
136
|
data: T[];
|
|
137
137
|
meta: PageMeta;
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Wrap a full array as a single-page PageResult.
|
|
141
|
+
*
|
|
142
|
+
* Bridges "regular" (un-paginated) list APIs that return a bare array into the
|
|
143
|
+
* PageResult envelope that paginated consumers (e.g. EntityTable) expect. The
|
|
144
|
+
* whole array is treated as one complete page: total = length, offset = 0, and
|
|
145
|
+
* no next/prev page. Use for small lists served without server-side pagination.
|
|
146
|
+
*/
|
|
147
|
+
declare function toPageResult<T>(rows: T[]): PageResult<T>;
|
|
139
148
|
|
|
140
|
-
export { FORM_ITEM_ID_FIELD, JSON_CSS_VARIABLES, type NormalizedError, type OffsetRange, type PageMeta, type PageResult, cn, copyTextToClipboard, devLog, errorLog, escapeHtml, formatDate, formatDateTime, formatDateTimeTz, formatJsonValue, formatNumber, formatNumberCompact, generateId, infoLog, isEqual, normalizeError, pluralize, resolvePointerRanges, snakeCaseToTitleCase, useComposedRefs, warnLog };
|
|
149
|
+
export { FORM_ITEM_ID_FIELD, JSON_CSS_VARIABLES, type NormalizedError, type OffsetRange, type PageMeta, type PageResult, cn, copyTextToClipboard, devLog, errorLog, escapeHtml, formatDate, formatDateTime, formatDateTimeTz, formatJsonValue, formatNumber, formatNumberCompact, generateId, infoLog, isEqual, normalizeError, pluralize, resolvePointerRanges, snakeCaseToTitleCase, toPageResult, useComposedRefs, warnLog };
|
package/dist/index.js
CHANGED
|
@@ -377,6 +377,20 @@ function resolvePointerRanges(json, pointers) {
|
|
|
377
377
|
ranges.sort((a, b) => a.from - b.from);
|
|
378
378
|
return ranges;
|
|
379
379
|
}
|
|
380
|
+
|
|
381
|
+
// src/pagination.ts
|
|
382
|
+
function toPageResult(rows) {
|
|
383
|
+
return {
|
|
384
|
+
data: rows,
|
|
385
|
+
meta: {
|
|
386
|
+
total: rows.length,
|
|
387
|
+
limit: rows.length,
|
|
388
|
+
offset: 0,
|
|
389
|
+
has_next: false,
|
|
390
|
+
has_prev: false
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
380
394
|
export {
|
|
381
395
|
FORM_ITEM_ID_FIELD,
|
|
382
396
|
JSON_CSS_VARIABLES,
|
|
@@ -398,6 +412,7 @@ export {
|
|
|
398
412
|
pluralize,
|
|
399
413
|
resolvePointerRanges,
|
|
400
414
|
snakeCaseToTitleCase_default as snakeCaseToTitleCase,
|
|
415
|
+
toPageResult,
|
|
401
416
|
useComposedRefs,
|
|
402
417
|
warnLog
|
|
403
418
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format.ts","../src/jsonUtils.ts","../src/utils/copyTextToClipboard.tsx","../src/utils/snakeCaseToTitleCase.ts","../src/errorNormalizer.ts","../src/compose-refs.ts","../src/resolvePointerRanges.ts"],"sourcesContent":["// Reusable formatting utilities.\n// Thin wrappers around Intl for consistent output across the app.\n\nconst numberFormatter = new Intl.NumberFormat(undefined, {\n maximumFractionDigits: 0,\n});\n\nexport function formatNumber(\n value: number | string | undefined | null,\n): string {\n if (value === null || value === undefined) return \"0\";\n const num = typeof value === \"string\" ? Number(value) : value;\n if (!Number.isFinite(num)) return String(value);\n return numberFormatter.format(num as number);\n}\n\nexport function formatNumberCompact(\n value: number | string | undefined | null,\n): string {\n if (value === null || value === undefined) return \"0\";\n const num = typeof value === \"string\" ? Number(value) : value;\n if (!Number.isFinite(num)) return String(value);\n return new Intl.NumberFormat(undefined, {\n notation: \"compact\",\n maximumFractionDigits: 1,\n }).format(num as number);\n}\n\n/** Format an ISO 8601 string as a short date (e.g. \"Jan 15, 2026\"). */\nexport function formatDate(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n return new Date(iso).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\n/** Format an ISO 8601 string as date + time (e.g. \"Jan 15, 2026, 09:32 AM\"). */\nexport function formatDateTime(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n return new Date(iso).toLocaleString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/**\n * Format a timezone-aware ISO 8601 string preserving the original offset.\n *\n * Parses the date/time components and offset directly from the string so\n * the emitter's local time and offset are shown exactly as recorded --\n * independent of the viewer's timezone.\n *\n * Examples:\n * \"2026-02-22T10:00:00+01:00\" -> \"22 Feb 2026 at 10:00 +01:00\"\n * \"2026-02-22T09:00:00Z\" -> \"22 Feb 2026 at 09:00 UTC\"\n */\nexport function formatDateTimeTz(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n\n // Match ISO 8601: date, time (HH:MM), optional seconds, offset or Z.\n const match = iso.match(\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::\\d{2}(?:\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$/,\n );\n if (!match) return iso;\n\n const [, year, month, day, hour, minute, rawOffset] = match;\n\n const months = [\n \"Jan\",\n \"Feb\",\n \"Mar\",\n \"Apr\",\n \"May\",\n \"Jun\",\n \"Jul\",\n \"Aug\",\n \"Sep\",\n \"Oct\",\n \"Nov\",\n \"Dec\",\n ];\n const monthName = months[parseInt(month, 10) - 1];\n const offset = rawOffset === \"Z\" ? \"UTC\" : rawOffset;\n\n return `${parseInt(day, 10)} ${monthName} ${year} at ${hour}:${minute} ${offset}`;\n}\n\n/** Return \"{count} {word}\" with the word pluralized when count != 1. */\nexport function pluralize(count: number, word: string): string {\n return `${count} ${word}${count !== 1 ? \"s\" : \"\"}`;\n}\n","/**\n * Escapes HTML characters to prevent XSS and rendering issues\n */\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Formats a JSON value into colored HTML with proper syntax highlighting\n * @param value - The value to format (any JSON-serializable type)\n * @param indent - Current indentation level for nested objects/arrays\n * @returns HTML string with syntax highlighting\n */\nfunction jsonSpan(cssVar: string, content: string): string {\n return `<span style=\"color: var(${cssVar})\">${content}</span>`;\n}\n\nfunction formatPrimitive(value: unknown): string | null {\n if (value === null) return jsonSpan(\"--json-null\", \"null\");\n if (typeof value === \"undefined\") return jsonSpan(\"--json-null\", \"undefined\");\n if (typeof value === \"boolean\")\n return jsonSpan(\"--json-boolean\", String(value));\n if (typeof value === \"number\")\n return jsonSpan(\"--json-number\", String(value));\n if (typeof value === \"string\")\n return jsonSpan(\"--json-string\", `\"${escapeHtml(value)}\"`);\n return null;\n}\n\nfunction formatArray(value: unknown[], indent: number): string {\n if (value.length === 0) return jsonSpan(\"--json-punctuation\", \"[]\");\n const indentStr = \" \".repeat(indent);\n const items = value\n .map((item) => `${indentStr} ${formatJsonValue(item, indent + 1)}`)\n .join(\",\\n\");\n return `${jsonSpan(\"--json-punctuation\", \"[\")}\\n${items}\\n${indentStr}${jsonSpan(\"--json-punctuation\", \"]\")}`;\n}\n\nfunction formatObject(value: object, indent: number): string {\n const entries = Object.entries(value);\n if (entries.length === 0) return jsonSpan(\"--json-punctuation\", \"{}\");\n const indentStr = \" \".repeat(indent);\n const items = entries\n .map(\n ([key, val]) =>\n `${indentStr} ${jsonSpan(\"--json-key\", `\"${escapeHtml(key)}\"`)}${jsonSpan(\"--json-punctuation\", \":\")} ${formatJsonValue(val, indent + 1)}`,\n )\n .join(\",\\n\");\n return `${jsonSpan(\"--json-punctuation\", \"{\")}\\n${items}\\n${indentStr}${jsonSpan(\"--json-punctuation\", \"}\")}`;\n}\n\nexport function formatJsonValue(value: unknown, indent = 0): string {\n const primitive = formatPrimitive(value);\n if (primitive !== null) return primitive;\n if (Array.isArray(value)) return formatArray(value, indent);\n if (typeof value === \"object\" && value !== null)\n return formatObject(value, indent);\n return jsonSpan(\"--json-fallback\", escapeHtml(String(value)));\n}\n\n/**\n * CSS custom properties for JSON syntax highlighting colors\n */\nexport const JSON_CSS_VARIABLES = {\n \"--json-key\": \"#2563eb\",\n \"--json-string\": \"#16a34a\",\n \"--json-number\": \"#ea580c\",\n \"--json-boolean\": \"#2563eb\",\n \"--json-null\": \"#64748b\",\n \"--json-punctuation\": \"#475569\",\n \"--json-fallback\": \"#6b7280\",\n} as const;\n","/**\n * Copy text to the system clipboard.\n *\n * Optionally calls `onSuccess` after a successful write. The caller\n * provides its own notification mechanism (e.g. `notification.success`\n * from the shell API) to keep this utility free of sonner or any other\n * toast dependency.\n */\nexport async function copyTextToClipboard(\n text: string,\n options?: { onSuccess?: (message: string) => void },\n): Promise<void> {\n await navigator.clipboard.writeText(text);\n options?.onSuccess?.(`${text} copied to clipboard`);\n}\n","function snakeCaseToTitleCase(snakeCase: string): string {\n const words = snakeCase.split(\"_\");\n const titleCaseWords = words.map(\n (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n );\n return titleCaseWords.join(\" \");\n}\n\nexport default snakeCaseToTitleCase;\n","/**\n * Error normalizer -- extract typed error info from any thrown value.\n *\n * Generic utility with no framework dependencies. Handles standard\n * Error objects and Axios-shaped error responses via duck-typing\n * (no axios import required).\n */\n\nexport interface NormalizedError {\n message: string;\n status?: number;\n details?: string[];\n}\n\n/**\n * Normalize any thrown value into a structured error object.\n *\n * Handles:\n * - Axios-shaped errors (duck-typed via `isAxiosError` property)\n * - Standard Error objects\n * - Arbitrary values (stringified)\n */\n// Extract validation detail messages from a FastAPI-style detail array.\nfunction extractDetailMessages(detail: unknown[]): string[] {\n const msgs: string[] = [];\n for (const item of detail) {\n if (\n item &&\n typeof item === \"object\" &&\n typeof (item as Record<string, unknown>).msg === \"string\"\n ) {\n msgs.push((item as Record<string, unknown>).msg as string);\n }\n }\n return msgs;\n}\n\n// Extract message and details from an Axios-like response data payload.\nfunction extractFromResponseData(\n data: unknown,\n fallback: string,\n): { message: string; details: string[] } {\n if (typeof data === \"string\") return { message: data, details: [] };\n if (typeof data !== \"object\" || data === null)\n return { message: fallback, details: [] };\n\n const d = data as Record<string, unknown>;\n if (typeof d.detail === \"string\") return { message: d.detail, details: [] };\n if (Array.isArray(d.detail)) {\n const details = extractDetailMessages(d.detail);\n return { message: details.length ? details.join(\"; \") : fallback, details };\n }\n if (typeof d.error === \"string\") return { message: d.error, details: [] };\n return { message: fallback, details: [] };\n}\n\n// Normalize an Axios-like error into a structured error object.\nfunction normalizeAxiosError(err: AxiosLikeError): NormalizedError {\n const status = err.response?.status;\n const { message, details } = extractFromResponseData(\n err.response?.data,\n err.message,\n );\n return { message, status, details: details.length ? details : undefined };\n}\n\nexport function normalizeError(err: unknown): NormalizedError {\n if (isAxiosLikeError(err)) return normalizeAxiosError(err);\n if (err instanceof Error) return { message: err.message };\n return { message: String(err) };\n}\n\n// Duck-type check for Axios-like errors.\ninterface AxiosLikeError {\n isAxiosError: true;\n message: string;\n response?: {\n status?: number;\n data?: unknown;\n };\n}\n\nfunction isAxiosLikeError(err: unknown): err is AxiosLikeError {\n return (\n err !== null &&\n typeof err === \"object\" &&\n \"isAxiosError\" in err &&\n (err as Record<string, unknown>).isAxiosError === true\n );\n}\n","import * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === \"function\") {\n return ref(value);\n }\n\n if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup === \"function\") {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup === \"function\") {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs.\n * Accepts callback refs and RefObject(s).\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n // Store the latest refs in a mutable ref so the stable callback always\n // sees the current values without needing a dynamic dependency array.\n const refsRef = React.useRef(refs);\n React.useEffect(() => {\n refsRef.current = refs;\n });\n\n return React.useCallback((node: T) => {\n composeRefs(...refsRef.current)(node);\n }, []);\n}\n\nexport { composeRefs, useComposedRefs };\n","/**\n * Resolve JSON Pointer (RFC 6901) paths to character offset ranges\n * within a formatted JSON string.\n *\n * Given a JSON string and a list of JSON Pointers, returns the\n * [from, to] byte ranges covering the *value* of each pointer.\n * The range includes the full value (object braces, array brackets,\n * string quotes, nested content) so the user can freely edit\n * everything inside.\n *\n * Zero external dependencies — pure TypeScript.\n * Maps to @petrarca/sonnet-core on extraction.\n */\n\nexport interface OffsetRange {\n /** JSON Pointer that was resolved */\n pointer: string;\n /** Start character offset (inclusive) */\n from: number;\n /** End character offset (exclusive) */\n to: number;\n}\n\n/**\n * Unescape a JSON Pointer token per RFC 6901:\n * ~1 -> /\n * ~0 -> ~\n */\nfunction unescapeToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Parse a JSON Pointer string into an array of unescaped tokens.\n * \"\" returns [] (root document, per RFC 6901)\n * \"/\" returns [] (sentinel for \"entire document\" -- treated as root)\n * \"/a/b\" returns [\"a\", \"b\"]\n */\nfunction parsePointer(pointer: string): string[] {\n if (pointer === \"\" || pointer === \"/\") return [];\n if (!pointer.startsWith(\"/\")) {\n throw new Error(`Invalid JSON Pointer: \"${pointer}\" (must start with /)`);\n }\n return pointer.slice(1).split(\"/\").map(unescapeToken);\n}\n\n/**\n * Find the character offset range of a value at the given path\n * within a JSON string. Walks the string character by character\n * to find the key, then captures the full value range.\n */\n/** Mutable cursor for walking a JSON string character by character. */\ninterface Cursor {\n pos: number;\n json: string;\n}\n\nfunction skipWs(c: Cursor): void {\n while (c.pos < c.json.length && /\\s/.test(c.json[c.pos])) c.pos++;\n}\n\nfunction skipString(c: Cursor): number {\n c.pos++; // skip opening \"\n while (c.pos < c.json.length) {\n if (c.json[c.pos] === \"\\\\\") {\n c.pos += 2;\n continue;\n }\n if (c.json[c.pos] === '\"') {\n c.pos++;\n return c.pos;\n }\n c.pos++;\n }\n return c.pos;\n}\n\nfunction skipContainer(c: Cursor, open: string, close: string): number {\n c.pos++; // skip opening bracket\n skipWs(c);\n if (c.json[c.pos] === close) {\n c.pos++;\n return c.pos;\n }\n while (c.pos < c.json.length) {\n if (open === \"{\") {\n skipValue(c); // key\n skipWs(c);\n if (c.json[c.pos] === \":\") c.pos++;\n }\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n continue;\n }\n if (c.json[c.pos] === close) {\n c.pos++;\n return c.pos;\n }\n break;\n }\n return c.pos;\n}\n\nfunction skipValue(c: Cursor): number {\n skipWs(c);\n if (c.pos >= c.json.length) return c.pos;\n const ch = c.json[c.pos];\n if (ch === '\"') return skipString(c);\n if (ch === \"{\") return skipContainer(c, \"{\", \"}\");\n if (ch === \"[\") return skipContainer(c, \"[\", \"]\");\n // Number, bool, null -- consume until delimiter\n while (c.pos < c.json.length && !/[,\\]}\\s]/.test(c.json[c.pos])) c.pos++;\n return c.pos;\n}\n\n/** Read and return the next JSON string key, advancing past the closing quote. */\nfunction readKey(c: Cursor): string {\n const keyStart = c.pos + 1;\n c.pos++;\n while (c.pos < c.json.length) {\n if (c.json[c.pos] === \"\\\\\") {\n c.pos += 2;\n continue;\n }\n if (c.json[c.pos] === '\"') break;\n c.pos++;\n }\n const key = c.json.slice(keyStart, c.pos);\n c.pos++; // skip closing \"\n return key;\n}\n\n/** Navigate into a JSON object to the value at `token`. */\nfunction navigateObject(c: Cursor, token: string): boolean {\n c.pos++; // skip {\n skipWs(c);\n while (c.pos < c.json.length && c.json[c.pos] !== \"}\") {\n if (c.json[c.pos] !== '\"') return false;\n const key = readKey(c);\n skipWs(c);\n if (c.json[c.pos] === \":\") c.pos++;\n skipWs(c);\n if (key === token) return true;\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n }\n }\n return false;\n}\n\n/** Navigate into a JSON array to the element at `index`. */\nfunction navigateArray(c: Cursor, index: number): boolean {\n c.pos++; // skip [\n skipWs(c);\n for (let j = 0; j < index; j++) {\n if (c.pos >= c.json.length || c.json[c.pos] === \"]\") return false;\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n }\n }\n return true;\n}\n\nfunction findValueRange(\n json: string,\n tokens: string[],\n): { from: number; to: number } | null {\n const c: Cursor = { pos: 0, json };\n\n for (const token of tokens) {\n skipWs(c);\n if (c.json[c.pos] === \"{\") {\n if (!navigateObject(c, token)) return null;\n } else if (c.json[c.pos] === \"[\") {\n const index = parseInt(token, 10);\n if (isNaN(index) || !navigateArray(c, index)) return null;\n } else {\n return null;\n }\n }\n\n skipWs(c);\n const from = c.pos;\n const to = skipValue(c);\n return { from, to };\n}\n\n/**\n * Resolve a list of JSON Pointers to character offset ranges.\n *\n * @param json - The formatted JSON string\n * @param pointers - Array of JSON Pointer strings (e.g. \"/metadata\", \"/definitions/Patient\")\n * @returns Array of resolved ranges sorted by position (unresolvable pointers are silently skipped)\n */\nexport function resolvePointerRanges(\n json: string,\n pointers: string[],\n): OffsetRange[] {\n const ranges: OffsetRange[] = [];\n\n for (const pointer of pointers) {\n const tokens = parsePointer(pointer);\n const range = findValueRange(json, tokens);\n if (range) {\n ranges.push({ pointer, from: range.from, to: range.to });\n }\n }\n\n // Sort by position so consumers can walk ranges in document order\n ranges.sort((a, b) => a.from - b.from);\n return ranges;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/format.ts","../src/jsonUtils.ts","../src/utils/copyTextToClipboard.tsx","../src/utils/snakeCaseToTitleCase.ts","../src/errorNormalizer.ts","../src/compose-refs.ts","../src/resolvePointerRanges.ts","../src/pagination.ts"],"sourcesContent":["// Reusable formatting utilities.\n// Thin wrappers around Intl for consistent output across the app.\n\nconst numberFormatter = new Intl.NumberFormat(undefined, {\n maximumFractionDigits: 0,\n});\n\nexport function formatNumber(\n value: number | string | undefined | null,\n): string {\n if (value === null || value === undefined) return \"0\";\n const num = typeof value === \"string\" ? Number(value) : value;\n if (!Number.isFinite(num)) return String(value);\n return numberFormatter.format(num as number);\n}\n\nexport function formatNumberCompact(\n value: number | string | undefined | null,\n): string {\n if (value === null || value === undefined) return \"0\";\n const num = typeof value === \"string\" ? Number(value) : value;\n if (!Number.isFinite(num)) return String(value);\n return new Intl.NumberFormat(undefined, {\n notation: \"compact\",\n maximumFractionDigits: 1,\n }).format(num as number);\n}\n\n/** Format an ISO 8601 string as a short date (e.g. \"Jan 15, 2026\"). */\nexport function formatDate(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n return new Date(iso).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\n/** Format an ISO 8601 string as date + time (e.g. \"Jan 15, 2026, 09:32 AM\"). */\nexport function formatDateTime(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n return new Date(iso).toLocaleString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/**\n * Format a timezone-aware ISO 8601 string preserving the original offset.\n *\n * Parses the date/time components and offset directly from the string so\n * the emitter's local time and offset are shown exactly as recorded --\n * independent of the viewer's timezone.\n *\n * Examples:\n * \"2026-02-22T10:00:00+01:00\" -> \"22 Feb 2026 at 10:00 +01:00\"\n * \"2026-02-22T09:00:00Z\" -> \"22 Feb 2026 at 09:00 UTC\"\n */\nexport function formatDateTimeTz(iso: string | null | undefined): string {\n if (!iso) return \"\\u2014\";\n\n // Match ISO 8601: date, time (HH:MM), optional seconds, offset or Z.\n const match = iso.match(\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::\\d{2}(?:\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$/,\n );\n if (!match) return iso;\n\n const [, year, month, day, hour, minute, rawOffset] = match;\n\n const months = [\n \"Jan\",\n \"Feb\",\n \"Mar\",\n \"Apr\",\n \"May\",\n \"Jun\",\n \"Jul\",\n \"Aug\",\n \"Sep\",\n \"Oct\",\n \"Nov\",\n \"Dec\",\n ];\n const monthName = months[parseInt(month, 10) - 1];\n const offset = rawOffset === \"Z\" ? \"UTC\" : rawOffset;\n\n return `${parseInt(day, 10)} ${monthName} ${year} at ${hour}:${minute} ${offset}`;\n}\n\n/** Return \"{count} {word}\" with the word pluralized when count != 1. */\nexport function pluralize(count: number, word: string): string {\n return `${count} ${word}${count !== 1 ? \"s\" : \"\"}`;\n}\n","/**\n * Escapes HTML characters to prevent XSS and rendering issues\n */\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Formats a JSON value into colored HTML with proper syntax highlighting\n * @param value - The value to format (any JSON-serializable type)\n * @param indent - Current indentation level for nested objects/arrays\n * @returns HTML string with syntax highlighting\n */\nfunction jsonSpan(cssVar: string, content: string): string {\n return `<span style=\"color: var(${cssVar})\">${content}</span>`;\n}\n\nfunction formatPrimitive(value: unknown): string | null {\n if (value === null) return jsonSpan(\"--json-null\", \"null\");\n if (typeof value === \"undefined\") return jsonSpan(\"--json-null\", \"undefined\");\n if (typeof value === \"boolean\")\n return jsonSpan(\"--json-boolean\", String(value));\n if (typeof value === \"number\")\n return jsonSpan(\"--json-number\", String(value));\n if (typeof value === \"string\")\n return jsonSpan(\"--json-string\", `\"${escapeHtml(value)}\"`);\n return null;\n}\n\nfunction formatArray(value: unknown[], indent: number): string {\n if (value.length === 0) return jsonSpan(\"--json-punctuation\", \"[]\");\n const indentStr = \" \".repeat(indent);\n const items = value\n .map((item) => `${indentStr} ${formatJsonValue(item, indent + 1)}`)\n .join(\",\\n\");\n return `${jsonSpan(\"--json-punctuation\", \"[\")}\\n${items}\\n${indentStr}${jsonSpan(\"--json-punctuation\", \"]\")}`;\n}\n\nfunction formatObject(value: object, indent: number): string {\n const entries = Object.entries(value);\n if (entries.length === 0) return jsonSpan(\"--json-punctuation\", \"{}\");\n const indentStr = \" \".repeat(indent);\n const items = entries\n .map(\n ([key, val]) =>\n `${indentStr} ${jsonSpan(\"--json-key\", `\"${escapeHtml(key)}\"`)}${jsonSpan(\"--json-punctuation\", \":\")} ${formatJsonValue(val, indent + 1)}`,\n )\n .join(\",\\n\");\n return `${jsonSpan(\"--json-punctuation\", \"{\")}\\n${items}\\n${indentStr}${jsonSpan(\"--json-punctuation\", \"}\")}`;\n}\n\nexport function formatJsonValue(value: unknown, indent = 0): string {\n const primitive = formatPrimitive(value);\n if (primitive !== null) return primitive;\n if (Array.isArray(value)) return formatArray(value, indent);\n if (typeof value === \"object\" && value !== null)\n return formatObject(value, indent);\n return jsonSpan(\"--json-fallback\", escapeHtml(String(value)));\n}\n\n/**\n * CSS custom properties for JSON syntax highlighting colors\n */\nexport const JSON_CSS_VARIABLES = {\n \"--json-key\": \"#2563eb\",\n \"--json-string\": \"#16a34a\",\n \"--json-number\": \"#ea580c\",\n \"--json-boolean\": \"#2563eb\",\n \"--json-null\": \"#64748b\",\n \"--json-punctuation\": \"#475569\",\n \"--json-fallback\": \"#6b7280\",\n} as const;\n","/**\n * Copy text to the system clipboard.\n *\n * Optionally calls `onSuccess` after a successful write. The caller\n * provides its own notification mechanism (e.g. `notification.success`\n * from the shell API) to keep this utility free of sonner or any other\n * toast dependency.\n */\nexport async function copyTextToClipboard(\n text: string,\n options?: { onSuccess?: (message: string) => void },\n): Promise<void> {\n await navigator.clipboard.writeText(text);\n options?.onSuccess?.(`${text} copied to clipboard`);\n}\n","function snakeCaseToTitleCase(snakeCase: string): string {\n const words = snakeCase.split(\"_\");\n const titleCaseWords = words.map(\n (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n );\n return titleCaseWords.join(\" \");\n}\n\nexport default snakeCaseToTitleCase;\n","/**\n * Error normalizer -- extract typed error info from any thrown value.\n *\n * Generic utility with no framework dependencies. Handles standard\n * Error objects and Axios-shaped error responses via duck-typing\n * (no axios import required).\n */\n\nexport interface NormalizedError {\n message: string;\n status?: number;\n details?: string[];\n}\n\n/**\n * Normalize any thrown value into a structured error object.\n *\n * Handles:\n * - Axios-shaped errors (duck-typed via `isAxiosError` property)\n * - Standard Error objects\n * - Arbitrary values (stringified)\n */\n// Extract validation detail messages from a FastAPI-style detail array.\nfunction extractDetailMessages(detail: unknown[]): string[] {\n const msgs: string[] = [];\n for (const item of detail) {\n if (\n item &&\n typeof item === \"object\" &&\n typeof (item as Record<string, unknown>).msg === \"string\"\n ) {\n msgs.push((item as Record<string, unknown>).msg as string);\n }\n }\n return msgs;\n}\n\n// Extract message and details from an Axios-like response data payload.\nfunction extractFromResponseData(\n data: unknown,\n fallback: string,\n): { message: string; details: string[] } {\n if (typeof data === \"string\") return { message: data, details: [] };\n if (typeof data !== \"object\" || data === null)\n return { message: fallback, details: [] };\n\n const d = data as Record<string, unknown>;\n if (typeof d.detail === \"string\") return { message: d.detail, details: [] };\n if (Array.isArray(d.detail)) {\n const details = extractDetailMessages(d.detail);\n return { message: details.length ? details.join(\"; \") : fallback, details };\n }\n if (typeof d.error === \"string\") return { message: d.error, details: [] };\n return { message: fallback, details: [] };\n}\n\n// Normalize an Axios-like error into a structured error object.\nfunction normalizeAxiosError(err: AxiosLikeError): NormalizedError {\n const status = err.response?.status;\n const { message, details } = extractFromResponseData(\n err.response?.data,\n err.message,\n );\n return { message, status, details: details.length ? details : undefined };\n}\n\nexport function normalizeError(err: unknown): NormalizedError {\n if (isAxiosLikeError(err)) return normalizeAxiosError(err);\n if (err instanceof Error) return { message: err.message };\n return { message: String(err) };\n}\n\n// Duck-type check for Axios-like errors.\ninterface AxiosLikeError {\n isAxiosError: true;\n message: string;\n response?: {\n status?: number;\n data?: unknown;\n };\n}\n\nfunction isAxiosLikeError(err: unknown): err is AxiosLikeError {\n return (\n err !== null &&\n typeof err === \"object\" &&\n \"isAxiosError\" in err &&\n (err as Record<string, unknown>).isAxiosError === true\n );\n}\n","import * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === \"function\") {\n return ref(value);\n }\n\n if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup === \"function\") {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup === \"function\") {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs.\n * Accepts callback refs and RefObject(s).\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n // Store the latest refs in a mutable ref so the stable callback always\n // sees the current values without needing a dynamic dependency array.\n const refsRef = React.useRef(refs);\n React.useEffect(() => {\n refsRef.current = refs;\n });\n\n return React.useCallback((node: T) => {\n composeRefs(...refsRef.current)(node);\n }, []);\n}\n\nexport { composeRefs, useComposedRefs };\n","/**\n * Resolve JSON Pointer (RFC 6901) paths to character offset ranges\n * within a formatted JSON string.\n *\n * Given a JSON string and a list of JSON Pointers, returns the\n * [from, to] byte ranges covering the *value* of each pointer.\n * The range includes the full value (object braces, array brackets,\n * string quotes, nested content) so the user can freely edit\n * everything inside.\n *\n * Zero external dependencies — pure TypeScript.\n * Maps to @petrarca/sonnet-core on extraction.\n */\n\nexport interface OffsetRange {\n /** JSON Pointer that was resolved */\n pointer: string;\n /** Start character offset (inclusive) */\n from: number;\n /** End character offset (exclusive) */\n to: number;\n}\n\n/**\n * Unescape a JSON Pointer token per RFC 6901:\n * ~1 -> /\n * ~0 -> ~\n */\nfunction unescapeToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Parse a JSON Pointer string into an array of unescaped tokens.\n * \"\" returns [] (root document, per RFC 6901)\n * \"/\" returns [] (sentinel for \"entire document\" -- treated as root)\n * \"/a/b\" returns [\"a\", \"b\"]\n */\nfunction parsePointer(pointer: string): string[] {\n if (pointer === \"\" || pointer === \"/\") return [];\n if (!pointer.startsWith(\"/\")) {\n throw new Error(`Invalid JSON Pointer: \"${pointer}\" (must start with /)`);\n }\n return pointer.slice(1).split(\"/\").map(unescapeToken);\n}\n\n/**\n * Find the character offset range of a value at the given path\n * within a JSON string. Walks the string character by character\n * to find the key, then captures the full value range.\n */\n/** Mutable cursor for walking a JSON string character by character. */\ninterface Cursor {\n pos: number;\n json: string;\n}\n\nfunction skipWs(c: Cursor): void {\n while (c.pos < c.json.length && /\\s/.test(c.json[c.pos])) c.pos++;\n}\n\nfunction skipString(c: Cursor): number {\n c.pos++; // skip opening \"\n while (c.pos < c.json.length) {\n if (c.json[c.pos] === \"\\\\\") {\n c.pos += 2;\n continue;\n }\n if (c.json[c.pos] === '\"') {\n c.pos++;\n return c.pos;\n }\n c.pos++;\n }\n return c.pos;\n}\n\nfunction skipContainer(c: Cursor, open: string, close: string): number {\n c.pos++; // skip opening bracket\n skipWs(c);\n if (c.json[c.pos] === close) {\n c.pos++;\n return c.pos;\n }\n while (c.pos < c.json.length) {\n if (open === \"{\") {\n skipValue(c); // key\n skipWs(c);\n if (c.json[c.pos] === \":\") c.pos++;\n }\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n continue;\n }\n if (c.json[c.pos] === close) {\n c.pos++;\n return c.pos;\n }\n break;\n }\n return c.pos;\n}\n\nfunction skipValue(c: Cursor): number {\n skipWs(c);\n if (c.pos >= c.json.length) return c.pos;\n const ch = c.json[c.pos];\n if (ch === '\"') return skipString(c);\n if (ch === \"{\") return skipContainer(c, \"{\", \"}\");\n if (ch === \"[\") return skipContainer(c, \"[\", \"]\");\n // Number, bool, null -- consume until delimiter\n while (c.pos < c.json.length && !/[,\\]}\\s]/.test(c.json[c.pos])) c.pos++;\n return c.pos;\n}\n\n/** Read and return the next JSON string key, advancing past the closing quote. */\nfunction readKey(c: Cursor): string {\n const keyStart = c.pos + 1;\n c.pos++;\n while (c.pos < c.json.length) {\n if (c.json[c.pos] === \"\\\\\") {\n c.pos += 2;\n continue;\n }\n if (c.json[c.pos] === '\"') break;\n c.pos++;\n }\n const key = c.json.slice(keyStart, c.pos);\n c.pos++; // skip closing \"\n return key;\n}\n\n/** Navigate into a JSON object to the value at `token`. */\nfunction navigateObject(c: Cursor, token: string): boolean {\n c.pos++; // skip {\n skipWs(c);\n while (c.pos < c.json.length && c.json[c.pos] !== \"}\") {\n if (c.json[c.pos] !== '\"') return false;\n const key = readKey(c);\n skipWs(c);\n if (c.json[c.pos] === \":\") c.pos++;\n skipWs(c);\n if (key === token) return true;\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n }\n }\n return false;\n}\n\n/** Navigate into a JSON array to the element at `index`. */\nfunction navigateArray(c: Cursor, index: number): boolean {\n c.pos++; // skip [\n skipWs(c);\n for (let j = 0; j < index; j++) {\n if (c.pos >= c.json.length || c.json[c.pos] === \"]\") return false;\n skipValue(c);\n skipWs(c);\n if (c.json[c.pos] === \",\") {\n c.pos++;\n skipWs(c);\n }\n }\n return true;\n}\n\nfunction findValueRange(\n json: string,\n tokens: string[],\n): { from: number; to: number } | null {\n const c: Cursor = { pos: 0, json };\n\n for (const token of tokens) {\n skipWs(c);\n if (c.json[c.pos] === \"{\") {\n if (!navigateObject(c, token)) return null;\n } else if (c.json[c.pos] === \"[\") {\n const index = parseInt(token, 10);\n if (isNaN(index) || !navigateArray(c, index)) return null;\n } else {\n return null;\n }\n }\n\n skipWs(c);\n const from = c.pos;\n const to = skipValue(c);\n return { from, to };\n}\n\n/**\n * Resolve a list of JSON Pointers to character offset ranges.\n *\n * @param json - The formatted JSON string\n * @param pointers - Array of JSON Pointer strings (e.g. \"/metadata\", \"/definitions/Patient\")\n * @returns Array of resolved ranges sorted by position (unresolvable pointers are silently skipped)\n */\nexport function resolvePointerRanges(\n json: string,\n pointers: string[],\n): OffsetRange[] {\n const ranges: OffsetRange[] = [];\n\n for (const pointer of pointers) {\n const tokens = parsePointer(pointer);\n const range = findValueRange(json, tokens);\n if (range) {\n ranges.push({ pointer, from: range.from, to: range.to });\n }\n }\n\n // Sort by position so consumers can walk ranges in document order\n ranges.sort((a, b) => a.from - b.from);\n return ranges;\n}\n","/**\n * Pagination types for paginated list responses.\n *\n * Generic shapes with no application-specific content.\n * Extractable to @petrarca/sonnet-core: no app-specific imports.\n */\n\n/** Pagination metadata included in paginated list responses. */\nexport interface PageMeta {\n total: number;\n limit: number;\n offset: number;\n has_next: boolean;\n has_prev: boolean;\n}\n\n/** Paginated list response envelope. */\nexport interface PageResult<T> {\n data: T[];\n meta: PageMeta;\n}\n\n/**\n * Wrap a full array as a single-page PageResult.\n *\n * Bridges \"regular\" (un-paginated) list APIs that return a bare array into the\n * PageResult envelope that paginated consumers (e.g. EntityTable) expect. The\n * whole array is treated as one complete page: total = length, offset = 0, and\n * no next/prev page. Use for small lists served without server-side pagination.\n */\nexport function toPageResult<T>(rows: T[]): PageResult<T> {\n return {\n data: rows,\n meta: {\n total: rows.length,\n limit: rows.length,\n offset: 0,\n has_next: false,\n has_prev: false,\n },\n };\n}\n"],"mappings":"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","names":[]}
|