@petrarca/sonnet-forms 0.7.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 +2 -2
- package/dist/index.d.ts +126 -4
- package/dist/index.js +391 -59
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1477,6 +1477,10 @@ 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,
|
|
@@ -1487,14 +1491,39 @@ function FormActions({
|
|
|
1487
1491
|
saveLabel = "Save",
|
|
1488
1492
|
cancelLabel = "Cancel",
|
|
1489
1493
|
showCancel = true,
|
|
1494
|
+
isValidating = false,
|
|
1490
1495
|
leadingActions
|
|
1491
1496
|
}) {
|
|
1492
|
-
const saveDisabled =
|
|
1497
|
+
const saveDisabled = isSaveDisabled({
|
|
1498
|
+
disabled,
|
|
1499
|
+
hasChanges,
|
|
1500
|
+
isValid,
|
|
1501
|
+
isValidating,
|
|
1502
|
+
saveWhenInvalid
|
|
1503
|
+
});
|
|
1504
|
+
const keepFocus = (e) => e.preventDefault();
|
|
1493
1505
|
return /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between mt-4", children: [
|
|
1494
1506
|
/* @__PURE__ */ jsx10("div", { className: "flex items-center gap-2", children: leadingActions }),
|
|
1495
1507
|
/* @__PURE__ */ jsxs9(SimpleGroup, { justify: "flex-end", children: [
|
|
1496
|
-
showCancel && /* @__PURE__ */ jsx10(
|
|
1497
|
-
|
|
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
|
+
)
|
|
1498
1527
|
] })
|
|
1499
1528
|
] });
|
|
1500
1529
|
}
|
|
@@ -1535,7 +1564,7 @@ function ExtraPropertiesCard({ properties, schema }) {
|
|
|
1535
1564
|
var ExtraPropertiesCard_default = ExtraPropertiesCard;
|
|
1536
1565
|
|
|
1537
1566
|
// src/JsonSchemaFormRenderer.tsx
|
|
1538
|
-
import React13, { useCallback as useCallback8, useContext as useContext2, useMemo as
|
|
1567
|
+
import React13, { useCallback as useCallback8, useContext as useContext2, useMemo as useMemo9 } from "react";
|
|
1539
1568
|
import { SimpleStack } from "@petrarca/sonnet-ui";
|
|
1540
1569
|
import { AlertCircle as AlertCircle2 } from "lucide-react";
|
|
1541
1570
|
import {
|
|
@@ -3471,19 +3500,211 @@ function FormFieldRenderer({
|
|
|
3471
3500
|
}
|
|
3472
3501
|
|
|
3473
3502
|
// src/hooks/useFormEngine.ts
|
|
3474
|
-
import { useState as
|
|
3503
|
+
import { useState as useState10, useEffect as useEffect4, useMemo as useMemo8, useCallback as useCallback7, useRef as useRef6 } from "react";
|
|
3475
3504
|
import { isEqual } from "@petrarca/sonnet-core";
|
|
3476
3505
|
import {
|
|
3477
|
-
getSchemaProperties,
|
|
3506
|
+
getSchemaProperties as getSchemaProperties4,
|
|
3478
3507
|
getFieldDefault,
|
|
3479
3508
|
applySchemaDefaults as applySchemaDefaults2,
|
|
3480
3509
|
resolveSchema,
|
|
3481
3510
|
resolveRefs,
|
|
3482
3511
|
validateFormData,
|
|
3512
|
+
mergeFieldErrors,
|
|
3483
3513
|
diffFormData,
|
|
3484
3514
|
ensureFormItemIds
|
|
3485
3515
|
} from "@petrarca/sonnet-core/schema";
|
|
3486
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
|
+
|
|
3487
3708
|
// src/hooks/orderProperties.ts
|
|
3488
3709
|
function orderProperties(properties, orderArray) {
|
|
3489
3710
|
const allEntries = Object.entries(properties);
|
|
@@ -3527,6 +3748,11 @@ function computeBaselineUpdate(newData, originalData, initialized, schemaPropert
|
|
|
3527
3748
|
}
|
|
3528
3749
|
return null;
|
|
3529
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
|
+
}
|
|
3530
3756
|
function useFormEngine({
|
|
3531
3757
|
schema,
|
|
3532
3758
|
data,
|
|
@@ -3541,51 +3767,54 @@ function useFormEngine({
|
|
|
3541
3767
|
deferChanges = false,
|
|
3542
3768
|
deferStateUpdates = false,
|
|
3543
3769
|
allowPristineSave = false,
|
|
3544
|
-
refResolver
|
|
3770
|
+
refResolver,
|
|
3771
|
+
extraErrors,
|
|
3772
|
+
validators,
|
|
3773
|
+
isRoot = true
|
|
3545
3774
|
}) {
|
|
3546
|
-
const refResolvedSchema =
|
|
3775
|
+
const refResolvedSchema = useMemo8(
|
|
3547
3776
|
() => resolveRefs(schema, refResolver),
|
|
3548
3777
|
[schema, refResolver]
|
|
3549
3778
|
);
|
|
3550
|
-
const seed =
|
|
3779
|
+
const seed = useState10(
|
|
3551
3780
|
() => ensureFormItemIds(
|
|
3552
3781
|
applySchemaDefaults2(data, refResolvedSchema),
|
|
3553
3782
|
refResolvedSchema
|
|
3554
3783
|
)
|
|
3555
3784
|
)[0];
|
|
3556
|
-
const [originalData, setOriginalData] =
|
|
3785
|
+
const [originalData, setOriginalData] = useState10(
|
|
3557
3786
|
() => seed
|
|
3558
3787
|
);
|
|
3559
|
-
const [originalDataInitialized, setOriginalDataInitialized] =
|
|
3560
|
-
const [nonSchemaData, setNonSchemaData] =
|
|
3788
|
+
const [originalDataInitialized, setOriginalDataInitialized] = useState10(false);
|
|
3789
|
+
const [nonSchemaData, setNonSchemaData] = useState10(
|
|
3561
3790
|
() => {
|
|
3562
|
-
const props =
|
|
3791
|
+
const props = getSchemaProperties4(refResolvedSchema);
|
|
3563
3792
|
const names = Object.keys(props);
|
|
3564
3793
|
return Object.fromEntries(
|
|
3565
3794
|
Object.entries(data).filter(([k]) => !names.includes(k))
|
|
3566
3795
|
);
|
|
3567
3796
|
}
|
|
3568
3797
|
);
|
|
3569
|
-
const [formData, setFormData] =
|
|
3570
|
-
const formDataRef =
|
|
3798
|
+
const [formData, setFormData] = useState10(() => seed);
|
|
3799
|
+
const formDataRef = useRef6(formData);
|
|
3571
3800
|
formDataRef.current = formData;
|
|
3572
|
-
const [pendingChanges, setPendingChanges] =
|
|
3573
|
-
const resolvedSchema =
|
|
3801
|
+
const [pendingChanges, setPendingChanges] = useState10({});
|
|
3802
|
+
const resolvedSchema = useMemo8(
|
|
3574
3803
|
() => resolveSchema(refResolvedSchema, formData),
|
|
3575
3804
|
[refResolvedSchema, formData]
|
|
3576
3805
|
);
|
|
3577
|
-
const orderedProperties =
|
|
3578
|
-
const properties2 =
|
|
3806
|
+
const orderedProperties = useMemo8(() => {
|
|
3807
|
+
const properties2 = getSchemaProperties4(resolvedSchema);
|
|
3579
3808
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
3580
3809
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
3581
3810
|
return orderProperties(properties2, orderArray);
|
|
3582
3811
|
}, [resolvedSchema, uiSchema]);
|
|
3583
|
-
const [initialDefaultsSent, setInitialDefaultsSent] =
|
|
3584
|
-
const [edited, setEdited] =
|
|
3585
|
-
const [touched, setTouched] =
|
|
3586
|
-
const [submitAttempted, setSubmitAttempted] =
|
|
3587
|
-
const properties =
|
|
3588
|
-
|
|
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(() => {
|
|
3589
3818
|
if (!initialDefaultsSent) {
|
|
3590
3819
|
const hasDefaults = Object.entries(properties).some(
|
|
3591
3820
|
([fieldName, property]) => data[fieldName] === void 0 && getFieldDefault(property) !== void 0
|
|
@@ -3596,7 +3825,7 @@ function useFormEngine({
|
|
|
3596
3825
|
setInitialDefaultsSent(true);
|
|
3597
3826
|
}
|
|
3598
3827
|
}, [initialDefaultsSent, properties, data, formData, onChange]);
|
|
3599
|
-
|
|
3828
|
+
useEffect4(() => {
|
|
3600
3829
|
const currentPropertyNames = Object.keys(properties);
|
|
3601
3830
|
const keysToRemove = Object.keys(formData).filter(
|
|
3602
3831
|
(k) => !currentPropertyNames.includes(k)
|
|
@@ -3610,8 +3839,8 @@ function useFormEngine({
|
|
|
3610
3839
|
if (onChange) onChange(cleanedData);
|
|
3611
3840
|
}
|
|
3612
3841
|
}, [properties, formData, onChange]);
|
|
3613
|
-
const prevDataRef =
|
|
3614
|
-
|
|
3842
|
+
const prevDataRef = useRef6(data);
|
|
3843
|
+
useEffect4(() => {
|
|
3615
3844
|
if (isEqual(prevDataRef.current, data)) return;
|
|
3616
3845
|
prevDataRef.current = data;
|
|
3617
3846
|
const newData = ensureFormItemIds(
|
|
@@ -3641,24 +3870,45 @@ function useFormEngine({
|
|
|
3641
3870
|
originalData,
|
|
3642
3871
|
originalDataInitialized
|
|
3643
3872
|
]);
|
|
3644
|
-
const hasChanges =
|
|
3873
|
+
const hasChanges = useMemo8(
|
|
3645
3874
|
() => !isEqual(formData, originalData),
|
|
3646
3875
|
[formData, originalData]
|
|
3647
3876
|
);
|
|
3648
|
-
const displayData =
|
|
3877
|
+
const displayData = useMemo8(() => {
|
|
3649
3878
|
if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
|
|
3650
3879
|
return { ...formData, ...pendingChanges };
|
|
3651
3880
|
}
|
|
3652
3881
|
return formData;
|
|
3653
3882
|
}, [formData, pendingChanges, deferStateUpdates]);
|
|
3654
|
-
const
|
|
3655
|
-
() =>
|
|
3656
|
-
[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
|
|
3657
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]);
|
|
3658
3908
|
const { isValid, fieldErrors } = validation;
|
|
3659
|
-
const onValidationChangeRef =
|
|
3909
|
+
const onValidationChangeRef = useRef6(onValidationChange);
|
|
3660
3910
|
onValidationChangeRef.current = onValidationChange;
|
|
3661
|
-
|
|
3911
|
+
useEffect4(() => {
|
|
3662
3912
|
onValidationChangeRef.current?.(validation.errors, validation.isValid);
|
|
3663
3913
|
}, [validation]);
|
|
3664
3914
|
const handleFieldChange = useCallback7(
|
|
@@ -3738,10 +3988,23 @@ function useFormEngine({
|
|
|
3738
3988
|
const handleSave = useCallback7(() => {
|
|
3739
3989
|
setSubmitAttempted(true);
|
|
3740
3990
|
if (!hasChanges && !allowPristineSave) return;
|
|
3991
|
+
if (isValidating) return;
|
|
3741
3992
|
const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
|
|
3742
3993
|
const completeData = { ...nonSchemaData, ...finalData };
|
|
3743
|
-
const
|
|
3744
|
-
|
|
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) {
|
|
3745
4008
|
const changed = {};
|
|
3746
4009
|
for (const key of Object.keys(finalData)) {
|
|
3747
4010
|
if (!isEqual(finalData[key], originalData[key])) {
|
|
@@ -3761,7 +4024,13 @@ function useFormEngine({
|
|
|
3761
4024
|
pendingChanges,
|
|
3762
4025
|
deferStateUpdates,
|
|
3763
4026
|
nonSchemaData,
|
|
3764
|
-
onUpdate
|
|
4027
|
+
onUpdate,
|
|
4028
|
+
extraErrors,
|
|
4029
|
+
asyncErrors,
|
|
4030
|
+
isValidating,
|
|
4031
|
+
uiSchema,
|
|
4032
|
+
validators,
|
|
4033
|
+
isRoot
|
|
3765
4034
|
]);
|
|
3766
4035
|
const handleCancel = useCallback7(() => {
|
|
3767
4036
|
setFormData({ ...originalData });
|
|
@@ -3778,6 +4047,7 @@ function useFormEngine({
|
|
|
3778
4047
|
orderedProperties,
|
|
3779
4048
|
hasChanges,
|
|
3780
4049
|
isValid,
|
|
4050
|
+
isValidating,
|
|
3781
4051
|
fieldErrors,
|
|
3782
4052
|
touched,
|
|
3783
4053
|
submitAttempted,
|
|
@@ -3940,8 +4210,11 @@ function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
|
|
|
3940
4210
|
);
|
|
3941
4211
|
return elements.concat(collectUnplacedFields(fields, placed));
|
|
3942
4212
|
}
|
|
3943
|
-
function
|
|
3944
|
-
return
|
|
4213
|
+
function rootFormData(nestedContext, ownData) {
|
|
4214
|
+
return nestedContext?.rootData ?? ownData;
|
|
4215
|
+
}
|
|
4216
|
+
function rootSubmitAttempted(nestedContext, ownSubmitAttempted) {
|
|
4217
|
+
return ownSubmitAttempted || (nestedContext?.submitAttempted ?? false);
|
|
3945
4218
|
}
|
|
3946
4219
|
function blurHandlerNeeded({
|
|
3947
4220
|
deferStateUpdates,
|
|
@@ -3960,6 +4233,19 @@ function pointerRoot(pointer) {
|
|
|
3960
4233
|
const token = pointer.split("/")[1] ?? "";
|
|
3961
4234
|
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3962
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
|
+
}
|
|
3963
4249
|
function groupErrorCount(tab, fieldErrors) {
|
|
3964
4250
|
if (fieldErrors.length === 0) return 0;
|
|
3965
4251
|
const owned = new Set(tab.rows.flatMap((row) => row.fields));
|
|
@@ -3997,6 +4283,23 @@ function HiddenErrorNotice({
|
|
|
3997
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.`
|
|
3998
4284
|
] });
|
|
3999
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
|
+
}
|
|
4000
4303
|
function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, formData, GroupTemplate) {
|
|
4001
4304
|
const fieldMap = buildFieldMap(fields);
|
|
4002
4305
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -4028,12 +4331,14 @@ function FormContainer({
|
|
|
4028
4331
|
disabled,
|
|
4029
4332
|
hasChanges,
|
|
4030
4333
|
isValid,
|
|
4334
|
+
isValidating,
|
|
4031
4335
|
saveWhenInvalid,
|
|
4032
4336
|
saveLabel,
|
|
4033
4337
|
cancelLabel,
|
|
4034
4338
|
showCancel,
|
|
4035
4339
|
leadingActions,
|
|
4036
4340
|
hiddenErrorCount,
|
|
4341
|
+
formLevelErrors,
|
|
4037
4342
|
children
|
|
4038
4343
|
}) {
|
|
4039
4344
|
if (displayContents) {
|
|
@@ -4043,6 +4348,7 @@ function FormContainer({
|
|
|
4043
4348
|
children,
|
|
4044
4349
|
showExtraProperties && /* @__PURE__ */ jsx37(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
|
|
4045
4350
|
/* @__PURE__ */ jsx37(HiddenErrorNotice, { count: hiddenErrorCount }),
|
|
4351
|
+
/* @__PURE__ */ jsx37(FormLevelErrors, { messages: formLevelErrors }),
|
|
4046
4352
|
showActions && /* @__PURE__ */ jsx37(
|
|
4047
4353
|
FormActions,
|
|
4048
4354
|
{
|
|
@@ -4051,6 +4357,7 @@ function FormContainer({
|
|
|
4051
4357
|
disabled,
|
|
4052
4358
|
hasChanges,
|
|
4053
4359
|
isValid,
|
|
4360
|
+
isValidating,
|
|
4054
4361
|
saveWhenInvalid,
|
|
4055
4362
|
saveLabel,
|
|
4056
4363
|
cancelLabel,
|
|
@@ -4070,9 +4377,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4070
4377
|
onUpdate,
|
|
4071
4378
|
onCancel,
|
|
4072
4379
|
templates,
|
|
4380
|
+
validators,
|
|
4073
4381
|
leadingActions,
|
|
4074
4382
|
idPrefix = "field"
|
|
4075
4383
|
} = props;
|
|
4384
|
+
const nestedFormContext = useContext2(NestedFormContext);
|
|
4385
|
+
const isRootForm = nestedFormContext === null;
|
|
4076
4386
|
const {
|
|
4077
4387
|
disabled,
|
|
4078
4388
|
readOnly,
|
|
@@ -4093,6 +4403,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4093
4403
|
orderedProperties,
|
|
4094
4404
|
hasChanges,
|
|
4095
4405
|
isValid,
|
|
4406
|
+
isValidating,
|
|
4096
4407
|
fieldErrors,
|
|
4097
4408
|
touched,
|
|
4098
4409
|
submitAttempted,
|
|
@@ -4114,17 +4425,20 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4114
4425
|
deferChanges,
|
|
4115
4426
|
deferStateUpdates,
|
|
4116
4427
|
allowPristineSave,
|
|
4117
|
-
refResolver: props.refResolver
|
|
4428
|
+
refResolver: props.refResolver,
|
|
4429
|
+
extraErrors: props.extraErrors,
|
|
4430
|
+
validators,
|
|
4431
|
+
isRoot: isRootForm
|
|
4118
4432
|
});
|
|
4119
4433
|
const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
|
|
4120
|
-
const globalOptions =
|
|
4434
|
+
const globalOptions = useMemo9(
|
|
4121
4435
|
() => ({
|
|
4122
4436
|
...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
|
|
4123
4437
|
...uiSchema["x-ui-globalOptions"] || {}
|
|
4124
4438
|
}),
|
|
4125
4439
|
[layoutLabelPlacement, uiSchema]
|
|
4126
4440
|
);
|
|
4127
|
-
const resolvedTemplates =
|
|
4441
|
+
const resolvedTemplates = useMemo9(
|
|
4128
4442
|
() => ({ ...DEFAULT_TEMPLATES, ...templates }),
|
|
4129
4443
|
[templates]
|
|
4130
4444
|
);
|
|
@@ -4134,20 +4448,26 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4134
4448
|
{
|
|
4135
4449
|
...props2,
|
|
4136
4450
|
templates: resolvedTemplates,
|
|
4451
|
+
validators,
|
|
4137
4452
|
showActions: false,
|
|
4138
4453
|
showExtraProperties: false
|
|
4139
4454
|
}
|
|
4140
4455
|
),
|
|
4141
|
-
[resolvedTemplates]
|
|
4456
|
+
[resolvedTemplates, validators]
|
|
4457
|
+
);
|
|
4458
|
+
const rootData = rootFormData(nestedFormContext, displayData);
|
|
4459
|
+
const effectiveSubmitAttempted = rootSubmitAttempted(
|
|
4460
|
+
nestedFormContext,
|
|
4461
|
+
submitAttempted
|
|
4142
4462
|
);
|
|
4143
|
-
const
|
|
4144
|
-
const contextValue = useMemo8(
|
|
4463
|
+
const contextValue = useMemo9(
|
|
4145
4464
|
() => ({
|
|
4146
4465
|
renderer: nestedRenderer,
|
|
4147
4466
|
templates: resolvedTemplates,
|
|
4148
|
-
rootData
|
|
4467
|
+
rootData,
|
|
4468
|
+
submitAttempted: effectiveSubmitAttempted
|
|
4149
4469
|
}),
|
|
4150
|
-
[nestedRenderer, resolvedTemplates, rootData]
|
|
4470
|
+
[nestedRenderer, resolvedTemplates, rootData, effectiveSubmitAttempted]
|
|
4151
4471
|
);
|
|
4152
4472
|
const validationDisplay = useValidationDisplay(props.validationDisplay);
|
|
4153
4473
|
const saveWhenInvalid = useSaveWhenInvalid(props.saveWhenInvalid);
|
|
@@ -4160,12 +4480,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4160
4480
|
const showFieldErrors = useCallback8(
|
|
4161
4481
|
(fieldName) => fieldErrorsVisible(
|
|
4162
4482
|
validationDisplay,
|
|
4163
|
-
|
|
4483
|
+
effectiveSubmitAttempted,
|
|
4164
4484
|
touched[fieldName]
|
|
4165
4485
|
),
|
|
4166
|
-
[validationDisplay,
|
|
4486
|
+
[validationDisplay, effectiveSubmitAttempted, touched]
|
|
4167
4487
|
);
|
|
4168
|
-
const fields =
|
|
4488
|
+
const fields = useMemo9(
|
|
4169
4489
|
() => orderedProperties.flatMap(([fieldName, property]) => {
|
|
4170
4490
|
const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
|
|
4171
4491
|
const { visible, enabled } = evaluateUiRule(rule, rootData);
|
|
@@ -4188,7 +4508,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4188
4508
|
globalOptions,
|
|
4189
4509
|
formData: displayData,
|
|
4190
4510
|
idPrefix,
|
|
4191
|
-
rawErrors:
|
|
4511
|
+
rawErrors: fieldRawErrors(
|
|
4512
|
+
fieldName,
|
|
4513
|
+
fieldErrors,
|
|
4514
|
+
props.extraErrors,
|
|
4515
|
+
showFieldErrors(fieldName)
|
|
4516
|
+
)
|
|
4192
4517
|
},
|
|
4193
4518
|
fieldName
|
|
4194
4519
|
)
|
|
@@ -4209,6 +4534,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4209
4534
|
needsBlurHandler,
|
|
4210
4535
|
showFieldErrors,
|
|
4211
4536
|
fieldErrors,
|
|
4537
|
+
props.extraErrors,
|
|
4212
4538
|
handleFieldChange,
|
|
4213
4539
|
handleFieldBlur
|
|
4214
4540
|
]
|
|
@@ -4221,7 +4547,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4221
4547
|
resolvedTemplates,
|
|
4222
4548
|
layoutConfig?.variant
|
|
4223
4549
|
);
|
|
4224
|
-
const layoutFields =
|
|
4550
|
+
const layoutFields = useMemo9(() => {
|
|
4225
4551
|
if (groupDefs && groupDefs.length > 0) {
|
|
4226
4552
|
return layoutFieldsInGroups(
|
|
4227
4553
|
fields,
|
|
@@ -4246,12 +4572,16 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4246
4572
|
rootData,
|
|
4247
4573
|
groupTemplate
|
|
4248
4574
|
]);
|
|
4249
|
-
const hiddenErrorCount =
|
|
4575
|
+
const hiddenErrorCount = useMemo9(() => {
|
|
4250
4576
|
if (!groupDefs?.length || fieldErrors.length === 0) return 0;
|
|
4251
4577
|
const hidden = hiddenGroupFieldNames(groupDefs, rootData);
|
|
4252
4578
|
if (hidden.size === 0) return 0;
|
|
4253
4579
|
return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
|
|
4254
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]);
|
|
4255
4585
|
return /* @__PURE__ */ jsx37(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx37(
|
|
4256
4586
|
FormContainer,
|
|
4257
4587
|
{
|
|
@@ -4265,21 +4595,23 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4265
4595
|
disabled,
|
|
4266
4596
|
hasChanges: hasChanges || allowPristineSave,
|
|
4267
4597
|
isValid,
|
|
4598
|
+
isValidating,
|
|
4268
4599
|
saveWhenInvalid,
|
|
4269
4600
|
saveLabel,
|
|
4270
4601
|
cancelLabel,
|
|
4271
4602
|
showCancel,
|
|
4272
4603
|
leadingActions,
|
|
4273
4604
|
hiddenErrorCount,
|
|
4605
|
+
formLevelErrors,
|
|
4274
4606
|
children: layoutFields
|
|
4275
4607
|
}
|
|
4276
4608
|
) });
|
|
4277
4609
|
}
|
|
4278
4610
|
|
|
4279
4611
|
// src/hooks/useResolvedSchema.ts
|
|
4280
|
-
import { useMemo as
|
|
4612
|
+
import { useMemo as useMemo10 } from "react";
|
|
4281
4613
|
import {
|
|
4282
|
-
getSchemaProperties as
|
|
4614
|
+
getSchemaProperties as getSchemaProperties5,
|
|
4283
4615
|
resolveSchema as resolveSchema2
|
|
4284
4616
|
} from "@petrarca/sonnet-core/schema";
|
|
4285
4617
|
function useResolvedSchema({
|
|
@@ -4287,12 +4619,12 @@ function useResolvedSchema({
|
|
|
4287
4619
|
formData,
|
|
4288
4620
|
uiSchema
|
|
4289
4621
|
}) {
|
|
4290
|
-
const resolvedSchema =
|
|
4622
|
+
const resolvedSchema = useMemo10(
|
|
4291
4623
|
() => resolveSchema2(schema, formData),
|
|
4292
4624
|
[schema, formData]
|
|
4293
4625
|
);
|
|
4294
|
-
const orderedProperties =
|
|
4295
|
-
const properties =
|
|
4626
|
+
const orderedProperties = useMemo10(() => {
|
|
4627
|
+
const properties = getSchemaProperties5(resolvedSchema);
|
|
4296
4628
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
4297
4629
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
4298
4630
|
return orderProperties(properties, orderArray);
|