@petrarca/sonnet-forms 0.5.0 → 0.6.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/dist/index.js CHANGED
@@ -1533,12 +1533,13 @@ function ExtraPropertiesCard({ properties, schema }) {
1533
1533
  var ExtraPropertiesCard_default = ExtraPropertiesCard;
1534
1534
 
1535
1535
  // src/JsonSchemaFormRenderer.tsx
1536
- import React11, { useCallback as useCallback8, useMemo as useMemo7 } from "react";
1536
+ import React13, { useCallback as useCallback8, useMemo as useMemo8 } from "react";
1537
1537
  import { SimpleStack } from "@petrarca/sonnet-ui";
1538
+ import { AlertCircle as AlertCircle2 } from "lucide-react";
1538
1539
  import {
1539
1540
  WIDTH_TOKEN_REMS
1540
1541
  } from "@petrarca/sonnet-core/schema";
1541
- import { isFieldRequired } from "@petrarca/sonnet-core/schema";
1542
+ import { isFieldRequired, evaluateUiRule } from "@petrarca/sonnet-core/schema";
1542
1543
  import { FORM_ITEM_ID_FIELD as FORM_ITEM_ID_FIELD2 } from "@petrarca/sonnet-core";
1543
1544
 
1544
1545
  // src/widgets/types.ts
@@ -1794,6 +1795,7 @@ function SelectWidget(props) {
1794
1795
  const enumNames = widgetOptions.enumNames || {};
1795
1796
  const clearable = widgetOptions.clearable !== false && !required;
1796
1797
  const isMultiple = widgetOptions.multiple ?? propertySchema.type === "array";
1798
+ const creatable = !!widgetOptions.creatable;
1797
1799
  const selectOptions = buildSelectOptions(propertySchema, enumNames);
1798
1800
  const error = firstError(rawErrors);
1799
1801
  const compact = widgetOptions?.compact;
@@ -1844,7 +1846,9 @@ function SelectWidget(props) {
1844
1846
  autoFocus: autofocus,
1845
1847
  onBlur: handleBlur,
1846
1848
  className: triggerClassName,
1847
- compact
1849
+ compact,
1850
+ allowCreate: creatable,
1851
+ onCreate: (label2) => onChange(label2)
1848
1852
  }
1849
1853
  );
1850
1854
  }
@@ -2696,9 +2700,309 @@ function QuantityWidget({
2696
2700
  );
2697
2701
  }
2698
2702
 
2703
+ // src/widgets/DateWidget.tsx
2704
+ import { DateInput } from "@petrarca/sonnet-ui";
2705
+ import { jsx as jsx26 } from "react/jsx-runtime";
2706
+ function resolvePrecision(value) {
2707
+ return value === "year" || value === "month" ? value : "day";
2708
+ }
2709
+ function DateWidget({
2710
+ id,
2711
+ value,
2712
+ onChange,
2713
+ onBlur,
2714
+ required,
2715
+ disabled,
2716
+ rawErrors,
2717
+ label,
2718
+ description,
2719
+ placeholder,
2720
+ options
2721
+ }) {
2722
+ const error = firstError(rawErrors);
2723
+ const precision = resolvePrecision(options?.precision);
2724
+ const locale = typeof options?.locale === "string" ? options.locale : void 0;
2725
+ return /* @__PURE__ */ jsx26(
2726
+ FormFieldWrapper,
2727
+ {
2728
+ inputId: id,
2729
+ label,
2730
+ description,
2731
+ required,
2732
+ labelPlacement: options?.labelPlacement,
2733
+ children: /* @__PURE__ */ jsx26(
2734
+ DateInput,
2735
+ {
2736
+ id,
2737
+ value: typeof value === "string" ? value : "",
2738
+ onChange,
2739
+ onBlur: () => onBlur?.(id, value),
2740
+ precision,
2741
+ locale,
2742
+ placeholder,
2743
+ required,
2744
+ disabled,
2745
+ error
2746
+ }
2747
+ )
2748
+ }
2749
+ );
2750
+ }
2751
+
2752
+ // src/widgets/TimeWidget.tsx
2753
+ import { TimeInput } from "@petrarca/sonnet-ui";
2754
+ import { jsx as jsx27 } from "react/jsx-runtime";
2755
+ function resolveGranularity(value) {
2756
+ return value === "second" ? "second" : "minute";
2757
+ }
2758
+ function TimeWidget({
2759
+ id,
2760
+ value,
2761
+ onChange,
2762
+ onBlur,
2763
+ required,
2764
+ disabled,
2765
+ rawErrors,
2766
+ label,
2767
+ description,
2768
+ placeholder,
2769
+ options
2770
+ }) {
2771
+ const error = firstError(rawErrors);
2772
+ const granularity = resolveGranularity(options?.granularity);
2773
+ return /* @__PURE__ */ jsx27(
2774
+ FormFieldWrapper,
2775
+ {
2776
+ inputId: id,
2777
+ label,
2778
+ description,
2779
+ required,
2780
+ labelPlacement: options?.labelPlacement,
2781
+ children: /* @__PURE__ */ jsx27(
2782
+ TimeInput,
2783
+ {
2784
+ id,
2785
+ value: typeof value === "string" ? value : "",
2786
+ onChange,
2787
+ onBlur: () => onBlur?.(id, value),
2788
+ granularity,
2789
+ placeholder,
2790
+ required,
2791
+ disabled,
2792
+ error
2793
+ }
2794
+ )
2795
+ }
2796
+ );
2797
+ }
2798
+
2799
+ // src/widgets/DateTimeWidget.tsx
2800
+ import { DateTimeInput } from "@petrarca/sonnet-ui";
2801
+ import { jsx as jsx28 } from "react/jsx-runtime";
2802
+ function resolveGranularity2(value) {
2803
+ return value === "second" ? "second" : "minute";
2804
+ }
2805
+ function DateTimeWidget({
2806
+ id,
2807
+ value,
2808
+ onChange,
2809
+ onBlur,
2810
+ required,
2811
+ disabled,
2812
+ rawErrors,
2813
+ label,
2814
+ description,
2815
+ options
2816
+ }) {
2817
+ const error = firstError(rawErrors);
2818
+ const granularity = resolveGranularity2(options?.granularity);
2819
+ const timeZone = typeof options?.timeZone === "string" ? options.timeZone : void 0;
2820
+ const locale = typeof options?.locale === "string" ? options.locale : void 0;
2821
+ return /* @__PURE__ */ jsx28(
2822
+ FormFieldWrapper,
2823
+ {
2824
+ inputId: id,
2825
+ label,
2826
+ description,
2827
+ required,
2828
+ labelPlacement: options?.labelPlacement,
2829
+ children: /* @__PURE__ */ jsx28(
2830
+ DateTimeInput,
2831
+ {
2832
+ idPrefix: id,
2833
+ value: typeof value === "string" ? value : "",
2834
+ onChange,
2835
+ onBlur: () => onBlur?.(id, value),
2836
+ granularity,
2837
+ timeZone,
2838
+ locale,
2839
+ required,
2840
+ disabled,
2841
+ error
2842
+ }
2843
+ )
2844
+ }
2845
+ );
2846
+ }
2847
+
2848
+ // src/widgets/DisplayWidget.tsx
2849
+ import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
2850
+ function DisplayWidget({
2851
+ label,
2852
+ description
2853
+ }) {
2854
+ if (!label && !description) return null;
2855
+ return /* @__PURE__ */ jsxs17("div", { className: "space-y-1", children: [
2856
+ label && /* @__PURE__ */ jsx29("p", { className: "text-sm text-foreground", children: label }),
2857
+ description && /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: description })
2858
+ ] });
2859
+ }
2860
+
2861
+ // src/widgets/OneOfWidget.tsx
2862
+ import { useMemo as useMemo6 } from "react";
2863
+ import {
2864
+ getDiscriminator,
2865
+ getBranchValue,
2866
+ getBranchLabel,
2867
+ findActiveBranch
2868
+ } from "@petrarca/sonnet-core/schema";
2869
+ import {
2870
+ Label,
2871
+ Select,
2872
+ SelectTrigger,
2873
+ SelectValue,
2874
+ SelectContent,
2875
+ SelectItem
2876
+ } from "@petrarca/sonnet-ui";
2877
+ import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
2878
+ function buildChoices(branches, discriminator) {
2879
+ return branches.map((branch, index) => {
2880
+ const raw = discriminator ? getBranchValue(branch, discriminator) : void 0;
2881
+ return {
2882
+ index,
2883
+ value: raw !== void 0 ? String(raw) : String(index),
2884
+ label: getBranchLabel(branch, discriminator, index)
2885
+ };
2886
+ });
2887
+ }
2888
+ function buildBranchSchema(branch) {
2889
+ if (!branch.properties) return null;
2890
+ return {
2891
+ title: branch.title || "",
2892
+ type: "object",
2893
+ properties: branch.properties,
2894
+ required: branch.required,
2895
+ ...branch["x-ui-order"] ? { "x-ui-order": branch["x-ui-order"] } : {}
2896
+ };
2897
+ }
2898
+ function branchChangeData(choices, branches, discriminator, selected) {
2899
+ const choice = choices.find((c) => c.value === selected);
2900
+ if (!choice || !discriminator) return {};
2901
+ const discValue = getBranchValue(branches[choice.index], discriminator) ?? selected;
2902
+ return { [discriminator]: discValue };
2903
+ }
2904
+ function BranchSelector({
2905
+ id,
2906
+ label,
2907
+ description,
2908
+ choices,
2909
+ activeValue,
2910
+ disabled,
2911
+ onSelect
2912
+ }) {
2913
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-1", children: [
2914
+ label && /* @__PURE__ */ jsx30(Label, { htmlFor: id, children: label }),
2915
+ /* @__PURE__ */ jsxs18(Select, { value: activeValue, onValueChange: onSelect, disabled, children: [
2916
+ /* @__PURE__ */ jsx30(SelectTrigger, { id, children: /* @__PURE__ */ jsx30(SelectValue, { placeholder: "Select an option" }) }),
2917
+ /* @__PURE__ */ jsx30(SelectContent, { children: choices.map((c) => /* @__PURE__ */ jsx30(SelectItem, { value: c.value, children: c.label }, c.value)) })
2918
+ ] }),
2919
+ description && /* @__PURE__ */ jsx30("p", { className: "text-sm text-muted-foreground", children: description })
2920
+ ] });
2921
+ }
2922
+ function resolveActive(propertySchema, branches, choices, discriminator, data) {
2923
+ const index = findActiveBranch(propertySchema, data, discriminator);
2924
+ if (index < 0) return { value: "", label: "", subSchema: null };
2925
+ return {
2926
+ value: choices[index]?.value ?? "",
2927
+ label: choices[index]?.label ?? "",
2928
+ subSchema: buildBranchSchema(branches[index])
2929
+ };
2930
+ }
2931
+ function OneOfWidget({
2932
+ propertySchema,
2933
+ value,
2934
+ onChange,
2935
+ registry,
2936
+ uiSchema,
2937
+ disabled,
2938
+ readonly,
2939
+ label,
2940
+ description,
2941
+ id,
2942
+ options
2943
+ }) {
2944
+ const { renderer: Renderer, templates } = useNestedFormContext();
2945
+ const ContainerTemplate = templates.ObjectContainerTemplate ?? DefaultObjectContainerTemplate;
2946
+ const branches = useMemo6(() => propertySchema.oneOf ?? [], [propertySchema]);
2947
+ const discriminator = useMemo6(
2948
+ () => getDiscriminator(propertySchema),
2949
+ [propertySchema]
2950
+ );
2951
+ const choices = useMemo6(
2952
+ () => buildChoices(branches, discriminator),
2953
+ [branches, discriminator]
2954
+ );
2955
+ const data = value || {};
2956
+ const active = resolveActive(
2957
+ propertySchema,
2958
+ branches,
2959
+ choices,
2960
+ discriminator,
2961
+ data
2962
+ );
2963
+ if (branches.length === 0) return null;
2964
+ const subUiSchema = uiSchema?.["x-ui-fields"] ?? {};
2965
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-4", children: [
2966
+ /* @__PURE__ */ jsx30(
2967
+ BranchSelector,
2968
+ {
2969
+ id,
2970
+ label,
2971
+ description,
2972
+ choices,
2973
+ activeValue: active.value,
2974
+ disabled: disabled || readonly,
2975
+ onSelect: (selected) => onChange(branchChangeData(choices, branches, discriminator, selected))
2976
+ }
2977
+ ),
2978
+ active.subSchema && /* @__PURE__ */ jsx30(
2979
+ ContainerTemplate,
2980
+ {
2981
+ label: active.label,
2982
+ description: void 0,
2983
+ variant: options?.variant || "section",
2984
+ defaultOpen: true,
2985
+ children: /* @__PURE__ */ jsx30(
2986
+ Renderer,
2987
+ {
2988
+ schema: active.subSchema,
2989
+ data,
2990
+ onChange,
2991
+ disabled,
2992
+ readOnly: readonly,
2993
+ widgets: registry,
2994
+ uiSchema: subUiSchema
2995
+ }
2996
+ )
2997
+ }
2998
+ )
2999
+ ] });
3000
+ }
3001
+
2699
3002
  // src/widgets/widgetResolver.ts
2700
3003
  import {
2701
3004
  isNullableBoolean as isNullableBoolean2,
3005
+ isOneOf,
2702
3006
  extractEnumValues as extractEnumValues2,
2703
3007
  getFieldType
2704
3008
  } from "@petrarca/sonnet-core/schema";
@@ -2710,8 +3014,11 @@ var DEFAULT_WIDGET_MAP = {
2710
3014
  // Uses email input type
2711
3015
  uri: "TextInputWidget",
2712
3016
  // Uses url input type
2713
- url: "TextInputWidget"
3017
+ url: "TextInputWidget",
2714
3018
  // Uses url input type
3019
+ date: "DateWidget",
3020
+ "date-time": "DateTimeWidget",
3021
+ time: "TimeWidget"
2715
3022
  },
2716
3023
  number: {
2717
3024
  default: "NumberWidget"
@@ -2722,6 +3029,9 @@ var DEFAULT_WIDGET_MAP = {
2722
3029
  },
2723
3030
  boolean: {
2724
3031
  default: "CheckboxWidget"
3032
+ },
3033
+ null: {
3034
+ default: "DisplayWidget"
2725
3035
  }
2726
3036
  };
2727
3037
  function resolveWidgetByName(widgetName, propertySchema, registry) {
@@ -2771,6 +3081,7 @@ function registryLookup(registry, widgetName) {
2771
3081
  return registry[widgetName] || registry["TextInputWidget"];
2772
3082
  }
2773
3083
  function resolveFromSchemaStructure(schema, registry) {
3084
+ if (isOneOf(schema)) return registryLookup(registry, "OneOfWidget");
2774
3085
  if (isNullableBoolean2(schema))
2775
3086
  return registryLookup(registry, "CheckboxWidget");
2776
3087
  if (extractEnumValues2(schema).length > 0)
@@ -2814,6 +3125,11 @@ var DEFAULT_WIDGETS = {
2814
3125
  ObjectWidget,
2815
3126
  ArrayWidget,
2816
3127
  QuantityWidget,
3128
+ DateWidget,
3129
+ TimeWidget,
3130
+ DateTimeWidget,
3131
+ DisplayWidget,
3132
+ OneOfWidget,
2817
3133
  // Lowercase aliases for x-ui-widget convenience
2818
3134
  text: TextInputWidget,
2819
3135
  textarea: TextareaWidget,
@@ -2825,13 +3141,18 @@ var DEFAULT_WIDGETS = {
2825
3141
  "entity-select": EntitySelectWidget,
2826
3142
  object: ObjectWidget,
2827
3143
  array: ArrayWidget,
2828
- quantity: QuantityWidget
3144
+ quantity: QuantityWidget,
3145
+ date: DateWidget,
3146
+ time: TimeWidget,
3147
+ "date-time": DateTimeWidget,
3148
+ display: DisplayWidget,
3149
+ "one-of": OneOfWidget
2829
3150
  };
2830
3151
 
2831
3152
  // src/templates/HorizontalArrayItemTemplate.tsx
2832
3153
  import { Trash2 as Trash22, ChevronUp as ChevronUp2, ChevronDown as ChevronDown4 } from "lucide-react";
2833
3154
  import { Button as Button6 } from "@petrarca/sonnet-ui";
2834
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
3155
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
2835
3156
  function HorizontalArrayItemTemplate({
2836
3157
  children,
2837
3158
  index,
@@ -2855,7 +3176,7 @@ function HorizontalArrayItemTemplate({
2855
3176
  );
2856
3177
  const gap = resolvedLayout.gap ?? 8;
2857
3178
  const alignItems = resolvedLayout.compact ? "center" : "start";
2858
- return /* @__PURE__ */ jsxs17(
3179
+ return /* @__PURE__ */ jsxs19(
2859
3180
  "div",
2860
3181
  {
2861
3182
  "data-fid": fid,
@@ -2867,8 +3188,8 @@ function HorizontalArrayItemTemplate({
2867
3188
  },
2868
3189
  children: [
2869
3190
  children,
2870
- orderable && /* @__PURE__ */ jsxs17("div", { className: "flex flex-col items-center justify-center", children: [
2871
- /* @__PURE__ */ jsx26(
3191
+ orderable && /* @__PURE__ */ jsxs19("div", { className: "flex flex-col items-center justify-center", children: [
3192
+ /* @__PURE__ */ jsx31(
2872
3193
  Button6,
2873
3194
  {
2874
3195
  type: "button",
@@ -2878,10 +3199,10 @@ function HorizontalArrayItemTemplate({
2878
3199
  onClick: onMoveUp,
2879
3200
  title: "Move up",
2880
3201
  className: "h-4 w-8 p-0",
2881
- children: /* @__PURE__ */ jsx26(ChevronUp2, { className: "h-3 w-3" })
3202
+ children: /* @__PURE__ */ jsx31(ChevronUp2, { className: "h-3 w-3" })
2882
3203
  }
2883
3204
  ),
2884
- /* @__PURE__ */ jsx26(
3205
+ /* @__PURE__ */ jsx31(
2885
3206
  Button6,
2886
3207
  {
2887
3208
  type: "button",
@@ -2891,11 +3212,11 @@ function HorizontalArrayItemTemplate({
2891
3212
  onClick: onMoveDown,
2892
3213
  title: "Move down",
2893
3214
  className: "h-4 w-8 p-0",
2894
- children: /* @__PURE__ */ jsx26(ChevronDown4, { className: "h-3 w-3" })
3215
+ children: /* @__PURE__ */ jsx31(ChevronDown4, { className: "h-3 w-3" })
2895
3216
  }
2896
3217
  )
2897
3218
  ] }),
2898
- canRemove && /* @__PURE__ */ jsx26(
3219
+ canRemove && /* @__PURE__ */ jsx31(
2899
3220
  Button6,
2900
3221
  {
2901
3222
  type: "button",
@@ -2905,7 +3226,7 @@ function HorizontalArrayItemTemplate({
2905
3226
  onClick: onRemove,
2906
3227
  title: "Remove item",
2907
3228
  className: "text-muted-foreground hover:text-destructive h-8 w-8 p-0",
2908
- children: /* @__PURE__ */ jsx26(Trash22, { className: "h-3.5 w-3.5" })
3229
+ children: /* @__PURE__ */ jsx31(Trash22, { className: "h-3.5 w-3.5" })
2909
3230
  }
2910
3231
  )
2911
3232
  ]
@@ -2913,13 +3234,123 @@ function HorizontalArrayItemTemplate({
2913
3234
  );
2914
3235
  }
2915
3236
 
3237
+ // src/templates/TabsGroupTemplate.tsx
3238
+ import { useState as useState7 } from "react";
3239
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@petrarca/sonnet-ui";
3240
+
3241
+ // src/templates/GroupErrorBadge.tsx
3242
+ import { AlertCircle } from "lucide-react";
3243
+ import { jsx as jsx32 } from "react/jsx-runtime";
3244
+ function GroupErrorBadge({
3245
+ count
3246
+ }) {
3247
+ if (count === 0) return null;
3248
+ const label = count === 1 ? "1 field needs attention" : `${count} fields need attention`;
3249
+ return /* @__PURE__ */ jsx32(
3250
+ "span",
3251
+ {
3252
+ role: "img",
3253
+ "aria-label": label,
3254
+ title: label,
3255
+ className: "inline-flex shrink-0 text-destructive",
3256
+ children: /* @__PURE__ */ jsx32(AlertCircle, { className: "h-3.5 w-3.5" })
3257
+ }
3258
+ );
3259
+ }
3260
+
3261
+ // src/templates/TabsGroupTemplate.tsx
3262
+ import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
3263
+ function TabsGroupTemplate({
3264
+ groups
3265
+ }) {
3266
+ const [active, setActive] = useState7(groups[0].key);
3267
+ const value = groups.some((g) => g.key === active) ? active : groups[0].key;
3268
+ return /* @__PURE__ */ jsxs20(Tabs, { value, onValueChange: setActive, children: [
3269
+ /* @__PURE__ */ jsx33(TabsList, { className: "mb-4", children: groups.map((g) => /* @__PURE__ */ jsxs20(TabsTrigger, { value: g.key, className: "gap-1.5", children: [
3270
+ g.label,
3271
+ /* @__PURE__ */ jsx33(GroupErrorBadge, { count: g.errorCount })
3272
+ ] }, g.key)) }),
3273
+ groups.map((g) => /* @__PURE__ */ jsx33(TabsContent, { value: g.key, className: "space-y-4", children: g.content }, g.key))
3274
+ ] });
3275
+ }
3276
+
3277
+ // src/templates/StepperGroupTemplate.tsx
3278
+ import { useState as useState8 } from "react";
3279
+ import {
3280
+ Button as Button7,
3281
+ Stepper,
3282
+ StepperList,
3283
+ StepperItem,
3284
+ StepperTrigger,
3285
+ StepperIndicator,
3286
+ StepperSeparator,
3287
+ StepperTitle,
3288
+ StepperContent,
3289
+ StepperPrev,
3290
+ StepperNext
3291
+ } from "@petrarca/sonnet-ui";
3292
+ import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
3293
+ function StepperGroupTemplate({
3294
+ groups
3295
+ }) {
3296
+ const [active, setActive] = useState8(groups[0].key);
3297
+ const value = groups.some((g) => g.key === active) ? active : groups[0].key;
3298
+ const isLast = value === groups[groups.length - 1].key;
3299
+ return /* @__PURE__ */ jsxs21(Stepper, { value, onValueChange: setActive, children: [
3300
+ /* @__PURE__ */ jsx34(StepperList, { className: "mb-4 gap-2", children: groups.map((g) => /* @__PURE__ */ jsxs21(StepperItem, { value: g.key, children: [
3301
+ /* @__PURE__ */ jsxs21(StepperTrigger, { children: [
3302
+ /* @__PURE__ */ jsx34(StepperIndicator, {}),
3303
+ /* @__PURE__ */ jsxs21(StepperTitle, { className: "inline-flex items-center gap-1.5", children: [
3304
+ g.label,
3305
+ /* @__PURE__ */ jsx34(GroupErrorBadge, { count: g.errorCount })
3306
+ ] })
3307
+ ] }),
3308
+ /* @__PURE__ */ jsx34(StepperSeparator, {})
3309
+ ] }, g.key)) }),
3310
+ groups.map((g) => /* @__PURE__ */ jsx34(StepperContent, { value: g.key, className: "space-y-4", children: g.content }, g.key)),
3311
+ /* @__PURE__ */ jsxs21("div", { className: "mt-4 flex justify-between", children: [
3312
+ /* @__PURE__ */ jsx34(StepperPrev, { asChild: true, children: /* @__PURE__ */ jsx34(Button7, { variant: "outline", size: "sm", children: "Back" }) }),
3313
+ !isLast && /* @__PURE__ */ jsx34(StepperNext, { asChild: true, children: /* @__PURE__ */ jsx34(Button7, { size: "sm", children: "Next" }) })
3314
+ ] })
3315
+ ] });
3316
+ }
3317
+
3318
+ // src/templates/SectionsGroupTemplate.tsx
3319
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
3320
+ function SectionsGroupTemplate({
3321
+ groups
3322
+ }) {
3323
+ return /* @__PURE__ */ jsx35("div", { className: "space-y-6", children: groups.map((g) => /* @__PURE__ */ jsxs22(
3324
+ "fieldset",
3325
+ {
3326
+ className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
3327
+ children: [
3328
+ /* @__PURE__ */ jsxs22("legend", { className: "inline-flex items-center gap-1.5 px-1 text-sm font-medium text-muted-foreground", children: [
3329
+ g.label,
3330
+ /* @__PURE__ */ jsx35(GroupErrorBadge, { count: g.errorCount })
3331
+ ] }),
3332
+ /* @__PURE__ */ jsx35("div", { className: "space-y-4", children: g.content })
3333
+ ]
3334
+ },
3335
+ g.key
3336
+ )) });
3337
+ }
3338
+
2916
3339
  // src/templates/index.ts
2917
3340
  var DEFAULT_TEMPLATES = {
2918
3341
  ArrayItemTemplate: DefaultArrayItemTemplate,
2919
3342
  HorizontalArrayItemTemplate,
2920
3343
  ArrayHeaderTemplate: DefaultArrayHeaderTemplate,
2921
- ObjectContainerTemplate: DefaultObjectContainerTemplate
3344
+ ObjectContainerTemplate: DefaultObjectContainerTemplate,
3345
+ TabsGroupTemplate,
3346
+ StepperGroupTemplate,
3347
+ SectionsGroupTemplate
2922
3348
  };
3349
+ function groupTemplateKey(variant) {
3350
+ if (!variant) return "TabsGroupTemplate";
3351
+ const name = variant.charAt(0).toUpperCase() + variant.slice(1);
3352
+ return `${name}GroupTemplate`;
3353
+ }
2923
3354
 
2924
3355
  // src/FormFieldRenderer.tsx
2925
3356
  import { warnLog } from "@petrarca/sonnet-core";
@@ -2928,7 +3359,7 @@ import {
2928
3359
  getFieldHelpText,
2929
3360
  getFieldPlaceholder
2930
3361
  } from "@petrarca/sonnet-core/schema";
2931
- import { jsx as jsx27 } from "react/jsx-runtime";
3362
+ import { jsx as jsx36 } from "react/jsx-runtime";
2932
3363
  function mergeFieldOptions(globalOptions, property, uiSchema) {
2933
3364
  const xWidgetOptions = property["x-ui-options"] || {};
2934
3365
  const fieldOptions = uiSchema?.["x-ui-options"] || {};
@@ -2955,7 +3386,7 @@ function applyFieldWidth(widget, uiSchema, mergedOptions) {
2955
3386
  }
2956
3387
  const width = candidate && candidate in FIELD_WIDTH_CLASS ? candidate : "full";
2957
3388
  if (width === "full") return widget;
2958
- return /* @__PURE__ */ jsx27("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
3389
+ return /* @__PURE__ */ jsx36("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
2959
3390
  }
2960
3391
  function resolvePresentationText(property, uiSchema) {
2961
3392
  return {
@@ -3005,7 +3436,7 @@ function FormFieldRenderer({
3005
3436
  uiSchema?.["x-ui-title"],
3006
3437
  globalOptions.label
3007
3438
  );
3008
- const widget = /* @__PURE__ */ jsx27(
3439
+ const widget = /* @__PURE__ */ jsx36(
3009
3440
  Widget,
3010
3441
  {
3011
3442
  id: `${idPrefix}-${fieldName}`,
@@ -3031,7 +3462,7 @@ function FormFieldRenderer({
3031
3462
  }
3032
3463
 
3033
3464
  // src/hooks/useFormEngine.ts
3034
- import { useState as useState7, useEffect as useEffect3, useMemo as useMemo6, useCallback as useCallback7, useRef as useRef5 } from "react";
3465
+ import { useState as useState9, useEffect as useEffect3, useMemo as useMemo7, useCallback as useCallback7, useRef as useRef5 } from "react";
3035
3466
  import { isEqual } from "@petrarca/sonnet-core";
3036
3467
  import {
3037
3468
  getSchemaProperties,
@@ -3099,14 +3530,18 @@ function useFormEngine({
3099
3530
  showActions = false,
3100
3531
  deferChanges = false,
3101
3532
  deferStateUpdates = false,
3102
- allowPristineSave = false
3533
+ allowPristineSave = false,
3534
+ refResolver
3103
3535
  }) {
3104
- const refResolvedSchema = useMemo6(() => resolveRefs(schema), [schema]);
3105
- const [originalData, setOriginalData] = useState7(
3536
+ const refResolvedSchema = useMemo7(
3537
+ () => resolveRefs(schema, refResolver),
3538
+ [schema, refResolver]
3539
+ );
3540
+ const [originalData, setOriginalData] = useState9(
3106
3541
  () => applySchemaDefaults2(data, refResolvedSchema)
3107
3542
  );
3108
- const [originalDataInitialized, setOriginalDataInitialized] = useState7(false);
3109
- const [nonSchemaData, setNonSchemaData] = useState7(
3543
+ const [originalDataInitialized, setOriginalDataInitialized] = useState9(false);
3544
+ const [nonSchemaData, setNonSchemaData] = useState9(
3110
3545
  () => {
3111
3546
  const props = getSchemaProperties(refResolvedSchema);
3112
3547
  const names = Object.keys(props);
@@ -3115,26 +3550,23 @@ function useFormEngine({
3115
3550
  );
3116
3551
  }
3117
3552
  );
3118
- const [formData, setFormData] = useState7(
3553
+ const [formData, setFormData] = useState9(
3119
3554
  () => applySchemaDefaults2(data, refResolvedSchema)
3120
3555
  );
3121
- const [pendingChanges, setPendingChanges] = useState7({});
3122
- const resolvedSchema = useMemo6(
3556
+ const formDataRef = useRef5(formData);
3557
+ formDataRef.current = formData;
3558
+ const [pendingChanges, setPendingChanges] = useState9({});
3559
+ const resolvedSchema = useMemo7(
3123
3560
  () => resolveSchema(refResolvedSchema, formData),
3124
3561
  [refResolvedSchema, formData]
3125
3562
  );
3126
- const orderedProperties = useMemo6(() => {
3563
+ const orderedProperties = useMemo7(() => {
3127
3564
  const properties2 = getSchemaProperties(resolvedSchema);
3128
3565
  const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
3129
3566
  const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
3130
3567
  return orderProperties(properties2, orderArray);
3131
3568
  }, [resolvedSchema, uiSchema]);
3132
- const [isValid, setIsValid] = useState7(() => {
3133
- const initResolved = resolveSchema(refResolvedSchema, data);
3134
- const validation = validateFormData(initResolved, data);
3135
- return validation.isValid;
3136
- });
3137
- const [initialDefaultsSent, setInitialDefaultsSent] = useState7(false);
3569
+ const [initialDefaultsSent, setInitialDefaultsSent] = useState9(false);
3138
3570
  const properties = getSchemaProperties(resolvedSchema);
3139
3571
  useEffect3(() => {
3140
3572
  if (!initialDefaultsSent) {
@@ -3181,8 +3613,6 @@ function useFormEngine({
3181
3613
  setOriginalDataInitialized(true);
3182
3614
  }
3183
3615
  }
3184
- const validation = validateFormData(resolvedSchema, newData);
3185
- setIsValid(validation.isValid);
3186
3616
  }, [
3187
3617
  data,
3188
3618
  refResolvedSchema,
@@ -3191,21 +3621,31 @@ function useFormEngine({
3191
3621
  originalData,
3192
3622
  originalDataInitialized
3193
3623
  ]);
3194
- const hasChanges = useMemo6(
3624
+ const hasChanges = useMemo7(
3195
3625
  () => !isEqual(formData, originalData),
3196
3626
  [formData, originalData]
3197
3627
  );
3198
- const displayData = useMemo6(() => {
3628
+ const displayData = useMemo7(() => {
3199
3629
  if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
3200
3630
  return { ...formData, ...pendingChanges };
3201
3631
  }
3202
3632
  return formData;
3203
3633
  }, [formData, pendingChanges, deferStateUpdates]);
3634
+ const validation = useMemo7(
3635
+ () => validateFormData(resolvedSchema, displayData),
3636
+ [resolvedSchema, displayData]
3637
+ );
3638
+ const { isValid, fieldErrors } = validation;
3639
+ const onValidationChangeRef = useRef5(onValidationChange);
3640
+ onValidationChangeRef.current = onValidationChange;
3641
+ useEffect3(() => {
3642
+ onValidationChangeRef.current?.(validation.errors, validation.isValid);
3643
+ }, [validation]);
3204
3644
  const handleFieldChange = useCallback7(
3205
3645
  (fieldName, value) => {
3206
3646
  if (readOnly) return;
3207
3647
  const immediate = isImmediateField(fieldName, properties);
3208
- if (showActions && deferStateUpdates && !immediate) {
3648
+ if (deferStateUpdates && !immediate) {
3209
3649
  setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
3210
3650
  return;
3211
3651
  }
@@ -3215,11 +3655,6 @@ function useFormEngine({
3215
3655
  const shouldCallOnChange = !deferChanges || immediate;
3216
3656
  if (shouldCallOnChange && onChange) onChange(newData);
3217
3657
  }
3218
- const validation = validateFormData(resolvedSchema, newData);
3219
- setIsValid(validation.isValid);
3220
- if (onValidationChange) {
3221
- onValidationChange(validation.errors, validation.isValid);
3222
- }
3223
3658
  },
3224
3659
  [
3225
3660
  readOnly,
@@ -3228,14 +3663,12 @@ function useFormEngine({
3228
3663
  deferStateUpdates,
3229
3664
  showActions,
3230
3665
  onChange,
3231
- resolvedSchema,
3232
- onValidationChange,
3233
3666
  properties
3234
3667
  ]
3235
3668
  );
3236
3669
  const flushPendingField = useCallback7(
3237
3670
  (fieldName) => {
3238
- if (!showActions || !deferStateUpdates) return;
3671
+ if (!deferStateUpdates) return;
3239
3672
  if (pendingChanges[fieldName] === void 0) return;
3240
3673
  const newData = { ...formData, ...pendingChanges };
3241
3674
  setFormData(newData);
@@ -3244,37 +3677,26 @@ function useFormEngine({
3244
3677
  delete updated[fieldName];
3245
3678
  return updated;
3246
3679
  });
3247
- const validation = validateFormData(resolvedSchema, newData);
3248
- setIsValid(validation.isValid);
3249
- if (onValidationChange) {
3250
- onValidationChange(validation.errors, validation.isValid);
3251
- }
3680
+ if (!showActions && onChange) onChange(newData);
3252
3681
  },
3253
- [
3254
- showActions,
3255
- deferStateUpdates,
3256
- pendingChanges,
3257
- formData,
3258
- resolvedSchema,
3259
- onValidationChange
3260
- ]
3682
+ [showActions, deferStateUpdates, pendingChanges, formData, onChange]
3261
3683
  );
3262
3684
  const handleFieldBlur = useCallback7(
3263
3685
  (fieldName) => {
3264
3686
  if (readOnly) return;
3265
3687
  flushPendingField(fieldName);
3688
+ const latest = formDataRef.current;
3266
3689
  if (!showActions && deferChanges && onChange) {
3267
- onChange(formData);
3690
+ onChange(latest);
3268
3691
  }
3269
3692
  if (onBlur) {
3270
3693
  const hasPending = deferStateUpdates && pendingChanges[fieldName] !== void 0;
3271
- const dataToPass = hasPending ? { ...formData, [fieldName]: pendingChanges[fieldName] } : formData;
3694
+ const dataToPass = hasPending ? { ...latest, [fieldName]: pendingChanges[fieldName] } : latest;
3272
3695
  onBlur(fieldName, dataToPass);
3273
3696
  }
3274
3697
  },
3275
3698
  [
3276
3699
  readOnly,
3277
- formData,
3278
3700
  pendingChanges,
3279
3701
  deferChanges,
3280
3702
  deferStateUpdates,
@@ -3288,8 +3710,8 @@ function useFormEngine({
3288
3710
  if (!hasChanges && !allowPristineSave) return;
3289
3711
  const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
3290
3712
  const completeData = { ...nonSchemaData, ...finalData };
3291
- const validation = validateFormData(resolvedSchema, finalData);
3292
- if (validation.isValid && onUpdate) {
3713
+ const validation2 = validateFormData(resolvedSchema, finalData);
3714
+ if (validation2.isValid && onUpdate) {
3293
3715
  const changed = {};
3294
3716
  for (const key of Object.keys(finalData)) {
3295
3717
  if (!isEqual(finalData[key], originalData[key])) {
@@ -3323,6 +3745,7 @@ function useFormEngine({
3323
3745
  orderedProperties,
3324
3746
  hasChanges,
3325
3747
  isValid,
3748
+ fieldErrors,
3326
3749
  handleFieldChange,
3327
3750
  handleFieldBlur,
3328
3751
  handleSave,
@@ -3331,7 +3754,7 @@ function useFormEngine({
3331
3754
  }
3332
3755
 
3333
3756
  // src/JsonSchemaFormRenderer.tsx
3334
- import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
3757
+ import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
3335
3758
  function stripFids(data) {
3336
3759
  const result = {};
3337
3760
  for (const [key, value] of Object.entries(data)) {
@@ -3398,7 +3821,7 @@ function useWrappedCallbacks(onChange, onBlur, onUpdate) {
3398
3821
  function buildFieldMap(fields) {
3399
3822
  const map = /* @__PURE__ */ new Map();
3400
3823
  for (const field of fields) {
3401
- if (React11.isValidElement(field) && field.key != null) {
3824
+ if (React13.isValidElement(field) && field.key != null) {
3402
3825
  map.set(String(field.key), field);
3403
3826
  }
3404
3827
  }
@@ -3417,12 +3840,12 @@ function resolveGridColumns(row, fieldCount) {
3417
3840
  }
3418
3841
  function wrapInGroup(group, key, content) {
3419
3842
  if (!group) return content;
3420
- return /* @__PURE__ */ jsxs18(
3843
+ return /* @__PURE__ */ jsxs23(
3421
3844
  "fieldset",
3422
3845
  {
3423
3846
  className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
3424
3847
  children: [
3425
- /* @__PURE__ */ jsx28("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
3848
+ /* @__PURE__ */ jsx37("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
3426
3849
  content
3427
3850
  ]
3428
3851
  },
@@ -3444,7 +3867,7 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
3444
3867
  gridTemplateColumns: resolveGridColumns(row, rowFields.length),
3445
3868
  gap: `${rowGap}px`
3446
3869
  };
3447
- const inner = /* @__PURE__ */ jsx28("div", { style, children: rowFields }, `row:${rowKey}`);
3870
+ const inner = /* @__PURE__ */ jsx37("div", { style, children: rowFields }, `row:${rowKey}`);
3448
3871
  return {
3449
3872
  element: wrapInGroup(row.group, rowKey, inner),
3450
3873
  placed: row.fields
@@ -3453,24 +3876,92 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
3453
3876
  function collectUnplacedFields(fields, placed) {
3454
3877
  const result = [];
3455
3878
  for (const field of fields) {
3456
- if (React11.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
3879
+ if (React13.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
3457
3880
  result.push(field);
3458
3881
  }
3459
3882
  }
3460
3883
  return result;
3461
3884
  }
3462
- function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
3463
- const fieldMap = buildFieldMap(fields);
3464
- const placed = /* @__PURE__ */ new Set();
3885
+ function buildRows(rowDefs, fieldMap, defaultRowGap, placed) {
3465
3886
  const elements = [];
3466
3887
  for (const row of rowDefs) {
3467
3888
  const result = buildRowElement(row, fieldMap, defaultRowGap);
3468
- if (result == null) continue;
3469
- elements.push(result.element);
3470
- for (const name of result.placed) placed.add(name);
3889
+ for (const name of row.fields) placed.add(name);
3890
+ if (result != null) elements.push(result.element);
3471
3891
  }
3892
+ return elements;
3893
+ }
3894
+ function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
3895
+ const placed = /* @__PURE__ */ new Set();
3896
+ const elements = buildRows(
3897
+ rowDefs,
3898
+ buildFieldMap(fields),
3899
+ defaultRowGap,
3900
+ placed
3901
+ );
3472
3902
  return elements.concat(collectUnplacedFields(fields, placed));
3473
3903
  }
3904
+ function pointerRoot(pointer) {
3905
+ const token = pointer.split("/")[1] ?? "";
3906
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
3907
+ }
3908
+ function groupErrorCount(tab, fieldErrors) {
3909
+ if (fieldErrors.length === 0) return 0;
3910
+ const owned = new Set(tab.rows.flatMap((row) => row.fields));
3911
+ return fieldErrors.filter((e) => owned.has(pointerRoot(e.pointer))).length;
3912
+ }
3913
+ function groupVisibility(groupDefs, formData) {
3914
+ const visible = groupDefs.map(
3915
+ (tab) => evaluateUiRule(tab.rule, formData).visible
3916
+ );
3917
+ return { visible, allHidden: !visible.some(Boolean) };
3918
+ }
3919
+ function hiddenGroupFieldNames(groupDefs, formData) {
3920
+ const hidden = /* @__PURE__ */ new Set();
3921
+ const { visible, allHidden } = groupVisibility(groupDefs, formData);
3922
+ if (allHidden) return hidden;
3923
+ groupDefs.forEach((tab, i) => {
3924
+ if (visible[i]) return;
3925
+ for (const row of tab.rows) for (const f of row.fields) hidden.add(f);
3926
+ });
3927
+ return hidden;
3928
+ }
3929
+ function resolveGroupTemplate(templates, variant) {
3930
+ const registered = templates[groupTemplateKey(variant)];
3931
+ if (typeof registered === "function") {
3932
+ return registered;
3933
+ }
3934
+ return DEFAULT_TEMPLATES.TabsGroupTemplate;
3935
+ }
3936
+ function HiddenErrorNotice({
3937
+ count
3938
+ }) {
3939
+ if (count === 0) return null;
3940
+ return /* @__PURE__ */ jsxs23("p", { className: "flex items-center gap-1.5 text-sm text-destructive", children: [
3941
+ /* @__PURE__ */ jsx37(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
3942
+ count === 1 ? "1 required field is in a section that is not currently shown." : `${count} required fields are in sections that are not currently shown.`
3943
+ ] });
3944
+ }
3945
+ function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, formData, GroupTemplate) {
3946
+ const fieldMap = buildFieldMap(fields);
3947
+ const placed = /* @__PURE__ */ new Set();
3948
+ const { visible, allHidden } = groupVisibility(groupDefs, formData);
3949
+ const built = groupDefs.map((tab, index) => ({
3950
+ index,
3951
+ rows: buildRows(tab.rows, fieldMap, defaultRowGap, placed)
3952
+ }));
3953
+ const unplaced = collectUnplacedFields(fields, placed);
3954
+ if (allHidden) {
3955
+ return [...built.flatMap((b) => b.rows), ...unplaced];
3956
+ }
3957
+ const groups = built.filter((b) => visible[b.index]).map((b) => ({
3958
+ key: `group-${b.index}`,
3959
+ label: groupDefs[b.index].label,
3960
+ content: b.rows,
3961
+ errorCount: groupErrorCount(groupDefs[b.index], fieldErrors)
3962
+ }));
3963
+ return [/* @__PURE__ */ jsx37(GroupTemplate, { groups }, "form:groups"), ...unplaced];
3964
+ }
3474
3965
  function FormContainer({
3475
3966
  displayContents,
3476
3967
  showExtraProperties,
@@ -3486,15 +3977,17 @@ function FormContainer({
3486
3977
  cancelLabel,
3487
3978
  showCancel,
3488
3979
  leadingActions,
3980
+ hiddenErrorCount,
3489
3981
  children
3490
3982
  }) {
3491
3983
  if (displayContents) {
3492
- return /* @__PURE__ */ jsx28("div", { style: { display: "contents" }, children });
3984
+ return /* @__PURE__ */ jsx37("div", { style: { display: "contents" }, children });
3493
3985
  }
3494
- return /* @__PURE__ */ jsxs18(SimpleStack, { gap: "md", children: [
3986
+ return /* @__PURE__ */ jsxs23(SimpleStack, { gap: "md", children: [
3495
3987
  children,
3496
- showExtraProperties && /* @__PURE__ */ jsx28(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
3497
- showActions && /* @__PURE__ */ jsx28(
3988
+ showExtraProperties && /* @__PURE__ */ jsx37(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
3989
+ /* @__PURE__ */ jsx37(HiddenErrorNotice, { count: hiddenErrorCount }),
3990
+ showActions && /* @__PURE__ */ jsx37(
3498
3991
  FormActions,
3499
3992
  {
3500
3993
  onSave: handleSave,
@@ -3543,6 +4036,7 @@ function JsonSchemaFormRenderer(props) {
3543
4036
  orderedProperties,
3544
4037
  hasChanges,
3545
4038
  isValid,
4039
+ fieldErrors,
3546
4040
  handleFieldChange,
3547
4041
  handleFieldBlur,
3548
4042
  handleSave,
@@ -3560,22 +4054,23 @@ function JsonSchemaFormRenderer(props) {
3560
4054
  showActions,
3561
4055
  deferChanges,
3562
4056
  deferStateUpdates,
3563
- allowPristineSave
4057
+ allowPristineSave,
4058
+ refResolver: props.refResolver
3564
4059
  });
3565
4060
  const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
3566
- const globalOptions = useMemo7(
4061
+ const globalOptions = useMemo8(
3567
4062
  () => ({
3568
4063
  ...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
3569
4064
  ...uiSchema["x-ui-globalOptions"] || {}
3570
4065
  }),
3571
4066
  [layoutLabelPlacement, uiSchema]
3572
4067
  );
3573
- const resolvedTemplates = useMemo7(
4068
+ const resolvedTemplates = useMemo8(
3574
4069
  () => ({ ...DEFAULT_TEMPLATES, ...templates }),
3575
4070
  [templates]
3576
4071
  );
3577
4072
  const nestedRenderer = useCallback8(
3578
- (props2) => /* @__PURE__ */ jsx28(
4073
+ (props2) => /* @__PURE__ */ jsx37(
3579
4074
  JsonSchemaFormRenderer,
3580
4075
  {
3581
4076
  ...props2,
@@ -3586,32 +4081,39 @@ function JsonSchemaFormRenderer(props) {
3586
4081
  ),
3587
4082
  [resolvedTemplates]
3588
4083
  );
3589
- const contextValue = useMemo7(
4084
+ const contextValue = useMemo8(
3590
4085
  () => ({ renderer: nestedRenderer, templates: resolvedTemplates }),
3591
4086
  [nestedRenderer, resolvedTemplates]
3592
4087
  );
3593
4088
  const needsBlurHandler = deferStateUpdates || !!onBlur;
3594
- const fields = useMemo7(
3595
- () => orderedProperties.map(([fieldName, property]) => /* @__PURE__ */ jsx28(
3596
- FormFieldRenderer,
3597
- {
3598
- fieldName,
3599
- property,
3600
- value: displayData[fieldName],
3601
- onChange: (v) => handleFieldChange(fieldName, v),
3602
- onBlur: needsBlurHandler ? () => handleFieldBlur(fieldName) : void 0,
3603
- required: isFieldRequired(resolvedSchema, fieldName),
3604
- disabled,
3605
- readOnly,
3606
- schema,
3607
- uiSchema: uiSchema[fieldName],
3608
- registry: widgets,
3609
- globalOptions,
3610
- formData: displayData,
3611
- idPrefix
3612
- },
3613
- fieldName
3614
- )),
4089
+ const fields = useMemo8(
4090
+ () => orderedProperties.flatMap(([fieldName, property]) => {
4091
+ const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
4092
+ const { visible, enabled } = evaluateUiRule(rule, displayData);
4093
+ if (!visible) return [];
4094
+ return [
4095
+ /* @__PURE__ */ jsx37(
4096
+ FormFieldRenderer,
4097
+ {
4098
+ fieldName,
4099
+ property,
4100
+ value: displayData[fieldName],
4101
+ onChange: (v) => handleFieldChange(fieldName, v),
4102
+ onBlur: needsBlurHandler ? () => handleFieldBlur(fieldName) : void 0,
4103
+ required: isFieldRequired(resolvedSchema, fieldName),
4104
+ disabled: disabled || !enabled,
4105
+ readOnly,
4106
+ schema,
4107
+ uiSchema: uiSchema[fieldName],
4108
+ registry: widgets,
4109
+ globalOptions,
4110
+ formData: displayData,
4111
+ idPrefix
4112
+ },
4113
+ fieldName
4114
+ )
4115
+ ];
4116
+ }),
3615
4117
  [
3616
4118
  orderedProperties,
3617
4119
  displayData,
@@ -3630,12 +4132,43 @@ function JsonSchemaFormRenderer(props) {
3630
4132
  );
3631
4133
  const layoutConfig = uiSchema["x-ui-layout"];
3632
4134
  const rowDefs = layoutConfig?.rows;
4135
+ const groupDefs = layoutConfig?.groups;
3633
4136
  const defaultRowGap = layoutConfig?.gap ?? 24;
3634
- const layoutFields = useMemo7(
3635
- () => rowDefs && rowDefs.length > 0 ? layoutFieldsInRows(fields, rowDefs, defaultRowGap) : fields,
3636
- [fields, rowDefs, defaultRowGap]
4137
+ const groupTemplate = resolveGroupTemplate(
4138
+ resolvedTemplates,
4139
+ layoutConfig?.variant
3637
4140
  );
3638
- return /* @__PURE__ */ jsx28(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx28(
4141
+ const layoutFields = useMemo8(() => {
4142
+ if (groupDefs && groupDefs.length > 0) {
4143
+ return layoutFieldsInGroups(
4144
+ fields,
4145
+ groupDefs,
4146
+ defaultRowGap,
4147
+ fieldErrors,
4148
+ displayData,
4149
+ groupTemplate
4150
+ );
4151
+ }
4152
+ if (rowDefs && rowDefs.length > 0) {
4153
+ return layoutFieldsInRows(fields, rowDefs, defaultRowGap);
4154
+ }
4155
+ return fields;
4156
+ }, [
4157
+ fields,
4158
+ rowDefs,
4159
+ groupDefs,
4160
+ defaultRowGap,
4161
+ fieldErrors,
4162
+ displayData,
4163
+ groupTemplate
4164
+ ]);
4165
+ const hiddenErrorCount = useMemo8(() => {
4166
+ if (!groupDefs?.length || fieldErrors.length === 0) return 0;
4167
+ const hidden = hiddenGroupFieldNames(groupDefs, displayData);
4168
+ if (hidden.size === 0) return 0;
4169
+ return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
4170
+ }, [groupDefs, fieldErrors, displayData]);
4171
+ return /* @__PURE__ */ jsx37(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx37(
3639
4172
  FormContainer,
3640
4173
  {
3641
4174
  displayContents,
@@ -3652,13 +4185,14 @@ function JsonSchemaFormRenderer(props) {
3652
4185
  cancelLabel,
3653
4186
  showCancel,
3654
4187
  leadingActions,
4188
+ hiddenErrorCount,
3655
4189
  children: layoutFields
3656
4190
  }
3657
4191
  ) });
3658
4192
  }
3659
4193
 
3660
4194
  // src/hooks/useResolvedSchema.ts
3661
- import { useMemo as useMemo8 } from "react";
4195
+ import { useMemo as useMemo9 } from "react";
3662
4196
  import {
3663
4197
  getSchemaProperties as getSchemaProperties2,
3664
4198
  resolveSchema as resolveSchema2
@@ -3668,11 +4202,11 @@ function useResolvedSchema({
3668
4202
  formData,
3669
4203
  uiSchema
3670
4204
  }) {
3671
- const resolvedSchema = useMemo8(
4205
+ const resolvedSchema = useMemo9(
3672
4206
  () => resolveSchema2(schema, formData),
3673
4207
  [schema, formData]
3674
4208
  );
3675
- const orderedProperties = useMemo8(() => {
4209
+ const orderedProperties = useMemo9(() => {
3676
4210
  const properties = getSchemaProperties2(resolvedSchema);
3677
4211
  const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
3678
4212
  const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
@@ -3686,6 +4220,9 @@ export {
3686
4220
  DEFAULT_TEMPLATES,
3687
4221
  DEFAULT_WIDGETS,
3688
4222
  DEFAULT_WIDGET_MAP,
4223
+ DateTimeWidget,
4224
+ DateWidget,
4225
+ DisplayWidget,
3689
4226
  EntitySelectWidget,
3690
4227
  ExtraPropertiesCard_default as ExtraPropertiesCard,
3691
4228
  FormActions,
@@ -3699,18 +4236,25 @@ export {
3699
4236
  FormSelect,
3700
4237
  FormTagsInput,
3701
4238
  FormTextarea,
4239
+ GroupErrorBadge,
3702
4240
  JsonEditorWidget,
3703
4241
  JsonSchemaFormRenderer,
3704
4242
  NumberWidget,
3705
4243
  ObjectWidget,
4244
+ OneOfWidget,
3706
4245
  QuantityWidget,
4246
+ SectionsGroupTemplate,
3707
4247
  SelectWidget,
4248
+ StepperGroupTemplate,
4249
+ TabsGroupTemplate,
3708
4250
  TagsInputWidget,
3709
4251
  TextInputWidget,
3710
4252
  TextareaWidget,
4253
+ TimeWidget,
3711
4254
  firstError,
3712
4255
  getAriaDescribedBy,
3713
4256
  getDefaultWidgetForType,
4257
+ groupTemplateKey,
3714
4258
  resolveWidget,
3715
4259
  useFormEngine,
3716
4260
  useNestedFormContext,