@petrarca/sonnet-core 0.5.0 → 0.7.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 +63 -24
- package/dist/config/index.d.ts +74 -2
- package/dist/config/index.js +32 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.d.ts +184 -1
- package/dist/index.js +316 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/index.d.ts +228 -21
- package/dist/schema/index.js +250 -40
- package/dist/schema/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
# @petrarca/sonnet-core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
3
|
+
The foundation of the Petrarca Sonnet component library: schema handling,
|
|
4
|
+
filter expressions, React hooks and formatting helpers, with no components
|
|
5
|
+
attached. Every other `@petrarca/sonnet-*` package depends on it.
|
|
6
|
+
|
|
7
|
+
## What it provides
|
|
8
|
+
|
|
9
|
+
**A JSON Schema toolkit.** The `JsonSchema` and `UISchema` types that the rest
|
|
10
|
+
of the workspace agrees on, plus `$ref` and conditional resolution, validation
|
|
11
|
+
through Ajv, visibility rules and form diffing.
|
|
12
|
+
|
|
13
|
+
**A Zod bridge.** Author a schema in Zod, carry the `x-ui-*` presentation hints
|
|
14
|
+
on `.meta()`, and convert once to the JSON Schema the renderers read.
|
|
15
|
+
|
|
16
|
+
**Filter expressions.** A serializable AST for search and faceted filtering,
|
|
17
|
+
and the helpers that turn it into list query parameters.
|
|
18
|
+
|
|
19
|
+
**Component configuration.** An app-wide default-props provider and locale
|
|
20
|
+
resolution, so a behavioural prop can be set once for a whole application
|
|
21
|
+
instead of at every call site.
|
|
22
|
+
|
|
23
|
+
**Hooks and utilities.** Clipboard, disclosure, form state, tree expansion,
|
|
24
|
+
resizable panels and async entity options, alongside date, time, number, id,
|
|
25
|
+
error and class-merging helpers.
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -30,6 +30,45 @@ all other Sonnet packages depend on it.
|
|
|
30
30
|
pnpm add @petrarca/sonnet-core
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
| Peer | Range | Needed for |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `react` | `>=19` | The `./hooks`, `./config` and `./entityOptions` subpaths |
|
|
36
|
+
| `@tanstack/react-query` | `>=5` | `useEntityOptions` only; optional otherwise |
|
|
37
|
+
|
|
38
|
+
Ajv, Zod, `nanoid`, `clsx`, `tailwind-merge` and `class-variance-authority`
|
|
39
|
+
arrive as regular dependencies. There is nothing else to install.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
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
|
|
45
|
+
who wants the filter AST should not pull in React.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { formatDate, generateId } from "@petrarca/sonnet-core";
|
|
49
|
+
import { validateFormData, type JsonSchema } from "@petrarca/sonnet-core/schema";
|
|
50
|
+
import { buildListParams } from "@petrarca/sonnet-core/search";
|
|
51
|
+
import { useDisclosure } from "@petrarca/sonnet-core/hooks";
|
|
52
|
+
import { FetcherContext } from "@petrarca/sonnet-core/entityOptions";
|
|
53
|
+
import { SonnetConfigProvider } from "@petrarca/sonnet-core/config";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Which symbol lives in which subpath, and what each one costs to import, is in
|
|
57
|
+
the usage guides:
|
|
58
|
+
[the schema toolkit](docs/usage/schema-toolkit.md),
|
|
59
|
+
[schemas from Zod](docs/usage/zod-schemas.md),
|
|
60
|
+
[search filters](docs/usage/search-filters.md),
|
|
61
|
+
[component config](docs/usage/component-config.md) and
|
|
62
|
+
[hooks and utilities](docs/usage/hooks-and-utilities.md).
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
- [Usage guides](docs/usage/index.md) -- task-oriented, for consumers.
|
|
67
|
+
- [Design docs](docs/design/index.md) -- what belongs in this package and why
|
|
68
|
+
it is split into six subpaths.
|
|
69
|
+
- [Workspace documentation](../../docs/README.md) -- how the packages layer, the
|
|
70
|
+
shared schema extension contract, and the architecture decision records.
|
|
71
|
+
|
|
33
72
|
## License
|
|
34
73
|
|
|
35
|
-
See [LICENSE.md](../../LICENSE.md).
|
|
74
|
+
Apache 2.0. See [LICENSE.md](../../LICENSE.md).
|
package/dist/config/index.d.ts
CHANGED
|
@@ -28,8 +28,53 @@ import React__default from 'react';
|
|
|
28
28
|
*/
|
|
29
29
|
interface SonnetComponentDefaults {
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* When a form reveals a field's validation error.
|
|
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.
|
|
41
|
+
* - `always` -- every invalid field shows its error immediately.
|
|
42
|
+
*
|
|
43
|
+
* This governs display only. Validity is always computed regardless.
|
|
44
|
+
*/
|
|
45
|
+
type ValidationDisplay = "onTouched" | "always" | "onSubmit";
|
|
46
|
+
/**
|
|
47
|
+
* Whether a form's save action is available while the form is invalid.
|
|
48
|
+
*
|
|
49
|
+
* - `reveal` (default) -- Save stays enabled; pressing it on an invalid form
|
|
50
|
+
* reveals every field's error and does not submit. The button always
|
|
51
|
+
* explains why it did not save.
|
|
52
|
+
* - `disable` -- Save is disabled while invalid. Simpler, but a disabled
|
|
53
|
+
* button gives no reason, and it cannot trigger the save-attempt reveal.
|
|
54
|
+
*/
|
|
55
|
+
type SaveWhenInvalid = "reveal" | "disable";
|
|
56
|
+
/**
|
|
57
|
+
* Cross-cutting global settings. Extend via declaration merging as needed.
|
|
58
|
+
* Empty by default; packages add fields without editing this file.
|
|
59
|
+
*/
|
|
32
60
|
interface SonnetGlobalConfig {
|
|
61
|
+
/**
|
|
62
|
+
* BCP-47 locale (e.g. `"en-US"`, `"de-DE"`) used for display and input
|
|
63
|
+
* parsing of locale-sensitive components (dates, numbers). Values exchanged
|
|
64
|
+
* with consumers stay locale-independent (ISO). When unset, components fall
|
|
65
|
+
* back to the browser locale (`navigator.language`), then `"en"`.
|
|
66
|
+
*/
|
|
67
|
+
locale?: string;
|
|
68
|
+
/**
|
|
69
|
+
* App-wide default for when forms reveal validation errors. A form may
|
|
70
|
+
* override it with its own `validationDisplay` prop. Defaults to `onSubmit`.
|
|
71
|
+
*/
|
|
72
|
+
validationDisplay?: ValidationDisplay;
|
|
73
|
+
/**
|
|
74
|
+
* App-wide default for whether Save is available on an invalid form. A form
|
|
75
|
+
* may override it with its own `saveWhenInvalid` prop. Defaults to `reveal`.
|
|
76
|
+
*/
|
|
77
|
+
saveWhenInvalid?: SaveWhenInvalid;
|
|
33
78
|
}
|
|
34
79
|
/** The full app-wide configuration object held by SonnetConfigProvider. */
|
|
35
80
|
interface SonnetConfig {
|
|
@@ -75,4 +120,31 @@ declare const SonnetConfigContext: React.Context<SonnetConfig>;
|
|
|
75
120
|
*/
|
|
76
121
|
declare function useComponentDefaults<Props extends Record<string, unknown>>(componentName: string, builtinDefaults: Partial<Props>, props: Props): Props;
|
|
77
122
|
|
|
78
|
-
|
|
123
|
+
/**
|
|
124
|
+
* useLocale -- resolve the active BCP-47 locale.
|
|
125
|
+
*
|
|
126
|
+
* Resolution order: explicit override > provider global (SonnetConfigProvider)
|
|
127
|
+
* > browser (`navigator.language`) > `"en"`. Locale-sensitive components use
|
|
128
|
+
* this for display and input parsing only; values exchanged with consumers
|
|
129
|
+
* remain locale-independent (ISO).
|
|
130
|
+
*/
|
|
131
|
+
/** The browser locale, or "en" when unavailable (e.g. SSR). */
|
|
132
|
+
declare function getBrowserLocale(): string;
|
|
133
|
+
declare function useLocale(override?: string): string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* useGlobalSetting -- resolve one SonnetGlobalConfig field.
|
|
137
|
+
*
|
|
138
|
+
* Resolution order: explicit override (a component's own prop) > provider
|
|
139
|
+
* global (SonnetConfigProvider) > the caller's fallback. This is the shared
|
|
140
|
+
* shape behind useLocale, useValidationDisplay and useSaveWhenInvalid: a house
|
|
141
|
+
* default set once at the root, overridable per instance.
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
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
|
+
declare function useValidationDisplay(override?: SonnetGlobalConfig["validationDisplay"]): NonNullable<SonnetGlobalConfig["validationDisplay"]>;
|
|
147
|
+
/** Resolve whether Save is available on an invalid form. Defaults to `"reveal"`. */
|
|
148
|
+
declare function useSaveWhenInvalid(override?: SonnetGlobalConfig["saveWhenInvalid"]): NonNullable<SonnetGlobalConfig["saveWhenInvalid"]>;
|
|
149
|
+
|
|
150
|
+
export { type SaveWhenInvalid, type SonnetComponentDefaults, type SonnetConfig, SonnetConfigContext, SonnetConfigProvider, type SonnetConfigProviderProps, type SonnetGlobalConfig, type ValidationDisplay, getBrowserLocale, useComponentDefaults, useGlobalSetting, useLocale, useSaveWhenInvalid, useValidationDisplay };
|
package/dist/config/index.js
CHANGED
|
@@ -55,9 +55,40 @@ function useComponentDefaults(componentName, builtinDefaults, props) {
|
|
|
55
55
|
return resolved;
|
|
56
56
|
}, [builtinDefaults, providerDefaults, props]);
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
// src/config/useLocale.ts
|
|
60
|
+
import { useContext as useContext3 } from "react";
|
|
61
|
+
function getBrowserLocale() {
|
|
62
|
+
if (typeof navigator !== "undefined" && navigator.language) {
|
|
63
|
+
return navigator.language;
|
|
64
|
+
}
|
|
65
|
+
return "en";
|
|
66
|
+
}
|
|
67
|
+
function useLocale(override) {
|
|
68
|
+
const config = useContext3(SonnetConfigContext);
|
|
69
|
+
return override ?? config.globals?.locale ?? getBrowserLocale();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/config/useGlobalSetting.ts
|
|
73
|
+
import { useContext as useContext4 } from "react";
|
|
74
|
+
function useGlobalSetting(key, override, fallback) {
|
|
75
|
+
const config = useContext4(SonnetConfigContext);
|
|
76
|
+
return override ?? config.globals?.[key] ?? fallback;
|
|
77
|
+
}
|
|
78
|
+
function useValidationDisplay(override) {
|
|
79
|
+
return useGlobalSetting("validationDisplay", override, "onSubmit");
|
|
80
|
+
}
|
|
81
|
+
function useSaveWhenInvalid(override) {
|
|
82
|
+
return useGlobalSetting("saveWhenInvalid", override, "reveal");
|
|
83
|
+
}
|
|
58
84
|
export {
|
|
59
85
|
SonnetConfigContext,
|
|
60
86
|
SonnetConfigProvider,
|
|
61
|
-
|
|
87
|
+
getBrowserLocale,
|
|
88
|
+
useComponentDefaults,
|
|
89
|
+
useGlobalSetting,
|
|
90
|
+
useLocale,
|
|
91
|
+
useSaveWhenInvalid,
|
|
92
|
+
useValidationDisplay
|
|
62
93
|
};
|
|
63
94
|
//# sourceMappingURL=index.js.map
|
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/SonnetConfigProvider.tsx","../../src/config/SonnetConfigContext.ts","../../src/config/useComponentDefaults.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"],"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;","names":["useContext","useMemo","useContext","useMemo"]}
|
|
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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,189 @@ declare function formatDateTimeTz(iso: string | null | undefined): string;
|
|
|
34
34
|
/** Return "{count} {word}" with the word pluralized when count != 1. */
|
|
35
35
|
declare function pluralize(count: number, word: string): string;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Date parsing and formatting -- pure functional API.
|
|
39
|
+
*
|
|
40
|
+
* The public surface is a set of pure functions. A `CompiledDateFormat`
|
|
41
|
+
* pre-computes the format structure so repeated parse/format calls (e.g.
|
|
42
|
+
* on every keystroke) don't re-derive component positions each time. Call
|
|
43
|
+
* `compileDateFormat` once in a `useMemo` and pass the result to
|
|
44
|
+
* `parseDate` / `formatDateInput`.
|
|
45
|
+
*
|
|
46
|
+
* The parser operates on native `Date` values internally. Widgets and form
|
|
47
|
+
* data exchange locale-independent ISO 8601 strings -- use
|
|
48
|
+
* `formatIsoDate` / `parseIsoDate` at the boundary.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Pre-compiled date format. Opaque to callers; produced by
|
|
52
|
+
* {@link compileDateFormat} and consumed by {@link parseDate} and
|
|
53
|
+
* {@link formatDateInput}.
|
|
54
|
+
*/
|
|
55
|
+
interface CompiledDateFormat {
|
|
56
|
+
readonly format: string;
|
|
57
|
+
readonly yearThreshold: number;
|
|
58
|
+
readonly dayIndex: number;
|
|
59
|
+
readonly monthIndex: number;
|
|
60
|
+
readonly yearIndex: number;
|
|
61
|
+
readonly has2DigitYear: boolean;
|
|
62
|
+
readonly validSeparators: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
/** Options for {@link compileDateFormat}. */
|
|
65
|
+
interface DateFormatOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Component order for parsing and display. Supported tokens: `DD` (day),
|
|
68
|
+
* `MM` (month), `YYYY` or `YY` (year). Default: `"DD/MM/YYYY"`.
|
|
69
|
+
*/
|
|
70
|
+
format?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Threshold for expanding 2-digit years. If a 2-digit year is greater than
|
|
73
|
+
* this value it is treated as the prior century, otherwise the current one.
|
|
74
|
+
* Special cases: `0` always current century, `-1` always prior century.
|
|
75
|
+
* Default: the current year modulo 100, captured at compile time.
|
|
76
|
+
*/
|
|
77
|
+
yearThreshold?: number;
|
|
78
|
+
}
|
|
79
|
+
/** Result of {@link parseDate}. */
|
|
80
|
+
interface DateParseResult {
|
|
81
|
+
/** Normalized display string (formatted per the compiled format), or "" on failure. */
|
|
82
|
+
value: string;
|
|
83
|
+
/** Parsed `Date` object, or null on failure. Needed by calendar components. */
|
|
84
|
+
date: Date | null;
|
|
85
|
+
/** Error message on failure, or "" on success. */
|
|
86
|
+
error: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Precision of a partial ISO 8601 date:
|
|
90
|
+
* year -> "YYYY"
|
|
91
|
+
* month -> "YYYY-MM"
|
|
92
|
+
* day -> "YYYY-MM-DD"
|
|
93
|
+
*/
|
|
94
|
+
type DatePrecision = "year" | "month" | "day";
|
|
95
|
+
/**
|
|
96
|
+
* Pre-compile a date format string into an opaque `CompiledDateFormat`.
|
|
97
|
+
*
|
|
98
|
+
* Call once per format string (e.g. in `useMemo`) and pass the result to
|
|
99
|
+
* {@link parseDate} and {@link formatDateInput}.
|
|
100
|
+
*/
|
|
101
|
+
declare function compileDateFormat(options?: DateFormatOptions): CompiledDateFormat;
|
|
102
|
+
/**
|
|
103
|
+
* Parse a user-entered date string against a compiled format.
|
|
104
|
+
*
|
|
105
|
+
* Accepts colon/separator-split and compact numeric input. The shorthands
|
|
106
|
+
* `"."`, `"t"`, and `"today"` resolve to the current date. Empty input
|
|
107
|
+
* returns `{ value: "", date: null, error: "" }`.
|
|
108
|
+
*/
|
|
109
|
+
declare function parseDate(input: string, fmt: CompiledDateFormat): DateParseResult;
|
|
110
|
+
/**
|
|
111
|
+
* Format a `Date` as a display string using the compiled format.
|
|
112
|
+
* Returns "" for null or invalid dates.
|
|
113
|
+
*/
|
|
114
|
+
declare function formatDateInput(date: Date | null | undefined, fmt: CompiledDateFormat): string;
|
|
115
|
+
/** Whether two dates fall on the same calendar day. */
|
|
116
|
+
declare function isDateEqual(a: Date | null, b: Date | null): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Convert a local `Date` to a partial ISO date string at the given precision
|
|
119
|
+
* (default `"day"`), using local components (no timezone shift).
|
|
120
|
+
* Returns "" for null or invalid dates.
|
|
121
|
+
*/
|
|
122
|
+
declare function formatIsoDate(date: Date | null | undefined, precision?: DatePrecision): string;
|
|
123
|
+
/**
|
|
124
|
+
* Parse a partial ISO date string (`YYYY`, `YYYY-MM`, or `YYYY-MM-DD`) into a
|
|
125
|
+
* local `Date`. Missing month/day default to January/1st. Returns null for
|
|
126
|
+
* empty or malformed input.
|
|
127
|
+
*/
|
|
128
|
+
declare function parseIsoDate(iso: string | null | undefined): Date | null;
|
|
129
|
+
/**
|
|
130
|
+
* Detect the precision of a partial ISO date string.
|
|
131
|
+
* Returns `"day"` for empty, malformed, or full-date input.
|
|
132
|
+
*/
|
|
133
|
+
declare function isoDatePrecision(iso: string | null | undefined): DatePrecision;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Time parsing and formatting -- pure functional API.
|
|
137
|
+
*
|
|
138
|
+
* A time-of-day is a zone-free wall-clock value (Temporal.PlainTime): it
|
|
139
|
+
* carries no date and no timezone. Values are normalized ISO time strings:
|
|
140
|
+
* `"HH:mm"` (minute granularity) or `"HH:mm:ss"` (second granularity).
|
|
141
|
+
*
|
|
142
|
+
* API conventions match dateParser.ts:
|
|
143
|
+
* parseTime(input, granularity) -> TimeParseResult { value, error }
|
|
144
|
+
* formatIsoTime(h, m, s, granularity) -> string
|
|
145
|
+
* normalizeTime(iso, granularity) -> string
|
|
146
|
+
* An empty or failed result is represented as `value: ""`, not null.
|
|
147
|
+
*/
|
|
148
|
+
/** Granularity of a time value: minutes or seconds. */
|
|
149
|
+
type TimeGranularity = "minute" | "second";
|
|
150
|
+
/** Result of {@link parseTime}. */
|
|
151
|
+
interface TimeParseResult {
|
|
152
|
+
/** Normalized ISO time string, or "" when parsing failed or input is empty. */
|
|
153
|
+
value: string;
|
|
154
|
+
/** Error message on failure, or "" on success. */
|
|
155
|
+
error: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Format H/M/S integer components as a normalized ISO time string.
|
|
159
|
+
* This is an internal building block; consumers typically use {@link normalizeTime}.
|
|
160
|
+
*/
|
|
161
|
+
declare function formatIsoTime(hour: number, minute: number, second: number, granularity?: TimeGranularity): string;
|
|
162
|
+
/**
|
|
163
|
+
* Parse a user-entered time string into a normalized ISO time value.
|
|
164
|
+
*
|
|
165
|
+
* Accepts colon-separated (`"9:30"`, `"21:05:00"`) and compact numeric
|
|
166
|
+
* (`"0930"`, `"213005"`) forms. Missing minutes/seconds default to zero.
|
|
167
|
+
* The shorthands `"."`, `"n"`, and `"now"` resolve to the current time.
|
|
168
|
+
* Empty input returns `{ value: "", error: "" }`.
|
|
169
|
+
*/
|
|
170
|
+
declare function parseTime(input: string, granularity?: TimeGranularity): TimeParseResult;
|
|
171
|
+
/**
|
|
172
|
+
* Normalize an ISO time string (`"HH:mm"` or `"HH:mm:ss"`) to the requested
|
|
173
|
+
* granularity. Returns "" for empty or malformed input.
|
|
174
|
+
*/
|
|
175
|
+
declare function normalizeTime(iso: string | null | undefined, granularity?: TimeGranularity): string;
|
|
176
|
+
/**
|
|
177
|
+
* Split a plain wall-clock datetime (`"YYYY-MM-DDTHH:mm[:ss]"`) into its date
|
|
178
|
+
* (`"YYYY-MM-DD"`) and time (`"HH:mm[:ss]"`) halves. Missing halves return "".
|
|
179
|
+
*/
|
|
180
|
+
declare function splitDateTime(iso: string | null | undefined): {
|
|
181
|
+
date: string;
|
|
182
|
+
time: string;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Join a date (`"YYYY-MM-DD"`) and time (`"HH:mm[:ss]"`) into a plain
|
|
186
|
+
* wall-clock datetime. Returns "" when the date is missing; a missing time
|
|
187
|
+
* yields the date alone.
|
|
188
|
+
*/
|
|
189
|
+
declare function joinDateTime(date: string, time: string): string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Locale-derived date formatting metadata.
|
|
193
|
+
*
|
|
194
|
+
* Uses the Intl APIs (no dependency) to derive, for a BCP-47 locale:
|
|
195
|
+
* - the day/month/year order and separator, as a DateParser format string
|
|
196
|
+
* - localized short weekday labels
|
|
197
|
+
* - the first day of the week (0 = Sunday .. 6 = Saturday)
|
|
198
|
+
*
|
|
199
|
+
* These drive display and input parsing only; exchanged values stay ISO.
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/** DateParser format string per precision, derived for a locale. */
|
|
203
|
+
interface LocaleDateFormats {
|
|
204
|
+
year: string;
|
|
205
|
+
month: string;
|
|
206
|
+
day: string;
|
|
207
|
+
}
|
|
208
|
+
/** Derive DateParser format strings for each precision in the given locale. */
|
|
209
|
+
declare function getLocaleDateFormats(locale: string): LocaleDateFormats;
|
|
210
|
+
/** Derive the DateParser format for a single precision in the given locale. */
|
|
211
|
+
declare function getLocaleDateFormat(locale: string, precision: DatePrecision): string;
|
|
212
|
+
/**
|
|
213
|
+
* Localized short weekday labels ordered from the locale's first day of week.
|
|
214
|
+
* Returns 7 entries, e.g. `["Mon","Tue",...]` or `["Sun","Mon",...]`.
|
|
215
|
+
*/
|
|
216
|
+
declare function getWeekdayLabels(locale: string): string[];
|
|
217
|
+
/** First day of the week for the locale: 0 = Sunday .. 6 = Saturday. */
|
|
218
|
+
declare function getFirstDayOfWeek(locale: string): number;
|
|
219
|
+
|
|
37
220
|
/**
|
|
38
221
|
* Escapes HTML characters to prevent XSS and rendering issues
|
|
39
222
|
*/
|
|
@@ -146,4 +329,4 @@ interface PageResult<T> {
|
|
|
146
329
|
*/
|
|
147
330
|
declare function toPageResult<T>(rows: T[]): PageResult<T>;
|
|
148
331
|
|
|
149
|
-
export { FORM_ITEM_ID_FIELD, JSON_CSS_VARIABLES, type NormalizedError, type OffsetRange, type PageMeta, type PageResult, cn, copyTextToClipboard, devLog, errorLog, escapeHtml, formatDate, formatDateTime, formatDateTimeTz, formatJsonValue, formatNumber, formatNumberCompact, generateId, infoLog, isEqual, normalizeError, pluralize, resolvePointerRanges, snakeCaseToTitleCase, toPageResult, useComposedRefs, warnLog };
|
|
332
|
+
export { type CompiledDateFormat, type DateFormatOptions, type DateParseResult, type DatePrecision, FORM_ITEM_ID_FIELD, JSON_CSS_VARIABLES, type LocaleDateFormats, type NormalizedError, type OffsetRange, type PageMeta, type PageResult, type TimeGranularity, type TimeParseResult, cn, compileDateFormat, copyTextToClipboard, devLog, errorLog, escapeHtml, formatDate, formatDateInput, formatDateTime, formatDateTimeTz, formatIsoDate, formatIsoTime, formatJsonValue, formatNumber, formatNumberCompact, generateId, getFirstDayOfWeek, getLocaleDateFormat, getLocaleDateFormats, getWeekdayLabels, infoLog, isDateEqual, isEqual, isoDatePrecision, joinDateTime, normalizeError, normalizeTime, parseDate, parseIsoDate, parseTime, pluralize, resolvePointerRanges, snakeCaseToTitleCase, splitDateTime, toPageResult, useComposedRefs, warnLog };
|