hs-uix 1.3.0 → 1.4.1
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 +39 -28
- package/dist/datatable.mjs +39 -28
- package/dist/form.js +228 -87
- package/dist/form.mjs +228 -87
- package/dist/index.js +267 -115
- package/dist/index.mjs +267 -115
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -509,28 +509,33 @@ var DataTable = ({
|
|
|
509
509
|
return next;
|
|
510
510
|
});
|
|
511
511
|
}, []);
|
|
512
|
-
const
|
|
513
|
-
if (!groupedData) return
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (expandedGroups.has(group.key)) {
|
|
518
|
-
group.rows.forEach((row) => flat.push({ type: "data", row }));
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
return flat;
|
|
522
|
-
}, [groupedData, sortedData, data, serverSide, expandedGroups]);
|
|
523
|
-
const totalItems = serverSide ? totalCount || data.length : flatRows.length;
|
|
512
|
+
const datasetRows = (0, import_react.useMemo)(() => {
|
|
513
|
+
if (!groupedData) return serverSide ? data : sortedData;
|
|
514
|
+
return groupedData.flatMap((group) => group.rows);
|
|
515
|
+
}, [groupedData, sortedData, data, serverSide]);
|
|
516
|
+
const totalItems = serverSide ? totalCount || data.length : datasetRows.length;
|
|
524
517
|
const pageCount = Math.ceil(totalItems / pageSize);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
} else {
|
|
529
|
-
displayRows = flatRows.slice(
|
|
518
|
+
const paginatedRows = (0, import_react.useMemo)(() => {
|
|
519
|
+
if (serverSide) return datasetRows;
|
|
520
|
+
return datasetRows.slice(
|
|
530
521
|
(activePage - 1) * pageSize,
|
|
531
522
|
activePage * pageSize
|
|
532
523
|
);
|
|
533
|
-
}
|
|
524
|
+
}, [serverSide, datasetRows, activePage, pageSize]);
|
|
525
|
+
const displayRows = (0, import_react.useMemo)(() => {
|
|
526
|
+
if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
|
|
527
|
+
const pageRows = new Set(paginatedRows);
|
|
528
|
+
const rows = [];
|
|
529
|
+
groupedData.forEach((group) => {
|
|
530
|
+
const groupPageRows = group.rows.filter((row) => pageRows.has(row));
|
|
531
|
+
if (groupPageRows.length === 0) return;
|
|
532
|
+
rows.push({ type: "group-header", group });
|
|
533
|
+
if (expandedGroups.has(group.key)) {
|
|
534
|
+
groupPageRows.forEach((row) => rows.push({ type: "data", row }));
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
return rows;
|
|
538
|
+
}, [groupedData, paginatedRows, expandedGroups]);
|
|
534
539
|
const footerData = serverSide ? data : filteredData;
|
|
535
540
|
const activeChips = (0, import_react.useMemo)(() => {
|
|
536
541
|
const chips = [];
|
|
@@ -641,8 +646,8 @@ var DataTable = ({
|
|
|
641
646
|
return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
|
|
642
647
|
}, [serverSide, data, displayRows, rowIdField]);
|
|
643
648
|
const allRowIds = (0, import_react.useMemo)(
|
|
644
|
-
() =>
|
|
645
|
-
[
|
|
649
|
+
() => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
|
|
650
|
+
[datasetRows, rowIdField]
|
|
646
651
|
);
|
|
647
652
|
const handleSelectRow = (0, import_react.useCallback)((rowId, checked) => {
|
|
648
653
|
const next = new Set(selectedIds);
|
|
@@ -689,19 +694,25 @@ var DataTable = ({
|
|
|
689
694
|
if (row) onEditStart(row, field, currentValue);
|
|
690
695
|
}
|
|
691
696
|
}, [onEditStart, data, rowIdField]);
|
|
692
|
-
const commitEdit = (0, import_react.useCallback)((row, field, value) => {
|
|
697
|
+
const commitEdit = (0, import_react.useCallback)((row, field, value, options = {}) => {
|
|
698
|
+
const { keepEditing = false } = options;
|
|
693
699
|
const col = columns.find((c) => c.field === field);
|
|
694
700
|
if (col == null ? void 0 : col.editValidate) {
|
|
695
701
|
const result = col.editValidate(value, row);
|
|
696
702
|
if (result !== true && result !== void 0 && result !== null) {
|
|
697
703
|
setEditError(typeof result === "string" ? result : "Invalid value");
|
|
698
|
-
return;
|
|
704
|
+
return false;
|
|
699
705
|
}
|
|
700
706
|
}
|
|
701
707
|
if (onRowEdit) onRowEdit(row, field, value);
|
|
702
|
-
|
|
703
|
-
|
|
708
|
+
if (!keepEditing) {
|
|
709
|
+
setEditingCell(null);
|
|
710
|
+
setEditValue(null);
|
|
711
|
+
} else {
|
|
712
|
+
setEditValue(value);
|
|
713
|
+
}
|
|
704
714
|
setEditError(null);
|
|
715
|
+
return true;
|
|
705
716
|
}, [onRowEdit, columns]);
|
|
706
717
|
const renderEditControl = (col, row) => {
|
|
707
718
|
const type = col.editType || "text";
|
|
@@ -760,12 +771,12 @@ var DataTable = ({
|
|
|
760
771
|
case "datetime":
|
|
761
772
|
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
|
|
762
773
|
const next = { ...editValue, date: val };
|
|
763
|
-
|
|
764
|
-
|
|
774
|
+
handleInput(next);
|
|
775
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
765
776
|
}, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
|
|
766
777
|
const next = { ...editValue, time: val };
|
|
767
|
-
|
|
768
|
-
|
|
778
|
+
handleInput(next);
|
|
779
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
769
780
|
}, onBlur: maybeExitDatetimeEdit }));
|
|
770
781
|
case "toggle":
|
|
771
782
|
return /* @__PURE__ */ import_react.default.createElement(import_ui_extensions.Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
|
|
@@ -1126,6 +1137,7 @@ var getEmptyValue = (field) => {
|
|
|
1126
1137
|
case "datetime":
|
|
1127
1138
|
return void 0;
|
|
1128
1139
|
case "display":
|
|
1140
|
+
case "slot":
|
|
1129
1141
|
case "crmPropertyList":
|
|
1130
1142
|
case "crmAssociationPropertyList":
|
|
1131
1143
|
return void 0;
|
|
@@ -1269,7 +1281,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
|
|
|
1269
1281
|
var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
|
|
1270
1282
|
const includeCustomValidators = options.includeCustomValidators !== false;
|
|
1271
1283
|
const msg = options.messages || {};
|
|
1272
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
1284
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
1273
1285
|
const isRequired = resolveRequired(field, allValues);
|
|
1274
1286
|
const plugin = fieldTypes && fieldTypes[field.type];
|
|
1275
1287
|
const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
|
|
@@ -1492,6 +1504,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1492
1504
|
// explicit row layout array (overrides columns + columnWidth)
|
|
1493
1505
|
sections,
|
|
1494
1506
|
// FormBuilderSection[] — accordion field grouping
|
|
1507
|
+
groups,
|
|
1508
|
+
// Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
|
|
1495
1509
|
gap = "sm",
|
|
1496
1510
|
// gap between fields
|
|
1497
1511
|
showRequiredIndicator = true,
|
|
@@ -1701,7 +1715,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1701
1715
|
const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
|
|
1702
1716
|
const vals = {};
|
|
1703
1717
|
for (const field of fields) {
|
|
1704
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
1718
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
1705
1719
|
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
1706
1720
|
for (const item of field.items) {
|
|
1707
1721
|
const subFields = field.fields(item);
|
|
@@ -1735,9 +1749,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1735
1749
|
const inputDebounceRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1736
1750
|
const rowKeyRef = (0, import_react2.useRef)(/* @__PURE__ */ new WeakMap());
|
|
1737
1751
|
const rowKeyCounterRef = (0, import_react2.useRef)(0);
|
|
1752
|
+
const controlledBaselineLockedRef = (0, import_react2.useRef)(false);
|
|
1738
1753
|
const initialSnapshot = (0, import_react2.useRef)(null);
|
|
1739
1754
|
if (initialSnapshot.current === null) {
|
|
1740
|
-
initialSnapshot.current = deepClone(computeInitialValues());
|
|
1755
|
+
initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
|
|
1741
1756
|
}
|
|
1742
1757
|
const formValues = values != null ? values : internalValues;
|
|
1743
1758
|
const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
|
|
@@ -1749,6 +1764,10 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1749
1764
|
const draftValuesRef = (0, import_react2.useRef)(null);
|
|
1750
1765
|
formValuesRef.current = formValues;
|
|
1751
1766
|
formErrorsRef.current = formErrors;
|
|
1767
|
+
const syncDirtyBaseline = (0, import_react2.useCallback)((nextValues) => {
|
|
1768
|
+
initialSnapshot.current = deepClone(nextValues || {});
|
|
1769
|
+
prevAutoSaveValues.current = deepClone(nextValues || {});
|
|
1770
|
+
}, []);
|
|
1752
1771
|
const fieldByName = (0, import_react2.useMemo)(() => {
|
|
1753
1772
|
const map = /* @__PURE__ */ new Map();
|
|
1754
1773
|
for (const field of fields) {
|
|
@@ -1825,6 +1844,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1825
1844
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1826
1845
|
};
|
|
1827
1846
|
}, []);
|
|
1847
|
+
(0, import_react2.useEffect)(() => {
|
|
1848
|
+
if (values == null) return;
|
|
1849
|
+
if (controlledBaselineLockedRef.current) return;
|
|
1850
|
+
syncDirtyBaseline(values);
|
|
1851
|
+
}, [values, syncDirtyBaseline]);
|
|
1828
1852
|
const isDirty = (0, import_react2.useMemo)(() => {
|
|
1829
1853
|
return !deepEqual(formValues, initialSnapshot.current);
|
|
1830
1854
|
}, [formValues]);
|
|
@@ -1943,6 +1967,50 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1943
1967
|
},
|
|
1944
1968
|
[fieldTypes]
|
|
1945
1969
|
);
|
|
1970
|
+
const setRepeaterSubFieldError = (0, import_react2.useCallback)(
|
|
1971
|
+
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
1972
|
+
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
1973
|
+
const merged = { ...formErrorsRef.current };
|
|
1974
|
+
if (errorMessage) {
|
|
1975
|
+
merged[key] = errorMessage;
|
|
1976
|
+
} else {
|
|
1977
|
+
delete merged[key];
|
|
1978
|
+
}
|
|
1979
|
+
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
1980
|
+
const match = k.match(/\[(\d+)\]\./);
|
|
1981
|
+
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
1982
|
+
return { key: k, row };
|
|
1983
|
+
}).sort((a, b) => a.row - b.row);
|
|
1984
|
+
if (subErrors.length > 0) {
|
|
1985
|
+
const first = subErrors[0];
|
|
1986
|
+
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
1987
|
+
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
1988
|
+
delete merged[fieldName];
|
|
1989
|
+
}
|
|
1990
|
+
replaceErrors(merged);
|
|
1991
|
+
},
|
|
1992
|
+
[replaceErrors]
|
|
1993
|
+
);
|
|
1994
|
+
const expandValidationFields = (0, import_react2.useCallback)(
|
|
1995
|
+
(fieldSubset) => {
|
|
1996
|
+
const toValidate = fieldSubset || visibleFields;
|
|
1997
|
+
const expanded = [];
|
|
1998
|
+
for (const field of toValidate) {
|
|
1999
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
2000
|
+
for (const item of field.items) {
|
|
2001
|
+
for (const subField of field.fields(item)) {
|
|
2002
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
2003
|
+
expanded.push(subField);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
expanded.push(field);
|
|
2009
|
+
}
|
|
2010
|
+
return expanded;
|
|
2011
|
+
},
|
|
2012
|
+
[visibleFields, formValues]
|
|
2013
|
+
);
|
|
1946
2014
|
const validateField = (0, import_react2.useCallback)(
|
|
1947
2015
|
(name, value) => {
|
|
1948
2016
|
const field = fieldByName.get(name);
|
|
@@ -1962,7 +2030,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1962
2030
|
);
|
|
1963
2031
|
const validateVisibleFields = (0, import_react2.useCallback)(
|
|
1964
2032
|
(fieldSubset) => {
|
|
1965
|
-
const toValidate = fieldSubset
|
|
2033
|
+
const toValidate = expandValidationFields(fieldSubset);
|
|
1966
2034
|
const errors = {};
|
|
1967
2035
|
let hasErrors = false;
|
|
1968
2036
|
for (const field of toValidate) {
|
|
@@ -1982,52 +2050,54 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
1982
2050
|
}
|
|
1983
2051
|
return { errors, hasErrors };
|
|
1984
2052
|
},
|
|
1985
|
-
[
|
|
2053
|
+
[expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
|
|
1986
2054
|
);
|
|
1987
|
-
const
|
|
1988
|
-
(
|
|
1989
|
-
const field =
|
|
1990
|
-
if (!field || field.type === "repeater") return null;
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
2055
|
+
const runAsyncValidationTarget = (0, import_react2.useCallback)(
|
|
2056
|
+
(target) => {
|
|
2057
|
+
const { validationKey, field, value, allValues, applyError } = target || {};
|
|
2058
|
+
if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
2059
|
+
const syncError = runValidators(value, field, allValues, fieldTypes, {
|
|
2060
|
+
includeCustomValidators: false,
|
|
2061
|
+
messages: validationMessages
|
|
2062
|
+
});
|
|
2063
|
+
const prevController = asyncAbortRef.current.get(validationKey);
|
|
1994
2064
|
if (prevController) prevController.abort();
|
|
1995
|
-
asyncAbortRef.current.delete(
|
|
2065
|
+
asyncAbortRef.current.delete(validationKey);
|
|
1996
2066
|
setValidatingFields((prev) => {
|
|
1997
|
-
if (!prev[
|
|
2067
|
+
if (!prev[validationKey]) return prev;
|
|
1998
2068
|
const next = { ...prev };
|
|
1999
|
-
delete next[
|
|
2069
|
+
delete next[validationKey];
|
|
2000
2070
|
return next;
|
|
2001
2071
|
});
|
|
2002
2072
|
if (syncError) return null;
|
|
2003
|
-
const version = (asyncValidationVersionRef.current.get(
|
|
2004
|
-
asyncValidationVersionRef.current.set(
|
|
2073
|
+
const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
|
|
2074
|
+
asyncValidationVersionRef.current.set(validationKey, version);
|
|
2005
2075
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
2006
|
-
if (controller) asyncAbortRef.current.set(
|
|
2076
|
+
if (controller) asyncAbortRef.current.set(validationKey, controller);
|
|
2007
2077
|
let asyncPromises;
|
|
2008
2078
|
try {
|
|
2009
2079
|
asyncPromises = collectAsyncValidatorPromises(
|
|
2010
|
-
|
|
2080
|
+
value,
|
|
2011
2081
|
field,
|
|
2012
|
-
|
|
2082
|
+
allValues,
|
|
2013
2083
|
controller ? { signal: controller.signal } : void 0
|
|
2014
2084
|
);
|
|
2015
2085
|
} catch (err) {
|
|
2016
|
-
|
|
2086
|
+
applyError((err == null ? void 0 : err.message) || "Validation failed");
|
|
2017
2087
|
return null;
|
|
2018
2088
|
}
|
|
2019
2089
|
if (asyncPromises.length === 0) {
|
|
2020
|
-
asyncAbortRef.current.delete(
|
|
2090
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2021
2091
|
return null;
|
|
2022
2092
|
}
|
|
2023
2093
|
const validationPromise = Promise.all(asyncPromises).then(
|
|
2024
2094
|
(results) => {
|
|
2025
|
-
if (asyncValidationVersionRef.current.get(
|
|
2026
|
-
asyncValidationRef.current.delete(
|
|
2027
|
-
asyncAbortRef.current.delete(
|
|
2095
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2096
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2097
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2028
2098
|
setValidatingFields((prev) => {
|
|
2029
2099
|
const next = { ...prev };
|
|
2030
|
-
delete next[
|
|
2100
|
+
delete next[validationKey];
|
|
2031
2101
|
return next;
|
|
2032
2102
|
});
|
|
2033
2103
|
let err = null;
|
|
@@ -2038,50 +2108,128 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2038
2108
|
break;
|
|
2039
2109
|
}
|
|
2040
2110
|
}
|
|
2041
|
-
|
|
2111
|
+
applyError(err);
|
|
2042
2112
|
},
|
|
2043
2113
|
(rejection) => {
|
|
2044
|
-
if (asyncValidationVersionRef.current.get(
|
|
2045
|
-
asyncValidationRef.current.delete(
|
|
2046
|
-
asyncAbortRef.current.delete(
|
|
2114
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2115
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2116
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2047
2117
|
setValidatingFields((prev) => {
|
|
2048
2118
|
const next = { ...prev };
|
|
2049
|
-
delete next[
|
|
2119
|
+
delete next[validationKey];
|
|
2050
2120
|
return next;
|
|
2051
2121
|
});
|
|
2052
2122
|
if (rejection && rejection.name === "AbortError") return;
|
|
2053
|
-
|
|
2123
|
+
applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
|
|
2054
2124
|
}
|
|
2055
2125
|
);
|
|
2056
|
-
asyncValidationRef.current.set(
|
|
2057
|
-
setValidatingFields((prev) => ({ ...prev, [
|
|
2126
|
+
asyncValidationRef.current.set(validationKey, validationPromise);
|
|
2127
|
+
setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
|
|
2058
2128
|
return validationPromise;
|
|
2059
2129
|
},
|
|
2060
|
-
[
|
|
2130
|
+
[fieldTypes, validationMessages]
|
|
2061
2131
|
);
|
|
2062
|
-
const
|
|
2132
|
+
const runAsyncValidation = (0, import_react2.useCallback)(
|
|
2063
2133
|
(name, value) => {
|
|
2064
2134
|
const field = fieldByName.get(name);
|
|
2065
|
-
if (!field || field.type === "repeater") return;
|
|
2066
|
-
|
|
2135
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
2136
|
+
return runAsyncValidationTarget({
|
|
2137
|
+
validationKey: name,
|
|
2138
|
+
field,
|
|
2139
|
+
value: value != null ? value : formValues[name],
|
|
2140
|
+
allValues: formValues,
|
|
2141
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
2142
|
+
});
|
|
2143
|
+
},
|
|
2144
|
+
[fieldByName, formValues, runAsyncValidationTarget, updateErrors]
|
|
2145
|
+
);
|
|
2146
|
+
const triggerAsyncValidationTarget = (0, import_react2.useCallback)(
|
|
2147
|
+
(target) => {
|
|
2148
|
+
if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
|
|
2149
|
+
const debounceMs = target.field.validateDebounce;
|
|
2067
2150
|
if (debounceMs && debounceMs > 0) {
|
|
2068
|
-
const existing = debounceTimersRef.current.get(
|
|
2151
|
+
const existing = debounceTimersRef.current.get(target.validationKey);
|
|
2069
2152
|
if (existing) clearTimeout(existing);
|
|
2070
2153
|
const timer = setTimeout(() => {
|
|
2071
|
-
debounceTimersRef.current.delete(
|
|
2072
|
-
|
|
2154
|
+
debounceTimersRef.current.delete(target.validationKey);
|
|
2155
|
+
runAsyncValidationTarget(target);
|
|
2073
2156
|
}, debounceMs);
|
|
2074
|
-
debounceTimersRef.current.set(
|
|
2157
|
+
debounceTimersRef.current.set(target.validationKey, timer);
|
|
2075
2158
|
} else {
|
|
2076
|
-
|
|
2159
|
+
runAsyncValidationTarget(target);
|
|
2077
2160
|
}
|
|
2078
2161
|
},
|
|
2079
|
-
[
|
|
2162
|
+
[runAsyncValidationTarget]
|
|
2163
|
+
);
|
|
2164
|
+
const triggerAsyncValidation = (0, import_react2.useCallback)(
|
|
2165
|
+
(name, value) => {
|
|
2166
|
+
const field = fieldByName.get(name);
|
|
2167
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
|
|
2168
|
+
triggerAsyncValidationTarget({
|
|
2169
|
+
validationKey: name,
|
|
2170
|
+
field,
|
|
2171
|
+
value: value != null ? value : formValuesRef.current[name],
|
|
2172
|
+
allValues: formValuesRef.current,
|
|
2173
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
2174
|
+
});
|
|
2175
|
+
},
|
|
2176
|
+
[fieldByName, triggerAsyncValidationTarget, updateErrors]
|
|
2177
|
+
);
|
|
2178
|
+
const getAsyncValidationTargets = (0, import_react2.useCallback)(
|
|
2179
|
+
(fieldSubset) => {
|
|
2180
|
+
const toValidate = fieldSubset || visibleFields;
|
|
2181
|
+
const targets = [];
|
|
2182
|
+
for (const field of toValidate) {
|
|
2183
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
2184
|
+
for (const item of field.items) {
|
|
2185
|
+
for (const subField of field.fields(item)) {
|
|
2186
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
2187
|
+
targets.push({
|
|
2188
|
+
validationKey: subField.name,
|
|
2189
|
+
field: subField,
|
|
2190
|
+
value: formValues[subField.name],
|
|
2191
|
+
allValues: formValues,
|
|
2192
|
+
applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (field.type === "repeater") {
|
|
2199
|
+
const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
|
|
2200
|
+
const subFields = field.fields || [];
|
|
2201
|
+
rows.forEach((row, rowIdx) => {
|
|
2202
|
+
const rowValues = { ...formValues, [field.name]: rows };
|
|
2203
|
+
subFields.forEach((subField) => {
|
|
2204
|
+
if (subField.visible && !subField.visible(rowValues)) return;
|
|
2205
|
+
targets.push({
|
|
2206
|
+
validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
|
|
2207
|
+
field: subField,
|
|
2208
|
+
value: row == null ? void 0 : row[subField.name],
|
|
2209
|
+
allValues: rowValues,
|
|
2210
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
2211
|
+
});
|
|
2212
|
+
});
|
|
2213
|
+
});
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
targets.push({
|
|
2217
|
+
validationKey: field.name,
|
|
2218
|
+
field,
|
|
2219
|
+
value: formValues[field.name],
|
|
2220
|
+
allValues: formValues,
|
|
2221
|
+
applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
return targets;
|
|
2225
|
+
},
|
|
2226
|
+
[visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
|
|
2080
2227
|
);
|
|
2081
2228
|
const commitValues = (0, import_react2.useCallback)(
|
|
2082
2229
|
(nextValues) => {
|
|
2083
2230
|
formValuesRef.current = nextValues;
|
|
2084
2231
|
if (values != null) {
|
|
2232
|
+
controlledBaselineLockedRef.current = true;
|
|
2085
2233
|
if (onChange) onChange(nextValues);
|
|
2086
2234
|
} else {
|
|
2087
2235
|
setInternalValues(nextValues);
|
|
@@ -2099,7 +2247,8 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2099
2247
|
[commitValues]
|
|
2100
2248
|
);
|
|
2101
2249
|
const handleFieldChange = (0, import_react2.useCallback)(
|
|
2102
|
-
(name, value) => {
|
|
2250
|
+
(name, value, options = {}) => {
|
|
2251
|
+
const { clearNestedErrors = true } = options;
|
|
2103
2252
|
const newValues = { ...formValuesRef.current, [name]: value };
|
|
2104
2253
|
const queue = [name];
|
|
2105
2254
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2140,9 +2289,11 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2140
2289
|
if (formErrorsRef.current[name] != null) {
|
|
2141
2290
|
clearedErrors[name] = null;
|
|
2142
2291
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2292
|
+
if (clearNestedErrors) {
|
|
2293
|
+
for (const key of Object.keys(formErrorsRef.current)) {
|
|
2294
|
+
if (key.startsWith(`${name}[`)) {
|
|
2295
|
+
clearedErrors[key] = null;
|
|
2296
|
+
}
|
|
2146
2297
|
}
|
|
2147
2298
|
}
|
|
2148
2299
|
draftValuesRef.current = newValues;
|
|
@@ -2211,7 +2362,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2211
2362
|
replaceErrors(errors);
|
|
2212
2363
|
return;
|
|
2213
2364
|
}
|
|
2214
|
-
const asyncSubmitValidations = allVisibleFields.map((
|
|
2365
|
+
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2215
2366
|
if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2216
2367
|
const pendingValidations = [
|
|
2217
2368
|
.../* @__PURE__ */ new Set([
|
|
@@ -2226,6 +2377,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2226
2377
|
const reset = () => {
|
|
2227
2378
|
const fresh = computeInitialValues();
|
|
2228
2379
|
if (values == null) setInternalValues(fresh);
|
|
2380
|
+
controlledBaselineLockedRef.current = false;
|
|
2229
2381
|
replaceErrors({});
|
|
2230
2382
|
initialSnapshot.current = deepClone(fresh);
|
|
2231
2383
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2233,7 +2385,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2233
2385
|
const rawValues = {};
|
|
2234
2386
|
for (const key of Object.keys(formValues)) {
|
|
2235
2387
|
const f = fieldByName.get(key);
|
|
2236
|
-
if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
2388
|
+
if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
2237
2389
|
rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
|
|
2238
2390
|
}
|
|
2239
2391
|
for (const f of fields) {
|
|
@@ -2265,7 +2417,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2265
2417
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2266
2418
|
}
|
|
2267
2419
|
},
|
|
2268
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName,
|
|
2420
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
2269
2421
|
);
|
|
2270
2422
|
const handleNext = (0, import_react2.useCallback)(async () => {
|
|
2271
2423
|
if (!isMultiStep) return;
|
|
@@ -2277,7 +2429,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2277
2429
|
replaceErrors({ ...formErrorsRef.current, ...errors });
|
|
2278
2430
|
return;
|
|
2279
2431
|
}
|
|
2280
|
-
const asyncStepValidations = stepFields.map((
|
|
2432
|
+
const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2281
2433
|
if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2282
2434
|
const pendingValidations = [
|
|
2283
2435
|
.../* @__PURE__ */ new Set([
|
|
@@ -2302,7 +2454,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2302
2454
|
} else {
|
|
2303
2455
|
setInternalStep(nextStep);
|
|
2304
2456
|
}
|
|
2305
|
-
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields,
|
|
2457
|
+
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
|
|
2306
2458
|
const handleBack = (0, import_react2.useCallback)(() => {
|
|
2307
2459
|
if (!isMultiStep) return;
|
|
2308
2460
|
const prevStep = Math.max(currentStep - 1, 0);
|
|
@@ -2334,6 +2486,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2334
2486
|
reset: () => {
|
|
2335
2487
|
const fresh = computeInitialValues();
|
|
2336
2488
|
if (values == null) setInternalValues(fresh);
|
|
2489
|
+
controlledBaselineLockedRef.current = false;
|
|
2337
2490
|
replaceErrors({});
|
|
2338
2491
|
initialSnapshot.current = deepClone(fresh);
|
|
2339
2492
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2346,30 +2499,6 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2346
2499
|
replaceErrors(errors);
|
|
2347
2500
|
}
|
|
2348
2501
|
}));
|
|
2349
|
-
const setRepeaterSubFieldError = (0, import_react2.useCallback)(
|
|
2350
|
-
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
2351
|
-
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
2352
|
-
const merged = { ...formErrorsRef.current };
|
|
2353
|
-
if (errorMessage) {
|
|
2354
|
-
merged[key] = errorMessage;
|
|
2355
|
-
} else {
|
|
2356
|
-
delete merged[key];
|
|
2357
|
-
}
|
|
2358
|
-
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
2359
|
-
const match = k.match(/\[(\d+)\]\./);
|
|
2360
|
-
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
2361
|
-
return { key: k, row };
|
|
2362
|
-
}).sort((a, b) => a.row - b.row);
|
|
2363
|
-
if (subErrors.length > 0) {
|
|
2364
|
-
const first = subErrors[0];
|
|
2365
|
-
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
2366
|
-
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
2367
|
-
delete merged[fieldName];
|
|
2368
|
-
}
|
|
2369
|
-
replaceErrors(merged);
|
|
2370
|
-
},
|
|
2371
|
-
[replaceErrors]
|
|
2372
|
-
);
|
|
2373
2502
|
const renderField = (field) => {
|
|
2374
2503
|
const fieldError = formErrors[field.name] || null;
|
|
2375
2504
|
const rendered = renderFieldInner(field);
|
|
@@ -2384,7 +2513,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2384
2513
|
const isReadOnly = field.readOnly || formReadOnly;
|
|
2385
2514
|
const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
|
|
2386
2515
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2387
|
-
if (field.type === "display") {
|
|
2516
|
+
if (field.type === "display" || field.type === "slot") {
|
|
2388
2517
|
if (field.render) {
|
|
2389
2518
|
return field.render({
|
|
2390
2519
|
values: formValues,
|
|
@@ -2849,12 +2978,13 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2849
2978
|
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
2850
2979
|
const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
|
|
2851
2980
|
setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
|
|
2981
|
+
return err;
|
|
2852
2982
|
};
|
|
2853
2983
|
const handleSubFieldChange = (rowIdx, subField, subValue) => {
|
|
2854
2984
|
const updated = rows.map(
|
|
2855
2985
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2856
2986
|
);
|
|
2857
|
-
handleFieldChange(field.name, updated);
|
|
2987
|
+
handleFieldChange(field.name, updated, { clearNestedErrors: false });
|
|
2858
2988
|
if (validateOnChange) {
|
|
2859
2989
|
validateSubField(rowIdx, subField, subValue, updated);
|
|
2860
2990
|
}
|
|
@@ -2864,13 +2994,24 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2864
2994
|
const nextRows = rows.map(
|
|
2865
2995
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2866
2996
|
);
|
|
2867
|
-
validateSubField(rowIdx, subField, subValue, nextRows);
|
|
2997
|
+
const err = validateSubField(rowIdx, subField, subValue, nextRows);
|
|
2998
|
+
if (err) return;
|
|
2999
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
|
|
3000
|
+
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
3001
|
+
triggerAsyncValidationTarget({
|
|
3002
|
+
validationKey,
|
|
3003
|
+
field: subField,
|
|
3004
|
+
value: subValue,
|
|
3005
|
+
allValues: rowValues,
|
|
3006
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
3007
|
+
});
|
|
2868
3008
|
};
|
|
2869
3009
|
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
|
|
2870
3010
|
const sfValue = row[sf.name];
|
|
2871
3011
|
const sfLabel = rowIdx === 0 ? sf.label : void 0;
|
|
2872
3012
|
const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
|
|
2873
3013
|
const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
|
|
3014
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
|
|
2874
3015
|
const sfProps = {
|
|
2875
3016
|
name: `${field.name}-${rowIdx}-${sf.name}`,
|
|
2876
3017
|
label: sfLabel,
|
|
@@ -2879,6 +3020,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2879
3020
|
disabled: resolveDisabled(sf, formValues) || isDisabled,
|
|
2880
3021
|
error: !!sfError,
|
|
2881
3022
|
validationMessage: sfError || void 0,
|
|
3023
|
+
...validatingFields[validationKey] ? { loading: true } : {},
|
|
2882
3024
|
...sf.fieldProps || {}
|
|
2883
3025
|
};
|
|
2884
3026
|
let sfElement;
|
|
@@ -2956,7 +3098,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2956
3098
|
const getFieldColSpan = (field) => {
|
|
2957
3099
|
if (field.colSpan != null) return Math.min(field.colSpan, columns);
|
|
2958
3100
|
if (field.width === "full" && columns > 1) return columns;
|
|
2959
|
-
if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
3101
|
+
if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
2960
3102
|
return 1;
|
|
2961
3103
|
};
|
|
2962
3104
|
const getDependents = (parentField) => visibleFields.filter(
|
|
@@ -2981,7 +3123,7 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
2981
3123
|
const colSpan = (field) => {
|
|
2982
3124
|
if (field.colSpan != null) return Math.min(field.colSpan, cols);
|
|
2983
3125
|
if (field.width === "full" && cols > 1) return cols;
|
|
2984
|
-
if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
3126
|
+
if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
2985
3127
|
return 1;
|
|
2986
3128
|
};
|
|
2987
3129
|
const flushRow = () => {
|
|
@@ -3145,13 +3287,23 @@ var FormBuilder = (0, import_react2.forwardRef)(function FormBuilder2(props, ref
|
|
|
3145
3287
|
const elements = [];
|
|
3146
3288
|
for (let i = 0; i < chunks.length; i++) {
|
|
3147
3289
|
const chunk = chunks[i];
|
|
3148
|
-
|
|
3290
|
+
const opts = chunk.group && groups && groups[chunk.group] || {};
|
|
3291
|
+
const showDivider = opts.showDivider !== false;
|
|
3292
|
+
const showLabel = opts.showLabel !== false;
|
|
3293
|
+
if (i > 0 && showDivider) {
|
|
3149
3294
|
elements.push(/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, { key: `group-div-${i}` }));
|
|
3150
3295
|
}
|
|
3151
|
-
if (chunk.group) {
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3296
|
+
if (chunk.group && showLabel) {
|
|
3297
|
+
if (typeof opts.renderHeader === "function") {
|
|
3298
|
+
const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
|
|
3299
|
+
if (header) elements.push(
|
|
3300
|
+
/* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: `group-header-${i}` }, header)
|
|
3301
|
+
);
|
|
3302
|
+
} else {
|
|
3303
|
+
elements.push(
|
|
3304
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3155
3307
|
}
|
|
3156
3308
|
const chunkElements = renderFn(chunk.fields);
|
|
3157
3309
|
if (Array.isArray(chunkElements)) {
|