@petrarca/sonnet-forms 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -244
- package/dist/index.d.ts +40 -3
- package/dist/index.js +106 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1483,16 +1483,18 @@ function FormActions({
|
|
|
1483
1483
|
disabled = false,
|
|
1484
1484
|
hasChanges,
|
|
1485
1485
|
isValid,
|
|
1486
|
+
saveWhenInvalid = "reveal",
|
|
1486
1487
|
saveLabel = "Save",
|
|
1487
1488
|
cancelLabel = "Cancel",
|
|
1488
1489
|
showCancel = true,
|
|
1489
1490
|
leadingActions
|
|
1490
1491
|
}) {
|
|
1492
|
+
const saveDisabled = disabled || !hasChanges || saveWhenInvalid === "disable" && !isValid;
|
|
1491
1493
|
return /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between mt-4", children: [
|
|
1492
1494
|
/* @__PURE__ */ jsx10("div", { className: "flex items-center gap-2", children: leadingActions }),
|
|
1493
1495
|
/* @__PURE__ */ jsxs9(SimpleGroup, { justify: "flex-end", children: [
|
|
1494
1496
|
showCancel && /* @__PURE__ */ jsx10(Button3, { variant: "outline", onClick: onCancel, disabled, children: cancelLabel }),
|
|
1495
|
-
/* @__PURE__ */ jsx10(Button3, { onClick: onSave, disabled:
|
|
1497
|
+
/* @__PURE__ */ jsx10(Button3, { onClick: onSave, disabled: saveDisabled, children: saveLabel })
|
|
1496
1498
|
] })
|
|
1497
1499
|
] });
|
|
1498
1500
|
}
|
|
@@ -1533,7 +1535,7 @@ function ExtraPropertiesCard({ properties, schema }) {
|
|
|
1533
1535
|
var ExtraPropertiesCard_default = ExtraPropertiesCard;
|
|
1534
1536
|
|
|
1535
1537
|
// src/JsonSchemaFormRenderer.tsx
|
|
1536
|
-
import React13, { useCallback as useCallback8, useMemo as useMemo8 } from "react";
|
|
1538
|
+
import React13, { useCallback as useCallback8, useContext as useContext2, useMemo as useMemo8 } from "react";
|
|
1537
1539
|
import { SimpleStack } from "@petrarca/sonnet-ui";
|
|
1538
1540
|
import { AlertCircle as AlertCircle2 } from "lucide-react";
|
|
1539
1541
|
import {
|
|
@@ -2194,9 +2196,11 @@ function resolveLayout(uiSchema, propertySchema) {
|
|
|
2194
2196
|
return uiSchema?.["x-ui-layout"] ?? propertySchema["x-ui-layout"];
|
|
2195
2197
|
}
|
|
2196
2198
|
function ObjectWidget({
|
|
2199
|
+
id,
|
|
2197
2200
|
propertySchema,
|
|
2198
2201
|
value,
|
|
2199
2202
|
onChange,
|
|
2203
|
+
onBlur,
|
|
2200
2204
|
registry,
|
|
2201
2205
|
uiSchema,
|
|
2202
2206
|
disabled,
|
|
@@ -2233,7 +2237,9 @@ function ObjectWidget({
|
|
|
2233
2237
|
readOnly: readonly,
|
|
2234
2238
|
widgets: registry,
|
|
2235
2239
|
uiSchema: effectiveUiSchema,
|
|
2236
|
-
displayContents
|
|
2240
|
+
displayContents,
|
|
2241
|
+
idPrefix: id,
|
|
2242
|
+
onBlur: onBlur ? () => onBlur(id, value) : void 0
|
|
2237
2243
|
}
|
|
2238
2244
|
);
|
|
2239
2245
|
if (displayContents) return /* @__PURE__ */ jsx21(Fragment3, { children: content });
|
|
@@ -2991,7 +2997,8 @@ function OneOfWidget({
|
|
|
2991
2997
|
disabled,
|
|
2992
2998
|
readOnly: readonly,
|
|
2993
2999
|
widgets: registry,
|
|
2994
|
-
uiSchema: subUiSchema
|
|
3000
|
+
uiSchema: subUiSchema,
|
|
3001
|
+
idPrefix: id
|
|
2995
3002
|
}
|
|
2996
3003
|
)
|
|
2997
3004
|
}
|
|
@@ -3414,7 +3421,8 @@ function FormFieldRenderer({
|
|
|
3414
3421
|
registry,
|
|
3415
3422
|
globalOptions = {},
|
|
3416
3423
|
formData,
|
|
3417
|
-
idPrefix = "field"
|
|
3424
|
+
idPrefix = "field",
|
|
3425
|
+
rawErrors
|
|
3418
3426
|
}) {
|
|
3419
3427
|
const label = getFieldTitle(fieldName, property);
|
|
3420
3428
|
const { description, placeholder } = resolvePresentationText(
|
|
@@ -3455,7 +3463,8 @@ function FormFieldRenderer({
|
|
|
3455
3463
|
disabled: flags.disabled,
|
|
3456
3464
|
readonly: flags.readOnly,
|
|
3457
3465
|
options: mergedOptions,
|
|
3458
|
-
formData
|
|
3466
|
+
formData,
|
|
3467
|
+
rawErrors
|
|
3459
3468
|
}
|
|
3460
3469
|
);
|
|
3461
3470
|
return applyFieldWidth(widget, uiSchema, mergedOptions);
|
|
@@ -3471,7 +3480,8 @@ import {
|
|
|
3471
3480
|
resolveSchema,
|
|
3472
3481
|
resolveRefs,
|
|
3473
3482
|
validateFormData,
|
|
3474
|
-
diffFormData
|
|
3483
|
+
diffFormData,
|
|
3484
|
+
ensureFormItemIds
|
|
3475
3485
|
} from "@petrarca/sonnet-core/schema";
|
|
3476
3486
|
|
|
3477
3487
|
// src/hooks/orderProperties.ts
|
|
@@ -3537,8 +3547,14 @@ function useFormEngine({
|
|
|
3537
3547
|
() => resolveRefs(schema, refResolver),
|
|
3538
3548
|
[schema, refResolver]
|
|
3539
3549
|
);
|
|
3550
|
+
const seed = useState9(
|
|
3551
|
+
() => ensureFormItemIds(
|
|
3552
|
+
applySchemaDefaults2(data, refResolvedSchema),
|
|
3553
|
+
refResolvedSchema
|
|
3554
|
+
)
|
|
3555
|
+
)[0];
|
|
3540
3556
|
const [originalData, setOriginalData] = useState9(
|
|
3541
|
-
() =>
|
|
3557
|
+
() => seed
|
|
3542
3558
|
);
|
|
3543
3559
|
const [originalDataInitialized, setOriginalDataInitialized] = useState9(false);
|
|
3544
3560
|
const [nonSchemaData, setNonSchemaData] = useState9(
|
|
@@ -3550,9 +3566,7 @@ function useFormEngine({
|
|
|
3550
3566
|
);
|
|
3551
3567
|
}
|
|
3552
3568
|
);
|
|
3553
|
-
const [formData, setFormData] = useState9(
|
|
3554
|
-
() => applySchemaDefaults2(data, refResolvedSchema)
|
|
3555
|
-
);
|
|
3569
|
+
const [formData, setFormData] = useState9(() => seed);
|
|
3556
3570
|
const formDataRef = useRef5(formData);
|
|
3557
3571
|
formDataRef.current = formData;
|
|
3558
3572
|
const [pendingChanges, setPendingChanges] = useState9({});
|
|
@@ -3567,6 +3581,9 @@ function useFormEngine({
|
|
|
3567
3581
|
return orderProperties(properties2, orderArray);
|
|
3568
3582
|
}, [resolvedSchema, uiSchema]);
|
|
3569
3583
|
const [initialDefaultsSent, setInitialDefaultsSent] = useState9(false);
|
|
3584
|
+
const [edited, setEdited] = useState9({});
|
|
3585
|
+
const [touched, setTouched] = useState9({});
|
|
3586
|
+
const [submitAttempted, setSubmitAttempted] = useState9(false);
|
|
3570
3587
|
const properties = getSchemaProperties(resolvedSchema);
|
|
3571
3588
|
useEffect3(() => {
|
|
3572
3589
|
if (!initialDefaultsSent) {
|
|
@@ -3597,7 +3614,10 @@ function useFormEngine({
|
|
|
3597
3614
|
useEffect3(() => {
|
|
3598
3615
|
if (isEqual(prevDataRef.current, data)) return;
|
|
3599
3616
|
prevDataRef.current = data;
|
|
3600
|
-
const newData =
|
|
3617
|
+
const newData = ensureFormItemIds(
|
|
3618
|
+
applySchemaDefaults2(data, refResolvedSchema),
|
|
3619
|
+
refResolvedSchema
|
|
3620
|
+
);
|
|
3601
3621
|
setFormData(newData);
|
|
3602
3622
|
const schemaPropertyNames = Object.keys(properties);
|
|
3603
3623
|
const result = computeBaselineUpdate(
|
|
@@ -3644,6 +3664,9 @@ function useFormEngine({
|
|
|
3644
3664
|
const handleFieldChange = useCallback7(
|
|
3645
3665
|
(fieldName, value) => {
|
|
3646
3666
|
if (readOnly) return;
|
|
3667
|
+
setEdited(
|
|
3668
|
+
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3669
|
+
);
|
|
3647
3670
|
const immediate = isImmediateField(fieldName, properties);
|
|
3648
3671
|
if (deferStateUpdates && !immediate) {
|
|
3649
3672
|
setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
|
|
@@ -3684,6 +3707,11 @@ function useFormEngine({
|
|
|
3684
3707
|
const handleFieldBlur = useCallback7(
|
|
3685
3708
|
(fieldName) => {
|
|
3686
3709
|
if (readOnly) return;
|
|
3710
|
+
if (edited[fieldName]) {
|
|
3711
|
+
setTouched(
|
|
3712
|
+
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3687
3715
|
flushPendingField(fieldName);
|
|
3688
3716
|
const latest = formDataRef.current;
|
|
3689
3717
|
if (!showActions && deferChanges && onChange) {
|
|
@@ -3697,6 +3725,7 @@ function useFormEngine({
|
|
|
3697
3725
|
},
|
|
3698
3726
|
[
|
|
3699
3727
|
readOnly,
|
|
3728
|
+
edited,
|
|
3700
3729
|
pendingChanges,
|
|
3701
3730
|
deferChanges,
|
|
3702
3731
|
deferStateUpdates,
|
|
@@ -3707,6 +3736,7 @@ function useFormEngine({
|
|
|
3707
3736
|
]
|
|
3708
3737
|
);
|
|
3709
3738
|
const handleSave = useCallback7(() => {
|
|
3739
|
+
setSubmitAttempted(true);
|
|
3710
3740
|
if (!hasChanges && !allowPristineSave) return;
|
|
3711
3741
|
const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
|
|
3712
3742
|
const completeData = { ...nonSchemaData, ...finalData };
|
|
@@ -3736,6 +3766,9 @@ function useFormEngine({
|
|
|
3736
3766
|
const handleCancel = useCallback7(() => {
|
|
3737
3767
|
setFormData({ ...originalData });
|
|
3738
3768
|
setPendingChanges({});
|
|
3769
|
+
setEdited({});
|
|
3770
|
+
setTouched({});
|
|
3771
|
+
setSubmitAttempted(false);
|
|
3739
3772
|
onCancel?.();
|
|
3740
3773
|
}, [originalData, onCancel]);
|
|
3741
3774
|
return {
|
|
@@ -3746,6 +3779,8 @@ function useFormEngine({
|
|
|
3746
3779
|
hasChanges,
|
|
3747
3780
|
isValid,
|
|
3748
3781
|
fieldErrors,
|
|
3782
|
+
touched,
|
|
3783
|
+
submitAttempted,
|
|
3749
3784
|
handleFieldChange,
|
|
3750
3785
|
handleFieldBlur,
|
|
3751
3786
|
handleSave,
|
|
@@ -3754,6 +3789,10 @@ function useFormEngine({
|
|
|
3754
3789
|
}
|
|
3755
3790
|
|
|
3756
3791
|
// src/JsonSchemaFormRenderer.tsx
|
|
3792
|
+
import {
|
|
3793
|
+
useValidationDisplay,
|
|
3794
|
+
useSaveWhenInvalid
|
|
3795
|
+
} from "@petrarca/sonnet-core/config";
|
|
3757
3796
|
import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3758
3797
|
function stripFids(data) {
|
|
3759
3798
|
const result = {};
|
|
@@ -3901,6 +3940,22 @@ function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
|
|
|
3901
3940
|
);
|
|
3902
3941
|
return elements.concat(collectUnplacedFields(fields, placed));
|
|
3903
3942
|
}
|
|
3943
|
+
function useRootFormData(ownData) {
|
|
3944
|
+
return useContext2(NestedFormContext)?.rootData ?? ownData;
|
|
3945
|
+
}
|
|
3946
|
+
function blurHandlerNeeded({
|
|
3947
|
+
deferStateUpdates,
|
|
3948
|
+
deferChanges,
|
|
3949
|
+
hasOnBlur,
|
|
3950
|
+
validationDisplay
|
|
3951
|
+
}) {
|
|
3952
|
+
return deferStateUpdates || deferChanges || hasOnBlur || validationDisplay === "onTouched";
|
|
3953
|
+
}
|
|
3954
|
+
function fieldErrorsVisible(validationDisplay, submitAttempted, isTouched) {
|
|
3955
|
+
if (validationDisplay === "always") return true;
|
|
3956
|
+
if (submitAttempted) return true;
|
|
3957
|
+
return validationDisplay === "onTouched" && !!isTouched;
|
|
3958
|
+
}
|
|
3904
3959
|
function pointerRoot(pointer) {
|
|
3905
3960
|
const token = pointer.split("/")[1] ?? "";
|
|
3906
3961
|
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
@@ -3973,6 +4028,7 @@ function FormContainer({
|
|
|
3973
4028
|
disabled,
|
|
3974
4029
|
hasChanges,
|
|
3975
4030
|
isValid,
|
|
4031
|
+
saveWhenInvalid,
|
|
3976
4032
|
saveLabel,
|
|
3977
4033
|
cancelLabel,
|
|
3978
4034
|
showCancel,
|
|
@@ -3995,6 +4051,7 @@ function FormContainer({
|
|
|
3995
4051
|
disabled,
|
|
3996
4052
|
hasChanges,
|
|
3997
4053
|
isValid,
|
|
4054
|
+
saveWhenInvalid,
|
|
3998
4055
|
saveLabel,
|
|
3999
4056
|
cancelLabel,
|
|
4000
4057
|
showCancel,
|
|
@@ -4037,6 +4094,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4037
4094
|
hasChanges,
|
|
4038
4095
|
isValid,
|
|
4039
4096
|
fieldErrors,
|
|
4097
|
+
touched,
|
|
4098
|
+
submitAttempted,
|
|
4040
4099
|
handleFieldChange,
|
|
4041
4100
|
handleFieldBlur,
|
|
4042
4101
|
handleSave,
|
|
@@ -4081,15 +4140,35 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4081
4140
|
),
|
|
4082
4141
|
[resolvedTemplates]
|
|
4083
4142
|
);
|
|
4143
|
+
const rootData = useRootFormData(displayData);
|
|
4084
4144
|
const contextValue = useMemo8(
|
|
4085
|
-
() => ({
|
|
4086
|
-
|
|
4145
|
+
() => ({
|
|
4146
|
+
renderer: nestedRenderer,
|
|
4147
|
+
templates: resolvedTemplates,
|
|
4148
|
+
rootData
|
|
4149
|
+
}),
|
|
4150
|
+
[nestedRenderer, resolvedTemplates, rootData]
|
|
4151
|
+
);
|
|
4152
|
+
const validationDisplay = useValidationDisplay(props.validationDisplay);
|
|
4153
|
+
const saveWhenInvalid = useSaveWhenInvalid(props.saveWhenInvalid);
|
|
4154
|
+
const needsBlurHandler = blurHandlerNeeded({
|
|
4155
|
+
deferStateUpdates,
|
|
4156
|
+
deferChanges,
|
|
4157
|
+
hasOnBlur: !!onBlur,
|
|
4158
|
+
validationDisplay
|
|
4159
|
+
});
|
|
4160
|
+
const showFieldErrors = useCallback8(
|
|
4161
|
+
(fieldName) => fieldErrorsVisible(
|
|
4162
|
+
validationDisplay,
|
|
4163
|
+
submitAttempted,
|
|
4164
|
+
touched[fieldName]
|
|
4165
|
+
),
|
|
4166
|
+
[validationDisplay, submitAttempted, touched]
|
|
4087
4167
|
);
|
|
4088
|
-
const needsBlurHandler = deferStateUpdates || !!onBlur;
|
|
4089
4168
|
const fields = useMemo8(
|
|
4090
4169
|
() => orderedProperties.flatMap(([fieldName, property]) => {
|
|
4091
4170
|
const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
|
|
4092
|
-
const { visible, enabled } = evaluateUiRule(rule,
|
|
4171
|
+
const { visible, enabled } = evaluateUiRule(rule, rootData);
|
|
4093
4172
|
if (!visible) return [];
|
|
4094
4173
|
return [
|
|
4095
4174
|
/* @__PURE__ */ jsx37(
|
|
@@ -4108,7 +4187,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4108
4187
|
registry: widgets,
|
|
4109
4188
|
globalOptions,
|
|
4110
4189
|
formData: displayData,
|
|
4111
|
-
idPrefix
|
|
4190
|
+
idPrefix,
|
|
4191
|
+
rawErrors: showFieldErrors(fieldName) ? fieldErrors.filter((e) => pointerRoot(e.pointer) === fieldName).map((e) => e.message) : void 0
|
|
4112
4192
|
},
|
|
4113
4193
|
fieldName
|
|
4114
4194
|
)
|
|
@@ -4117,6 +4197,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4117
4197
|
[
|
|
4118
4198
|
orderedProperties,
|
|
4119
4199
|
displayData,
|
|
4200
|
+
rootData,
|
|
4120
4201
|
resolvedSchema,
|
|
4121
4202
|
disabled,
|
|
4122
4203
|
readOnly,
|
|
@@ -4126,6 +4207,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4126
4207
|
globalOptions,
|
|
4127
4208
|
idPrefix,
|
|
4128
4209
|
needsBlurHandler,
|
|
4210
|
+
showFieldErrors,
|
|
4211
|
+
fieldErrors,
|
|
4129
4212
|
handleFieldChange,
|
|
4130
4213
|
handleFieldBlur
|
|
4131
4214
|
]
|
|
@@ -4145,7 +4228,8 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4145
4228
|
groupDefs,
|
|
4146
4229
|
defaultRowGap,
|
|
4147
4230
|
fieldErrors,
|
|
4148
|
-
|
|
4231
|
+
// Tab rules are x-ui-rule too: root-anchored like field rules.
|
|
4232
|
+
rootData,
|
|
4149
4233
|
groupTemplate
|
|
4150
4234
|
);
|
|
4151
4235
|
}
|
|
@@ -4159,15 +4243,15 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4159
4243
|
groupDefs,
|
|
4160
4244
|
defaultRowGap,
|
|
4161
4245
|
fieldErrors,
|
|
4162
|
-
|
|
4246
|
+
rootData,
|
|
4163
4247
|
groupTemplate
|
|
4164
4248
|
]);
|
|
4165
4249
|
const hiddenErrorCount = useMemo8(() => {
|
|
4166
4250
|
if (!groupDefs?.length || fieldErrors.length === 0) return 0;
|
|
4167
|
-
const hidden = hiddenGroupFieldNames(groupDefs,
|
|
4251
|
+
const hidden = hiddenGroupFieldNames(groupDefs, rootData);
|
|
4168
4252
|
if (hidden.size === 0) return 0;
|
|
4169
4253
|
return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
|
|
4170
|
-
}, [groupDefs, fieldErrors,
|
|
4254
|
+
}, [groupDefs, fieldErrors, rootData]);
|
|
4171
4255
|
return /* @__PURE__ */ jsx37(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx37(
|
|
4172
4256
|
FormContainer,
|
|
4173
4257
|
{
|
|
@@ -4181,6 +4265,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4181
4265
|
disabled,
|
|
4182
4266
|
hasChanges: hasChanges || allowPristineSave,
|
|
4183
4267
|
isValid,
|
|
4268
|
+
saveWhenInvalid,
|
|
4184
4269
|
saveLabel,
|
|
4185
4270
|
cancelLabel,
|
|
4186
4271
|
showCancel,
|