@petrarca/sonnet-forms 0.6.0 → 0.8.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 +165 -6
- package/dist/index.js +481 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1477,22 +1477,53 @@ FormQuantityInput.displayName = "FormQuantityInput";
|
|
|
1477
1477
|
import { Button as Button3 } from "@petrarca/sonnet-ui";
|
|
1478
1478
|
import { SimpleGroup } from "@petrarca/sonnet-ui";
|
|
1479
1479
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1480
|
+
function isSaveDisabled(args) {
|
|
1481
|
+
if (args.disabled || !args.hasChanges || args.isValidating) return true;
|
|
1482
|
+
return args.saveWhenInvalid === "disable" && !args.isValid;
|
|
1483
|
+
}
|
|
1480
1484
|
function FormActions({
|
|
1481
1485
|
onSave,
|
|
1482
1486
|
onCancel,
|
|
1483
1487
|
disabled = false,
|
|
1484
1488
|
hasChanges,
|
|
1485
1489
|
isValid,
|
|
1490
|
+
saveWhenInvalid = "reveal",
|
|
1486
1491
|
saveLabel = "Save",
|
|
1487
1492
|
cancelLabel = "Cancel",
|
|
1488
1493
|
showCancel = true,
|
|
1494
|
+
isValidating = false,
|
|
1489
1495
|
leadingActions
|
|
1490
1496
|
}) {
|
|
1497
|
+
const saveDisabled = isSaveDisabled({
|
|
1498
|
+
disabled,
|
|
1499
|
+
hasChanges,
|
|
1500
|
+
isValid,
|
|
1501
|
+
isValidating,
|
|
1502
|
+
saveWhenInvalid
|
|
1503
|
+
});
|
|
1504
|
+
const keepFocus = (e) => e.preventDefault();
|
|
1491
1505
|
return /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between mt-4", children: [
|
|
1492
1506
|
/* @__PURE__ */ jsx10("div", { className: "flex items-center gap-2", children: leadingActions }),
|
|
1493
1507
|
/* @__PURE__ */ jsxs9(SimpleGroup, { justify: "flex-end", children: [
|
|
1494
|
-
showCancel && /* @__PURE__ */ jsx10(
|
|
1495
|
-
|
|
1508
|
+
showCancel && /* @__PURE__ */ jsx10(
|
|
1509
|
+
Button3,
|
|
1510
|
+
{
|
|
1511
|
+
variant: "outline",
|
|
1512
|
+
onClick: onCancel,
|
|
1513
|
+
onMouseDown: keepFocus,
|
|
1514
|
+
disabled,
|
|
1515
|
+
children: cancelLabel
|
|
1516
|
+
}
|
|
1517
|
+
),
|
|
1518
|
+
/* @__PURE__ */ jsx10(
|
|
1519
|
+
Button3,
|
|
1520
|
+
{
|
|
1521
|
+
onClick: onSave,
|
|
1522
|
+
onMouseDown: keepFocus,
|
|
1523
|
+
disabled: saveDisabled,
|
|
1524
|
+
children: saveLabel
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1496
1527
|
] })
|
|
1497
1528
|
] });
|
|
1498
1529
|
}
|
|
@@ -1533,7 +1564,7 @@ function ExtraPropertiesCard({ properties, schema }) {
|
|
|
1533
1564
|
var ExtraPropertiesCard_default = ExtraPropertiesCard;
|
|
1534
1565
|
|
|
1535
1566
|
// src/JsonSchemaFormRenderer.tsx
|
|
1536
|
-
import React13, { useCallback as useCallback8, useMemo as
|
|
1567
|
+
import React13, { useCallback as useCallback8, useContext as useContext2, useMemo as useMemo9 } from "react";
|
|
1537
1568
|
import { SimpleStack } from "@petrarca/sonnet-ui";
|
|
1538
1569
|
import { AlertCircle as AlertCircle2 } from "lucide-react";
|
|
1539
1570
|
import {
|
|
@@ -2194,9 +2225,11 @@ function resolveLayout(uiSchema, propertySchema) {
|
|
|
2194
2225
|
return uiSchema?.["x-ui-layout"] ?? propertySchema["x-ui-layout"];
|
|
2195
2226
|
}
|
|
2196
2227
|
function ObjectWidget({
|
|
2228
|
+
id,
|
|
2197
2229
|
propertySchema,
|
|
2198
2230
|
value,
|
|
2199
2231
|
onChange,
|
|
2232
|
+
onBlur,
|
|
2200
2233
|
registry,
|
|
2201
2234
|
uiSchema,
|
|
2202
2235
|
disabled,
|
|
@@ -2233,7 +2266,9 @@ function ObjectWidget({
|
|
|
2233
2266
|
readOnly: readonly,
|
|
2234
2267
|
widgets: registry,
|
|
2235
2268
|
uiSchema: effectiveUiSchema,
|
|
2236
|
-
displayContents
|
|
2269
|
+
displayContents,
|
|
2270
|
+
idPrefix: id,
|
|
2271
|
+
onBlur: onBlur ? () => onBlur(id, value) : void 0
|
|
2237
2272
|
}
|
|
2238
2273
|
);
|
|
2239
2274
|
if (displayContents) return /* @__PURE__ */ jsx21(Fragment3, { children: content });
|
|
@@ -2991,7 +3026,8 @@ function OneOfWidget({
|
|
|
2991
3026
|
disabled,
|
|
2992
3027
|
readOnly: readonly,
|
|
2993
3028
|
widgets: registry,
|
|
2994
|
-
uiSchema: subUiSchema
|
|
3029
|
+
uiSchema: subUiSchema,
|
|
3030
|
+
idPrefix: id
|
|
2995
3031
|
}
|
|
2996
3032
|
)
|
|
2997
3033
|
}
|
|
@@ -3414,7 +3450,8 @@ function FormFieldRenderer({
|
|
|
3414
3450
|
registry,
|
|
3415
3451
|
globalOptions = {},
|
|
3416
3452
|
formData,
|
|
3417
|
-
idPrefix = "field"
|
|
3453
|
+
idPrefix = "field",
|
|
3454
|
+
rawErrors
|
|
3418
3455
|
}) {
|
|
3419
3456
|
const label = getFieldTitle(fieldName, property);
|
|
3420
3457
|
const { description, placeholder } = resolvePresentationText(
|
|
@@ -3455,25 +3492,219 @@ function FormFieldRenderer({
|
|
|
3455
3492
|
disabled: flags.disabled,
|
|
3456
3493
|
readonly: flags.readOnly,
|
|
3457
3494
|
options: mergedOptions,
|
|
3458
|
-
formData
|
|
3495
|
+
formData,
|
|
3496
|
+
rawErrors
|
|
3459
3497
|
}
|
|
3460
3498
|
);
|
|
3461
3499
|
return applyFieldWidth(widget, uiSchema, mergedOptions);
|
|
3462
3500
|
}
|
|
3463
3501
|
|
|
3464
3502
|
// src/hooks/useFormEngine.ts
|
|
3465
|
-
import { useState as
|
|
3503
|
+
import { useState as useState10, useEffect as useEffect4, useMemo as useMemo8, useCallback as useCallback7, useRef as useRef6 } from "react";
|
|
3466
3504
|
import { isEqual } from "@petrarca/sonnet-core";
|
|
3467
3505
|
import {
|
|
3468
|
-
getSchemaProperties,
|
|
3506
|
+
getSchemaProperties as getSchemaProperties4,
|
|
3469
3507
|
getFieldDefault,
|
|
3470
3508
|
applySchemaDefaults as applySchemaDefaults2,
|
|
3471
3509
|
resolveSchema,
|
|
3472
3510
|
resolveRefs,
|
|
3473
3511
|
validateFormData,
|
|
3474
|
-
|
|
3512
|
+
mergeFieldErrors,
|
|
3513
|
+
diffFormData,
|
|
3514
|
+
ensureFormItemIds
|
|
3475
3515
|
} from "@petrarca/sonnet-core/schema";
|
|
3476
3516
|
|
|
3517
|
+
// src/validators/runValidators.ts
|
|
3518
|
+
import { getSchemaProperties as getSchemaProperties2 } from "@petrarca/sonnet-core/schema";
|
|
3519
|
+
|
|
3520
|
+
// src/validators/shared.ts
|
|
3521
|
+
import { getSchemaProperties } from "@petrarca/sonnet-core/schema";
|
|
3522
|
+
function toRefs(v) {
|
|
3523
|
+
if (v === void 0) return [];
|
|
3524
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
3525
|
+
return arr.map((item) => typeof item === "string" ? { name: item } : item);
|
|
3526
|
+
}
|
|
3527
|
+
function escapeToken(token) {
|
|
3528
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
3529
|
+
}
|
|
3530
|
+
function fieldPointer(fieldName) {
|
|
3531
|
+
return `/${escapeToken(fieldName)}`;
|
|
3532
|
+
}
|
|
3533
|
+
function resolveFieldRefs(fieldName, schema, uiSchema) {
|
|
3534
|
+
const property = getSchemaProperties(schema)[fieldName] ?? {};
|
|
3535
|
+
return toRefs(
|
|
3536
|
+
uiSchema[fieldName]?.["x-ui-validate"] ?? property["x-ui-validate"]
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3539
|
+
function toFieldErrors(result, pointer) {
|
|
3540
|
+
if (result == null || result === "") return [];
|
|
3541
|
+
if (typeof result === "string") return [{ pointer, message: result }];
|
|
3542
|
+
if (Array.isArray(result)) return result;
|
|
3543
|
+
return [];
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// src/validators/runValidators.ts
|
|
3547
|
+
function isKnownName(registry, name) {
|
|
3548
|
+
return !!(registry.field?.[name] || registry.asyncField?.[name] || registry.form?.[name] || registry.asyncForm?.[name]);
|
|
3549
|
+
}
|
|
3550
|
+
function unknownValidator(kind, name, registry) {
|
|
3551
|
+
const registered = [
|
|
3552
|
+
...Object.keys(registry.field ?? {}),
|
|
3553
|
+
...Object.keys(registry.asyncField ?? {}),
|
|
3554
|
+
...Object.keys(registry.form ?? {}),
|
|
3555
|
+
...Object.keys(registry.asyncForm ?? {})
|
|
3556
|
+
];
|
|
3557
|
+
throw new Error(
|
|
3558
|
+
`unknown ${kind} validator '${name}' (registered: [${registered.join(", ")}])`
|
|
3559
|
+
);
|
|
3560
|
+
}
|
|
3561
|
+
function runFieldRefs(fieldName, data, registry, uiSchema, schema, out) {
|
|
3562
|
+
const refs = resolveFieldRefs(fieldName, schema, uiSchema);
|
|
3563
|
+
if (refs.length === 0) return;
|
|
3564
|
+
const pointer = fieldPointer(fieldName);
|
|
3565
|
+
const value = data[fieldName];
|
|
3566
|
+
for (const ref of refs) {
|
|
3567
|
+
if (!isKnownName(registry, ref.name)) {
|
|
3568
|
+
unknownValidator("field", ref.name, registry);
|
|
3569
|
+
}
|
|
3570
|
+
const fn = registry.field?.[ref.name];
|
|
3571
|
+
if (!fn) continue;
|
|
3572
|
+
const ctx = {
|
|
3573
|
+
formData: data,
|
|
3574
|
+
path: pointer,
|
|
3575
|
+
args: ref.args
|
|
3576
|
+
};
|
|
3577
|
+
out.push(...toFieldErrors(fn(value, ctx), pointer));
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
function runFormRefs(data, registry, schema, out) {
|
|
3581
|
+
for (const ref of toRefs(schema["x-ui-validate"])) {
|
|
3582
|
+
if (!isKnownName(registry, ref.name)) {
|
|
3583
|
+
unknownValidator("form", ref.name, registry);
|
|
3584
|
+
}
|
|
3585
|
+
const fn = registry.form?.[ref.name];
|
|
3586
|
+
if (!fn) continue;
|
|
3587
|
+
const ctx = { formData: data, path: "", args: ref.args };
|
|
3588
|
+
out.push(...fn(data, ctx));
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
function runValidators(schema, data, registry, uiSchema, includeFormValidators) {
|
|
3592
|
+
const out = [];
|
|
3593
|
+
if (!registry) return out;
|
|
3594
|
+
for (const fieldName of Object.keys(getSchemaProperties2(schema))) {
|
|
3595
|
+
runFieldRefs(fieldName, data, registry, uiSchema, schema, out);
|
|
3596
|
+
}
|
|
3597
|
+
if (includeFormValidators) {
|
|
3598
|
+
runFormRefs(data, registry, schema, out);
|
|
3599
|
+
}
|
|
3600
|
+
return out;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// src/validators/useAsyncValidators.ts
|
|
3604
|
+
import { useEffect as useEffect3, useMemo as useMemo7, useRef as useRef5, useState as useState9 } from "react";
|
|
3605
|
+
import { getSchemaProperties as getSchemaProperties3 } from "@petrarca/sonnet-core/schema";
|
|
3606
|
+
function keyOf(value) {
|
|
3607
|
+
try {
|
|
3608
|
+
return JSON.stringify(value) ?? "undefined";
|
|
3609
|
+
} catch {
|
|
3610
|
+
return String(value);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
function collectFieldCandidates(schema, data, registry, uiSchema, out) {
|
|
3614
|
+
for (const fieldName of Object.keys(getSchemaProperties3(schema))) {
|
|
3615
|
+
const refs = resolveFieldRefs(fieldName, schema, uiSchema);
|
|
3616
|
+
const pointer = fieldPointer(fieldName);
|
|
3617
|
+
const value = data[fieldName];
|
|
3618
|
+
for (const ref of refs) {
|
|
3619
|
+
const fn = registry[ref.name];
|
|
3620
|
+
if (!fn) continue;
|
|
3621
|
+
const ctx = {
|
|
3622
|
+
formData: data,
|
|
3623
|
+
path: pointer,
|
|
3624
|
+
args: ref.args
|
|
3625
|
+
};
|
|
3626
|
+
out.push({
|
|
3627
|
+
id: `asyncField:${pointer}:${ref.name}`,
|
|
3628
|
+
key: keyOf(value),
|
|
3629
|
+
pointer,
|
|
3630
|
+
run: () => fn(value, ctx)
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
function collectFormCandidates(schema, data, registry, out) {
|
|
3636
|
+
for (const ref of toRefs(schema["x-ui-validate"])) {
|
|
3637
|
+
const fn = registry[ref.name];
|
|
3638
|
+
if (!fn) continue;
|
|
3639
|
+
const ctx = { formData: data, path: "", args: ref.args };
|
|
3640
|
+
const basis = ref.dependsOn ? ref.dependsOn.map((f) => keyOf(data[f])).join("|") : keyOf(data);
|
|
3641
|
+
out.push({
|
|
3642
|
+
id: `asyncForm:${ref.name}`,
|
|
3643
|
+
key: basis,
|
|
3644
|
+
pointer: "",
|
|
3645
|
+
run: () => fn(data, ctx)
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
function collectCandidates(schema, data, registry, uiSchema, isRoot) {
|
|
3650
|
+
const out = [];
|
|
3651
|
+
if (registry.asyncField) {
|
|
3652
|
+
collectFieldCandidates(schema, data, registry.asyncField, uiSchema, out);
|
|
3653
|
+
}
|
|
3654
|
+
if (registry.asyncForm && isRoot) {
|
|
3655
|
+
collectFormCandidates(schema, data, registry.asyncForm, out);
|
|
3656
|
+
}
|
|
3657
|
+
return out;
|
|
3658
|
+
}
|
|
3659
|
+
function useAsyncValidators(schema, data, registry, uiSchema, isRoot) {
|
|
3660
|
+
const [entries, setEntries] = useState9({});
|
|
3661
|
+
const handledKey = useRef5({});
|
|
3662
|
+
const candidates = registry && (registry.asyncField || registry.asyncForm) ? collectCandidates(schema, data, registry, uiSchema, isRoot) : [];
|
|
3663
|
+
const liveIds = new Set(candidates.map((c) => c.id));
|
|
3664
|
+
const signature = candidates.map((c) => `${c.id}=${c.key}`).join(";");
|
|
3665
|
+
useEffect3(() => {
|
|
3666
|
+
for (const c of candidates) {
|
|
3667
|
+
if (handledKey.current[c.id] === c.key) continue;
|
|
3668
|
+
handledKey.current[c.id] = c.key;
|
|
3669
|
+
setEntries((e) => ({
|
|
3670
|
+
...e,
|
|
3671
|
+
[c.id]: { key: c.key, pending: true, errors: [] }
|
|
3672
|
+
}));
|
|
3673
|
+
const settle = (errors) => {
|
|
3674
|
+
if (handledKey.current[c.id] !== c.key) return;
|
|
3675
|
+
setEntries((e) => ({
|
|
3676
|
+
...e,
|
|
3677
|
+
[c.id]: { key: c.key, pending: false, errors }
|
|
3678
|
+
}));
|
|
3679
|
+
};
|
|
3680
|
+
c.run().then((r) => settle(toFieldErrors(r, c.pointer))).catch(() => settle([]));
|
|
3681
|
+
}
|
|
3682
|
+
}, [signature]);
|
|
3683
|
+
const liveKey = [...liveIds].sort().join(",");
|
|
3684
|
+
useEffect3(() => {
|
|
3685
|
+
setEntries((e) => {
|
|
3686
|
+
const kept = Object.entries(e).filter(([id]) => liveIds.has(id));
|
|
3687
|
+
return kept.length === Object.keys(e).length ? e : Object.fromEntries(kept);
|
|
3688
|
+
});
|
|
3689
|
+
for (const id of Object.keys(handledKey.current)) {
|
|
3690
|
+
if (!liveIds.has(id)) delete handledKey.current[id];
|
|
3691
|
+
}
|
|
3692
|
+
}, [liveKey]);
|
|
3693
|
+
let isValidating = false;
|
|
3694
|
+
const errorList = [];
|
|
3695
|
+
for (const c of candidates) {
|
|
3696
|
+
const entry = entries[c.id];
|
|
3697
|
+
if (!entry || entry.key !== c.key || entry.pending) {
|
|
3698
|
+
isValidating = true;
|
|
3699
|
+
continue;
|
|
3700
|
+
}
|
|
3701
|
+
errorList.push(...entry.errors);
|
|
3702
|
+
}
|
|
3703
|
+
const errorSig = errorList.map((e) => `${e.pointer}\0${e.message}`).join("|");
|
|
3704
|
+
const asyncErrors = useMemo7(() => errorList, [errorSig]);
|
|
3705
|
+
return { asyncErrors, isValidating };
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3477
3708
|
// src/hooks/orderProperties.ts
|
|
3478
3709
|
function orderProperties(properties, orderArray) {
|
|
3479
3710
|
const allEntries = Object.entries(properties);
|
|
@@ -3517,6 +3748,11 @@ function computeBaselineUpdate(newData, originalData, initialized, schemaPropert
|
|
|
3517
3748
|
}
|
|
3518
3749
|
return null;
|
|
3519
3750
|
}
|
|
3751
|
+
function computeAllErrors(args) {
|
|
3752
|
+
const { schema, data, customSync, asyncErrors, extraErrors } = args;
|
|
3753
|
+
const base = validateFormData(schema, data).fieldErrors;
|
|
3754
|
+
return mergeFieldErrors(base, customSync, asyncErrors, extraErrors ?? []);
|
|
3755
|
+
}
|
|
3520
3756
|
function useFormEngine({
|
|
3521
3757
|
schema,
|
|
3522
3758
|
data,
|
|
@@ -3531,44 +3767,54 @@ function useFormEngine({
|
|
|
3531
3767
|
deferChanges = false,
|
|
3532
3768
|
deferStateUpdates = false,
|
|
3533
3769
|
allowPristineSave = false,
|
|
3534
|
-
refResolver
|
|
3770
|
+
refResolver,
|
|
3771
|
+
extraErrors,
|
|
3772
|
+
validators,
|
|
3773
|
+
isRoot = true
|
|
3535
3774
|
}) {
|
|
3536
|
-
const refResolvedSchema =
|
|
3775
|
+
const refResolvedSchema = useMemo8(
|
|
3537
3776
|
() => resolveRefs(schema, refResolver),
|
|
3538
3777
|
[schema, refResolver]
|
|
3539
3778
|
);
|
|
3540
|
-
const
|
|
3541
|
-
() =>
|
|
3779
|
+
const seed = useState10(
|
|
3780
|
+
() => ensureFormItemIds(
|
|
3781
|
+
applySchemaDefaults2(data, refResolvedSchema),
|
|
3782
|
+
refResolvedSchema
|
|
3783
|
+
)
|
|
3784
|
+
)[0];
|
|
3785
|
+
const [originalData, setOriginalData] = useState10(
|
|
3786
|
+
() => seed
|
|
3542
3787
|
);
|
|
3543
|
-
const [originalDataInitialized, setOriginalDataInitialized] =
|
|
3544
|
-
const [nonSchemaData, setNonSchemaData] =
|
|
3788
|
+
const [originalDataInitialized, setOriginalDataInitialized] = useState10(false);
|
|
3789
|
+
const [nonSchemaData, setNonSchemaData] = useState10(
|
|
3545
3790
|
() => {
|
|
3546
|
-
const props =
|
|
3791
|
+
const props = getSchemaProperties4(refResolvedSchema);
|
|
3547
3792
|
const names = Object.keys(props);
|
|
3548
3793
|
return Object.fromEntries(
|
|
3549
3794
|
Object.entries(data).filter(([k]) => !names.includes(k))
|
|
3550
3795
|
);
|
|
3551
3796
|
}
|
|
3552
3797
|
);
|
|
3553
|
-
const [formData, setFormData] =
|
|
3554
|
-
|
|
3555
|
-
);
|
|
3556
|
-
const formDataRef = useRef5(formData);
|
|
3798
|
+
const [formData, setFormData] = useState10(() => seed);
|
|
3799
|
+
const formDataRef = useRef6(formData);
|
|
3557
3800
|
formDataRef.current = formData;
|
|
3558
|
-
const [pendingChanges, setPendingChanges] =
|
|
3559
|
-
const resolvedSchema =
|
|
3801
|
+
const [pendingChanges, setPendingChanges] = useState10({});
|
|
3802
|
+
const resolvedSchema = useMemo8(
|
|
3560
3803
|
() => resolveSchema(refResolvedSchema, formData),
|
|
3561
3804
|
[refResolvedSchema, formData]
|
|
3562
3805
|
);
|
|
3563
|
-
const orderedProperties =
|
|
3564
|
-
const properties2 =
|
|
3806
|
+
const orderedProperties = useMemo8(() => {
|
|
3807
|
+
const properties2 = getSchemaProperties4(resolvedSchema);
|
|
3565
3808
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
3566
3809
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
3567
3810
|
return orderProperties(properties2, orderArray);
|
|
3568
3811
|
}, [resolvedSchema, uiSchema]);
|
|
3569
|
-
const [initialDefaultsSent, setInitialDefaultsSent] =
|
|
3570
|
-
const
|
|
3571
|
-
|
|
3812
|
+
const [initialDefaultsSent, setInitialDefaultsSent] = useState10(false);
|
|
3813
|
+
const [edited, setEdited] = useState10({});
|
|
3814
|
+
const [touched, setTouched] = useState10({});
|
|
3815
|
+
const [submitAttempted, setSubmitAttempted] = useState10(false);
|
|
3816
|
+
const properties = getSchemaProperties4(resolvedSchema);
|
|
3817
|
+
useEffect4(() => {
|
|
3572
3818
|
if (!initialDefaultsSent) {
|
|
3573
3819
|
const hasDefaults = Object.entries(properties).some(
|
|
3574
3820
|
([fieldName, property]) => data[fieldName] === void 0 && getFieldDefault(property) !== void 0
|
|
@@ -3579,7 +3825,7 @@ function useFormEngine({
|
|
|
3579
3825
|
setInitialDefaultsSent(true);
|
|
3580
3826
|
}
|
|
3581
3827
|
}, [initialDefaultsSent, properties, data, formData, onChange]);
|
|
3582
|
-
|
|
3828
|
+
useEffect4(() => {
|
|
3583
3829
|
const currentPropertyNames = Object.keys(properties);
|
|
3584
3830
|
const keysToRemove = Object.keys(formData).filter(
|
|
3585
3831
|
(k) => !currentPropertyNames.includes(k)
|
|
@@ -3593,11 +3839,14 @@ function useFormEngine({
|
|
|
3593
3839
|
if (onChange) onChange(cleanedData);
|
|
3594
3840
|
}
|
|
3595
3841
|
}, [properties, formData, onChange]);
|
|
3596
|
-
const prevDataRef =
|
|
3597
|
-
|
|
3842
|
+
const prevDataRef = useRef6(data);
|
|
3843
|
+
useEffect4(() => {
|
|
3598
3844
|
if (isEqual(prevDataRef.current, data)) return;
|
|
3599
3845
|
prevDataRef.current = data;
|
|
3600
|
-
const newData =
|
|
3846
|
+
const newData = ensureFormItemIds(
|
|
3847
|
+
applySchemaDefaults2(data, refResolvedSchema),
|
|
3848
|
+
refResolvedSchema
|
|
3849
|
+
);
|
|
3601
3850
|
setFormData(newData);
|
|
3602
3851
|
const schemaPropertyNames = Object.keys(properties);
|
|
3603
3852
|
const result = computeBaselineUpdate(
|
|
@@ -3621,29 +3870,53 @@ function useFormEngine({
|
|
|
3621
3870
|
originalData,
|
|
3622
3871
|
originalDataInitialized
|
|
3623
3872
|
]);
|
|
3624
|
-
const hasChanges =
|
|
3873
|
+
const hasChanges = useMemo8(
|
|
3625
3874
|
() => !isEqual(formData, originalData),
|
|
3626
3875
|
[formData, originalData]
|
|
3627
3876
|
);
|
|
3628
|
-
const displayData =
|
|
3877
|
+
const displayData = useMemo8(() => {
|
|
3629
3878
|
if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
|
|
3630
3879
|
return { ...formData, ...pendingChanges };
|
|
3631
3880
|
}
|
|
3632
3881
|
return formData;
|
|
3633
3882
|
}, [formData, pendingChanges, deferStateUpdates]);
|
|
3634
|
-
const
|
|
3635
|
-
() =>
|
|
3636
|
-
[resolvedSchema, displayData]
|
|
3883
|
+
const customSync = useMemo8(
|
|
3884
|
+
() => runValidators(resolvedSchema, displayData, validators, uiSchema, isRoot),
|
|
3885
|
+
[resolvedSchema, displayData, validators, uiSchema, isRoot]
|
|
3886
|
+
);
|
|
3887
|
+
const { asyncErrors, isValidating } = useAsyncValidators(
|
|
3888
|
+
resolvedSchema,
|
|
3889
|
+
displayData,
|
|
3890
|
+
validators,
|
|
3891
|
+
uiSchema,
|
|
3892
|
+
isRoot
|
|
3637
3893
|
);
|
|
3894
|
+
const validation = useMemo8(() => {
|
|
3895
|
+
const merged = computeAllErrors({
|
|
3896
|
+
schema: resolvedSchema,
|
|
3897
|
+
data: displayData,
|
|
3898
|
+
customSync,
|
|
3899
|
+
asyncErrors,
|
|
3900
|
+
extraErrors
|
|
3901
|
+
});
|
|
3902
|
+
return {
|
|
3903
|
+
fieldErrors: merged,
|
|
3904
|
+
errors: merged.map((e) => e.message),
|
|
3905
|
+
isValid: merged.length === 0
|
|
3906
|
+
};
|
|
3907
|
+
}, [resolvedSchema, displayData, customSync, asyncErrors, extraErrors]);
|
|
3638
3908
|
const { isValid, fieldErrors } = validation;
|
|
3639
|
-
const onValidationChangeRef =
|
|
3909
|
+
const onValidationChangeRef = useRef6(onValidationChange);
|
|
3640
3910
|
onValidationChangeRef.current = onValidationChange;
|
|
3641
|
-
|
|
3911
|
+
useEffect4(() => {
|
|
3642
3912
|
onValidationChangeRef.current?.(validation.errors, validation.isValid);
|
|
3643
3913
|
}, [validation]);
|
|
3644
3914
|
const handleFieldChange = useCallback7(
|
|
3645
3915
|
(fieldName, value) => {
|
|
3646
3916
|
if (readOnly) return;
|
|
3917
|
+
setEdited(
|
|
3918
|
+
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3919
|
+
);
|
|
3647
3920
|
const immediate = isImmediateField(fieldName, properties);
|
|
3648
3921
|
if (deferStateUpdates && !immediate) {
|
|
3649
3922
|
setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
|
|
@@ -3684,6 +3957,11 @@ function useFormEngine({
|
|
|
3684
3957
|
const handleFieldBlur = useCallback7(
|
|
3685
3958
|
(fieldName) => {
|
|
3686
3959
|
if (readOnly) return;
|
|
3960
|
+
if (edited[fieldName]) {
|
|
3961
|
+
setTouched(
|
|
3962
|
+
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3963
|
+
);
|
|
3964
|
+
}
|
|
3687
3965
|
flushPendingField(fieldName);
|
|
3688
3966
|
const latest = formDataRef.current;
|
|
3689
3967
|
if (!showActions && deferChanges && onChange) {
|
|
@@ -3697,6 +3975,7 @@ function useFormEngine({
|
|
|
3697
3975
|
},
|
|
3698
3976
|
[
|
|
3699
3977
|
readOnly,
|
|
3978
|
+
edited,
|
|
3700
3979
|
pendingChanges,
|
|
3701
3980
|
deferChanges,
|
|
3702
3981
|
deferStateUpdates,
|
|
@@ -3707,11 +3986,25 @@ function useFormEngine({
|
|
|
3707
3986
|
]
|
|
3708
3987
|
);
|
|
3709
3988
|
const handleSave = useCallback7(() => {
|
|
3989
|
+
setSubmitAttempted(true);
|
|
3710
3990
|
if (!hasChanges && !allowPristineSave) return;
|
|
3991
|
+
if (isValidating) return;
|
|
3711
3992
|
const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
|
|
3712
3993
|
const completeData = { ...nonSchemaData, ...finalData };
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3994
|
+
const saveErrors = computeAllErrors({
|
|
3995
|
+
schema: resolvedSchema,
|
|
3996
|
+
data: finalData,
|
|
3997
|
+
customSync: runValidators(
|
|
3998
|
+
resolvedSchema,
|
|
3999
|
+
finalData,
|
|
4000
|
+
validators,
|
|
4001
|
+
uiSchema,
|
|
4002
|
+
isRoot
|
|
4003
|
+
),
|
|
4004
|
+
asyncErrors,
|
|
4005
|
+
extraErrors
|
|
4006
|
+
});
|
|
4007
|
+
if (saveErrors.length === 0 && onUpdate) {
|
|
3715
4008
|
const changed = {};
|
|
3716
4009
|
for (const key of Object.keys(finalData)) {
|
|
3717
4010
|
if (!isEqual(finalData[key], originalData[key])) {
|
|
@@ -3731,11 +4024,20 @@ function useFormEngine({
|
|
|
3731
4024
|
pendingChanges,
|
|
3732
4025
|
deferStateUpdates,
|
|
3733
4026
|
nonSchemaData,
|
|
3734
|
-
onUpdate
|
|
4027
|
+
onUpdate,
|
|
4028
|
+
extraErrors,
|
|
4029
|
+
asyncErrors,
|
|
4030
|
+
isValidating,
|
|
4031
|
+
uiSchema,
|
|
4032
|
+
validators,
|
|
4033
|
+
isRoot
|
|
3735
4034
|
]);
|
|
3736
4035
|
const handleCancel = useCallback7(() => {
|
|
3737
4036
|
setFormData({ ...originalData });
|
|
3738
4037
|
setPendingChanges({});
|
|
4038
|
+
setEdited({});
|
|
4039
|
+
setTouched({});
|
|
4040
|
+
setSubmitAttempted(false);
|
|
3739
4041
|
onCancel?.();
|
|
3740
4042
|
}, [originalData, onCancel]);
|
|
3741
4043
|
return {
|
|
@@ -3745,7 +4047,10 @@ function useFormEngine({
|
|
|
3745
4047
|
orderedProperties,
|
|
3746
4048
|
hasChanges,
|
|
3747
4049
|
isValid,
|
|
4050
|
+
isValidating,
|
|
3748
4051
|
fieldErrors,
|
|
4052
|
+
touched,
|
|
4053
|
+
submitAttempted,
|
|
3749
4054
|
handleFieldChange,
|
|
3750
4055
|
handleFieldBlur,
|
|
3751
4056
|
handleSave,
|
|
@@ -3754,6 +4059,10 @@ function useFormEngine({
|
|
|
3754
4059
|
}
|
|
3755
4060
|
|
|
3756
4061
|
// src/JsonSchemaFormRenderer.tsx
|
|
4062
|
+
import {
|
|
4063
|
+
useValidationDisplay,
|
|
4064
|
+
useSaveWhenInvalid
|
|
4065
|
+
} from "@petrarca/sonnet-core/config";
|
|
3757
4066
|
import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3758
4067
|
function stripFids(data) {
|
|
3759
4068
|
const result = {};
|
|
@@ -3901,10 +4210,42 @@ function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
|
|
|
3901
4210
|
);
|
|
3902
4211
|
return elements.concat(collectUnplacedFields(fields, placed));
|
|
3903
4212
|
}
|
|
4213
|
+
function rootFormData(nestedContext, ownData) {
|
|
4214
|
+
return nestedContext?.rootData ?? ownData;
|
|
4215
|
+
}
|
|
4216
|
+
function rootSubmitAttempted(nestedContext, ownSubmitAttempted) {
|
|
4217
|
+
return ownSubmitAttempted || (nestedContext?.submitAttempted ?? false);
|
|
4218
|
+
}
|
|
4219
|
+
function blurHandlerNeeded({
|
|
4220
|
+
deferStateUpdates,
|
|
4221
|
+
deferChanges,
|
|
4222
|
+
hasOnBlur,
|
|
4223
|
+
validationDisplay
|
|
4224
|
+
}) {
|
|
4225
|
+
return deferStateUpdates || deferChanges || hasOnBlur || validationDisplay === "onTouched";
|
|
4226
|
+
}
|
|
4227
|
+
function fieldErrorsVisible(validationDisplay, submitAttempted, isTouched) {
|
|
4228
|
+
if (validationDisplay === "always") return true;
|
|
4229
|
+
if (submitAttempted) return true;
|
|
4230
|
+
return validationDisplay === "onTouched" && !!isTouched;
|
|
4231
|
+
}
|
|
3904
4232
|
function pointerRoot(pointer) {
|
|
3905
4233
|
const token = pointer.split("/")[1] ?? "";
|
|
3906
4234
|
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3907
4235
|
}
|
|
4236
|
+
function fieldRawErrors(fieldName, fieldErrors, extraErrors, gated) {
|
|
4237
|
+
const forField = fieldErrors.filter(
|
|
4238
|
+
(e) => pointerRoot(e.pointer) === fieldName
|
|
4239
|
+
);
|
|
4240
|
+
if (forField.length === 0) return void 0;
|
|
4241
|
+
const extraMessages = new Set(
|
|
4242
|
+
(extraErrors ?? []).filter((e) => pointerRoot(e.pointer) === fieldName).map((e) => e.message)
|
|
4243
|
+
);
|
|
4244
|
+
const always = forField.filter((e) => extraMessages.has(e.message)).map((e) => e.message);
|
|
4245
|
+
const rest = gated ? forField.filter((e) => !extraMessages.has(e.message)).map((e) => e.message) : [];
|
|
4246
|
+
const all = [...always, ...rest];
|
|
4247
|
+
return all.length > 0 ? all : void 0;
|
|
4248
|
+
}
|
|
3908
4249
|
function groupErrorCount(tab, fieldErrors) {
|
|
3909
4250
|
if (fieldErrors.length === 0) return 0;
|
|
3910
4251
|
const owned = new Set(tab.rows.flatMap((row) => row.fields));
|
|
@@ -3942,6 +4283,23 @@ function HiddenErrorNotice({
|
|
|
3942
4283
|
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
4284
|
] });
|
|
3944
4285
|
}
|
|
4286
|
+
function FormLevelErrors({
|
|
4287
|
+
messages
|
|
4288
|
+
}) {
|
|
4289
|
+
if (messages.length === 0) return null;
|
|
4290
|
+
return /* @__PURE__ */ jsx37("div", { className: "space-y-1", children: messages.map((msg, i) => /* @__PURE__ */ jsxs23(
|
|
4291
|
+
"p",
|
|
4292
|
+
{
|
|
4293
|
+
role: "alert",
|
|
4294
|
+
className: "flex items-center gap-1.5 text-sm text-destructive",
|
|
4295
|
+
children: [
|
|
4296
|
+
/* @__PURE__ */ jsx37(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
|
|
4297
|
+
msg
|
|
4298
|
+
]
|
|
4299
|
+
},
|
|
4300
|
+
i
|
|
4301
|
+
)) });
|
|
4302
|
+
}
|
|
3945
4303
|
function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, formData, GroupTemplate) {
|
|
3946
4304
|
const fieldMap = buildFieldMap(fields);
|
|
3947
4305
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -3973,11 +4331,14 @@ function FormContainer({
|
|
|
3973
4331
|
disabled,
|
|
3974
4332
|
hasChanges,
|
|
3975
4333
|
isValid,
|
|
4334
|
+
isValidating,
|
|
4335
|
+
saveWhenInvalid,
|
|
3976
4336
|
saveLabel,
|
|
3977
4337
|
cancelLabel,
|
|
3978
4338
|
showCancel,
|
|
3979
4339
|
leadingActions,
|
|
3980
4340
|
hiddenErrorCount,
|
|
4341
|
+
formLevelErrors,
|
|
3981
4342
|
children
|
|
3982
4343
|
}) {
|
|
3983
4344
|
if (displayContents) {
|
|
@@ -3987,6 +4348,7 @@ function FormContainer({
|
|
|
3987
4348
|
children,
|
|
3988
4349
|
showExtraProperties && /* @__PURE__ */ jsx37(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
|
|
3989
4350
|
/* @__PURE__ */ jsx37(HiddenErrorNotice, { count: hiddenErrorCount }),
|
|
4351
|
+
/* @__PURE__ */ jsx37(FormLevelErrors, { messages: formLevelErrors }),
|
|
3990
4352
|
showActions && /* @__PURE__ */ jsx37(
|
|
3991
4353
|
FormActions,
|
|
3992
4354
|
{
|
|
@@ -3995,6 +4357,8 @@ function FormContainer({
|
|
|
3995
4357
|
disabled,
|
|
3996
4358
|
hasChanges,
|
|
3997
4359
|
isValid,
|
|
4360
|
+
isValidating,
|
|
4361
|
+
saveWhenInvalid,
|
|
3998
4362
|
saveLabel,
|
|
3999
4363
|
cancelLabel,
|
|
4000
4364
|
showCancel,
|
|
@@ -4013,9 +4377,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4013
4377
|
onUpdate,
|
|
4014
4378
|
onCancel,
|
|
4015
4379
|
templates,
|
|
4380
|
+
validators,
|
|
4016
4381
|
leadingActions,
|
|
4017
4382
|
idPrefix = "field"
|
|
4018
4383
|
} = props;
|
|
4384
|
+
const nestedFormContext = useContext2(NestedFormContext);
|
|
4385
|
+
const isRootForm = nestedFormContext === null;
|
|
4019
4386
|
const {
|
|
4020
4387
|
disabled,
|
|
4021
4388
|
readOnly,
|
|
@@ -4036,7 +4403,10 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4036
4403
|
orderedProperties,
|
|
4037
4404
|
hasChanges,
|
|
4038
4405
|
isValid,
|
|
4406
|
+
isValidating,
|
|
4039
4407
|
fieldErrors,
|
|
4408
|
+
touched,
|
|
4409
|
+
submitAttempted,
|
|
4040
4410
|
handleFieldChange,
|
|
4041
4411
|
handleFieldBlur,
|
|
4042
4412
|
handleSave,
|
|
@@ -4055,17 +4425,20 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4055
4425
|
deferChanges,
|
|
4056
4426
|
deferStateUpdates,
|
|
4057
4427
|
allowPristineSave,
|
|
4058
|
-
refResolver: props.refResolver
|
|
4428
|
+
refResolver: props.refResolver,
|
|
4429
|
+
extraErrors: props.extraErrors,
|
|
4430
|
+
validators,
|
|
4431
|
+
isRoot: isRootForm
|
|
4059
4432
|
});
|
|
4060
4433
|
const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
|
|
4061
|
-
const globalOptions =
|
|
4434
|
+
const globalOptions = useMemo9(
|
|
4062
4435
|
() => ({
|
|
4063
4436
|
...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
|
|
4064
4437
|
...uiSchema["x-ui-globalOptions"] || {}
|
|
4065
4438
|
}),
|
|
4066
4439
|
[layoutLabelPlacement, uiSchema]
|
|
4067
4440
|
);
|
|
4068
|
-
const resolvedTemplates =
|
|
4441
|
+
const resolvedTemplates = useMemo9(
|
|
4069
4442
|
() => ({ ...DEFAULT_TEMPLATES, ...templates }),
|
|
4070
4443
|
[templates]
|
|
4071
4444
|
);
|
|
@@ -4075,21 +4448,47 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4075
4448
|
{
|
|
4076
4449
|
...props2,
|
|
4077
4450
|
templates: resolvedTemplates,
|
|
4451
|
+
validators,
|
|
4078
4452
|
showActions: false,
|
|
4079
4453
|
showExtraProperties: false
|
|
4080
4454
|
}
|
|
4081
4455
|
),
|
|
4082
|
-
[resolvedTemplates]
|
|
4456
|
+
[resolvedTemplates, validators]
|
|
4083
4457
|
);
|
|
4084
|
-
const
|
|
4085
|
-
|
|
4086
|
-
|
|
4458
|
+
const rootData = rootFormData(nestedFormContext, displayData);
|
|
4459
|
+
const effectiveSubmitAttempted = rootSubmitAttempted(
|
|
4460
|
+
nestedFormContext,
|
|
4461
|
+
submitAttempted
|
|
4087
4462
|
);
|
|
4088
|
-
const
|
|
4089
|
-
|
|
4463
|
+
const contextValue = useMemo9(
|
|
4464
|
+
() => ({
|
|
4465
|
+
renderer: nestedRenderer,
|
|
4466
|
+
templates: resolvedTemplates,
|
|
4467
|
+
rootData,
|
|
4468
|
+
submitAttempted: effectiveSubmitAttempted
|
|
4469
|
+
}),
|
|
4470
|
+
[nestedRenderer, resolvedTemplates, rootData, effectiveSubmitAttempted]
|
|
4471
|
+
);
|
|
4472
|
+
const validationDisplay = useValidationDisplay(props.validationDisplay);
|
|
4473
|
+
const saveWhenInvalid = useSaveWhenInvalid(props.saveWhenInvalid);
|
|
4474
|
+
const needsBlurHandler = blurHandlerNeeded({
|
|
4475
|
+
deferStateUpdates,
|
|
4476
|
+
deferChanges,
|
|
4477
|
+
hasOnBlur: !!onBlur,
|
|
4478
|
+
validationDisplay
|
|
4479
|
+
});
|
|
4480
|
+
const showFieldErrors = useCallback8(
|
|
4481
|
+
(fieldName) => fieldErrorsVisible(
|
|
4482
|
+
validationDisplay,
|
|
4483
|
+
effectiveSubmitAttempted,
|
|
4484
|
+
touched[fieldName]
|
|
4485
|
+
),
|
|
4486
|
+
[validationDisplay, effectiveSubmitAttempted, touched]
|
|
4487
|
+
);
|
|
4488
|
+
const fields = useMemo9(
|
|
4090
4489
|
() => orderedProperties.flatMap(([fieldName, property]) => {
|
|
4091
4490
|
const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
|
|
4092
|
-
const { visible, enabled } = evaluateUiRule(rule,
|
|
4491
|
+
const { visible, enabled } = evaluateUiRule(rule, rootData);
|
|
4093
4492
|
if (!visible) return [];
|
|
4094
4493
|
return [
|
|
4095
4494
|
/* @__PURE__ */ jsx37(
|
|
@@ -4108,7 +4507,13 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4108
4507
|
registry: widgets,
|
|
4109
4508
|
globalOptions,
|
|
4110
4509
|
formData: displayData,
|
|
4111
|
-
idPrefix
|
|
4510
|
+
idPrefix,
|
|
4511
|
+
rawErrors: fieldRawErrors(
|
|
4512
|
+
fieldName,
|
|
4513
|
+
fieldErrors,
|
|
4514
|
+
props.extraErrors,
|
|
4515
|
+
showFieldErrors(fieldName)
|
|
4516
|
+
)
|
|
4112
4517
|
},
|
|
4113
4518
|
fieldName
|
|
4114
4519
|
)
|
|
@@ -4117,6 +4522,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4117
4522
|
[
|
|
4118
4523
|
orderedProperties,
|
|
4119
4524
|
displayData,
|
|
4525
|
+
rootData,
|
|
4120
4526
|
resolvedSchema,
|
|
4121
4527
|
disabled,
|
|
4122
4528
|
readOnly,
|
|
@@ -4126,6 +4532,9 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4126
4532
|
globalOptions,
|
|
4127
4533
|
idPrefix,
|
|
4128
4534
|
needsBlurHandler,
|
|
4535
|
+
showFieldErrors,
|
|
4536
|
+
fieldErrors,
|
|
4537
|
+
props.extraErrors,
|
|
4129
4538
|
handleFieldChange,
|
|
4130
4539
|
handleFieldBlur
|
|
4131
4540
|
]
|
|
@@ -4138,14 +4547,15 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4138
4547
|
resolvedTemplates,
|
|
4139
4548
|
layoutConfig?.variant
|
|
4140
4549
|
);
|
|
4141
|
-
const layoutFields =
|
|
4550
|
+
const layoutFields = useMemo9(() => {
|
|
4142
4551
|
if (groupDefs && groupDefs.length > 0) {
|
|
4143
4552
|
return layoutFieldsInGroups(
|
|
4144
4553
|
fields,
|
|
4145
4554
|
groupDefs,
|
|
4146
4555
|
defaultRowGap,
|
|
4147
4556
|
fieldErrors,
|
|
4148
|
-
|
|
4557
|
+
// Tab rules are x-ui-rule too: root-anchored like field rules.
|
|
4558
|
+
rootData,
|
|
4149
4559
|
groupTemplate
|
|
4150
4560
|
);
|
|
4151
4561
|
}
|
|
@@ -4159,15 +4569,19 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4159
4569
|
groupDefs,
|
|
4160
4570
|
defaultRowGap,
|
|
4161
4571
|
fieldErrors,
|
|
4162
|
-
|
|
4572
|
+
rootData,
|
|
4163
4573
|
groupTemplate
|
|
4164
4574
|
]);
|
|
4165
|
-
const hiddenErrorCount =
|
|
4575
|
+
const hiddenErrorCount = useMemo9(() => {
|
|
4166
4576
|
if (!groupDefs?.length || fieldErrors.length === 0) return 0;
|
|
4167
|
-
const hidden = hiddenGroupFieldNames(groupDefs,
|
|
4577
|
+
const hidden = hiddenGroupFieldNames(groupDefs, rootData);
|
|
4168
4578
|
if (hidden.size === 0) return 0;
|
|
4169
4579
|
return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
|
|
4170
|
-
}, [groupDefs, fieldErrors,
|
|
4580
|
+
}, [groupDefs, fieldErrors, rootData]);
|
|
4581
|
+
const formLevelErrors = useMemo9(() => {
|
|
4582
|
+
if (validationDisplay !== "always" && !submitAttempted) return [];
|
|
4583
|
+
return fieldErrors.filter((e) => e.pointer === "").map((e) => e.message);
|
|
4584
|
+
}, [fieldErrors, validationDisplay, submitAttempted]);
|
|
4171
4585
|
return /* @__PURE__ */ jsx37(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx37(
|
|
4172
4586
|
FormContainer,
|
|
4173
4587
|
{
|
|
@@ -4181,20 +4595,23 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4181
4595
|
disabled,
|
|
4182
4596
|
hasChanges: hasChanges || allowPristineSave,
|
|
4183
4597
|
isValid,
|
|
4598
|
+
isValidating,
|
|
4599
|
+
saveWhenInvalid,
|
|
4184
4600
|
saveLabel,
|
|
4185
4601
|
cancelLabel,
|
|
4186
4602
|
showCancel,
|
|
4187
4603
|
leadingActions,
|
|
4188
4604
|
hiddenErrorCount,
|
|
4605
|
+
formLevelErrors,
|
|
4189
4606
|
children: layoutFields
|
|
4190
4607
|
}
|
|
4191
4608
|
) });
|
|
4192
4609
|
}
|
|
4193
4610
|
|
|
4194
4611
|
// src/hooks/useResolvedSchema.ts
|
|
4195
|
-
import { useMemo as
|
|
4612
|
+
import { useMemo as useMemo10 } from "react";
|
|
4196
4613
|
import {
|
|
4197
|
-
getSchemaProperties as
|
|
4614
|
+
getSchemaProperties as getSchemaProperties5,
|
|
4198
4615
|
resolveSchema as resolveSchema2
|
|
4199
4616
|
} from "@petrarca/sonnet-core/schema";
|
|
4200
4617
|
function useResolvedSchema({
|
|
@@ -4202,12 +4619,12 @@ function useResolvedSchema({
|
|
|
4202
4619
|
formData,
|
|
4203
4620
|
uiSchema
|
|
4204
4621
|
}) {
|
|
4205
|
-
const resolvedSchema =
|
|
4622
|
+
const resolvedSchema = useMemo10(
|
|
4206
4623
|
() => resolveSchema2(schema, formData),
|
|
4207
4624
|
[schema, formData]
|
|
4208
4625
|
);
|
|
4209
|
-
const orderedProperties =
|
|
4210
|
-
const properties =
|
|
4626
|
+
const orderedProperties = useMemo10(() => {
|
|
4627
|
+
const properties = getSchemaProperties5(resolvedSchema);
|
|
4211
4628
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
4212
4629
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
4213
4630
|
return orderProperties(properties, orderArray);
|