@djangocfg/ui-tools 2.1.320 → 2.1.321

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.
Files changed (31) hide show
  1. package/dist/JsonSchemaForm-OSPUUUHM.cjs +13 -0
  2. package/dist/{JsonSchemaForm-IIYKSH6X.cjs.map → JsonSchemaForm-OSPUUUHM.cjs.map} +1 -1
  3. package/dist/JsonSchemaForm-TSLX2GRO.mjs +4 -0
  4. package/dist/{JsonSchemaForm-RN3XWSWX.mjs.map → JsonSchemaForm-TSLX2GRO.mjs.map} +1 -1
  5. package/dist/{chunk-L37FZYJU.cjs → chunk-4IW7GZFQ.cjs} +189 -74
  6. package/dist/chunk-4IW7GZFQ.cjs.map +1 -0
  7. package/dist/{chunk-JUGQNNDC.mjs → chunk-EXGXUK2N.mjs} +190 -76
  8. package/dist/chunk-EXGXUK2N.mjs.map +1 -0
  9. package/dist/index.cjs +28 -24
  10. package/dist/index.d.cts +240 -206
  11. package/dist/index.d.ts +240 -206
  12. package/dist/index.mjs +2 -2
  13. package/package.json +6 -6
  14. package/src/index.ts +15 -0
  15. package/src/tools/JsonForm/JsonForm.story.tsx +217 -1
  16. package/src/tools/JsonForm/JsonSchemaForm.tsx +15 -4
  17. package/src/tools/JsonForm/README.md +268 -0
  18. package/src/tools/JsonForm/index.ts +22 -1
  19. package/src/tools/JsonForm/templates/FieldTemplate.tsx +28 -5
  20. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +110 -3
  21. package/src/tools/JsonForm/types.ts +37 -5
  22. package/src/tools/JsonForm/utils.ts +25 -0
  23. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +6 -11
  24. package/src/tools/JsonForm/widgets/SelectWidget.tsx +20 -12
  25. package/src/tools/JsonForm/widgets/SliderWidget.tsx +9 -5
  26. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +6 -10
  27. package/src/tools/JsonForm/widgets/_useWidgetEnv.ts +43 -0
  28. package/dist/JsonSchemaForm-IIYKSH6X.cjs +0 -13
  29. package/dist/JsonSchemaForm-RN3XWSWX.mjs +0 -4
  30. package/dist/chunk-JUGQNNDC.mjs.map +0 -1
  31. package/dist/chunk-L37FZYJU.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { __name } from './chunk-CGILA3WO.mjs';
2
2
  import consola from 'consola';
3
3
  import { cn, isDev } from '@djangocfg/ui-core/lib';
4
- import { ChevronDown, Plus, AlertCircle } from 'lucide-react';
4
+ import { ChevronDown, ChevronRight, Plus, AlertCircle } from 'lucide-react';
5
5
  import { useState, useMemo, useCallback, useRef } from 'react';
6
6
  import { Label, Collapsible, CollapsibleTrigger, CollapsibleContent, Button, Alert, AlertTitle, AlertDescription, Input, Checkbox, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Switch, Slider } from '@djangocfg/ui-core/components';
7
7
  import Form from '@rjsf/core';
@@ -25,24 +25,41 @@ function FieldTemplate(props) {
25
25
  hidden,
26
26
  rawErrors
27
27
  } = props;
28
+ const formContext = props.formContext;
28
29
  if (hidden) {
29
30
  return /* @__PURE__ */ jsx("div", { className: "hidden", children });
30
31
  }
31
32
  const hasError = rawErrors && rawErrors.length > 0;
33
+ const density = formContext?.density ?? "comfortable";
34
+ const compact = density === "compact";
35
+ const descriptionText = typeof description === "string" ? description : void 0;
36
+ const labelTitle = compact ? descriptionText : void 0;
37
+ const showDescription = !compact && Boolean(description);
32
38
  return /* @__PURE__ */ jsxs(
33
39
  "div",
34
40
  {
35
- className: cn("space-y-2", classNames),
41
+ className: cn(compact ? "space-y-1" : "space-y-2", classNames),
36
42
  style,
37
43
  children: [
38
- displayLabel && label && /* @__PURE__ */ jsxs(Label, { htmlFor: id, className: cn(hasError && "text-destructive"), children: [
39
- label,
40
- required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
41
- ] }),
42
- description && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: description }),
44
+ displayLabel && label && /* @__PURE__ */ jsxs(
45
+ Label,
46
+ {
47
+ htmlFor: id,
48
+ className: cn(
49
+ hasError && "text-destructive",
50
+ compact && "text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70"
51
+ ),
52
+ title: labelTitle,
53
+ children: [
54
+ label,
55
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
56
+ ]
57
+ }
58
+ ),
59
+ showDescription && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: description }),
43
60
  /* @__PURE__ */ jsx("div", { children }),
44
- errors && /* @__PURE__ */ jsx("div", { className: "text-sm text-destructive", children: errors }),
45
- help && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: help })
61
+ errors && /* @__PURE__ */ jsx("div", { className: compact ? "text-xs text-destructive" : "text-sm text-destructive", children: errors }),
62
+ help && !compact && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: help })
46
63
  ]
47
64
  }
48
65
  );
@@ -56,14 +73,19 @@ function ObjectFieldTemplate(props) {
56
73
  required,
57
74
  uiSchema
58
75
  } = props;
76
+ const formContext = props.formContext;
59
77
  const isCollapsible = uiSchema?.["ui:collapsible"] === true;
60
78
  const defaultCollapsed = uiSchema?.["ui:collapsed"] === true;
61
79
  const gridCols = uiSchema?.["ui:grid"];
62
80
  const className = uiSchema?.["ui:className"];
81
+ const uiGroups = uiSchema?.["ui:groups"];
82
+ const density = formContext?.density ?? "comfortable";
83
+ const compact = density === "compact";
63
84
  const [isOpen, setIsOpen] = useState(!defaultCollapsed);
64
85
  const isRoot = !title;
65
- const gridClass = gridCols ? `grid gap-4 grid-cols-${gridCols}` : "space-y-4";
66
- const content = /* @__PURE__ */ jsx("div", { className: cn(gridClass, className), children: properties.map((element) => /* @__PURE__ */ jsx("div", { className: "property-wrapper", children: element.content }, element.name)) });
86
+ const gridClass = gridCols ? `grid gap-4 grid-cols-${gridCols}` : compact ? "space-y-2" : "space-y-4";
87
+ const groupedContent = uiGroups ? renderUiGroups({ properties, uiGroups, gridClass, className, compact }) : null;
88
+ const content = groupedContent ?? /* @__PURE__ */ jsx("div", { className: cn(gridClass, className), children: properties.map((element) => /* @__PURE__ */ jsx("div", { className: "property-wrapper", children: element.content }, element.name)) });
67
89
  if (isRoot) {
68
90
  return /* @__PURE__ */ jsx("div", { className: "space-y-6", children: content });
69
91
  }
@@ -102,6 +124,64 @@ function ObjectFieldTemplate(props) {
102
124
  ] });
103
125
  }
104
126
  __name(ObjectFieldTemplate, "ObjectFieldTemplate");
127
+ function renderUiGroups({
128
+ properties,
129
+ uiGroups,
130
+ gridClass,
131
+ className,
132
+ compact
133
+ }) {
134
+ const propsByName = new Map(properties.map((p) => [p.name, p]));
135
+ const groupedFieldNames = new Set(uiGroups.flatMap((g) => g.fields));
136
+ const ungrouped = properties.filter((p) => !groupedFieldNames.has(p.name));
137
+ return /* @__PURE__ */ jsxs("div", { className: cn(compact ? "space-y-2" : "space-y-3", className), children: [
138
+ ungrouped.length > 0 ? /* @__PURE__ */ jsx("div", { className: gridClass, children: ungrouped.map((element) => /* @__PURE__ */ jsx("div", { className: "property-wrapper", children: element.content }, element.name)) }) : null,
139
+ uiGroups.map((group) => {
140
+ const groupProps = group.fields.map((name) => propsByName.get(name)).filter((p) => Boolean(p));
141
+ if (groupProps.length === 0) return null;
142
+ return /* @__PURE__ */ jsx(
143
+ UiGroupSection,
144
+ {
145
+ group,
146
+ gridClass,
147
+ compact,
148
+ children: groupProps.map((element) => /* @__PURE__ */ jsx("div", { className: "property-wrapper", children: element.content }, element.name))
149
+ },
150
+ group.title
151
+ );
152
+ })
153
+ ] });
154
+ }
155
+ __name(renderUiGroups, "renderUiGroups");
156
+ function UiGroupSection({ group, gridClass, compact, children }) {
157
+ const [open, setOpen] = useState(group.defaultOpen ?? true);
158
+ return /* @__PURE__ */ jsxs(Collapsible, { open, onOpenChange: setOpen, children: [
159
+ /* @__PURE__ */ jsxs(
160
+ CollapsibleTrigger,
161
+ {
162
+ className: cn(
163
+ "flex w-full items-center gap-1.5 py-1 transition-colors hover:text-foreground",
164
+ compact ? "text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70" : "text-xs font-semibold uppercase tracking-wide text-muted-foreground"
165
+ ),
166
+ children: [
167
+ /* @__PURE__ */ jsx(
168
+ ChevronRight,
169
+ {
170
+ className: cn(
171
+ "h-3 w-3 shrink-0 transition-transform duration-200",
172
+ open && "rotate-90"
173
+ ),
174
+ "aria-hidden": true
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx("span", { children: group.title })
178
+ ]
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx(CollapsibleContent, { className: "overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down", children: /* @__PURE__ */ jsx("div", { className: cn("border-l border-border/40 pl-3 pb-2 pt-1", gridClass), children }) })
182
+ ] });
183
+ }
184
+ __name(UiGroupSection, "UiGroupSection");
105
185
  function ArrayFieldTemplate(props) {
106
186
  const {
107
187
  title,
@@ -392,6 +472,18 @@ function getNestedValue(obj, path) {
392
472
  return path.split(".").reduce((current, key) => current?.[key], obj);
393
473
  }
394
474
  __name(getNestedValue, "getNestedValue");
475
+ function evaluateDisabledWhen(rule, formData) {
476
+ if (!rule) return false;
477
+ const value = getNestedValue(formData, rule.path);
478
+ if ("eq" in rule) return value === rule.eq;
479
+ if ("notEq" in rule) return value !== rule.notEq;
480
+ if ("in" in rule) return rule.in.includes(value);
481
+ if ("notIn" in rule) return !rule.notIn.includes(value);
482
+ if ("truthy" in rule) return Boolean(value);
483
+ if ("falsy" in rule) return !value;
484
+ return false;
485
+ }
486
+ __name(evaluateDisabledWhen, "evaluateDisabledWhen");
395
487
  function TextWidget(props) {
396
488
  const {
397
489
  id,
@@ -536,17 +628,26 @@ function NumberWidget(props) {
536
628
  );
537
629
  }
538
630
  __name(NumberWidget, "NumberWidget");
631
+
632
+ // src/tools/JsonForm/widgets/_useWidgetEnv.ts
633
+ function useWidgetEnv(props) {
634
+ const { disabled, readonly, formContext, uiSchema, schema } = props;
635
+ const density = formContext?.density ?? "comfortable";
636
+ const compact = density === "compact";
637
+ const disabledWhen = uiSchema?.["ui:disabledWhen"];
638
+ const disabledByRule = evaluateDisabledWhen(disabledWhen, formContext?.formData);
639
+ const tooltipText = compact ? uiSchema?.["ui:description"] ?? schema?.description : void 0;
640
+ return {
641
+ density,
642
+ compact,
643
+ disabled: Boolean(disabled || readonly || disabledByRule),
644
+ tooltipText
645
+ };
646
+ }
647
+ __name(useWidgetEnv, "useWidgetEnv");
539
648
  function CheckboxWidget(props) {
540
- const {
541
- id,
542
- value,
543
- disabled,
544
- readonly,
545
- autofocus,
546
- onChange,
547
- onBlur,
548
- onFocus
549
- } = props;
649
+ const { id, value, autofocus, onChange, onBlur, onFocus } = props;
650
+ const { disabled, tooltipText } = useWidgetEnv(props);
550
651
  const handleChange = /* @__PURE__ */ __name((checked) => {
551
652
  onChange(checked);
552
653
  }, "handleChange");
@@ -555,11 +656,12 @@ function CheckboxWidget(props) {
555
656
  {
556
657
  id,
557
658
  checked: value || false,
558
- disabled: disabled || readonly,
659
+ disabled,
559
660
  autoFocus: autofocus,
560
661
  onCheckedChange: handleChange,
561
662
  onBlur: () => onBlur(id, value),
562
- onFocus: () => onFocus(id, value)
663
+ onFocus: () => onFocus(id, value),
664
+ title: tooltipText
563
665
  }
564
666
  );
565
667
  }
@@ -570,8 +672,6 @@ function SelectWidget(props) {
570
672
  options,
571
673
  value,
572
674
  required,
573
- disabled,
574
- readonly,
575
675
  autofocus,
576
676
  onChange,
577
677
  onBlur,
@@ -579,6 +679,7 @@ function SelectWidget(props) {
579
679
  placeholder,
580
680
  rawErrors
581
681
  } = props;
682
+ const { disabled, compact, tooltipText } = useWidgetEnv(props);
582
683
  const enumOptions = useMemo(() => {
583
684
  const opts = options?.enumOptions;
584
685
  if (!Array.isArray(opts)) return [];
@@ -605,8 +706,9 @@ function SelectWidget(props) {
605
706
  onChange: (e) => onChange(e.target.value),
606
707
  onBlur: (e) => onBlur(id, e.target.value),
607
708
  onFocus: (e) => onFocus(id, e.target.value),
608
- disabled: disabled || readonly,
609
- readOnly: readonly,
709
+ disabled,
710
+ readOnly: disabled,
711
+ title: tooltipText,
610
712
  className: `flex h-10 w-full rounded-md border ${hasError ? "border-destructive" : "border-input"} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`,
611
713
  placeholder
612
714
  }
@@ -617,35 +719,32 @@ function SelectWidget(props) {
617
719
  {
618
720
  value: safeValue,
619
721
  onValueChange: handleChange,
620
- disabled: disabled || readonly,
722
+ disabled,
621
723
  required,
622
724
  children: [
623
725
  /* @__PURE__ */ jsx(
624
726
  SelectTrigger,
625
727
  {
626
728
  id,
627
- className: hasError ? "border-destructive" : "",
729
+ className: `${hasError ? "border-destructive" : ""} ${compact ? "h-7 text-xs" : ""}`.trim(),
628
730
  autoFocus: autofocus,
629
731
  onFocus: () => onFocus(id, value),
732
+ title: tooltipText,
630
733
  children: /* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder || "Select an option" })
631
734
  }
632
735
  ),
633
- /* @__PURE__ */ jsx(SelectContent, { children: enumOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option.value), children: option.label || String(option.value) }, String(option.value))) })
736
+ /* @__PURE__ */ jsx(SelectContent, { children: enumOptions.map((option) => {
737
+ const optValue = String(option.value);
738
+ return /* @__PURE__ */ jsx(SelectItem, { value: optValue, children: option.label || optValue }, optValue || "__empty__");
739
+ }) })
634
740
  ]
635
741
  }
636
742
  );
637
743
  }
638
744
  __name(SelectWidget, "SelectWidget");
639
745
  function SwitchWidget(props) {
640
- const {
641
- id,
642
- value,
643
- disabled,
644
- readonly,
645
- onChange,
646
- onBlur,
647
- onFocus
648
- } = props;
746
+ const { id, value, onChange, onBlur, onFocus } = props;
747
+ const { disabled, tooltipText } = useWidgetEnv(props);
649
748
  const handleChange = /* @__PURE__ */ __name((checked) => {
650
749
  onChange(checked);
651
750
  }, "handleChange");
@@ -654,10 +753,11 @@ function SwitchWidget(props) {
654
753
  {
655
754
  id,
656
755
  checked: value || false,
657
- disabled: disabled || readonly,
756
+ disabled,
658
757
  onCheckedChange: handleChange,
659
758
  onBlur: () => onBlur(id, value),
660
- onFocus: () => onFocus(id, value)
759
+ onFocus: () => onFocus(id, value),
760
+ title: tooltipText
661
761
  }
662
762
  );
663
763
  }
@@ -841,14 +941,13 @@ __name(ColorWidget, "ColorWidget");
841
941
  function SliderWidget(props) {
842
942
  const {
843
943
  id,
844
- disabled,
845
- readonly,
846
944
  value,
847
945
  onChange,
848
946
  schema,
849
947
  options,
850
948
  rawErrors
851
949
  } = props;
950
+ const { disabled, tooltipText } = useWidgetEnv(props);
852
951
  const config = useMemo(() => {
853
952
  const min = schema.minimum ?? options?.min ?? 0;
854
953
  const max = schema.maximum ?? options?.max ?? 100;
@@ -902,35 +1001,42 @@ function SliderWidget(props) {
902
1001
  }
903
1002
  return String(numericValue);
904
1003
  }, [numericValue, config.unit]);
905
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", hasError && "text-destructive"), children: [
906
- /* @__PURE__ */ jsx(
907
- Slider,
908
- {
909
- id,
910
- disabled: disabled || readonly,
911
- value: [numericValue],
912
- onValueChange: handleSliderChange,
913
- min: config.min,
914
- max: config.max,
915
- step: config.step,
916
- className: "flex-1"
917
- }
918
- ),
919
- config.showInput ? /* @__PURE__ */ jsx(
920
- Input,
921
- {
922
- type: "text",
923
- value: displayValue,
924
- onChange: handleInputChange,
925
- disabled,
926
- readOnly: readonly,
927
- className: cn(
928
- "w-20 text-center font-mono text-sm",
929
- hasError && "border-destructive"
930
- )
931
- }
932
- ) : /* @__PURE__ */ jsx("span", { className: "w-16 text-right font-mono text-sm text-muted-foreground", children: displayValue })
933
- ] });
1004
+ return /* @__PURE__ */ jsxs(
1005
+ "div",
1006
+ {
1007
+ className: cn("flex items-center gap-3", hasError && "text-destructive"),
1008
+ title: tooltipText,
1009
+ children: [
1010
+ /* @__PURE__ */ jsx(
1011
+ Slider,
1012
+ {
1013
+ id,
1014
+ disabled,
1015
+ value: [numericValue],
1016
+ onValueChange: handleSliderChange,
1017
+ min: config.min,
1018
+ max: config.max,
1019
+ step: config.step,
1020
+ className: "flex-1"
1021
+ }
1022
+ ),
1023
+ config.showInput ? /* @__PURE__ */ jsx(
1024
+ Input,
1025
+ {
1026
+ type: "text",
1027
+ value: displayValue,
1028
+ onChange: handleInputChange,
1029
+ disabled,
1030
+ readOnly: disabled,
1031
+ className: cn(
1032
+ "w-20 text-center font-mono text-sm",
1033
+ hasError && "border-destructive"
1034
+ )
1035
+ }
1036
+ ) : /* @__PURE__ */ jsx("span", { className: "w-16 text-right font-mono text-sm text-muted-foreground", children: displayValue })
1037
+ ]
1038
+ }
1039
+ );
934
1040
  }
935
1041
  __name(SliderWidget, "SliderWidget");
936
1042
  function JsonSchemaForm(props) {
@@ -948,6 +1054,8 @@ function JsonSchemaForm(props) {
948
1054
  className,
949
1055
  showSubmitButton = true,
950
1056
  submitButtonText = "Submit",
1057
+ density = "comfortable",
1058
+ formContext: callerFormContext,
951
1059
  ...restProps
952
1060
  } = props;
953
1061
  const validatedSchema = useMemo(() => {
@@ -1029,7 +1137,12 @@ function JsonSchemaForm(props) {
1029
1137
  /* @__PURE__ */ jsx(AlertDescription, { children: "Invalid schema provided. Please check the schema format." })
1030
1138
  ] }) });
1031
1139
  }
1032
- return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
1140
+ const formContext = useMemo(() => ({
1141
+ ...callerFormContext,
1142
+ density,
1143
+ formData: normalizedFormData
1144
+ }), [callerFormContext, density, normalizedFormData]);
1145
+ return /* @__PURE__ */ jsx("div", { className, "data-jsonform-density": density, children: /* @__PURE__ */ jsx(
1033
1146
  Form,
1034
1147
  {
1035
1148
  schema: validatedSchema,
@@ -1038,6 +1151,7 @@ function JsonSchemaForm(props) {
1038
1151
  validator,
1039
1152
  widgets,
1040
1153
  templates,
1154
+ formContext,
1041
1155
  onSubmit: handleSubmit,
1042
1156
  onChange: handleChange,
1043
1157
  onError: handleError,
@@ -1052,6 +1166,6 @@ function JsonSchemaForm(props) {
1052
1166
  }
1053
1167
  __name(JsonSchemaForm, "JsonSchemaForm");
1054
1168
 
1055
- export { ArrayFieldItemTemplate, ArrayFieldTemplate, BaseInputTemplate, CheckboxWidget, ColorWidget, ErrorListTemplate, FieldTemplate, JsonSchemaForm, NumberWidget, ObjectFieldTemplate, SelectWidget, SliderWidget, SwitchWidget, TextWidget, getRequiredFields, hasRequiredFields, mergeDefaults, normalizeFormData, safeJsonParse, safeJsonStringify, validateRequiredFields, validateSchema };
1056
- //# sourceMappingURL=chunk-JUGQNNDC.mjs.map
1057
- //# sourceMappingURL=chunk-JUGQNNDC.mjs.map
1169
+ export { ArrayFieldItemTemplate, ArrayFieldTemplate, BaseInputTemplate, CheckboxWidget, ColorWidget, ErrorListTemplate, FieldTemplate, JsonSchemaForm, NumberWidget, ObjectFieldTemplate, SelectWidget, SliderWidget, SwitchWidget, TextWidget, evaluateDisabledWhen, getRequiredFields, hasRequiredFields, mergeDefaults, normalizeFormData, safeJsonParse, safeJsonStringify, validateRequiredFields, validateSchema };
1170
+ //# sourceMappingURL=chunk-EXGXUK2N.mjs.map
1171
+ //# sourceMappingURL=chunk-EXGXUK2N.mjs.map