@petrarca/sonnet-core 0.8.0 → 0.9.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 CHANGED
@@ -10,6 +10,15 @@ attached. Every other `@petrarca/sonnet-*` package depends on it.
10
10
  of the workspace agrees on, plus `$ref` and conditional resolution, validation
11
11
  through Ajv, visibility rules and form diffing.
12
12
 
13
+ **Core value types.** A quantity, a coding and a codeable concept, each defined
14
+ once and referenced by `$ref`, aligned with FHIR and openEHR, with the bindings
15
+ that tie a field to the lists its codes come from.
16
+
17
+ **A terminology service contract.** `TerminologyService` -- search, look up,
18
+ validate, translate -- with a static and a caching implementation and a
19
+ `TerminologyProvider`, so the coded-value widgets ask the application's own
20
+ terminology server.
21
+
13
22
  **A Zod bridge.** Author a schema in Zod, carry the `x-ui-*` presentation hints
14
23
  on `.meta()`, and convert once to the JSON Schema the renderers read.
15
24
 
@@ -32,7 +41,7 @@ pnpm add @petrarca/sonnet-core
32
41
 
33
42
  | Peer | Range | Needed for |
34
43
  |---|---|---|
35
- | `react` | `>=19` | The `./hooks`, `./config` and `./entityOptions` subpaths |
44
+ | `react` | `>=19` | The `./hooks`, `./config`, `./entityOptions` and `./terminology` subpaths |
36
45
  | `@tanstack/react-query` | `>=5` | `useEntityOptions` only; optional otherwise |
37
46
 
38
47
  Ajv, Zod, `nanoid`, `clsx`, `tailwind-merge` and `class-variance-authority`
@@ -40,8 +49,8 @@ arrive as regular dependencies. There is nothing else to install.
40
49
 
41
50
  ## Usage
42
51
 
43
- The package is six entry points, and the root deliberately re-exports none of
44
- the other five: a caller who wants `formatDate` should not pay for Ajv, and one
52
+ The package is eight entry points, and the root deliberately re-exports none of
53
+ the others but the logger: a caller who wants `formatDate` should not pay for Ajv, and one
45
54
  who wants the filter AST should not pull in React.
46
55
 
47
56
  ```ts
@@ -51,6 +60,8 @@ import { buildListParams } from "@petrarca/sonnet-core/search";
51
60
  import { useDisclosure } from "@petrarca/sonnet-core/hooks";
52
61
  import { FetcherContext } from "@petrarca/sonnet-core/entityOptions";
53
62
  import { SonnetConfigProvider } from "@petrarca/sonnet-core/config";
63
+ import { TerminologyProvider, staticTerminology } from "@petrarca/sonnet-core/terminology";
64
+ import { devLog } from "@petrarca/sonnet-core/logger";
54
65
  ```
55
66
 
56
67
  Which symbol lives in which subpath, and what each one costs to import, is in
@@ -59,13 +70,15 @@ the usage guides:
59
70
  [schemas from Zod](docs/usage/zod-schemas.md),
60
71
  [search filters](docs/usage/search-filters.md),
61
72
  [component config](docs/usage/component-config.md) and
62
- [hooks and utilities](docs/usage/hooks-and-utilities.md).
73
+ [hooks and utilities](docs/usage/hooks-and-utilities.md); the terminology
74
+ service in the workspace guide to
75
+ [coded values](../../docs/usage/coded-values.md).
63
76
 
64
77
  ## Documentation
65
78
 
66
79
  - [Usage guides](docs/usage/index.md) -- task-oriented, for consumers.
67
80
  - [Design docs](docs/design/index.md) -- what belongs in this package and why
68
- it is split into six subpaths.
81
+ it is split into eight subpaths.
69
82
  - [Workspace documentation](../../docs/README.md) -- how the packages layer, the
70
83
  shared schema extension contract, and the architecture decision records.
71
84
 
@@ -26,4 +26,4 @@ export {
26
26
  warnLog,
27
27
  errorLog
28
28
  };
29
- //# sourceMappingURL=chunk-XAEHYRXC.js.map
29
+ //# sourceMappingURL=chunk-LOWDCP73.js.map
@@ -0,0 +1,98 @@
1
+ // src/lazyTree.ts
2
+ function lazyTreePages(rootId, expandedIds, morePages, pageParams) {
3
+ const levels = [rootId, ...[...expandedIds].filter((id) => id !== rootId)];
4
+ return levels.flatMap(
5
+ (levelId) => Array.from({ length: 1 + (morePages[levelId] ?? 0) }, (_, page) => ({
6
+ levelId,
7
+ page,
8
+ params: pageParams(levelId, page)
9
+ }))
10
+ );
11
+ }
12
+ function joinLazyTreePages(pages) {
13
+ const loaded = pages.flatMap((p) => p.data ? [p.data] : []);
14
+ return {
15
+ items: loaded.flatMap((p) => p.items),
16
+ total: loaded.at(-1)?.total ?? 0,
17
+ isPending: pages.some((p) => p.isPending),
18
+ isFirstPending: pages[0]?.isPending ?? true,
19
+ isError: pages.some((p) => p.isError)
20
+ };
21
+ }
22
+ function lazyTreeLevels(requests, states) {
23
+ const byLevel = /* @__PURE__ */ new Map();
24
+ requests.forEach((r, i) => {
25
+ const list = byLevel.get(r.levelId) ?? [];
26
+ list.push(states[i]);
27
+ byLevel.set(r.levelId, list);
28
+ });
29
+ return (levelId) => joinLazyTreePages(byLevel.get(levelId) ?? []);
30
+ }
31
+ function lazyTreeClosingRows(level, levelId, options) {
32
+ if (level.isFirstPending) return [];
33
+ const row = (id, data) => ({
34
+ id,
35
+ data,
36
+ hasChildren: false
37
+ });
38
+ const rows = [];
39
+ if (level.total === 0 && (options.showEmpty?.(levelId) ?? true))
40
+ rows.push(row(`empty:${levelId}`, { row: "empty", levelId }));
41
+ if (level.total > level.items.length)
42
+ rows.push(
43
+ row(`more:${levelId}:${level.items.length}`, {
44
+ row: "more",
45
+ levelId,
46
+ shown: level.items.length,
47
+ total: level.total,
48
+ loading: level.isPending
49
+ })
50
+ );
51
+ (options.extraRows?.(level, levelId) ?? []).forEach(
52
+ (extra, i) => rows.push(row(`extra${i}:${levelId}`, { row: "extra", levelId, extra }))
53
+ );
54
+ return rows;
55
+ }
56
+ function buildLazyTree(options) {
57
+ const { expandedIds, levelOf, toNode } = options;
58
+ const nodesOf = (level, levelId) => [
59
+ ...level.items.map(build),
60
+ ...lazyTreeClosingRows(level, levelId, options)
61
+ ];
62
+ const build = (item) => {
63
+ const { id, data, hasChildren } = toNode(item);
64
+ const level = hasChildren && expandedIds.has(id) ? levelOf(id) : void 0;
65
+ return {
66
+ id,
67
+ data: { row: "node", data },
68
+ hasChildren,
69
+ isLoading: level?.isFirstPending,
70
+ isError: level?.isError,
71
+ children: level && !level.isFirstPending ? nodesOf(level, id) : void 0
72
+ };
73
+ };
74
+ return nodesOf(levelOf(options.rootId), options.rootId);
75
+ }
76
+ function lazyTreeRevealStep(rootId, path, levelOf, idOf) {
77
+ let levelId = rootId;
78
+ for (const id of path) {
79
+ const level = levelOf(levelId);
80
+ if (level.isError) return { state: "error", levelId };
81
+ if (level.isPending || level.isFirstPending) return { state: "loading" };
82
+ if (!level.items.some((item) => idOf(item) === id)) {
83
+ return level.total > level.items.length ? { state: "more", levelId, shown: level.items.length } : { state: "missing", id };
84
+ }
85
+ levelId = id;
86
+ }
87
+ return { state: "found" };
88
+ }
89
+
90
+ export {
91
+ lazyTreePages,
92
+ joinLazyTreePages,
93
+ lazyTreeLevels,
94
+ lazyTreeClosingRows,
95
+ buildLazyTree,
96
+ lazyTreeRevealStep
97
+ };
98
+ //# sourceMappingURL=chunk-ZM3U3HR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lazyTree.ts"],"sourcesContent":["/**\n * Lazy tree -- the loading side of a tree too large to fetch at once: a level is\n * fetched when its node opens, a page at a time; a level larger than what is\n * loaded ends in a \"more\" row, an empty one in an \"empty\" row. Pure functions,\n * no fetching: `useLazyTree` (in `./hooks`) connects them to TanStack Query; an\n * application fetching otherwise uses them directly.\n *\n * The nodes built have the shape of sonnet-ui's `TreeNode`, so they go to\n * `TreeView` as they are.\n */\n\n/** A page of a level as fetched: its items in order, and the level's total. */\nexport interface LazyTreePage<TItem> {\n items: TItem[];\n total: number;\n}\n\n/** The state of one page's fetch -- a TanStack Query result is one. */\nexport interface LazyTreePageState<TPage> {\n data?: TPage;\n isPending: boolean;\n isError: boolean;\n}\n\n/** A level as fetched so far: its pages, put together. */\nexport interface LazyTreeLevel<TItem> {\n items: TItem[];\n total: number;\n /** A page is loading (a further one shows as the rows already there). */\n isPending: boolean;\n /** Nothing to show yet: the first page is loading. */\n isFirstPending: boolean;\n isError: boolean;\n}\n\n/** A page to fetch: of which level, which one, and with what parameters. */\nexport interface LazyTreePageRequest<TParams> {\n levelId: string;\n page: number;\n params: TParams;\n}\n\n/**\n * A row of the tree: an item's node, or a row closing a level -- \"more\" while\n * items remain to load (`loading` while the next page is on its way), \"empty\"\n * for a level without any, or one the application adds (\"extra\", e.g. a switch\n * of what the level shows).\n */\nexport type LazyTreeEntry<TData, TExtra = never> =\n | { row: \"node\"; data: TData }\n | {\n row: \"more\";\n levelId: string;\n shown: number;\n total: number;\n loading: boolean;\n }\n | { row: \"empty\"; levelId: string }\n | { row: \"extra\"; levelId: string; extra: TExtra };\n\n/** A node as sonnet-ui's `TreeView` takes it. */\nexport interface LazyTreeNode<TEntry> {\n id: string;\n data: TEntry;\n hasChildren: boolean;\n children?: LazyTreeNode<TEntry>[];\n isLoading?: boolean;\n isError?: boolean;\n}\n\n/** What an item becomes in the tree; its id also names the level below it. */\nexport interface LazyTreeItemNode<TData> {\n id: string;\n data: TData;\n hasChildren: boolean;\n}\n\nexport interface LazyTreeBuildOptions<TItem, TData, TExtra> {\n rootId: string;\n expandedIds: ReadonlySet<string>;\n /** The level of a level's id, as fetched so far. */\n levelOf: (levelId: string) => LazyTreeLevel<TItem>;\n toNode: (item: TItem) => LazyTreeItemNode<TData>;\n /** Rows the application closes a level with, after \"more\". */\n extraRows?: (level: LazyTreeLevel<TItem>, levelId: string) => TExtra[];\n /** Whether an empty level gets an \"empty\" row (default: every one). */\n showEmpty?: (levelId: string) => boolean;\n}\n\n/**\n * The pages to fetch: the top level, then each opened node's -- its first page\n * and as many more as shown (`morePages`, by level id).\n */\nexport function lazyTreePages<TParams>(\n rootId: string,\n expandedIds: Iterable<string>,\n morePages: Readonly<Record<string, number>>,\n pageParams: (levelId: string, page: number) => TParams,\n): LazyTreePageRequest<TParams>[] {\n const levels = [rootId, ...[...expandedIds].filter((id) => id !== rootId)];\n return levels.flatMap((levelId) =>\n Array.from({ length: 1 + (morePages[levelId] ?? 0) }, (_, page) => ({\n levelId,\n page,\n params: pageParams(levelId, page),\n })),\n );\n}\n\n/** A level's pages put together, in order; the total is the latest page's. */\nexport function joinLazyTreePages<TItem>(\n pages: LazyTreePageState<LazyTreePage<TItem>>[],\n): LazyTreeLevel<TItem> {\n const loaded = pages.flatMap((p) => (p.data ? [p.data] : []));\n return {\n items: loaded.flatMap((p) => p.items),\n total: loaded.at(-1)?.total ?? 0,\n isPending: pages.some((p) => p.isPending),\n isFirstPending: pages[0]?.isPending ?? true,\n isError: pages.some((p) => p.isError),\n };\n}\n\n/** Each level's pages put together, by level id, from the requests and their states. */\nexport function lazyTreeLevels<TItem>(\n requests: LazyTreePageRequest<unknown>[],\n states: LazyTreePageState<LazyTreePage<TItem>>[],\n): (levelId: string) => LazyTreeLevel<TItem> {\n const byLevel = new Map<string, LazyTreePageState<LazyTreePage<TItem>>[]>();\n requests.forEach((r, i) => {\n const list = byLevel.get(r.levelId) ?? [];\n list.push(states[i]);\n byLevel.set(r.levelId, list);\n });\n return (levelId) => joinLazyTreePages(byLevel.get(levelId) ?? []);\n}\n\n/**\n * The rows closing a level: \"empty\", \"more\", then the application's own.\n *\n * The \"more\" row stays while the page it asked for loads (`loading`), and its\n * id names how many items it follows: when the page lands, the row is a new\n * one at the end, and a TreeView passes the focus it had to the first item of\n * the page, now in its place.\n */\nexport function lazyTreeClosingRows<TItem, TData, TExtra>(\n level: LazyTreeLevel<TItem>,\n levelId: string,\n options: Pick<\n LazyTreeBuildOptions<TItem, TData, TExtra>,\n \"extraRows\" | \"showEmpty\"\n >,\n): LazyTreeNode<LazyTreeEntry<TData, TExtra>>[] {\n if (level.isFirstPending) return [];\n const row = (\n id: string,\n data: LazyTreeEntry<TData, TExtra>,\n ): LazyTreeNode<LazyTreeEntry<TData, TExtra>> => ({\n id,\n data,\n hasChildren: false,\n });\n const rows: LazyTreeNode<LazyTreeEntry<TData, TExtra>>[] = [];\n if (level.total === 0 && (options.showEmpty?.(levelId) ?? true))\n rows.push(row(`empty:${levelId}`, { row: \"empty\", levelId }));\n if (level.total > level.items.length)\n rows.push(\n row(`more:${levelId}:${level.items.length}`, {\n row: \"more\",\n levelId,\n shown: level.items.length,\n total: level.total,\n loading: level.isPending,\n }),\n );\n (options.extraRows?.(level, levelId) ?? []).forEach((extra, i) =>\n rows.push(row(`extra${i}:${levelId}`, { row: \"extra\", levelId, extra })),\n );\n return rows;\n}\n\n/** The tree: the top level's nodes, each opened one with its level below it. */\nexport function buildLazyTree<TItem, TData, TExtra = never>(\n options: LazyTreeBuildOptions<TItem, TData, TExtra>,\n): LazyTreeNode<LazyTreeEntry<TData, TExtra>>[] {\n const { expandedIds, levelOf, toNode } = options;\n const nodesOf = (\n level: LazyTreeLevel<TItem>,\n levelId: string,\n ): LazyTreeNode<LazyTreeEntry<TData, TExtra>>[] => [\n ...level.items.map(build),\n ...lazyTreeClosingRows<TItem, TData, TExtra>(level, levelId, options),\n ];\n const build = (item: TItem): LazyTreeNode<LazyTreeEntry<TData, TExtra>> => {\n const { id, data, hasChildren } = toNode(item);\n const level = hasChildren && expandedIds.has(id) ? levelOf(id) : undefined;\n return {\n id,\n data: { row: \"node\", data },\n hasChildren,\n isLoading: level?.isFirstPending,\n isError: level?.isError,\n children: level && !level.isFirstPending ? nodesOf(level, id) : undefined,\n };\n };\n return nodesOf(levelOf(options.rootId), options.rootId);\n}\n\n/**\n * What revealing a node still needs -- e.g. a search result picked in a tree\n * whose levels load in pages: \"loading\" while a level on its path loads;\n * \"more\" when the next node of the path lies beyond the pages loaded of its\n * level (show another one: `shown` changes with each, so an effect can key on\n * it); \"found\" when the node is in the tree; \"missing\" when a complete level\n * lacks the next node; \"error\" when a level on the path failed.\n */\nexport type LazyTreeRevealStep =\n | { state: \"loading\" }\n | { state: \"more\"; levelId: string; shown: number }\n | { state: \"found\" }\n | { state: \"missing\"; id: string }\n | { state: \"error\"; levelId: string };\n\n/**\n * The next step of revealing the node at the end of `path` (ids from the top\n * level down to the node). The application opens the path's ancestors\n * (`expandedIds`); this says which level needs another page, until each node\n * of the path is in its level.\n */\nexport function lazyTreeRevealStep<TItem>(\n rootId: string,\n path: readonly string[],\n levelOf: (levelId: string) => LazyTreeLevel<TItem>,\n idOf: (item: TItem) => string,\n): LazyTreeRevealStep {\n let levelId = rootId;\n for (const id of path) {\n const level = levelOf(levelId);\n if (level.isError) return { state: \"error\", levelId };\n // Pending, or not asked for yet (its node opened only now).\n if (level.isPending || level.isFirstPending) return { state: \"loading\" };\n if (!level.items.some((item) => idOf(item) === id)) {\n return level.total > level.items.length\n ? { state: \"more\", levelId, shown: level.items.length }\n : { state: \"missing\", id };\n }\n levelId = id;\n }\n return { state: \"found\" };\n}\n"],"mappings":"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","names":[]}
@@ -148,4 +148,5 @@ declare function useValidationDisplay(override?: SonnetGlobalConfig["validationD
148
148
  /** Resolve whether Save is available on an invalid form. Defaults to `"reveal"`. */
149
149
  declare function useSaveWhenInvalid(override?: SonnetGlobalConfig["saveWhenInvalid"]): NonNullable<SonnetGlobalConfig["saveWhenInvalid"]>;
150
150
 
151
- export { type SaveWhenInvalid, type SonnetComponentDefaults, type SonnetConfig, SonnetConfigContext, SonnetConfigProvider, type SonnetConfigProviderProps, type SonnetGlobalConfig, type ValidationDisplay, getBrowserLocale, useComponentDefaults, useGlobalSetting, useLocale, useSaveWhenInvalid, useValidationDisplay };
151
+ export { SonnetConfigContext, SonnetConfigProvider, getBrowserLocale, useComponentDefaults, useGlobalSetting, useLocale, useSaveWhenInvalid, useValidationDisplay };
152
+ export type { SaveWhenInvalid, SonnetComponentDefaults, SonnetConfig, SonnetConfigProviderProps, SonnetGlobalConfig, ValidationDisplay };
@@ -1,5 +1,5 @@
1
- import { b as EntityFetcherFactory, a as EntityOption, E as EntityOptionFetcher } from '../types-DJ4hb97y.js';
2
- export { c as EntityFetchParams, G as GraphqlFetcherConfig, R as RestFetcherConfig } from '../types-DJ4hb97y.js';
1
+ import { E as EntityFetcherFactory, a as EntityOption, b as EntityOptionFetcher } from '../types-BiFVnfqr.js';
2
+ export { c as EntityFetchParams, G as GraphqlFetcherConfig, R as RestFetcherConfig } from '../types-BiFVnfqr.js';
3
3
  import * as React from 'react';
4
4
 
5
5
  declare const FetcherContext: React.Context<EntityFetcherFactory | null>;
@@ -1,10 +1,17 @@
1
- import { E as EntityOptionFetcher, a as EntityOption } from '../types-DJ4hb97y.js';
1
+ import { EntityOptionFetcher, EntityOption } from '../entityOptions/index.js';
2
+ import { QueryKey } from '@tanstack/react-query';
3
+ import { LazyTreePage, LazyTreeBuildOptions, LazyTreeNode, LazyTreeEntry, LazyTreeLevel, LazyTreeRevealStep } from '../index.js';
2
4
 
3
5
  interface UseClipboardOptions {
4
6
  timeout?: number;
5
7
  }
6
8
  interface UseClipboardReturn {
7
9
  copy: (text: string) => Promise<void>;
10
+ /**
11
+ * The text on the clipboard; null when it cannot be read (the browser may
12
+ * ask the user first, or refuse) -- `error` then says why.
13
+ */
14
+ paste: () => Promise<string | null>;
8
15
  copied: boolean;
9
16
  error: Error | null;
10
17
  reset: () => void;
@@ -16,7 +23,7 @@ interface UseClipboardReturn {
16
23
  *
17
24
  * @param options - Configuration options
18
25
  * @param options.timeout - Time in ms before copied state resets (default: 2000)
19
- * @returns Object with copy function, copied state, error, and reset function
26
+ * @returns Object with copy and paste functions, copied state, error, and reset function
20
27
  *
21
28
  * @example
22
29
  * ```tsx
@@ -238,6 +245,69 @@ interface UseEntityOptionsReturn {
238
245
  }
239
246
  declare function useEntityOptions({ fetcher, queryKey, mode, debounceMs, minChars, limit, enabled, }: UseEntityOptionsConfig): UseEntityOptionsReturn;
240
247
 
248
+ /**
249
+ * useLazyTree -- a tree loaded per level and in pages, on TanStack Query.
250
+ *
251
+ * Transport-agnostic: receives the parameters of a level's page, its query key
252
+ * and a fetcher; returns the nodes for sonnet-ui's TreeView -- each opened node
253
+ * with its level below it, a level closed by a "more" row while items remain,
254
+ * an "empty" row when it has none, and rows the application adds. Expansion and
255
+ * the pages shown beyond the first stay with the application (controlled), so
256
+ * they can live in its store or URL.
257
+ *
258
+ * The bookkeeping is in `../lazyTree`, usable without TanStack Query.
259
+ */
260
+
261
+ interface UseLazyTreeConfig<TItem, TData, TParams, TPage = LazyTreePage<TItem>, TExtra = never> extends Omit<LazyTreeBuildOptions<TItem, TData, TExtra>, "levelOf"> {
262
+ /** Pages shown of a level beyond its first, by level id. */
263
+ morePages: Readonly<Record<string, number>>;
264
+ /** The parameters of a level's page. */
265
+ pageParams: (levelId: string, page: number) => TParams;
266
+ /** The query key of a page. */
267
+ queryKey: (params: TParams) => QueryKey;
268
+ fetchPage: (params: TParams) => Promise<TPage>;
269
+ /**
270
+ * A page as fetched to its items and total -- when the API's shape differs;
271
+ * the cache keeps the page as fetched.
272
+ */
273
+ select?: (page: TPage) => LazyTreePage<TItem>;
274
+ }
275
+ interface UseLazyTreeReturn<TItem, TData, TExtra> {
276
+ /** The nodes for TreeView. */
277
+ nodes: LazyTreeNode<LazyTreeEntry<TData, TExtra>>[];
278
+ /** The top level as fetched so far -- for its loading and error states. */
279
+ root: LazyTreeLevel<TItem>;
280
+ /** A level as fetched so far, by its id. */
281
+ levelOf: (levelId: string) => LazyTreeLevel<TItem>;
282
+ /** The next step of revealing the node at the end of a path (see useLazyTreeReveal). */
283
+ revealStep: (path: readonly string[]) => LazyTreeRevealStep;
284
+ }
285
+ declare function useLazyTree<TItem, TData, TParams, TPage = LazyTreePage<TItem>, TExtra = never>(config: UseLazyTreeConfig<TItem, TData, TParams, TPage, TExtra>): UseLazyTreeReturn<TItem, TData, TExtra>;
286
+
287
+ /**
288
+ * useLazyTreeReveal -- reveal a node of a lazy tree whose levels load in pages,
289
+ * e.g. a search result picked: shows further pages of each level on the node's
290
+ * path until the next node of the path is loaded, then stops.
291
+ *
292
+ * The application opens the path's ancestors itself (its `expandedIds`), as it
293
+ * calls `reveal`; and marks the node (TreeView `activeId`), which scrolls it into
294
+ * view once it is shown.
295
+ */
296
+
297
+ interface UseLazyTreeRevealConfig {
298
+ /** From useLazyTree. */
299
+ revealStep: (path: readonly string[]) => LazyTreeRevealStep;
300
+ /** Show another page of a level -- what the "more" row does. */
301
+ showMore: (levelId: string) => void;
302
+ }
303
+ interface UseLazyTreeRevealReturn {
304
+ /** Reveal the node at the end of `path` (ids from the top level down to it). */
305
+ reveal: (path: readonly string[]) => void;
306
+ /** Where the current reveal stands; undefined when none is under way. */
307
+ step: LazyTreeRevealStep | undefined;
308
+ }
309
+ declare function useLazyTreeReveal({ revealStep, showMore, }: UseLazyTreeRevealConfig): UseLazyTreeRevealReturn;
310
+
241
311
  /**
242
312
  * Shared types for React Query hooks.
243
313
  *
@@ -250,4 +320,31 @@ interface QueryHookOptions {
250
320
  enabled?: boolean;
251
321
  }
252
322
 
253
- export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, type UseTreeExpansionReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, useMediaQuery, useResizablePanel, useTreeExpansion };
323
+ /** The colour theme: the sonnet tokens' light set, or their dark one. */
324
+ type Theme = "light" | "dark";
325
+ /** Switch the theme, and remember it for the next visit. */
326
+ declare function setTheme(theme: Theme): void;
327
+ /**
328
+ * Apply the theme the reader chose last, else the system's. Call once before
329
+ * the app renders, so the first paint is already in that theme.
330
+ *
331
+ * @example
332
+ * initTheme();
333
+ * ReactDOM.createRoot(root).render(<App />);
334
+ */
335
+ declare function initTheme(): void;
336
+ /**
337
+ * The current theme, re-rendering when it changes, and the way to switch it.
338
+ * Follows the "dark" class on <html>, whoever sets it.
339
+ *
340
+ * @example
341
+ * const { theme, setTheme } = useTheme();
342
+ * setTheme(theme === "dark" ? "light" : "dark");
343
+ */
344
+ declare function useTheme(): {
345
+ theme: Theme;
346
+ setTheme: (theme: Theme) => void;
347
+ };
348
+
349
+ export { initTheme, setTheme, useClipboard, useDisclosure, useEntityOptions, useFormState, useLazyTree, useLazyTreeReveal, useMediaQuery, useResizablePanel, useTheme, useTreeExpansion };
350
+ export type { EntitySelectMode, QueryHookOptions, Theme, UseClipboardOptions, UseClipboardReturn, UseEntityOptionsConfig, UseEntityOptionsReturn, UseLazyTreeConfig, UseLazyTreeReturn, UseLazyTreeRevealConfig, UseLazyTreeRevealReturn, UseTreeExpansionReturn };
@@ -1,3 +1,9 @@
1
+ import {
2
+ buildLazyTree,
3
+ lazyTreeLevels,
4
+ lazyTreePages,
5
+ lazyTreeRevealStep
6
+ } from "../chunk-ZM3U3HR2.js";
1
7
  import {
2
8
  isEqual
3
9
  } from "../chunk-XCCOEUVC.js";
@@ -23,11 +29,21 @@ function useClipboard({
23
29
  },
24
30
  [timeout]
25
31
  );
32
+ const paste = useCallback(async () => {
33
+ try {
34
+ const text = await navigator.clipboard.readText();
35
+ setError(null);
36
+ return text;
37
+ } catch (err) {
38
+ setError(err instanceof Error ? err : new Error(String(err)));
39
+ return null;
40
+ }
41
+ }, []);
26
42
  const reset = useCallback(() => {
27
43
  setCopied(false);
28
44
  setError(null);
29
45
  }, []);
30
- return { copy, copied, error, reset };
46
+ return { copy, paste, copied, error, reset };
31
47
  }
32
48
 
33
49
  // src/hooks/useTreeExpansion.ts
@@ -232,13 +248,119 @@ function useEntityOptions({
232
248
  const options = data ?? [];
233
249
  return { options, isLoading, isError, search, setSearch, submit };
234
250
  }
251
+
252
+ // src/hooks/useLazyTree.ts
253
+ import { useQueries } from "@tanstack/react-query";
254
+ function useLazyTree(config) {
255
+ const { rootId, expandedIds, morePages, pageParams, queryKey, fetchPage } = config;
256
+ const select = config.select ?? ((page) => page);
257
+ const requests = lazyTreePages(rootId, expandedIds, morePages, pageParams);
258
+ const states = useQueries({
259
+ queries: requests.map((r) => ({
260
+ queryKey: queryKey(r.params),
261
+ queryFn: () => fetchPage(r.params),
262
+ select
263
+ }))
264
+ });
265
+ const levelOf = lazyTreeLevels(requests, states);
266
+ return {
267
+ nodes: buildLazyTree({ ...config, levelOf }),
268
+ root: levelOf(rootId),
269
+ levelOf,
270
+ revealStep: (path) => lazyTreeRevealStep(
271
+ rootId,
272
+ path,
273
+ levelOf,
274
+ (item) => config.toNode(item).id
275
+ )
276
+ };
277
+ }
278
+
279
+ // src/hooks/useLazyTreeReveal.ts
280
+ import { useCallback as useCallback7, useEffect as useEffect4, useState as useState8 } from "react";
281
+ function moreOf(step) {
282
+ return step?.state === "more" ? { levelId: step.levelId, shown: step.shown } : {};
283
+ }
284
+ var SETTLED = /* @__PURE__ */ new Set([
285
+ "found",
286
+ "missing",
287
+ "error"
288
+ ]);
289
+ function useLazyTreeReveal({
290
+ revealStep,
291
+ showMore
292
+ }) {
293
+ const [path, setPath] = useState8(void 0);
294
+ const step = path ? revealStep(path) : void 0;
295
+ const { levelId: moreLevel, shown: moreShown } = moreOf(step);
296
+ useEffect4(() => {
297
+ if (moreLevel !== void 0) showMore(moreLevel);
298
+ }, [moreLevel, moreShown, showMore]);
299
+ const settled = step !== void 0 && SETTLED.has(step.state);
300
+ useEffect4(() => {
301
+ if (settled) setPath(void 0);
302
+ }, [settled]);
303
+ const reveal = useCallback7((next) => setPath(next), []);
304
+ return { reveal, step };
305
+ }
306
+
307
+ // src/hooks/useTheme.ts
308
+ import { useSyncExternalStore } from "react";
309
+ var DARK_CLASS = "dark";
310
+ var STORAGE_KEY = "sonnet-theme";
311
+ function currentTheme() {
312
+ return document.documentElement.classList.contains(DARK_CLASS) ? "dark" : "light";
313
+ }
314
+ function storedTheme() {
315
+ try {
316
+ const value = localStorage.getItem(STORAGE_KEY);
317
+ return value === "dark" || value === "light" ? value : void 0;
318
+ } catch {
319
+ return void 0;
320
+ }
321
+ }
322
+ function applyTheme(theme) {
323
+ document.documentElement.classList.toggle(DARK_CLASS, theme === "dark");
324
+ }
325
+ function setTheme(theme) {
326
+ applyTheme(theme);
327
+ try {
328
+ localStorage.setItem(STORAGE_KEY, theme);
329
+ } catch {
330
+ }
331
+ }
332
+ function initTheme() {
333
+ const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
334
+ applyTheme(storedTheme() ?? (prefersDark ? "dark" : "light"));
335
+ }
336
+ function subscribe(onChange) {
337
+ const observer = new MutationObserver(onChange);
338
+ observer.observe(document.documentElement, {
339
+ attributes: true,
340
+ attributeFilter: ["class"]
341
+ });
342
+ return () => observer.disconnect();
343
+ }
344
+ function useTheme() {
345
+ const theme = useSyncExternalStore(
346
+ subscribe,
347
+ currentTheme,
348
+ () => "light"
349
+ );
350
+ return { theme, setTheme };
351
+ }
235
352
  export {
353
+ initTheme,
354
+ setTheme,
236
355
  useClipboard,
237
356
  useDisclosure,
238
357
  useEntityOptions,
239
358
  useFormState,
359
+ useLazyTree,
360
+ useLazyTreeReveal,
240
361
  useMediaQuery,
241
362
  useResizablePanel,
363
+ useTheme,
242
364
  useTreeExpansion
243
365
  };
244
366
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useClipboard.ts","../../src/hooks/useTreeExpansion.ts","../../src/hooks/useDisclosure.ts","../../src/hooks/useMediaQuery.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","import { useState, useEffect } from \"react\";\n\n/**\n * Subscribe to a CSS media query and re-render when it changes.\n *\n * SSR-safe: returns `false` on the server and during the first client render\n * when `window.matchMedia` is unavailable, then syncs on mount.\n *\n * @param query - a media query string, e.g. \"(max-width: 1023px)\"\n * @returns whether the query currently matches\n *\n * @example\n * ```tsx\n * const isTablet = useMediaQuery(\"(max-width: 1023px)\");\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return;\n const mql = window.matchMedia(query);\n const onChange = () => setMatches(mql.matches);\n // Sync immediately in case the query changed between render and effect.\n onChange();\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, [query]);\n\n return matches;\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":";;;;;AAAA,SAAS,UAAU,mBAAmB;AA+B/B,SAAS,aAAa;AAAA,EAC3B,UAAU;AACZ,IAAyB,CAAC,GAAuB;AAC/C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,OAAO;AAAA,IACX,OAAO,SAAiB;AACtB,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,IAAI;AACxC,kBAAU,IAAI;AACd,iBAAS,IAAI;AAGb,mBAAW,MAAM,UAAU,KAAK,GAAG,OAAO;AAAA,MAC5C,SAAS,KAAK;AACZ,iBAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAC5D,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,cAAU,KAAK;AACf,aAAS,IAAI;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,MAAM,QAAQ,OAAO,MAAM;AACtC;;;AC5DA,SAAS,YAAAA,WAAU,eAAAC,oBAAmB;AAS/B,SAAS,mBAA2C;AACzD,QAAM,CAAC,aAAa,cAAc,IAAID,UAAsB,oBAAI,IAAI,CAAC;AAErE,QAAM,WAAWC,aAAY,CAAC,OAAe;AAC3C,mBAAe,CAAC,SAAS,oBAAI,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;AAAA,EACjD,GAAG,CAAC,CAAC;AAEL,QAAM,aAAaA,aAAY,CAAC,OAAe;AAC7C,mBAAe,CAAC,SAAS;AACvB,YAAM,OAAO,IAAI,IAAI,IAAI;AACzB,WAAK,OAAO,EAAE;AACd,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,aAAa,UAAU,WAAW;AAC7C;;;ACzBA,SAAS,YAAAC,WAAU,eAAAC,oBAAmB;AA0B/B,SAAS,cACd,eAAe,OAC0B;AACzC,QAAM,CAAC,QAAQ,SAAS,IAAID,UAAS,YAAY;AAEjD,QAAM,OAAOC,aAAY,MAAM,UAAU,IAAI,GAAG,CAAC,CAAC;AAClD,QAAM,QAAQA,aAAY,MAAM,UAAU,KAAK,GAAG,CAAC,CAAC;AACpD,QAAM,SAASA,aAAY,MAAM,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC;AAE/D,SAAO,CAAC,QAAQ,EAAE,MAAM,OAAO,OAAO,CAAC;AACzC;;;ACpCA,SAAS,YAAAC,WAAU,iBAAiB;AAgB7B,SAAS,cAAc,OAAwB;AACpD,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAkB,MAAM;AACpD,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO;AAChE,WAAO,OAAO,WAAW,KAAK,EAAE;AAAA,EAClC,CAAC;AAED,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY;AACzD,UAAM,MAAM,OAAO,WAAW,KAAK;AACnC,UAAM,WAAW,MAAM,WAAW,IAAI,OAAO;AAE7C,aAAS;AACT,QAAI,iBAAiB,UAAU,QAAQ;AACvC,WAAO,MAAM,IAAI,oBAAoB,UAAU,QAAQ;AAAA,EACzD,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO;AACT;;;AC1BA,SAAS,YAAAC,WAAU,QAAQ,eAAAC,cAAa,aAAAC,kBAAiB;AAoClD,SAAS,kBACd,UAAoC,CAAC,GACZ;AACzB,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,YAAY;AAAA,EACd,IAAI;AAEJ,QAAM,CAAC,YAAY,aAAa,IAAIF,UAAiB,YAAY;AACjE,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,YAAY,OAAO,CAAC;AAC1B,QAAM,gBAAgB,OAAO,CAAC;AAE9B,QAAM,QAAQC;AAAA,IACZ,CAAC,MAAc,KAAK,IAAI,KAAK,IAAI,GAAG,QAAQ,GAAG,QAAQ;AAAA,IACvD,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,QAAM,eAAeA,aAAY,MAAM;AACrC,QAAI,CAAC,YAAY,QAAS;AAC1B,gBAAY,UAAU;AACtB,aAAS,KAAK,UAAU,OAAO,aAAa;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoBA;AAAA,IACxB,CAAC,MAAoB;AACnB,UAAI,CAAC,YAAY,QAAS;AAC1B,YAAM,QAAQ,EAAE,UAAU,UAAU;AAGpC,YAAM,WACJ,cAAc,eACV,cAAc,UAAU,QACxB,cAAc,UAAU;AAC9B,oBAAc,MAAM,QAAQ,CAAC;AAAA,IAC/B;AAAA,IACA,CAAC,OAAO,SAAS;AAAA,EACnB;AAEA,QAAM,oBAAoBA;AAAA,IACxB,CAAC,MAA0B;AACzB,kBAAY,UAAU;AACtB,gBAAU,UAAU,EAAE;AACtB,oBAAc,UAAU;AACxB,eAAS,KAAK,UAAU,IAAI,aAAa;AAAA,IAC3C;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,oBAAoBA;AAAA,IACxB,MAAM,cAAc,YAAY;AAAA,IAChC,CAAC,YAAY;AAAA,EACf;AAEA,EAAAC,WAAU,MAAM;AACd,WAAO,iBAAiB,eAAe,iBAAiB;AACxD,WAAO,iBAAiB,aAAa,YAAY;AACjD,WAAO,iBAAiB,iBAAiB,YAAY;AAErD,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,YAAY,YAAY,SAAS;AAC7C,sBAAc,cAAc,OAAO;AACnC,qBAAa;AAAA,MACf;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,SAAS;AAE5C,WAAO,MAAM;AACX,aAAO,oBAAoB,eAAe,iBAAiB;AAC3D,aAAO,oBAAoB,aAAa,YAAY;AACpD,aAAO,oBAAoB,iBAAiB,YAAY;AACxD,aAAO,oBAAoB,WAAW,SAAS;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,mBAAmB,YAAY,CAAC;AAEpC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,IACnB;AAAA,EACF;AACF;;;AC3HA,SAAS,YAAAC,WAAU,eAAAC,cAAa,eAAe;AA8DxC,SAAS,aAAgB,aAAuC;AACrE,QAAM,CAAC,MAAM,YAAY,IAAIC,UAAY,WAAW;AACpD,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAY,WAAW;AAKvD,QAAM,UAAU,QAAQ,MAAM;AAC5B,WAAO,CAAC,QAAQ,MAAM,QAAQ;AAAA,EAChC,GAAG,CAAC,MAAM,QAAQ,CAAC;AAKnB,QAAM,UAAUC,aAAY,CAAC,YAAkC;AAC7D,iBAAa,OAAO;AAAA,EACtB,GAAG,CAAC,CAAC;AAKL,QAAM,WAAWA,aAAY,CAAoB,OAAU,UAAgB;AACzE,iBAAa,CAAC,UAAU;AAAA,MACtB,GAAG;AAAA,MACH,CAAC,KAAK,GAAG;AAAA,IACX,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAML,QAAM,QAAQA,aAAY,CAAC,YAAe;AACxC,iBAAa,OAAO;AACpB,gBAAY,OAAO;AAAA,EACrB,GAAG,CAAC,CAAC;AAML,QAAM,UAAUA,aAAY,MAAM;AAChC,iBAAa,QAAQ;AAAA,EACvB,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AChHA,SAAS,YAAAC,WAAU,aAAAC,YAAW,eAAAC,oBAAmB;AACjD,SAAS,gBAAgB;AA4CzB,SAAS,eACP,SACA,MACA,cACA,UACA,iBACS;AACT,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,SAAS,YAAa,QAAO,aAAa,UAAU;AACxD,MAAI,SAAS,WAAY,QAAO,oBAAoB;AACpD,SAAO;AACT;AAEO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,aAAa;AAAA,EACb,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU;AACZ,GAAmD;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAIF,UAAS,EAAE;AAGvC,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,UAAS,EAAE;AAEzD,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,UAAwB,IAAI;AAE1E,EAAAC,WAAU,MAAM;AACd,QAAI,SAAS,WAAY;AACzB,UAAM,QAAQ,WAAW,MAAM,mBAAmB,MAAM,GAAG,UAAU;AACrE,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,QAAQ,YAAY,IAAI,CAAC;AAE7B,QAAM,SAASC,aAAY,MAAM;AAC/B,uBAAmB,MAAM;AAAA,EAC3B,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,eACJ,SAAS,aAAc,mBAAmB,KAAM;AAElD,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,SAAS;AAAA,IAC5C,UAAU,CAAC,UAAU,WAAW,EAAE,QAAQ,cAAc,MAAM,CAAC;AAAA,IAC/D,SAAS,MAAM,QAAQ,EAAE,QAAQ,gBAAgB,QAAW,MAAM,CAAC;AAAA,IACnE,SAAS;AAAA,IACT,WAAW,IAAI,KAAK;AAAA,EACtB,CAAC;AAED,QAAM,UAA0B,QAAQ,CAAC;AAEzC,SAAO,EAAE,SAAS,WAAW,SAAS,QAAQ,WAAW,OAAO;AAClE;","names":["useState","useCallback","useState","useCallback","useState","useState","useCallback","useEffect","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/useMediaQuery.ts","../../src/hooks/useResizablePanel.ts","../../src/hooks/useFormState.ts","../../src/hooks/useEntityOptions.ts","../../src/hooks/useLazyTree.ts","../../src/hooks/useLazyTreeReveal.ts","../../src/hooks/useTheme.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 /**\n * The text on the clipboard; null when it cannot be read (the browser may\n * ask the user first, or refuse) -- `error` then says why.\n */\n paste: () => Promise<string | null>;\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 and paste functions, 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 paste = useCallback(async (): Promise<string | null> => {\n try {\n const text = await navigator.clipboard.readText();\n setError(null);\n return text;\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n return null;\n }\n }, []);\n\n const reset = useCallback(() => {\n setCopied(false);\n setError(null);\n }, []);\n\n return { copy, paste, 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","import { useState, useEffect } from \"react\";\n\n/**\n * Subscribe to a CSS media query and re-render when it changes.\n *\n * SSR-safe: returns `false` on the server and during the first client render\n * when `window.matchMedia` is unavailable, then syncs on mount.\n *\n * @param query - a media query string, e.g. \"(max-width: 1023px)\"\n * @returns whether the query currently matches\n *\n * @example\n * ```tsx\n * const isTablet = useMediaQuery(\"(max-width: 1023px)\");\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return;\n const mql = window.matchMedia(query);\n const onChange = () => setMatches(mql.matches);\n // Sync immediately in case the query changed between render and effect.\n onChange();\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, [query]);\n\n return matches;\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","/**\n * useLazyTree -- a tree loaded per level and in pages, on TanStack Query.\n *\n * Transport-agnostic: receives the parameters of a level's page, its query key\n * and a fetcher; returns the nodes for sonnet-ui's TreeView -- each opened node\n * with its level below it, a level closed by a \"more\" row while items remain,\n * an \"empty\" row when it has none, and rows the application adds. Expansion and\n * the pages shown beyond the first stay with the application (controlled), so\n * they can live in its store or URL.\n *\n * The bookkeeping is in `../lazyTree`, usable without TanStack Query.\n */\n\nimport { useQueries, type QueryKey } from \"@tanstack/react-query\";\nimport {\n buildLazyTree,\n lazyTreeLevels,\n lazyTreePages,\n lazyTreeRevealStep,\n type LazyTreeBuildOptions,\n type LazyTreeEntry,\n type LazyTreeLevel,\n type LazyTreeNode,\n type LazyTreePage,\n type LazyTreeRevealStep,\n} from \"../lazyTree\";\n\nexport interface UseLazyTreeConfig<\n TItem,\n TData,\n TParams,\n TPage = LazyTreePage<TItem>,\n TExtra = never,\n> extends Omit<LazyTreeBuildOptions<TItem, TData, TExtra>, \"levelOf\"> {\n /** Pages shown of a level beyond its first, by level id. */\n morePages: Readonly<Record<string, number>>;\n /** The parameters of a level's page. */\n pageParams: (levelId: string, page: number) => TParams;\n /** The query key of a page. */\n queryKey: (params: TParams) => QueryKey;\n fetchPage: (params: TParams) => Promise<TPage>;\n /**\n * A page as fetched to its items and total -- when the API's shape differs;\n * the cache keeps the page as fetched.\n */\n select?: (page: TPage) => LazyTreePage<TItem>;\n}\n\nexport interface UseLazyTreeReturn<TItem, TData, TExtra> {\n /** The nodes for TreeView. */\n nodes: LazyTreeNode<LazyTreeEntry<TData, TExtra>>[];\n /** The top level as fetched so far -- for its loading and error states. */\n root: LazyTreeLevel<TItem>;\n /** A level as fetched so far, by its id. */\n levelOf: (levelId: string) => LazyTreeLevel<TItem>;\n /** The next step of revealing the node at the end of a path (see useLazyTreeReveal). */\n revealStep: (path: readonly string[]) => LazyTreeRevealStep;\n}\n\nexport function useLazyTree<\n TItem,\n TData,\n TParams,\n TPage = LazyTreePage<TItem>,\n TExtra = never,\n>(\n config: UseLazyTreeConfig<TItem, TData, TParams, TPage, TExtra>,\n): UseLazyTreeReturn<TItem, TData, TExtra> {\n const { rootId, expandedIds, morePages, pageParams, queryKey, fetchPage } =\n config;\n const select =\n config.select ?? ((page: TPage) => page as unknown as LazyTreePage<TItem>);\n const requests = lazyTreePages(rootId, expandedIds, morePages, pageParams);\n const states = useQueries({\n queries: requests.map((r) => ({\n queryKey: queryKey(r.params),\n queryFn: () => fetchPage(r.params),\n select,\n })),\n });\n const levelOf = lazyTreeLevels(requests, states);\n return {\n nodes: buildLazyTree({ ...config, levelOf }),\n root: levelOf(rootId),\n levelOf,\n revealStep: (path) =>\n lazyTreeRevealStep(\n rootId,\n path,\n levelOf,\n (item) => config.toNode(item).id,\n ),\n };\n}\n","/**\n * useLazyTreeReveal -- reveal a node of a lazy tree whose levels load in pages,\n * e.g. a search result picked: shows further pages of each level on the node's\n * path until the next node of the path is loaded, then stops.\n *\n * The application opens the path's ancestors itself (its `expandedIds`), as it\n * calls `reveal`; and marks the node (TreeView `activeId`), which scrolls it into\n * view once it is shown.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport type { LazyTreeRevealStep } from \"../lazyTree\";\n\nexport interface UseLazyTreeRevealConfig {\n /** From useLazyTree. */\n revealStep: (path: readonly string[]) => LazyTreeRevealStep;\n /** Show another page of a level -- what the \"more\" row does. */\n showMore: (levelId: string) => void;\n}\n\nexport interface UseLazyTreeRevealReturn {\n /** Reveal the node at the end of `path` (ids from the top level down to it). */\n reveal: (path: readonly string[]) => void;\n /** Where the current reveal stands; undefined when none is under way. */\n step: LazyTreeRevealStep | undefined;\n}\n\n// The level the next node lies beyond, and how many items it shows.\nfunction moreOf(step: LazyTreeRevealStep | undefined): {\n levelId?: string;\n shown?: number;\n} {\n return step?.state === \"more\"\n ? { levelId: step.levelId, shown: step.shown }\n : {};\n}\n\n// Found, missing or failed: nothing more to do.\nconst SETTLED = new Set<LazyTreeRevealStep[\"state\"]>([\n \"found\",\n \"missing\",\n \"error\",\n]);\n\nexport function useLazyTreeReveal({\n revealStep,\n showMore,\n}: UseLazyTreeRevealConfig): UseLazyTreeRevealReturn {\n const [path, setPath] = useState<readonly string[] | undefined>(undefined);\n const step = path ? revealStep(path) : undefined;\n\n // Another page of the level the next node lies in; keyed on what it shows,\n // so each page that lands without the node asks for the next.\n const { levelId: moreLevel, shown: moreShown } = moreOf(step);\n useEffect(() => {\n if (moreLevel !== undefined) showMore(moreLevel);\n }, [moreLevel, moreShown, showMore]);\n\n // Done -- found, missing or failed: the reveal ends.\n const settled = step !== undefined && SETTLED.has(step.state);\n useEffect(() => {\n if (settled) setPath(undefined);\n }, [settled]);\n\n const reveal = useCallback((next: readonly string[]) => setPath(next), []);\n return { reveal, step };\n}\n","import { useSyncExternalStore } from \"react\";\n\n/** The colour theme: the sonnet tokens' light set, or their dark one. */\nexport type Theme = \"light\" | \"dark\";\n\n// The class on <html> that switches the dark tokens in (the preset's\n// darkMode: [\"class\"]), and where the reader's choice is kept.\nconst DARK_CLASS = \"dark\";\nconst STORAGE_KEY = \"sonnet-theme\";\n\nfunction currentTheme(): Theme {\n return document.documentElement.classList.contains(DARK_CLASS)\n ? \"dark\"\n : \"light\";\n}\n\nfunction storedTheme(): Theme | undefined {\n try {\n const value = localStorage.getItem(STORAGE_KEY);\n return value === \"dark\" || value === \"light\" ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction applyTheme(theme: Theme): void {\n document.documentElement.classList.toggle(DARK_CLASS, theme === \"dark\");\n}\n\n/** Switch the theme, and remember it for the next visit. */\nexport function setTheme(theme: Theme): void {\n applyTheme(theme);\n try {\n localStorage.setItem(STORAGE_KEY, theme);\n } catch {\n // No storage (private mode, say): the choice holds for this page.\n }\n}\n\n/**\n * Apply the theme the reader chose last, else the system's. Call once before\n * the app renders, so the first paint is already in that theme.\n *\n * @example\n * initTheme();\n * ReactDOM.createRoot(root).render(<App />);\n */\nexport function initTheme(): void {\n const prefersDark =\n window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n applyTheme(storedTheme() ?? (prefersDark ? \"dark\" : \"light\"));\n}\n\n// Changes of the class on <html>, by setTheme or by the app itself.\nfunction subscribe(onChange: () => void): () => void {\n const observer = new MutationObserver(onChange);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n return () => observer.disconnect();\n}\n\n/**\n * The current theme, re-rendering when it changes, and the way to switch it.\n * Follows the \"dark\" class on <html>, whoever sets it.\n *\n * @example\n * const { theme, setTheme } = useTheme();\n * setTheme(theme === \"dark\" ? \"light\" : \"dark\");\n */\nexport function useTheme(): { theme: Theme; setTheme: (theme: Theme) => void } {\n const theme = useSyncExternalStore(\n subscribe,\n currentTheme,\n (): Theme => \"light\",\n );\n return { theme, setTheme };\n}\n"],"mappings":"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","names":["useState","useCallback","useState","useCallback","useState","useState","useCallback","useEffect","useState","useCallback","useState","useCallback","useState","useEffect","useCallback","useCallback","useEffect","useState"]}