@petrarca/sonnet-core 0.7.0 → 0.8.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.
@@ -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,7 +143,7 @@ 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"]>;
@@ -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"]}
@@ -62,6 +62,22 @@ interface UseDisclosureReturn {
62
62
  */
63
63
  declare function useDisclosure(initialState?: boolean): readonly [boolean, UseDisclosureReturn];
64
64
 
65
+ /**
66
+ * Subscribe to a CSS media query and re-render when it changes.
67
+ *
68
+ * SSR-safe: returns `false` on the server and during the first client render
69
+ * when `window.matchMedia` is unavailable, then syncs on mount.
70
+ *
71
+ * @param query - a media query string, e.g. "(max-width: 1023px)"
72
+ * @returns whether the query currently matches
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * const isTablet = useMediaQuery("(max-width: 1023px)");
77
+ * ```
78
+ */
79
+ declare function useMediaQuery(query: string): boolean;
80
+
65
81
  /**
66
82
  * Custom hook for resizable panel functionality.
67
83
  * Extracted from GraphViewer to be reusable across components.
@@ -234,4 +250,4 @@ interface QueryHookOptions {
234
250
  enabled?: boolean;
235
251
  }
236
252
 
237
- export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, type UseTreeExpansionReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, useResizablePanel, useTreeExpansion };
253
+ export { type EntitySelectMode, type QueryHookOptions, type UseClipboardOptions, type UseClipboardReturn, type UseEntityOptionsConfig, type UseEntityOptionsReturn, type UseTreeExpansionReturn, useClipboard, useDisclosure, useEntityOptions, useFormState, useMediaQuery, useResizablePanel, useTreeExpansion };
@@ -57,8 +57,26 @@ function useDisclosure(initialState = false) {
57
57
  return [opened, { open, close, toggle }];
58
58
  }
59
59
 
60
+ // src/hooks/useMediaQuery.ts
61
+ import { useState as useState4, useEffect } from "react";
62
+ function useMediaQuery(query) {
63
+ const [matches, setMatches] = useState4(() => {
64
+ if (typeof window === "undefined" || !window.matchMedia) return false;
65
+ return window.matchMedia(query).matches;
66
+ });
67
+ useEffect(() => {
68
+ if (typeof window === "undefined" || !window.matchMedia) return;
69
+ const mql = window.matchMedia(query);
70
+ const onChange = () => setMatches(mql.matches);
71
+ onChange();
72
+ mql.addEventListener("change", onChange);
73
+ return () => mql.removeEventListener("change", onChange);
74
+ }, [query]);
75
+ return matches;
76
+ }
77
+
60
78
  // src/hooks/useResizablePanel.ts
61
- import { useState as useState4, useRef, useCallback as useCallback4, useEffect } from "react";
79
+ import { useState as useState5, useRef, useCallback as useCallback4, useEffect as useEffect2 } from "react";
62
80
  function useResizablePanel(options = {}) {
63
81
  const {
64
82
  defaultWidth = 320,
@@ -66,7 +84,7 @@ function useResizablePanel(options = {}) {
66
84
  maxWidth = 640,
67
85
  direction = "left-edge"
68
86
  } = options;
69
- const [panelWidth, setPanelWidth] = useState4(defaultWidth);
87
+ const [panelWidth, setPanelWidth] = useState5(defaultWidth);
70
88
  const resizingRef = useRef(false);
71
89
  const startXRef = useRef(0);
72
90
  const startWidthRef = useRef(0);
@@ -101,7 +119,7 @@ function useResizablePanel(options = {}) {
101
119
  () => setPanelWidth(defaultWidth),
102
120
  [defaultWidth]
103
121
  );
104
- useEffect(() => {
122
+ useEffect2(() => {
105
123
  window.addEventListener("pointermove", handlePointerMove);
106
124
  window.addEventListener("pointerup", finishResize);
107
125
  window.addEventListener("pointercancel", finishResize);
@@ -134,10 +152,10 @@ function useResizablePanel(options = {}) {
134
152
  }
135
153
 
136
154
  // src/hooks/useFormState.ts
137
- import { useState as useState5, useCallback as useCallback5, useMemo } from "react";
155
+ import { useState as useState6, useCallback as useCallback5, useMemo } from "react";
138
156
  function useFormState(initialData) {
139
- const [data, setDataState] = useState5(initialData);
140
- const [original, setOriginal] = useState5(initialData);
157
+ const [data, setDataState] = useState6(initialData);
158
+ const [original, setOriginal] = useState6(initialData);
141
159
  const isDirty = useMemo(() => {
142
160
  return !isEqual(data, original);
143
161
  }, [data, original]);
@@ -169,7 +187,7 @@ function useFormState(initialData) {
169
187
  }
170
188
 
171
189
  // src/hooks/useEntityOptions.ts
172
- import { useState as useState6, useEffect as useEffect2, useCallback as useCallback6 } from "react";
190
+ import { useState as useState7, useEffect as useEffect3, useCallback as useCallback6 } from "react";
173
191
  import { useQuery } from "@tanstack/react-query";
174
192
  function resolveEnabled(enabled, mode, activeSearch, minChars, committedSearch) {
175
193
  if (!enabled) return false;
@@ -186,10 +204,10 @@ function useEntityOptions({
186
204
  limit = 50,
187
205
  enabled = true
188
206
  }) {
189
- const [search, setSearch] = useState6("");
190
- const [debouncedSearch, setDebouncedSearch] = useState6("");
191
- const [committedSearch, setCommittedSearch] = useState6(null);
192
- useEffect2(() => {
207
+ const [search, setSearch] = useState7("");
208
+ const [debouncedSearch, setDebouncedSearch] = useState7("");
209
+ const [committedSearch, setCommittedSearch] = useState7(null);
210
+ useEffect3(() => {
193
211
  if (mode === "explicit") return;
194
212
  const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);
195
213
  return () => clearTimeout(timer);
@@ -219,6 +237,7 @@ export {
219
237
  useDisclosure,
220
238
  useEntityOptions,
221
239
  useFormState,
240
+ useMediaQuery,
222
241
  useResizablePanel,
223
242
  useTreeExpansion
224
243
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useClipboard.ts","../../src/hooks/useTreeExpansion.ts","../../src/hooks/useDisclosure.ts","../../src/hooks/useResizablePanel.ts","../../src/hooks/useFormState.ts","../../src/hooks/useEntityOptions.ts"],"sourcesContent":["import { useState, useCallback } from \"react\";\n\nexport interface UseClipboardOptions {\n timeout?: number;\n}\n\nexport interface UseClipboardReturn {\n copy: (text: string) => Promise<void>;\n copied: boolean;\n error: Error | null;\n reset: () => void;\n}\n\n/**\n * Custom hook for clipboard operations\n *\n * Replaces Mantine's useClipboard hook with native Clipboard API\n *\n * @param options - Configuration options\n * @param options.timeout - Time in ms before copied state resets (default: 2000)\n * @returns Object with copy function, copied state, error, and reset function\n *\n * @example\n * ```tsx\n * const { copy, copied } = useClipboard({ timeout: 3000 });\n *\n * <button onClick={() => copy('Hello World')}>\n * {copied ? 'Copied!' : 'Copy'}\n * </button>\n * ```\n */\nexport function useClipboard({\n timeout = 2000,\n}: UseClipboardOptions = {}): UseClipboardReturn {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const copy = useCallback(\n async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setError(null);\n\n // Reset copied state after timeout\n setTimeout(() => setCopied(false), timeout);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setCopied(false);\n }\n },\n [timeout],\n );\n\n const reset = useCallback(() => {\n setCopied(false);\n setError(null);\n }, []);\n\n return { copy, copied, error, reset };\n}\n","import { useState, useCallback } from \"react\";\n\nexport interface UseTreeExpansionReturn {\n expandedIds: Set<string>;\n onExpand: (id: string) => void;\n onCollapse: (id: string) => void;\n}\n\n/** Manages expand/collapse state for a TreeView. */\nexport function useTreeExpansion(): UseTreeExpansionReturn {\n const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());\n\n const onExpand = useCallback((id: string) => {\n setExpandedIds((prev) => new Set([...prev, id]));\n }, []);\n\n const onCollapse = useCallback((id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n return { expandedIds, onExpand, onCollapse };\n}\n","import { useState, useCallback } from \"react\";\n\nexport interface UseDisclosureReturn {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Custom hook for managing open/close state\n *\n * Replaces Mantine's useDisclosure hook with simple useState wrapper\n *\n * @param initialState - Initial open state (default: false)\n * @returns Tuple of [opened, handlers] where handlers contains open, close, toggle functions\n *\n * @example\n * ```tsx\n * const [opened, { open, close, toggle }] = useDisclosure();\n *\n * <button onClick={open}>Open Modal</button>\n * <Modal opened={opened} onClose={close}>\n * <button onClick={toggle}>Toggle</button>\n * </Modal>\n * ```\n */\nexport function useDisclosure(\n initialState = false,\n): readonly [boolean, UseDisclosureReturn] {\n const [opened, setOpened] = useState(initialState);\n\n const open = useCallback(() => setOpened(true), []);\n const close = useCallback(() => setOpened(false), []);\n const toggle = useCallback(() => setOpened((prev) => !prev), []);\n\n return [opened, { open, close, toggle }] as const;\n}\n","/**\n * Custom hook for resizable panel functionality.\n * Extracted from GraphViewer to be reusable across components.\n *\n * @module hooks/useResizablePanel\n */\n\nimport { useState, useRef, useCallback, useEffect } from \"react\";\n\ninterface UseResizablePanelOptions {\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /**\n * Which edge the drag handle sits on relative to the panel content.\n *\n * - \"left-edge\" — handle is on the panel's left; dragging right shrinks the\n * panel (default, matches the right-side action panel in\n * GraphViewer where the separator is left of the panel).\n * - \"right-edge\" — handle is on the panel's right; dragging right grows the\n * panel (matches a left-side SidePane where the separator is\n * right of the panel).\n */\n direction?: \"left-edge\" | \"right-edge\";\n}\n\ninterface UseResizablePanelReturn {\n panelWidth: number;\n handlePointerDown: (e: React.PointerEvent) => void;\n handleDoubleClick: () => void;\n separatorProps: {\n role: string;\n \"aria-orientation\": \"vertical\";\n \"aria-valuenow\": number;\n \"aria-valuemin\": number;\n \"aria-valuemax\": number;\n };\n}\n\n/**\n * Hook for managing resizable panel state and interactions.\n * Handles pointer events, constraints, and keyboard shortcuts (Escape to cancel).\n */\nexport function useResizablePanel(\n options: UseResizablePanelOptions = {},\n): UseResizablePanelReturn {\n const {\n defaultWidth = 320,\n minWidth = 260,\n maxWidth = 640,\n direction = \"left-edge\",\n } = options;\n\n const [panelWidth, setPanelWidth] = useState<number>(defaultWidth);\n const resizingRef = useRef(false);\n const startXRef = useRef(0);\n const startWidthRef = useRef(0);\n\n const clamp = useCallback(\n (v: number) => Math.min(Math.max(v, minWidth), maxWidth),\n [minWidth, maxWidth],\n );\n\n const finishResize = useCallback(() => {\n if (!resizingRef.current) return;\n resizingRef.current = false;\n document.body.classList.remove(\"select-none\");\n }, []);\n\n const handlePointerMove = useCallback(\n (e: PointerEvent) => {\n if (!resizingRef.current) return;\n const delta = e.clientX - startXRef.current;\n // left-edge: separator left of panel, drag right shrinks (subtract delta)\n // right-edge: separator right of panel, drag right grows (add delta)\n const newWidth =\n direction === \"right-edge\"\n ? startWidthRef.current + delta\n : startWidthRef.current - delta;\n setPanelWidth(clamp(newWidth));\n },\n [clamp, direction],\n );\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n resizingRef.current = true;\n startXRef.current = e.clientX;\n startWidthRef.current = panelWidth;\n document.body.classList.add(\"select-none\");\n },\n [panelWidth],\n );\n\n const handleDoubleClick = useCallback(\n () => setPanelWidth(defaultWidth),\n [defaultWidth],\n );\n\n useEffect(() => {\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", finishResize);\n window.addEventListener(\"pointercancel\", finishResize);\n\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && resizingRef.current) {\n setPanelWidth(startWidthRef.current);\n finishResize();\n }\n };\n window.addEventListener(\"keydown\", handleKey);\n\n return () => {\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", finishResize);\n window.removeEventListener(\"pointercancel\", finishResize);\n window.removeEventListener(\"keydown\", handleKey);\n };\n }, [handlePointerMove, finishResize]);\n\n return {\n panelWidth,\n handlePointerDown,\n handleDoubleClick,\n separatorProps: {\n role: \"separator\",\n \"aria-orientation\": \"vertical\",\n \"aria-valuenow\": panelWidth,\n \"aria-valuemin\": minWidth,\n \"aria-valuemax\": maxWidth,\n },\n };\n}\n","/**\n * useFormState Hook\n *\n * Manages form state with automatic dirty tracking and common operations.\n * Tracks changes against original data to determine if form has been modified.\n *\n * @module hooks/useFormState\n */\n\nimport { useState, useCallback, useMemo } from \"react\";\nimport { isEqual } from \"../utils\";\n\n/**\n * Return type for useFormState hook\n */\nexport interface UseFormStateReturn<T> {\n /** Current form data */\n data: T;\n /** Update form data (partial or full) */\n setData: (data: T | ((prev: T) => T)) => void;\n /** Update a single field */\n setField: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Whether form has been modified */\n isDirty: boolean;\n /** Load new data and mark as clean (for switching items) */\n reset: (newData: T) => void;\n /** Revert to original data */\n discard: () => void;\n /** Original data for comparison */\n original: T;\n}\n\n/**\n * Hook for managing form state with dirty tracking\n *\n * Provides state management for forms with automatic change detection.\n * Compares current data against original to determine if form has been modified.\n *\n * @template T - Type of the form data\n * @param initialData - Initial form data\n * @returns Form state and control functions\n *\n * @example\n * ```tsx\n * interface FormData {\n * name: string;\n * email: string;\n * }\n *\n * function MyForm() {\n * const form = useFormState<FormData>({\n * name: '',\n * email: '',\n * });\n *\n * // Update field\n * form.setField('name', 'John');\n *\n * // Check if dirty\n * if (form.isDirty) {\n * console.log('Form has unsaved changes');\n * }\n *\n * // Load new data (e.g., from API)\n * form.reset({ name: 'Jane', email: 'jane@example.com' });\n *\n * // Revert changes\n * form.discard();\n * }\n * ```\n */\nexport function useFormState<T>(initialData: T): UseFormStateReturn<T> {\n const [data, setDataState] = useState<T>(initialData);\n const [original, setOriginal] = useState<T>(initialData);\n\n /**\n * Compute dirty state by comparing current data with original\n */\n const isDirty = useMemo(() => {\n return !isEqual(data, original);\n }, [data, original]);\n\n /**\n * Update form data\n */\n const setData = useCallback((newData: T | ((prev: T) => T)) => {\n setDataState(newData);\n }, []);\n\n /**\n * Update a single field\n */\n const setField = useCallback(<K extends keyof T>(field: K, value: T[K]) => {\n setDataState((prev) => ({\n ...prev,\n [field]: value,\n }));\n }, []);\n\n /**\n * Load new data and mark as clean\n * Use when loading data from API or switching between items\n */\n const reset = useCallback((newData: T) => {\n setDataState(newData);\n setOriginal(newData);\n }, []);\n\n /**\n * Revert to original data\n * Use when user clicks \"Cancel\" or \"Discard Changes\"\n */\n const discard = useCallback(() => {\n setDataState(original);\n }, [original]);\n\n return {\n data,\n setData,\n setField,\n isDirty,\n reset,\n discard,\n original,\n };\n}\n","/**\n * useEntityOptions -- fetch selectable options from any data source.\n *\n * Transport-agnostic: receives an EntityOptionFetcher and returns\n * TanStack Query state. Supports three search modes:\n *\n * eager -- fetches on mount, refetches on debounced search (default)\n * typeahead -- fetches only after the user types >= minChars (debounced)\n * explicit -- fetches only when submit() is called\n *\n * Extractable to @petrarca/sonnet-core/hooks -- no app-specific imports.\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { EntityOption, EntityOptionFetcher } from \"../entityOptions\";\n\n/** Search mode for the entity options hook and EntitySelect component. */\nexport type EntitySelectMode = \"eager\" | \"typeahead\" | \"explicit\";\n\nexport interface UseEntityOptionsConfig {\n /** The fetcher function (created by a fetcher factory or provided directly). */\n fetcher: EntityOptionFetcher;\n /** TanStack Query cache key prefix. Must be stable. */\n queryKey: string;\n /**\n * Search mode. Default: \"eager\".\n * eager -- fetch on mount, refetch on debounced typing\n * typeahead -- fetch only after minChars are typed (debounced)\n * explicit -- fetch only when submit() is called\n */\n mode?: EntitySelectMode;\n /** Debounce delay in ms for the search input (default 300). */\n debounceMs?: number;\n /** Minimum characters before typeahead fires (default 1). Ignored in other modes. */\n minChars?: number;\n /** Maximum options to fetch (default 50). */\n limit?: number;\n /** Disable fetching (e.g. when a dependency is not yet satisfied). */\n enabled?: boolean;\n}\n\nexport interface UseEntityOptionsReturn {\n /** Current options from the server. */\n options: EntityOption[];\n /** Whether a fetch is in progress. */\n isLoading: boolean;\n /** Whether the fetch errored. */\n isError: boolean;\n /** The current search string (set by the combobox input). */\n search: string;\n /** Update the search string (drives debounced refetch). */\n setSearch: (value: string) => void;\n /** Trigger a fetch with the current search string. Only meaningful in explicit mode. */\n submit: () => void;\n}\n\n// Determine whether the query should be enabled based on mode and state.\nfunction resolveEnabled(\n enabled: boolean,\n mode: EntitySelectMode,\n activeSearch: string,\n minChars: number,\n committedSearch: string | null,\n): boolean {\n if (!enabled) return false;\n if (mode === \"typeahead\") return activeSearch.length >= minChars;\n if (mode === \"explicit\") return committedSearch !== null;\n return true;\n}\n\nexport function useEntityOptions({\n fetcher,\n queryKey,\n mode = \"eager\",\n debounceMs = 300,\n minChars = 1,\n limit = 50,\n enabled = true,\n}: UseEntityOptionsConfig): UseEntityOptionsReturn {\n const [search, setSearch] = useState(\"\");\n\n // Debounced search drives eager and typeahead modes.\n const [debouncedSearch, setDebouncedSearch] = useState(\"\");\n // Committed search drives explicit mode -- only updated on submit().\n const [committedSearch, setCommittedSearch] = useState<string | null>(null);\n\n useEffect(() => {\n if (mode === \"explicit\") return;\n const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);\n return () => clearTimeout(timer);\n }, [search, debounceMs, mode]);\n\n const submit = useCallback(() => {\n setCommittedSearch(search);\n }, [search]);\n\n const activeSearch =\n mode === \"explicit\" ? (committedSearch ?? \"\") : debouncedSearch;\n\n const queryEnabled = resolveEnabled(\n enabled,\n mode,\n activeSearch,\n minChars,\n committedSearch,\n );\n\n const { data, isLoading, isError } = useQuery({\n queryKey: [queryKey, \"options\", { search: activeSearch, limit }],\n queryFn: () => fetcher({ search: activeSearch || undefined, limit }),\n enabled: queryEnabled,\n staleTime: 5 * 60 * 1000,\n });\n\n const options: EntityOption[] = data ?? [];\n\n return { options, isLoading, isError, search, setSearch, submit };\n}\n"],"mappings":"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","names":["useState","useCallback","useState","useCallback","useState","useCallback","useState","useCallback","useState","useCallback","useState","useEffect","useCallback"]}
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":"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","names":["useState","useCallback","useState","useCallback","useState","useState","useCallback","useEffect","useState","useCallback","useState","useCallback","useState","useEffect","useCallback"]}
@@ -39,6 +39,31 @@ interface UIRule {
39
39
  effect: UIRuleEffect;
40
40
  condition: UIRuleCondition;
41
41
  }
42
+ /**
43
+ * A named validator reference carried by `x-ui-validate`. The bare-string form
44
+ * is the no-config common case (`"ibanValid"`); the object form supplies
45
+ * per-reference config and, for async form validators, the inputs that trigger
46
+ * a re-run (`dependsOn`). See the custom-validation design in sonnet-forms.
47
+ */
48
+ interface UIValidateRef {
49
+ /** Registered validator name, resolved against the ValidatorRegistry. */
50
+ name: string;
51
+ /** Per-reference config, passed to the validator as `ctx.args`. */
52
+ args?: Record<string, unknown>;
53
+ /**
54
+ * For an async FORM validator: the field names whose values trigger a re-run.
55
+ * The engine keys/debounces the async result on these values. Ignored for
56
+ * sync validators and for field validators.
57
+ */
58
+ dependsOn?: string[];
59
+ }
60
+ /**
61
+ * The value of `x-ui-validate`: one validator or a list. On a property it
62
+ * selects field validators (`(value, ctx) => ...`); on the root object it
63
+ * selects form validators (`(data, ctx) => FieldError[]`). A bare string is
64
+ * shorthand for `{ name }`.
65
+ */
66
+ type UIValidate = string | UIValidateRef | Array<string | UIValidateRef>;
42
67
  /**
43
68
  * JSON Schema property definition following Draft 2020-12 specification.
44
69
  * Supports basic types, validation constraints, conditionals, and custom extensions.
@@ -90,6 +115,7 @@ type JsonSchemaProperty = {
90
115
  "x-ui-title"?: string;
91
116
  "x-ui-placeholder"?: string;
92
117
  "x-ui-description"?: string;
118
+ "x-ui-validate"?: UIValidate;
93
119
  "x-ui-column"?: {
94
120
  /**
95
121
  * Display order (ascending). Properties without an order are appended
@@ -131,6 +157,7 @@ type JsonSchema = {
131
157
  $defs?: Record<string, JsonSchemaProperty>;
132
158
  definitions?: Record<string, JsonSchemaProperty>;
133
159
  "x-ui-order"?: string[];
160
+ "x-ui-validate"?: UIValidate;
134
161
  [key: `x-${string}`]: unknown;
135
162
  };
136
163
  /**
@@ -163,6 +190,8 @@ interface UISchema {
163
190
  "x-ui-order"?: string[];
164
191
  /** Declarative show/hide or enable/disable rule (see UIRule). */
165
192
  "x-ui-rule"?: UIRule;
193
+ /** Custom validators by name (override of the schema-level x-ui-validate). */
194
+ "x-ui-validate"?: UIValidate;
166
195
  "x-ui-title"?: string | false;
167
196
  "x-ui-description"?: string;
168
197
  "x-ui-placeholder"?: string;
@@ -741,9 +770,69 @@ interface ValidationResult {
741
770
  /**
742
771
  * Validate form data against a JSON Schema, recursing into nested objects
743
772
  * and array items.
773
+ *
774
+ * Two layers: this function owns required-ness and array cardinality with
775
+ * human-authored messages; `validateContentConstraints` adds the JSON Schema
776
+ * content constraints (format, pattern, type, length, range, enum, const) via
777
+ * AJV. Both contribute to the same `FieldError[]`, so downstream attribution
778
+ * (per-field `rawErrors`, per-tab error badges) is unchanged.
744
779
  */
745
780
  declare function validateFormData(schema: JsonSchema, data: Record<string, unknown>): ValidationResult;
746
781
 
782
+ /**
783
+ * Content-constraint validation, layered on top of validateFormData.
784
+ *
785
+ * validateFormData covers required-ness and array cardinality with
786
+ * human-authored messages. This module adds the JSON Schema *content*
787
+ * constraints it does not: `format`, `pattern`, `type`, `minLength` /
788
+ * `maxLength`, `minimum` / `maximum` (and their exclusive variants),
789
+ * `enum`, `const`, and `multipleOf`. It reuses the shared AJV instance
790
+ * (already configured with `ajv-formats`) and maps AJV's errors into the
791
+ * same `FieldError` shape the renderer consumes -- AJV's `instancePath` is
792
+ * already an RFC 6901 JSON Pointer, so per-field and per-tab attribution
793
+ * keep working unchanged.
794
+ *
795
+ * Required and array-cardinality keywords are intentionally excluded here so
796
+ * validateFormData stays the single source of those messages -- no field ever
797
+ * shows two errors for the same cause.
798
+ */
799
+
800
+ /**
801
+ * Validate content constraints via AJV and return them as FieldErrors.
802
+ *
803
+ * Only content keywords are returned; required and array-cardinality errors
804
+ * are filtered out (validateFormData owns those). Returns an empty array when
805
+ * the schema compiles cleanly and the data satisfies every content rule, or
806
+ * when AJV cannot compile the schema (in which case the caller's structural
807
+ * validation still stands).
808
+ */
809
+ declare function validateContentConstraints(schema: JsonSchema, data: Record<string, unknown>): FieldError[];
810
+
811
+ /**
812
+ * Merge FieldError lists from several validation sources into one, de-duplicated
813
+ * by field so a field never shows two errors for the same cause.
814
+ *
815
+ * Sources are passed in precedence order (strongest first): structural/content
816
+ * from validateFormData, then custom validators, then external/extra errors.
817
+ * The first source to flag a given field (by its pointer's root segment) wins;
818
+ * later sources add errors only for fields not already flagged. This extends the
819
+ * existing rule inside validateFormData (a content error is skipped on a field
820
+ * that already has a structural error) across every source.
821
+ *
822
+ * Form-level errors (pointer "") are never de-duplicated against fields -- they
823
+ * belong to the form, not a field -- but identical form-level messages collapse.
824
+ *
825
+ * Pure function -- no React, no side effects.
826
+ */
827
+
828
+ /**
829
+ * Merge ordered FieldError sources. Earlier sources take precedence per field.
830
+ *
831
+ * @param sources FieldError lists in precedence order (strongest first).
832
+ * @returns one de-duplicated list preserving each kept error's order.
833
+ */
834
+ declare function mergeFieldErrors(...sources: FieldError[][]): FieldError[];
835
+
747
836
  /**
748
837
  * diffFormData -- structural diff between two form data snapshots.
749
838
  *
@@ -786,4 +875,4 @@ declare function diffFormData(original: Record<string, unknown>, updated: Record
786
875
  */
787
876
  declare function ensureFormItemIds(data: Record<string, unknown>, schema: JsonSchema): Record<string, unknown>;
788
877
 
789
- export { type ArrayItemChange, type FieldDiff, type FieldError, type FormDiff, type JsonSchema, type JsonSchemaProperty, type RefResolver, type RuleState, type UILayoutConfig, type UILayoutGroupConfig, type UILayoutRowConfig, type UIRule, type UIRuleCondition, type UIRuleEffect, type UISchema, type UISchemaOptions, type ValidationResult, type ValueChange, WIDTH_TOKEN_REMS, type WidthToken, ajv, applySchemaDefaults, diffFormData, ensureFormItemIds, evaluateUiRule, extractEnumValues, extractSchemaDefaults, findActiveBranch, generateJsonSchemaFromVariables, getAtPointer, getBranchLabel, getBranchValue, getDiscriminator, getFieldConstraints, getFieldDefault, getFieldHelpText, getFieldPlaceholder, getFieldTitle, getFieldType, getFieldWidget, getOptionalFields, getRequiredFields, getSchemaProperties, hasConditionals, hasProperties, isFieldOptional, isFieldRequired, isNullableBoolean, isOneOf, matchesConst, resolveRefs, resolveSchema, toJsonSchema, validateFormData, validateSchema };
878
+ export { type ArrayItemChange, type FieldDiff, type FieldError, type FormDiff, type JsonSchema, type JsonSchemaProperty, type RefResolver, type RuleState, type UILayoutConfig, type UILayoutGroupConfig, type UILayoutRowConfig, type UIRule, type UIRuleCondition, type UIRuleEffect, type UISchema, type UISchemaOptions, type UIValidate, type UIValidateRef, type ValidationResult, type ValueChange, WIDTH_TOKEN_REMS, type WidthToken, ajv, applySchemaDefaults, diffFormData, ensureFormItemIds, evaluateUiRule, extractEnumValues, extractSchemaDefaults, findActiveBranch, generateJsonSchemaFromVariables, getAtPointer, getBranchLabel, getBranchValue, getDiscriminator, getFieldConstraints, getFieldDefault, getFieldHelpText, getFieldPlaceholder, getFieldTitle, getFieldType, getFieldWidget, getOptionalFields, getRequiredFields, getSchemaProperties, hasConditionals, hasProperties, isFieldOptional, isFieldRequired, isNullableBoolean, isOneOf, matchesConst, mergeFieldErrors, resolveRefs, resolveSchema, toJsonSchema, validateContentConstraints, validateFormData, validateSchema };
@@ -147,7 +147,12 @@ var ajv = new Ajv({
147
147
  strict: false,
148
148
  validateFormats: true,
149
149
  // Allow unknown keywords (for x-ui-widget, x-ui-title, etc.)
150
- strictSchema: false
150
+ strictSchema: false,
151
+ // Enable $data references so a keyword can reference another field's value,
152
+ // e.g. { "const": { "$data": "1/password" } } for "must equal password".
153
+ // This is the declarative path for simple cross-field rules (see the
154
+ // custom-validation design); complex cross-field logic uses form validators.
155
+ $data: true
151
156
  });
152
157
  addFormats(ajv);
153
158
  function resolveSchema(schema, formData) {
@@ -467,6 +472,63 @@ function resolveRefs(schema, resolve) {
467
472
  return resolved;
468
473
  }
469
474
 
475
+ // src/schema/contentValidation.ts
476
+ var OWNED_BY_VALIDATE_FORM_DATA = /* @__PURE__ */ new Set([
477
+ "required",
478
+ "minItems",
479
+ "maxItems"
480
+ ]);
481
+ function unescapePointerToken(token) {
482
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
483
+ }
484
+ function labelForPointer(schema, pointer) {
485
+ if (pointer === "") return "";
486
+ const tokens = pointer.split("/").slice(1).map(unescapePointerToken);
487
+ const parts = [];
488
+ let node = schema;
489
+ for (const token of tokens) {
490
+ if (/^\d+$/.test(token)) {
491
+ if (parts.length > 0) {
492
+ parts[parts.length - 1] = `${parts[parts.length - 1]}[${Number(token) + 1}]`;
493
+ }
494
+ node = node?.items;
495
+ continue;
496
+ }
497
+ const props = node?.properties;
498
+ const child = props?.[token];
499
+ parts.push(child ? getFieldTitle(token, child) : token);
500
+ node = child;
501
+ }
502
+ return parts.join(" > ");
503
+ }
504
+ function messageFor(label, keyword, message) {
505
+ const what = label || "Value";
506
+ const detail = message ?? "is invalid";
507
+ return `${what} ${detail}`;
508
+ }
509
+ function validateContentConstraints(schema, data) {
510
+ let validate;
511
+ try {
512
+ validate = ajv.compile(schema);
513
+ } catch {
514
+ return [];
515
+ }
516
+ if (validate(data)) return [];
517
+ const out = [];
518
+ const seen = /* @__PURE__ */ new Set();
519
+ for (const err of validate.errors ?? []) {
520
+ if (OWNED_BY_VALIDATE_FORM_DATA.has(err.keyword)) continue;
521
+ const pointer = err.instancePath;
522
+ const label = labelForPointer(schema, pointer);
523
+ const message = messageFor(label, err.keyword, err.message);
524
+ const key = `${pointer}\0${message}`;
525
+ if (seen.has(key)) continue;
526
+ seen.add(key);
527
+ out.push({ pointer, message });
528
+ }
529
+ return out;
530
+ }
531
+
470
532
  // src/schema/validateFormData.ts
471
533
  function escapePointerToken(token) {
472
534
  return token.replace(/~/g, "~0").replace(/\//g, "~1");
@@ -557,6 +619,11 @@ function validateFormData(schema, data) {
557
619
  { label: "", pointer: "" },
558
620
  fieldErrors
559
621
  );
622
+ const structurallyFlagged = new Set(fieldErrors.map((e) => e.pointer));
623
+ for (const err of validateContentConstraints(schema, data)) {
624
+ if (structurallyFlagged.has(err.pointer)) continue;
625
+ fieldErrors.push(err);
626
+ }
560
627
  return {
561
628
  errors: fieldErrors.map((e) => e.message),
562
629
  fieldErrors,
@@ -564,6 +631,34 @@ function validateFormData(schema, data) {
564
631
  };
565
632
  }
566
633
 
634
+ // src/schema/mergeFieldErrors.ts
635
+ function pointerRoot(pointer) {
636
+ const token = pointer.split("/")[1] ?? "";
637
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
638
+ }
639
+ function mergeFieldErrors(...sources) {
640
+ const out = [];
641
+ const claimedFields = /* @__PURE__ */ new Set();
642
+ const seenFormMessages = /* @__PURE__ */ new Set();
643
+ for (const source of sources) {
644
+ for (const err of source) {
645
+ if (err.pointer === "") {
646
+ if (seenFormMessages.has(err.message)) continue;
647
+ seenFormMessages.add(err.message);
648
+ out.push(err);
649
+ continue;
650
+ }
651
+ const root = pointerRoot(err.pointer);
652
+ if (claimedFields.has(root)) continue;
653
+ out.push(err);
654
+ }
655
+ for (const err of source) {
656
+ if (err.pointer !== "") claimedFields.add(pointerRoot(err.pointer));
657
+ }
658
+ }
659
+ return out;
660
+ }
661
+
567
662
  // src/schema/diffFormData.ts
568
663
  function areBothPlainObjects(a, b) {
569
664
  return a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b);
@@ -776,9 +871,11 @@ export {
776
871
  isNullableBoolean,
777
872
  isOneOf,
778
873
  matchesConst,
874
+ mergeFieldErrors,
779
875
  resolveRefs,
780
876
  resolveSchema,
781
877
  toJsonSchema,
878
+ validateContentConstraints,
782
879
  validateFormData,
783
880
  validateSchema
784
881
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/types.ts","../../src/schema/schemaService.ts","../../src/schema/variant.ts","../../src/schema/schemaResolver.ts","../../src/schema/uiRule.ts","../../src/schema/schemaUtils.ts","../../src/schema/zodSchema.ts","../../src/schema/resolveRefs.ts","../../src/schema/validateFormData.ts","../../src/schema/diffFormData.ts","../../src/schema/ensureFormItemIds.ts"],"sourcesContent":["/**\n * Generic JSON Schema type definitions.\n *\n * These types follow Draft 2020-12 and include x-ui-* extension points\n * for form/widget integration. They are domain-agnostic -- no\n * application-specific entity concepts live here.\n */\n\n/**\n * A declarative presentation rule (JSONForms-style) attached to a field.\n *\n * The rule lives on the field it controls (the target). Its `condition` points\n * at another field's value via a JSON Pointer (RFC 6901) into the form DATA\n * (rooted at the whole form), and validates that value against `condition.schema`.\n * When the condition holds, `effect` is applied to the target:\n * - \"show\" -- render only when the condition holds\n * - \"hide\" -- render only when the condition does NOT hold\n * - \"enable\" -- interactive only when the condition holds\n * - \"disable\"-- non-interactive when the condition holds\n *\n * Example: show `pregnancy_status` when `gender` equals \"female\":\n * ```json\n * { \"effect\": \"show\",\n * \"condition\": { \"scope\": \"/gender\", \"schema\": { \"const\": \"female\" } } }\n * ```\n * Unlike if/then/else, the target field stays declared at its own position, so\n * layout placement is stable.\n */\nexport interface UIRuleCondition {\n /** JSON Pointer (RFC 6901) into the form data, e.g. \"/gender\" or \"/patient/gender\". */\n scope: string;\n /** Schema the value at `scope` is validated against (e.g. { const: \"female\" }). */\n schema: JsonSchemaProperty;\n}\n\nexport type UIRuleEffect = \"show\" | \"hide\" | \"enable\" | \"disable\";\n\nexport interface UIRule {\n effect: UIRuleEffect;\n condition: UIRuleCondition;\n}\n\n/**\n * JSON Schema property definition following Draft 2020-12 specification.\n * Supports basic types, validation constraints, conditionals, and custom extensions.\n */\nexport type JsonSchemaProperty = {\n type?:\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"array\"\n | \"object\"\n | \"null\";\n title?: string;\n description?: string;\n default?: any;\n\n // Validation constraints\n const?: any;\n enum?: any[];\n pattern?: string;\n format?: string;\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n exclusiveMinimum?: number;\n exclusiveMaximum?: number;\n\n // Array types\n items?: JsonSchemaProperty;\n minItems?: number;\n maxItems?: number;\n\n // Object types\n properties?: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Schema references ($ref to $defs / definitions within the same document)\n $ref?: string;\n\n // Reusable sub-schema definitions. Canonical at the document root, but valid\n // at any subschema level (Draft 2020-12: $defs, Draft 7: definitions).\n $defs?: Record<string, JsonSchemaProperty>;\n definitions?: Record<string, JsonSchemaProperty>;\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n /**\n * Discriminator for a `oneOf` union (OpenAPI convention). `propertyName` is\n * the shared property whose `const` value tags the active branch. When\n * absent, the discriminator is auto-detected as the property that is const\n * in every branch.\n */\n discriminator?: { propertyName: string };\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Field ordering within nested objects (same as JsonSchema's x-ui-order)\n \"x-ui-order\"?: string[];\n\n // Declarative presentation rule: show/hide or enable/disable this field based\n // on another field's value. Unlike if/then/else (which adds/removes the field\n // structurally), the field stays declared at its position; the rule only\n // toggles its presentation. See UIRule.\n \"x-ui-rule\"?: UIRule;\n\n // Custom UI hints (x-ui-* extensions align with UISchema)\n \"x-ui-widget\"?: string;\n \"x-ui-options\"?: Record<string, any>;\n \"x-ui-layout\"?: UILayoutConfig;\n \"x-ui-title\"?: string;\n \"x-ui-placeholder\"?: string;\n \"x-ui-description\"?: string;\n\n // Table column extensions\n \"x-ui-column\"?: {\n /**\n * Display order (ascending). Properties without an order are appended\n * after the ordered ones, keeping their schema declaration order.\n */\n order?: number;\n /** Exclude this column from the table entirely. */\n hidden?: boolean;\n /** Fixed column width in pixels. */\n width?: number;\n /** Minimum column width in pixels. */\n minWidth?: number;\n };\n /** Cell renderer name. Inferred from type+format when absent. */\n \"x-ui-cell\"?: string;\n /** Cell renderer configuration options (colors, href template, etc.). */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n};\n\n/**\n * JSON Schema following Draft 2020-12 specification.\n *\n * This is the generic base type. Domain-specific consumers extend it\n * with their own x-* extension keys via intersection types in their\n * own type files.\n */\nexport type JsonSchema = {\n $schema?: string;\n title: string;\n type: \"object\";\n properties: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Reusable sub-schema definitions (Draft 2020-12: $defs, Draft 7: definitions)\n $defs?: Record<string, JsonSchemaProperty>;\n definitions?: Record<string, JsonSchemaProperty>;\n\n // UI customization (optional)\n \"x-ui-order\"?: string[];\n\n // Allow domain-specific extension keys (x-entity, x-graph, x-custom, etc.)\n [key: `x-${string}`]: unknown;\n};\n\n/**\n * WidthToken -- the shared semantic sizing vocabulary used across the form\n * layout system: per-field slot width (x-ui-width) and per-column width\n * (UILayoutRowConfig.widths). \"full\" fills available space; sm/md/lg are\n * fixed inline sizes. Raw CSS remains available as an advanced escape hatch.\n */\nexport type WidthToken = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * The fixed rem values for each non-full WidthToken. This is the single source\n * of truth for token sizing used by:\n * - the CSS grid track map in JsonSchemaFormRenderer (sm -> \"8rem\" track)\n * - the Tailwind class map in FormFieldRenderer (sm -> w-32 = 8rem)\n * Consumers that need px can multiply by 16.\n */\nexport const WIDTH_TOKEN_REMS: Record<WidthToken, string> = {\n sm: \"8rem\",\n md: \"12rem\",\n lg: \"16rem\",\n full: \"1fr\", // grid track only; field slots use w-full instead\n};\n\n/**\n * UISchema - User Interface Schema configuration.\n *\n * Defines how form fields should be rendered and behave in the UI.\n * Separate from JSON Schema (which defines data structure) and focuses\n * on presentation, widgets, and user interaction.\n */\nexport interface UISchema {\n /** Widget name (string) or direct component reference */\n \"x-ui-widget\"?: string | object;\n \"x-ui-options\"?: UISchemaOptions;\n \"x-ui-globalOptions\"?: UISchemaOptions;\n \"x-ui-order\"?: string[];\n /** Declarative show/hide or enable/disable rule (see UIRule). */\n \"x-ui-rule\"?: UIRule;\n \"x-ui-title\"?: string | false;\n \"x-ui-description\"?: string;\n \"x-ui-placeholder\"?: string;\n /** Semantic field-slot width (default \"full\"). */\n \"x-ui-width\"?: WidthToken;\n /** Column override -- merged over schema-level x-ui-column, UI schema wins. */\n \"x-ui-column\"?: JsonSchemaProperty[\"x-ui-column\"] & { label?: string };\n /** Cell renderer override. */\n \"x-ui-cell\"?: string;\n /** Cell renderer options override -- shallow-merged over schema-level x-ui-cell-options. */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n /** Per-field UISchema for nested sub-forms (ObjectWidget / ArrayWidget) */\n \"x-ui-fields\"?: { [fieldName: string]: UISchema };\n /** Layout config forwarded to the active template (ObjectWidget / ArrayWidget) */\n \"x-ui-layout\"?: UILayoutConfig;\n /** Extension keys not yet enumerated above */\n [key: `x-ui-${string}`]: unknown;\n}\n\n/**\n * UILayoutRowConfig -- one horizontal row in a multi-column object form layout.\n *\n * `fields` lists the field names to place in this row, left to right.\n * Column sizing (grid mode) is resolved in this precedence:\n * 1. `columns` -- raw CSS grid-template-columns (advanced escape hatch).\n * 2. `widths` -- per-cell semantic tokens (the designer-friendly path).\n * 3. default -- equal 1fr per field.\n * `gap` overrides the parent layout gap for this row only.\n *\n * `group` renders the row inside a titled fieldset (presentation-only grouping\n * over flat schema fields -- the data shape is unchanged).\n *\n * Examples:\n * { fields: [\"first_name\", \"last_name\"], widths: [\"md\", \"full\"] }\n * { fields: [\"npi\", \"specialty\", \"email\"], columns: \"20% 1fr 1fr\" }\n * { group: \"Contact\", fields: [\"email\", \"phone\"] }\n */\nexport interface UILayoutRowConfig {\n fields: string[];\n columns?: string;\n /**\n * Per-cell semantic widths, aligned to `fields` by index. Translated to grid\n * tracks: sm/md/lg -> fixed rem tracks, full -> 1fr. Ignored when `columns`\n * is set (raw CSS wins) or in \"flow\" mode. Missing/short entries default to\n * \"full\".\n */\n widths?: WidthToken[];\n gap?: number;\n /**\n * Optional title. When set, the row renders inside a <fieldset> with a\n * <legend>, grouping its fields visually without nesting them in the schema.\n */\n group?: string;\n /**\n * How this row arranges its fields:\n * - \"grid\" (default): CSS grid using `columns`/`widths` (equal 1fr per field\n * when neither is set).\n * - \"flow\": flex row, fields at their natural width, left-to-right, wrapping.\n * Use for compact toolbars / tight field groups. `columns`/`widths` are\n * ignored.\n */\n arrange?: \"grid\" | \"flow\";\n}\n\n/**\n * One named group of a grouped form layout. A group holds rows of fields under\n * a label; the data stays flat (grouping is presentation only, like `group` on\n * a single row). How groups are shown -- tabs, wizard steps, stacked sections\n * -- is chosen by {@link UILayoutConfig.variant}, not by this structure.\n *\n * A group may carry an `x-ui-rule`-style {@link UIRule} to appear conditionally\n * (e.g. show an \"Address\" group only when \"provide address\" is checked). The\n * rule is evaluated against the form data exactly as a field-level rule is.\n */\nexport interface UILayoutGroupConfig {\n /** Label shown on the group's trigger or heading. */\n label: string;\n /** Rows of fields in this group (same shape as UILayoutConfig.rows). */\n rows: UILayoutRowConfig[];\n /**\n * Optional show/hide rule for the whole group: a hidden group disappears\n * along with its fields. Narrowed to the visibility effects -- a disabled\n * group has no meaning, and permitting the effect would let it be authored\n * and then silently ignored.\n */\n rule?: UIRule & { effect: \"show\" | \"hide\" };\n}\n\n/**\n * UILayoutConfig -- layout configuration for ObjectWidget and ArrayWidget.\n *\n * Configures the built-in default templates. Has no effect when a custom\n * template is registered for the field.\n */\nexport interface UILayoutConfig {\n // Selects which built-in item template to use (default: \"vertical\")\n direction?: \"vertical\" | \"horizontal\";\n\n // Multi-column row layout for object forms. Each entry defines one\n // horizontal row of fields. Fields not mentioned in any row are rendered\n // in a vertical stack below the rows.\n rows?: UILayoutRowConfig[];\n\n // Grouped layout. When present (and non-empty), fields are arranged into\n // named groups and `rows` is ignored -- each group carries its own rows.\n // Fields in no group render in a vertical stack below, so nothing can be\n // hidden by accident.\n groups?: UILayoutGroupConfig[];\n\n // How `groups` are presented: \"tabs\" (default) a tab bar, \"stepper\" a wizard,\n // \"sections\" stacked fieldsets. The value names a template in the renderer's\n // TemplateRegistry (\"wizard\" -> WizardGroupTemplate), so a consumer can\n // register its own presentation and select it here. Unknown values fall back\n // to tabs.\n variant?: \"tabs\" | \"stepper\" | \"sections\" | (string & {});\n\n // CSS grid-template-columns for field cells (horizontal mode only).\n // The remove-button column (32px) is appended automatically when canRemove\n // is true -- do not include it here.\n // Default: equal 1fr columns for each field.\n columns?: string;\n\n // Gap between cells in pixels (default: 24 vertical, 8 horizontal)\n gap?: number;\n\n // Show per-field labels (default: true vertical, false horizontal)\n labels?: boolean;\n\n // Compact widget sizing -- h-8 / text-sm instead of full height\n compact?: boolean;\n\n // Label placement for fields in this container: \"top\" (default) renders the\n // label above the control; \"start\" renders it inline on the reading-start\n // side (RTL-safe: left in LTR, right in RTL). Logical values follow the MUI\n // convention. Inherited by the container's fields unless overridden per field\n // via x-ui-options.\n labelPlacement?: \"top\" | \"start\";\n}\n\n/**\n * ValueChange -- a single scalar or nested-object field that changed value.\n *\n * `path` is a dot-separated field path: \"name\", \"address.city\".\n */\nexport type ValueChange = {\n type: \"value\";\n path: string;\n from: unknown;\n to: unknown;\n};\n\n/**\n * ArrayItemChange -- one item-level change within an array field.\n *\n * Items are identified by their form-internal `_fid` (never the server ID).\n * A single item can appear as both \"moved\" and \"modified\" if it was\n * repositioned and its values also changed -- these are emitted as two\n * separate entries with the same `fid`.\n */\nexport type ArrayItemChange =\n | { type: \"added\"; fid: string; index: number; item: unknown }\n | { type: \"removed\"; fid: string; index: number; item: unknown }\n | { type: \"moved\"; fid: string; from: number; to: number }\n | { type: \"modified\"; fid: string; index: number; changes: ValueChange[] };\n\n/**\n * FieldDiff -- the diff for one top-level form field.\n */\nexport type FieldDiff =\n | ValueChange\n | { type: \"array\"; field: string; changes: ArrayItemChange[] };\n\n/**\n * FormDiff -- structured diff produced by diffFormData().\n *\n * One entry per changed top-level field. Fields whose value is deeply\n * equal between original and updated are omitted.\n */\nexport type FormDiff = FieldDiff[];\n\n/**\n * UISchemaOptions - Widget configuration options.\n */\nexport interface UISchemaOptions {\n label?: string | false;\n disabled?: boolean;\n readonly?: boolean;\n compact?: boolean;\n enumNames?: Record<string | number, string>;\n inputType?: string;\n rows?: number;\n searchable?: boolean;\n clearable?: boolean;\n /** Allow the user to type and submit a value not in the enum list (combobox / open-choice). */\n creatable?: boolean;\n /** Label placement: \"top\" (default, above) or \"start\" (inline, RTL-safe). */\n labelPlacement?: \"top\" | \"start\";\n /** Semantic field-slot width (default \"full\"). */\n width?: WidthToken;\n [key: string]: any;\n}\n","/**\n * Schema Service - Pure functions for JSON Schema manipulation.\n *\n * Generic, domain-agnostic utilities for reading and inspecting JSON\n * Schema structures. No React, no HTTP, no side effects.\n *\n * Domain-specific accessors (x-entity, x-graph) live in the consumer's\n * own service layer and import from here.\n */\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nexport function getSchemaProperties(\n schema: JsonSchema,\n): Record<string, JsonSchemaProperty> {\n return schema.properties || {};\n}\n\nexport function isFieldRequired(\n schema: JsonSchema,\n fieldName: string,\n): boolean {\n return schema.required?.includes(fieldName) ?? false;\n}\n\nexport function getFieldDefault(property: JsonSchemaProperty): unknown {\n return property.default;\n}\n\ntype JsonSchemaType =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\n/**\n * Get the effective type of a property.\n *\n * Unwraps the nullable anyOf pattern produced by Zod / Pydantic Optional[T]:\n * `{ anyOf: [{ type: T }, { type: \"null\" }] }` → T.\n * Falls back to \"string\" when no type can be determined.\n */\nexport function getFieldType(property: JsonSchemaProperty): JsonSchemaType {\n if (Array.isArray(property.anyOf)) {\n const nonNull = property.anyOf.find(\n (t): t is { type: JsonSchemaType } => \"type\" in t && t.type !== \"null\",\n );\n if (nonNull) return nonNull.type;\n }\n return (property.type as JsonSchemaType) || \"string\";\n}\n\n/**\n * Returns true when a property uses the Pydantic Optional[bool] pattern:\n * anyOf containing both a boolean member and a null member.\n * Used for tri-state checkbox rendering.\n */\nexport function isNullableBoolean(property: JsonSchemaProperty): boolean {\n if (!Array.isArray(property.anyOf)) return false;\n const hasBoolean = property.anyOf.some((s) => s.type === \"boolean\");\n const hasNull = property.anyOf.some((s) => s.type === \"null\");\n return hasBoolean && hasNull;\n}\n\n/**\n * Extract enum values from a property schema.\n *\n * Handles both the simple case (`{ enum: [...] }`) and the nullable\n * pattern produced by Zod (`{ anyOf: [{ type: \"string\", enum: [...] }, { type: \"null\" }] }`).\n *\n * Returns the enum array if found, or an empty array.\n */\nexport function extractEnumValues(\n property: JsonSchemaProperty,\n): (string | number | boolean)[] {\n if (Array.isArray(property.enum) && property.enum.length > 0) {\n return property.enum;\n }\n if (Array.isArray(property.anyOf)) {\n for (const branch of property.anyOf) {\n if (Array.isArray(branch.enum) && branch.enum.length > 0) {\n return branch.enum;\n }\n }\n }\n return [];\n}\n\n/** Priority: x-ui-title > title > field name. */\nexport function getFieldTitle(\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return property[\"x-ui-title\"] || property.title || fieldName;\n}\n\n/**\n * Help text for a field, rendered under the control.\n *\n * `x-ui-description` overrides the standard JSON Schema `description`. There is\n * one help-text slot per field, so a schema that only carries `description` --\n * anything generated from a typed model, or authored in the designer's Data tab\n * -- shows it without needing a UI hint.\n */\nexport function getFieldHelpText(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-description\"] ?? property.description;\n}\n\nexport function getFieldPlaceholder(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-placeholder\"];\n}\n\nexport function getFieldWidget(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-widget\"];\n}\n\nexport function isFieldOptional(\n schema: JsonSchema,\n fieldName: string,\n property: JsonSchemaProperty,\n): boolean {\n if (isFieldRequired(schema, fieldName)) {\n return false;\n }\n if (property.anyOf && Array.isArray(property.anyOf)) {\n return property.anyOf.some((t) => \"type\" in t && t.type === \"null\");\n }\n return true;\n}\n\nexport function getFieldConstraints(property: JsonSchemaProperty): {\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n format?: string;\n} {\n return {\n minLength: property.minLength,\n maxLength: property.maxLength,\n minimum: property.minimum,\n maximum: property.maximum,\n pattern: property.pattern,\n format: property.format,\n };\n}\n\nexport function getRequiredFields(schema: JsonSchema): string[] {\n return schema.required || [];\n}\n\nexport function getOptionalFields(schema: JsonSchema): string[] {\n const allFields = Object.keys(schema.properties || {});\n const required = schema.required || [];\n return allFields.filter((field) => !required.includes(field));\n}\n\nexport function hasProperties(schema: JsonSchema): boolean {\n return Object.keys(schema.properties || {}).length > 0;\n}\n","/**\n * oneOf (discriminated-union) helpers.\n *\n * A oneOf property is an object whose shape is one of several branches, modeled\n * as `oneOf: [branchSchema, ...]`. Each branch shares a discriminator property\n * -- a field with a `const` value that tags which branch is active. The\n * discriminator is named by `discriminator.propertyName` (OpenAPI convention),\n * or auto-detected as the property that carries a `const` in every branch.\n *\n * These helpers are pure and domain-agnostic.\n */\n\nimport type { JsonSchemaProperty } from \"./types\";\n\n/** Whether a property is a oneOf union (has a non-empty `oneOf`). */\nexport function isOneOf(property: JsonSchemaProperty): boolean {\n return Array.isArray(property.oneOf) && property.oneOf.length > 0;\n}\n\n/**\n * Resolve the discriminator property name for a oneOf union.\n *\n * Uses `discriminator.propertyName` when set; otherwise auto-detects the single\n * property that carries a `const` in every branch. Returns undefined when no\n * consistent discriminator can be determined.\n */\nexport function getDiscriminator(\n property: JsonSchemaProperty,\n): string | undefined {\n const explicit = property.discriminator?.propertyName;\n if (typeof explicit === \"string\" && explicit) return explicit;\n\n const branches = property.oneOf;\n if (!Array.isArray(branches) || branches.length === 0) return undefined;\n\n // Candidate keys: const-valued properties in the first branch.\n const first = branches[0].properties ?? {};\n const candidates = Object.keys(first).filter(\n (k) => first[k]?.const !== undefined,\n );\n\n // Keep only candidates that are const in every branch.\n return candidates.find((key) =>\n branches.every((b) => b.properties?.[key]?.const !== undefined),\n );\n}\n\n/** The discriminator const value for a branch, or undefined. */\nexport function getBranchValue(\n branch: JsonSchemaProperty,\n discriminator: string,\n): string | number | boolean | undefined {\n const c = branch.properties?.[discriminator]?.const;\n if (\n typeof c === \"string\" ||\n typeof c === \"number\" ||\n typeof c === \"boolean\"\n ) {\n return c;\n }\n return undefined;\n}\n\n/** Human label for a branch: its `title`, else the discriminator value. */\nexport function getBranchLabel(\n branch: JsonSchemaProperty,\n discriminator: string | undefined,\n index: number,\n): string {\n if (branch.title) return branch.title;\n if (discriminator) {\n const v = getBranchValue(branch, discriminator);\n if (v !== undefined) return String(v);\n }\n return `Option ${index + 1}`;\n}\n\n/**\n * Find the branch that matches the current form data by comparing the\n * discriminator value. Returns the branch index, or -1 when none match.\n */\nexport function findActiveBranch(\n property: JsonSchemaProperty,\n data: Record<string, unknown> | undefined,\n discriminator: string | undefined,\n): number {\n const branches = property.oneOf;\n if (!Array.isArray(branches) || !discriminator || !data) return -1;\n const current = data[discriminator];\n if (current === undefined) return -1;\n return branches.findIndex(\n (b) => getBranchValue(b, discriminator) === current,\n );\n}\n","/**\n * Schema Resolver - Resolves conditional schemas (if-then-else, allOf, etc.)\n *\n * This service handles JSON Schema conditionals by evaluating conditions\n * based on form data and merging the appropriate schema branches.\n *\n * Based on react-jsonschema-form's retrieveSchema implementation.\n *\n * Supported features:\n * - if-then-else conditionals\n * - allOf schema merging\n * - Nested conditionals (if-then-else inside allOf)\n *\n * @module services/schemaResolver\n */\n\nimport Ajv from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { JsonSchema } from \"./types\";\nimport { warnLog } from \"../logger\";\n\n// Singleton AJV instance. Exported so sibling modules (e.g. uiRule) validate\n// with the same configuration rather than spinning up a second compiler.\nexport const ajv = new Ajv({\n allErrors: true,\n strict: false,\n validateFormats: true,\n // Allow unknown keywords (for x-ui-widget, x-ui-title, etc.)\n strictSchema: false,\n});\naddFormats(ajv);\n\n/**\n * Resolve conditional schema based on form data\n *\n * Evaluates if-then-else conditions and merges allOf schemas to produce\n * a final schema with only the properties that should be visible given\n * the current form data.\n *\n * @param schema - Original JSON Schema with conditionals\n * @param formData - Current form data to evaluate conditions against\n * @returns Resolved schema with conditionals merged\n *\n * @example\n * ```typescript\n * const schema = {\n * properties: { taxonomy: { type: 'string', enum: ['Tech', 'Arch'] } },\n * allOf: [\n * {\n * if: { properties: { taxonomy: { const: 'Tech' } } },\n * then: { properties: { tech_field: { type: 'string' } } }\n * }\n * ]\n * };\n *\n * const resolved = resolveSchema(schema, { taxonomy: 'Tech' });\n * // Result: { properties: { taxonomy: {...}, tech_field: {...} } }\n * ```\n */\nexport function resolveSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n\n // Resolve allOf members (AND -- each member's conditional is merged).\n let resolvedSchema = resolveAllOf(schema, formData);\n\n // Resolve anyOf members that carry if/then/else (OR -- merge the `then` of\n // any member whose condition holds; ignore members without an `if`).\n resolvedSchema = resolveAnyOfConditionals(resolvedSchema, formData);\n\n // Resolve any top-level if-then-else.\n resolvedSchema = resolveConditionals(resolvedSchema, formData);\n\n return resolvedSchema;\n}\n\n/**\n * Resolve anyOf members that carry if/then/else (OR-conditional semantics).\n *\n * Used for FHIR `enableBehavior: \"any\"`: a field is enabled when ANY of its\n * `enableWhen` conditions holds. In JSON Schema this is expressed as `anyOf`\n * members with `if/then/else`. Members without an `if` are left untouched\n * (they may be structural anyOf entries, e.g. the nullable pattern).\n *\n * For each matching member (condition holds), the resolved `then` is merged\n * into the base schema. Non-conditional anyOf members are preserved on the\n * result so downstream resolvers (nullable anyOf etc.) still see them.\n */\nfunction resolveAnyOfConditionals(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.anyOf || !Array.isArray(schema.anyOf)) return schema;\n\n const conditionalMembers = schema.anyOf.filter(\n (m) => typeof m === \"object\" && m !== null && \"if\" in m,\n );\n // No conditional members -- leave anyOf untouched for other resolvers.\n if (conditionalMembers.length === 0) return schema;\n\n const nonConditionalMembers = schema.anyOf.filter(\n (m) => !(typeof m === \"object\" && m !== null && \"if\" in m),\n );\n\n // Strip anyOf so mergeSchemas doesn't clobber it; re-attach non-conditional\n // members if any remain.\n const { anyOf: _anyOf, ...baseSchema } = schema as JsonSchema & {\n anyOf: unknown[];\n };\n\n let result: JsonSchema =\n nonConditionalMembers.length > 0\n ? { ...(baseSchema as JsonSchema), anyOf: nonConditionalMembers }\n : (baseSchema as JsonSchema);\n\n // Merge the `then` of every conditional member whose `if` holds (OR).\n for (const member of conditionalMembers) {\n const resolved = resolveConditionals(member, formData);\n // resolveConditionals strips `if/then/else` and merges the branch.\n // Only merge if something was actually resolved (condition held).\n if (resolved !== member && typeof resolved === \"object\") {\n result = mergeSchemas(result, resolved);\n }\n }\n\n return result;\n}\n\n/**\n * Resolve if-then-else conditionals\n *\n * Evaluates the 'if' condition against formData and merges the appropriate\n * branch ('then' or 'else') into the base schema.\n *\n * @param schema - Schema potentially containing if-then-else\n * @param formData - Current form data\n * @returns Schema with conditional merged\n */\nfunction resolveConditionals(schema: any, formData: Record<string, any>): any {\n if (!schema.if) {\n return schema;\n }\n\n const {\n if: ifSchema,\n then: thenSchema,\n else: elseSchema,\n ...baseSchema\n } = schema;\n\n try {\n // Compile and validate the 'if' condition\n const validator = ajv.compile(ifSchema);\n const conditionMet = validator(formData || {});\n\n // Choose the appropriate branch\n const branchSchema = conditionMet ? thenSchema : elseSchema;\n\n if (branchSchema && typeof branchSchema === \"object\") {\n // Merge the branch into the base schema\n return mergeSchemas(baseSchema, branchSchema);\n }\n\n return baseSchema;\n } catch (error) {\n warnLog(\"Error resolving conditional schema:\", error);\n return baseSchema;\n }\n}\n\n/**\n * Resolve allOf schemas\n *\n * Merges all schemas in the allOf array, recursively resolving any\n * conditionals within each subschema.\n *\n * @param schema - Schema potentially containing allOf\n * @param formData - Current form data\n * @returns Schema with allOf merged\n */\nfunction resolveAllOf(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.allOf || !Array.isArray(schema.allOf)) {\n return schema;\n }\n\n const { allOf, ...baseSchema } = schema;\n\n // Resolve each allOf element (they might have if-then-else)\n const resolvedAllOf = allOf.map((subschema) => {\n if (typeof subschema !== \"object\") {\n return subschema;\n }\n\n // Recursively resolve conditionals in each allOf element\n return resolveConditionals(subschema, formData);\n });\n\n // Merge all resolved schemas together\n return resolvedAllOf.reduce(\n (acc, subschema) => mergeSchemas(acc, subschema),\n baseSchema,\n );\n}\n\n/**\n * Deep merge two schemas\n *\n * Merges two JSON Schemas together, with special handling for:\n * - properties: Deep merge\n * - required: Array union (unique values)\n * - Other keys: Override with schema2 value\n *\n * @param schema1 - Base schema\n * @param schema2 - Schema to merge in (takes precedence)\n * @returns Merged schema\n */\nfunction mergeSchemas(\n schema1: JsonSchema | any,\n schema2: JsonSchema | any,\n): JsonSchema {\n if (!schema2 || typeof schema2 !== \"object\") {\n return schema1;\n }\n if (!schema1 || typeof schema1 !== \"object\") {\n return schema2;\n }\n\n const merged = { ...schema1 };\n\n Object.keys(schema2).forEach((key) => {\n if (key === \"properties\" && schema1.properties && schema2.properties) {\n // Deep merge properties - for each property, merge the definitions\n merged.properties = { ...schema1.properties };\n Object.keys(schema2.properties).forEach((propName) => {\n if (merged.properties[propName]) {\n // Property exists in both schemas, deep merge them\n merged.properties[propName] = {\n ...merged.properties[propName],\n ...schema2.properties[propName],\n };\n } else {\n // Property only in schema2, just copy it\n merged.properties[propName] = schema2.properties[propName];\n }\n });\n } else if (key === \"required\" && schema1.required && schema2.required) {\n // Merge required arrays (unique values)\n const combinedRequired = [...schema1.required, ...schema2.required];\n merged.required = Array.from(new Set(combinedRequired));\n } else if (key === \"allOf\" && schema1.allOf && schema2.allOf) {\n // Concatenate allOf arrays\n merged.allOf = [...schema1.allOf, ...schema2.allOf];\n } else {\n // Override for other keys\n merged[key] = schema2[key];\n }\n });\n\n return merged;\n}\n\n/**\n * Validate formData against a schema\n *\n * Useful for checking if form data meets schema requirements,\n * including type validation and format checking.\n *\n * @param schema - JSON Schema to validate against\n * @param formData - Form data to validate\n * @returns Validation result with errors\n *\n * @example\n * ```typescript\n * const result = validateSchema(schema, { name: 'John', age: 'invalid' });\n * if (!result.valid) {\n * errorLog(result.errors); // AJV error objects\n * }\n * ```\n */\nexport function validateSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): { valid: boolean; errors: any[] } {\n try {\n const validator = ajv.compile(schema);\n const valid = validator(formData);\n\n return {\n valid,\n errors: validator.errors || [],\n };\n } catch (error) {\n warnLog(\"Error validating schema:\", error);\n return {\n valid: false,\n errors: [{ message: \"Schema validation error\" }],\n };\n }\n}\n\n/**\n * Check if a value matches a const constraint\n *\n * Helper for evaluating const conditions in conditionals.\n *\n * @param value - Value to check\n * @param constValue - Expected const value\n * @returns true if value matches const\n */\nexport function matchesConst(value: any, constValue: any): boolean {\n return value === constValue;\n}\n\n/**\n * Check if a schema has conditionals\n *\n * Useful for determining if schema resolution is needed.\n *\n * @param schema - JSON Schema to check\n * @returns true if schema contains if-then-else or allOf\n */\nexport function hasConditionals(schema: JsonSchema): boolean {\n if (!schema || typeof schema !== \"object\") {\n return false;\n }\n\n return !!(schema.if || schema.allOf || schema.anyOf || schema.oneOf);\n}\n","/**\n * Declarative UI rules (x-ui-rule): show/hide or enable/disable a field based\n * on another field's value.\n *\n * The rule lives on the target field. Its condition references a value elsewhere\n * in the form data via a JSON Pointer (RFC 6901) rooted at the whole form, and\n * validates that value against a schema. Evaluation yields presentation flags\n * (visible / enabled) the renderer applies -- the field itself stays declared at\n * its own position, so layout is stable (unlike if/then/else).\n *\n * Scopes are root-anchored at every depth: a rule on a nested field reads its\n * pointer from the top of the form, so `/country` means the form's `country`\n * even from inside `address`, and a sibling is reached by its full path\n * (`/address/city`). This matches every other JSON Pointer in the vocabulary\n * (`editablePaths`, validation error pointers). The renderer is responsible for\n * passing the root form data in -- see `useRootFormData` in sonnet-forms.\n *\n * A consequence worth knowing when authoring inside `$defs`: a definition is\n * reusable, but a root-anchored scope is not. A rule inside a definition that\n * references `/country` only holds for forms that have a top-level `country`.\n * This is inherent to root-anchoring and is the same in comparable libraries.\n */\n\nimport type { JsonSchemaProperty, UIRule } from \"./types\";\nimport { ajv } from \"./schemaResolver\";\nimport { warnLog } from \"../logger\";\n\n// Cache compiled validators by the serialized schema to avoid recompiling the\n// same condition schema on every form render. The Map is module-level and\n// bounded by the number of distinct x-ui-rule condition schemas in the app.\nconst validatorCache = new Map<string, ReturnType<typeof ajv.compile>>();\n\nfunction getValidator(\n schema: JsonSchemaProperty,\n): ReturnType<typeof ajv.compile> {\n const key = JSON.stringify(schema);\n let validator = validatorCache.get(key);\n if (!validator) {\n validator = ajv.compile(schema);\n validatorCache.set(key, validator);\n }\n return validator;\n}\n\n/** The presentation state a rule resolves to for its target field. */\nexport interface RuleState {\n visible: boolean;\n enabled: boolean;\n}\n\nconst VISIBLE_ENABLED: RuleState = { visible: true, enabled: true };\n\n/** Unescape a single JSON Pointer reference token (RFC 6901: ~1 -> /, ~0 -> ~). */\nfunction unescapeToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Resolve a JSON Pointer against a data object. Returns the value at the pointer,\n * or undefined when any segment is missing. \"\" (empty pointer) returns the root.\n */\nexport function getAtPointer(data: unknown, pointer: string): unknown {\n if (pointer === \"\") return data;\n if (!pointer.startsWith(\"/\")) return undefined;\n const tokens = pointer.slice(1).split(\"/\").map(unescapeToken);\n let current: unknown = data;\n for (const token of tokens) {\n if (current == null || typeof current !== \"object\") return undefined;\n if (Array.isArray(current)) {\n const idx = Number(token);\n if (!Number.isInteger(idx) || idx < 0 || idx >= current.length)\n return undefined;\n current = current[idx];\n } else {\n current = (current as Record<string, unknown>)[token];\n }\n }\n return current;\n}\n\n// Whether the rule's condition holds for the given form data.\nfunction conditionHolds(rule: UIRule, formData: unknown): boolean {\n const { scope, schema } = rule.condition;\n const value = getAtPointer(formData, scope);\n try {\n const validator = getValidator(schema as JsonSchemaProperty);\n return !!validator(value);\n } catch (error) {\n warnLog(\"Error evaluating x-ui-rule condition:\", error);\n return false;\n }\n}\n\n/**\n * Resolve a field's presentation state from its `x-ui-rule` (if any) against the\n * current form data. Fields without a rule are visible and enabled.\n */\nexport function evaluateUiRule(\n rule: UIRule | undefined,\n formData: unknown,\n): RuleState {\n if (!rule) return VISIBLE_ENABLED;\n const holds = conditionHolds(rule, formData);\n switch (rule.effect) {\n case \"show\":\n return { visible: holds, enabled: true };\n case \"hide\":\n return { visible: !holds, enabled: true };\n case \"enable\":\n return { visible: true, enabled: holds };\n case \"disable\":\n return { visible: true, enabled: !holds };\n }\n}\n","/**\n * Schema utilities\n *\n * Pure functions for common JSON Schema operations:\n * - Applying schema defaults to form data\n * - Extracting default values from a schema\n * - Generating a schema from a list of template variables\n *\n * All functions are side-effect free.\n *\n * @module services/schema/schemaUtils\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\n/**\n * Merge schema default values into an existing data object.\n *\n * For each property in the schema that has a `default` value, if the\n * corresponding key is absent (`undefined`) in `data`, the default is applied.\n * Existing values — including `null` and `false` — are never overwritten.\n *\n * @param data - Existing form data (may be partial)\n * @param schema - JSON Schema containing property defaults\n * @returns New object with defaults filled in for missing keys\n */\nexport function applySchemaDefaults(\n data: Record<string, unknown>,\n schema: JsonSchema | JsonSchemaProperty,\n): Record<string, unknown> {\n const result = { ...data };\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (result[fieldName] === undefined && property.default !== undefined) {\n result[fieldName] = property.default;\n }\n }\n\n return result;\n}\n\n/**\n * Extract all default values declared in a JSON Schema.\n *\n * Walks the schema's top-level properties and collects any `default` values.\n * Properties without a `default` are omitted from the result.\n *\n * @param schema - JSON Schema to extract defaults from\n * @returns Record of field names to their default values\n *\n * @example\n * ```ts\n * extractSchemaDefaults({\n * type: 'object',\n * properties: {\n * name: { type: 'string', default: 'John' },\n * age: { type: 'number' },\n * },\n * });\n * // => { name: 'John' }\n * ```\n */\nexport function extractSchemaDefaults(\n schema: JsonSchema,\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (property.default !== undefined) {\n defaults[fieldName] = property.default;\n }\n }\n\n return defaults;\n}\n\n/**\n * Generate a JSON Schema from a list of template variable names.\n *\n * Each variable becomes a required string property with a capitalised title.\n * If an existing schema is provided, its property definitions are preserved\n * for variables that still appear in the list; new variables are added as plain\n * strings; properties no longer in the list are dropped.\n *\n * @param variables - Variable names to include in the schema\n * @param existingSchema - Optional schema to merge with (preserves extensions)\n * @returns A JSON Schema object with one property per variable\n *\n * @example\n * ```ts\n * generateJsonSchemaFromVariables(['productId', 'limit']);\n * // => {\n * // type: 'object',\n * // properties: {\n * // productId: { type: 'string', title: 'ProductId' },\n * // limit: { type: 'string', title: 'Limit' },\n * // },\n * // required: ['productId', 'limit'],\n * // }\n * ```\n */\nexport function generateJsonSchemaFromVariables(\n variables: string[],\n existingSchema?: { properties?: Record<string, JsonSchemaProperty> },\n): JsonSchema {\n const existingProperties = existingSchema?.properties ?? {};\n\n const properties: JsonSchema[\"properties\"] = {};\n for (const variable of variables) {\n properties[variable] = existingProperties[variable] ?? {\n type: \"string\",\n title: variable.charAt(0).toUpperCase() + variable.slice(1),\n };\n }\n\n return {\n title: \"\",\n type: \"object\",\n properties,\n required: variables,\n };\n}\n","/**\n * Zod-to-JSON-Schema conversion with x-ui-* metadata passthrough.\n *\n * Wraps z.toJSONSchema() with an override that copies all .meta() keys\n * into the generated JSON Schema output, including x-ui-* extension keys\n * used by the form renderer.\n *\n * @module lib/schema/zodSchema\n */\n\nimport { z } from \"zod\";\nimport type { JsonSchema } from \"./types\";\n\n/**\n * Convert a Zod schema to a JsonSchema compatible with the form renderer.\n *\n * All fields annotated with .meta() -- including x-ui-widget, x-ui-order,\n * x-ui-options, title, description -- are passed through to the output.\n *\n * @example\n * ```typescript\n * const MySchema = z.object({\n * name: z.string().meta({ title: \"Name\" }),\n * notes: z.string().optional().meta({ \"x-ui-widget\": \"textarea\" }),\n * });\n *\n * const jsonSchema = toJsonSchema(MySchema);\n * type MyData = z.infer<typeof MySchema>;\n * ```\n */\nexport function toJsonSchema(schema: z.ZodType): JsonSchema {\n return z.toJSONSchema(schema, {\n reused: \"inline\",\n override(ctx) {\n const def = (\n ctx.zodSchema as unknown as {\n _def?: { metadata?: Record<string, unknown> };\n }\n )._def;\n if (def?.metadata) {\n Object.assign(ctx.jsonSchema, def.metadata);\n }\n },\n }) as JsonSchema;\n}\n","/**\n * $ref Resolution -- resolves JSON Schema references.\n *\n * Walks the schema tree and replaces `{ \"$ref\": ... }` nodes with the\n * corresponding sub-schema. By default it resolves local, in-document refs:\n * `#/$defs/name` (Draft 2020-12) or `#/definitions/name` (Draft 7).\n *\n * A caller may inject a `RefResolver` to resolve ANY ref string (e.g. a schema\n * registry id like `sdf_...`, or a canonical URI). The injected resolver is\n * tried first; the in-document `$defs`/`definitions` lookup is the fallback, so\n * both ref styles work together. The resolver is synchronous: consumers that\n * fetch schemas asynchronously (e.g. from a registry over HTTP) pre-load them\n * into a map and pass a `(ref) => map[ref]` lookup. This keeps the toolkit pure\n * (no HTTP, no async).\n *\n * Properties on the referencing node are merged on top of the resolved target,\n * so context-specific overrides (e.g. a custom title) work without duplicating\n * the definition. Refs that no resolver can satisfy are left as-is.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\ntype Defs = Record<string, JsonSchemaProperty>;\n\n/** Resolve a `$ref` string to its target sub-schema, or undefined if unknown. */\nexport type RefResolver = (ref: string) => JsonSchemaProperty | undefined;\n\n// Extract the definition name from a local $ref pointer.\n// Supports \"#/$defs/name\" (2020-12) and \"#/definitions/name\" (Draft 7).\nfunction extractDefName(ref: string): string | null {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n return match ? match[2] : null;\n}\n\n/** Build the default resolver: an in-document `$defs`/`definitions` lookup. */\nfunction inDocumentResolver(defs: Defs): RefResolver {\n return (ref) => {\n const name = extractDefName(ref);\n return name ? defs[name] : undefined;\n };\n}\n\n// Resolve a $ref node: look up the target, merge overrides, recurse.\nfunction resolveRef(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty {\n const ref = node.$ref!;\n const resolved = resolve(ref);\n if (!resolved) return node; // unknown ref -- leave as-is\n if (seen.has(ref)) return node; // cycle guard\n seen.add(ref);\n\n const overrides = Object.fromEntries(\n Object.entries(node).filter(([k]) => k !== \"$ref\"),\n ) as JsonSchemaProperty;\n const merged =\n Object.keys(overrides).length > 0\n ? { ...resolved, ...overrides }\n : resolved;\n\n const result = resolveNode(merged, resolve, seen);\n seen.delete(ref);\n return result;\n}\n\n// Resolve an array of schemas, returning null if nothing changed.\nfunction resolveList(\n list: JsonSchemaProperty[],\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty[] | null {\n const resolved = list.map((s) => resolveNode(s, resolve, seen));\n return resolved.some((s, i) => s !== list[i]) ? resolved : null;\n}\n\n// Resolve properties map, returning null if nothing changed.\nfunction resolveProperties(\n props: Record<string, JsonSchemaProperty>,\n resolve: RefResolver,\n seen: Set<string>,\n): Record<string, JsonSchemaProperty> | null {\n const entries = Object.entries(props);\n const resolved = entries.map(\n ([k, v]) => [k, resolveNode(v, resolve, seen)] as const,\n );\n return resolved.some(([, v], i) => v !== entries[i][1])\n ? Object.fromEntries(resolved)\n : null;\n}\n\n// Resolve items, returning null if unchanged.\nfunction resolveItems(\n items: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty | null {\n const r = resolveNode(items, resolve, seen);\n return r !== items ? r : null;\n}\n\n// Collect all resolved children of a node into a partial override object.\nfunction resolveChildren(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): Partial<JsonSchemaProperty> | null {\n const overrides: Partial<JsonSchemaProperty> = {};\n let changed = false;\n\n const props = node.properties\n ? resolveProperties(node.properties, resolve, seen)\n : null;\n if (props) {\n overrides.properties = props;\n changed = true;\n }\n\n const items = node.items ? resolveItems(node.items, resolve, seen) : null;\n if (items) {\n overrides.items = items;\n changed = true;\n }\n\n for (const key of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n const list = node[key] ? resolveList(node[key]!, resolve, seen) : null;\n if (list) {\n (overrides as Record<string, unknown>)[key] = list;\n changed = true;\n }\n }\n\n return changed ? overrides : null;\n}\n\nfunction resolveNode(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty {\n if (node.$ref) return resolveRef(node, resolve, seen);\n const overrides = resolveChildren(node, resolve, seen);\n return overrides ? { ...node, ...overrides } : node;\n}\n\n/**\n * Resolve `$ref` pointers in a JSON Schema document.\n *\n * By default resolves in-document `#/$defs` / `#/definitions` refs. Pass a\n * `resolve` to also resolve registry/URI refs; it is tried first and the\n * in-document lookup is the fallback. The original schema is never mutated;\n * a document with no refs to resolve is returned unchanged.\n */\nexport function resolveRefs(\n schema: JsonSchema,\n resolve?: RefResolver,\n): JsonSchema {\n const defs: Defs = schema.$defs ?? schema.definitions ?? {};\n // Nothing to resolve against: no injected resolver and no in-document defs.\n if (!resolve && Object.keys(defs).length === 0) return schema;\n\n const fallback = inDocumentResolver(defs);\n const resolver: RefResolver = resolve\n ? (ref) => resolve(ref) ?? fallback(ref)\n : fallback;\n\n // Cast to JsonSchemaProperty so resolveNode can walk the full tree\n // including the root-level properties and allOf. The result is cast\n // back -- JsonSchema is a structural subtype of JsonSchemaProperty.\n const seen = new Set<string>();\n const resolved = resolveNode(\n schema as unknown as JsonSchemaProperty,\n resolver,\n seen,\n );\n return resolved as unknown as JsonSchema;\n}\n","/**\n * Recursive form data validation against a JSON Schema.\n *\n * Validates required fields at every nesting level: top-level properties,\n * nested objects (type: \"object\"), and array items (type: \"array\" with\n * items.type: \"object\"). Returns human-readable error strings, the same\n * errors keyed by JSON Pointer, and a boolean summary.\n *\n * The pointer form lets callers attribute an error to a specific field --\n * needed to surface errors that are not currently on screen (e.g. a badge on\n * a form tab whose fields are hidden). `errors` remains the flat message list.\n *\n * Pure function -- no React, no side effects.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\nimport { getSchemaProperties, getFieldTitle } from \"./schemaService\";\n\n/** A validation error attributed to the field that produced it. */\nexport interface FieldError {\n /**\n * JSON Pointer (RFC 6901) to the offending field, e.g. `/email`,\n * `/contact/email`, or `/tags/2/label` for the third array item.\n */\n pointer: string;\n /** Human-readable message, e.g. \"Contact > Email is required\". */\n message: string;\n}\n\nexport interface ValidationResult {\n /** Flat, human-readable messages (in field declaration order). */\n errors: string[];\n /** The same errors, each attributed to its field via a JSON Pointer. */\n fieldErrors: FieldError[];\n isValid: boolean;\n}\n\n// Where a validated value sits: its human label path and its JSON Pointer.\ninterface FieldContext {\n /** Display path, e.g. \"Address > City\" or \"Tags[3] > Label\". */\n label: string;\n /** JSON Pointer to the value, e.g. \"/address/city\" or \"/tags/2/label\". */\n pointer: string;\n}\n\n// RFC 6901 token escaping: \"~\" -> \"~0\", \"/\" -> \"~1\".\nfunction escapePointerToken(token: string): string {\n return token.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\n\n// Check whether a single value satisfies a \"required\" constraint.\nfunction isEmpty(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n// Check whether a value is a non-array plain object suitable for recursion.\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n// The context of a named child of the object at `parent`.\nfunction childContext(\n parent: FieldContext,\n fieldName: string,\n property: JsonSchemaProperty,\n): FieldContext {\n const title = getFieldTitle(fieldName, property);\n return {\n label: parent.label ? `${parent.label} > ${title}` : title,\n pointer: `${parent.pointer}/${escapePointerToken(fieldName)}`,\n };\n}\n\n// The context of the i-th item of the array at `parent`. The label uses a\n// 1-based index for readability; the pointer uses the 0-based array index.\nfunction itemContext(parent: FieldContext, index: number): FieldContext {\n return {\n label: `${parent.label}[${index + 1}]`,\n pointer: `${parent.pointer}/${index}`,\n };\n}\n\n// Validate array cardinality and recurse into object items.\nfunction validateArray(\n property: JsonSchemaProperty,\n value: unknown[],\n ctx: FieldContext,\n out: FieldError[],\n): void {\n if (property.minItems !== undefined && value.length < property.minItems) {\n out.push({\n pointer: ctx.pointer,\n message: `${ctx.label} requires at least ${property.minItems} item(s)`,\n });\n }\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n out.push({\n pointer: ctx.pointer,\n message: `${ctx.label} allows at most ${property.maxItems} item(s)`,\n });\n }\n if (!property.items?.properties) return;\n const itemProperties = property.items.properties;\n const itemSchema = property.items;\n for (let i = 0; i < value.length; i++) {\n if (isPlainObject(value[i])) {\n validateObject(\n itemProperties,\n itemSchema,\n value[i] as Record<string, unknown>,\n itemContext(ctx, i),\n out,\n );\n }\n }\n}\n\n// Check whether a property+value pair represents a nested object to recurse into.\nfunction isNestedObject(\n property: JsonSchemaProperty,\n value: unknown,\n): value is Record<string, unknown> {\n return (\n property.type === \"object\" && !!property.properties && isPlainObject(value)\n );\n}\n\n// Validate a single field: check required, recurse into objects/arrays.\nfunction validateField(\n property: JsonSchemaProperty,\n value: unknown,\n required: boolean,\n ctx: FieldContext,\n out: FieldError[],\n): void {\n if (required && isEmpty(value)) {\n out.push({ pointer: ctx.pointer, message: `${ctx.label} is required` });\n return;\n }\n if (isNestedObject(property, value)) {\n validateObject(property.properties!, property, value, ctx, out);\n return;\n }\n if (property.type === \"array\" && Array.isArray(value)) {\n validateArray(property, value, ctx, out);\n }\n}\n\n// Validate the required fields of a single object against its schema.\nfunction validateObject(\n properties: Record<string, JsonSchemaProperty>,\n schema: { required?: string[] },\n data: Record<string, unknown>,\n ctx: FieldContext,\n out: FieldError[],\n): void {\n for (const fieldName of Object.keys(properties)) {\n const property = properties[fieldName];\n const required = schema.required?.includes(fieldName) ?? false;\n validateField(\n property,\n data[fieldName],\n required,\n childContext(ctx, fieldName, property),\n out,\n );\n }\n}\n\n/**\n * Validate form data against a JSON Schema, recursing into nested objects\n * and array items.\n */\nexport function validateFormData(\n schema: JsonSchema,\n data: Record<string, unknown>,\n): ValidationResult {\n const fieldErrors: FieldError[] = [];\n const properties = getSchemaProperties(schema);\n validateObject(\n properties,\n schema,\n data,\n { label: \"\", pointer: \"\" },\n fieldErrors,\n );\n return {\n errors: fieldErrors.map((e) => e.message),\n fieldErrors,\n isValid: fieldErrors.length === 0,\n };\n}\n","/**\n * diffFormData -- structural diff between two form data snapshots.\n *\n * Produces a FormDiff that classifies every change at field level (value\n * changes, nested object changes) and at array item level (add, remove,\n * move, modify). Array items are matched by their form-internal `_fid`\n * field, which is assigned by ArrayWidget and never sent to the server.\n *\n * Items that lack a `_fid` (e.g. items that arrived before the form\n * system assigned IDs) are matched by position as a fallback.\n */\n\nimport { isEqual, FORM_ITEM_ID_FIELD } from \"../utils\";\nimport type {\n FormDiff,\n FieldDiff,\n ValueChange,\n ArrayItemChange,\n} from \"./types\";\n\n// Check whether both values are non-array plain objects suitable for recursion.\nfunction areBothPlainObjects(a: unknown, b: unknown): boolean {\n return (\n a !== null &&\n b !== null &&\n typeof a === \"object\" &&\n typeof b === \"object\" &&\n !Array.isArray(a) &&\n !Array.isArray(b)\n );\n}\n\n// Diff two plain objects recursively, collecting ValueChange entries.\nfunction diffObjects(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n prefix: string,\n): ValueChange[] {\n const changes: ValueChange[] = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const path = prefix ? `${prefix}.${key}` : key;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n\n if (areBothPlainObjects(a, b)) {\n changes.push(\n ...diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n path,\n ),\n );\n } else {\n changes.push({ type: \"value\", path, from: a, to: b });\n }\n }\n\n return changes;\n}\n\n// Strip _fid from an item before including it in diff output.\nfunction stripFid(item: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(item).filter(([k]) => k !== FORM_ITEM_ID_FIELD),\n );\n}\n\ntype FidMap = Map<string, { item: Record<string, unknown>; index: number }>;\n\n// Build identity map: fid -> { item, index }\nfunction buildFidMap(items: Record<string, unknown>[]): FidMap {\n const map: FidMap = new Map();\n for (let i = 0; i < items.length; i++) {\n const fid = items[i][FORM_ITEM_ID_FIELD] as string | undefined;\n if (fid) map.set(fid, { item: items[i], index: i });\n }\n return map;\n}\n\n// Detect removed and added items between two fid maps.\nfunction detectAddedRemoved(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item, index }] of origByFid) {\n if (!updByFid.has(fid)) {\n changes.push({ type: \"removed\", fid, index, item: stripFid(item) });\n }\n }\n for (const [fid, { item, index }] of updByFid) {\n if (!origByFid.has(fid)) {\n changes.push({ type: \"added\", fid, index, item: stripFid(item) });\n }\n }\n return changes;\n}\n\n// Detect moved and modified items present in both snapshots.\nfunction detectMovedModified(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item: updItem, index: updIndex }] of updByFid) {\n const orig = origByFid.get(fid);\n if (!orig) continue;\n if (orig.index !== updIndex) {\n changes.push({ type: \"moved\", fid, from: orig.index, to: updIndex });\n }\n const subChanges = diffObjects(orig.item, updItem, \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid,\n index: updIndex,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Fallback diff for items without _fid: match by position.\nfunction diffUnfidItems(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n): ArrayItemChange[] {\n const unfidOrig = original.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const unfidUpd = updated.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const changes: ArrayItemChange[] = [];\n const len = Math.min(unfidOrig.length, unfidUpd.length);\n for (let i = 0; i < len; i++) {\n if (isEqual(unfidOrig[i], unfidUpd[i])) continue;\n const subChanges = diffObjects(unfidOrig[i], unfidUpd[i], \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid: `__pos_${i}`,\n index: i,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Diff two arrays of objects using _fid for item identity.\nfunction diffArrays(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n field: string,\n): FieldDiff {\n const origByFid = buildFidMap(original);\n const updByFid = buildFidMap(updated);\n const changes: ArrayItemChange[] = [\n ...detectAddedRemoved(origByFid, updByFid),\n ...detectMovedModified(origByFid, updByFid),\n ...diffUnfidItems(original, updated),\n ];\n return { type: \"array\", field, changes };\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n *\n * Returns a FormDiff array with one entry per changed top-level field.\n * Fields whose value is deeply equal are omitted.\n *\n * Array items are matched by their `_fid` field (assigned by ArrayWidget).\n * Items without `_fid` fall back to positional matching.\n */\n// Check whether an array contains object items (vs scalar items).\nfunction isObjectArray(arr: unknown[]): boolean {\n return arr.some((i) => i !== null && typeof i === \"object\");\n}\n\n// Diff a single top-level field, returning the entries to add to the FormDiff.\nfunction diffField(\n key: string,\n a: unknown,\n b: unknown,\n): (ValueChange | FieldDiff)[] {\n if (Array.isArray(a) || Array.isArray(b)) {\n const origArr = Array.isArray(a) ? a : [];\n const updArr = Array.isArray(b) ? b : [];\n if (isObjectArray(origArr) || isObjectArray(updArr)) {\n return [\n diffArrays(\n origArr as Record<string, unknown>[],\n updArr as Record<string, unknown>[],\n key,\n ),\n ];\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n }\n if (areBothPlainObjects(a, b)) {\n return diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n key,\n );\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n */\nexport function diffFormData(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n): FormDiff {\n const diff: FormDiff = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n diff.push(...diffField(key, a, b));\n }\n\n return diff;\n}\n","/**\n * ensureFormItemIds -- stamp a stable `_fid` onto every array-of-object item\n * in a form data snapshot, guided by the schema.\n *\n * The form system matches array items across a diff by their `_fid` (see\n * diffFormData). ArrayWidget assigns one when it renders, but that only reaches\n * the working copy. The dirty-tracking baseline is snapshotted from the\n * incoming `data`, which has none -- so a loaded array diffs as a wholesale\n * replace after a single-item edit.\n *\n * Stamping the incoming data once, before both the working copy and the\n * baseline are derived from it, gives the two sides the *same* ids, so\n * identity survives the diff. Items that already carry a `_fid` keep it.\n *\n * Only array-of-object fields declared in the schema are touched; scalar\n * arrays and everything else pass through unchanged. Recurses into nested\n * objects and into the items of nested arrays.\n */\n\nimport { FORM_ITEM_ID_FIELD, generateId } from \"../utils\";\nimport { getSchemaProperties } from \"./schemaService\";\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n// An array field is a candidate for item ids when its items are objects with\n// declared properties. This mirrors ArrayWidget's own resolution.\nfunction isObjectArray(property: JsonSchemaProperty): boolean {\n return property.type === \"array\" && !!property.items?.properties;\n}\n\nfunction stampItem(\n item: unknown,\n itemSchema: JsonSchemaProperty,\n): Record<string, unknown> | unknown {\n if (!isPlainObject(item)) return item;\n const withChildren = stampObject(item, itemSchema.properties ?? {});\n return FORM_ITEM_ID_FIELD in withChildren\n ? withChildren\n : { ...withChildren, [FORM_ITEM_ID_FIELD]: generateId() };\n}\n\nfunction stampValue(value: unknown, property: JsonSchemaProperty): unknown {\n if (isObjectArray(property) && Array.isArray(value)) {\n return value.map((item) => stampItem(item, property.items!));\n }\n if (\n property.type === \"object\" &&\n property.properties &&\n isPlainObject(value)\n ) {\n return stampObject(value, property.properties);\n }\n return value;\n}\n\nfunction stampObject(\n data: Record<string, unknown>,\n properties: Record<string, JsonSchemaProperty>,\n): Record<string, unknown> {\n let changed = false;\n const out: Record<string, unknown> = { ...data };\n for (const [name, property] of Object.entries(properties)) {\n if (!(name in data)) continue;\n const next = stampValue(data[name], property);\n if (next !== data[name]) {\n out[name] = next;\n changed = true;\n }\n }\n return changed ? out : data;\n}\n\n/**\n * Return `data` with a `_fid` on every array-of-object item the schema\n * declares. Returns the input unchanged when nothing needed stamping.\n */\nexport function ensureFormItemIds(\n data: Record<string, unknown>,\n schema: JsonSchema,\n): Record<string, unknown> {\n return stampObject(data, getSchemaProperties(schema));\n}\n"],"mappings":";;;;;;;;;;AAmMO,IAAM,mBAA+C;AAAA,EAC1D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AAAA;AACR;;;AC7LO,SAAS,oBACd,QACoC;AACpC,SAAO,OAAO,cAAc,CAAC;AAC/B;AAEO,SAAS,gBACd,QACA,WACS;AACT,SAAO,OAAO,UAAU,SAAS,SAAS,KAAK;AACjD;AAEO,SAAS,gBAAgB,UAAuC;AACrE,SAAO,SAAS;AAClB;AAkBO,SAAS,aAAa,UAA8C;AACzE,MAAI,MAAM,QAAQ,SAAS,KAAK,GAAG;AACjC,UAAM,UAAU,SAAS,MAAM;AAAA,MAC7B,CAAC,MAAqC,UAAU,KAAK,EAAE,SAAS;AAAA,IAClE;AACA,QAAI,QAAS,QAAO,QAAQ;AAAA,EAC9B;AACA,SAAQ,SAAS,QAA2B;AAC9C;AAOO,SAAS,kBAAkB,UAAuC;AACvE,MAAI,CAAC,MAAM,QAAQ,SAAS,KAAK,EAAG,QAAO;AAC3C,QAAM,aAAa,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS;AAClE,QAAM,UAAU,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC5D,SAAO,cAAc;AACvB;AAUO,SAAS,kBACd,UAC+B;AAC/B,MAAI,MAAM,QAAQ,SAAS,IAAI,KAAK,SAAS,KAAK,SAAS,GAAG;AAC5D,WAAO,SAAS;AAAA,EAClB;AACA,MAAI,MAAM,QAAQ,SAAS,KAAK,GAAG;AACjC,eAAW,UAAU,SAAS,OAAO;AACnC,UAAI,MAAM,QAAQ,OAAO,IAAI,KAAK,OAAO,KAAK,SAAS,GAAG;AACxD,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAGO,SAAS,cACd,WACA,UACQ;AACR,SAAO,SAAS,YAAY,KAAK,SAAS,SAAS;AACrD;AAUO,SAAS,iBACd,UACoB;AACpB,SAAO,SAAS,kBAAkB,KAAK,SAAS;AAClD;AAEO,SAAS,oBACd,UACoB;AACpB,SAAO,SAAS,kBAAkB;AACpC;AAEO,SAAS,eACd,UACoB;AACpB,SAAO,SAAS,aAAa;AAC/B;AAEO,SAAS,gBACd,QACA,WACA,UACS;AACT,MAAI,gBAAgB,QAAQ,SAAS,GAAG;AACtC,WAAO;AAAA,EACT;AACA,MAAI,SAAS,SAAS,MAAM,QAAQ,SAAS,KAAK,GAAG;AACnD,WAAO,SAAS,MAAM,KAAK,CAAC,MAAM,UAAU,KAAK,EAAE,SAAS,MAAM;AAAA,EACpE;AACA,SAAO;AACT;AAEO,SAAS,oBAAoB,UAOlC;AACA,SAAO;AAAA,IACL,WAAW,SAAS;AAAA,IACpB,WAAW,SAAS;AAAA,IACpB,SAAS,SAAS;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB,QAAQ,SAAS;AAAA,EACnB;AACF;AAEO,SAAS,kBAAkB,QAA8B;AAC9D,SAAO,OAAO,YAAY,CAAC;AAC7B;AAEO,SAAS,kBAAkB,QAA8B;AAC9D,QAAM,YAAY,OAAO,KAAK,OAAO,cAAc,CAAC,CAAC;AACrD,QAAM,WAAW,OAAO,YAAY,CAAC;AACrC,SAAO,UAAU,OAAO,CAAC,UAAU,CAAC,SAAS,SAAS,KAAK,CAAC;AAC9D;AAEO,SAAS,cAAc,QAA6B;AACzD,SAAO,OAAO,KAAK,OAAO,cAAc,CAAC,CAAC,EAAE,SAAS;AACvD;;;ACzJO,SAAS,QAAQ,UAAuC;AAC7D,SAAO,MAAM,QAAQ,SAAS,KAAK,KAAK,SAAS,MAAM,SAAS;AAClE;AASO,SAAS,iBACd,UACoB;AACpB,QAAM,WAAW,SAAS,eAAe;AACzC,MAAI,OAAO,aAAa,YAAY,SAAU,QAAO;AAErD,QAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO;AAG9D,QAAM,QAAQ,SAAS,CAAC,EAAE,cAAc,CAAC;AACzC,QAAM,aAAa,OAAO,KAAK,KAAK,EAAE;AAAA,IACpC,CAAC,MAAM,MAAM,CAAC,GAAG,UAAU;AAAA,EAC7B;AAGA,SAAO,WAAW;AAAA,IAAK,CAAC,QACtB,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,GAAG,UAAU,MAAS;AAAA,EAChE;AACF;AAGO,SAAS,eACd,QACA,eACuC;AACvC,QAAM,IAAI,OAAO,aAAa,aAAa,GAAG;AAC9C,MACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,OAAO,MAAM,WACb;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,eACd,QACA,eACA,OACQ;AACR,MAAI,OAAO,MAAO,QAAO,OAAO;AAChC,MAAI,eAAe;AACjB,UAAM,IAAI,eAAe,QAAQ,aAAa;AAC9C,QAAI,MAAM,OAAW,QAAO,OAAO,CAAC;AAAA,EACtC;AACA,SAAO,UAAU,QAAQ,CAAC;AAC5B;AAMO,SAAS,iBACd,UACA,MACA,eACQ;AACR,QAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,CAAC,iBAAiB,CAAC,KAAM,QAAO;AAChE,QAAM,UAAU,KAAK,aAAa;AAClC,MAAI,YAAY,OAAW,QAAO;AAClC,SAAO,SAAS;AAAA,IACd,CAAC,MAAM,eAAe,GAAG,aAAa,MAAM;AAAA,EAC9C;AACF;;;AC7EA,OAAO,SAAS;AAChB,OAAO,gBAAgB;AAMhB,IAAM,MAAM,IAAI,IAAI;AAAA,EACzB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,iBAAiB;AAAA;AAAA,EAEjB,cAAc;AAChB,CAAC;AACD,WAAW,GAAG;AA6BP,SAAS,cACd,QACA,UACY;AACZ,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO;AAAA,EACT;AAGA,MAAI,iBAAiB,aAAa,QAAQ,QAAQ;AAIlD,mBAAiB,yBAAyB,gBAAgB,QAAQ;AAGlE,mBAAiB,oBAAoB,gBAAgB,QAAQ;AAE7D,SAAO;AACT;AAcA,SAAS,yBACP,QACA,UACY;AACZ,MAAI,CAAC,OAAO,SAAS,CAAC,MAAM,QAAQ,OAAO,KAAK,EAAG,QAAO;AAE1D,QAAM,qBAAqB,OAAO,MAAM;AAAA,IACtC,CAAC,MAAM,OAAO,MAAM,YAAY,MAAM,QAAQ,QAAQ;AAAA,EACxD;AAEA,MAAI,mBAAmB,WAAW,EAAG,QAAO;AAE5C,QAAM,wBAAwB,OAAO,MAAM;AAAA,IACzC,CAAC,MAAM,EAAE,OAAO,MAAM,YAAY,MAAM,QAAQ,QAAQ;AAAA,EAC1D;AAIA,QAAM,EAAE,OAAO,QAAQ,GAAG,WAAW,IAAI;AAIzC,MAAI,SACF,sBAAsB,SAAS,IAC3B,EAAE,GAAI,YAA2B,OAAO,sBAAsB,IAC7D;AAGP,aAAW,UAAU,oBAAoB;AACvC,UAAM,WAAW,oBAAoB,QAAQ,QAAQ;AAGrD,QAAI,aAAa,UAAU,OAAO,aAAa,UAAU;AACvD,eAAS,aAAa,QAAQ,QAAQ;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AACT;AAYA,SAAS,oBAAoB,QAAa,UAAoC;AAC5E,MAAI,CAAC,OAAO,IAAI;AACd,WAAO;AAAA,EACT;AAEA,QAAM;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,GAAG;AAAA,EACL,IAAI;AAEJ,MAAI;AAEF,UAAM,YAAY,IAAI,QAAQ,QAAQ;AACtC,UAAM,eAAe,UAAU,YAAY,CAAC,CAAC;AAG7C,UAAM,eAAe,eAAe,aAAa;AAEjD,QAAI,gBAAgB,OAAO,iBAAiB,UAAU;AAEpD,aAAO,aAAa,YAAY,YAAY;AAAA,IAC9C;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,uCAAuC,KAAK;AACpD,WAAO;AAAA,EACT;AACF;AAYA,SAAS,aACP,QACA,UACY;AACZ,MAAI,CAAC,OAAO,SAAS,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACjD,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,OAAO,GAAG,WAAW,IAAI;AAGjC,QAAM,gBAAgB,MAAM,IAAI,CAAC,cAAc;AAC7C,QAAI,OAAO,cAAc,UAAU;AACjC,aAAO;AAAA,IACT;AAGA,WAAO,oBAAoB,WAAW,QAAQ;AAAA,EAChD,CAAC;AAGD,SAAO,cAAc;AAAA,IACnB,CAAC,KAAK,cAAc,aAAa,KAAK,SAAS;AAAA,IAC/C;AAAA,EACF;AACF;AAcA,SAAS,aACP,SACA,SACY;AACZ,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,EAAE,GAAG,QAAQ;AAE5B,SAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AACpC,QAAI,QAAQ,gBAAgB,QAAQ,cAAc,QAAQ,YAAY;AAEpE,aAAO,aAAa,EAAE,GAAG,QAAQ,WAAW;AAC5C,aAAO,KAAK,QAAQ,UAAU,EAAE,QAAQ,CAAC,aAAa;AACpD,YAAI,OAAO,WAAW,QAAQ,GAAG;AAE/B,iBAAO,WAAW,QAAQ,IAAI;AAAA,YAC5B,GAAG,OAAO,WAAW,QAAQ;AAAA,YAC7B,GAAG,QAAQ,WAAW,QAAQ;AAAA,UAChC;AAAA,QACF,OAAO;AAEL,iBAAO,WAAW,QAAQ,IAAI,QAAQ,WAAW,QAAQ;AAAA,QAC3D;AAAA,MACF,CAAC;AAAA,IACH,WAAW,QAAQ,cAAc,QAAQ,YAAY,QAAQ,UAAU;AAErE,YAAM,mBAAmB,CAAC,GAAG,QAAQ,UAAU,GAAG,QAAQ,QAAQ;AAClE,aAAO,WAAW,MAAM,KAAK,IAAI,IAAI,gBAAgB,CAAC;AAAA,IACxD,WAAW,QAAQ,WAAW,QAAQ,SAAS,QAAQ,OAAO;AAE5D,aAAO,QAAQ,CAAC,GAAG,QAAQ,OAAO,GAAG,QAAQ,KAAK;AAAA,IACpD,OAAO;AAEL,aAAO,GAAG,IAAI,QAAQ,GAAG;AAAA,IAC3B;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAoBO,SAAS,eACd,QACA,UACmC;AACnC,MAAI;AACF,UAAM,YAAY,IAAI,QAAQ,MAAM;AACpC,UAAM,QAAQ,UAAU,QAAQ;AAEhC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,UAAU,UAAU,CAAC;AAAA,IAC/B;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,4BAA4B,KAAK;AACzC,WAAO;AAAA,MACL,OAAO;AAAA,MACP,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC;AAAA,IACjD;AAAA,EACF;AACF;AAWO,SAAS,aAAa,OAAY,YAA0B;AACjE,SAAO,UAAU;AACnB;AAUO,SAAS,gBAAgB,QAA6B;AAC3D,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO;AAAA,EACT;AAEA,SAAO,CAAC,EAAE,OAAO,MAAM,OAAO,SAAS,OAAO,SAAS,OAAO;AAChE;;;AChTA,IAAM,iBAAiB,oBAAI,IAA4C;AAEvE,SAAS,aACP,QACgC;AAChC,QAAM,MAAM,KAAK,UAAU,MAAM;AACjC,MAAI,YAAY,eAAe,IAAI,GAAG;AACtC,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,QAAQ,MAAM;AAC9B,mBAAe,IAAI,KAAK,SAAS;AAAA,EACnC;AACA,SAAO;AACT;AAQA,IAAM,kBAA6B,EAAE,SAAS,MAAM,SAAS,KAAK;AAGlE,SAAS,cAAc,OAAuB;AAC5C,SAAO,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG;AACrD;AAMO,SAAS,aAAa,MAAe,SAA0B;AACpE,MAAI,YAAY,GAAI,QAAO;AAC3B,MAAI,CAAC,QAAQ,WAAW,GAAG,EAAG,QAAO;AACrC,QAAM,SAAS,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,aAAa;AAC5D,MAAI,UAAmB;AACvB,aAAW,SAAS,QAAQ;AAC1B,QAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,OAAO,QAAQ;AACtD,eAAO;AACT,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,gBAAW,QAAoC,KAAK;AAAA,IACtD;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAAc,UAA4B;AAChE,QAAM,EAAE,OAAO,OAAO,IAAI,KAAK;AAC/B,QAAM,QAAQ,aAAa,UAAU,KAAK;AAC1C,MAAI;AACF,UAAM,YAAY,aAAa,MAA4B;AAC3D,WAAO,CAAC,CAAC,UAAU,KAAK;AAAA,EAC1B,SAAS,OAAO;AACd,YAAQ,yCAAyC,KAAK;AACtD,WAAO;AAAA,EACT;AACF;AAMO,SAAS,eACd,MACA,UACW;AACX,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,eAAe,MAAM,QAAQ;AAC3C,UAAQ,KAAK,QAAQ;AAAA,IACnB,KAAK;AACH,aAAO,EAAE,SAAS,OAAO,SAAS,KAAK;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,SAAS,CAAC,OAAO,SAAS,KAAK;AAAA,IAC1C,KAAK;AACH,aAAO,EAAE,SAAS,MAAM,SAAS,MAAM;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,SAAS,MAAM,SAAS,CAAC,MAAM;AAAA,EAC5C;AACF;;;ACvFO,SAAS,oBACd,MACA,QACyB;AACzB,QAAM,SAAS,EAAE,GAAG,KAAK;AACzB,QAAM,aAAa,OAAO,cAAc,CAAC;AAEzC,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC9D,QAAI,OAAO,SAAS,MAAM,UAAa,SAAS,YAAY,QAAW;AACrE,aAAO,SAAS,IAAI,SAAS;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAuBO,SAAS,sBACd,QACyB;AACzB,QAAM,WAAoC,CAAC;AAC3C,QAAM,aAAa,OAAO,cAAc,CAAC;AAEzC,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC9D,QAAI,SAAS,YAAY,QAAW;AAClC,eAAS,SAAS,IAAI,SAAS;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AACT;AA2BO,SAAS,gCACd,WACA,gBACY;AACZ,QAAM,qBAAqB,gBAAgB,cAAc,CAAC;AAE1D,QAAM,aAAuC,CAAC;AAC9C,aAAW,YAAY,WAAW;AAChC,eAAW,QAAQ,IAAI,mBAAmB,QAAQ,KAAK;AAAA,MACrD,MAAM;AAAA,MACN,OAAO,SAAS,OAAO,CAAC,EAAE,YAAY,IAAI,SAAS,MAAM,CAAC;AAAA,IAC5D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN;AAAA,IACA,UAAU;AAAA,EACZ;AACF;;;ACjHA,SAAS,SAAS;AAoBX,SAAS,aAAa,QAA+B;AAC1D,SAAO,EAAE,aAAa,QAAQ;AAAA,IAC5B,QAAQ;AAAA,IACR,SAAS,KAAK;AACZ,YAAM,MACJ,IAAI,UAGJ;AACF,UAAI,KAAK,UAAU;AACjB,eAAO,OAAO,IAAI,YAAY,IAAI,QAAQ;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;ACfA,SAAS,eAAe,KAA4B;AAClD,QAAM,QAAQ,IAAI,MAAM,iCAAiC;AACzD,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAGA,SAAS,mBAAmB,MAAyB;AACnD,SAAO,CAAC,QAAQ;AACd,UAAM,OAAO,eAAe,GAAG;AAC/B,WAAO,OAAO,KAAK,IAAI,IAAI;AAAA,EAC7B;AACF;AAGA,SAAS,WACP,MACA,SACA,MACoB;AACpB,QAAM,MAAM,KAAK;AACjB,QAAM,WAAW,QAAQ,GAAG;AAC5B,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,OAAK,IAAI,GAAG;AAEZ,QAAM,YAAY,OAAO;AAAA,IACvB,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,MAAM;AAAA,EACnD;AACA,QAAM,SACJ,OAAO,KAAK,SAAS,EAAE,SAAS,IAC5B,EAAE,GAAG,UAAU,GAAG,UAAU,IAC5B;AAEN,QAAM,SAAS,YAAY,QAAQ,SAAS,IAAI;AAChD,OAAK,OAAO,GAAG;AACf,SAAO;AACT;AAGA,SAAS,YACP,MACA,SACA,MAC6B;AAC7B,QAAM,WAAW,KAAK,IAAI,CAAC,MAAM,YAAY,GAAG,SAAS,IAAI,CAAC;AAC9D,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,WAAW;AAC7D;AAGA,SAAS,kBACP,OACA,SACA,MAC2C;AAC3C,QAAM,UAAU,OAAO,QAAQ,KAAK;AACpC,QAAM,WAAW,QAAQ;AAAA,IACvB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,YAAY,GAAG,SAAS,IAAI,CAAC;AAAA,EAC/C;AACA,SAAO,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,MAAM,QAAQ,CAAC,EAAE,CAAC,CAAC,IAClD,OAAO,YAAY,QAAQ,IAC3B;AACN;AAGA,SAAS,aACP,OACA,SACA,MAC2B;AAC3B,QAAM,IAAI,YAAY,OAAO,SAAS,IAAI;AAC1C,SAAO,MAAM,QAAQ,IAAI;AAC3B;AAGA,SAAS,gBACP,MACA,SACA,MACoC;AACpC,QAAM,YAAyC,CAAC;AAChD,MAAI,UAAU;AAEd,QAAM,QAAQ,KAAK,aACf,kBAAkB,KAAK,YAAY,SAAS,IAAI,IAChD;AACJ,MAAI,OAAO;AACT,cAAU,aAAa;AACvB,cAAU;AAAA,EACZ;AAEA,QAAM,QAAQ,KAAK,QAAQ,aAAa,KAAK,OAAO,SAAS,IAAI,IAAI;AACrE,MAAI,OAAO;AACT,cAAU,QAAQ;AAClB,cAAU;AAAA,EACZ;AAEA,aAAW,OAAO,CAAC,SAAS,SAAS,OAAO,GAAY;AACtD,UAAM,OAAO,KAAK,GAAG,IAAI,YAAY,KAAK,GAAG,GAAI,SAAS,IAAI,IAAI;AAClE,QAAI,MAAM;AACR,MAAC,UAAsC,GAAG,IAAI;AAC9C,gBAAU;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,UAAU,YAAY;AAC/B;AAEA,SAAS,YACP,MACA,SACA,MACoB;AACpB,MAAI,KAAK,KAAM,QAAO,WAAW,MAAM,SAAS,IAAI;AACpD,QAAM,YAAY,gBAAgB,MAAM,SAAS,IAAI;AACrD,SAAO,YAAY,EAAE,GAAG,MAAM,GAAG,UAAU,IAAI;AACjD;AAUO,SAAS,YACd,QACA,SACY;AACZ,QAAM,OAAa,OAAO,SAAS,OAAO,eAAe,CAAC;AAE1D,MAAI,CAAC,WAAW,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO;AAEvD,QAAM,WAAW,mBAAmB,IAAI;AACxC,QAAM,WAAwB,UAC1B,CAAC,QAAQ,QAAQ,GAAG,KAAK,SAAS,GAAG,IACrC;AAKJ,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO;AACT;;;ACnIA,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MAAM,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI;AACtD;AAGA,SAAS,QAAQ,OAAyB;AACxC,SAAO,UAAU,UAAa,UAAU,QAAQ,UAAU;AAC5D;AAGA,SAAS,cAAc,OAAkD;AACvE,SAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAGA,SAAS,aACP,QACA,WACA,UACc;AACd,QAAM,QAAQ,cAAc,WAAW,QAAQ;AAC/C,SAAO;AAAA,IACL,OAAO,OAAO,QAAQ,GAAG,OAAO,KAAK,MAAM,KAAK,KAAK;AAAA,IACrD,SAAS,GAAG,OAAO,OAAO,IAAI,mBAAmB,SAAS,CAAC;AAAA,EAC7D;AACF;AAIA,SAAS,YAAY,QAAsB,OAA6B;AACtE,SAAO;AAAA,IACL,OAAO,GAAG,OAAO,KAAK,IAAI,QAAQ,CAAC;AAAA,IACnC,SAAS,GAAG,OAAO,OAAO,IAAI,KAAK;AAAA,EACrC;AACF;AAGA,SAAS,cACP,UACA,OACA,KACA,KACM;AACN,MAAI,SAAS,aAAa,UAAa,MAAM,SAAS,SAAS,UAAU;AACvE,QAAI,KAAK;AAAA,MACP,SAAS,IAAI;AAAA,MACb,SAAS,GAAG,IAAI,KAAK,sBAAsB,SAAS,QAAQ;AAAA,IAC9D,CAAC;AAAA,EACH;AACA,MAAI,SAAS,aAAa,UAAa,MAAM,SAAS,SAAS,UAAU;AACvE,QAAI,KAAK;AAAA,MACP,SAAS,IAAI;AAAA,MACb,SAAS,GAAG,IAAI,KAAK,mBAAmB,SAAS,QAAQ;AAAA,IAC3D,CAAC;AAAA,EACH;AACA,MAAI,CAAC,SAAS,OAAO,WAAY;AACjC,QAAM,iBAAiB,SAAS,MAAM;AACtC,QAAM,aAAa,SAAS;AAC5B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,cAAc,MAAM,CAAC,CAAC,GAAG;AAC3B;AAAA,QACE;AAAA,QACA;AAAA,QACA,MAAM,CAAC;AAAA,QACP,YAAY,KAAK,CAAC;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,eACP,UACA,OACkC;AAClC,SACE,SAAS,SAAS,YAAY,CAAC,CAAC,SAAS,cAAc,cAAc,KAAK;AAE9E;AAGA,SAAS,cACP,UACA,OACA,UACA,KACA,KACM;AACN,MAAI,YAAY,QAAQ,KAAK,GAAG;AAC9B,QAAI,KAAK,EAAE,SAAS,IAAI,SAAS,SAAS,GAAG,IAAI,KAAK,eAAe,CAAC;AACtE;AAAA,EACF;AACA,MAAI,eAAe,UAAU,KAAK,GAAG;AACnC,mBAAe,SAAS,YAAa,UAAU,OAAO,KAAK,GAAG;AAC9D;AAAA,EACF;AACA,MAAI,SAAS,SAAS,WAAW,MAAM,QAAQ,KAAK,GAAG;AACrD,kBAAc,UAAU,OAAO,KAAK,GAAG;AAAA,EACzC;AACF;AAGA,SAAS,eACP,YACA,QACA,MACA,KACA,KACM;AACN,aAAW,aAAa,OAAO,KAAK,UAAU,GAAG;AAC/C,UAAM,WAAW,WAAW,SAAS;AACrC,UAAM,WAAW,OAAO,UAAU,SAAS,SAAS,KAAK;AACzD;AAAA,MACE;AAAA,MACA,KAAK,SAAS;AAAA,MACd;AAAA,MACA,aAAa,KAAK,WAAW,QAAQ;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,iBACd,QACA,MACkB;AAClB,QAAM,cAA4B,CAAC;AACnC,QAAM,aAAa,oBAAoB,MAAM;AAC7C;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,OAAO,IAAI,SAAS,GAAG;AAAA,IACzB;AAAA,EACF;AACA,SAAO;AAAA,IACL,QAAQ,YAAY,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,IACxC;AAAA,IACA,SAAS,YAAY,WAAW;AAAA,EAClC;AACF;;;AC1KA,SAAS,oBAAoB,GAAY,GAAqB;AAC5D,SACE,MAAM,QACN,MAAM,QACN,OAAO,MAAM,YACb,OAAO,MAAM,YACb,CAAC,MAAM,QAAQ,CAAC,KAChB,CAAC,MAAM,QAAQ,CAAC;AAEpB;AAGA,SAAS,YACP,UACA,SACA,QACe;AACf,QAAM,UAAyB,CAAC;AAChC,QAAM,UAAU,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAE3E,aAAW,OAAO,SAAS;AACzB,QAAI,QAAQ,mBAAoB;AAChC,UAAM,OAAO,SAAS,GAAG,MAAM,IAAI,GAAG,KAAK;AAC3C,UAAM,IAAI,SAAS,GAAG;AACtB,UAAM,IAAI,QAAQ,GAAG;AACrB,QAAI,QAAQ,GAAG,CAAC,EAAG;AAEnB,QAAI,oBAAoB,GAAG,CAAC,GAAG;AAC7B,cAAQ;AAAA,QACN,GAAG;AAAA,UACD;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;AAAA,IACtD;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,SAAS,MAAwD;AACxE,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,kBAAkB;AAAA,EAC/D;AACF;AAKA,SAAS,YAAY,OAA0C;AAC7D,QAAM,MAAc,oBAAI,IAAI;AAC5B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,MAAM,MAAM,CAAC,EAAE,kBAAkB;AACvC,QAAI,IAAK,KAAI,IAAI,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;AAAA,EACpD;AACA,SAAO;AACT;AAGA,SAAS,mBACP,WACA,UACmB;AACnB,QAAM,UAA6B,CAAC;AACpC,aAAW,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC,KAAK,WAAW;AAC9C,QAAI,CAAC,SAAS,IAAI,GAAG,GAAG;AACtB,cAAQ,KAAK,EAAE,MAAM,WAAW,KAAK,OAAO,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IACpE;AAAA,EACF;AACA,aAAW,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC,KAAK,UAAU;AAC7C,QAAI,CAAC,UAAU,IAAI,GAAG,GAAG;AACvB,cAAQ,KAAK,EAAE,MAAM,SAAS,KAAK,OAAO,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IAClE;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,oBACP,WACA,UACmB;AACnB,QAAM,UAA6B,CAAC;AACpC,aAAW,CAAC,KAAK,EAAE,MAAM,SAAS,OAAO,SAAS,CAAC,KAAK,UAAU;AAChE,UAAM,OAAO,UAAU,IAAI,GAAG;AAC9B,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,UAAU,UAAU;AAC3B,cAAQ,KAAK,EAAE,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,SAAS,CAAC;AAAA,IACrE;AACA,UAAM,aAAa,YAAY,KAAK,MAAM,SAAS,EAAE;AACrD,QAAI,WAAW,SAAS,GAAG;AACzB,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,eACP,UACA,SACmB;AACnB,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;AAC/D,QAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;AAC7D,QAAM,UAA6B,CAAC;AACpC,QAAM,MAAM,KAAK,IAAI,UAAU,QAAQ,SAAS,MAAM;AACtD,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,QAAI,QAAQ,UAAU,CAAC,GAAG,SAAS,CAAC,CAAC,EAAG;AACxC,UAAM,aAAa,YAAY,UAAU,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE;AAC5D,QAAI,WAAW,SAAS,GAAG;AACzB,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,KAAK,SAAS,CAAC;AAAA,QACf,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,WACP,UACA,SACA,OACW;AACX,QAAM,YAAY,YAAY,QAAQ;AACtC,QAAM,WAAW,YAAY,OAAO;AACpC,QAAM,UAA6B;AAAA,IACjC,GAAG,mBAAmB,WAAW,QAAQ;AAAA,IACzC,GAAG,oBAAoB,WAAW,QAAQ;AAAA,IAC1C,GAAG,eAAe,UAAU,OAAO;AAAA,EACrC;AACA,SAAO,EAAE,MAAM,SAAS,OAAO,QAAQ;AACzC;AAYA,SAAS,cAAc,KAAyB;AAC9C,SAAO,IAAI,KAAK,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ;AAC5D;AAGA,SAAS,UACP,KACA,GACA,GAC6B;AAC7B,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,UAAM,UAAU,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC;AACxC,UAAM,SAAS,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC;AACvC,QAAI,cAAc,OAAO,KAAK,cAAc,MAAM,GAAG;AACnD,aAAO;AAAA,QACL;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC,EAAE,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,EAAE,CAAC;AAAA,EACtD;AACA,MAAI,oBAAoB,GAAG,CAAC,GAAG;AAC7B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC,EAAE,MAAM,SAAS,MAAM,KAAK,MAAM,GAAG,IAAI,EAAE,CAAC;AACtD;AAKO,SAAS,aACd,UACA,SACU;AACV,QAAM,OAAiB,CAAC;AACxB,QAAM,UAAU,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAE3E,aAAW,OAAO,SAAS;AACzB,QAAI,QAAQ,mBAAoB;AAChC,UAAM,IAAI,SAAS,GAAG;AACtB,UAAM,IAAI,QAAQ,GAAG;AACrB,QAAI,QAAQ,GAAG,CAAC,EAAG;AACnB,SAAK,KAAK,GAAG,UAAU,KAAK,GAAG,CAAC,CAAC;AAAA,EACnC;AAEA,SAAO;AACT;;;AC/MA,SAASA,eAAc,GAA0C;AAC/D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAIA,SAASC,eAAc,UAAuC;AAC5D,SAAO,SAAS,SAAS,WAAW,CAAC,CAAC,SAAS,OAAO;AACxD;AAEA,SAAS,UACP,MACA,YACmC;AACnC,MAAI,CAACD,eAAc,IAAI,EAAG,QAAO;AACjC,QAAM,eAAe,YAAY,MAAM,WAAW,cAAc,CAAC,CAAC;AAClE,SAAO,sBAAsB,eACzB,eACA,EAAE,GAAG,cAAc,CAAC,kBAAkB,GAAG,WAAW,EAAE;AAC5D;AAEA,SAAS,WAAW,OAAgB,UAAuC;AACzE,MAAIC,eAAc,QAAQ,KAAK,MAAM,QAAQ,KAAK,GAAG;AACnD,WAAO,MAAM,IAAI,CAAC,SAAS,UAAU,MAAM,SAAS,KAAM,CAAC;AAAA,EAC7D;AACA,MACE,SAAS,SAAS,YAClB,SAAS,cACTD,eAAc,KAAK,GACnB;AACA,WAAO,YAAY,OAAO,SAAS,UAAU;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,SAAS,YACP,MACA,YACyB;AACzB,MAAI,UAAU;AACd,QAAM,MAA+B,EAAE,GAAG,KAAK;AAC/C,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,UAAU,GAAG;AACzD,QAAI,EAAE,QAAQ,MAAO;AACrB,UAAM,OAAO,WAAW,KAAK,IAAI,GAAG,QAAQ;AAC5C,QAAI,SAAS,KAAK,IAAI,GAAG;AACvB,UAAI,IAAI,IAAI;AACZ,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO,UAAU,MAAM;AACzB;AAMO,SAAS,kBACd,MACA,QACyB;AACzB,SAAO,YAAY,MAAM,oBAAoB,MAAM,CAAC;AACtD;","names":["isPlainObject","isObjectArray"]}
1
+ {"version":3,"sources":["../../src/schema/types.ts","../../src/schema/schemaService.ts","../../src/schema/variant.ts","../../src/schema/schemaResolver.ts","../../src/schema/uiRule.ts","../../src/schema/schemaUtils.ts","../../src/schema/zodSchema.ts","../../src/schema/resolveRefs.ts","../../src/schema/contentValidation.ts","../../src/schema/validateFormData.ts","../../src/schema/mergeFieldErrors.ts","../../src/schema/diffFormData.ts","../../src/schema/ensureFormItemIds.ts"],"sourcesContent":["/**\n * Generic JSON Schema type definitions.\n *\n * These types follow Draft 2020-12 and include x-ui-* extension points\n * for form/widget integration. They are domain-agnostic -- no\n * application-specific entity concepts live here.\n */\n\n/**\n * A declarative presentation rule (JSONForms-style) attached to a field.\n *\n * The rule lives on the field it controls (the target). Its `condition` points\n * at another field's value via a JSON Pointer (RFC 6901) into the form DATA\n * (rooted at the whole form), and validates that value against `condition.schema`.\n * When the condition holds, `effect` is applied to the target:\n * - \"show\" -- render only when the condition holds\n * - \"hide\" -- render only when the condition does NOT hold\n * - \"enable\" -- interactive only when the condition holds\n * - \"disable\"-- non-interactive when the condition holds\n *\n * Example: show `pregnancy_status` when `gender` equals \"female\":\n * ```json\n * { \"effect\": \"show\",\n * \"condition\": { \"scope\": \"/gender\", \"schema\": { \"const\": \"female\" } } }\n * ```\n * Unlike if/then/else, the target field stays declared at its own position, so\n * layout placement is stable.\n */\nexport interface UIRuleCondition {\n /** JSON Pointer (RFC 6901) into the form data, e.g. \"/gender\" or \"/patient/gender\". */\n scope: string;\n /** Schema the value at `scope` is validated against (e.g. { const: \"female\" }). */\n schema: JsonSchemaProperty;\n}\n\nexport type UIRuleEffect = \"show\" | \"hide\" | \"enable\" | \"disable\";\n\nexport interface UIRule {\n effect: UIRuleEffect;\n condition: UIRuleCondition;\n}\n\n/**\n * A named validator reference carried by `x-ui-validate`. The bare-string form\n * is the no-config common case (`\"ibanValid\"`); the object form supplies\n * per-reference config and, for async form validators, the inputs that trigger\n * a re-run (`dependsOn`). See the custom-validation design in sonnet-forms.\n */\nexport interface UIValidateRef {\n /** Registered validator name, resolved against the ValidatorRegistry. */\n name: string;\n /** Per-reference config, passed to the validator as `ctx.args`. */\n args?: Record<string, unknown>;\n /**\n * For an async FORM validator: the field names whose values trigger a re-run.\n * The engine keys/debounces the async result on these values. Ignored for\n * sync validators and for field validators.\n */\n dependsOn?: string[];\n}\n\n/**\n * The value of `x-ui-validate`: one validator or a list. On a property it\n * selects field validators (`(value, ctx) => ...`); on the root object it\n * selects form validators (`(data, ctx) => FieldError[]`). A bare string is\n * shorthand for `{ name }`.\n */\nexport type UIValidate = string | UIValidateRef | Array<string | UIValidateRef>;\n\n/**\n * JSON Schema property definition following Draft 2020-12 specification.\n * Supports basic types, validation constraints, conditionals, and custom extensions.\n */\nexport type JsonSchemaProperty = {\n type?:\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"array\"\n | \"object\"\n | \"null\";\n title?: string;\n description?: string;\n default?: any;\n\n // Validation constraints\n const?: any;\n enum?: any[];\n pattern?: string;\n format?: string;\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n exclusiveMinimum?: number;\n exclusiveMaximum?: number;\n\n // Array types\n items?: JsonSchemaProperty;\n minItems?: number;\n maxItems?: number;\n\n // Object types\n properties?: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Schema references ($ref to $defs / definitions within the same document)\n $ref?: string;\n\n // Reusable sub-schema definitions. Canonical at the document root, but valid\n // at any subschema level (Draft 2020-12: $defs, Draft 7: definitions).\n $defs?: Record<string, JsonSchemaProperty>;\n definitions?: Record<string, JsonSchemaProperty>;\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n /**\n * Discriminator for a `oneOf` union (OpenAPI convention). `propertyName` is\n * the shared property whose `const` value tags the active branch. When\n * absent, the discriminator is auto-detected as the property that is const\n * in every branch.\n */\n discriminator?: { propertyName: string };\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Field ordering within nested objects (same as JsonSchema's x-ui-order)\n \"x-ui-order\"?: string[];\n\n // Declarative presentation rule: show/hide or enable/disable this field based\n // on another field's value. Unlike if/then/else (which adds/removes the field\n // structurally), the field stays declared at its position; the rule only\n // toggles its presentation. See UIRule.\n \"x-ui-rule\"?: UIRule;\n\n // Custom UI hints (x-ui-* extensions align with UISchema)\n \"x-ui-widget\"?: string;\n \"x-ui-options\"?: Record<string, any>;\n \"x-ui-layout\"?: UILayoutConfig;\n \"x-ui-title\"?: string;\n \"x-ui-placeholder\"?: string;\n \"x-ui-description\"?: string;\n\n // Custom validators, by name, resolved against the ValidatorRegistry. On a\n // property these are field validators (one value); on the root object they are\n // form validators (whole data). See the custom-validation design in\n // sonnet-forms. Interpreted by the form renderer; declared here as a shared\n // x-ui-* contract key.\n \"x-ui-validate\"?: UIValidate;\n\n // Table column extensions\n \"x-ui-column\"?: {\n /**\n * Display order (ascending). Properties without an order are appended\n * after the ordered ones, keeping their schema declaration order.\n */\n order?: number;\n /** Exclude this column from the table entirely. */\n hidden?: boolean;\n /** Fixed column width in pixels. */\n width?: number;\n /** Minimum column width in pixels. */\n minWidth?: number;\n };\n /** Cell renderer name. Inferred from type+format when absent. */\n \"x-ui-cell\"?: string;\n /** Cell renderer configuration options (colors, href template, etc.). */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n};\n\n/**\n * JSON Schema following Draft 2020-12 specification.\n *\n * This is the generic base type. Domain-specific consumers extend it\n * with their own x-* extension keys via intersection types in their\n * own type files.\n */\nexport type JsonSchema = {\n $schema?: string;\n title: string;\n type: \"object\";\n properties: Record<string, JsonSchemaProperty>;\n required?: string[];\n\n // Combining schemas (Draft 7+)\n allOf?: JsonSchemaProperty[];\n anyOf?: JsonSchemaProperty[];\n oneOf?: JsonSchemaProperty[];\n not?: JsonSchemaProperty;\n\n // Conditional schemas (Draft 7+)\n if?: JsonSchemaProperty;\n then?: JsonSchemaProperty;\n else?: JsonSchemaProperty;\n\n // Reusable sub-schema definitions (Draft 2020-12: $defs, Draft 7: definitions)\n $defs?: Record<string, JsonSchemaProperty>;\n definitions?: Record<string, JsonSchemaProperty>;\n\n // UI customization (optional)\n \"x-ui-order\"?: string[];\n // Form (whole-data / cross-field) validators, by name, resolved against the\n // ValidatorRegistry. On the root object these run over the whole form data.\n // See the custom-validation design in sonnet-forms.\n \"x-ui-validate\"?: UIValidate;\n\n // Allow domain-specific extension keys (x-entity, x-graph, x-custom, etc.)\n [key: `x-${string}`]: unknown;\n};\n\n/**\n * WidthToken -- the shared semantic sizing vocabulary used across the form\n * layout system: per-field slot width (x-ui-width) and per-column width\n * (UILayoutRowConfig.widths). \"full\" fills available space; sm/md/lg are\n * fixed inline sizes. Raw CSS remains available as an advanced escape hatch.\n */\nexport type WidthToken = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * The fixed rem values for each non-full WidthToken. This is the single source\n * of truth for token sizing used by:\n * - the CSS grid track map in JsonSchemaFormRenderer (sm -> \"8rem\" track)\n * - the Tailwind class map in FormFieldRenderer (sm -> w-32 = 8rem)\n * Consumers that need px can multiply by 16.\n */\nexport const WIDTH_TOKEN_REMS: Record<WidthToken, string> = {\n sm: \"8rem\",\n md: \"12rem\",\n lg: \"16rem\",\n full: \"1fr\", // grid track only; field slots use w-full instead\n};\n\n/**\n * UISchema - User Interface Schema configuration.\n *\n * Defines how form fields should be rendered and behave in the UI.\n * Separate from JSON Schema (which defines data structure) and focuses\n * on presentation, widgets, and user interaction.\n */\nexport interface UISchema {\n /** Widget name (string) or direct component reference */\n \"x-ui-widget\"?: string | object;\n \"x-ui-options\"?: UISchemaOptions;\n \"x-ui-globalOptions\"?: UISchemaOptions;\n \"x-ui-order\"?: string[];\n /** Declarative show/hide or enable/disable rule (see UIRule). */\n \"x-ui-rule\"?: UIRule;\n /** Custom validators by name (override of the schema-level x-ui-validate). */\n \"x-ui-validate\"?: UIValidate;\n \"x-ui-title\"?: string | false;\n \"x-ui-description\"?: string;\n \"x-ui-placeholder\"?: string;\n /** Semantic field-slot width (default \"full\"). */\n \"x-ui-width\"?: WidthToken;\n /** Column override -- merged over schema-level x-ui-column, UI schema wins. */\n \"x-ui-column\"?: JsonSchemaProperty[\"x-ui-column\"] & { label?: string };\n /** Cell renderer override. */\n \"x-ui-cell\"?: string;\n /** Cell renderer options override -- shallow-merged over schema-level x-ui-cell-options. */\n \"x-ui-cell-options\"?: Record<string, unknown>;\n /** Per-field UISchema for nested sub-forms (ObjectWidget / ArrayWidget) */\n \"x-ui-fields\"?: { [fieldName: string]: UISchema };\n /** Layout config forwarded to the active template (ObjectWidget / ArrayWidget) */\n \"x-ui-layout\"?: UILayoutConfig;\n /** Extension keys not yet enumerated above */\n [key: `x-ui-${string}`]: unknown;\n}\n\n/**\n * UILayoutRowConfig -- one horizontal row in a multi-column object form layout.\n *\n * `fields` lists the field names to place in this row, left to right.\n * Column sizing (grid mode) is resolved in this precedence:\n * 1. `columns` -- raw CSS grid-template-columns (advanced escape hatch).\n * 2. `widths` -- per-cell semantic tokens (the designer-friendly path).\n * 3. default -- equal 1fr per field.\n * `gap` overrides the parent layout gap for this row only.\n *\n * `group` renders the row inside a titled fieldset (presentation-only grouping\n * over flat schema fields -- the data shape is unchanged).\n *\n * Examples:\n * { fields: [\"first_name\", \"last_name\"], widths: [\"md\", \"full\"] }\n * { fields: [\"npi\", \"specialty\", \"email\"], columns: \"20% 1fr 1fr\" }\n * { group: \"Contact\", fields: [\"email\", \"phone\"] }\n */\nexport interface UILayoutRowConfig {\n fields: string[];\n columns?: string;\n /**\n * Per-cell semantic widths, aligned to `fields` by index. Translated to grid\n * tracks: sm/md/lg -> fixed rem tracks, full -> 1fr. Ignored when `columns`\n * is set (raw CSS wins) or in \"flow\" mode. Missing/short entries default to\n * \"full\".\n */\n widths?: WidthToken[];\n gap?: number;\n /**\n * Optional title. When set, the row renders inside a <fieldset> with a\n * <legend>, grouping its fields visually without nesting them in the schema.\n */\n group?: string;\n /**\n * How this row arranges its fields:\n * - \"grid\" (default): CSS grid using `columns`/`widths` (equal 1fr per field\n * when neither is set).\n * - \"flow\": flex row, fields at their natural width, left-to-right, wrapping.\n * Use for compact toolbars / tight field groups. `columns`/`widths` are\n * ignored.\n */\n arrange?: \"grid\" | \"flow\";\n}\n\n/**\n * One named group of a grouped form layout. A group holds rows of fields under\n * a label; the data stays flat (grouping is presentation only, like `group` on\n * a single row). How groups are shown -- tabs, wizard steps, stacked sections\n * -- is chosen by {@link UILayoutConfig.variant}, not by this structure.\n *\n * A group may carry an `x-ui-rule`-style {@link UIRule} to appear conditionally\n * (e.g. show an \"Address\" group only when \"provide address\" is checked). The\n * rule is evaluated against the form data exactly as a field-level rule is.\n */\nexport interface UILayoutGroupConfig {\n /** Label shown on the group's trigger or heading. */\n label: string;\n /** Rows of fields in this group (same shape as UILayoutConfig.rows). */\n rows: UILayoutRowConfig[];\n /**\n * Optional show/hide rule for the whole group: a hidden group disappears\n * along with its fields. Narrowed to the visibility effects -- a disabled\n * group has no meaning, and permitting the effect would let it be authored\n * and then silently ignored.\n */\n rule?: UIRule & { effect: \"show\" | \"hide\" };\n}\n\n/**\n * UILayoutConfig -- layout configuration for ObjectWidget and ArrayWidget.\n *\n * Configures the built-in default templates. Has no effect when a custom\n * template is registered for the field.\n */\nexport interface UILayoutConfig {\n // Selects which built-in item template to use (default: \"vertical\")\n direction?: \"vertical\" | \"horizontal\";\n\n // Multi-column row layout for object forms. Each entry defines one\n // horizontal row of fields. Fields not mentioned in any row are rendered\n // in a vertical stack below the rows.\n rows?: UILayoutRowConfig[];\n\n // Grouped layout. When present (and non-empty), fields are arranged into\n // named groups and `rows` is ignored -- each group carries its own rows.\n // Fields in no group render in a vertical stack below, so nothing can be\n // hidden by accident.\n groups?: UILayoutGroupConfig[];\n\n // How `groups` are presented: \"tabs\" (default) a tab bar, \"stepper\" a wizard,\n // \"sections\" stacked fieldsets. The value names a template in the renderer's\n // TemplateRegistry (\"wizard\" -> WizardGroupTemplate), so a consumer can\n // register its own presentation and select it here. Unknown values fall back\n // to tabs.\n variant?: \"tabs\" | \"stepper\" | \"sections\" | (string & {});\n\n // CSS grid-template-columns for field cells (horizontal mode only).\n // The remove-button column (32px) is appended automatically when canRemove\n // is true -- do not include it here.\n // Default: equal 1fr columns for each field.\n columns?: string;\n\n // Gap between cells in pixels (default: 24 vertical, 8 horizontal)\n gap?: number;\n\n // Show per-field labels (default: true vertical, false horizontal)\n labels?: boolean;\n\n // Compact widget sizing -- h-8 / text-sm instead of full height\n compact?: boolean;\n\n // Label placement for fields in this container: \"top\" (default) renders the\n // label above the control; \"start\" renders it inline on the reading-start\n // side (RTL-safe: left in LTR, right in RTL). Logical values follow the MUI\n // convention. Inherited by the container's fields unless overridden per field\n // via x-ui-options.\n labelPlacement?: \"top\" | \"start\";\n}\n\n/**\n * ValueChange -- a single scalar or nested-object field that changed value.\n *\n * `path` is a dot-separated field path: \"name\", \"address.city\".\n */\nexport type ValueChange = {\n type: \"value\";\n path: string;\n from: unknown;\n to: unknown;\n};\n\n/**\n * ArrayItemChange -- one item-level change within an array field.\n *\n * Items are identified by their form-internal `_fid` (never the server ID).\n * A single item can appear as both \"moved\" and \"modified\" if it was\n * repositioned and its values also changed -- these are emitted as two\n * separate entries with the same `fid`.\n */\nexport type ArrayItemChange =\n | { type: \"added\"; fid: string; index: number; item: unknown }\n | { type: \"removed\"; fid: string; index: number; item: unknown }\n | { type: \"moved\"; fid: string; from: number; to: number }\n | { type: \"modified\"; fid: string; index: number; changes: ValueChange[] };\n\n/**\n * FieldDiff -- the diff for one top-level form field.\n */\nexport type FieldDiff =\n | ValueChange\n | { type: \"array\"; field: string; changes: ArrayItemChange[] };\n\n/**\n * FormDiff -- structured diff produced by diffFormData().\n *\n * One entry per changed top-level field. Fields whose value is deeply\n * equal between original and updated are omitted.\n */\nexport type FormDiff = FieldDiff[];\n\n/**\n * UISchemaOptions - Widget configuration options.\n */\nexport interface UISchemaOptions {\n label?: string | false;\n disabled?: boolean;\n readonly?: boolean;\n compact?: boolean;\n enumNames?: Record<string | number, string>;\n inputType?: string;\n rows?: number;\n searchable?: boolean;\n clearable?: boolean;\n /** Allow the user to type and submit a value not in the enum list (combobox / open-choice). */\n creatable?: boolean;\n /** Label placement: \"top\" (default, above) or \"start\" (inline, RTL-safe). */\n labelPlacement?: \"top\" | \"start\";\n /** Semantic field-slot width (default \"full\"). */\n width?: WidthToken;\n [key: string]: any;\n}\n","/**\n * Schema Service - Pure functions for JSON Schema manipulation.\n *\n * Generic, domain-agnostic utilities for reading and inspecting JSON\n * Schema structures. No React, no HTTP, no side effects.\n *\n * Domain-specific accessors (x-entity, x-graph) live in the consumer's\n * own service layer and import from here.\n */\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nexport function getSchemaProperties(\n schema: JsonSchema,\n): Record<string, JsonSchemaProperty> {\n return schema.properties || {};\n}\n\nexport function isFieldRequired(\n schema: JsonSchema,\n fieldName: string,\n): boolean {\n return schema.required?.includes(fieldName) ?? false;\n}\n\nexport function getFieldDefault(property: JsonSchemaProperty): unknown {\n return property.default;\n}\n\ntype JsonSchemaType =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\n/**\n * Get the effective type of a property.\n *\n * Unwraps the nullable anyOf pattern produced by Zod / Pydantic Optional[T]:\n * `{ anyOf: [{ type: T }, { type: \"null\" }] }` → T.\n * Falls back to \"string\" when no type can be determined.\n */\nexport function getFieldType(property: JsonSchemaProperty): JsonSchemaType {\n if (Array.isArray(property.anyOf)) {\n const nonNull = property.anyOf.find(\n (t): t is { type: JsonSchemaType } => \"type\" in t && t.type !== \"null\",\n );\n if (nonNull) return nonNull.type;\n }\n return (property.type as JsonSchemaType) || \"string\";\n}\n\n/**\n * Returns true when a property uses the Pydantic Optional[bool] pattern:\n * anyOf containing both a boolean member and a null member.\n * Used for tri-state checkbox rendering.\n */\nexport function isNullableBoolean(property: JsonSchemaProperty): boolean {\n if (!Array.isArray(property.anyOf)) return false;\n const hasBoolean = property.anyOf.some((s) => s.type === \"boolean\");\n const hasNull = property.anyOf.some((s) => s.type === \"null\");\n return hasBoolean && hasNull;\n}\n\n/**\n * Extract enum values from a property schema.\n *\n * Handles both the simple case (`{ enum: [...] }`) and the nullable\n * pattern produced by Zod (`{ anyOf: [{ type: \"string\", enum: [...] }, { type: \"null\" }] }`).\n *\n * Returns the enum array if found, or an empty array.\n */\nexport function extractEnumValues(\n property: JsonSchemaProperty,\n): (string | number | boolean)[] {\n if (Array.isArray(property.enum) && property.enum.length > 0) {\n return property.enum;\n }\n if (Array.isArray(property.anyOf)) {\n for (const branch of property.anyOf) {\n if (Array.isArray(branch.enum) && branch.enum.length > 0) {\n return branch.enum;\n }\n }\n }\n return [];\n}\n\n/** Priority: x-ui-title > title > field name. */\nexport function getFieldTitle(\n fieldName: string,\n property: JsonSchemaProperty,\n): string {\n return property[\"x-ui-title\"] || property.title || fieldName;\n}\n\n/**\n * Help text for a field, rendered under the control.\n *\n * `x-ui-description` overrides the standard JSON Schema `description`. There is\n * one help-text slot per field, so a schema that only carries `description` --\n * anything generated from a typed model, or authored in the designer's Data tab\n * -- shows it without needing a UI hint.\n */\nexport function getFieldHelpText(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-description\"] ?? property.description;\n}\n\nexport function getFieldPlaceholder(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-placeholder\"];\n}\n\nexport function getFieldWidget(\n property: JsonSchemaProperty,\n): string | undefined {\n return property[\"x-ui-widget\"];\n}\n\nexport function isFieldOptional(\n schema: JsonSchema,\n fieldName: string,\n property: JsonSchemaProperty,\n): boolean {\n if (isFieldRequired(schema, fieldName)) {\n return false;\n }\n if (property.anyOf && Array.isArray(property.anyOf)) {\n return property.anyOf.some((t) => \"type\" in t && t.type === \"null\");\n }\n return true;\n}\n\nexport function getFieldConstraints(property: JsonSchemaProperty): {\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n format?: string;\n} {\n return {\n minLength: property.minLength,\n maxLength: property.maxLength,\n minimum: property.minimum,\n maximum: property.maximum,\n pattern: property.pattern,\n format: property.format,\n };\n}\n\nexport function getRequiredFields(schema: JsonSchema): string[] {\n return schema.required || [];\n}\n\nexport function getOptionalFields(schema: JsonSchema): string[] {\n const allFields = Object.keys(schema.properties || {});\n const required = schema.required || [];\n return allFields.filter((field) => !required.includes(field));\n}\n\nexport function hasProperties(schema: JsonSchema): boolean {\n return Object.keys(schema.properties || {}).length > 0;\n}\n","/**\n * oneOf (discriminated-union) helpers.\n *\n * A oneOf property is an object whose shape is one of several branches, modeled\n * as `oneOf: [branchSchema, ...]`. Each branch shares a discriminator property\n * -- a field with a `const` value that tags which branch is active. The\n * discriminator is named by `discriminator.propertyName` (OpenAPI convention),\n * or auto-detected as the property that carries a `const` in every branch.\n *\n * These helpers are pure and domain-agnostic.\n */\n\nimport type { JsonSchemaProperty } from \"./types\";\n\n/** Whether a property is a oneOf union (has a non-empty `oneOf`). */\nexport function isOneOf(property: JsonSchemaProperty): boolean {\n return Array.isArray(property.oneOf) && property.oneOf.length > 0;\n}\n\n/**\n * Resolve the discriminator property name for a oneOf union.\n *\n * Uses `discriminator.propertyName` when set; otherwise auto-detects the single\n * property that carries a `const` in every branch. Returns undefined when no\n * consistent discriminator can be determined.\n */\nexport function getDiscriminator(\n property: JsonSchemaProperty,\n): string | undefined {\n const explicit = property.discriminator?.propertyName;\n if (typeof explicit === \"string\" && explicit) return explicit;\n\n const branches = property.oneOf;\n if (!Array.isArray(branches) || branches.length === 0) return undefined;\n\n // Candidate keys: const-valued properties in the first branch.\n const first = branches[0].properties ?? {};\n const candidates = Object.keys(first).filter(\n (k) => first[k]?.const !== undefined,\n );\n\n // Keep only candidates that are const in every branch.\n return candidates.find((key) =>\n branches.every((b) => b.properties?.[key]?.const !== undefined),\n );\n}\n\n/** The discriminator const value for a branch, or undefined. */\nexport function getBranchValue(\n branch: JsonSchemaProperty,\n discriminator: string,\n): string | number | boolean | undefined {\n const c = branch.properties?.[discriminator]?.const;\n if (\n typeof c === \"string\" ||\n typeof c === \"number\" ||\n typeof c === \"boolean\"\n ) {\n return c;\n }\n return undefined;\n}\n\n/** Human label for a branch: its `title`, else the discriminator value. */\nexport function getBranchLabel(\n branch: JsonSchemaProperty,\n discriminator: string | undefined,\n index: number,\n): string {\n if (branch.title) return branch.title;\n if (discriminator) {\n const v = getBranchValue(branch, discriminator);\n if (v !== undefined) return String(v);\n }\n return `Option ${index + 1}`;\n}\n\n/**\n * Find the branch that matches the current form data by comparing the\n * discriminator value. Returns the branch index, or -1 when none match.\n */\nexport function findActiveBranch(\n property: JsonSchemaProperty,\n data: Record<string, unknown> | undefined,\n discriminator: string | undefined,\n): number {\n const branches = property.oneOf;\n if (!Array.isArray(branches) || !discriminator || !data) return -1;\n const current = data[discriminator];\n if (current === undefined) return -1;\n return branches.findIndex(\n (b) => getBranchValue(b, discriminator) === current,\n );\n}\n","/**\n * Schema Resolver - Resolves conditional schemas (if-then-else, allOf, etc.)\n *\n * This service handles JSON Schema conditionals by evaluating conditions\n * based on form data and merging the appropriate schema branches.\n *\n * Based on react-jsonschema-form's retrieveSchema implementation.\n *\n * Supported features:\n * - if-then-else conditionals\n * - allOf schema merging\n * - Nested conditionals (if-then-else inside allOf)\n *\n * @module services/schemaResolver\n */\n\nimport Ajv from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { JsonSchema } from \"./types\";\nimport { warnLog } from \"../logger\";\n\n// Singleton AJV instance. Exported so sibling modules (e.g. uiRule) validate\n// with the same configuration rather than spinning up a second compiler.\nexport const ajv = new Ajv({\n allErrors: true,\n strict: false,\n validateFormats: true,\n // Allow unknown keywords (for x-ui-widget, x-ui-title, etc.)\n strictSchema: false,\n // Enable $data references so a keyword can reference another field's value,\n // e.g. { \"const\": { \"$data\": \"1/password\" } } for \"must equal password\".\n // This is the declarative path for simple cross-field rules (see the\n // custom-validation design); complex cross-field logic uses form validators.\n $data: true,\n});\naddFormats(ajv);\n\n/**\n * Resolve conditional schema based on form data\n *\n * Evaluates if-then-else conditions and merges allOf schemas to produce\n * a final schema with only the properties that should be visible given\n * the current form data.\n *\n * @param schema - Original JSON Schema with conditionals\n * @param formData - Current form data to evaluate conditions against\n * @returns Resolved schema with conditionals merged\n *\n * @example\n * ```typescript\n * const schema = {\n * properties: { taxonomy: { type: 'string', enum: ['Tech', 'Arch'] } },\n * allOf: [\n * {\n * if: { properties: { taxonomy: { const: 'Tech' } } },\n * then: { properties: { tech_field: { type: 'string' } } }\n * }\n * ]\n * };\n *\n * const resolved = resolveSchema(schema, { taxonomy: 'Tech' });\n * // Result: { properties: { taxonomy: {...}, tech_field: {...} } }\n * ```\n */\nexport function resolveSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n\n // Resolve allOf members (AND -- each member's conditional is merged).\n let resolvedSchema = resolveAllOf(schema, formData);\n\n // Resolve anyOf members that carry if/then/else (OR -- merge the `then` of\n // any member whose condition holds; ignore members without an `if`).\n resolvedSchema = resolveAnyOfConditionals(resolvedSchema, formData);\n\n // Resolve any top-level if-then-else.\n resolvedSchema = resolveConditionals(resolvedSchema, formData);\n\n return resolvedSchema;\n}\n\n/**\n * Resolve anyOf members that carry if/then/else (OR-conditional semantics).\n *\n * Used for FHIR `enableBehavior: \"any\"`: a field is enabled when ANY of its\n * `enableWhen` conditions holds. In JSON Schema this is expressed as `anyOf`\n * members with `if/then/else`. Members without an `if` are left untouched\n * (they may be structural anyOf entries, e.g. the nullable pattern).\n *\n * For each matching member (condition holds), the resolved `then` is merged\n * into the base schema. Non-conditional anyOf members are preserved on the\n * result so downstream resolvers (nullable anyOf etc.) still see them.\n */\nfunction resolveAnyOfConditionals(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.anyOf || !Array.isArray(schema.anyOf)) return schema;\n\n const conditionalMembers = schema.anyOf.filter(\n (m) => typeof m === \"object\" && m !== null && \"if\" in m,\n );\n // No conditional members -- leave anyOf untouched for other resolvers.\n if (conditionalMembers.length === 0) return schema;\n\n const nonConditionalMembers = schema.anyOf.filter(\n (m) => !(typeof m === \"object\" && m !== null && \"if\" in m),\n );\n\n // Strip anyOf so mergeSchemas doesn't clobber it; re-attach non-conditional\n // members if any remain.\n const { anyOf: _anyOf, ...baseSchema } = schema as JsonSchema & {\n anyOf: unknown[];\n };\n\n let result: JsonSchema =\n nonConditionalMembers.length > 0\n ? { ...(baseSchema as JsonSchema), anyOf: nonConditionalMembers }\n : (baseSchema as JsonSchema);\n\n // Merge the `then` of every conditional member whose `if` holds (OR).\n for (const member of conditionalMembers) {\n const resolved = resolveConditionals(member, formData);\n // resolveConditionals strips `if/then/else` and merges the branch.\n // Only merge if something was actually resolved (condition held).\n if (resolved !== member && typeof resolved === \"object\") {\n result = mergeSchemas(result, resolved);\n }\n }\n\n return result;\n}\n\n/**\n * Resolve if-then-else conditionals\n *\n * Evaluates the 'if' condition against formData and merges the appropriate\n * branch ('then' or 'else') into the base schema.\n *\n * @param schema - Schema potentially containing if-then-else\n * @param formData - Current form data\n * @returns Schema with conditional merged\n */\nfunction resolveConditionals(schema: any, formData: Record<string, any>): any {\n if (!schema.if) {\n return schema;\n }\n\n const {\n if: ifSchema,\n then: thenSchema,\n else: elseSchema,\n ...baseSchema\n } = schema;\n\n try {\n // Compile and validate the 'if' condition\n const validator = ajv.compile(ifSchema);\n const conditionMet = validator(formData || {});\n\n // Choose the appropriate branch\n const branchSchema = conditionMet ? thenSchema : elseSchema;\n\n if (branchSchema && typeof branchSchema === \"object\") {\n // Merge the branch into the base schema\n return mergeSchemas(baseSchema, branchSchema);\n }\n\n return baseSchema;\n } catch (error) {\n warnLog(\"Error resolving conditional schema:\", error);\n return baseSchema;\n }\n}\n\n/**\n * Resolve allOf schemas\n *\n * Merges all schemas in the allOf array, recursively resolving any\n * conditionals within each subschema.\n *\n * @param schema - Schema potentially containing allOf\n * @param formData - Current form data\n * @returns Schema with allOf merged\n */\nfunction resolveAllOf(\n schema: JsonSchema,\n formData: Record<string, any>,\n): JsonSchema {\n if (!schema.allOf || !Array.isArray(schema.allOf)) {\n return schema;\n }\n\n const { allOf, ...baseSchema } = schema;\n\n // Resolve each allOf element (they might have if-then-else)\n const resolvedAllOf = allOf.map((subschema) => {\n if (typeof subschema !== \"object\") {\n return subschema;\n }\n\n // Recursively resolve conditionals in each allOf element\n return resolveConditionals(subschema, formData);\n });\n\n // Merge all resolved schemas together\n return resolvedAllOf.reduce(\n (acc, subschema) => mergeSchemas(acc, subschema),\n baseSchema,\n );\n}\n\n/**\n * Deep merge two schemas\n *\n * Merges two JSON Schemas together, with special handling for:\n * - properties: Deep merge\n * - required: Array union (unique values)\n * - Other keys: Override with schema2 value\n *\n * @param schema1 - Base schema\n * @param schema2 - Schema to merge in (takes precedence)\n * @returns Merged schema\n */\nfunction mergeSchemas(\n schema1: JsonSchema | any,\n schema2: JsonSchema | any,\n): JsonSchema {\n if (!schema2 || typeof schema2 !== \"object\") {\n return schema1;\n }\n if (!schema1 || typeof schema1 !== \"object\") {\n return schema2;\n }\n\n const merged = { ...schema1 };\n\n Object.keys(schema2).forEach((key) => {\n if (key === \"properties\" && schema1.properties && schema2.properties) {\n // Deep merge properties - for each property, merge the definitions\n merged.properties = { ...schema1.properties };\n Object.keys(schema2.properties).forEach((propName) => {\n if (merged.properties[propName]) {\n // Property exists in both schemas, deep merge them\n merged.properties[propName] = {\n ...merged.properties[propName],\n ...schema2.properties[propName],\n };\n } else {\n // Property only in schema2, just copy it\n merged.properties[propName] = schema2.properties[propName];\n }\n });\n } else if (key === \"required\" && schema1.required && schema2.required) {\n // Merge required arrays (unique values)\n const combinedRequired = [...schema1.required, ...schema2.required];\n merged.required = Array.from(new Set(combinedRequired));\n } else if (key === \"allOf\" && schema1.allOf && schema2.allOf) {\n // Concatenate allOf arrays\n merged.allOf = [...schema1.allOf, ...schema2.allOf];\n } else {\n // Override for other keys\n merged[key] = schema2[key];\n }\n });\n\n return merged;\n}\n\n/**\n * Validate formData against a schema\n *\n * Useful for checking if form data meets schema requirements,\n * including type validation and format checking.\n *\n * @param schema - JSON Schema to validate against\n * @param formData - Form data to validate\n * @returns Validation result with errors\n *\n * @example\n * ```typescript\n * const result = validateSchema(schema, { name: 'John', age: 'invalid' });\n * if (!result.valid) {\n * errorLog(result.errors); // AJV error objects\n * }\n * ```\n */\nexport function validateSchema(\n schema: JsonSchema,\n formData: Record<string, any>,\n): { valid: boolean; errors: any[] } {\n try {\n const validator = ajv.compile(schema);\n const valid = validator(formData);\n\n return {\n valid,\n errors: validator.errors || [],\n };\n } catch (error) {\n warnLog(\"Error validating schema:\", error);\n return {\n valid: false,\n errors: [{ message: \"Schema validation error\" }],\n };\n }\n}\n\n/**\n * Check if a value matches a const constraint\n *\n * Helper for evaluating const conditions in conditionals.\n *\n * @param value - Value to check\n * @param constValue - Expected const value\n * @returns true if value matches const\n */\nexport function matchesConst(value: any, constValue: any): boolean {\n return value === constValue;\n}\n\n/**\n * Check if a schema has conditionals\n *\n * Useful for determining if schema resolution is needed.\n *\n * @param schema - JSON Schema to check\n * @returns true if schema contains if-then-else or allOf\n */\nexport function hasConditionals(schema: JsonSchema): boolean {\n if (!schema || typeof schema !== \"object\") {\n return false;\n }\n\n return !!(schema.if || schema.allOf || schema.anyOf || schema.oneOf);\n}\n","/**\n * Declarative UI rules (x-ui-rule): show/hide or enable/disable a field based\n * on another field's value.\n *\n * The rule lives on the target field. Its condition references a value elsewhere\n * in the form data via a JSON Pointer (RFC 6901) rooted at the whole form, and\n * validates that value against a schema. Evaluation yields presentation flags\n * (visible / enabled) the renderer applies -- the field itself stays declared at\n * its own position, so layout is stable (unlike if/then/else).\n *\n * Scopes are root-anchored at every depth: a rule on a nested field reads its\n * pointer from the top of the form, so `/country` means the form's `country`\n * even from inside `address`, and a sibling is reached by its full path\n * (`/address/city`). This matches every other JSON Pointer in the vocabulary\n * (`editablePaths`, validation error pointers). The renderer is responsible for\n * passing the root form data in -- see `useRootFormData` in sonnet-forms.\n *\n * A consequence worth knowing when authoring inside `$defs`: a definition is\n * reusable, but a root-anchored scope is not. A rule inside a definition that\n * references `/country` only holds for forms that have a top-level `country`.\n * This is inherent to root-anchoring and is the same in comparable libraries.\n */\n\nimport type { JsonSchemaProperty, UIRule } from \"./types\";\nimport { ajv } from \"./schemaResolver\";\nimport { warnLog } from \"../logger\";\n\n// Cache compiled validators by the serialized schema to avoid recompiling the\n// same condition schema on every form render. The Map is module-level and\n// bounded by the number of distinct x-ui-rule condition schemas in the app.\nconst validatorCache = new Map<string, ReturnType<typeof ajv.compile>>();\n\nfunction getValidator(\n schema: JsonSchemaProperty,\n): ReturnType<typeof ajv.compile> {\n const key = JSON.stringify(schema);\n let validator = validatorCache.get(key);\n if (!validator) {\n validator = ajv.compile(schema);\n validatorCache.set(key, validator);\n }\n return validator;\n}\n\n/** The presentation state a rule resolves to for its target field. */\nexport interface RuleState {\n visible: boolean;\n enabled: boolean;\n}\n\nconst VISIBLE_ENABLED: RuleState = { visible: true, enabled: true };\n\n/** Unescape a single JSON Pointer reference token (RFC 6901: ~1 -> /, ~0 -> ~). */\nfunction unescapeToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Resolve a JSON Pointer against a data object. Returns the value at the pointer,\n * or undefined when any segment is missing. \"\" (empty pointer) returns the root.\n */\nexport function getAtPointer(data: unknown, pointer: string): unknown {\n if (pointer === \"\") return data;\n if (!pointer.startsWith(\"/\")) return undefined;\n const tokens = pointer.slice(1).split(\"/\").map(unescapeToken);\n let current: unknown = data;\n for (const token of tokens) {\n if (current == null || typeof current !== \"object\") return undefined;\n if (Array.isArray(current)) {\n const idx = Number(token);\n if (!Number.isInteger(idx) || idx < 0 || idx >= current.length)\n return undefined;\n current = current[idx];\n } else {\n current = (current as Record<string, unknown>)[token];\n }\n }\n return current;\n}\n\n// Whether the rule's condition holds for the given form data.\nfunction conditionHolds(rule: UIRule, formData: unknown): boolean {\n const { scope, schema } = rule.condition;\n const value = getAtPointer(formData, scope);\n try {\n const validator = getValidator(schema as JsonSchemaProperty);\n return !!validator(value);\n } catch (error) {\n warnLog(\"Error evaluating x-ui-rule condition:\", error);\n return false;\n }\n}\n\n/**\n * Resolve a field's presentation state from its `x-ui-rule` (if any) against the\n * current form data. Fields without a rule are visible and enabled.\n */\nexport function evaluateUiRule(\n rule: UIRule | undefined,\n formData: unknown,\n): RuleState {\n if (!rule) return VISIBLE_ENABLED;\n const holds = conditionHolds(rule, formData);\n switch (rule.effect) {\n case \"show\":\n return { visible: holds, enabled: true };\n case \"hide\":\n return { visible: !holds, enabled: true };\n case \"enable\":\n return { visible: true, enabled: holds };\n case \"disable\":\n return { visible: true, enabled: !holds };\n }\n}\n","/**\n * Schema utilities\n *\n * Pure functions for common JSON Schema operations:\n * - Applying schema defaults to form data\n * - Extracting default values from a schema\n * - Generating a schema from a list of template variables\n *\n * All functions are side-effect free.\n *\n * @module services/schema/schemaUtils\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\n/**\n * Merge schema default values into an existing data object.\n *\n * For each property in the schema that has a `default` value, if the\n * corresponding key is absent (`undefined`) in `data`, the default is applied.\n * Existing values — including `null` and `false` — are never overwritten.\n *\n * @param data - Existing form data (may be partial)\n * @param schema - JSON Schema containing property defaults\n * @returns New object with defaults filled in for missing keys\n */\nexport function applySchemaDefaults(\n data: Record<string, unknown>,\n schema: JsonSchema | JsonSchemaProperty,\n): Record<string, unknown> {\n const result = { ...data };\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (result[fieldName] === undefined && property.default !== undefined) {\n result[fieldName] = property.default;\n }\n }\n\n return result;\n}\n\n/**\n * Extract all default values declared in a JSON Schema.\n *\n * Walks the schema's top-level properties and collects any `default` values.\n * Properties without a `default` are omitted from the result.\n *\n * @param schema - JSON Schema to extract defaults from\n * @returns Record of field names to their default values\n *\n * @example\n * ```ts\n * extractSchemaDefaults({\n * type: 'object',\n * properties: {\n * name: { type: 'string', default: 'John' },\n * age: { type: 'number' },\n * },\n * });\n * // => { name: 'John' }\n * ```\n */\nexport function extractSchemaDefaults(\n schema: JsonSchema,\n): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n const properties = schema.properties ?? {};\n\n for (const [fieldName, property] of Object.entries(properties)) {\n if (property.default !== undefined) {\n defaults[fieldName] = property.default;\n }\n }\n\n return defaults;\n}\n\n/**\n * Generate a JSON Schema from a list of template variable names.\n *\n * Each variable becomes a required string property with a capitalised title.\n * If an existing schema is provided, its property definitions are preserved\n * for variables that still appear in the list; new variables are added as plain\n * strings; properties no longer in the list are dropped.\n *\n * @param variables - Variable names to include in the schema\n * @param existingSchema - Optional schema to merge with (preserves extensions)\n * @returns A JSON Schema object with one property per variable\n *\n * @example\n * ```ts\n * generateJsonSchemaFromVariables(['productId', 'limit']);\n * // => {\n * // type: 'object',\n * // properties: {\n * // productId: { type: 'string', title: 'ProductId' },\n * // limit: { type: 'string', title: 'Limit' },\n * // },\n * // required: ['productId', 'limit'],\n * // }\n * ```\n */\nexport function generateJsonSchemaFromVariables(\n variables: string[],\n existingSchema?: { properties?: Record<string, JsonSchemaProperty> },\n): JsonSchema {\n const existingProperties = existingSchema?.properties ?? {};\n\n const properties: JsonSchema[\"properties\"] = {};\n for (const variable of variables) {\n properties[variable] = existingProperties[variable] ?? {\n type: \"string\",\n title: variable.charAt(0).toUpperCase() + variable.slice(1),\n };\n }\n\n return {\n title: \"\",\n type: \"object\",\n properties,\n required: variables,\n };\n}\n","/**\n * Zod-to-JSON-Schema conversion with x-ui-* metadata passthrough.\n *\n * Wraps z.toJSONSchema() with an override that copies all .meta() keys\n * into the generated JSON Schema output, including x-ui-* extension keys\n * used by the form renderer.\n *\n * @module lib/schema/zodSchema\n */\n\nimport { z } from \"zod\";\nimport type { JsonSchema } from \"./types\";\n\n/**\n * Convert a Zod schema to a JsonSchema compatible with the form renderer.\n *\n * All fields annotated with .meta() -- including x-ui-widget, x-ui-order,\n * x-ui-options, title, description -- are passed through to the output.\n *\n * @example\n * ```typescript\n * const MySchema = z.object({\n * name: z.string().meta({ title: \"Name\" }),\n * notes: z.string().optional().meta({ \"x-ui-widget\": \"textarea\" }),\n * });\n *\n * const jsonSchema = toJsonSchema(MySchema);\n * type MyData = z.infer<typeof MySchema>;\n * ```\n */\nexport function toJsonSchema(schema: z.ZodType): JsonSchema {\n return z.toJSONSchema(schema, {\n reused: \"inline\",\n override(ctx) {\n const def = (\n ctx.zodSchema as unknown as {\n _def?: { metadata?: Record<string, unknown> };\n }\n )._def;\n if (def?.metadata) {\n Object.assign(ctx.jsonSchema, def.metadata);\n }\n },\n }) as JsonSchema;\n}\n","/**\n * $ref Resolution -- resolves JSON Schema references.\n *\n * Walks the schema tree and replaces `{ \"$ref\": ... }` nodes with the\n * corresponding sub-schema. By default it resolves local, in-document refs:\n * `#/$defs/name` (Draft 2020-12) or `#/definitions/name` (Draft 7).\n *\n * A caller may inject a `RefResolver` to resolve ANY ref string (e.g. a schema\n * registry id like `sdf_...`, or a canonical URI). The injected resolver is\n * tried first; the in-document `$defs`/`definitions` lookup is the fallback, so\n * both ref styles work together. The resolver is synchronous: consumers that\n * fetch schemas asynchronously (e.g. from a registry over HTTP) pre-load them\n * into a map and pass a `(ref) => map[ref]` lookup. This keeps the toolkit pure\n * (no HTTP, no async).\n *\n * Properties on the referencing node are merged on top of the resolved target,\n * so context-specific overrides (e.g. a custom title) work without duplicating\n * the definition. Refs that no resolver can satisfy are left as-is.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\ntype Defs = Record<string, JsonSchemaProperty>;\n\n/** Resolve a `$ref` string to its target sub-schema, or undefined if unknown. */\nexport type RefResolver = (ref: string) => JsonSchemaProperty | undefined;\n\n// Extract the definition name from a local $ref pointer.\n// Supports \"#/$defs/name\" (2020-12) and \"#/definitions/name\" (Draft 7).\nfunction extractDefName(ref: string): string | null {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n return match ? match[2] : null;\n}\n\n/** Build the default resolver: an in-document `$defs`/`definitions` lookup. */\nfunction inDocumentResolver(defs: Defs): RefResolver {\n return (ref) => {\n const name = extractDefName(ref);\n return name ? defs[name] : undefined;\n };\n}\n\n// Resolve a $ref node: look up the target, merge overrides, recurse.\nfunction resolveRef(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty {\n const ref = node.$ref!;\n const resolved = resolve(ref);\n if (!resolved) return node; // unknown ref -- leave as-is\n if (seen.has(ref)) return node; // cycle guard\n seen.add(ref);\n\n const overrides = Object.fromEntries(\n Object.entries(node).filter(([k]) => k !== \"$ref\"),\n ) as JsonSchemaProperty;\n const merged =\n Object.keys(overrides).length > 0\n ? { ...resolved, ...overrides }\n : resolved;\n\n const result = resolveNode(merged, resolve, seen);\n seen.delete(ref);\n return result;\n}\n\n// Resolve an array of schemas, returning null if nothing changed.\nfunction resolveList(\n list: JsonSchemaProperty[],\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty[] | null {\n const resolved = list.map((s) => resolveNode(s, resolve, seen));\n return resolved.some((s, i) => s !== list[i]) ? resolved : null;\n}\n\n// Resolve properties map, returning null if nothing changed.\nfunction resolveProperties(\n props: Record<string, JsonSchemaProperty>,\n resolve: RefResolver,\n seen: Set<string>,\n): Record<string, JsonSchemaProperty> | null {\n const entries = Object.entries(props);\n const resolved = entries.map(\n ([k, v]) => [k, resolveNode(v, resolve, seen)] as const,\n );\n return resolved.some(([, v], i) => v !== entries[i][1])\n ? Object.fromEntries(resolved)\n : null;\n}\n\n// Resolve items, returning null if unchanged.\nfunction resolveItems(\n items: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty | null {\n const r = resolveNode(items, resolve, seen);\n return r !== items ? r : null;\n}\n\n// Collect all resolved children of a node into a partial override object.\nfunction resolveChildren(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): Partial<JsonSchemaProperty> | null {\n const overrides: Partial<JsonSchemaProperty> = {};\n let changed = false;\n\n const props = node.properties\n ? resolveProperties(node.properties, resolve, seen)\n : null;\n if (props) {\n overrides.properties = props;\n changed = true;\n }\n\n const items = node.items ? resolveItems(node.items, resolve, seen) : null;\n if (items) {\n overrides.items = items;\n changed = true;\n }\n\n for (const key of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n const list = node[key] ? resolveList(node[key]!, resolve, seen) : null;\n if (list) {\n (overrides as Record<string, unknown>)[key] = list;\n changed = true;\n }\n }\n\n return changed ? overrides : null;\n}\n\nfunction resolveNode(\n node: JsonSchemaProperty,\n resolve: RefResolver,\n seen: Set<string>,\n): JsonSchemaProperty {\n if (node.$ref) return resolveRef(node, resolve, seen);\n const overrides = resolveChildren(node, resolve, seen);\n return overrides ? { ...node, ...overrides } : node;\n}\n\n/**\n * Resolve `$ref` pointers in a JSON Schema document.\n *\n * By default resolves in-document `#/$defs` / `#/definitions` refs. Pass a\n * `resolve` to also resolve registry/URI refs; it is tried first and the\n * in-document lookup is the fallback. The original schema is never mutated;\n * a document with no refs to resolve is returned unchanged.\n */\nexport function resolveRefs(\n schema: JsonSchema,\n resolve?: RefResolver,\n): JsonSchema {\n const defs: Defs = schema.$defs ?? schema.definitions ?? {};\n // Nothing to resolve against: no injected resolver and no in-document defs.\n if (!resolve && Object.keys(defs).length === 0) return schema;\n\n const fallback = inDocumentResolver(defs);\n const resolver: RefResolver = resolve\n ? (ref) => resolve(ref) ?? fallback(ref)\n : fallback;\n\n // Cast to JsonSchemaProperty so resolveNode can walk the full tree\n // including the root-level properties and allOf. The result is cast\n // back -- JsonSchema is a structural subtype of JsonSchemaProperty.\n const seen = new Set<string>();\n const resolved = resolveNode(\n schema as unknown as JsonSchemaProperty,\n resolver,\n seen,\n );\n return resolved as unknown as JsonSchema;\n}\n","/**\n * Content-constraint validation, layered on top of validateFormData.\n *\n * validateFormData covers required-ness and array cardinality with\n * human-authored messages. This module adds the JSON Schema *content*\n * constraints it does not: `format`, `pattern`, `type`, `minLength` /\n * `maxLength`, `minimum` / `maximum` (and their exclusive variants),\n * `enum`, `const`, and `multipleOf`. It reuses the shared AJV instance\n * (already configured with `ajv-formats`) and maps AJV's errors into the\n * same `FieldError` shape the renderer consumes -- AJV's `instancePath` is\n * already an RFC 6901 JSON Pointer, so per-field and per-tab attribution\n * keep working unchanged.\n *\n * Required and array-cardinality keywords are intentionally excluded here so\n * validateFormData stays the single source of those messages -- no field ever\n * shows two errors for the same cause.\n */\n\nimport { ajv } from \"./schemaResolver\";\nimport { getFieldTitle } from \"./schemaService\";\nimport type { FieldError } from \"./validateFormData\";\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\n// Keywords owned by validateFormData (its messages win). Everything else AJV\n// reports -- format, pattern, type, length, range, enum, const -- is content.\nconst OWNED_BY_VALIDATE_FORM_DATA = new Set([\n \"required\",\n \"minItems\",\n \"maxItems\",\n]);\n\n// RFC 6901 token unescape: \"~1\" -> \"/\", \"~0\" -> \"~\".\nfunction unescapePointerToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n// Walk an instance pointer through the schema to build a human label matching\n// validateFormData's style: \"Address > City\", \"Tags[3] > Label\".\nfunction labelForPointer(schema: JsonSchema, pointer: string): string {\n if (pointer === \"\") return \"\";\n const tokens = pointer.split(\"/\").slice(1).map(unescapePointerToken);\n const parts: string[] = [];\n // A schema node exposes both `properties` and `items`; treat every step as a\n // property so the walk is uniform.\n let node: JsonSchemaProperty | undefined = schema as JsonSchemaProperty;\n\n for (const token of tokens) {\n if (/^\\d+$/.test(token)) {\n // Array index: append 1-based [i] to the last label part, descend items.\n if (parts.length > 0) {\n parts[parts.length - 1] =\n `${parts[parts.length - 1]}[${Number(token) + 1}]`;\n }\n node = node?.items;\n continue;\n }\n const props: Record<string, JsonSchemaProperty> | undefined =\n node?.properties;\n const child: JsonSchemaProperty | undefined = props?.[token];\n parts.push(child ? getFieldTitle(token, child) : token);\n node = child;\n }\n return parts.join(\" > \");\n}\n\n// Build one human message for an AJV content error.\nfunction messageFor(label: string, keyword: string, message?: string): string {\n const what = label || \"Value\";\n // AJV format errors read best with the field label prepended.\n const detail = message ?? \"is invalid\";\n return `${what} ${detail}`;\n}\n\n/**\n * Validate content constraints via AJV and return them as FieldErrors.\n *\n * Only content keywords are returned; required and array-cardinality errors\n * are filtered out (validateFormData owns those). Returns an empty array when\n * the schema compiles cleanly and the data satisfies every content rule, or\n * when AJV cannot compile the schema (in which case the caller's structural\n * validation still stands).\n */\nexport function validateContentConstraints(\n schema: JsonSchema,\n data: Record<string, unknown>,\n): FieldError[] {\n let validate;\n try {\n validate = ajv.compile(schema);\n } catch {\n // A schema AJV cannot compile must not break the form -- structural\n // validation (validateFormData) already ran and stands on its own.\n return [];\n }\n\n if (validate(data)) return [];\n\n const out: FieldError[] = [];\n const seen = new Set<string>();\n for (const err of validate.errors ?? []) {\n if (OWNED_BY_VALIDATE_FORM_DATA.has(err.keyword)) continue;\n const pointer = err.instancePath;\n const label = labelForPointer(schema, pointer);\n const message = messageFor(label, err.keyword, err.message);\n // De-dupe identical pointer+message (AJV can emit siblings for unions).\n const key = `${pointer}\\u0000${message}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ pointer, message });\n }\n return out;\n}\n","/**\n * Recursive form data validation against a JSON Schema.\n *\n * Validates required fields at every nesting level: top-level properties,\n * nested objects (type: \"object\"), and array items (type: \"array\" with\n * items.type: \"object\"). Returns human-readable error strings, the same\n * errors keyed by JSON Pointer, and a boolean summary.\n *\n * The pointer form lets callers attribute an error to a specific field --\n * needed to surface errors that are not currently on screen (e.g. a badge on\n * a form tab whose fields are hidden). `errors` remains the flat message list.\n *\n * Pure function -- no React, no side effects.\n */\n\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\nimport { getSchemaProperties, getFieldTitle } from \"./schemaService\";\nimport { validateContentConstraints } from \"./contentValidation\";\n\n/** A validation error attributed to the field that produced it. */\nexport interface FieldError {\n /**\n * JSON Pointer (RFC 6901) to the offending field, e.g. `/email`,\n * `/contact/email`, or `/tags/2/label` for the third array item.\n */\n pointer: string;\n /** Human-readable message, e.g. \"Contact > Email is required\". */\n message: string;\n}\n\nexport interface ValidationResult {\n /** Flat, human-readable messages (in field declaration order). */\n errors: string[];\n /** The same errors, each attributed to its field via a JSON Pointer. */\n fieldErrors: FieldError[];\n isValid: boolean;\n}\n\n// Where a validated value sits: its human label path and its JSON Pointer.\ninterface FieldContext {\n /** Display path, e.g. \"Address > City\" or \"Tags[3] > Label\". */\n label: string;\n /** JSON Pointer to the value, e.g. \"/address/city\" or \"/tags/2/label\". */\n pointer: string;\n}\n\n// RFC 6901 token escaping: \"~\" -> \"~0\", \"/\" -> \"~1\".\nfunction escapePointerToken(token: string): string {\n return token.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\n\n// Check whether a single value satisfies a \"required\" constraint.\nfunction isEmpty(value: unknown): boolean {\n return value === undefined || value === null || value === \"\";\n}\n\n// Check whether a value is a non-array plain object suitable for recursion.\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n// The context of a named child of the object at `parent`.\nfunction childContext(\n parent: FieldContext,\n fieldName: string,\n property: JsonSchemaProperty,\n): FieldContext {\n const title = getFieldTitle(fieldName, property);\n return {\n label: parent.label ? `${parent.label} > ${title}` : title,\n pointer: `${parent.pointer}/${escapePointerToken(fieldName)}`,\n };\n}\n\n// The context of the i-th item of the array at `parent`. The label uses a\n// 1-based index for readability; the pointer uses the 0-based array index.\nfunction itemContext(parent: FieldContext, index: number): FieldContext {\n return {\n label: `${parent.label}[${index + 1}]`,\n pointer: `${parent.pointer}/${index}`,\n };\n}\n\n// Validate array cardinality and recurse into object items.\nfunction validateArray(\n property: JsonSchemaProperty,\n value: unknown[],\n ctx: FieldContext,\n out: FieldError[],\n): void {\n if (property.minItems !== undefined && value.length < property.minItems) {\n out.push({\n pointer: ctx.pointer,\n message: `${ctx.label} requires at least ${property.minItems} item(s)`,\n });\n }\n if (property.maxItems !== undefined && value.length > property.maxItems) {\n out.push({\n pointer: ctx.pointer,\n message: `${ctx.label} allows at most ${property.maxItems} item(s)`,\n });\n }\n if (!property.items?.properties) return;\n const itemProperties = property.items.properties;\n const itemSchema = property.items;\n for (let i = 0; i < value.length; i++) {\n if (isPlainObject(value[i])) {\n validateObject(\n itemProperties,\n itemSchema,\n value[i] as Record<string, unknown>,\n itemContext(ctx, i),\n out,\n );\n }\n }\n}\n\n// Check whether a property+value pair represents a nested object to recurse into.\nfunction isNestedObject(\n property: JsonSchemaProperty,\n value: unknown,\n): value is Record<string, unknown> {\n return (\n property.type === \"object\" && !!property.properties && isPlainObject(value)\n );\n}\n\n// Validate a single field: check required, recurse into objects/arrays.\nfunction validateField(\n property: JsonSchemaProperty,\n value: unknown,\n required: boolean,\n ctx: FieldContext,\n out: FieldError[],\n): void {\n if (required && isEmpty(value)) {\n out.push({ pointer: ctx.pointer, message: `${ctx.label} is required` });\n return;\n }\n if (isNestedObject(property, value)) {\n validateObject(property.properties!, property, value, ctx, out);\n return;\n }\n if (property.type === \"array\" && Array.isArray(value)) {\n validateArray(property, value, ctx, out);\n }\n}\n\n// Validate the required fields of a single object against its schema.\nfunction validateObject(\n properties: Record<string, JsonSchemaProperty>,\n schema: { required?: string[] },\n data: Record<string, unknown>,\n ctx: FieldContext,\n out: FieldError[],\n): void {\n for (const fieldName of Object.keys(properties)) {\n const property = properties[fieldName];\n const required = schema.required?.includes(fieldName) ?? false;\n validateField(\n property,\n data[fieldName],\n required,\n childContext(ctx, fieldName, property),\n out,\n );\n }\n}\n\n/**\n * Validate form data against a JSON Schema, recursing into nested objects\n * and array items.\n *\n * Two layers: this function owns required-ness and array cardinality with\n * human-authored messages; `validateContentConstraints` adds the JSON Schema\n * content constraints (format, pattern, type, length, range, enum, const) via\n * AJV. Both contribute to the same `FieldError[]`, so downstream attribution\n * (per-field `rawErrors`, per-tab error badges) is unchanged.\n */\nexport function validateFormData(\n schema: JsonSchema,\n data: Record<string, unknown>,\n): ValidationResult {\n const fieldErrors: FieldError[] = [];\n const properties = getSchemaProperties(schema);\n validateObject(\n properties,\n schema,\n data,\n { label: \"\", pointer: \"\" },\n fieldErrors,\n );\n\n // Layer content-constraint errors on top, skipping any field that already\n // has a structural (required/cardinality) error so a field never shows two\n // errors for the same cause.\n const structurallyFlagged = new Set(fieldErrors.map((e) => e.pointer));\n for (const err of validateContentConstraints(schema, data)) {\n if (structurallyFlagged.has(err.pointer)) continue;\n fieldErrors.push(err);\n }\n\n return {\n errors: fieldErrors.map((e) => e.message),\n fieldErrors,\n isValid: fieldErrors.length === 0,\n };\n}\n","/**\n * Merge FieldError lists from several validation sources into one, de-duplicated\n * by field so a field never shows two errors for the same cause.\n *\n * Sources are passed in precedence order (strongest first): structural/content\n * from validateFormData, then custom validators, then external/extra errors.\n * The first source to flag a given field (by its pointer's root segment) wins;\n * later sources add errors only for fields not already flagged. This extends the\n * existing rule inside validateFormData (a content error is skipped on a field\n * that already has a structural error) across every source.\n *\n * Form-level errors (pointer \"\") are never de-duplicated against fields -- they\n * belong to the form, not a field -- but identical form-level messages collapse.\n *\n * Pure function -- no React, no side effects.\n */\n\nimport type { FieldError } from \"./validateFormData\";\n\n// The root field a pointer addresses: \"/contact/email\" -> \"contact\", \"\" -> \"\".\n// Root-scoped de-dup matches how the renderer attributes an error to a field\n// (by the pointer's first segment).\nfunction pointerRoot(pointer: string): string {\n const token = pointer.split(\"/\")[1] ?? \"\";\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\n/**\n * Merge ordered FieldError sources. Earlier sources take precedence per field.\n *\n * @param sources FieldError lists in precedence order (strongest first).\n * @returns one de-duplicated list preserving each kept error's order.\n */\nexport function mergeFieldErrors(...sources: FieldError[][]): FieldError[] {\n const out: FieldError[] = [];\n const claimedFields = new Set<string>(); // field roots already flagged\n const seenFormMessages = new Set<string>(); // form-level (pointer \"\") messages\n\n for (const source of sources) {\n for (const err of source) {\n if (err.pointer === \"\") {\n if (seenFormMessages.has(err.message)) continue;\n seenFormMessages.add(err.message);\n out.push(err);\n continue;\n }\n const root = pointerRoot(err.pointer);\n if (claimedFields.has(root)) continue;\n out.push(err);\n }\n // A field is \"claimed\" only after a whole source is processed, so multiple\n // errors on the same field from the SAME source all survive; a later source\n // is the one that gets suppressed.\n for (const err of source) {\n if (err.pointer !== \"\") claimedFields.add(pointerRoot(err.pointer));\n }\n }\n\n return out;\n}\n","/**\n * diffFormData -- structural diff between two form data snapshots.\n *\n * Produces a FormDiff that classifies every change at field level (value\n * changes, nested object changes) and at array item level (add, remove,\n * move, modify). Array items are matched by their form-internal `_fid`\n * field, which is assigned by ArrayWidget and never sent to the server.\n *\n * Items that lack a `_fid` (e.g. items that arrived before the form\n * system assigned IDs) are matched by position as a fallback.\n */\n\nimport { isEqual, FORM_ITEM_ID_FIELD } from \"../utils\";\nimport type {\n FormDiff,\n FieldDiff,\n ValueChange,\n ArrayItemChange,\n} from \"./types\";\n\n// Check whether both values are non-array plain objects suitable for recursion.\nfunction areBothPlainObjects(a: unknown, b: unknown): boolean {\n return (\n a !== null &&\n b !== null &&\n typeof a === \"object\" &&\n typeof b === \"object\" &&\n !Array.isArray(a) &&\n !Array.isArray(b)\n );\n}\n\n// Diff two plain objects recursively, collecting ValueChange entries.\nfunction diffObjects(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n prefix: string,\n): ValueChange[] {\n const changes: ValueChange[] = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const path = prefix ? `${prefix}.${key}` : key;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n\n if (areBothPlainObjects(a, b)) {\n changes.push(\n ...diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n path,\n ),\n );\n } else {\n changes.push({ type: \"value\", path, from: a, to: b });\n }\n }\n\n return changes;\n}\n\n// Strip _fid from an item before including it in diff output.\nfunction stripFid(item: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(item).filter(([k]) => k !== FORM_ITEM_ID_FIELD),\n );\n}\n\ntype FidMap = Map<string, { item: Record<string, unknown>; index: number }>;\n\n// Build identity map: fid -> { item, index }\nfunction buildFidMap(items: Record<string, unknown>[]): FidMap {\n const map: FidMap = new Map();\n for (let i = 0; i < items.length; i++) {\n const fid = items[i][FORM_ITEM_ID_FIELD] as string | undefined;\n if (fid) map.set(fid, { item: items[i], index: i });\n }\n return map;\n}\n\n// Detect removed and added items between two fid maps.\nfunction detectAddedRemoved(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item, index }] of origByFid) {\n if (!updByFid.has(fid)) {\n changes.push({ type: \"removed\", fid, index, item: stripFid(item) });\n }\n }\n for (const [fid, { item, index }] of updByFid) {\n if (!origByFid.has(fid)) {\n changes.push({ type: \"added\", fid, index, item: stripFid(item) });\n }\n }\n return changes;\n}\n\n// Detect moved and modified items present in both snapshots.\nfunction detectMovedModified(\n origByFid: FidMap,\n updByFid: FidMap,\n): ArrayItemChange[] {\n const changes: ArrayItemChange[] = [];\n for (const [fid, { item: updItem, index: updIndex }] of updByFid) {\n const orig = origByFid.get(fid);\n if (!orig) continue;\n if (orig.index !== updIndex) {\n changes.push({ type: \"moved\", fid, from: orig.index, to: updIndex });\n }\n const subChanges = diffObjects(orig.item, updItem, \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid,\n index: updIndex,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Fallback diff for items without _fid: match by position.\nfunction diffUnfidItems(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n): ArrayItemChange[] {\n const unfidOrig = original.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const unfidUpd = updated.filter((i) => !i[FORM_ITEM_ID_FIELD]);\n const changes: ArrayItemChange[] = [];\n const len = Math.min(unfidOrig.length, unfidUpd.length);\n for (let i = 0; i < len; i++) {\n if (isEqual(unfidOrig[i], unfidUpd[i])) continue;\n const subChanges = diffObjects(unfidOrig[i], unfidUpd[i], \"\");\n if (subChanges.length > 0) {\n changes.push({\n type: \"modified\",\n fid: `__pos_${i}`,\n index: i,\n changes: subChanges,\n });\n }\n }\n return changes;\n}\n\n// Diff two arrays of objects using _fid for item identity.\nfunction diffArrays(\n original: Record<string, unknown>[],\n updated: Record<string, unknown>[],\n field: string,\n): FieldDiff {\n const origByFid = buildFidMap(original);\n const updByFid = buildFidMap(updated);\n const changes: ArrayItemChange[] = [\n ...detectAddedRemoved(origByFid, updByFid),\n ...detectMovedModified(origByFid, updByFid),\n ...diffUnfidItems(original, updated),\n ];\n return { type: \"array\", field, changes };\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n *\n * Returns a FormDiff array with one entry per changed top-level field.\n * Fields whose value is deeply equal are omitted.\n *\n * Array items are matched by their `_fid` field (assigned by ArrayWidget).\n * Items without `_fid` fall back to positional matching.\n */\n// Check whether an array contains object items (vs scalar items).\nfunction isObjectArray(arr: unknown[]): boolean {\n return arr.some((i) => i !== null && typeof i === \"object\");\n}\n\n// Diff a single top-level field, returning the entries to add to the FormDiff.\nfunction diffField(\n key: string,\n a: unknown,\n b: unknown,\n): (ValueChange | FieldDiff)[] {\n if (Array.isArray(a) || Array.isArray(b)) {\n const origArr = Array.isArray(a) ? a : [];\n const updArr = Array.isArray(b) ? b : [];\n if (isObjectArray(origArr) || isObjectArray(updArr)) {\n return [\n diffArrays(\n origArr as Record<string, unknown>[],\n updArr as Record<string, unknown>[],\n key,\n ),\n ];\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n }\n if (areBothPlainObjects(a, b)) {\n return diffObjects(\n a as Record<string, unknown>,\n b as Record<string, unknown>,\n key,\n );\n }\n return [{ type: \"value\", path: key, from: a, to: b }];\n}\n\n/**\n * Compute a structural diff between two form data snapshots.\n */\nexport function diffFormData(\n original: Record<string, unknown>,\n updated: Record<string, unknown>,\n): FormDiff {\n const diff: FormDiff = [];\n const allKeys = new Set([...Object.keys(original), ...Object.keys(updated)]);\n\n for (const key of allKeys) {\n if (key === FORM_ITEM_ID_FIELD) continue;\n const a = original[key];\n const b = updated[key];\n if (isEqual(a, b)) continue;\n diff.push(...diffField(key, a, b));\n }\n\n return diff;\n}\n","/**\n * ensureFormItemIds -- stamp a stable `_fid` onto every array-of-object item\n * in a form data snapshot, guided by the schema.\n *\n * The form system matches array items across a diff by their `_fid` (see\n * diffFormData). ArrayWidget assigns one when it renders, but that only reaches\n * the working copy. The dirty-tracking baseline is snapshotted from the\n * incoming `data`, which has none -- so a loaded array diffs as a wholesale\n * replace after a single-item edit.\n *\n * Stamping the incoming data once, before both the working copy and the\n * baseline are derived from it, gives the two sides the *same* ids, so\n * identity survives the diff. Items that already carry a `_fid` keep it.\n *\n * Only array-of-object fields declared in the schema are touched; scalar\n * arrays and everything else pass through unchanged. Recurses into nested\n * objects and into the items of nested arrays.\n */\n\nimport { FORM_ITEM_ID_FIELD, generateId } from \"../utils\";\nimport { getSchemaProperties } from \"./schemaService\";\nimport type { JsonSchema, JsonSchemaProperty } from \"./types\";\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n// An array field is a candidate for item ids when its items are objects with\n// declared properties. This mirrors ArrayWidget's own resolution.\nfunction isObjectArray(property: JsonSchemaProperty): boolean {\n return property.type === \"array\" && !!property.items?.properties;\n}\n\nfunction stampItem(\n item: unknown,\n itemSchema: JsonSchemaProperty,\n): Record<string, unknown> | unknown {\n if (!isPlainObject(item)) return item;\n const withChildren = stampObject(item, itemSchema.properties ?? {});\n return FORM_ITEM_ID_FIELD in withChildren\n ? withChildren\n : { ...withChildren, [FORM_ITEM_ID_FIELD]: generateId() };\n}\n\nfunction stampValue(value: unknown, property: JsonSchemaProperty): unknown {\n if (isObjectArray(property) && Array.isArray(value)) {\n return value.map((item) => stampItem(item, property.items!));\n }\n if (\n property.type === \"object\" &&\n property.properties &&\n isPlainObject(value)\n ) {\n return stampObject(value, property.properties);\n }\n return value;\n}\n\nfunction stampObject(\n data: Record<string, unknown>,\n properties: Record<string, JsonSchemaProperty>,\n): Record<string, unknown> {\n let changed = false;\n const out: Record<string, unknown> = { ...data };\n for (const [name, property] of Object.entries(properties)) {\n if (!(name in data)) continue;\n const next = stampValue(data[name], property);\n if (next !== data[name]) {\n out[name] = next;\n changed = true;\n }\n }\n return changed ? out : data;\n}\n\n/**\n * Return `data` with a `_fid` on every array-of-object item the schema\n * declares. Returns the input unchanged when nothing needed stamping.\n */\nexport function ensureFormItemIds(\n data: Record<string, unknown>,\n schema: JsonSchema,\n): Record<string, unknown> {\n return stampObject(data, getSchemaProperties(schema));\n}\n"],"mappings":"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","names":["isPlainObject","isObjectArray"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petrarca/sonnet-core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Shared utilities, schema tools, and hooks for the Petrarca Sonnet component library",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {