@petrarca/sonnet-core 0.7.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":[]}
@@ -31,13 +31,14 @@ interface SonnetComponentDefaults {
31
31
  /**
32
32
  * When a form reveals a field's validation error.
33
33
  *
34
- * - `onSubmit` (default) -- quiet until the first save attempt, then reveal
35
- * all. Matches the prior art: rjsf performs no live validation unless
36
- * `liveValidate` is opted into.
37
- * - `onTouched` -- a field reveals its error once it has been touched (focused
38
- * then blurred), and every invalid field reveals on a save attempt. Gives
39
- * earlier feedback, at the cost of an error appearing mid-interaction, which
40
- * grows the field and shifts whatever sits below it.
34
+ * - `onTouched` (default) -- a field reveals its error once it has been
35
+ * edited then blurred, and every invalid field reveals on a save attempt.
36
+ * Feedback lands when the user leaves a field (rjsf's `liveValidate="onBlur"`).
37
+ * The blur-reveal grows the field in flow; the action row's buttons keep
38
+ * focus on press (FormActions preventDefault) so that growth never shifts a
39
+ * button out from under a click.
40
+ * - `onSubmit` -- quiet until the first save attempt, then reveal all. Matches
41
+ * rjsf's default (no live validation unless `liveValidate` is opted into).
41
42
  * - `always` -- every invalid field shows its error immediately.
42
43
  *
43
44
  * This governs display only. Validity is always computed regardless.
@@ -67,7 +68,7 @@ interface SonnetGlobalConfig {
67
68
  locale?: string;
68
69
  /**
69
70
  * App-wide default for when forms reveal validation errors. A form may
70
- * override it with its own `validationDisplay` prop. Defaults to `onSubmit`.
71
+ * override it with its own `validationDisplay` prop. Defaults to `onTouched`.
71
72
  */
72
73
  validationDisplay?: ValidationDisplay;
73
74
  /**
@@ -142,9 +143,10 @@ declare function useLocale(override?: string): string;
142
143
  */
143
144
 
144
145
  declare function useGlobalSetting<K extends keyof SonnetGlobalConfig>(key: K, override: SonnetGlobalConfig[K], fallback: NonNullable<SonnetGlobalConfig[K]>): NonNullable<SonnetGlobalConfig[K]>;
145
- /** Resolve when a form reveals validation errors. Defaults to `"onSubmit"`. */
146
+ /** Resolve when a form reveals validation errors. Defaults to `"onTouched"`. */
146
147
  declare function useValidationDisplay(override?: SonnetGlobalConfig["validationDisplay"]): NonNullable<SonnetGlobalConfig["validationDisplay"]>;
147
148
  /** Resolve whether Save is available on an invalid form. Defaults to `"reveal"`. */
148
149
  declare function useSaveWhenInvalid(override?: SonnetGlobalConfig["saveWhenInvalid"]): NonNullable<SonnetGlobalConfig["saveWhenInvalid"]>;
149
150
 
150
- 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 };
@@ -76,7 +76,7 @@ function useGlobalSetting(key, override, fallback) {
76
76
  return override ?? config.globals?.[key] ?? fallback;
77
77
  }
78
78
  function useValidationDisplay(override) {
79
- return useGlobalSetting("validationDisplay", override, "onSubmit");
79
+ return useGlobalSetting("validationDisplay", override, "onTouched");
80
80
  }
81
81
  function useSaveWhenInvalid(override) {
82
82
  return useGlobalSetting("saveWhenInvalid", override, "reveal");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config/SonnetConfigProvider.tsx","../../src/config/SonnetConfigContext.ts","../../src/config/useComponentDefaults.ts","../../src/config/useLocale.ts","../../src/config/useGlobalSetting.ts"],"sourcesContent":["/**\n * SonnetConfigProvider -- sets app-wide component defaults.\n *\n * Place once at the app root to define default props for components (and\n * cross-cutting globals). Individual instances still override via props.\n * Nested providers merge into their parent, so a subtree can refine or\n * override outer defaults.\n *\n * Resolution order for any prop (see useComponentDefaults):\n * instance prop > provider default > built-in default.\n */\n\nimport React, { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\nimport type { SonnetConfig } from \"./types\";\n\nexport interface SonnetConfigProviderProps {\n /** The configuration to apply. Merged onto any parent provider's config. */\n value: SonnetConfig;\n children: React.ReactNode;\n}\n\n// Merge child config onto parent. Globals shallow-merge; component defaults\n// merge per component name, with each component's defaultProps shallow-merged\n// (child wins). One level of nesting is sufficient for the defaultProps shape.\nfunction mergeConfig(parent: SonnetConfig, child: SonnetConfig): SonnetConfig {\n const components: Record<string, { defaultProps?: Record<string, unknown> }> =\n {\n ...(parent.components as Record<\n string,\n { defaultProps?: Record<string, unknown> }\n >),\n };\n const childComponents = child.components as\n | Record<string, { defaultProps?: Record<string, unknown> }>\n | undefined;\n if (childComponents) {\n for (const name of Object.keys(childComponents)) {\n components[name] = {\n ...components[name],\n ...childComponents[name],\n defaultProps: {\n ...components[name]?.defaultProps,\n ...childComponents[name]?.defaultProps,\n },\n };\n }\n }\n return {\n globals: { ...parent.globals, ...child.globals },\n components: components as SonnetConfig[\"components\"],\n };\n}\n\nexport function SonnetConfigProvider({\n value,\n children,\n}: SonnetConfigProviderProps): React.ReactElement {\n const parent = useContext(SonnetConfigContext);\n const merged = useMemo(() => mergeConfig(parent, value), [parent, value]);\n return (\n <SonnetConfigContext.Provider value={merged}>\n {children}\n </SonnetConfigContext.Provider>\n );\n}\n","/**\n * React context holding the resolved app-wide SonnetConfig.\n *\n * Nested providers merge into their parent (see SonnetConfigProvider), so a\n * provider deeper in the tree can refine or override an outer one for its\n * subtree. The default is an empty config -- with no provider, components fall\n * back to their built-in defaults, preserving existing behavior.\n */\n\nimport { createContext } from \"react\";\nimport type { SonnetConfig } from \"./types\";\n\nexport const SonnetConfigContext = createContext<SonnetConfig>({});\n","/**\n * useComponentDefaults -- resolve a component's props against app-wide config.\n *\n * Resolution order, per prop: instance prop > provider default > built-in.\n * A component calls this at the top of its body:\n *\n * const resolved = useComponentDefaults(\"EntityTable\", BUILTIN, props);\n *\n * where BUILTIN is a stable (module-level) object of the component's built-in\n * defaults. With no provider in the tree, the result is BUILTIN merged with\n * props -- identical to not using the config at all, so existing behavior is\n * preserved.\n */\n\nimport { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\n\nexport function useComponentDefaults<Props extends Record<string, unknown>>(\n componentName: string,\n builtinDefaults: Partial<Props>,\n props: Props,\n): Props {\n const config = useContext(SonnetConfigContext);\n const components = config.components as\n | Record<string, { defaultProps?: Partial<Props> } | undefined>\n | undefined;\n const providerDefaults = components?.[componentName]?.defaultProps;\n\n return useMemo(() => {\n // Spread order encodes precedence: builtin < provider < instance props.\n // Only undefined instance props fall through to provider/builtin, so an\n // explicit prop always wins.\n const resolved: Record<string, unknown> = {\n ...builtinDefaults,\n ...providerDefaults,\n };\n for (const key of Object.keys(props)) {\n if (props[key] !== undefined) resolved[key] = props[key];\n }\n return resolved as Props;\n }, [builtinDefaults, providerDefaults, props]);\n}\n","/**\n * useLocale -- resolve the active BCP-47 locale.\n *\n * Resolution order: explicit override > provider global (SonnetConfigProvider)\n * > browser (`navigator.language`) > `\"en\"`. Locale-sensitive components use\n * this for display and input parsing only; values exchanged with consumers\n * remain locale-independent (ISO).\n */\n\nimport { useContext } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\n\n/** The browser locale, or \"en\" when unavailable (e.g. SSR). */\nexport function getBrowserLocale(): string {\n if (typeof navigator !== \"undefined\" && navigator.language) {\n return navigator.language;\n }\n return \"en\";\n}\n\nexport function useLocale(override?: string): string {\n const config = useContext(SonnetConfigContext);\n return override ?? config.globals?.locale ?? getBrowserLocale();\n}\n","/**\n * useGlobalSetting -- resolve one SonnetGlobalConfig field.\n *\n * Resolution order: explicit override (a component's own prop) > provider\n * global (SonnetConfigProvider) > the caller's fallback. This is the shared\n * shape behind useLocale, useValidationDisplay and useSaveWhenInvalid: a house\n * default set once at the root, overridable per instance.\n */\n\nimport { useContext } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\nimport type { SonnetGlobalConfig } from \"./types\";\n\nexport function useGlobalSetting<K extends keyof SonnetGlobalConfig>(\n key: K,\n override: SonnetGlobalConfig[K],\n fallback: NonNullable<SonnetGlobalConfig[K]>,\n): NonNullable<SonnetGlobalConfig[K]> {\n const config = useContext(SonnetConfigContext);\n return override ?? config.globals?.[key] ?? fallback;\n}\n\n/** Resolve when a form reveals validation errors. Defaults to `\"onSubmit\"`. */\nexport function useValidationDisplay(\n override?: SonnetGlobalConfig[\"validationDisplay\"],\n): NonNullable<SonnetGlobalConfig[\"validationDisplay\"]> {\n return useGlobalSetting(\"validationDisplay\", override, \"onSubmit\");\n}\n\n/** Resolve whether Save is available on an invalid form. Defaults to `\"reveal\"`. */\nexport function useSaveWhenInvalid(\n override?: SonnetGlobalConfig[\"saveWhenInvalid\"],\n): NonNullable<SonnetGlobalConfig[\"saveWhenInvalid\"]> {\n return useGlobalSetting(\"saveWhenInvalid\", override, \"reveal\");\n}\n"],"mappings":";AAYA,SAAgB,YAAY,eAAe;;;ACH3C,SAAS,qBAAqB;AAGvB,IAAM,sBAAsB,cAA4B,CAAC,CAAC;;;ADiD7D;AApCJ,SAAS,YAAY,QAAsB,OAAmC;AAC5E,QAAM,aACJ;AAAA,IACE,GAAI,OAAO;AAAA,EAIb;AACF,QAAM,kBAAkB,MAAM;AAG9B,MAAI,iBAAiB;AACnB,eAAW,QAAQ,OAAO,KAAK,eAAe,GAAG;AAC/C,iBAAW,IAAI,IAAI;AAAA,QACjB,GAAG,WAAW,IAAI;AAAA,QAClB,GAAG,gBAAgB,IAAI;AAAA,QACvB,cAAc;AAAA,UACZ,GAAG,WAAW,IAAI,GAAG;AAAA,UACrB,GAAG,gBAAgB,IAAI,GAAG;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,EAAE,GAAG,OAAO,SAAS,GAAG,MAAM,QAAQ;AAAA,IAC/C;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAAkD;AAChD,QAAM,SAAS,WAAW,mBAAmB;AAC7C,QAAM,SAAS,QAAQ,MAAM,YAAY,QAAQ,KAAK,GAAG,CAAC,QAAQ,KAAK,CAAC;AACxE,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,QAClC,UACH;AAEJ;;;AEnDA,SAAS,cAAAA,aAAY,WAAAC,gBAAe;AAG7B,SAAS,qBACd,eACA,iBACA,OACO;AACP,QAAM,SAASC,YAAW,mBAAmB;AAC7C,QAAM,aAAa,OAAO;AAG1B,QAAM,mBAAmB,aAAa,aAAa,GAAG;AAEtD,SAAOC,SAAQ,MAAM;AAInB,UAAM,WAAoC;AAAA,MACxC,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAI,MAAM,GAAG,MAAM,OAAW,UAAS,GAAG,IAAI,MAAM,GAAG;AAAA,IACzD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,kBAAkB,KAAK,CAAC;AAC/C;;;AChCA,SAAS,cAAAC,mBAAkB;AAIpB,SAAS,mBAA2B;AACzC,MAAI,OAAO,cAAc,eAAe,UAAU,UAAU;AAC1D,WAAO,UAAU;AAAA,EACnB;AACA,SAAO;AACT;AAEO,SAAS,UAAU,UAA2B;AACnD,QAAM,SAASC,YAAW,mBAAmB;AAC7C,SAAO,YAAY,OAAO,SAAS,UAAU,iBAAiB;AAChE;;;ACdA,SAAS,cAAAC,mBAAkB;AAIpB,SAAS,iBACd,KACA,UACA,UACoC;AACpC,QAAM,SAASC,YAAW,mBAAmB;AAC7C,SAAO,YAAY,OAAO,UAAU,GAAG,KAAK;AAC9C;AAGO,SAAS,qBACd,UACsD;AACtD,SAAO,iBAAiB,qBAAqB,UAAU,UAAU;AACnE;AAGO,SAAS,mBACd,UACoD;AACpD,SAAO,iBAAiB,mBAAmB,UAAU,QAAQ;AAC/D;","names":["useContext","useMemo","useContext","useMemo","useContext","useContext","useContext","useContext"]}
1
+ {"version":3,"sources":["../../src/config/SonnetConfigProvider.tsx","../../src/config/SonnetConfigContext.ts","../../src/config/useComponentDefaults.ts","../../src/config/useLocale.ts","../../src/config/useGlobalSetting.ts"],"sourcesContent":["/**\n * SonnetConfigProvider -- sets app-wide component defaults.\n *\n * Place once at the app root to define default props for components (and\n * cross-cutting globals). Individual instances still override via props.\n * Nested providers merge into their parent, so a subtree can refine or\n * override outer defaults.\n *\n * Resolution order for any prop (see useComponentDefaults):\n * instance prop > provider default > built-in default.\n */\n\nimport React, { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\nimport type { SonnetConfig } from \"./types\";\n\nexport interface SonnetConfigProviderProps {\n /** The configuration to apply. Merged onto any parent provider's config. */\n value: SonnetConfig;\n children: React.ReactNode;\n}\n\n// Merge child config onto parent. Globals shallow-merge; component defaults\n// merge per component name, with each component's defaultProps shallow-merged\n// (child wins). One level of nesting is sufficient for the defaultProps shape.\nfunction mergeConfig(parent: SonnetConfig, child: SonnetConfig): SonnetConfig {\n const components: Record<string, { defaultProps?: Record<string, unknown> }> =\n {\n ...(parent.components as Record<\n string,\n { defaultProps?: Record<string, unknown> }\n >),\n };\n const childComponents = child.components as\n | Record<string, { defaultProps?: Record<string, unknown> }>\n | undefined;\n if (childComponents) {\n for (const name of Object.keys(childComponents)) {\n components[name] = {\n ...components[name],\n ...childComponents[name],\n defaultProps: {\n ...components[name]?.defaultProps,\n ...childComponents[name]?.defaultProps,\n },\n };\n }\n }\n return {\n globals: { ...parent.globals, ...child.globals },\n components: components as SonnetConfig[\"components\"],\n };\n}\n\nexport function SonnetConfigProvider({\n value,\n children,\n}: SonnetConfigProviderProps): React.ReactElement {\n const parent = useContext(SonnetConfigContext);\n const merged = useMemo(() => mergeConfig(parent, value), [parent, value]);\n return (\n <SonnetConfigContext.Provider value={merged}>\n {children}\n </SonnetConfigContext.Provider>\n );\n}\n","/**\n * React context holding the resolved app-wide SonnetConfig.\n *\n * Nested providers merge into their parent (see SonnetConfigProvider), so a\n * provider deeper in the tree can refine or override an outer one for its\n * subtree. The default is an empty config -- with no provider, components fall\n * back to their built-in defaults, preserving existing behavior.\n */\n\nimport { createContext } from \"react\";\nimport type { SonnetConfig } from \"./types\";\n\nexport const SonnetConfigContext = createContext<SonnetConfig>({});\n","/**\n * useComponentDefaults -- resolve a component's props against app-wide config.\n *\n * Resolution order, per prop: instance prop > provider default > built-in.\n * A component calls this at the top of its body:\n *\n * const resolved = useComponentDefaults(\"EntityTable\", BUILTIN, props);\n *\n * where BUILTIN is a stable (module-level) object of the component's built-in\n * defaults. With no provider in the tree, the result is BUILTIN merged with\n * props -- identical to not using the config at all, so existing behavior is\n * preserved.\n */\n\nimport { useContext, useMemo } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\n\nexport function useComponentDefaults<Props extends Record<string, unknown>>(\n componentName: string,\n builtinDefaults: Partial<Props>,\n props: Props,\n): Props {\n const config = useContext(SonnetConfigContext);\n const components = config.components as\n | Record<string, { defaultProps?: Partial<Props> } | undefined>\n | undefined;\n const providerDefaults = components?.[componentName]?.defaultProps;\n\n return useMemo(() => {\n // Spread order encodes precedence: builtin < provider < instance props.\n // Only undefined instance props fall through to provider/builtin, so an\n // explicit prop always wins.\n const resolved: Record<string, unknown> = {\n ...builtinDefaults,\n ...providerDefaults,\n };\n for (const key of Object.keys(props)) {\n if (props[key] !== undefined) resolved[key] = props[key];\n }\n return resolved as Props;\n }, [builtinDefaults, providerDefaults, props]);\n}\n","/**\n * useLocale -- resolve the active BCP-47 locale.\n *\n * Resolution order: explicit override > provider global (SonnetConfigProvider)\n * > browser (`navigator.language`) > `\"en\"`. Locale-sensitive components use\n * this for display and input parsing only; values exchanged with consumers\n * remain locale-independent (ISO).\n */\n\nimport { useContext } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\n\n/** The browser locale, or \"en\" when unavailable (e.g. SSR). */\nexport function getBrowserLocale(): string {\n if (typeof navigator !== \"undefined\" && navigator.language) {\n return navigator.language;\n }\n return \"en\";\n}\n\nexport function useLocale(override?: string): string {\n const config = useContext(SonnetConfigContext);\n return override ?? config.globals?.locale ?? getBrowserLocale();\n}\n","/**\n * useGlobalSetting -- resolve one SonnetGlobalConfig field.\n *\n * Resolution order: explicit override (a component's own prop) > provider\n * global (SonnetConfigProvider) > the caller's fallback. This is the shared\n * shape behind useLocale, useValidationDisplay and useSaveWhenInvalid: a house\n * default set once at the root, overridable per instance.\n */\n\nimport { useContext } from \"react\";\nimport { SonnetConfigContext } from \"./SonnetConfigContext\";\nimport type { SonnetGlobalConfig } from \"./types\";\n\nexport function useGlobalSetting<K extends keyof SonnetGlobalConfig>(\n key: K,\n override: SonnetGlobalConfig[K],\n fallback: NonNullable<SonnetGlobalConfig[K]>,\n): NonNullable<SonnetGlobalConfig[K]> {\n const config = useContext(SonnetConfigContext);\n return override ?? config.globals?.[key] ?? fallback;\n}\n\n/** Resolve when a form reveals validation errors. Defaults to `\"onTouched\"`. */\nexport function useValidationDisplay(\n override?: SonnetGlobalConfig[\"validationDisplay\"],\n): NonNullable<SonnetGlobalConfig[\"validationDisplay\"]> {\n return useGlobalSetting(\"validationDisplay\", override, \"onTouched\");\n}\n\n/** Resolve whether Save is available on an invalid form. Defaults to `\"reveal\"`. */\nexport function useSaveWhenInvalid(\n override?: SonnetGlobalConfig[\"saveWhenInvalid\"],\n): NonNullable<SonnetGlobalConfig[\"saveWhenInvalid\"]> {\n return useGlobalSetting(\"saveWhenInvalid\", override, \"reveal\");\n}\n"],"mappings":";AAYA,SAAgB,YAAY,eAAe;;;ACH3C,SAAS,qBAAqB;AAGvB,IAAM,sBAAsB,cAA4B,CAAC,CAAC;;;ADiD7D;AApCJ,SAAS,YAAY,QAAsB,OAAmC;AAC5E,QAAM,aACJ;AAAA,IACE,GAAI,OAAO;AAAA,EAIb;AACF,QAAM,kBAAkB,MAAM;AAG9B,MAAI,iBAAiB;AACnB,eAAW,QAAQ,OAAO,KAAK,eAAe,GAAG;AAC/C,iBAAW,IAAI,IAAI;AAAA,QACjB,GAAG,WAAW,IAAI;AAAA,QAClB,GAAG,gBAAgB,IAAI;AAAA,QACvB,cAAc;AAAA,UACZ,GAAG,WAAW,IAAI,GAAG;AAAA,UACrB,GAAG,gBAAgB,IAAI,GAAG;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,EAAE,GAAG,OAAO,SAAS,GAAG,MAAM,QAAQ;AAAA,IAC/C;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAAkD;AAChD,QAAM,SAAS,WAAW,mBAAmB;AAC7C,QAAM,SAAS,QAAQ,MAAM,YAAY,QAAQ,KAAK,GAAG,CAAC,QAAQ,KAAK,CAAC;AACxE,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,QAClC,UACH;AAEJ;;;AEnDA,SAAS,cAAAA,aAAY,WAAAC,gBAAe;AAG7B,SAAS,qBACd,eACA,iBACA,OACO;AACP,QAAM,SAASC,YAAW,mBAAmB;AAC7C,QAAM,aAAa,OAAO;AAG1B,QAAM,mBAAmB,aAAa,aAAa,GAAG;AAEtD,SAAOC,SAAQ,MAAM;AAInB,UAAM,WAAoC;AAAA,MACxC,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAI,MAAM,GAAG,MAAM,OAAW,UAAS,GAAG,IAAI,MAAM,GAAG;AAAA,IACzD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,kBAAkB,KAAK,CAAC;AAC/C;;;AChCA,SAAS,cAAAC,mBAAkB;AAIpB,SAAS,mBAA2B;AACzC,MAAI,OAAO,cAAc,eAAe,UAAU,UAAU;AAC1D,WAAO,UAAU;AAAA,EACnB;AACA,SAAO;AACT;AAEO,SAAS,UAAU,UAA2B;AACnD,QAAM,SAASC,YAAW,mBAAmB;AAC7C,SAAO,YAAY,OAAO,SAAS,UAAU,iBAAiB;AAChE;;;ACdA,SAAS,cAAAC,mBAAkB;AAIpB,SAAS,iBACd,KACA,UACA,UACoC;AACpC,QAAM,SAASC,YAAW,mBAAmB;AAC7C,SAAO,YAAY,OAAO,UAAU,GAAG,KAAK;AAC9C;AAGO,SAAS,qBACd,UACsD;AACtD,SAAO,iBAAiB,qBAAqB,UAAU,WAAW;AACpE;AAGO,SAAS,mBACd,UACoD;AACpD,SAAO,iBAAiB,mBAAmB,UAAU,QAAQ;AAC/D;","names":["useContext","useMemo","useContext","useMemo","useContext","useContext","useContext","useContext"]}
@@ -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
@@ -62,6 +69,22 @@ interface UseDisclosureReturn {
62
69
  */
63
70
  declare function useDisclosure(initialState?: boolean): readonly [boolean, UseDisclosureReturn];
64
71
 
72
+ /**
73
+ * Subscribe to a CSS media query and re-render when it changes.
74
+ *
75
+ * SSR-safe: returns `false` on the server and during the first client render
76
+ * when `window.matchMedia` is unavailable, then syncs on mount.
77
+ *
78
+ * @param query - a media query string, e.g. "(max-width: 1023px)"
79
+ * @returns whether the query currently matches
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * const isTablet = useMediaQuery("(max-width: 1023px)");
84
+ * ```
85
+ */
86
+ declare function useMediaQuery(query: string): boolean;
87
+
65
88
  /**
66
89
  * Custom hook for resizable panel functionality.
67
90
  * Extracted from GraphViewer to be reusable across components.
@@ -222,6 +245,69 @@ interface UseEntityOptionsReturn {
222
245
  }
223
246
  declare function useEntityOptions({ fetcher, queryKey, mode, debounceMs, minChars, limit, enabled, }: UseEntityOptionsConfig): UseEntityOptionsReturn;
224
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
+
225
311
  /**
226
312
  * Shared types for React Query hooks.
227
313
  *
@@ -234,4 +320,31 @@ interface QueryHookOptions {
234
320
  enabled?: boolean;
235
321
  }
236
322
 
237
- export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, type UseTreeExpansionReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, 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 };