hs-uix 1.6.3 → 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 +6 -2
- package/dist/datatable.mjs +6 -2
- package/dist/form.js +66 -10
- package/dist/form.mjs +66 -10
- package/dist/index.js +72 -12
- package/dist/index.mjs +72 -12
- package/form.d.ts +1 -0
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/datatable.js
CHANGED
|
@@ -186,6 +186,8 @@ var DataTable = ({
|
|
|
186
186
|
showFirstLastButtons,
|
|
187
187
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
188
188
|
// Row count
|
|
189
|
+
title,
|
|
190
|
+
// optional title shown as demibold text above the table toolbar
|
|
189
191
|
showRowCount = true,
|
|
190
192
|
// show "X records" / "X of Y records" text
|
|
191
193
|
rowCountBold = false,
|
|
@@ -655,6 +657,8 @@ var DataTable = ({
|
|
|
655
657
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
656
658
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
657
659
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
660
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
661
|
+
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
658
662
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
659
663
|
const applySelection = (0, import_react.useCallback)((nextSet) => {
|
|
660
664
|
if (externalSelectedIds == null) {
|
|
@@ -1004,7 +1008,7 @@ var DataTable = ({
|
|
|
1004
1008
|
}
|
|
1005
1009
|
);
|
|
1006
1010
|
};
|
|
1007
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__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(
|
|
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(
|
|
1008
1012
|
import_ui_extensions.SearchInput,
|
|
1009
1013
|
{
|
|
1010
1014
|
name: "datatable-search",
|
|
@@ -1030,7 +1034,7 @@ var DataTable = ({
|
|
|
1030
1034
|
onClick: () => handleFilterRemove("all")
|
|
1031
1035
|
},
|
|
1032
1036
|
resolvedClearAllLabel
|
|
1033
|
-
)))),
|
|
1037
|
+
)))), showToolbarCount && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
|
|
1034
1038
|
selectedIds,
|
|
1035
1039
|
selectedCount: selectedIds.size,
|
|
1036
1040
|
displayCount,
|
package/dist/datatable.mjs
CHANGED
|
@@ -182,6 +182,8 @@ var DataTable = ({
|
|
|
182
182
|
showFirstLastButtons,
|
|
183
183
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
184
184
|
// Row count
|
|
185
|
+
title,
|
|
186
|
+
// optional title shown as demibold text above the table toolbar
|
|
185
187
|
showRowCount = true,
|
|
186
188
|
// show "X records" / "X of Y records" text
|
|
187
189
|
rowCountBold = false,
|
|
@@ -651,6 +653,8 @@ var DataTable = ({
|
|
|
651
653
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
652
654
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
653
655
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
656
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
657
|
+
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
654
658
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
655
659
|
const applySelection = useCallback((nextSet) => {
|
|
656
660
|
if (externalSelectedIds == null) {
|
|
@@ -1000,7 +1004,7 @@ var DataTable = ({
|
|
|
1000
1004
|
}
|
|
1001
1005
|
);
|
|
1002
1006
|
};
|
|
1003
|
-
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__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(
|
|
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(
|
|
1004
1008
|
SearchInput,
|
|
1005
1009
|
{
|
|
1006
1010
|
name: "datatable-search",
|
|
@@ -1026,7 +1030,7 @@ var DataTable = ({
|
|
|
1026
1030
|
onClick: () => handleFilterRemove("all")
|
|
1027
1031
|
},
|
|
1028
1032
|
resolvedClearAllLabel
|
|
1029
|
-
)))),
|
|
1033
|
+
)))), showToolbarCount && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
|
|
1030
1034
|
selectedIds,
|
|
1031
1035
|
selectedCount: selectedIds.size,
|
|
1032
1036
|
displayCount,
|
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,
|
|
@@ -404,6 +408,8 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
404
408
|
// () => void
|
|
405
409
|
submitPosition = "bottom",
|
|
406
410
|
// "bottom" | "none"
|
|
411
|
+
submitAlign,
|
|
412
|
+
// default single-step action row alignment
|
|
407
413
|
loading: controlledLoading,
|
|
408
414
|
// controlled loading state
|
|
409
415
|
disabled = false,
|
|
@@ -526,6 +532,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
526
532
|
"tooltip",
|
|
527
533
|
"required",
|
|
528
534
|
"readOnly",
|
|
535
|
+
"alwaysEditable",
|
|
529
536
|
"disabled",
|
|
530
537
|
"defaultValue",
|
|
531
538
|
"fieldProps",
|
|
@@ -698,6 +705,17 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
698
705
|
}
|
|
699
706
|
return map;
|
|
700
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);
|
|
701
719
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
702
720
|
const configWarningsRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
703
721
|
const warnConfig = (0, import_react.useCallback)((message) => {
|
|
@@ -708,6 +726,10 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
708
726
|
console.warn(`[FormBuilder] ${message}`);
|
|
709
727
|
}
|
|
710
728
|
}, [isDev]);
|
|
729
|
+
(0, import_react.useEffect)(() => {
|
|
730
|
+
if (!isMultiStep || !submitAlign) return;
|
|
731
|
+
warnConfig("submitAlign is ignored when steps are provided. Use renderButtons for custom multi-step button layout.");
|
|
732
|
+
}, [isMultiStep, submitAlign, warnConfig]);
|
|
711
733
|
const replaceErrors = (0, import_react.useCallback)(
|
|
712
734
|
(nextErrors) => {
|
|
713
735
|
if (controlledErrors == null) setInternalErrors(nextErrors);
|
|
@@ -1274,10 +1296,35 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1274
1296
|
const handleSubmit = (0, import_react.useCallback)(
|
|
1275
1297
|
async (e) => {
|
|
1276
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
|
+
};
|
|
1277
1323
|
if (validateOnSubmit) {
|
|
1278
1324
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
1279
1325
|
if (hasErrors) {
|
|
1280
1326
|
replaceErrors(errors);
|
|
1327
|
+
reportValidationFailure(errors);
|
|
1281
1328
|
return;
|
|
1282
1329
|
}
|
|
1283
1330
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -1289,7 +1336,10 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1289
1336
|
])
|
|
1290
1337
|
];
|
|
1291
1338
|
await Promise.all(pendingValidations);
|
|
1292
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
1339
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
1340
|
+
reportValidationFailure(formErrorsRef.current);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1293
1343
|
}
|
|
1294
1344
|
}
|
|
1295
1345
|
const reset = () => {
|
|
@@ -1335,7 +1385,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1335
1385
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1336
1386
|
}
|
|
1337
1387
|
},
|
|
1338
|
-
[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]
|
|
1339
1389
|
);
|
|
1340
1390
|
const handleNext = (0, import_react.useCallback)(async () => {
|
|
1341
1391
|
if (!isMultiStep) return;
|
|
@@ -1428,8 +1478,9 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1428
1478
|
const fieldError = formErrors[field.name] || null;
|
|
1429
1479
|
const hasError = !!fieldError;
|
|
1430
1480
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
1431
|
-
const
|
|
1432
|
-
const
|
|
1481
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
1482
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
1483
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
1433
1484
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1434
1485
|
if (field.type === "display" || field.type === "slot") {
|
|
1435
1486
|
if (field.render) {
|
|
@@ -1472,8 +1523,9 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
1472
1523
|
const sfValue = formValues[sf.name];
|
|
1473
1524
|
const sfError = formErrors[sf.name] || null;
|
|
1474
1525
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
1475
|
-
const
|
|
1476
|
-
const
|
|
1526
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
1527
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
1528
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
1477
1529
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
1478
1530
|
const sfProps = {
|
|
1479
1531
|
name: sf.name,
|
|
@@ -2270,13 +2322,16 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
2270
2322
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
2271
2323
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
2272
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;
|
|
2273
2328
|
const accordion = /* @__PURE__ */ import_react.default.createElement(
|
|
2274
2329
|
import_ui_extensions.Accordion,
|
|
2275
2330
|
{
|
|
2276
|
-
key:
|
|
2331
|
+
key: accordionKey,
|
|
2277
2332
|
title: sec.label,
|
|
2278
2333
|
size: "sm",
|
|
2279
|
-
defaultOpen:
|
|
2334
|
+
defaultOpen: accordionDefaultOpen
|
|
2280
2335
|
},
|
|
2281
2336
|
accordionContent
|
|
2282
2337
|
);
|
|
@@ -2310,6 +2365,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
2310
2365
|
if (submitPosition === "none" || formReadOnly) return null;
|
|
2311
2366
|
const isLastStep = !isMultiStep || currentStep === steps.length - 1;
|
|
2312
2367
|
const isFirstStep = !isMultiStep || currentStep === 0;
|
|
2368
|
+
const singleStepJustify = submitAlign || (showCancel ? "between" : "start");
|
|
2313
2369
|
const buttonContext = {
|
|
2314
2370
|
isMultiStep,
|
|
2315
2371
|
isFirstStep,
|
|
@@ -2344,7 +2400,7 @@ var FormBuilder = (0, import_react.forwardRef)(function FormBuilder2(props, ref)
|
|
|
2344
2400
|
submitButtonLabel
|
|
2345
2401
|
) : /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
|
|
2346
2402
|
}
|
|
2347
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify:
|
|
2403
|
+
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ import_react.default.createElement(
|
|
2348
2404
|
import_ui_extensions.LoadingButton,
|
|
2349
2405
|
{
|
|
2350
2406
|
variant: submitVariant,
|
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,
|
|
@@ -408,6 +412,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
408
412
|
// () => void
|
|
409
413
|
submitPosition = "bottom",
|
|
410
414
|
// "bottom" | "none"
|
|
415
|
+
submitAlign,
|
|
416
|
+
// default single-step action row alignment
|
|
411
417
|
loading: controlledLoading,
|
|
412
418
|
// controlled loading state
|
|
413
419
|
disabled = false,
|
|
@@ -530,6 +536,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
530
536
|
"tooltip",
|
|
531
537
|
"required",
|
|
532
538
|
"readOnly",
|
|
539
|
+
"alwaysEditable",
|
|
533
540
|
"disabled",
|
|
534
541
|
"defaultValue",
|
|
535
542
|
"fieldProps",
|
|
@@ -702,6 +709,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
702
709
|
}
|
|
703
710
|
return map;
|
|
704
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);
|
|
705
723
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
706
724
|
const configWarningsRef = useRef(/* @__PURE__ */ new Set());
|
|
707
725
|
const warnConfig = useCallback((message) => {
|
|
@@ -712,6 +730,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
712
730
|
console.warn(`[FormBuilder] ${message}`);
|
|
713
731
|
}
|
|
714
732
|
}, [isDev]);
|
|
733
|
+
useEffect(() => {
|
|
734
|
+
if (!isMultiStep || !submitAlign) return;
|
|
735
|
+
warnConfig("submitAlign is ignored when steps are provided. Use renderButtons for custom multi-step button layout.");
|
|
736
|
+
}, [isMultiStep, submitAlign, warnConfig]);
|
|
715
737
|
const replaceErrors = useCallback(
|
|
716
738
|
(nextErrors) => {
|
|
717
739
|
if (controlledErrors == null) setInternalErrors(nextErrors);
|
|
@@ -1278,10 +1300,35 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1278
1300
|
const handleSubmit = useCallback(
|
|
1279
1301
|
async (e) => {
|
|
1280
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
|
+
};
|
|
1281
1327
|
if (validateOnSubmit) {
|
|
1282
1328
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
1283
1329
|
if (hasErrors) {
|
|
1284
1330
|
replaceErrors(errors);
|
|
1331
|
+
reportValidationFailure(errors);
|
|
1285
1332
|
return;
|
|
1286
1333
|
}
|
|
1287
1334
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -1293,7 +1340,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1293
1340
|
])
|
|
1294
1341
|
];
|
|
1295
1342
|
await Promise.all(pendingValidations);
|
|
1296
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
1343
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
1344
|
+
reportValidationFailure(formErrorsRef.current);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1297
1347
|
}
|
|
1298
1348
|
}
|
|
1299
1349
|
const reset = () => {
|
|
@@ -1339,7 +1389,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1339
1389
|
if (controlledLoading == null) setInternalLoading(false);
|
|
1340
1390
|
}
|
|
1341
1391
|
},
|
|
1342
|
-
[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]
|
|
1343
1393
|
);
|
|
1344
1394
|
const handleNext = useCallback(async () => {
|
|
1345
1395
|
if (!isMultiStep) return;
|
|
@@ -1432,8 +1482,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1432
1482
|
const fieldError = formErrors[field.name] || null;
|
|
1433
1483
|
const hasError = !!fieldError;
|
|
1434
1484
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
1435
|
-
const
|
|
1436
|
-
const
|
|
1485
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
1486
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
1487
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
1437
1488
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
1438
1489
|
if (field.type === "display" || field.type === "slot") {
|
|
1439
1490
|
if (field.render) {
|
|
@@ -1476,8 +1527,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1476
1527
|
const sfValue = formValues[sf.name];
|
|
1477
1528
|
const sfError = formErrors[sf.name] || null;
|
|
1478
1529
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
1479
|
-
const
|
|
1480
|
-
const
|
|
1530
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
1531
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
1532
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
1481
1533
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
1482
1534
|
const sfProps = {
|
|
1483
1535
|
name: sf.name,
|
|
@@ -2274,13 +2326,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2274
2326
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
2275
2327
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
2276
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;
|
|
2277
2332
|
const accordion = /* @__PURE__ */ React.createElement(
|
|
2278
2333
|
Accordion,
|
|
2279
2334
|
{
|
|
2280
|
-
key:
|
|
2335
|
+
key: accordionKey,
|
|
2281
2336
|
title: sec.label,
|
|
2282
2337
|
size: "sm",
|
|
2283
|
-
defaultOpen:
|
|
2338
|
+
defaultOpen: accordionDefaultOpen
|
|
2284
2339
|
},
|
|
2285
2340
|
accordionContent
|
|
2286
2341
|
);
|
|
@@ -2314,6 +2369,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2314
2369
|
if (submitPosition === "none" || formReadOnly) return null;
|
|
2315
2370
|
const isLastStep = !isMultiStep || currentStep === steps.length - 1;
|
|
2316
2371
|
const isFirstStep = !isMultiStep || currentStep === 0;
|
|
2372
|
+
const singleStepJustify = submitAlign || (showCancel ? "between" : "start");
|
|
2317
2373
|
const buttonContext = {
|
|
2318
2374
|
isMultiStep,
|
|
2319
2375
|
isFirstStep,
|
|
@@ -2348,7 +2404,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2348
2404
|
submitButtonLabel
|
|
2349
2405
|
) : /* @__PURE__ */ React.createElement(Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
|
|
2350
2406
|
}
|
|
2351
|
-
return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify:
|
|
2407
|
+
return /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ React.createElement(Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ React.createElement(
|
|
2352
2408
|
LoadingButton,
|
|
2353
2409
|
{
|
|
2354
2410
|
variant: submitVariant,
|
package/dist/index.js
CHANGED
|
@@ -217,6 +217,8 @@ var DataTable = ({
|
|
|
217
217
|
showFirstLastButtons,
|
|
218
218
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
219
219
|
// Row count
|
|
220
|
+
title,
|
|
221
|
+
// optional title shown as demibold text above the table toolbar
|
|
220
222
|
showRowCount = true,
|
|
221
223
|
// show "X records" / "X of Y records" text
|
|
222
224
|
rowCountBold = false,
|
|
@@ -686,6 +688,8 @@ var DataTable = ({
|
|
|
686
688
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
687
689
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
688
690
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
691
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
692
|
+
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
689
693
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
690
694
|
const applySelection = (0, import_react.useCallback)((nextSet) => {
|
|
691
695
|
if (externalSelectedIds == null) {
|
|
@@ -1035,7 +1039,7 @@ var DataTable = ({
|
|
|
1035
1039
|
}
|
|
1036
1040
|
);
|
|
1037
1041
|
};
|
|
1038
|
-
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "column", gap: "xs" }, /* @__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(
|
|
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(
|
|
1039
1043
|
import_ui_extensions.SearchInput,
|
|
1040
1044
|
{
|
|
1041
1045
|
name: "datatable-search",
|
|
@@ -1061,7 +1065,7 @@ var DataTable = ({
|
|
|
1061
1065
|
onClick: () => handleFilterRemove("all")
|
|
1062
1066
|
},
|
|
1063
1067
|
resolvedClearAllLabel
|
|
1064
|
-
)))),
|
|
1068
|
+
)))), showToolbarCount && /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
|
|
1065
1069
|
selectedIds,
|
|
1066
1070
|
selectedCount: selectedIds.size,
|
|
1067
1071
|
displayCount,
|
|
@@ -1514,8 +1518,12 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1514
1518
|
// validate on blur
|
|
1515
1519
|
validateOnSubmit = true,
|
|
1516
1520
|
// validate all before onSubmit
|
|
1517
|
-
onValidationChange
|
|
1521
|
+
onValidationChange,
|
|
1518
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
|
|
1519
1527
|
} = props;
|
|
1520
1528
|
const {
|
|
1521
1529
|
steps,
|
|
@@ -1540,6 +1548,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1540
1548
|
// () => void
|
|
1541
1549
|
submitPosition = "bottom",
|
|
1542
1550
|
// "bottom" | "none"
|
|
1551
|
+
submitAlign,
|
|
1552
|
+
// default single-step action row alignment
|
|
1543
1553
|
loading: controlledLoading,
|
|
1544
1554
|
// controlled loading state
|
|
1545
1555
|
disabled = false,
|
|
@@ -1662,6 +1672,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1662
1672
|
"tooltip",
|
|
1663
1673
|
"required",
|
|
1664
1674
|
"readOnly",
|
|
1675
|
+
"alwaysEditable",
|
|
1665
1676
|
"disabled",
|
|
1666
1677
|
"defaultValue",
|
|
1667
1678
|
"fieldProps",
|
|
@@ -1834,6 +1845,17 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1834
1845
|
}
|
|
1835
1846
|
return map;
|
|
1836
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);
|
|
1837
1859
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
1838
1860
|
const configWarningsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1839
1861
|
const warnConfig = (0, import_react2.useCallback)((message) => {
|
|
@@ -1844,6 +1866,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1844
1866
|
console.warn(`[FormBuilder] ${message}`);
|
|
1845
1867
|
}
|
|
1846
1868
|
}, [isDev]);
|
|
1869
|
+
(0, import_react2.useEffect)(() => {
|
|
1870
|
+
if (!isMultiStep || !submitAlign) return;
|
|
1871
|
+
warnConfig("submitAlign is ignored when steps are provided. Use renderButtons for custom multi-step button layout.");
|
|
1872
|
+
}, [isMultiStep, submitAlign, warnConfig]);
|
|
1847
1873
|
const replaceErrors = (0, import_react2.useCallback)(
|
|
1848
1874
|
(nextErrors) => {
|
|
1849
1875
|
if (controlledErrors == null) setInternalErrors(nextErrors);
|
|
@@ -2410,10 +2436,35 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2410
2436
|
const handleSubmit = (0, import_react2.useCallback)(
|
|
2411
2437
|
async (e) => {
|
|
2412
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
|
+
};
|
|
2413
2463
|
if (validateOnSubmit) {
|
|
2414
2464
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
2415
2465
|
if (hasErrors) {
|
|
2416
2466
|
replaceErrors(errors);
|
|
2467
|
+
reportValidationFailure(errors);
|
|
2417
2468
|
return;
|
|
2418
2469
|
}
|
|
2419
2470
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -2425,7 +2476,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2425
2476
|
])
|
|
2426
2477
|
];
|
|
2427
2478
|
await Promise.all(pendingValidations);
|
|
2428
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
2479
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
2480
|
+
reportValidationFailure(formErrorsRef.current);
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2429
2483
|
}
|
|
2430
2484
|
}
|
|
2431
2485
|
const reset = () => {
|
|
@@ -2471,7 +2525,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2471
2525
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2472
2526
|
}
|
|
2473
2527
|
},
|
|
2474
|
-
[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]
|
|
2475
2529
|
);
|
|
2476
2530
|
const handleNext = (0, import_react2.useCallback)(async () => {
|
|
2477
2531
|
if (!isMultiStep) return;
|
|
@@ -2564,8 +2618,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2564
2618
|
const fieldError = formErrors[field.name] || null;
|
|
2565
2619
|
const hasError = !!fieldError;
|
|
2566
2620
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
2567
|
-
const
|
|
2568
|
-
const
|
|
2621
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
2622
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
2623
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
2569
2624
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2570
2625
|
if (field.type === "display" || field.type === "slot") {
|
|
2571
2626
|
if (field.render) {
|
|
@@ -2608,8 +2663,9 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2608
2663
|
const sfValue = formValues[sf.name];
|
|
2609
2664
|
const sfError = formErrors[sf.name] || null;
|
|
2610
2665
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
2611
|
-
const
|
|
2612
|
-
const
|
|
2666
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
2667
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
2668
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
2613
2669
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
2614
2670
|
const sfProps = {
|
|
2615
2671
|
name: sf.name,
|
|
@@ -3406,13 +3462,16 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
3406
3462
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
3407
3463
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
3408
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;
|
|
3409
3468
|
const accordion = /* @__PURE__ */ import_react2.default.createElement(
|
|
3410
3469
|
import_ui_extensions2.Accordion,
|
|
3411
3470
|
{
|
|
3412
|
-
key:
|
|
3471
|
+
key: accordionKey,
|
|
3413
3472
|
title: sec.label,
|
|
3414
3473
|
size: "sm",
|
|
3415
|
-
defaultOpen:
|
|
3474
|
+
defaultOpen: accordionDefaultOpen
|
|
3416
3475
|
},
|
|
3417
3476
|
accordionContent
|
|
3418
3477
|
);
|
|
@@ -3446,6 +3505,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
3446
3505
|
if (submitPosition === "none" || formReadOnly) return null;
|
|
3447
3506
|
const isLastStep = !isMultiStep || currentStep === steps.length - 1;
|
|
3448
3507
|
const isFirstStep = !isMultiStep || currentStep === 0;
|
|
3508
|
+
const singleStepJustify = submitAlign || (showCancel ? "between" : "start");
|
|
3449
3509
|
const buttonContext = {
|
|
3450
3510
|
isMultiStep,
|
|
3451
3511
|
isFirstStep,
|
|
@@ -3480,7 +3540,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
3480
3540
|
submitButtonLabel
|
|
3481
3541
|
) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
|
|
3482
3542
|
}
|
|
3483
|
-
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify:
|
|
3543
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ import_react2.default.createElement(
|
|
3484
3544
|
import_ui_extensions2.LoadingButton,
|
|
3485
3545
|
{
|
|
3486
3546
|
variant: submitVariant,
|
package/dist/index.mjs
CHANGED
|
@@ -182,6 +182,8 @@ var DataTable = ({
|
|
|
182
182
|
showFirstLastButtons,
|
|
183
183
|
// show First/Last page buttons (default: auto when pageCount > 5)
|
|
184
184
|
// Row count
|
|
185
|
+
title,
|
|
186
|
+
// optional title shown as demibold text above the table toolbar
|
|
185
187
|
showRowCount = true,
|
|
186
188
|
// show "X records" / "X of Y records" text
|
|
187
189
|
rowCountBold = false,
|
|
@@ -651,6 +653,8 @@ var DataTable = ({
|
|
|
651
653
|
selectionResetRef.current = combinedSelectionResetKey;
|
|
652
654
|
}, [combinedSelectionResetKey, selectable, externalSelectedIds]);
|
|
653
655
|
const selectedIds = externalSelectedIds != null ? new Set(externalSelectedIds) : internalSelectedIds;
|
|
656
|
+
const showToolbarCount = showRowCount && displayCount > 0 && !(showSelectionBar && selectable && selectedIds.size > 0);
|
|
657
|
+
const hasToolbarContent = showSearch && searchFields.length > 0 || filters.length > 0 || activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) || showToolbarCount;
|
|
654
658
|
const showRowActionsColumn = !!rowActions && !(hideRowActionsWhenSelectionActive && selectable && selectedIds.size > 0);
|
|
655
659
|
const applySelection = useCallback((nextSet) => {
|
|
656
660
|
if (externalSelectedIds == null) {
|
|
@@ -1000,7 +1004,7 @@ var DataTable = ({
|
|
|
1000
1004
|
}
|
|
1001
1005
|
);
|
|
1002
1006
|
};
|
|
1003
|
-
return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, /* @__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(
|
|
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(
|
|
1004
1008
|
SearchInput,
|
|
1005
1009
|
{
|
|
1006
1010
|
name: "datatable-search",
|
|
@@ -1026,7 +1030,7 @@ var DataTable = ({
|
|
|
1026
1030
|
onClick: () => handleFilterRemove("all")
|
|
1027
1031
|
},
|
|
1028
1032
|
resolvedClearAllLabel
|
|
1029
|
-
)))),
|
|
1033
|
+
)))), showToolbarCount && /* @__PURE__ */ React.createElement(Box, { flex: 1, alignSelf: "end" }, /* @__PURE__ */ React.createElement(Flex, { direction: "row", justify: "end" }, /* @__PURE__ */ React.createElement(Text, { variant: "microcopy", format: rowCountBold ? { fontWeight: "bold" } : void 0 }, recordCountLabel)))), showSelectionBar && selectable && selectedIds.size > 0 && (renderSelectionBar ? renderSelectionBar({
|
|
1030
1034
|
selectedIds,
|
|
1031
1035
|
selectedCount: selectedIds.size,
|
|
1032
1036
|
displayCount,
|
|
@@ -1519,8 +1523,12 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1519
1523
|
// validate on blur
|
|
1520
1524
|
validateOnSubmit = true,
|
|
1521
1525
|
// validate all before onSubmit
|
|
1522
|
-
onValidationChange
|
|
1526
|
+
onValidationChange,
|
|
1523
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
|
|
1524
1532
|
} = props;
|
|
1525
1533
|
const {
|
|
1526
1534
|
steps,
|
|
@@ -1545,6 +1553,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1545
1553
|
// () => void
|
|
1546
1554
|
submitPosition = "bottom",
|
|
1547
1555
|
// "bottom" | "none"
|
|
1556
|
+
submitAlign,
|
|
1557
|
+
// default single-step action row alignment
|
|
1548
1558
|
loading: controlledLoading,
|
|
1549
1559
|
// controlled loading state
|
|
1550
1560
|
disabled = false,
|
|
@@ -1667,6 +1677,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1667
1677
|
"tooltip",
|
|
1668
1678
|
"required",
|
|
1669
1679
|
"readOnly",
|
|
1680
|
+
"alwaysEditable",
|
|
1670
1681
|
"disabled",
|
|
1671
1682
|
"defaultValue",
|
|
1672
1683
|
"fieldProps",
|
|
@@ -1839,6 +1850,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1839
1850
|
}
|
|
1840
1851
|
return map;
|
|
1841
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);
|
|
1842
1864
|
const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
|
|
1843
1865
|
const configWarningsRef = useRef2(/* @__PURE__ */ new Set());
|
|
1844
1866
|
const warnConfig = useCallback2((message) => {
|
|
@@ -1849,6 +1871,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1849
1871
|
console.warn(`[FormBuilder] ${message}`);
|
|
1850
1872
|
}
|
|
1851
1873
|
}, [isDev]);
|
|
1874
|
+
useEffect2(() => {
|
|
1875
|
+
if (!isMultiStep || !submitAlign) return;
|
|
1876
|
+
warnConfig("submitAlign is ignored when steps are provided. Use renderButtons for custom multi-step button layout.");
|
|
1877
|
+
}, [isMultiStep, submitAlign, warnConfig]);
|
|
1852
1878
|
const replaceErrors = useCallback2(
|
|
1853
1879
|
(nextErrors) => {
|
|
1854
1880
|
if (controlledErrors == null) setInternalErrors(nextErrors);
|
|
@@ -2415,10 +2441,35 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2415
2441
|
const handleSubmit = useCallback2(
|
|
2416
2442
|
async (e) => {
|
|
2417
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
|
+
};
|
|
2418
2468
|
if (validateOnSubmit) {
|
|
2419
2469
|
const { errors, hasErrors } = validateVisibleFields(allVisibleFields);
|
|
2420
2470
|
if (hasErrors) {
|
|
2421
2471
|
replaceErrors(errors);
|
|
2472
|
+
reportValidationFailure(errors);
|
|
2422
2473
|
return;
|
|
2423
2474
|
}
|
|
2424
2475
|
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
@@ -2430,7 +2481,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2430
2481
|
])
|
|
2431
2482
|
];
|
|
2432
2483
|
await Promise.all(pendingValidations);
|
|
2433
|
-
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields))
|
|
2484
|
+
if (fieldSetHasErrors(formErrorsRef.current, allVisibleFields)) {
|
|
2485
|
+
reportValidationFailure(formErrorsRef.current);
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2434
2488
|
}
|
|
2435
2489
|
}
|
|
2436
2490
|
const reset = () => {
|
|
@@ -2476,7 +2530,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2476
2530
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2477
2531
|
}
|
|
2478
2532
|
},
|
|
2479
|
-
[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]
|
|
2480
2534
|
);
|
|
2481
2535
|
const handleNext = useCallback2(async () => {
|
|
2482
2536
|
if (!isMultiStep) return;
|
|
@@ -2569,8 +2623,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2569
2623
|
const fieldError = formErrors[field.name] || null;
|
|
2570
2624
|
const hasError = !!fieldError;
|
|
2571
2625
|
const isRequired = showRequiredIndicator && resolveRequired(field, formValues);
|
|
2572
|
-
const
|
|
2573
|
-
const
|
|
2626
|
+
const fieldFormReadOnly = field.alwaysEditable ? false : formReadOnly;
|
|
2627
|
+
const isReadOnly = field.readOnly || fieldFormReadOnly;
|
|
2628
|
+
const isDisabled = disabled || resolveDisabled(field, formValues) || fieldFormReadOnly;
|
|
2574
2629
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2575
2630
|
if (field.type === "display" || field.type === "slot") {
|
|
2576
2631
|
if (field.render) {
|
|
@@ -2613,8 +2668,9 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2613
2668
|
const sfValue = formValues[sf.name];
|
|
2614
2669
|
const sfError = formErrors[sf.name] || null;
|
|
2615
2670
|
const sfLabel = itemIdx === 0 ? sf.label : void 0;
|
|
2616
|
-
const
|
|
2617
|
-
const
|
|
2671
|
+
const sfFormReadOnly = sf.alwaysEditable ? false : formReadOnly;
|
|
2672
|
+
const sfReadOnly = sf.readOnly || sfFormReadOnly;
|
|
2673
|
+
const sfDisabled = disabled || resolveDisabled(sf, formValues) || sfFormReadOnly;
|
|
2618
2674
|
const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
|
|
2619
2675
|
const sfProps = {
|
|
2620
2676
|
name: sf.name,
|
|
@@ -3411,13 +3467,16 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3411
3467
|
const sectionContext = { values: formValues, errors: formErrors };
|
|
3412
3468
|
const sectionOverrides = sec.columns ? { columns: sec.columns } : void 0;
|
|
3413
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;
|
|
3414
3473
|
const accordion = /* @__PURE__ */ React2.createElement(
|
|
3415
3474
|
Accordion,
|
|
3416
3475
|
{
|
|
3417
|
-
key:
|
|
3476
|
+
key: accordionKey,
|
|
3418
3477
|
title: sec.label,
|
|
3419
3478
|
size: "sm",
|
|
3420
|
-
defaultOpen:
|
|
3479
|
+
defaultOpen: accordionDefaultOpen
|
|
3421
3480
|
},
|
|
3422
3481
|
accordionContent
|
|
3423
3482
|
);
|
|
@@ -3451,6 +3510,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3451
3510
|
if (submitPosition === "none" || formReadOnly) return null;
|
|
3452
3511
|
const isLastStep = !isMultiStep || currentStep === steps.length - 1;
|
|
3453
3512
|
const isFirstStep = !isMultiStep || currentStep === 0;
|
|
3513
|
+
const singleStepJustify = submitAlign || (showCancel ? "between" : "start");
|
|
3454
3514
|
const buttonContext = {
|
|
3455
3515
|
isMultiStep,
|
|
3456
3516
|
isFirstStep,
|
|
@@ -3485,7 +3545,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3485
3545
|
submitButtonLabel
|
|
3486
3546
|
) : /* @__PURE__ */ React2.createElement(Button2, { variant: "primary", onClick: handleNext, disabled }, nextButtonLabel)));
|
|
3487
3547
|
}
|
|
3488
|
-
return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify:
|
|
3548
|
+
return /* @__PURE__ */ React2.createElement(Flex2, { direction: "row", justify: singleStepJustify, gap: "sm" }, showCancel && /* @__PURE__ */ React2.createElement(Button2, { variant: "secondary", onClick: onCancel, disabled }, cancelButtonLabel), /* @__PURE__ */ React2.createElement(
|
|
3489
3549
|
LoadingButton,
|
|
3490
3550
|
{
|
|
3491
3551
|
variant: submitVariant,
|
package/form.d.ts
CHANGED
package/index.d.ts
CHANGED