@petrarca/sonnet-forms 0.4.2 → 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.d.ts +255 -5
- package/dist/index.js +973 -243
- package/dist/index.js.map +1 -1
- package/dist/widget-options.d.ts +61 -0
- package/dist/widget-options.js +237 -0
- package/dist/widget-options.js.map +1 -0
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -8,39 +8,39 @@ function FormFieldWrapper({
|
|
|
8
8
|
error,
|
|
9
9
|
required,
|
|
10
10
|
compact,
|
|
11
|
+
labelPlacement = "top",
|
|
11
12
|
className,
|
|
12
13
|
children
|
|
13
14
|
}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
if (compact) return /* @__PURE__ */ jsx("div", { className, children });
|
|
16
|
+
const labelEl = !!label && /* @__PURE__ */ jsxs(
|
|
17
|
+
"label",
|
|
18
|
+
{
|
|
19
|
+
htmlFor: inputId,
|
|
20
|
+
className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
21
|
+
children: [
|
|
22
|
+
label,
|
|
23
|
+
required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
const descriptionEl = description && /* @__PURE__ */ jsx("p", { id: `${inputId}-description`, className: "text-sm text-muted-foreground", children: description });
|
|
28
|
+
const errorEl = error && /* @__PURE__ */ jsx("p", { id: `${inputId}-error`, className: "text-sm font-medium text-destructive", children: error });
|
|
29
|
+
if (labelPlacement === "start" && !!label) {
|
|
30
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
31
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 whitespace-nowrap", children: labelEl }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full min-w-0 space-y-1", children: [
|
|
33
|
+
descriptionEl,
|
|
34
|
+
children,
|
|
35
|
+
errorEl
|
|
36
|
+
] })
|
|
37
|
+
] });
|
|
38
|
+
}
|
|
39
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
40
|
+
labelEl,
|
|
41
|
+
descriptionEl,
|
|
35
42
|
children,
|
|
36
|
-
|
|
37
|
-
"p",
|
|
38
|
-
{
|
|
39
|
-
id: `${inputId}-error`,
|
|
40
|
-
className: "text-sm font-medium text-destructive",
|
|
41
|
-
children: error
|
|
42
|
-
}
|
|
43
|
-
)
|
|
43
|
+
errorEl
|
|
44
44
|
] });
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -84,6 +84,7 @@ var FormInput = React.forwardRef(
|
|
|
84
84
|
rightSection,
|
|
85
85
|
rightSectionWidth,
|
|
86
86
|
compact,
|
|
87
|
+
labelPlacement,
|
|
87
88
|
...props
|
|
88
89
|
}, ref) => {
|
|
89
90
|
const generatedId = useId();
|
|
@@ -98,6 +99,7 @@ var FormInput = React.forwardRef(
|
|
|
98
99
|
error,
|
|
99
100
|
required,
|
|
100
101
|
compact,
|
|
102
|
+
labelPlacement,
|
|
101
103
|
className: wrapperClassName,
|
|
102
104
|
children: /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
103
105
|
/* @__PURE__ */ jsx2(
|
|
@@ -170,16 +172,21 @@ var FormTextarea = React2.forwardRef((allProps, ref) => {
|
|
|
170
172
|
error,
|
|
171
173
|
required,
|
|
172
174
|
wrapperClassName,
|
|
175
|
+
labelPlacement,
|
|
173
176
|
className,
|
|
174
177
|
id,
|
|
175
178
|
value,
|
|
176
179
|
onChange,
|
|
177
180
|
rightSection,
|
|
178
181
|
rightSectionWidth,
|
|
182
|
+
// Destructured out so they are not spread onto the DOM textarea below;
|
|
183
|
+
// these are component-level props, not valid HTML attributes.
|
|
184
|
+
autosize: autosizeProp,
|
|
185
|
+
minRows: minRowsProp,
|
|
179
186
|
...props
|
|
180
187
|
} = allProps;
|
|
181
|
-
const autosize =
|
|
182
|
-
const minRows =
|
|
188
|
+
const autosize = autosizeProp ?? false;
|
|
189
|
+
const minRows = minRowsProp ?? 3;
|
|
183
190
|
const generatedId = useId2();
|
|
184
191
|
const textareaId = id ?? generatedId;
|
|
185
192
|
const internalRef = useRef(null);
|
|
@@ -196,6 +203,7 @@ var FormTextarea = React2.forwardRef((allProps, ref) => {
|
|
|
196
203
|
description,
|
|
197
204
|
error,
|
|
198
205
|
required,
|
|
206
|
+
labelPlacement,
|
|
199
207
|
className: cn3("flex flex-col", wrapperClassName),
|
|
200
208
|
children: /* @__PURE__ */ jsxs3("div", { className: "relative flex-1 min-h-0", children: [
|
|
201
209
|
/* @__PURE__ */ jsx3(
|
|
@@ -246,7 +254,8 @@ var FormNumberInput = React3.forwardRef(
|
|
|
246
254
|
wrapperClassName,
|
|
247
255
|
className,
|
|
248
256
|
id,
|
|
249
|
-
name
|
|
257
|
+
name,
|
|
258
|
+
labelPlacement
|
|
250
259
|
}, ref) => {
|
|
251
260
|
const generatedId = useId3();
|
|
252
261
|
const inputId = id ?? generatedId;
|
|
@@ -269,6 +278,7 @@ var FormNumberInput = React3.forwardRef(
|
|
|
269
278
|
description,
|
|
270
279
|
error,
|
|
271
280
|
required,
|
|
281
|
+
labelPlacement,
|
|
272
282
|
className: wrapperClassName,
|
|
273
283
|
children: /* @__PURE__ */ jsx4(
|
|
274
284
|
Input2,
|
|
@@ -422,7 +432,7 @@ FormCheckbox.displayName = "FormCheckbox";
|
|
|
422
432
|
|
|
423
433
|
// src/FormSelect.tsx
|
|
424
434
|
import * as React5 from "react";
|
|
425
|
-
import { Check, ChevronsUpDown, X } from "lucide-react";
|
|
435
|
+
import { Check, ChevronsUpDown, Plus, X } from "lucide-react";
|
|
426
436
|
import { cn as cn6 } from "@petrarca/sonnet-core";
|
|
427
437
|
import { Button } from "@petrarca/sonnet-ui";
|
|
428
438
|
import {
|
|
@@ -434,12 +444,28 @@ import {
|
|
|
434
444
|
CommandList
|
|
435
445
|
} from "@petrarca/sonnet-ui";
|
|
436
446
|
import { Popover, PopoverContent, PopoverTrigger } from "@petrarca/sonnet-ui";
|
|
447
|
+
|
|
448
|
+
// src/formSelectUtils.ts
|
|
449
|
+
function canCreateFromSearch(allowCreate, searchQuery, options) {
|
|
450
|
+
const trimmed = searchQuery.trim();
|
|
451
|
+
if (!allowCreate || trimmed.length === 0) return false;
|
|
452
|
+
const lower = trimmed.toLowerCase();
|
|
453
|
+
return !options.some((o) => o.label.toLowerCase() === lower);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/FormSelect.tsx
|
|
437
457
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
438
|
-
function useSelectState(value, onChange, onBlur) {
|
|
458
|
+
function useSelectState(value, onChange, onBlur, onCreate, clearable) {
|
|
439
459
|
const [open, setOpen] = React5.useState(false);
|
|
440
460
|
const [searchQuery, setSearchQuery] = React5.useState("");
|
|
441
461
|
const handleSelect = (selectedValue) => {
|
|
442
|
-
|
|
462
|
+
const next = clearable && selectedValue === value ? null : selectedValue;
|
|
463
|
+
onChange?.(next);
|
|
464
|
+
setOpen(false);
|
|
465
|
+
setSearchQuery("");
|
|
466
|
+
};
|
|
467
|
+
const handleCreate = (label) => {
|
|
468
|
+
onCreate?.(label);
|
|
443
469
|
setOpen(false);
|
|
444
470
|
setSearchQuery("");
|
|
445
471
|
};
|
|
@@ -458,6 +484,7 @@ function useSelectState(value, onChange, onBlur) {
|
|
|
458
484
|
searchQuery,
|
|
459
485
|
setSearchQuery,
|
|
460
486
|
handleSelect,
|
|
487
|
+
handleCreate,
|
|
461
488
|
handleClear,
|
|
462
489
|
handleOpenChange
|
|
463
490
|
};
|
|
@@ -479,8 +506,11 @@ function SelectDropdown({
|
|
|
479
506
|
emptyMessage,
|
|
480
507
|
options,
|
|
481
508
|
value,
|
|
482
|
-
onSelect
|
|
509
|
+
onSelect,
|
|
510
|
+
allowCreate = false,
|
|
511
|
+
onCreate
|
|
483
512
|
}) {
|
|
513
|
+
const showCreate = canCreateFromSearch(allowCreate, searchQuery, options);
|
|
484
514
|
return /* @__PURE__ */ jsxs5(Command, { shouldFilter: false, children: [
|
|
485
515
|
/* @__PURE__ */ jsx6(
|
|
486
516
|
CommandInput,
|
|
@@ -491,29 +521,45 @@ function SelectDropdown({
|
|
|
491
521
|
}
|
|
492
522
|
),
|
|
493
523
|
/* @__PURE__ */ jsxs5(CommandList, { children: [
|
|
494
|
-
/* @__PURE__ */ jsx6(CommandEmpty, { children: loading ? "Loading..." : emptyMessage }),
|
|
495
|
-
/* @__PURE__ */
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
524
|
+
!showCreate && /* @__PURE__ */ jsx6(CommandEmpty, { children: loading ? "Loading..." : emptyMessage }),
|
|
525
|
+
/* @__PURE__ */ jsxs5(CommandGroup, { children: [
|
|
526
|
+
options.filter(
|
|
527
|
+
(option) => option.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
528
|
+
).map((option) => /* @__PURE__ */ jsxs5(
|
|
529
|
+
CommandItem,
|
|
530
|
+
{
|
|
531
|
+
value: option.value,
|
|
532
|
+
onSelect,
|
|
533
|
+
children: [
|
|
534
|
+
/* @__PURE__ */ jsx6(
|
|
535
|
+
Check,
|
|
536
|
+
{
|
|
537
|
+
className: cn6(
|
|
538
|
+
"mr-2 h-4 w-4",
|
|
539
|
+
value === option.value ? "opacity-100" : "opacity-0"
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
),
|
|
543
|
+
option.label
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
option.value
|
|
547
|
+
)),
|
|
548
|
+
showCreate && /* @__PURE__ */ jsxs5(
|
|
549
|
+
CommandItem,
|
|
550
|
+
{
|
|
551
|
+
value: `__create__${searchQuery}`,
|
|
552
|
+
onSelect: () => onCreate?.(searchQuery.trim()),
|
|
553
|
+
children: [
|
|
554
|
+
/* @__PURE__ */ jsx6(Plus, { className: "mr-2 h-4 w-4" }),
|
|
555
|
+
"Create \u201C",
|
|
556
|
+
searchQuery.trim(),
|
|
557
|
+
"\u201D"
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
"__create__"
|
|
561
|
+
)
|
|
562
|
+
] })
|
|
517
563
|
] })
|
|
518
564
|
] });
|
|
519
565
|
}
|
|
@@ -536,13 +582,16 @@ function FormSelect(props) {
|
|
|
536
582
|
required,
|
|
537
583
|
wrapperClassName,
|
|
538
584
|
compact,
|
|
585
|
+
labelPlacement,
|
|
539
586
|
options,
|
|
540
587
|
value,
|
|
541
588
|
onChange,
|
|
542
589
|
onBlur,
|
|
543
590
|
className,
|
|
544
591
|
id,
|
|
545
|
-
autoFocus
|
|
592
|
+
autoFocus,
|
|
593
|
+
allowCreate,
|
|
594
|
+
onCreate
|
|
546
595
|
} = props;
|
|
547
596
|
const {
|
|
548
597
|
placeholder,
|
|
@@ -560,9 +609,10 @@ function FormSelect(props) {
|
|
|
560
609
|
searchQuery,
|
|
561
610
|
setSearchQuery,
|
|
562
611
|
handleSelect,
|
|
612
|
+
handleCreate,
|
|
563
613
|
handleClear,
|
|
564
614
|
handleOpenChange
|
|
565
|
-
} = useSelectState(value, onChange, onBlur);
|
|
615
|
+
} = useSelectState(value, onChange, onBlur, onCreate, clearable);
|
|
566
616
|
const selectedOption = options.find((opt) => opt.value === value);
|
|
567
617
|
const isDisabled = disabled || readOnly || loading;
|
|
568
618
|
return /* @__PURE__ */ jsx6(
|
|
@@ -574,6 +624,7 @@ function FormSelect(props) {
|
|
|
574
624
|
error,
|
|
575
625
|
required,
|
|
576
626
|
compact,
|
|
627
|
+
labelPlacement,
|
|
577
628
|
className: wrapperClassName,
|
|
578
629
|
children: /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
579
630
|
/* @__PURE__ */ jsx6(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
|
|
@@ -627,7 +678,9 @@ function FormSelect(props) {
|
|
|
627
678
|
emptyMessage,
|
|
628
679
|
options,
|
|
629
680
|
value,
|
|
630
|
-
onSelect: handleSelect
|
|
681
|
+
onSelect: handleSelect,
|
|
682
|
+
allowCreate,
|
|
683
|
+
onCreate: handleCreate
|
|
631
684
|
}
|
|
632
685
|
)
|
|
633
686
|
}
|
|
@@ -661,13 +714,22 @@ function RemovableBadge({
|
|
|
661
714
|
}) {
|
|
662
715
|
return /* @__PURE__ */ jsxs6(Badge, { variant: "secondary", badgeColor, className: "mr-1", children: [
|
|
663
716
|
option.label,
|
|
664
|
-
!isDisabled &&
|
|
665
|
-
|
|
717
|
+
!isDisabled && // Rendered as a role="button" span, not a <button>: this badge lives
|
|
718
|
+
// inside the combobox trigger <button>, and a button cannot be nested
|
|
719
|
+
// in a button (invalid HTML / hydration error). The span keeps click +
|
|
720
|
+
// Enter/Space activation and a focus ring.
|
|
721
|
+
/* @__PURE__ */ jsx7(
|
|
722
|
+
"span",
|
|
666
723
|
{
|
|
667
|
-
|
|
668
|
-
|
|
724
|
+
role: "button",
|
|
725
|
+
tabIndex: 0,
|
|
726
|
+
"aria-label": `Remove ${option.label}`,
|
|
727
|
+
className: "ml-1 inline-flex cursor-pointer ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
669
728
|
onKeyDown: (e) => {
|
|
670
|
-
if (e.key === "Enter"
|
|
729
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
730
|
+
e.preventDefault();
|
|
731
|
+
onRemove(option.value, e);
|
|
732
|
+
}
|
|
671
733
|
},
|
|
672
734
|
onMouseDown: (e) => {
|
|
673
735
|
e.preventDefault();
|
|
@@ -852,6 +914,7 @@ function FormMultiSelect(props) {
|
|
|
852
914
|
error,
|
|
853
915
|
required,
|
|
854
916
|
wrapperClassName,
|
|
917
|
+
labelPlacement,
|
|
855
918
|
options,
|
|
856
919
|
onChange,
|
|
857
920
|
onBlur,
|
|
@@ -897,6 +960,7 @@ function FormMultiSelect(props) {
|
|
|
897
960
|
description,
|
|
898
961
|
error,
|
|
899
962
|
required,
|
|
963
|
+
labelPlacement,
|
|
900
964
|
className: wrapperClassName,
|
|
901
965
|
children: [
|
|
902
966
|
/* @__PURE__ */ jsxs6(Popover2, { open, onOpenChange: handleOpenChange, children: [
|
|
@@ -1063,6 +1127,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1063
1127
|
description,
|
|
1064
1128
|
error,
|
|
1065
1129
|
wrapperClassName,
|
|
1130
|
+
labelPlacement,
|
|
1066
1131
|
id,
|
|
1067
1132
|
name,
|
|
1068
1133
|
value = [],
|
|
@@ -1089,6 +1154,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1089
1154
|
description,
|
|
1090
1155
|
error,
|
|
1091
1156
|
required,
|
|
1157
|
+
labelPlacement,
|
|
1092
1158
|
className: wrapperClassName,
|
|
1093
1159
|
children: /* @__PURE__ */ jsxs7(
|
|
1094
1160
|
"div",
|
|
@@ -1292,6 +1358,7 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1292
1358
|
readOnly,
|
|
1293
1359
|
autoFocus,
|
|
1294
1360
|
wrapperClassName,
|
|
1361
|
+
labelPlacement,
|
|
1295
1362
|
className,
|
|
1296
1363
|
name
|
|
1297
1364
|
} = allProps;
|
|
@@ -1361,6 +1428,7 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1361
1428
|
description,
|
|
1362
1429
|
error: resolvedError,
|
|
1363
1430
|
required,
|
|
1431
|
+
labelPlacement,
|
|
1364
1432
|
className: wrapperClassName,
|
|
1365
1433
|
children: /* @__PURE__ */ jsxs8(
|
|
1366
1434
|
InputGroup,
|
|
@@ -1465,9 +1533,13 @@ function ExtraPropertiesCard({ properties, schema }) {
|
|
|
1465
1533
|
var ExtraPropertiesCard_default = ExtraPropertiesCard;
|
|
1466
1534
|
|
|
1467
1535
|
// src/JsonSchemaFormRenderer.tsx
|
|
1468
|
-
import
|
|
1536
|
+
import React13, { useCallback as useCallback8, useMemo as useMemo8 } from "react";
|
|
1469
1537
|
import { SimpleStack } from "@petrarca/sonnet-ui";
|
|
1470
|
-
import {
|
|
1538
|
+
import { AlertCircle as AlertCircle2 } from "lucide-react";
|
|
1539
|
+
import {
|
|
1540
|
+
WIDTH_TOKEN_REMS
|
|
1541
|
+
} from "@petrarca/sonnet-core/schema";
|
|
1542
|
+
import { isFieldRequired, evaluateUiRule } from "@petrarca/sonnet-core/schema";
|
|
1471
1543
|
import { FORM_ITEM_ID_FIELD as FORM_ITEM_ID_FIELD2 } from "@petrarca/sonnet-core";
|
|
1472
1544
|
|
|
1473
1545
|
// src/widgets/types.ts
|
|
@@ -1510,6 +1582,7 @@ function TextInputWidget({
|
|
|
1510
1582
|
onChange: (event) => onChange(event.currentTarget.value),
|
|
1511
1583
|
onBlur: () => onBlur?.(id, value),
|
|
1512
1584
|
label,
|
|
1585
|
+
labelPlacement: options?.labelPlacement,
|
|
1513
1586
|
description,
|
|
1514
1587
|
placeholder,
|
|
1515
1588
|
required,
|
|
@@ -1557,6 +1630,7 @@ function TextareaWidget({
|
|
|
1557
1630
|
onChange: (event) => onChange(event.currentTarget.value),
|
|
1558
1631
|
onBlur: () => onBlur?.(id, value),
|
|
1559
1632
|
label,
|
|
1633
|
+
labelPlacement: options?.labelPlacement,
|
|
1560
1634
|
description,
|
|
1561
1635
|
placeholder,
|
|
1562
1636
|
required,
|
|
@@ -1604,6 +1678,7 @@ function NumberWidget({
|
|
|
1604
1678
|
onChange: (val) => onChange(val),
|
|
1605
1679
|
onBlur: () => onBlur?.(id, value),
|
|
1606
1680
|
label,
|
|
1681
|
+
labelPlacement: options.labelPlacement,
|
|
1607
1682
|
description,
|
|
1608
1683
|
placeholder,
|
|
1609
1684
|
required,
|
|
@@ -1720,6 +1795,7 @@ function SelectWidget(props) {
|
|
|
1720
1795
|
const enumNames = widgetOptions.enumNames || {};
|
|
1721
1796
|
const clearable = widgetOptions.clearable !== false && !required;
|
|
1722
1797
|
const isMultiple = widgetOptions.multiple ?? propertySchema.type === "array";
|
|
1798
|
+
const creatable = !!widgetOptions.creatable;
|
|
1723
1799
|
const selectOptions = buildSelectOptions(propertySchema, enumNames);
|
|
1724
1800
|
const error = firstError(rawErrors);
|
|
1725
1801
|
const compact = widgetOptions?.compact;
|
|
@@ -1735,6 +1811,7 @@ function SelectWidget(props) {
|
|
|
1735
1811
|
value: normalizeMultiValue(value),
|
|
1736
1812
|
onChange: (newValue) => onChange(newValue || []),
|
|
1737
1813
|
label,
|
|
1814
|
+
labelPlacement: widgetOptions.labelPlacement,
|
|
1738
1815
|
description,
|
|
1739
1816
|
placeholder,
|
|
1740
1817
|
required,
|
|
@@ -1758,6 +1835,7 @@ function SelectWidget(props) {
|
|
|
1758
1835
|
value: normalizeSingleValue(value),
|
|
1759
1836
|
onChange: (val) => onChange(val || ""),
|
|
1760
1837
|
label,
|
|
1838
|
+
labelPlacement: widgetOptions.labelPlacement,
|
|
1761
1839
|
description,
|
|
1762
1840
|
placeholder,
|
|
1763
1841
|
required,
|
|
@@ -1768,7 +1846,9 @@ function SelectWidget(props) {
|
|
|
1768
1846
|
autoFocus: autofocus,
|
|
1769
1847
|
onBlur: handleBlur,
|
|
1770
1848
|
className: triggerClassName,
|
|
1771
|
-
compact
|
|
1849
|
+
compact,
|
|
1850
|
+
allowCreate: creatable,
|
|
1851
|
+
onCreate: (label2) => onChange(label2)
|
|
1772
1852
|
}
|
|
1773
1853
|
);
|
|
1774
1854
|
}
|
|
@@ -1799,6 +1879,7 @@ function TagsInputWidget({
|
|
|
1799
1879
|
value: tags,
|
|
1800
1880
|
onChange: (newTags) => onChange(newTags),
|
|
1801
1881
|
label,
|
|
1882
|
+
labelPlacement: options?.labelPlacement,
|
|
1802
1883
|
description,
|
|
1803
1884
|
placeholder: placeholder ?? "Type and press Enter",
|
|
1804
1885
|
required,
|
|
@@ -1880,11 +1961,14 @@ function resolveMinChars(options) {
|
|
|
1880
1961
|
}
|
|
1881
1962
|
function buildRestConfig(options) {
|
|
1882
1963
|
if (!("endpoint" in options) || !options.endpoint) return null;
|
|
1964
|
+
const rawExtra = options.extraParams;
|
|
1965
|
+
const extraParams = rawExtra && typeof rawExtra === "object" ? rawExtra : void 0;
|
|
1883
1966
|
return {
|
|
1884
1967
|
endpoint: String(options.endpoint),
|
|
1885
1968
|
valueKey: String(options.valueKey ?? "id"),
|
|
1886
1969
|
labelKey: String(options.labelKey ?? "name"),
|
|
1887
|
-
searchKey: options.searchKey ? String(options.searchKey) : void 0
|
|
1970
|
+
searchKey: options.searchKey ? String(options.searchKey) : void 0,
|
|
1971
|
+
extraParams
|
|
1888
1972
|
};
|
|
1889
1973
|
}
|
|
1890
1974
|
function buildGraphqlConfig(options) {
|
|
@@ -1948,12 +2032,13 @@ function EntitySelectWidget({
|
|
|
1948
2032
|
mode: selectMode,
|
|
1949
2033
|
minChars: selectMinChars,
|
|
1950
2034
|
value: typeof value === "string" ? value : null,
|
|
1951
|
-
onChange: (v) => onChange(v ??
|
|
2035
|
+
onChange: (v) => onChange(v ?? null),
|
|
1952
2036
|
onBlur: () => onBlur?.(id, value),
|
|
1953
2037
|
label: label || false,
|
|
1954
2038
|
description,
|
|
1955
2039
|
placeholder,
|
|
1956
2040
|
required,
|
|
2041
|
+
clearable: !required,
|
|
1957
2042
|
disabled: disabled || readonly,
|
|
1958
2043
|
error: firstError(rawErrors)
|
|
1959
2044
|
}
|
|
@@ -2166,7 +2251,7 @@ function ObjectWidget({
|
|
|
2166
2251
|
|
|
2167
2252
|
// src/widgets/ArrayWidget.tsx
|
|
2168
2253
|
import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
|
|
2169
|
-
import { Plus } from "lucide-react";
|
|
2254
|
+
import { Plus as Plus2 } from "lucide-react";
|
|
2170
2255
|
import { Button as Button5 } from "@petrarca/sonnet-ui";
|
|
2171
2256
|
import { applySchemaDefaults } from "@petrarca/sonnet-core/schema";
|
|
2172
2257
|
import { FORM_ITEM_ID_FIELD, generateId } from "@petrarca/sonnet-core";
|
|
@@ -2182,53 +2267,61 @@ function DefaultArrayItemTemplate({
|
|
|
2182
2267
|
canRemove,
|
|
2183
2268
|
orderable,
|
|
2184
2269
|
disabled,
|
|
2270
|
+
fid,
|
|
2185
2271
|
onRemove,
|
|
2186
2272
|
onMoveUp,
|
|
2187
2273
|
onMoveDown
|
|
2188
2274
|
}) {
|
|
2189
2275
|
const showControls = canRemove || orderable;
|
|
2190
|
-
return /* @__PURE__ */ jsxs14(
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2276
|
+
return /* @__PURE__ */ jsxs14(
|
|
2277
|
+
"div",
|
|
2278
|
+
{
|
|
2279
|
+
"data-fid": fid,
|
|
2280
|
+
className: "relative flex gap-2 rounded-md border border-border/50 bg-muted/20 p-3",
|
|
2281
|
+
children: [
|
|
2282
|
+
/* @__PURE__ */ jsx22("div", { className: "flex-1 min-w-0", children }),
|
|
2283
|
+
showControls && /* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1 pt-0.5", children: [
|
|
2284
|
+
orderable && index > 0 && /* @__PURE__ */ jsx22(
|
|
2285
|
+
Button4,
|
|
2286
|
+
{
|
|
2287
|
+
type: "button",
|
|
2288
|
+
variant: "ghost",
|
|
2289
|
+
size: "compact",
|
|
2290
|
+
disabled,
|
|
2291
|
+
onClick: onMoveUp,
|
|
2292
|
+
title: "Move up",
|
|
2293
|
+
children: /* @__PURE__ */ jsx22(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
2294
|
+
}
|
|
2295
|
+
),
|
|
2296
|
+
orderable && index < total - 1 && /* @__PURE__ */ jsx22(
|
|
2297
|
+
Button4,
|
|
2298
|
+
{
|
|
2299
|
+
type: "button",
|
|
2300
|
+
variant: "ghost",
|
|
2301
|
+
size: "compact",
|
|
2302
|
+
disabled,
|
|
2303
|
+
onClick: onMoveDown,
|
|
2304
|
+
title: "Move down",
|
|
2305
|
+
children: /* @__PURE__ */ jsx22(ChevronDown3, { className: "h-3.5 w-3.5" })
|
|
2306
|
+
}
|
|
2307
|
+
),
|
|
2308
|
+
canRemove && /* @__PURE__ */ jsx22(
|
|
2309
|
+
Button4,
|
|
2310
|
+
{
|
|
2311
|
+
type: "button",
|
|
2312
|
+
variant: "ghost",
|
|
2313
|
+
size: "compact",
|
|
2314
|
+
disabled,
|
|
2315
|
+
onClick: onRemove,
|
|
2316
|
+
title: "Remove item",
|
|
2317
|
+
className: "text-muted-foreground hover:text-destructive",
|
|
2318
|
+
children: /* @__PURE__ */ jsx22(Trash2, { className: "h-3.5 w-3.5" })
|
|
2319
|
+
}
|
|
2320
|
+
)
|
|
2321
|
+
] })
|
|
2322
|
+
]
|
|
2323
|
+
}
|
|
2324
|
+
);
|
|
2232
2325
|
}
|
|
2233
2326
|
|
|
2234
2327
|
// src/templates/gridUtils.ts
|
|
@@ -2494,6 +2587,7 @@ function ArrayWidgetInner({
|
|
|
2494
2587
|
disabled,
|
|
2495
2588
|
layout,
|
|
2496
2589
|
fieldCount,
|
|
2590
|
+
fid: item[FORM_ITEM_ID_FIELD],
|
|
2497
2591
|
onRemove: () => onRemove(index),
|
|
2498
2592
|
onMoveUp: index > 0 ? () => onMove(index, index - 1) : void 0,
|
|
2499
2593
|
onMoveDown: index < items.length - 1 ? () => onMove(index, index + 1) : void 0,
|
|
@@ -2528,7 +2622,7 @@ function ArrayWidgetInner({
|
|
|
2528
2622
|
disabled,
|
|
2529
2623
|
className: "gap-1.5",
|
|
2530
2624
|
children: [
|
|
2531
|
-
/* @__PURE__ */ jsx24(
|
|
2625
|
+
/* @__PURE__ */ jsx24(Plus2, { className: "h-3.5 w-3.5" }),
|
|
2532
2626
|
display.addLabel
|
|
2533
2627
|
]
|
|
2534
2628
|
}
|
|
@@ -2542,6 +2636,13 @@ import { jsx as jsx25 } from "react/jsx-runtime";
|
|
|
2542
2636
|
function isUnitOption(u) {
|
|
2543
2637
|
return typeof u === "object" && u !== null && typeof u.value === "string" && typeof u.label === "string";
|
|
2544
2638
|
}
|
|
2639
|
+
function parseQuantityOptions(options) {
|
|
2640
|
+
const units = Array.isArray(options.units) ? options.units.filter(isUnitOption) : [];
|
|
2641
|
+
const precision = typeof options.precision === "number" ? options.precision : void 0;
|
|
2642
|
+
const step = typeof options.step === "number" ? options.step : void 0;
|
|
2643
|
+
const unitPosition = options.unitPosition === "prefix" || options.unitPosition === "suffix" ? options.unitPosition : void 0;
|
|
2644
|
+
return { units, precision, step, unitPosition };
|
|
2645
|
+
}
|
|
2545
2646
|
function QuantityWidget({
|
|
2546
2647
|
id,
|
|
2547
2648
|
name,
|
|
@@ -2560,10 +2661,12 @@ function QuantityWidget({
|
|
|
2560
2661
|
options = {}
|
|
2561
2662
|
}) {
|
|
2562
2663
|
const error = firstError(rawErrors);
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2664
|
+
const {
|
|
2665
|
+
units,
|
|
2666
|
+
precision,
|
|
2667
|
+
step,
|
|
2668
|
+
unitPosition: unitPositionOpt
|
|
2669
|
+
} = parseQuantityOptions(options);
|
|
2567
2670
|
const quantityValue = value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2568
2671
|
const valueRef = useRef4(value);
|
|
2569
2672
|
valueRef.current = value;
|
|
@@ -2585,6 +2688,7 @@ function QuantityWidget({
|
|
|
2585
2688
|
min: propertySchema.minimum,
|
|
2586
2689
|
max: propertySchema.maximum,
|
|
2587
2690
|
label,
|
|
2691
|
+
labelPlacement: options?.labelPlacement,
|
|
2588
2692
|
description,
|
|
2589
2693
|
error,
|
|
2590
2694
|
placeholder,
|
|
@@ -2596,9 +2700,309 @@ function QuantityWidget({
|
|
|
2596
2700
|
);
|
|
2597
2701
|
}
|
|
2598
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
|
+
|
|
2599
3002
|
// src/widgets/widgetResolver.ts
|
|
2600
3003
|
import {
|
|
2601
3004
|
isNullableBoolean as isNullableBoolean2,
|
|
3005
|
+
isOneOf,
|
|
2602
3006
|
extractEnumValues as extractEnumValues2,
|
|
2603
3007
|
getFieldType
|
|
2604
3008
|
} from "@petrarca/sonnet-core/schema";
|
|
@@ -2610,8 +3014,11 @@ var DEFAULT_WIDGET_MAP = {
|
|
|
2610
3014
|
// Uses email input type
|
|
2611
3015
|
uri: "TextInputWidget",
|
|
2612
3016
|
// Uses url input type
|
|
2613
|
-
url: "TextInputWidget"
|
|
3017
|
+
url: "TextInputWidget",
|
|
2614
3018
|
// Uses url input type
|
|
3019
|
+
date: "DateWidget",
|
|
3020
|
+
"date-time": "DateTimeWidget",
|
|
3021
|
+
time: "TimeWidget"
|
|
2615
3022
|
},
|
|
2616
3023
|
number: {
|
|
2617
3024
|
default: "NumberWidget"
|
|
@@ -2622,6 +3029,9 @@ var DEFAULT_WIDGET_MAP = {
|
|
|
2622
3029
|
},
|
|
2623
3030
|
boolean: {
|
|
2624
3031
|
default: "CheckboxWidget"
|
|
3032
|
+
},
|
|
3033
|
+
null: {
|
|
3034
|
+
default: "DisplayWidget"
|
|
2625
3035
|
}
|
|
2626
3036
|
};
|
|
2627
3037
|
function resolveWidgetByName(widgetName, propertySchema, registry) {
|
|
@@ -2671,6 +3081,7 @@ function registryLookup(registry, widgetName) {
|
|
|
2671
3081
|
return registry[widgetName] || registry["TextInputWidget"];
|
|
2672
3082
|
}
|
|
2673
3083
|
function resolveFromSchemaStructure(schema, registry) {
|
|
3084
|
+
if (isOneOf(schema)) return registryLookup(registry, "OneOfWidget");
|
|
2674
3085
|
if (isNullableBoolean2(schema))
|
|
2675
3086
|
return registryLookup(registry, "CheckboxWidget");
|
|
2676
3087
|
if (extractEnumValues2(schema).length > 0)
|
|
@@ -2714,6 +3125,11 @@ var DEFAULT_WIDGETS = {
|
|
|
2714
3125
|
ObjectWidget,
|
|
2715
3126
|
ArrayWidget,
|
|
2716
3127
|
QuantityWidget,
|
|
3128
|
+
DateWidget,
|
|
3129
|
+
TimeWidget,
|
|
3130
|
+
DateTimeWidget,
|
|
3131
|
+
DisplayWidget,
|
|
3132
|
+
OneOfWidget,
|
|
2717
3133
|
// Lowercase aliases for x-ui-widget convenience
|
|
2718
3134
|
text: TextInputWidget,
|
|
2719
3135
|
textarea: TextareaWidget,
|
|
@@ -2725,13 +3141,18 @@ var DEFAULT_WIDGETS = {
|
|
|
2725
3141
|
"entity-select": EntitySelectWidget,
|
|
2726
3142
|
object: ObjectWidget,
|
|
2727
3143
|
array: ArrayWidget,
|
|
2728
|
-
quantity: QuantityWidget
|
|
3144
|
+
quantity: QuantityWidget,
|
|
3145
|
+
date: DateWidget,
|
|
3146
|
+
time: TimeWidget,
|
|
3147
|
+
"date-time": DateTimeWidget,
|
|
3148
|
+
display: DisplayWidget,
|
|
3149
|
+
"one-of": OneOfWidget
|
|
2729
3150
|
};
|
|
2730
3151
|
|
|
2731
3152
|
// src/templates/HorizontalArrayItemTemplate.tsx
|
|
2732
3153
|
import { Trash2 as Trash22, ChevronUp as ChevronUp2, ChevronDown as ChevronDown4 } from "lucide-react";
|
|
2733
3154
|
import { Button as Button6 } from "@petrarca/sonnet-ui";
|
|
2734
|
-
import { jsx as
|
|
3155
|
+
import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2735
3156
|
function HorizontalArrayItemTemplate({
|
|
2736
3157
|
children,
|
|
2737
3158
|
index,
|
|
@@ -2741,6 +3162,7 @@ function HorizontalArrayItemTemplate({
|
|
|
2741
3162
|
disabled,
|
|
2742
3163
|
layout,
|
|
2743
3164
|
fieldCount,
|
|
3165
|
+
fid,
|
|
2744
3166
|
onRemove,
|
|
2745
3167
|
onMoveUp,
|
|
2746
3168
|
onMoveDown
|
|
@@ -2754,9 +3176,10 @@ function HorizontalArrayItemTemplate({
|
|
|
2754
3176
|
);
|
|
2755
3177
|
const gap = resolvedLayout.gap ?? 8;
|
|
2756
3178
|
const alignItems = resolvedLayout.compact ? "center" : "start";
|
|
2757
|
-
return /* @__PURE__ */
|
|
3179
|
+
return /* @__PURE__ */ jsxs19(
|
|
2758
3180
|
"div",
|
|
2759
3181
|
{
|
|
3182
|
+
"data-fid": fid,
|
|
2760
3183
|
style: {
|
|
2761
3184
|
display: "grid",
|
|
2762
3185
|
gridTemplateColumns: template,
|
|
@@ -2765,8 +3188,8 @@ function HorizontalArrayItemTemplate({
|
|
|
2765
3188
|
},
|
|
2766
3189
|
children: [
|
|
2767
3190
|
children,
|
|
2768
|
-
orderable && /* @__PURE__ */
|
|
2769
|
-
/* @__PURE__ */
|
|
3191
|
+
orderable && /* @__PURE__ */ jsxs19("div", { className: "flex flex-col items-center justify-center", children: [
|
|
3192
|
+
/* @__PURE__ */ jsx31(
|
|
2770
3193
|
Button6,
|
|
2771
3194
|
{
|
|
2772
3195
|
type: "button",
|
|
@@ -2776,10 +3199,10 @@ function HorizontalArrayItemTemplate({
|
|
|
2776
3199
|
onClick: onMoveUp,
|
|
2777
3200
|
title: "Move up",
|
|
2778
3201
|
className: "h-4 w-8 p-0",
|
|
2779
|
-
children: /* @__PURE__ */
|
|
3202
|
+
children: /* @__PURE__ */ jsx31(ChevronUp2, { className: "h-3 w-3" })
|
|
2780
3203
|
}
|
|
2781
3204
|
),
|
|
2782
|
-
/* @__PURE__ */
|
|
3205
|
+
/* @__PURE__ */ jsx31(
|
|
2783
3206
|
Button6,
|
|
2784
3207
|
{
|
|
2785
3208
|
type: "button",
|
|
@@ -2789,11 +3212,11 @@ function HorizontalArrayItemTemplate({
|
|
|
2789
3212
|
onClick: onMoveDown,
|
|
2790
3213
|
title: "Move down",
|
|
2791
3214
|
className: "h-4 w-8 p-0",
|
|
2792
|
-
children: /* @__PURE__ */
|
|
3215
|
+
children: /* @__PURE__ */ jsx31(ChevronDown4, { className: "h-3 w-3" })
|
|
2793
3216
|
}
|
|
2794
3217
|
)
|
|
2795
3218
|
] }),
|
|
2796
|
-
canRemove && /* @__PURE__ */
|
|
3219
|
+
canRemove && /* @__PURE__ */ jsx31(
|
|
2797
3220
|
Button6,
|
|
2798
3221
|
{
|
|
2799
3222
|
type: "button",
|
|
@@ -2803,7 +3226,7 @@ function HorizontalArrayItemTemplate({
|
|
|
2803
3226
|
onClick: onRemove,
|
|
2804
3227
|
title: "Remove item",
|
|
2805
3228
|
className: "text-muted-foreground hover:text-destructive h-8 w-8 p-0",
|
|
2806
|
-
children: /* @__PURE__ */
|
|
3229
|
+
children: /* @__PURE__ */ jsx31(Trash22, { className: "h-3.5 w-3.5" })
|
|
2807
3230
|
}
|
|
2808
3231
|
)
|
|
2809
3232
|
]
|
|
@@ -2811,21 +3234,132 @@ function HorizontalArrayItemTemplate({
|
|
|
2811
3234
|
);
|
|
2812
3235
|
}
|
|
2813
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
|
+
|
|
2814
3339
|
// src/templates/index.ts
|
|
2815
3340
|
var DEFAULT_TEMPLATES = {
|
|
2816
3341
|
ArrayItemTemplate: DefaultArrayItemTemplate,
|
|
2817
3342
|
HorizontalArrayItemTemplate,
|
|
2818
3343
|
ArrayHeaderTemplate: DefaultArrayHeaderTemplate,
|
|
2819
|
-
ObjectContainerTemplate: DefaultObjectContainerTemplate
|
|
3344
|
+
ObjectContainerTemplate: DefaultObjectContainerTemplate,
|
|
3345
|
+
TabsGroupTemplate,
|
|
3346
|
+
StepperGroupTemplate,
|
|
3347
|
+
SectionsGroupTemplate
|
|
2820
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
|
+
}
|
|
2821
3354
|
|
|
2822
3355
|
// src/FormFieldRenderer.tsx
|
|
3356
|
+
import { warnLog } from "@petrarca/sonnet-core";
|
|
2823
3357
|
import {
|
|
2824
3358
|
getFieldTitle,
|
|
2825
3359
|
getFieldHelpText,
|
|
2826
3360
|
getFieldPlaceholder
|
|
2827
3361
|
} from "@petrarca/sonnet-core/schema";
|
|
2828
|
-
import { jsx as
|
|
3362
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
2829
3363
|
function mergeFieldOptions(globalOptions, property, uiSchema) {
|
|
2830
3364
|
const xWidgetOptions = property["x-ui-options"] || {};
|
|
2831
3365
|
const fieldOptions = uiSchema?.["x-ui-options"] || {};
|
|
@@ -2837,6 +3371,29 @@ function resolveEffectiveLabel(label, uiTitle, globalLabelOption) {
|
|
|
2837
3371
|
if (uiTitle === false) return "";
|
|
2838
3372
|
return label;
|
|
2839
3373
|
}
|
|
3374
|
+
var FIELD_WIDTH_CLASS = {
|
|
3375
|
+
sm: "w-32",
|
|
3376
|
+
md: "w-48",
|
|
3377
|
+
lg: "w-64",
|
|
3378
|
+
full: "w-full"
|
|
3379
|
+
};
|
|
3380
|
+
function applyFieldWidth(widget, uiSchema, mergedOptions) {
|
|
3381
|
+
const candidate = uiSchema?.["x-ui-width"] ?? mergedOptions.width;
|
|
3382
|
+
if (candidate && !(candidate in FIELD_WIDTH_CLASS)) {
|
|
3383
|
+
warnLog(
|
|
3384
|
+
`[FormFieldRenderer] Unknown x-ui-width value: "${candidate}". Falling back to "full".`
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
const width = candidate && candidate in FIELD_WIDTH_CLASS ? candidate : "full";
|
|
3388
|
+
if (width === "full") return widget;
|
|
3389
|
+
return /* @__PURE__ */ jsx36("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
|
|
3390
|
+
}
|
|
3391
|
+
function resolvePresentationText(property, uiSchema) {
|
|
3392
|
+
return {
|
|
3393
|
+
description: uiSchema?.["x-ui-description"] ?? getFieldHelpText(property),
|
|
3394
|
+
placeholder: uiSchema?.["x-ui-placeholder"] ?? getFieldPlaceholder(property)
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
2840
3397
|
function resolveFieldFlags(fieldOptions, globalOptions, disabled, readOnly) {
|
|
2841
3398
|
return {
|
|
2842
3399
|
disabled: fieldOptions.disabled ?? globalOptions.disabled ?? disabled,
|
|
@@ -2856,11 +3413,14 @@ function FormFieldRenderer({
|
|
|
2856
3413
|
uiSchema,
|
|
2857
3414
|
registry,
|
|
2858
3415
|
globalOptions = {},
|
|
2859
|
-
formData
|
|
3416
|
+
formData,
|
|
3417
|
+
idPrefix = "field"
|
|
2860
3418
|
}) {
|
|
2861
3419
|
const label = getFieldTitle(fieldName, property);
|
|
2862
|
-
const description
|
|
2863
|
-
|
|
3420
|
+
const { description, placeholder } = resolvePresentationText(
|
|
3421
|
+
property,
|
|
3422
|
+
uiSchema
|
|
3423
|
+
);
|
|
2864
3424
|
const Widget = resolveWidget(property, uiSchema, registry);
|
|
2865
3425
|
const mergedOptions = mergeFieldOptions(globalOptions, property, uiSchema);
|
|
2866
3426
|
if (mergedOptions.hidden) return null;
|
|
@@ -2876,10 +3436,10 @@ function FormFieldRenderer({
|
|
|
2876
3436
|
uiSchema?.["x-ui-title"],
|
|
2877
3437
|
globalOptions.label
|
|
2878
3438
|
);
|
|
2879
|
-
|
|
3439
|
+
const widget = /* @__PURE__ */ jsx36(
|
|
2880
3440
|
Widget,
|
|
2881
3441
|
{
|
|
2882
|
-
id:
|
|
3442
|
+
id: `${idPrefix}-${fieldName}`,
|
|
2883
3443
|
name: fieldName,
|
|
2884
3444
|
value,
|
|
2885
3445
|
onChange,
|
|
@@ -2898,10 +3458,11 @@ function FormFieldRenderer({
|
|
|
2898
3458
|
formData
|
|
2899
3459
|
}
|
|
2900
3460
|
);
|
|
3461
|
+
return applyFieldWidth(widget, uiSchema, mergedOptions);
|
|
2901
3462
|
}
|
|
2902
3463
|
|
|
2903
3464
|
// src/hooks/useFormEngine.ts
|
|
2904
|
-
import { useState as
|
|
3465
|
+
import { useState as useState9, useEffect as useEffect3, useMemo as useMemo7, useCallback as useCallback7, useRef as useRef5 } from "react";
|
|
2905
3466
|
import { isEqual } from "@petrarca/sonnet-core";
|
|
2906
3467
|
import {
|
|
2907
3468
|
getSchemaProperties,
|
|
@@ -2968,14 +3529,19 @@ function useFormEngine({
|
|
|
2968
3529
|
readOnly = false,
|
|
2969
3530
|
showActions = false,
|
|
2970
3531
|
deferChanges = false,
|
|
2971
|
-
deferStateUpdates = false
|
|
3532
|
+
deferStateUpdates = false,
|
|
3533
|
+
allowPristineSave = false,
|
|
3534
|
+
refResolver
|
|
2972
3535
|
}) {
|
|
2973
|
-
const refResolvedSchema =
|
|
2974
|
-
|
|
3536
|
+
const refResolvedSchema = useMemo7(
|
|
3537
|
+
() => resolveRefs(schema, refResolver),
|
|
3538
|
+
[schema, refResolver]
|
|
3539
|
+
);
|
|
3540
|
+
const [originalData, setOriginalData] = useState9(
|
|
2975
3541
|
() => applySchemaDefaults2(data, refResolvedSchema)
|
|
2976
3542
|
);
|
|
2977
|
-
const [originalDataInitialized, setOriginalDataInitialized] =
|
|
2978
|
-
const [nonSchemaData, setNonSchemaData] =
|
|
3543
|
+
const [originalDataInitialized, setOriginalDataInitialized] = useState9(false);
|
|
3544
|
+
const [nonSchemaData, setNonSchemaData] = useState9(
|
|
2979
3545
|
() => {
|
|
2980
3546
|
const props = getSchemaProperties(refResolvedSchema);
|
|
2981
3547
|
const names = Object.keys(props);
|
|
@@ -2984,26 +3550,23 @@ function useFormEngine({
|
|
|
2984
3550
|
);
|
|
2985
3551
|
}
|
|
2986
3552
|
);
|
|
2987
|
-
const [formData, setFormData] =
|
|
3553
|
+
const [formData, setFormData] = useState9(
|
|
2988
3554
|
() => applySchemaDefaults2(data, refResolvedSchema)
|
|
2989
3555
|
);
|
|
2990
|
-
const
|
|
2991
|
-
|
|
3556
|
+
const formDataRef = useRef5(formData);
|
|
3557
|
+
formDataRef.current = formData;
|
|
3558
|
+
const [pendingChanges, setPendingChanges] = useState9({});
|
|
3559
|
+
const resolvedSchema = useMemo7(
|
|
2992
3560
|
() => resolveSchema(refResolvedSchema, formData),
|
|
2993
3561
|
[refResolvedSchema, formData]
|
|
2994
3562
|
);
|
|
2995
|
-
const orderedProperties =
|
|
3563
|
+
const orderedProperties = useMemo7(() => {
|
|
2996
3564
|
const properties2 = getSchemaProperties(resolvedSchema);
|
|
2997
3565
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
2998
3566
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
2999
3567
|
return orderProperties(properties2, orderArray);
|
|
3000
3568
|
}, [resolvedSchema, uiSchema]);
|
|
3001
|
-
const [
|
|
3002
|
-
const initResolved = resolveSchema(refResolvedSchema, data);
|
|
3003
|
-
const validation = validateFormData(initResolved, data);
|
|
3004
|
-
return validation.isValid;
|
|
3005
|
-
});
|
|
3006
|
-
const [initialDefaultsSent, setInitialDefaultsSent] = useState7(false);
|
|
3569
|
+
const [initialDefaultsSent, setInitialDefaultsSent] = useState9(false);
|
|
3007
3570
|
const properties = getSchemaProperties(resolvedSchema);
|
|
3008
3571
|
useEffect3(() => {
|
|
3009
3572
|
if (!initialDefaultsSent) {
|
|
@@ -3050,8 +3613,6 @@ function useFormEngine({
|
|
|
3050
3613
|
setOriginalDataInitialized(true);
|
|
3051
3614
|
}
|
|
3052
3615
|
}
|
|
3053
|
-
const validation = validateFormData(resolvedSchema, newData);
|
|
3054
|
-
setIsValid(validation.isValid);
|
|
3055
3616
|
}, [
|
|
3056
3617
|
data,
|
|
3057
3618
|
refResolvedSchema,
|
|
@@ -3060,21 +3621,31 @@ function useFormEngine({
|
|
|
3060
3621
|
originalData,
|
|
3061
3622
|
originalDataInitialized
|
|
3062
3623
|
]);
|
|
3063
|
-
const hasChanges =
|
|
3624
|
+
const hasChanges = useMemo7(
|
|
3064
3625
|
() => !isEqual(formData, originalData),
|
|
3065
3626
|
[formData, originalData]
|
|
3066
3627
|
);
|
|
3067
|
-
const displayData =
|
|
3628
|
+
const displayData = useMemo7(() => {
|
|
3068
3629
|
if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
|
|
3069
3630
|
return { ...formData, ...pendingChanges };
|
|
3070
3631
|
}
|
|
3071
3632
|
return formData;
|
|
3072
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]);
|
|
3073
3644
|
const handleFieldChange = useCallback7(
|
|
3074
3645
|
(fieldName, value) => {
|
|
3075
3646
|
if (readOnly) return;
|
|
3076
3647
|
const immediate = isImmediateField(fieldName, properties);
|
|
3077
|
-
if (
|
|
3648
|
+
if (deferStateUpdates && !immediate) {
|
|
3078
3649
|
setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
|
|
3079
3650
|
return;
|
|
3080
3651
|
}
|
|
@@ -3084,11 +3655,6 @@ function useFormEngine({
|
|
|
3084
3655
|
const shouldCallOnChange = !deferChanges || immediate;
|
|
3085
3656
|
if (shouldCallOnChange && onChange) onChange(newData);
|
|
3086
3657
|
}
|
|
3087
|
-
const validation = validateFormData(resolvedSchema, newData);
|
|
3088
|
-
setIsValid(validation.isValid);
|
|
3089
|
-
if (onValidationChange) {
|
|
3090
|
-
onValidationChange(validation.errors, validation.isValid);
|
|
3091
|
-
}
|
|
3092
3658
|
},
|
|
3093
3659
|
[
|
|
3094
3660
|
readOnly,
|
|
@@ -3097,14 +3663,12 @@ function useFormEngine({
|
|
|
3097
3663
|
deferStateUpdates,
|
|
3098
3664
|
showActions,
|
|
3099
3665
|
onChange,
|
|
3100
|
-
resolvedSchema,
|
|
3101
|
-
onValidationChange,
|
|
3102
3666
|
properties
|
|
3103
3667
|
]
|
|
3104
3668
|
);
|
|
3105
3669
|
const flushPendingField = useCallback7(
|
|
3106
3670
|
(fieldName) => {
|
|
3107
|
-
if (!
|
|
3671
|
+
if (!deferStateUpdates) return;
|
|
3108
3672
|
if (pendingChanges[fieldName] === void 0) return;
|
|
3109
3673
|
const newData = { ...formData, ...pendingChanges };
|
|
3110
3674
|
setFormData(newData);
|
|
@@ -3113,37 +3677,26 @@ function useFormEngine({
|
|
|
3113
3677
|
delete updated[fieldName];
|
|
3114
3678
|
return updated;
|
|
3115
3679
|
});
|
|
3116
|
-
|
|
3117
|
-
setIsValid(validation.isValid);
|
|
3118
|
-
if (onValidationChange) {
|
|
3119
|
-
onValidationChange(validation.errors, validation.isValid);
|
|
3120
|
-
}
|
|
3680
|
+
if (!showActions && onChange) onChange(newData);
|
|
3121
3681
|
},
|
|
3122
|
-
[
|
|
3123
|
-
showActions,
|
|
3124
|
-
deferStateUpdates,
|
|
3125
|
-
pendingChanges,
|
|
3126
|
-
formData,
|
|
3127
|
-
resolvedSchema,
|
|
3128
|
-
onValidationChange
|
|
3129
|
-
]
|
|
3682
|
+
[showActions, deferStateUpdates, pendingChanges, formData, onChange]
|
|
3130
3683
|
);
|
|
3131
3684
|
const handleFieldBlur = useCallback7(
|
|
3132
3685
|
(fieldName) => {
|
|
3133
3686
|
if (readOnly) return;
|
|
3134
3687
|
flushPendingField(fieldName);
|
|
3688
|
+
const latest = formDataRef.current;
|
|
3135
3689
|
if (!showActions && deferChanges && onChange) {
|
|
3136
|
-
onChange(
|
|
3690
|
+
onChange(latest);
|
|
3137
3691
|
}
|
|
3138
3692
|
if (onBlur) {
|
|
3139
3693
|
const hasPending = deferStateUpdates && pendingChanges[fieldName] !== void 0;
|
|
3140
|
-
const dataToPass = hasPending ? { ...
|
|
3694
|
+
const dataToPass = hasPending ? { ...latest, [fieldName]: pendingChanges[fieldName] } : latest;
|
|
3141
3695
|
onBlur(fieldName, dataToPass);
|
|
3142
3696
|
}
|
|
3143
3697
|
},
|
|
3144
3698
|
[
|
|
3145
3699
|
readOnly,
|
|
3146
|
-
formData,
|
|
3147
3700
|
pendingChanges,
|
|
3148
3701
|
deferChanges,
|
|
3149
3702
|
deferStateUpdates,
|
|
@@ -3154,11 +3707,11 @@ function useFormEngine({
|
|
|
3154
3707
|
]
|
|
3155
3708
|
);
|
|
3156
3709
|
const handleSave = useCallback7(() => {
|
|
3157
|
-
if (!hasChanges) return;
|
|
3710
|
+
if (!hasChanges && !allowPristineSave) return;
|
|
3158
3711
|
const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
|
|
3159
3712
|
const completeData = { ...nonSchemaData, ...finalData };
|
|
3160
|
-
const
|
|
3161
|
-
if (
|
|
3713
|
+
const validation2 = validateFormData(resolvedSchema, finalData);
|
|
3714
|
+
if (validation2.isValid && onUpdate) {
|
|
3162
3715
|
const changed = {};
|
|
3163
3716
|
for (const key of Object.keys(finalData)) {
|
|
3164
3717
|
if (!isEqual(finalData[key], originalData[key])) {
|
|
@@ -3171,6 +3724,7 @@ function useFormEngine({
|
|
|
3171
3724
|
}
|
|
3172
3725
|
}, [
|
|
3173
3726
|
hasChanges,
|
|
3727
|
+
allowPristineSave,
|
|
3174
3728
|
resolvedSchema,
|
|
3175
3729
|
formData,
|
|
3176
3730
|
originalData,
|
|
@@ -3191,6 +3745,7 @@ function useFormEngine({
|
|
|
3191
3745
|
orderedProperties,
|
|
3192
3746
|
hasChanges,
|
|
3193
3747
|
isValid,
|
|
3748
|
+
fieldErrors,
|
|
3194
3749
|
handleFieldChange,
|
|
3195
3750
|
handleFieldBlur,
|
|
3196
3751
|
handleSave,
|
|
@@ -3199,7 +3754,7 @@ function useFormEngine({
|
|
|
3199
3754
|
}
|
|
3200
3755
|
|
|
3201
3756
|
// src/JsonSchemaFormRenderer.tsx
|
|
3202
|
-
import { jsx as
|
|
3757
|
+
import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3203
3758
|
function stripFids(data) {
|
|
3204
3759
|
const result = {};
|
|
3205
3760
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -3232,7 +3787,8 @@ function resolveFlagDefaults(props) {
|
|
|
3232
3787
|
deferStateUpdates: props.deferStateUpdates ?? false,
|
|
3233
3788
|
showCancel: props.showCancel ?? true,
|
|
3234
3789
|
showExtraProperties: props.showExtraProperties ?? true,
|
|
3235
|
-
displayContents: props.displayContents ?? false
|
|
3790
|
+
displayContents: props.displayContents ?? false,
|
|
3791
|
+
allowPristineSave: props.allowPristineSave ?? false
|
|
3236
3792
|
};
|
|
3237
3793
|
}
|
|
3238
3794
|
function resolveValueDefaults(props) {
|
|
@@ -3265,54 +3821,147 @@ function useWrappedCallbacks(onChange, onBlur, onUpdate) {
|
|
|
3265
3821
|
function buildFieldMap(fields) {
|
|
3266
3822
|
const map = /* @__PURE__ */ new Map();
|
|
3267
3823
|
for (const field of fields) {
|
|
3268
|
-
if (
|
|
3824
|
+
if (React13.isValidElement(field) && field.key != null) {
|
|
3269
3825
|
map.set(String(field.key), field);
|
|
3270
3826
|
}
|
|
3271
3827
|
}
|
|
3272
3828
|
return map;
|
|
3273
3829
|
}
|
|
3830
|
+
var COLUMN_TRACK = WIDTH_TOKEN_REMS;
|
|
3831
|
+
function resolveGridColumns(row, fieldCount) {
|
|
3832
|
+
if (row.columns) return row.columns;
|
|
3833
|
+
if (row.widths && row.widths.length > 0) {
|
|
3834
|
+
return Array.from(
|
|
3835
|
+
{ length: fieldCount },
|
|
3836
|
+
(_, i) => COLUMN_TRACK[row.widths?.[i] ?? "full"]
|
|
3837
|
+
).join(" ");
|
|
3838
|
+
}
|
|
3839
|
+
return `repeat(${fieldCount}, 1fr)`;
|
|
3840
|
+
}
|
|
3841
|
+
function wrapInGroup(group, key, content) {
|
|
3842
|
+
if (!group) return content;
|
|
3843
|
+
return /* @__PURE__ */ jsxs23(
|
|
3844
|
+
"fieldset",
|
|
3845
|
+
{
|
|
3846
|
+
className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
|
|
3847
|
+
children: [
|
|
3848
|
+
/* @__PURE__ */ jsx37("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
|
|
3849
|
+
content
|
|
3850
|
+
]
|
|
3851
|
+
},
|
|
3852
|
+
`group:${key}`
|
|
3853
|
+
);
|
|
3854
|
+
}
|
|
3274
3855
|
function buildRowElement(row, fieldMap, defaultRowGap) {
|
|
3275
3856
|
const rowFields = row.fields.map((name) => fieldMap.get(name)).filter((el) => el != null);
|
|
3276
3857
|
if (rowFields.length === 0) return null;
|
|
3277
|
-
const gridColumns = row.columns ?? `repeat(${rowFields.length}, 1fr)`;
|
|
3278
3858
|
const rowGap = row.gap ?? defaultRowGap;
|
|
3859
|
+
const rowKey = row.fields.join(",");
|
|
3860
|
+
const style = row.arrange === "flow" ? {
|
|
3861
|
+
display: "flex",
|
|
3862
|
+
flexWrap: "wrap",
|
|
3863
|
+
alignItems: "center",
|
|
3864
|
+
gap: `${rowGap}px`
|
|
3865
|
+
} : {
|
|
3866
|
+
display: "grid",
|
|
3867
|
+
gridTemplateColumns: resolveGridColumns(row, rowFields.length),
|
|
3868
|
+
gap: `${rowGap}px`
|
|
3869
|
+
};
|
|
3870
|
+
const inner = /* @__PURE__ */ jsx37("div", { style, children: rowFields }, `row:${rowKey}`);
|
|
3279
3871
|
return {
|
|
3280
|
-
element:
|
|
3281
|
-
"div",
|
|
3282
|
-
{
|
|
3283
|
-
style: {
|
|
3284
|
-
display: "grid",
|
|
3285
|
-
gridTemplateColumns: gridColumns,
|
|
3286
|
-
gap: `${rowGap}px`
|
|
3287
|
-
},
|
|
3288
|
-
children: rowFields
|
|
3289
|
-
},
|
|
3290
|
-
`row:${row.fields.join(",")}`
|
|
3291
|
-
),
|
|
3872
|
+
element: wrapInGroup(row.group, rowKey, inner),
|
|
3292
3873
|
placed: row.fields
|
|
3293
3874
|
};
|
|
3294
3875
|
}
|
|
3295
3876
|
function collectUnplacedFields(fields, placed) {
|
|
3296
3877
|
const result = [];
|
|
3297
3878
|
for (const field of fields) {
|
|
3298
|
-
if (
|
|
3879
|
+
if (React13.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
|
|
3299
3880
|
result.push(field);
|
|
3300
3881
|
}
|
|
3301
3882
|
}
|
|
3302
3883
|
return result;
|
|
3303
3884
|
}
|
|
3304
|
-
function
|
|
3305
|
-
const fieldMap = buildFieldMap(fields);
|
|
3306
|
-
const placed = /* @__PURE__ */ new Set();
|
|
3885
|
+
function buildRows(rowDefs, fieldMap, defaultRowGap, placed) {
|
|
3307
3886
|
const elements = [];
|
|
3308
3887
|
for (const row of rowDefs) {
|
|
3309
3888
|
const result = buildRowElement(row, fieldMap, defaultRowGap);
|
|
3310
|
-
|
|
3311
|
-
elements.push(result.element);
|
|
3312
|
-
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);
|
|
3313
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
|
+
);
|
|
3314
3902
|
return elements.concat(collectUnplacedFields(fields, placed));
|
|
3315
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
|
+
}
|
|
3316
3965
|
function FormContainer({
|
|
3317
3966
|
displayContents,
|
|
3318
3967
|
showExtraProperties,
|
|
@@ -3328,15 +3977,17 @@ function FormContainer({
|
|
|
3328
3977
|
cancelLabel,
|
|
3329
3978
|
showCancel,
|
|
3330
3979
|
leadingActions,
|
|
3980
|
+
hiddenErrorCount,
|
|
3331
3981
|
children
|
|
3332
3982
|
}) {
|
|
3333
3983
|
if (displayContents) {
|
|
3334
|
-
return /* @__PURE__ */
|
|
3984
|
+
return /* @__PURE__ */ jsx37("div", { style: { display: "contents" }, children });
|
|
3335
3985
|
}
|
|
3336
|
-
return /* @__PURE__ */
|
|
3986
|
+
return /* @__PURE__ */ jsxs23(SimpleStack, { gap: "md", children: [
|
|
3337
3987
|
children,
|
|
3338
|
-
showExtraProperties && /* @__PURE__ */
|
|
3339
|
-
|
|
3988
|
+
showExtraProperties && /* @__PURE__ */ jsx37(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
|
|
3989
|
+
/* @__PURE__ */ jsx37(HiddenErrorNotice, { count: hiddenErrorCount }),
|
|
3990
|
+
showActions && /* @__PURE__ */ jsx37(
|
|
3340
3991
|
FormActions,
|
|
3341
3992
|
{
|
|
3342
3993
|
onSave: handleSave,
|
|
@@ -3362,7 +4013,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3362
4013
|
onUpdate,
|
|
3363
4014
|
onCancel,
|
|
3364
4015
|
templates,
|
|
3365
|
-
leadingActions
|
|
4016
|
+
leadingActions,
|
|
4017
|
+
idPrefix = "field"
|
|
3366
4018
|
} = props;
|
|
3367
4019
|
const {
|
|
3368
4020
|
disabled,
|
|
@@ -3372,7 +4024,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3372
4024
|
deferStateUpdates,
|
|
3373
4025
|
showCancel,
|
|
3374
4026
|
showExtraProperties,
|
|
3375
|
-
displayContents
|
|
4027
|
+
displayContents,
|
|
4028
|
+
allowPristineSave
|
|
3376
4029
|
} = resolveFlagDefaults(props);
|
|
3377
4030
|
const { saveLabel, cancelLabel, widgets, uiSchema } = resolveValueDefaults(props);
|
|
3378
4031
|
const { wrappedOnChange, wrappedOnBlur, wrappedOnUpdate } = useWrappedCallbacks(onChange, onBlur, onUpdate);
|
|
@@ -3383,6 +4036,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3383
4036
|
orderedProperties,
|
|
3384
4037
|
hasChanges,
|
|
3385
4038
|
isValid,
|
|
4039
|
+
fieldErrors,
|
|
3386
4040
|
handleFieldChange,
|
|
3387
4041
|
handleFieldBlur,
|
|
3388
4042
|
handleSave,
|
|
@@ -3399,15 +4053,24 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3399
4053
|
readOnly,
|
|
3400
4054
|
showActions,
|
|
3401
4055
|
deferChanges,
|
|
3402
|
-
deferStateUpdates
|
|
4056
|
+
deferStateUpdates,
|
|
4057
|
+
allowPristineSave,
|
|
4058
|
+
refResolver: props.refResolver
|
|
3403
4059
|
});
|
|
3404
|
-
const
|
|
3405
|
-
const
|
|
4060
|
+
const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
|
|
4061
|
+
const globalOptions = useMemo8(
|
|
4062
|
+
() => ({
|
|
4063
|
+
...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
|
|
4064
|
+
...uiSchema["x-ui-globalOptions"] || {}
|
|
4065
|
+
}),
|
|
4066
|
+
[layoutLabelPlacement, uiSchema]
|
|
4067
|
+
);
|
|
4068
|
+
const resolvedTemplates = useMemo8(
|
|
3406
4069
|
() => ({ ...DEFAULT_TEMPLATES, ...templates }),
|
|
3407
4070
|
[templates]
|
|
3408
4071
|
);
|
|
3409
4072
|
const nestedRenderer = useCallback8(
|
|
3410
|
-
(props2) => /* @__PURE__ */
|
|
4073
|
+
(props2) => /* @__PURE__ */ jsx37(
|
|
3411
4074
|
JsonSchemaFormRenderer,
|
|
3412
4075
|
{
|
|
3413
4076
|
...props2,
|
|
@@ -3418,38 +4081,94 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3418
4081
|
),
|
|
3419
4082
|
[resolvedTemplates]
|
|
3420
4083
|
);
|
|
3421
|
-
const contextValue =
|
|
4084
|
+
const contextValue = useMemo8(
|
|
3422
4085
|
() => ({ renderer: nestedRenderer, templates: resolvedTemplates }),
|
|
3423
4086
|
[nestedRenderer, resolvedTemplates]
|
|
3424
4087
|
);
|
|
3425
4088
|
const needsBlurHandler = deferStateUpdates || !!onBlur;
|
|
3426
|
-
const fields =
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
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
|
+
}),
|
|
4117
|
+
[
|
|
4118
|
+
orderedProperties,
|
|
4119
|
+
displayData,
|
|
4120
|
+
resolvedSchema,
|
|
3435
4121
|
disabled,
|
|
3436
4122
|
readOnly,
|
|
3437
4123
|
schema,
|
|
3438
|
-
uiSchema
|
|
3439
|
-
|
|
4124
|
+
uiSchema,
|
|
4125
|
+
widgets,
|
|
3440
4126
|
globalOptions,
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
4127
|
+
idPrefix,
|
|
4128
|
+
needsBlurHandler,
|
|
4129
|
+
handleFieldChange,
|
|
4130
|
+
handleFieldBlur
|
|
4131
|
+
]
|
|
4132
|
+
);
|
|
3445
4133
|
const layoutConfig = uiSchema["x-ui-layout"];
|
|
3446
4134
|
const rowDefs = layoutConfig?.rows;
|
|
4135
|
+
const groupDefs = layoutConfig?.groups;
|
|
3447
4136
|
const defaultRowGap = layoutConfig?.gap ?? 24;
|
|
3448
|
-
const
|
|
3449
|
-
|
|
3450
|
-
|
|
4137
|
+
const groupTemplate = resolveGroupTemplate(
|
|
4138
|
+
resolvedTemplates,
|
|
4139
|
+
layoutConfig?.variant
|
|
3451
4140
|
);
|
|
3452
|
-
|
|
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(
|
|
3453
4172
|
FormContainer,
|
|
3454
4173
|
{
|
|
3455
4174
|
displayContents,
|
|
@@ -3460,19 +4179,20 @@ function JsonSchemaFormRenderer(props) {
|
|
|
3460
4179
|
handleSave,
|
|
3461
4180
|
handleCancel,
|
|
3462
4181
|
disabled,
|
|
3463
|
-
hasChanges,
|
|
4182
|
+
hasChanges: hasChanges || allowPristineSave,
|
|
3464
4183
|
isValid,
|
|
3465
4184
|
saveLabel,
|
|
3466
4185
|
cancelLabel,
|
|
3467
4186
|
showCancel,
|
|
3468
4187
|
leadingActions,
|
|
4188
|
+
hiddenErrorCount,
|
|
3469
4189
|
children: layoutFields
|
|
3470
4190
|
}
|
|
3471
4191
|
) });
|
|
3472
4192
|
}
|
|
3473
4193
|
|
|
3474
4194
|
// src/hooks/useResolvedSchema.ts
|
|
3475
|
-
import { useMemo as
|
|
4195
|
+
import { useMemo as useMemo9 } from "react";
|
|
3476
4196
|
import {
|
|
3477
4197
|
getSchemaProperties as getSchemaProperties2,
|
|
3478
4198
|
resolveSchema as resolveSchema2
|
|
@@ -3482,11 +4202,11 @@ function useResolvedSchema({
|
|
|
3482
4202
|
formData,
|
|
3483
4203
|
uiSchema
|
|
3484
4204
|
}) {
|
|
3485
|
-
const resolvedSchema =
|
|
4205
|
+
const resolvedSchema = useMemo9(
|
|
3486
4206
|
() => resolveSchema2(schema, formData),
|
|
3487
4207
|
[schema, formData]
|
|
3488
4208
|
);
|
|
3489
|
-
const orderedProperties =
|
|
4209
|
+
const orderedProperties = useMemo9(() => {
|
|
3490
4210
|
const properties = getSchemaProperties2(resolvedSchema);
|
|
3491
4211
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
3492
4212
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
@@ -3500,6 +4220,9 @@ export {
|
|
|
3500
4220
|
DEFAULT_TEMPLATES,
|
|
3501
4221
|
DEFAULT_WIDGETS,
|
|
3502
4222
|
DEFAULT_WIDGET_MAP,
|
|
4223
|
+
DateTimeWidget,
|
|
4224
|
+
DateWidget,
|
|
4225
|
+
DisplayWidget,
|
|
3503
4226
|
EntitySelectWidget,
|
|
3504
4227
|
ExtraPropertiesCard_default as ExtraPropertiesCard,
|
|
3505
4228
|
FormActions,
|
|
@@ -3513,18 +4236,25 @@ export {
|
|
|
3513
4236
|
FormSelect,
|
|
3514
4237
|
FormTagsInput,
|
|
3515
4238
|
FormTextarea,
|
|
4239
|
+
GroupErrorBadge,
|
|
3516
4240
|
JsonEditorWidget,
|
|
3517
4241
|
JsonSchemaFormRenderer,
|
|
3518
4242
|
NumberWidget,
|
|
3519
4243
|
ObjectWidget,
|
|
4244
|
+
OneOfWidget,
|
|
3520
4245
|
QuantityWidget,
|
|
4246
|
+
SectionsGroupTemplate,
|
|
3521
4247
|
SelectWidget,
|
|
4248
|
+
StepperGroupTemplate,
|
|
4249
|
+
TabsGroupTemplate,
|
|
3522
4250
|
TagsInputWidget,
|
|
3523
4251
|
TextInputWidget,
|
|
3524
4252
|
TextareaWidget,
|
|
4253
|
+
TimeWidget,
|
|
3525
4254
|
firstError,
|
|
3526
4255
|
getAriaDescribedBy,
|
|
3527
4256
|
getDefaultWidgetForType,
|
|
4257
|
+
groupTemplateKey,
|
|
3528
4258
|
resolveWidget,
|
|
3529
4259
|
useFormEngine,
|
|
3530
4260
|
useNestedFormContext,
|