@petrarca/sonnet-forms 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,8 +10,8 @@ labelled, validated controls.
10
10
  flattening against the current values, schema defaults, nested objects and
11
11
  arrays, buffered or live editing, and a change set on save.
12
12
 
13
- **A widget registry.** Sixteen built-in controls covering text, numbers,
14
- quantities, enums, checkboxes, tags, arrays, objects, entity references and
13
+ **A widget registry.** Twenty-two built-in controls covering text, numbers,
14
+ quantities, coded values and codeable concepts, dates and times, enums, checkboxes, tags, arrays, objects, entity references and
15
15
  embedded JSON, plus your own controls keyed by `x-ui-widget`. A parallel
16
16
  template registry makes the structure around a field -- the array-item row, the
17
17
  object container, the group chrome -- replaceable without touching the widgets.
@@ -91,8 +91,8 @@ actually enforces are in [getting started](docs/usage/getting-started.md).
91
91
  [templates](docs/usage/templates.md),
92
92
  [field components](docs/usage/field-components.md) and
93
93
  [change tracking](docs/usage/change-tracking.md).
94
- - [Design docs](docs/design/index.md) -- form layout, nested fields and the
95
- change-tracking model.
94
+ - [Design docs](docs/design/index.md) -- form layout, nested fields, the
95
+ change-tracking model, and custom validation.
96
96
  - [Workspace documentation](../../docs/README.md) -- how the packages layer and
97
97
  the shared schema extension contract.
98
98
 
package/dist/index.d.ts CHANGED
@@ -1,12 +1,23 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as React$1 from 'react';
3
2
  import React__default, { ComponentType, ReactNode } from 'react';
4
3
  import { Input, Textarea, badgeVariants } from '@petrarca/sonnet-ui';
5
- import { JsonSchemaProperty, UILayoutConfig, JsonSchema, UISchema, UISchemaOptions, FormDiff, RefResolver, FieldError } from '@petrarca/sonnet-core/schema';
4
+ import { JsonSchema, JsonSchemaProperty, UISchema, UISchemaOptions, UILayoutConfig, FieldError, FormDiff, RefResolver } from '@petrarca/sonnet-core/schema';
6
5
  export { UISchema, UISchemaOptions } from '@petrarca/sonnet-core/schema';
7
6
  import { VariantProps } from 'class-variance-authority';
8
7
  import { SaveWhenInvalid, ValidationDisplay } from '@petrarca/sonnet-core/config';
9
8
 
9
+ /**
10
+ * FormFieldWrapper - Shared label / description / error chrome for form fields.
11
+ *
12
+ * Eliminates duplication across FormInput, FormNumberInput, FormQuantityInput,
13
+ * etc. Each field component renders its control as `children` and delegates
14
+ * the surrounding chrome here.
15
+ *
16
+ * Supports two modes:
17
+ * - Normal: label, children, description, error displayed vertically
18
+ * - Compact: suppresses label, description, error text (for grid layouts)
19
+ */
20
+
10
21
  interface FormFieldWrapperProps {
11
22
  /** Field id -- wired to the label's htmlFor */
12
23
  inputId: string;
@@ -39,7 +50,7 @@ interface FormFieldWrapperProps {
39
50
  * FormFieldWrapper renders label, children (the control), description, and
40
51
  * error in the standard vertical layout used by all form field components.
41
52
  */
42
- declare function FormFieldWrapper({ inputId, label, description, error, required, compact, labelPlacement, className, children, }: FormFieldWrapperProps): react_jsx_runtime.JSX.Element;
53
+ declare function FormFieldWrapper({ inputId, label, description, error, required, compact, labelPlacement, className, children, }: FormFieldWrapperProps): React__default.JSX.Element;
43
54
 
44
55
  /**
45
56
  * Shared utility functions for form field components.
@@ -470,6 +481,14 @@ type TemplateRegistry = {
470
481
  [templateName: string]: unknown;
471
482
  };
472
483
 
484
+ /**
485
+ * FormSelect - Searchable single-select dropdown with form field wrapper
486
+ *
487
+ * Composable select component using Popover + Command for searchable,
488
+ * keyboard-navigable dropdown. Follows Form wrapper pattern with
489
+ * label, description, error support.
490
+ */
491
+
473
492
  interface FormSelectProps {
474
493
  label?: string | false;
475
494
  description?: string;
@@ -509,7 +528,15 @@ interface FormSelectProps {
509
528
  /** Called with the trimmed search text when the create item is chosen. */
510
529
  onCreate?: (label: string) => void;
511
530
  }
512
- declare function FormSelect(props: FormSelectProps): react_jsx_runtime.JSX.Element;
531
+ declare function FormSelect(props: FormSelectProps): React$1.JSX.Element;
532
+
533
+ /**
534
+ * FormMultiSelect - Searchable multi-select dropdown with badges
535
+ *
536
+ * Multi-select component using Popover + Command with badge display
537
+ * for selected items. Follows Form wrapper pattern with label,
538
+ * description, error support.
539
+ */
513
540
 
514
541
  type BadgeColor = NonNullable<VariantProps<typeof badgeVariants>["badgeColor"]>;
515
542
 
@@ -539,7 +566,7 @@ interface FormMultiSelectProps {
539
566
  maxDisplay?: number;
540
567
  badgeColor?: BadgeColor;
541
568
  }
542
- declare function FormMultiSelect(props: FormMultiSelectProps): react_jsx_runtime.JSX.Element;
569
+ declare function FormMultiSelect(props: FormMultiSelectProps): React$1.JSX.Element;
543
570
 
544
571
  /**
545
572
  * FormTagsInput - Input field for managing tags with badges
@@ -570,6 +597,59 @@ interface FormTagsInputProps {
570
597
  }
571
598
  declare const FormTagsInput: React$1.ForwardRefExoticComponent<FormTagsInputProps & React$1.RefAttributes<HTMLInputElement>>;
572
599
 
600
+ /**
601
+ * FormKeyValueInput - Input for a map of string keys to string values
602
+ *
603
+ * Each pair is entered in two fields (key, value) and added with Enter or the
604
+ * add button. A key is unique: adding it again replaces its value. The entered
605
+ * pairs are displayed one of two ways (`display`):
606
+ *
607
+ * tags (default) as "key:value" tags -- for short labels. Clicking a tag
608
+ * loads it back into the fields to edit it; its X removes it.
609
+ * rows as a list, a row per pair: its key and value editable in
610
+ * place (taken on leaving the field or Enter), a remove
611
+ * button -- for values too long or too many to read as tags,
612
+ * such as templates or paths.
613
+
614
+ * keys only the keys, as badges, each value on hover -- a compact
615
+ * summary, not edited (the host edits the pairs elsewhere,
616
+ * e.g. as rows in a dialog).
617
+ *
618
+ * `valueSuggestions` offers values to pick in the value fields (still free to
619
+ * type) -- e.g. the references a value may name.
620
+ *
621
+ * Follows the Form wrapper pattern with label, description, error support.
622
+ */
623
+
624
+ interface FormKeyValueInputProps {
625
+ label?: string | false;
626
+ description?: string;
627
+ error?: string;
628
+ required?: boolean;
629
+ wrapperClassName?: string;
630
+ labelPlacement?: "top" | "start";
631
+ id?: string;
632
+ name?: string;
633
+ value: Record<string, string>;
634
+ onChange?: (value: Record<string, string>) => void;
635
+ disabled?: boolean;
636
+ readOnly?: boolean;
637
+ /** Placeholders of the two fields. Default "Key" / "Value". */
638
+ keyPlaceholder?: string;
639
+ valuePlaceholder?: string;
640
+ /** How a pair is shown as a tag. Default ":" ("key:value"). */
641
+ separator?: string;
642
+ /** How the pairs are displayed: as tags (default), as rows, or only their
643
+ * keys (a summary, not edited). */
644
+ display?: "tags" | "rows" | "keys";
645
+ /** Values the value fields suggest; one may still type any other. */
646
+ valueSuggestions?: {
647
+ value: string;
648
+ label?: string;
649
+ }[];
650
+ }
651
+ declare const FormKeyValueInput: React$1.ForwardRefExoticComponent<FormKeyValueInputProps & React$1.RefAttributes<HTMLInputElement>>;
652
+
573
653
  /**
574
654
  * FormQuantityInput - Compound input for a numeric value paired with a unit selector.
575
655
  *
@@ -612,8 +692,10 @@ interface FormQuantityInputProps {
612
692
  onChange?: (value: QuantityValue) => void;
613
693
  /** Blur handler */
614
694
  onBlur?: () => void;
615
- /** Available unit options. Single = static text, 2+ = dropdown, none = no addon. */
695
+ /** Available unit options. Single = static text, 2+ = dropdown, none = no addon (or a typed unit, with `freeUnit`). */
616
696
  units?: UnitOption[];
697
+ /** Without units, the unit is typed -- a code such as `kg`. Default false: no unit at all. */
698
+ freeUnit?: boolean;
617
699
  /**
618
700
  * Default position for all units. Individual UnitOption.position overrides
619
701
  * this. Default: "suffix".
@@ -673,13 +755,18 @@ interface FormActionsProps {
673
755
  saveLabel?: string;
674
756
  cancelLabel?: string;
675
757
  showCancel?: boolean;
758
+ /**
759
+ * True while an async validator is in flight. Save is disabled until it
760
+ * settles (validity is not yet known); Cancel stays enabled.
761
+ */
762
+ isValidating?: boolean;
676
763
  /**
677
764
  * Optional content rendered left-aligned in the action row (e.g. a secondary
678
765
  * action like "Test connection"). Cancel/Save stay grouped on the right.
679
766
  */
680
767
  leadingActions?: ReactNode;
681
768
  }
682
- declare function FormActions({ onSave, onCancel, disabled, hasChanges, isValid, saveWhenInvalid, saveLabel, cancelLabel, showCancel, leadingActions, }: FormActionsProps): react_jsx_runtime.JSX.Element;
769
+ declare function FormActions({ onSave, onCancel, disabled, hasChanges, isValid, saveWhenInvalid, saveLabel, cancelLabel, showCancel, isValidating, leadingActions, }: FormActionsProps): React$1.JSX.Element;
683
770
 
684
771
  type Props = {
685
772
  properties: Record<string, any>;
@@ -692,7 +779,7 @@ type Props = {
692
779
  * This is a generic component that can be used anywhere a schema and properties
693
780
  * need to be compared to show additional/extra properties.
694
781
  */
695
- declare function ExtraPropertiesCard({ properties, schema }: Props): react_jsx_runtime.JSX.Element | null;
782
+ declare function ExtraPropertiesCard({ properties, schema }: Props): React$1.JSX.Element | null;
696
783
 
697
784
  /**
698
785
  * TextInputWidget - Standard text input widget
@@ -723,7 +810,7 @@ declare function TextInputWidget({ id, name, value, onChange, onBlur, propertySc
723
810
  *
724
811
  * @param props - WidgetProps
725
812
  */
726
- declare function TextareaWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): react_jsx_runtime.JSX.Element;
813
+ declare function TextareaWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): React$1.JSX.Element;
727
814
 
728
815
  /**
729
816
  * NumberWidget renders a number input field.
@@ -736,7 +823,7 @@ declare function TextareaWidget({ id, name, value, onChange, onBlur, propertySch
736
823
  *
737
824
  * @param props - WidgetProps
738
825
  */
739
- declare function NumberWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): react_jsx_runtime.JSX.Element;
826
+ declare function NumberWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): React$1.JSX.Element;
740
827
 
741
828
  /**
742
829
  * CheckboxWidget - Boolean checkbox widget
@@ -745,7 +832,8 @@ declare function NumberWidget({ id, name, value, onChange, onBlur, propertySchem
745
832
  * Used for boolean type fields.
746
833
  *
747
834
  * Supports tri-state behavior for nullable boolean fields (anyOf[boolean, null]):
748
- * - null (indeterminate) -> true (checked) -> false (unchecked) -> null
835
+ * - null or no value (indeterminate) -> true (checked) -> false (unchecked) -> null
836
+ * No value is shown as null is: not answered -- unchecked would read as "no".
749
837
  */
750
838
 
751
839
  /**
@@ -758,13 +846,13 @@ declare function NumberWidget({ id, name, value, onChange, onBlur, propertySchem
758
846
  * - Description text
759
847
  *
760
848
  * Tri-state behavior (when anyOf[boolean, null] is detected):
761
- * - Click on null -> becomes true
849
+ * - Click on null, or no value -> becomes true
762
850
  * - Click on true -> becomes false
763
851
  * - Click on false -> becomes null
764
852
  */
765
853
  declare function CheckboxWidget({ id, value, onChange, onBlur, disabled, readonly, rawErrors, label, description, autofocus, propertySchema, options, }: WidgetProps): React.ReactElement;
766
854
 
767
- declare function SelectWidget(props: WidgetProps): react_jsx_runtime.JSX.Element;
855
+ declare function SelectWidget(props: WidgetProps): React$1.JSX.Element;
768
856
 
769
857
  /**
770
858
  * TagsInputWidget - Free-text tag input widget
@@ -805,9 +893,24 @@ declare function TagsInputWidget({ id, name, value, onChange, required, disabled
805
893
  * - Full document editing (no readonly regions)
806
894
  * - Invalid JSON is silently retained until fixed
807
895
  * - Height configurable via x-ui-options.height (default: "240px")
896
+ * - The editor loads on first use (a placeholder of that height meanwhile)
808
897
  */
809
898
  declare function JsonEditorWidget({ id, value, onChange, label, description, required, options, }: WidgetProps): React__default.ReactElement;
810
899
 
900
+ /**
901
+ * KeyValueWidget - Key-value pair input widget
902
+ *
903
+ * Renders FormKeyValueInput for an object of string values (a free map such as
904
+ * labels: `additionalProperties: { type: "string" }`, no declared properties).
905
+ * Pairs are entered in a key and a value field and shown as "key:value" tags,
906
+ * or -- `x-ui-options: { display: "rows" }` -- as rows edited in place, for long
907
+ * values such as templates or paths, or -- `display: "keys"` -- only the keys,
908
+ * a summary not edited. Chosen automatically for such maps;
909
+ * explicitly via x-ui-widget: "key-value".
910
+ */
911
+
912
+ declare function KeyValueWidget({ id, name, value, onChange, required, disabled, readonly, rawErrors, label, description, options, }: WidgetProps): React__default.ReactElement;
913
+
811
914
  /**
812
915
  * EntitySelectWidget -- form widget for async entity reference selection.
813
916
  *
@@ -873,9 +976,68 @@ declare function ObjectWidget({ id, propertySchema, value, onChange, onBlur, reg
873
976
  * never leave the renderer boundary.
874
977
  */
875
978
 
876
- declare function ArrayWidget({ propertySchema, value, onChange, registry, uiSchema, disabled, readonly, label, description, options, schema, name, }: WidgetProps): React.ReactElement;
979
+ declare function ArrayWidget({ propertySchema, value, onChange, registry, uiSchema, disabled, readonly, label, description, options, schema, name, onBlur, }: WidgetProps): React.ReactElement;
877
980
 
878
- declare function QuantityWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): react_jsx_runtime.JSX.Element;
981
+ declare function QuantityWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, placeholder, autofocus, options, }: WidgetProps): React$1.JSX.Element;
982
+
983
+ /**
984
+ * CodedValueWidget -- a code from a list someone else maintains, asked of the
985
+ * application's terminology service (`TerminologyProvider`).
986
+ *
987
+ * The list is the field's binding (`x-binding`: a value set or a code system,
988
+ * its strength). What is stored the field's type decides: the core coding type
989
+ * stores `{ system, code, version?, display? }`; a string bound to a list stores
990
+ * the code alone, its label looked up to show it; a coding or a text (`anyOf`,
991
+ * an open list) stores a coding, or a text of one's own as a plain string --
992
+ * never a coding without its code. An array of any of them takes several
993
+ * answers, shown as chips. The first search decides whether the list is shown
994
+ * whole (it fits one page) or searched; a hit shows its code and, when the
995
+ * service gives it, its path. An open list -- a coding or a text, or an
996
+ * `extensible` binding on a string -- takes a typed text no code fits.
997
+ *
998
+ * Registered as x-ui-widget: "coded-value"; the core coding type carries that
999
+ * hint, and a string, a coding or a text, or an array bound to a list resolves
1000
+ * to it without one.
1001
+ *
1002
+ * x-ui-options:
1003
+ * mode - "list" | "search": overrides what the list's size decides
1004
+ * minChars - the characters a search waits for (default 1)
1005
+ * store - the hit's property a code-only field stores (default "code")
1006
+ * keep - properties of a hit a coding keeps beside its own -- an
1007
+ * application's, which its service returns on its hits
1008
+ *
1009
+ * See docs/design/coded-values.md (workspace).
1010
+ */
1011
+
1012
+ declare function CodedValueWidget(props: WidgetProps): React$1.ReactElement;
1013
+
1014
+ /**
1015
+ * CodeableConceptWidget -- one concept, coded in the field's lists, compact:
1016
+ * the coded-value picker on the first list, and one line of chips for the
1017
+ * codings of the others -- as clinical systems show a concept: its term, the
1018
+ * other codes beside it.
1019
+ *
1020
+ * [ Glucose [Mass/volume] in Serum or Plasma 2345-7 v ]
1021
+ * [Lab GLU-S x] [Registry *] + Coding + Text
1022
+ *
1023
+ * A chip -- or "+ Coding", for a list without one -- opens the same search on
1024
+ * its list. What a person picks is stored as theirs (`userSelected`), with the
1025
+ * list's purpose; a required list without a coding shows as an empty chip. A
1026
+ * coding of a list the field does not name is shown and kept, never dropped.
1027
+ *
1028
+ * The lists are the field's binding (`x-binding`): the first, then
1029
+ * `additional`. Registered as x-ui-widget: "codeable-concept"; the core
1030
+ * codeable concept type carries that hint.
1031
+ *
1032
+ * x-ui-options:
1033
+ * text - offer the text (default: when the first list is extensible)
1034
+ * mode - "list" | "search", for every list (default: each list's size decides)
1035
+ * minChars - the characters a search waits for
1036
+ *
1037
+ * See docs/design/codeable-concepts.md (workspace).
1038
+ */
1039
+
1040
+ declare function CodeableConceptWidget(props: WidgetProps): React$1.ReactElement;
879
1041
 
880
1042
  /**
881
1043
  * DateWidget - date field widget backed by the DateInput primitive.
@@ -884,6 +1046,9 @@ declare function QuantityWidget({ id, name, value, onChange, onBlur, propertySch
884
1046
  * string (`YYYY`, `YYYY-MM`, or `YYYY-MM-DD`). Auto-resolves from schema
885
1047
  * `format: "date"`, or `x-ui-widget: "date"`.
886
1048
  *
1049
+ * Its range is the schema's `formatMinimum` / `formatMaximum`: the calendar
1050
+ * offers only the dates between; validation names a typed one outside.
1051
+ *
887
1052
  * x-ui-options:
888
1053
  * precision - "year" | "month" | "day" (default "day")
889
1054
  * locale - BCP-47 locale override (defaults to the app locale)
@@ -892,7 +1057,7 @@ declare function QuantityWidget({ id, name, value, onChange, onBlur, propertySch
892
1057
  * the inline error message and invalid border.
893
1058
  */
894
1059
 
895
- declare function DateWidget({ id, value, onChange, onBlur, required, disabled, rawErrors, label, description, placeholder, options, }: WidgetProps): React.ReactElement;
1060
+ declare function DateWidget({ id, value, onChange, onBlur, required, disabled, rawErrors, label, description, placeholder, options, propertySchema, }: WidgetProps): React.ReactElement;
896
1061
 
897
1062
  /**
898
1063
  * TimeWidget - time-of-day field widget backed by the TimeInput primitive.
@@ -925,7 +1090,7 @@ declare function TimeWidget({ id, value, onChange, onBlur, required, disabled, r
925
1090
  * the inline error message and invalid border.
926
1091
  */
927
1092
 
928
- declare function DateTimeWidget({ id, value, onChange, onBlur, required, disabled, rawErrors, label, description, options, }: WidgetProps): React.ReactElement;
1093
+ declare function DateTimeWidget({ id, value, onChange, onBlur, required, disabled, rawErrors, label, description, options, propertySchema, }: WidgetProps): React.ReactElement;
929
1094
 
930
1095
  /**
931
1096
  * DisplayWidget - read-only text block, no data binding.
@@ -954,6 +1119,55 @@ declare function DisplayWidget({ label, description, }: WidgetProps): React.Reac
954
1119
 
955
1120
  declare function OneOfWidget({ propertySchema, value, onChange, registry, uiSchema, disabled, readonly, label, description, id, options, }: WidgetProps): React.ReactElement | null;
956
1121
 
1122
+ /**
1123
+ * RadioWidget -- a single choice as radio buttons, every option in view.
1124
+ *
1125
+ * For an enum property, selected by `x-ui-widget: "radio"`. Labels come from
1126
+ * x-ui-options.enumNames, as for the select. `x-ui-options.orientation`
1127
+ * "horizontal" lays the options out in a row (default: a column).
1128
+ */
1129
+
1130
+ declare function RadioWidget({ id, name, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, options, }: WidgetProps): React.ReactElement;
1131
+
1132
+ /**
1133
+ * CheckboxGroupWidget -- a multiple choice as checkboxes, every option in view.
1134
+ *
1135
+ * For an array of enum items, selected by `x-ui-widget: "checkbox-group"`.
1136
+ * Labels come from x-ui-options.enumNames, as for the select; the value keeps
1137
+ * the options' order. `x-ui-options.orientation` "horizontal" lays them out in
1138
+ * a row (default: a column).
1139
+ */
1140
+
1141
+ declare function CheckboxGroupWidget({ id, value, onChange, onBlur, propertySchema, required, disabled, readonly, rawErrors, label, description, options, }: WidgetProps): React.ReactElement;
1142
+
1143
+ /**
1144
+ * ChoiceWithTextWidget -- a choice and a text as one answer: "one of these, and
1145
+ * add detail".
1146
+ *
1147
+ * A container, selected by `x-ui-widget: "choice-with-text"` on an object of
1148
+ * `choice` (an enum) and `text` (a string). It only arranges the two -- the
1149
+ * choice beside the text, the choice unlabelled -- and renders them as any
1150
+ * nested object is (ObjectWidget, inline, under the question's own label):
1151
+ * each child by its own widget, resolved as
1152
+ * for any field. So the choice is a select, radio buttons or an application's
1153
+ * terminology picker, set on the child (`x-ui-fields.choice`), and the text a
1154
+ * line or a textarea.
1155
+ */
1156
+
1157
+ declare function ChoiceWithTextWidget(props: WidgetProps): React.ReactElement | null;
1158
+
1159
+ /**
1160
+ * AbsentReasonsWidget -- the questions of a section left unanswered, and why:
1161
+ * one row each, the question and its reason (*unknown*, *not applicable*).
1162
+ *
1163
+ * For an object property mapping question keys to reasons, selected by
1164
+ * `x-ui-widget: "absent-reasons"`. The reasons are its `additionalProperties`
1165
+ * enum, labelled by x-ui-options.enumNames; the questions are its siblings in
1166
+ * the form schema it renders in -- every field but itself and static texts.
1167
+ */
1168
+
1169
+ declare function AbsentReasonsWidget(props: WidgetProps): React.ReactElement;
1170
+
957
1171
  /**
958
1172
  * Widget Resolution Algorithm
959
1173
  *
@@ -1026,6 +1240,71 @@ declare function getDefaultWidgetForType(type: string): string;
1026
1240
  */
1027
1241
  declare const DEFAULT_WIDGETS: WidgetRegistry;
1028
1242
 
1243
+ /**
1244
+ * Custom validation registry -- named field and form validators referenced from
1245
+ * the schema via `x-ui-validate` and implemented here.
1246
+ *
1247
+ * See the custom-validation design in docs/design/custom-validation.md. `field`
1248
+ * and `form` are synchronous, invoked by `runValidators` every validation pass;
1249
+ * `asyncField` and `asyncForm` return a promise and are invoked only by
1250
+ * `useAsyncValidators`, keyed on their inputs so a remote check fires on
1251
+ * change, never per render.
1252
+ */
1253
+
1254
+ /**
1255
+ * Context handed to every validator so a rule can reach beyond its immediate
1256
+ * value without the caller wiring it. `formData` is the ROOT form data (carried
1257
+ * like x-ui-rule's rootData), so a validator on a nested field can still compare
1258
+ * against a top-level field.
1259
+ */
1260
+ interface ValidatorContext {
1261
+ /** The whole current form data (root), for cross-field checks. */
1262
+ formData: Record<string, unknown>;
1263
+ /** RFC 6901 JSON Pointer of the field being validated (field validators). */
1264
+ path: string;
1265
+ /** Per-reference config from the `x-ui-validate` object form (`{ name, args }`). */
1266
+ args?: Record<string, unknown>;
1267
+ }
1268
+ /**
1269
+ * What a SYNC validator returns. A field validator may use the `string | null`
1270
+ * shorthand (message attached to its own field, or null when valid); a form
1271
+ * validator returns `FieldError[]` because it decides which field each error
1272
+ * lands on (pointer "" = form-level).
1273
+ */
1274
+ type ValidatorResult = string | null | FieldError[];
1275
+ /** A per-value sync validator: judges one field's value. */
1276
+ type FieldValidator = (value: unknown, ctx: ValidatorContext) => string | null | FieldError[];
1277
+ /** A whole-data sync validator: cross-field / form-level rules. */
1278
+ type FormValidator = (data: Record<string, unknown>, ctx: ValidatorContext) => FieldError[];
1279
+ /**
1280
+ * An async per-value validator -- returns a promise. Declared in its own
1281
+ * namespace (`asyncField`) so the engine knows it is async without calling it,
1282
+ * and invokes it only when its field value changes (never per render). A failed
1283
+ * promise means "could not check", not "invalid".
1284
+ */
1285
+ type AsyncFieldValidator = (value: unknown, ctx: ValidatorContext) => Promise<string | null | FieldError[]>;
1286
+ /** An async whole-data validator -- returns a promise. See `dependsOn` on the
1287
+ * `x-ui-validate` reference to control when it re-fires. */
1288
+ type AsyncFormValidator = (data: Record<string, unknown>, ctx: ValidatorContext) => Promise<FieldError[]>;
1289
+ /**
1290
+ * The registry: sync and async namespaces, keeping the signatures -- and the
1291
+ * invocation strategy -- apart. Sync validators run on every validation pass;
1292
+ * async validators are invoked only when their inputs change (the engine can
1293
+ * tell them apart because they are declared separately, so a remote check is
1294
+ * never fired speculatively per render). A plain object, like the widget and
1295
+ * template registries -- no factory, no lifecycle.
1296
+ */
1297
+ interface ValidatorRegistry {
1298
+ /** Sync per-field validators, referenced by `x-ui-validate` on a property. */
1299
+ field?: Record<string, FieldValidator>;
1300
+ /** Sync whole-data validators, referenced by `x-ui-validate` on the root. */
1301
+ form?: Record<string, FormValidator>;
1302
+ /** Async per-field validators (promise), referenced by `x-ui-validate`. */
1303
+ asyncField?: Record<string, AsyncFieldValidator>;
1304
+ /** Async whole-data validators (promise), referenced by `x-ui-validate`. */
1305
+ asyncForm?: Record<string, AsyncFormValidator>;
1306
+ }
1307
+
1029
1308
  /**
1030
1309
  * JsonSchemaFormRenderer - Generic form renderer based on JSON Schema
1031
1310
  *
@@ -1132,6 +1411,22 @@ type JsonSchemaFormRendererProps = {
1132
1411
  * a partial registry -- unspecified keys fall back to DEFAULT_TEMPLATES.
1133
1412
  */
1134
1413
  templates?: TemplateRegistry;
1414
+ /**
1415
+ * Custom validator registry -- named field and form validators referenced
1416
+ * from the schema via `x-ui-validate`, run alongside the built-in schema
1417
+ * validation. `field`/`form` are synchronous; `asyncField`/`asyncForm`
1418
+ * return a promise and gate Save while in flight. See
1419
+ * docs/design/custom-validation.md.
1420
+ */
1421
+ validators?: ValidatorRegistry;
1422
+ /**
1423
+ * Externally-supplied errors (e.g. a server 422) shown as persistent field
1424
+ * errors, keyed by JSON Pointer. Always displayed regardless of
1425
+ * `validationDisplay`, and block Save. Should be stable/memoized. A
1426
+ * higher-precedence error (built-in or a custom validator) on the same field
1427
+ * wins over an extraError. See the custom-validation design.
1428
+ */
1429
+ extraErrors?: FieldError[];
1135
1430
  /**
1136
1431
  * When true, renders with `display: contents` instead of a SimpleStack.
1137
1432
  *
@@ -1198,7 +1493,7 @@ interface FormFieldRendererProps {
1198
1493
  /** Validation messages for this field, rendered inline by the widget. */
1199
1494
  rawErrors?: string[];
1200
1495
  }
1201
- declare function FormFieldRenderer({ fieldName, property, value, onChange, onBlur, required, disabled, readOnly, schema, uiSchema, registry, globalOptions, formData, idPrefix, rawErrors, }: FormFieldRendererProps): React__default.ReactElement | null;
1496
+ declare function FormFieldRenderer({ fieldName, property: declared, value, onChange, onBlur, required, disabled, readOnly, schema, uiSchema, registry, globalOptions, formData, idPrefix, rawErrors, }: FormFieldRendererProps): React__default.ReactElement | null;
1202
1497
 
1203
1498
  /**
1204
1499
  * useFormEngine - Core form state, schema resolution, and update-mode logic
@@ -1248,6 +1543,29 @@ interface UseFormEngineOptions {
1248
1543
  * synchronous -- pre-load referenced schemas into a map and pass a lookup.
1249
1544
  */
1250
1545
  refResolver?: RefResolver;
1546
+ /**
1547
+ * Externally-supplied errors (e.g. a server 422), keyed by JSON Pointer.
1548
+ * Merged into `fieldErrors` (so they affect `isValid` and block Save) with
1549
+ * the lowest precedence -- a built-in error on the same field wins. The
1550
+ * renderer shows them regardless of `validationDisplay`; see the
1551
+ * custom-validation design.
1552
+ */
1553
+ extraErrors?: FieldError[];
1554
+ /**
1555
+ * Custom validator registry. Field validators (named by `x-ui-validate` on a
1556
+ * property) and, at the root, form/cross-field validators (named on the root
1557
+ * object) run here and merge into `fieldErrors` between built-in errors and
1558
+ * extraErrors. The sync `field`/`form` namespaces run every pass via
1559
+ * `runValidators`; the async `asyncField`/`asyncForm` namespaces are invoked
1560
+ * by `useAsyncValidators` and gate Save (`isValidating`) while in flight.
1561
+ */
1562
+ validators?: ValidatorRegistry;
1563
+ /**
1564
+ * Whether this engine is the ROOT form (not a nested sub-form). Root-only
1565
+ * concerns -- currently the whole-form/cross-field validators -- run only when
1566
+ * true, so a nested sub-form does not re-run them against its slice.
1567
+ */
1568
+ isRoot?: boolean;
1251
1569
  }
1252
1570
  interface UseFormEngineResult {
1253
1571
  /** The current working form data (for controlled field values) */
@@ -1263,6 +1581,8 @@ interface UseFormEngineResult {
1263
1581
  orderedProperties: [string, JsonSchemaProperty][];
1264
1582
  hasChanges: boolean;
1265
1583
  isValid: boolean;
1584
+ /** True while any async validator's promise is in flight. */
1585
+ isValidating: boolean;
1266
1586
  /**
1267
1587
  * Current validation errors, each attributed to its field by JSON Pointer.
1268
1588
  * Lets the renderer surface problems for fields that are not on screen.
@@ -1277,7 +1597,7 @@ interface UseFormEngineResult {
1277
1597
  handleSave: () => void;
1278
1598
  handleCancel: () => void;
1279
1599
  }
1280
- declare function useFormEngine({ schema, data, uiSchema, onChange, onBlur, onValidationChange, onUpdate, onCancel, readOnly, showActions, deferChanges, deferStateUpdates, allowPristineSave, refResolver, }: UseFormEngineOptions): UseFormEngineResult;
1600
+ declare function useFormEngine({ schema, data, uiSchema, onChange, onBlur, onValidationChange, onUpdate, onCancel, readOnly, showActions, deferChanges, deferStateUpdates, allowPristineSave, refResolver, extraErrors, validators, isRoot, }: UseFormEngineOptions): UseFormEngineResult;
1281
1601
 
1282
1602
  /**
1283
1603
  * useResolvedSchema - Schema resolution and property ordering
@@ -1336,6 +1656,17 @@ interface NestedFormContextValue {
1336
1656
  * it unchanged.
1337
1657
  */
1338
1658
  rootData: Record<string, unknown>;
1659
+ /**
1660
+ * Whether the root form's save has been attempted. A nested sub-form is a
1661
+ * separate form-engine instance with its own submit state, which the root's
1662
+ * Save never flips -- so without this a required field inside a nested object
1663
+ * would never reveal its error on submit (only on touch). Carried down so a
1664
+ * nested renderer ORs it into its own error-visibility.
1665
+ *
1666
+ * The outermost renderer sets this to its own submitAttempted; nested
1667
+ * renderers inherit it unchanged.
1668
+ */
1669
+ submitAttempted: boolean;
1339
1670
  }
1340
1671
 
1341
1672
  /**
@@ -1434,4 +1765,5 @@ declare const DEFAULT_TEMPLATES: TemplateRegistry;
1434
1765
  */
1435
1766
  declare function groupTemplateKey(variant: string | undefined): string;
1436
1767
 
1437
- export { type ArrayHeaderTemplate, type ArrayHeaderTemplateProps, type ArrayItemTemplate, type ArrayItemTemplateProps, ArrayWidget, CheckboxWidget, DEFAULT_TEMPLATES, DEFAULT_WIDGETS, DEFAULT_WIDGET_MAP, DateTimeWidget, DateWidget, DisplayWidget, EntitySelectWidget, ExtraPropertiesCard, type FieldGroup, FormActions, FormCheckbox, FormFieldRenderer, FormFieldWrapper, type FormFieldWrapperProps, FormInput, FormMultiSelect, FormNumberInput, FormQuantityInput, FormSelect, FormTagsInput, FormTextarea, GroupErrorBadge, type GroupLayoutTemplate, type GroupLayoutTemplateProps, JsonEditorWidget, JsonSchemaFormRenderer, type JsonSchemaFormRendererProps, type NestedFormContextValue, type NestedRenderer, NumberWidget, type ObjectContainerTemplate, type ObjectContainerTemplateProps, ObjectWidget, OneOfWidget, type QuantityValue, QuantityWidget, SectionsGroupTemplate, type SelectOption, SelectWidget, StepperGroupTemplate, TabsGroupTemplate, TagsInputWidget, type TemplateRegistry, TextInputWidget, TextareaWidget, TimeWidget, type UnitOption, type UnitPosition, type Widget, type WidgetDescriptor, type WidgetMap, type WidgetProps, type WidgetRegistry, firstError, getAriaDescribedBy, getDefaultWidgetForType, groupTemplateKey, resolveWidget, useFormEngine, useNestedFormContext, useResolvedSchema };
1768
+ export { AbsentReasonsWidget, ArrayWidget, CheckboxGroupWidget, CheckboxWidget, ChoiceWithTextWidget, CodeableConceptWidget, CodedValueWidget, DEFAULT_TEMPLATES, DEFAULT_WIDGETS, DEFAULT_WIDGET_MAP, DateTimeWidget, DateWidget, DisplayWidget, EntitySelectWidget, ExtraPropertiesCard, FormActions, FormCheckbox, FormFieldRenderer, FormFieldWrapper, FormInput, FormKeyValueInput, FormMultiSelect, FormNumberInput, FormQuantityInput, FormSelect, FormTagsInput, FormTextarea, GroupErrorBadge, JsonEditorWidget, JsonSchemaFormRenderer, KeyValueWidget, NumberWidget, ObjectWidget, OneOfWidget, QuantityWidget, RadioWidget, SectionsGroupTemplate, SelectWidget, StepperGroupTemplate, TabsGroupTemplate, TagsInputWidget, TextInputWidget, TextareaWidget, TimeWidget, firstError, getAriaDescribedBy, getDefaultWidgetForType, groupTemplateKey, resolveWidget, useFormEngine, useNestedFormContext, useResolvedSchema };
1769
+ export type { ArrayHeaderTemplate, ArrayHeaderTemplateProps, ArrayItemTemplate, ArrayItemTemplateProps, AsyncFieldValidator, AsyncFormValidator, FieldGroup, FieldValidator, FormFieldWrapperProps, FormValidator, GroupLayoutTemplate, GroupLayoutTemplateProps, JsonSchemaFormRendererProps, NestedFormContextValue, NestedRenderer, ObjectContainerTemplate, ObjectContainerTemplateProps, QuantityValue, SelectOption, TemplateRegistry, UnitOption, UnitPosition, ValidatorContext, ValidatorRegistry, ValidatorResult, Widget, WidgetDescriptor, WidgetMap, WidgetProps, WidgetRegistry };