@petrarca/sonnet-core 0.1.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.
@@ -0,0 +1,207 @@
1
+ import {
2
+ isEqual
3
+ } from "../chunk-XCCOEUVC.js";
4
+
5
+ // src/hooks/useClipboard.ts
6
+ import { useState, useCallback } from "react";
7
+ function useClipboard({
8
+ timeout = 2e3
9
+ } = {}) {
10
+ const [copied, setCopied] = useState(false);
11
+ const [error, setError] = useState(null);
12
+ const copy = useCallback(
13
+ async (text) => {
14
+ try {
15
+ await navigator.clipboard.writeText(text);
16
+ setCopied(true);
17
+ setError(null);
18
+ setTimeout(() => setCopied(false), timeout);
19
+ } catch (err) {
20
+ setError(err instanceof Error ? err : new Error(String(err)));
21
+ setCopied(false);
22
+ }
23
+ },
24
+ [timeout]
25
+ );
26
+ const reset = useCallback(() => {
27
+ setCopied(false);
28
+ setError(null);
29
+ }, []);
30
+ return { copy, copied, error, reset };
31
+ }
32
+
33
+ // src/hooks/useDisclosure.ts
34
+ import { useState as useState2, useCallback as useCallback2 } from "react";
35
+ function useDisclosure(initialState = false) {
36
+ const [opened, setOpened] = useState2(initialState);
37
+ const open = useCallback2(() => setOpened(true), []);
38
+ const close = useCallback2(() => setOpened(false), []);
39
+ const toggle = useCallback2(() => setOpened((prev) => !prev), []);
40
+ return [opened, { open, close, toggle }];
41
+ }
42
+
43
+ // src/hooks/useResizablePanel.ts
44
+ import { useState as useState3, useRef, useCallback as useCallback3, useEffect } from "react";
45
+ function useResizablePanel(options = {}) {
46
+ const {
47
+ defaultWidth = 320,
48
+ minWidth = 260,
49
+ maxWidth = 640,
50
+ direction = "left-edge"
51
+ } = options;
52
+ const [panelWidth, setPanelWidth] = useState3(defaultWidth);
53
+ const resizingRef = useRef(false);
54
+ const startXRef = useRef(0);
55
+ const startWidthRef = useRef(0);
56
+ const clamp = useCallback3(
57
+ (v) => Math.min(Math.max(v, minWidth), maxWidth),
58
+ [minWidth, maxWidth]
59
+ );
60
+ const finishResize = useCallback3(() => {
61
+ if (!resizingRef.current) return;
62
+ resizingRef.current = false;
63
+ document.body.classList.remove("select-none");
64
+ }, []);
65
+ const handlePointerMove = useCallback3(
66
+ (e) => {
67
+ if (!resizingRef.current) return;
68
+ const delta = e.clientX - startXRef.current;
69
+ const newWidth = direction === "right-edge" ? startWidthRef.current + delta : startWidthRef.current - delta;
70
+ setPanelWidth(clamp(newWidth));
71
+ },
72
+ [clamp, direction]
73
+ );
74
+ const handlePointerDown = useCallback3(
75
+ (e) => {
76
+ resizingRef.current = true;
77
+ startXRef.current = e.clientX;
78
+ startWidthRef.current = panelWidth;
79
+ document.body.classList.add("select-none");
80
+ },
81
+ [panelWidth]
82
+ );
83
+ const handleDoubleClick = useCallback3(
84
+ () => setPanelWidth(defaultWidth),
85
+ [defaultWidth]
86
+ );
87
+ useEffect(() => {
88
+ window.addEventListener("pointermove", handlePointerMove);
89
+ window.addEventListener("pointerup", finishResize);
90
+ window.addEventListener("pointercancel", finishResize);
91
+ const handleKey = (e) => {
92
+ if (e.key === "Escape" && resizingRef.current) {
93
+ setPanelWidth(startWidthRef.current);
94
+ finishResize();
95
+ }
96
+ };
97
+ window.addEventListener("keydown", handleKey);
98
+ return () => {
99
+ window.removeEventListener("pointermove", handlePointerMove);
100
+ window.removeEventListener("pointerup", finishResize);
101
+ window.removeEventListener("pointercancel", finishResize);
102
+ window.removeEventListener("keydown", handleKey);
103
+ };
104
+ }, [handlePointerMove, finishResize]);
105
+ return {
106
+ panelWidth,
107
+ handlePointerDown,
108
+ handleDoubleClick,
109
+ separatorProps: {
110
+ role: "separator",
111
+ "aria-orientation": "vertical",
112
+ "aria-valuenow": panelWidth,
113
+ "aria-valuemin": minWidth,
114
+ "aria-valuemax": maxWidth
115
+ }
116
+ };
117
+ }
118
+
119
+ // src/hooks/useFormState.ts
120
+ import { useState as useState4, useCallback as useCallback4, useMemo } from "react";
121
+ function useFormState(initialData) {
122
+ const [data, setDataState] = useState4(initialData);
123
+ const [original, setOriginal] = useState4(initialData);
124
+ const isDirty = useMemo(() => {
125
+ return !isEqual(data, original);
126
+ }, [data, original]);
127
+ const setData = useCallback4((newData) => {
128
+ setDataState(newData);
129
+ }, []);
130
+ const setField = useCallback4((field, value) => {
131
+ setDataState((prev) => ({
132
+ ...prev,
133
+ [field]: value
134
+ }));
135
+ }, []);
136
+ const reset = useCallback4((newData) => {
137
+ setDataState(newData);
138
+ setOriginal(newData);
139
+ }, []);
140
+ const discard = useCallback4(() => {
141
+ setDataState(original);
142
+ }, [original]);
143
+ return {
144
+ data,
145
+ setData,
146
+ setField,
147
+ isDirty,
148
+ reset,
149
+ discard,
150
+ original
151
+ };
152
+ }
153
+
154
+ // src/hooks/useEntityOptions.ts
155
+ import { useState as useState5, useEffect as useEffect2, useCallback as useCallback5 } from "react";
156
+ import { useQuery } from "@tanstack/react-query";
157
+ function resolveEnabled(enabled, mode, activeSearch, minChars, committedSearch) {
158
+ if (!enabled) return false;
159
+ if (mode === "typeahead") return activeSearch.length >= minChars;
160
+ if (mode === "explicit") return committedSearch !== null;
161
+ return true;
162
+ }
163
+ function useEntityOptions({
164
+ fetcher,
165
+ queryKey,
166
+ mode = "eager",
167
+ debounceMs = 300,
168
+ minChars = 1,
169
+ limit = 50,
170
+ enabled = true
171
+ }) {
172
+ const [search, setSearch] = useState5("");
173
+ const [debouncedSearch, setDebouncedSearch] = useState5("");
174
+ const [committedSearch, setCommittedSearch] = useState5(null);
175
+ useEffect2(() => {
176
+ if (mode === "explicit") return;
177
+ const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);
178
+ return () => clearTimeout(timer);
179
+ }, [search, debounceMs, mode]);
180
+ const submit = useCallback5(() => {
181
+ setCommittedSearch(search);
182
+ }, [search]);
183
+ const activeSearch = mode === "explicit" ? committedSearch ?? "" : debouncedSearch;
184
+ const queryEnabled = resolveEnabled(
185
+ enabled,
186
+ mode,
187
+ activeSearch,
188
+ minChars,
189
+ committedSearch
190
+ );
191
+ const { data, isLoading, isError } = useQuery({
192
+ queryKey: [queryKey, "options", { search: activeSearch, limit }],
193
+ queryFn: () => fetcher({ search: activeSearch || void 0, limit }),
194
+ enabled: queryEnabled,
195
+ staleTime: 5 * 60 * 1e3
196
+ });
197
+ const options = data ?? [];
198
+ return { options, isLoading, isError, search, setSearch, submit };
199
+ }
200
+ export {
201
+ useClipboard,
202
+ useDisclosure,
203
+ useEntityOptions,
204
+ useFormState,
205
+ useResizablePanel
206
+ };
207
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,140 @@
1
+ import { ClassValue } from 'clsx';
2
+ import * as React from 'react';
3
+
4
+ /** Field name used to embed stable form-internal item IDs on array items. Never sent to the server. */
5
+ declare const FORM_ITEM_ID_FIELD: "_fid";
6
+ /** Generate a stable 21-character form-internal item ID. */
7
+ declare const generateId: () => string;
8
+ declare function cn(...inputs: ClassValue[]): string;
9
+ declare function isEqual(a: unknown, b: unknown): boolean;
10
+
11
+ declare const devLog: (...a: unknown[]) => void;
12
+ declare const infoLog: (...a: unknown[]) => void;
13
+ declare const warnLog: (...a: unknown[]) => void;
14
+ declare const errorLog: (...a: unknown[]) => void;
15
+
16
+ declare function formatNumber(value: number | string | undefined | null): string;
17
+ declare function formatNumberCompact(value: number | string | undefined | null): string;
18
+ /** Format an ISO 8601 string as a short date (e.g. "Jan 15, 2026"). */
19
+ declare function formatDate(iso: string | null | undefined): string;
20
+ /** Format an ISO 8601 string as date + time (e.g. "Jan 15, 2026, 09:32 AM"). */
21
+ declare function formatDateTime(iso: string | null | undefined): string;
22
+ /**
23
+ * Format a timezone-aware ISO 8601 string preserving the original offset.
24
+ *
25
+ * Parses the date/time components and offset directly from the string so
26
+ * the emitter's local time and offset are shown exactly as recorded --
27
+ * independent of the viewer's timezone.
28
+ *
29
+ * Examples:
30
+ * "2026-02-22T10:00:00+01:00" -> "22 Feb 2026 at 10:00 +01:00"
31
+ * "2026-02-22T09:00:00Z" -> "22 Feb 2026 at 09:00 UTC"
32
+ */
33
+ declare function formatDateTimeTz(iso: string | null | undefined): string;
34
+ /** Return "{count} {word}" with the word pluralized when count != 1. */
35
+ declare function pluralize(count: number, word: string): string;
36
+
37
+ /**
38
+ * Escapes HTML characters to prevent XSS and rendering issues
39
+ */
40
+ declare function escapeHtml(value: string): string;
41
+ declare function formatJsonValue(value: unknown, indent?: number): string;
42
+ /**
43
+ * CSS custom properties for JSON syntax highlighting colors
44
+ */
45
+ declare const JSON_CSS_VARIABLES: {
46
+ readonly "--json-key": "#2563eb";
47
+ readonly "--json-string": "#16a34a";
48
+ readonly "--json-number": "#ea580c";
49
+ readonly "--json-boolean": "#2563eb";
50
+ readonly "--json-null": "#64748b";
51
+ readonly "--json-punctuation": "#475569";
52
+ readonly "--json-fallback": "#6b7280";
53
+ };
54
+
55
+ /**
56
+ * Copy text to the system clipboard.
57
+ *
58
+ * Optionally calls `onSuccess` after a successful write. The caller
59
+ * provides its own notification mechanism (e.g. `notification.success`
60
+ * from the shell API) to keep this utility free of sonner or any other
61
+ * toast dependency.
62
+ */
63
+ declare function copyTextToClipboard(text: string, options?: {
64
+ onSuccess?: (message: string) => void;
65
+ }): Promise<void>;
66
+
67
+ declare function snakeCaseToTitleCase(snakeCase: string): string;
68
+
69
+ /**
70
+ * Error normalizer -- extract typed error info from any thrown value.
71
+ *
72
+ * Generic utility with no framework dependencies. Handles standard
73
+ * Error objects and Axios-shaped error responses via duck-typing
74
+ * (no axios import required).
75
+ */
76
+ interface NormalizedError {
77
+ message: string;
78
+ status?: number;
79
+ details?: string[];
80
+ }
81
+ declare function normalizeError(err: unknown): NormalizedError;
82
+
83
+ type PossibleRef<T> = React.Ref<T> | undefined;
84
+ /**
85
+ * A custom hook that composes multiple refs.
86
+ * Accepts callback refs and RefObject(s).
87
+ */
88
+ declare function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T>;
89
+
90
+ /**
91
+ * Resolve JSON Pointer (RFC 6901) paths to character offset ranges
92
+ * within a formatted JSON string.
93
+ *
94
+ * Given a JSON string and a list of JSON Pointers, returns the
95
+ * [from, to] byte ranges covering the *value* of each pointer.
96
+ * The range includes the full value (object braces, array brackets,
97
+ * string quotes, nested content) so the user can freely edit
98
+ * everything inside.
99
+ *
100
+ * Zero external dependencies — pure TypeScript.
101
+ * Maps to @petrarca/sonnet-core on extraction.
102
+ */
103
+ interface OffsetRange {
104
+ /** JSON Pointer that was resolved */
105
+ pointer: string;
106
+ /** Start character offset (inclusive) */
107
+ from: number;
108
+ /** End character offset (exclusive) */
109
+ to: number;
110
+ }
111
+ /**
112
+ * Resolve a list of JSON Pointers to character offset ranges.
113
+ *
114
+ * @param json - The formatted JSON string
115
+ * @param pointers - Array of JSON Pointer strings (e.g. "/metadata", "/definitions/Patient")
116
+ * @returns Array of resolved ranges sorted by position (unresolvable pointers are silently skipped)
117
+ */
118
+ declare function resolvePointerRanges(json: string, pointers: string[]): OffsetRange[];
119
+
120
+ /**
121
+ * Pagination types for paginated list responses.
122
+ *
123
+ * Generic shapes with no application-specific content.
124
+ * Extractable to @petrarca/sonnet-core: no app-specific imports.
125
+ */
126
+ /** Pagination metadata included in paginated list responses. */
127
+ interface PageMeta {
128
+ total: number;
129
+ limit: number;
130
+ offset: number;
131
+ has_next: boolean;
132
+ has_prev: boolean;
133
+ }
134
+ /** Paginated list response envelope. */
135
+ interface PageResult<T> {
136
+ data: T[];
137
+ meta: PageMeta;
138
+ }
139
+
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 };