hs-uix 1.6.4 → 1.6.5
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/datatable.js +3 -4
- package/dist/datatable.mjs +3 -5
- package/dist/form.js +58 -9
- package/dist/form.mjs +58 -9
- package/dist/index.js +61 -13
- package/dist/index.mjs +63 -16
- package/package.json +1 -1
package/dist/datatable.js
CHANGED
|
@@ -187,7 +187,7 @@ var DataTable = ({
|
|
|
187
187
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
188
188
|
// Row count
|
|
189
189
|
title,
|
|
190
|
-
// optional title shown above the table toolbar
|
|
190
|
+
// optional title shown as demibold text above the table toolbar
|
|
191
191
|
showRowCount = true,
|
|
192
192
|
// show "X records" / "X of Y records" text
|
|
193
193
|
rowCountBold = false,
|
|
@@ -657,8 +657,7 @@ var DataTable = ({
|
|
|
657
657
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
658
658
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
659
659
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
660
|
-
const showToolbarCount = showRowCount &&
|
|
661
|
-
const showTitleCount = showRowCount && !!title && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
660
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
662
661
|
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
663
662
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
664
663
|
const applySelection = (0, import_react.useCallback)((nextSet) => {
|
|
@@ -1009,7 +1008,7 @@ var DataTable = ({
|
|
|
1009
1008
|
}
|
|
1010
1009
|
);
|
|
1011
1010
|
};
|
|
1012
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.
|
|
1011
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, title)), hasToolbarContent && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
|
|
1013
1012
|
import_ui_extensions.SearchInput,
|
|
1014
1013
|
{
|
|
1015
1014
|
name: "datatable-search",
|
package/dist/datatable.mjs
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
EmptyState,
|
|
11
11
|
ErrorState,
|
|
12
12
|
Flex,
|
|
13
|
-
Heading,
|
|
14
13
|
Icon,
|
|
15
14
|
Input,
|
|
16
15
|
Link,
|
|
@@ -184,7 +183,7 @@ var DataTable = ({
|
|
|
184
183
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
185
184
|
// Row count
|
|
186
185
|
title,
|
|
187
|
-
// optional title shown above the table toolbar
|
|
186
|
+
// optional title shown as demibold text above the table toolbar
|
|
188
187
|
showRowCount = true,
|
|
189
188
|
// show "X records" / "X of Y records" text
|
|
190
189
|
rowCountBold = false,
|
|
@@ -654,8 +653,7 @@ var DataTable = ({
|
|
|
654
653
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
655
654
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
656
655
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
657
|
-
const showToolbarCount = showRowCount &&
|
|
658
|
-
const showTitleCount = showRowCount && !!title && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
656
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
659
657
|
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
660
658
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
661
659
|
const applySelection = useCallback((nextSet) => {
|
|
@@ -1006,7 +1004,7 @@ var DataTable = ({
|
|
|
1006
1004
|
}
|
|
1007
1005
|
);
|
|
1008
1006
|
};
|
|
1009
|
-
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(
|
|
1007
|
+
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, title)), hasToolbarContent && /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
|
|
1010
1008
|
SearchInput,
|
|
1011
1009
|
{
|
|
1012
1010
|
name: "datatable-search",
|
package/dist/form.js
CHANGED
|
@@ -378,8 +378,12 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
378
378
|
// validate on blur
|
|
379
379
|
validateOnSubmit = true,
|
|
380
380
|
// validate all before onSubmit
|
|
381
|
-
onValidationChange
|
|
381
|
+
onValidationChange,
|
|
382
382
|
// (errors) => void
|
|
383
|
+
onValidationFail,
|
|
384
|
+
// ({ errors, fields, firstInvalidField }) => void — called when submit-time validation blocks submission
|
|
385
|
+
openSectionOnValidationFail = false
|
|
386
|
+
// auto-open accordion section containing first invalid field on submit failure
|
|
383
387
|
} = props;
|
|
384
388
|
const {
|
|
385
389
|
steps,
|
|
@@ -528,6 +532,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
528
532
|
"tooltip",
|
|
529
533
|
"required",
|
|
530
534
|
"readOnly",
|
|
535
|
+
"alwaysEditable",
|
|
531
536
|
"disabled",
|
|
532
537
|
"defaultValue",
|
|
533
538
|
"fieldProps",
|
|
@@ -700,6 +705,17 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
700
705
|
}
|
|
701
706
|
return map;
|
|
702
707
|
}, [fields]);
|
|
708
|
+
const sectionIdByFieldName = (0, import_react.useMemo)(() => {
|
|
709
|
+
const map = /* @__PURE__ */ new Map();
|
|
710
|
+
if (Array.isArray(sections)) {
|
|
711
|
+
for (const sec of sections) {
|
|
712
|
+
if (!sec || !Array.isArray(sec.fields)) continue;
|
|
713
|
+
for (const name of sec.fields) map.set(name, sec.id);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return map;
|
|
717
|
+
}, [sections]);
|
|
718
|
+
const [validationOpenSection, setValidationOpenSection] = (0, import_react.useState)(null);
|
|
703
719
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
704
720
|
const configWarningsRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
705
721
|
const warnConfig = (0, import_react.useCallback)((message) => {
|
|
@@ -1280,10 +1296,35 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1280
1296
|
const handleSubmit = (0, import_react.useCallback)(
|
|
1281
1297
|
async (e) => {
|
|
1282
1298
|
if (e && e.preventDefault) e.preventDefault();
|
|
1299
|
+
const reportValidationFailure = (errors) => {
|
|
1300
|
+
const errorNames = Object.keys(errors).filter((n) => !!errors[n]);
|
|
1301
|
+
if (errorNames.length === 0) return;
|
|
1302
|
+
const orderedNames = allVisibleFields.map((f) => f.name).filter((n) => errorNames.includes(n));
|
|
1303
|
+
for (const n of errorNames) if (!orderedNames.includes(n)) orderedNames.push(n);
|
|
1304
|
+
const fieldInfos = orderedNames.map((name) => {
|
|
1305
|
+
const f = fieldByName.get(name);
|
|
1306
|
+
return {
|
|
1307
|
+
name,
|
|
1308
|
+
label: f == null ? void 0 : f.label,
|
|
1309
|
+
sectionId: sectionIdByFieldName.get(name)
|
|
1310
|
+
};
|
|
1311
|
+
});
|
|
1312
|
+
const firstInvalidField = fieldInfos[0];
|
|
1313
|
+
if (openSectionOnValidationFail && (firstInvalidField == null ? void 0 : firstInvalidField.sectionId)) {
|
|
1314
|
+
setValidationOpenSection({
|
|
1315
|
+
id: firstInvalidField.sectionId,
|
|
1316
|
+
nonce: ((validationOpenSection == null ? void 0 : validationOpenSection.nonce) || 0) + 1
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
if (onValidationFail) {
|
|
1320
|
+
onValidationFail({ errors, fields: fieldInfos, firstInvalidField });
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1283
1323
|
if (validateOnSubmit) {
|
|
1284
1324
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
1285
1325
|
if (hasErrors) {
|
|
1286
1326
|
replaceErrors(errors);
|
|
1327
|
+
reportValidationFailure(errors);
|
|
1287
1328
|
return;
|
|
1288
1329
|
}
|
|
1289
1330
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -1295,7 +1336,10 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1295
1336
|
])
|
|
1296
1337
|
];
|
|
1297
1338
|
await Promise.all(pendingValidations);
|
|
1298
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
1339
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
1340
|
+
reportValidationFailure(formErrorsRef.current);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1299
1343
|
}
|
|
1300
1344
|
}
|
|
1301
1345
|
const reset = () => {
|
|
@@ -1341,7 +1385,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1341
1385
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1342
1386
|
}
|
|
1343
1387
|
},
|
|
1344
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
1388
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget, onValidationFail, openSectionOnValidationFail, sectionIdByFieldName, validationOpenSection]
|
|
1345
1389
|
);
|
|
1346
1390
|
const handleNext = (0, import_react.useCallback)(async () => {
|
|
1347
1391
|
if (!isMultiStep) return;
|
|
@@ -1434,8 +1478,9 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1434
1478
|
const fieldError = formErrors[field.name] || null;
|
|
1435
1479
|
const hasError = !!fieldError;
|
|
1436
1480
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
1437
|
-
const
|
|
1438
|
-
const
|
|
1481
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
1482
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
1483
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
1439
1484
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1440
1485
|
if (field.type === "display" || field.type === "slot") {
|
|
1441
1486
|
if (field.render) {
|
|
@@ -1478,8 +1523,9 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1478
1523
|
const sfValue = formValues[sf.name];
|
|
1479
1524
|
const sfError = formErrors[sf.name] || null;
|
|
1480
1525
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1526
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
1527
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
1528
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
1483
1529
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
1484
1530
|
const sfProps = {
|
|
1485
1531
|
name: sf.name,
|
|
@@ -2276,13 +2322,16 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
2276
2322
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
2277
2323
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
2278
2324
|
const accordionContent = /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields, sectionOverrides), sec.renderAfter && sec.renderAfter(sectionContext));
|
|
2325
|
+
const isValidationOverrideTarget = validationOpenSection && validationOpenSection.id === sec.id;
|
|
2326
|
+
const accordionKey = isValidationOverrideTarget ? `${sec.id}::open::${validationOpenSection.nonce}` : sec.id;
|
|
2327
|
+
const accordionDefaultOpen = isValidationOverrideTarget ? true : sec.defaultOpen !== false;
|
|
2279
2328
|
const accordion = /* @__PURE__ */ import_react.default.createElement(
|
|
2280
2329
|
import_ui_extensions.Accordion,
|
|
2281
2330
|
{
|
|
2282
|
-
key:
|
|
2331
|
+
key: accordionKey,
|
|
2283
2332
|
title: sec.label,
|
|
2284
2333
|
size: "sm",
|
|
2285
|
-
defaultOpen:
|
|
2334
|
+
defaultOpen: accordionDefaultOpen
|
|
2286
2335
|
},
|
|
2287
2336
|
accordionContent
|
|
2288
2337
|
);
|
package/dist/form.mjs
CHANGED
|
@@ -382,8 +382,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
382
382
|
// validate on blur
|
|
383
383
|
validateOnSubmit = true,
|
|
384
384
|
// validate all before onSubmit
|
|
385
|
-
onValidationChange
|
|
385
|
+
onValidationChange,
|
|
386
386
|
// (errors) => void
|
|
387
|
+
onValidationFail,
|
|
388
|
+
// ({ errors, fields, firstInvalidField }) => void — called when submit-time validation blocks submission
|
|
389
|
+
openSectionOnValidationFail = false
|
|
390
|
+
// auto-open accordion section containing first invalid field on submit failure
|
|
387
391
|
} = props;
|
|
388
392
|
const {
|
|
389
393
|
steps,
|
|
@@ -532,6 +536,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
532
536
|
"tooltip",
|
|
533
537
|
"required",
|
|
534
538
|
"readOnly",
|
|
539
|
+
"alwaysEditable",
|
|
535
540
|
"disabled",
|
|
536
541
|
"defaultValue",
|
|
537
542
|
"fieldProps",
|
|
@@ -704,6 +709,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
704
709
|
}
|
|
705
710
|
return map;
|
|
706
711
|
}, [fields]);
|
|
712
|
+
const sectionIdByFieldName = useMemo(() => {
|
|
713
|
+
const map = /* @__PURE__ */ new Map();
|
|
714
|
+
if (Array.isArray(sections)) {
|
|
715
|
+
for (const sec of sections) {
|
|
716
|
+
if (!sec || !Array.isArray(sec.fields)) continue;
|
|
717
|
+
for (const name of sec.fields) map.set(name, sec.id);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return map;
|
|
721
|
+
}, [sections]);
|
|
722
|
+
const [validationOpenSection, setValidationOpenSection] = useState(null);
|
|
707
723
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
708
724
|
const configWarningsRef = useRef(/* @__PURE__ */ new Set());
|
|
709
725
|
const warnConfig = useCallback((message) => {
|
|
@@ -1284,10 +1300,35 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1284
1300
|
const handleSubmit = useCallback(
|
|
1285
1301
|
async (e) => {
|
|
1286
1302
|
if (e && e.preventDefault) e.preventDefault();
|
|
1303
|
+
const reportValidationFailure = (errors) => {
|
|
1304
|
+
const errorNames = Object.keys(errors).filter((n) => !!errors[n]);
|
|
1305
|
+
if (errorNames.length === 0) return;
|
|
1306
|
+
const orderedNames = allVisibleFields.map((f) => f.name).filter((n) => errorNames.includes(n));
|
|
1307
|
+
for (const n of errorNames) if (!orderedNames.includes(n)) orderedNames.push(n);
|
|
1308
|
+
const fieldInfos = orderedNames.map((name) => {
|
|
1309
|
+
const f = fieldByName.get(name);
|
|
1310
|
+
return {
|
|
1311
|
+
name,
|
|
1312
|
+
label: f == null ? void 0 : f.label,
|
|
1313
|
+
sectionId: sectionIdByFieldName.get(name)
|
|
1314
|
+
};
|
|
1315
|
+
});
|
|
1316
|
+
const firstInvalidField = fieldInfos[0];
|
|
1317
|
+
if (openSectionOnValidationFail && (firstInvalidField == null ? void 0 : firstInvalidField.sectionId)) {
|
|
1318
|
+
setValidationOpenSection({
|
|
1319
|
+
id: firstInvalidField.sectionId,
|
|
1320
|
+
nonce: ((validationOpenSection == null ? void 0 : validationOpenSection.nonce) || 0) + 1
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
if (onValidationFail) {
|
|
1324
|
+
onValidationFail({ errors, fields: fieldInfos, firstInvalidField });
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1287
1327
|
if (validateOnSubmit) {
|
|
1288
1328
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
1289
1329
|
if (hasErrors) {
|
|
1290
1330
|
replaceErrors(errors);
|
|
1331
|
+
reportValidationFailure(errors);
|
|
1291
1332
|
return;
|
|
1292
1333
|
}
|
|
1293
1334
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -1299,7 +1340,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1299
1340
|
])
|
|
1300
1341
|
];
|
|
1301
1342
|
await Promise.all(pendingValidations);
|
|
1302
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
1343
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
1344
|
+
reportValidationFailure(formErrorsRef.current);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1303
1347
|
}
|
|
1304
1348
|
}
|
|
1305
1349
|
const reset = () => {
|
|
@@ -1345,7 +1389,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1345
1389
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1346
1390
|
}
|
|
1347
1391
|
},
|
|
1348
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
1392
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget, onValidationFail, openSectionOnValidationFail, sectionIdByFieldName, validationOpenSection]
|
|
1349
1393
|
);
|
|
1350
1394
|
const handleNext = useCallback(async () => {
|
|
1351
1395
|
if (!isMultiStep) return;
|
|
@@ -1438,8 +1482,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1438
1482
|
const fieldError = formErrors[field.name] || null;
|
|
1439
1483
|
const hasError = !!fieldError;
|
|
1440
1484
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
1441
|
-
const
|
|
1442
|
-
const
|
|
1485
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
1486
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
1487
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
1443
1488
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1444
1489
|
if (field.type === "display" || field.type === "slot") {
|
|
1445
1490
|
if (field.render) {
|
|
@@ -1482,8 +1527,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1482
1527
|
const sfValue = formValues[sf.name];
|
|
1483
1528
|
const sfError = formErrors[sf.name] || null;
|
|
1484
1529
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
1485
|
-
const
|
|
1486
|
-
const
|
|
1530
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
1531
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
1532
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
1487
1533
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
1488
1534
|
const sfProps = {
|
|
1489
1535
|
name: sf.name,
|
|
@@ -2280,13 +2326,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2280
2326
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
2281
2327
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
2282
2328
|
const accordionContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields, sectionOverrides), sec.renderAfter && sec.renderAfter(sectionContext));
|
|
2329
|
+
const isValidationOverrideTarget = validationOpenSection && validationOpenSection.id === sec.id;
|
|
2330
|
+
const accordionKey = isValidationOverrideTarget ? `${sec.id}::open::${validationOpenSection.nonce}` : sec.id;
|
|
2331
|
+
const accordionDefaultOpen = isValidationOverrideTarget ? true : sec.defaultOpen !== false;
|
|
2283
2332
|
const accordion = /* @__PURE__ */ React.createElement(
|
|
2284
2333
|
Accordion,
|
|
2285
2334
|
{
|
|
2286
|
-
key:
|
|
2335
|
+
key: accordionKey,
|
|
2287
2336
|
title: sec.label,
|
|
2288
2337
|
size: "sm",
|
|
2289
|
-
defaultOpen:
|
|
2338
|
+
defaultOpen: accordionDefaultOpen
|
|
2290
2339
|
},
|
|
2291
2340
|
accordionContent
|
|
2292
2341
|
);
|
package/dist/index.js
CHANGED
|
@@ -218,7 +218,7 @@ var DataTable = ({
|
|
|
218
218
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
219
219
|
// Row count
|
|
220
220
|
title,
|
|
221
|
-
// optional title shown above the table toolbar
|
|
221
|
+
// optional title shown as demibold text above the table toolbar
|
|
222
222
|
showRowCount = true,
|
|
223
223
|
// show "X records" / "X of Y records" text
|
|
224
224
|
rowCountBold = false,
|
|
@@ -688,8 +688,7 @@ var DataTable = ({
|
|
|
688
688
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
689
689
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
690
690
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
691
|
-
const showToolbarCount = showRowCount &&
|
|
692
|
-
const showTitleCount = showRowCount && !!title && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
691
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
693
692
|
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
694
693
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
695
694
|
const applySelection = (0, import_react.useCallback)((nextSet) => {
|
|
@@ -1040,7 +1039,7 @@ var DataTable = ({
|
|
|
1040
1039
|
}
|
|
1041
1040
|
);
|
|
1042
1041
|
};
|
|
1043
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.
|
|
1042
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { format: { fontWeight: "demibold" } }, title)), hasToolbarContent && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 3 }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ import_react.default.createElement(
|
|
1044
1043
|
import_ui_extensions.SearchInput,
|
|
1045
1044
|
{
|
|
1046
1045
|
name: "datatable-search",
|
|
@@ -1519,8 +1518,12 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1519
1518
|
// validate on blur
|
|
1520
1519
|
validateOnSubmit = true,
|
|
1521
1520
|
// validate all before onSubmit
|
|
1522
|
-
onValidationChange
|
|
1521
|
+
onValidationChange,
|
|
1523
1522
|
// (errors) => void
|
|
1523
|
+
onValidationFail,
|
|
1524
|
+
// ({ errors, fields, firstInvalidField }) => void — called when submit-time validation blocks submission
|
|
1525
|
+
openSectionOnValidationFail = false
|
|
1526
|
+
// auto-open accordion section containing first invalid field on submit failure
|
|
1524
1527
|
} = props;
|
|
1525
1528
|
const {
|
|
1526
1529
|
steps,
|
|
@@ -1669,6 +1672,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1669
1672
|
"tooltip",
|
|
1670
1673
|
"required",
|
|
1671
1674
|
"readOnly",
|
|
1675
|
+
"alwaysEditable",
|
|
1672
1676
|
"disabled",
|
|
1673
1677
|
"defaultValue",
|
|
1674
1678
|
"fieldProps",
|
|
@@ -1841,6 +1845,17 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1841
1845
|
}
|
|
1842
1846
|
return map;
|
|
1843
1847
|
}, [fields]);
|
|
1848
|
+
const sectionIdByFieldName = (0, import_react2.useMemo)(() => {
|
|
1849
|
+
const map = /* @__PURE__ */ new Map();
|
|
1850
|
+
if (Array.isArray(sections)) {
|
|
1851
|
+
for (const sec of sections) {
|
|
1852
|
+
if (!sec || !Array.isArray(sec.fields)) continue;
|
|
1853
|
+
for (const name of sec.fields) map.set(name, sec.id);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return map;
|
|
1857
|
+
}, [sections]);
|
|
1858
|
+
const [validationOpenSection, setValidationOpenSection] = (0, import_react2.useState)(null);
|
|
1844
1859
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
1845
1860
|
const configWarningsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1846
1861
|
const warnConfig = (0, import_react2.useCallback)((message) => {
|
|
@@ -2421,10 +2436,35 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2421
2436
|
const handleSubmit = (0, import_react2.useCallback)(
|
|
2422
2437
|
async (e) => {
|
|
2423
2438
|
if (e && e.preventDefault) e.preventDefault();
|
|
2439
|
+
const reportValidationFailure = (errors) => {
|
|
2440
|
+
const errorNames = Object.keys(errors).filter((n) => !!errors[n]);
|
|
2441
|
+
if (errorNames.length === 0) return;
|
|
2442
|
+
const orderedNames = allVisibleFields.map((f) => f.name).filter((n) => errorNames.includes(n));
|
|
2443
|
+
for (const n of errorNames) if (!orderedNames.includes(n)) orderedNames.push(n);
|
|
2444
|
+
const fieldInfos = orderedNames.map((name) => {
|
|
2445
|
+
const f = fieldByName.get(name);
|
|
2446
|
+
return {
|
|
2447
|
+
name,
|
|
2448
|
+
label: f == null ? void 0 : f.label,
|
|
2449
|
+
sectionId: sectionIdByFieldName.get(name)
|
|
2450
|
+
};
|
|
2451
|
+
});
|
|
2452
|
+
const firstInvalidField = fieldInfos[0];
|
|
2453
|
+
if (openSectionOnValidationFail && (firstInvalidField == null ? void 0 : firstInvalidField.sectionId)) {
|
|
2454
|
+
setValidationOpenSection({
|
|
2455
|
+
id: firstInvalidField.sectionId,
|
|
2456
|
+
nonce: ((validationOpenSection == null ? void 0 : validationOpenSection.nonce) || 0) + 1
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
if (onValidationFail) {
|
|
2460
|
+
onValidationFail({ errors, fields: fieldInfos, firstInvalidField });
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2424
2463
|
if (validateOnSubmit) {
|
|
2425
2464
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
2426
2465
|
if (hasErrors) {
|
|
2427
2466
|
replaceErrors(errors);
|
|
2467
|
+
reportValidationFailure(errors);
|
|
2428
2468
|
return;
|
|
2429
2469
|
}
|
|
2430
2470
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -2436,7 +2476,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2436
2476
|
])
|
|
2437
2477
|
];
|
|
2438
2478
|
await Promise.all(pendingValidations);
|
|
2439
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
2479
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
2480
|
+
reportValidationFailure(formErrorsRef.current);
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2440
2483
|
}
|
|
2441
2484
|
}
|
|
2442
2485
|
const reset = () => {
|
|
@@ -2482,7 +2525,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2482
2525
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2483
2526
|
}
|
|
2484
2527
|
},
|
|
2485
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
2528
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget, onValidationFail, openSectionOnValidationFail, sectionIdByFieldName, validationOpenSection]
|
|
2486
2529
|
);
|
|
2487
2530
|
const handleNext = (0, import_react2.useCallback)(async () => {
|
|
2488
2531
|
if (!isMultiStep) return;
|
|
@@ -2575,8 +2618,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2575
2618
|
const fieldError = formErrors[field.name] || null;
|
|
2576
2619
|
const hasError = !!fieldError;
|
|
2577
2620
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
2578
|
-
const
|
|
2579
|
-
const
|
|
2621
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
2622
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
2623
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
2580
2624
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2581
2625
|
if (field.type === "display" || field.type === "slot") {
|
|
2582
2626
|
if (field.render) {
|
|
@@ -2619,8 +2663,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2619
2663
|
const sfValue = formValues[sf.name];
|
|
2620
2664
|
const sfError = formErrors[sf.name] || null;
|
|
2621
2665
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
2622
|
-
const
|
|
2623
|
-
const
|
|
2666
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
2667
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
2668
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
2624
2669
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
2625
2670
|
const sfProps = {
|
|
2626
2671
|
name: sf.name,
|
|
@@ -3417,13 +3462,16 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
3417
3462
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
3418
3463
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
3419
3464
|
const accordionContent = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields, sectionOverrides), sec.renderAfter && sec.renderAfter(sectionContext));
|
|
3465
|
+
const isValidationOverrideTarget = validationOpenSection && validationOpenSection.id === sec.id;
|
|
3466
|
+
const accordionKey = isValidationOverrideTarget ? `${sec.id}::open::${validationOpenSection.nonce}` : sec.id;
|
|
3467
|
+
const accordionDefaultOpen = isValidationOverrideTarget ? true : sec.defaultOpen !== false;
|
|
3420
3468
|
const accordion = /* @__PURE__ */ import_react2.default.createElement(
|
|
3421
3469
|
import_ui_extensions2.Accordion,
|
|
3422
3470
|
{
|
|
3423
|
-
key:
|
|
3471
|
+
key: accordionKey,
|
|
3424
3472
|
title: sec.label,
|
|
3425
3473
|
size: "sm",
|
|
3426
|
-
defaultOpen:
|
|
3474
|
+
defaultOpen: accordionDefaultOpen
|
|
3427
3475
|
},
|
|
3428
3476
|
accordionContent
|
|
3429
3477
|
);
|
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
EmptyState,
|
|
11
11
|
ErrorState,
|
|
12
12
|
Flex,
|
|
13
|
-
Heading,
|
|
14
13
|
Icon,
|
|
15
14
|
Input,
|
|
16
15
|
Link,
|
|
@@ -184,7 +183,7 @@ var DataTable = ({
|
|
|
184
183
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
185
184
|
// Row count
|
|
186
185
|
title,
|
|
187
|
-
// optional title shown above the table toolbar
|
|
186
|
+
// optional title shown as demibold text above the table toolbar
|
|
188
187
|
showRowCount = true,
|
|
189
188
|
// show "X records" / "X of Y records" text
|
|
190
189
|
rowCountBold = false,
|
|
@@ -654,8 +653,7 @@ var DataTable = ({
|
|
|
654
653
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
655
654
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
656
655
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
657
|
-
const showToolbarCount = showRowCount &&
|
|
658
|
-
const showTitleCount = showRowCount && !!title && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
656
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
659
657
|
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
660
658
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
661
659
|
const applySelection = useCallback((nextSet) => {
|
|
@@ -1006,7 +1004,7 @@ var DataTable = ({
|
|
|
1006
1004
|
}
|
|
1007
1005
|
);
|
|
1008
1006
|
};
|
|
1009
|
-
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(
|
|
1007
|
+
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, title && /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", justify: "between", gap: "sm" }, /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, title)), hasToolbarContent && /* @__PURE__ */ React.createElement(Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 3 }, /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch && searchFields.length > 0 && /* @__PURE__ */ React.createElement(
|
|
1010
1008
|
SearchInput,
|
|
1011
1009
|
{
|
|
1012
1010
|
name: "datatable-search",
|
|
@@ -1525,8 +1523,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1525
1523
|
// validate on blur
|
|
1526
1524
|
validateOnSubmit = true,
|
|
1527
1525
|
// validate all before onSubmit
|
|
1528
|
-
onValidationChange
|
|
1526
|
+
onValidationChange,
|
|
1529
1527
|
// (errors) => void
|
|
1528
|
+
onValidationFail,
|
|
1529
|
+
// ({ errors, fields, firstInvalidField }) => void — called when submit-time validation blocks submission
|
|
1530
|
+
openSectionOnValidationFail = false
|
|
1531
|
+
// auto-open accordion section containing first invalid field on submit failure
|
|
1530
1532
|
} = props;
|
|
1531
1533
|
const {
|
|
1532
1534
|
steps,
|
|
@@ -1675,6 +1677,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1675
1677
|
"tooltip",
|
|
1676
1678
|
"required",
|
|
1677
1679
|
"readOnly",
|
|
1680
|
+
"alwaysEditable",
|
|
1678
1681
|
"disabled",
|
|
1679
1682
|
"defaultValue",
|
|
1680
1683
|
"fieldProps",
|
|
@@ -1847,6 +1850,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1847
1850
|
}
|
|
1848
1851
|
return map;
|
|
1849
1852
|
}, [fields]);
|
|
1853
|
+
const sectionIdByFieldName = useMemo2(() => {
|
|
1854
|
+
const map = /* @__PURE__ */ new Map();
|
|
1855
|
+
if (Array.isArray(sections)) {
|
|
1856
|
+
for (const sec of sections) {
|
|
1857
|
+
if (!sec || !Array.isArray(sec.fields)) continue;
|
|
1858
|
+
for (const name of sec.fields) map.set(name, sec.id);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return map;
|
|
1862
|
+
}, [sections]);
|
|
1863
|
+
const [validationOpenSection, setValidationOpenSection] = useState2(null);
|
|
1850
1864
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
1851
1865
|
const configWarningsRef = useRef2(/* @__PURE__ */ new Set());
|
|
1852
1866
|
const warnConfig = useCallback2((message) => {
|
|
@@ -2427,10 +2441,35 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2427
2441
|
const handleSubmit = useCallback2(
|
|
2428
2442
|
async (e) => {
|
|
2429
2443
|
if (e && e.preventDefault) e.preventDefault();
|
|
2444
|
+
const reportValidationFailure = (errors) => {
|
|
2445
|
+
const errorNames = Object.keys(errors).filter((n) => !!errors[n]);
|
|
2446
|
+
if (errorNames.length === 0) return;
|
|
2447
|
+
const orderedNames = allVisibleFields.map((f) => f.name).filter((n) => errorNames.includes(n));
|
|
2448
|
+
for (const n of errorNames) if (!orderedNames.includes(n)) orderedNames.push(n);
|
|
2449
|
+
const fieldInfos = orderedNames.map((name) => {
|
|
2450
|
+
const f = fieldByName.get(name);
|
|
2451
|
+
return {
|
|
2452
|
+
name,
|
|
2453
|
+
label: f == null ? void 0 : f.label,
|
|
2454
|
+
sectionId: sectionIdByFieldName.get(name)
|
|
2455
|
+
};
|
|
2456
|
+
});
|
|
2457
|
+
const firstInvalidField = fieldInfos[0];
|
|
2458
|
+
if (openSectionOnValidationFail && (firstInvalidField == null ? void 0 : firstInvalidField.sectionId)) {
|
|
2459
|
+
setValidationOpenSection({
|
|
2460
|
+
id: firstInvalidField.sectionId,
|
|
2461
|
+
nonce: ((validationOpenSection == null ? void 0 : validationOpenSection.nonce) || 0) + 1
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
if (onValidationFail) {
|
|
2465
|
+
onValidationFail({ errors, fields: fieldInfos, firstInvalidField });
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2430
2468
|
if (validateOnSubmit) {
|
|
2431
2469
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
2432
2470
|
if (hasErrors) {
|
|
2433
2471
|
replaceErrors(errors);
|
|
2472
|
+
reportValidationFailure(errors);
|
|
2434
2473
|
return;
|
|
2435
2474
|
}
|
|
2436
2475
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -2442,7 +2481,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2442
2481
|
])
|
|
2443
2482
|
];
|
|
2444
2483
|
await Promise.all(pendingValidations);
|
|
2445
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
2484
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
2485
|
+
reportValidationFailure(formErrorsRef.current);
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2446
2488
|
}
|
|
2447
2489
|
}
|
|
2448
2490
|
const reset = () => {
|
|
@@ -2488,7 +2530,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2488
2530
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2489
2531
|
}
|
|
2490
2532
|
},
|
|
2491
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
2533
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget, onValidationFail, openSectionOnValidationFail, sectionIdByFieldName, validationOpenSection]
|
|
2492
2534
|
);
|
|
2493
2535
|
const handleNext = useCallback2(async () => {
|
|
2494
2536
|
if (!isMultiStep) return;
|
|
@@ -2581,8 +2623,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2581
2623
|
const fieldError = formErrors[field.name] || null;
|
|
2582
2624
|
const hasError = !!fieldError;
|
|
2583
2625
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
2584
|
-
const
|
|
2585
|
-
const
|
|
2626
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
2627
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
2628
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
2586
2629
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2587
2630
|
if (field.type === "display" || field.type === "slot") {
|
|
2588
2631
|
if (field.render) {
|
|
@@ -2625,8 +2668,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2625
2668
|
const sfValue = formValues[sf.name];
|
|
2626
2669
|
const sfError = formErrors[sf.name] || null;
|
|
2627
2670
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
2628
|
-
const
|
|
2629
|
-
const
|
|
2671
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
2672
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
2673
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
2630
2674
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
2631
2675
|
const sfProps = {
|
|
2632
2676
|
name: sf.name,
|
|
@@ -3423,13 +3467,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3423
3467
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
3424
3468
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
3425
3469
|
const accordionContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields, sectionOverrides), sec.renderAfter && sec.renderAfter(sectionContext));
|
|
3470
|
+
const isValidationOverrideTarget = validationOpenSection && validationOpenSection.id === sec.id;
|
|
3471
|
+
const accordionKey = isValidationOverrideTarget ? `${sec.id}::open::${validationOpenSection.nonce}` : sec.id;
|
|
3472
|
+
const accordionDefaultOpen = isValidationOverrideTarget ? true : sec.defaultOpen !== false;
|
|
3426
3473
|
const accordion = /* @__PURE__ */ React2.createElement(
|
|
3427
3474
|
Accordion,
|
|
3428
3475
|
{
|
|
3429
|
-
key:
|
|
3476
|
+
key: accordionKey,
|
|
3430
3477
|
title: sec.label,
|
|
3431
3478
|
size: "sm",
|
|
3432
|
-
defaultOpen:
|
|
3479
|
+
defaultOpen: accordionDefaultOpen
|
|
3433
3480
|
},
|
|
3434
3481
|
accordionContent
|
|
3435
3482
|
);
|
|
@@ -5442,7 +5489,7 @@ var KeyValueList = ({ items = [], direction = "row", gap = "sm" }) => {
|
|
|
5442
5489
|
|
|
5443
5490
|
// src/common-components/SectionHeader.js
|
|
5444
5491
|
import React10 from "react";
|
|
5445
|
-
import { Flex as Flex6, Heading
|
|
5492
|
+
import { Flex as Flex6, Heading, Text as Text5 } from "@hubspot/ui-extensions";
|
|
5446
5493
|
var SectionHeader = ({
|
|
5447
5494
|
title,
|
|
5448
5495
|
description,
|
|
@@ -5453,7 +5500,7 @@ var SectionHeader = ({
|
|
|
5453
5500
|
}) => {
|
|
5454
5501
|
const body = [];
|
|
5455
5502
|
if (title != null) {
|
|
5456
|
-
body.push(React10.createElement(
|
|
5503
|
+
body.push(React10.createElement(Heading, { key: "title", as: titleAs }, title));
|
|
5457
5504
|
}
|
|
5458
5505
|
if (description != null) {
|
|
5459
5506
|
body.push(
|