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.mjs
CHANGED
|
@@ -502,28 +502,33 @@ var DataTable = ({
|
|
|
502
502
|
return next;
|
|
503
503
|
});
|
|
504
504
|
}, []);
|
|
505
|
-
const
|
|
506
|
-
if (!groupedData) return
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (expandedGroups.has(group.key)) {
|
|
511
|
-
group.rows.forEach((row) => flat.push({ type: "data", row }));
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
return flat;
|
|
515
|
-
}, [groupedData, sortedData, data, serverSide, expandedGroups]);
|
|
516
|
-
const totalItems = serverSide ? totalCount || data.length : flatRows.length;
|
|
505
|
+
const datasetRows = useMemo(() => {
|
|
506
|
+
if (!groupedData) return serverSide ? data : sortedData;
|
|
507
|
+
return groupedData.flatMap((group) => group.rows);
|
|
508
|
+
}, [groupedData, sortedData, data, serverSide]);
|
|
509
|
+
const totalItems = serverSide ? totalCount || data.length : datasetRows.length;
|
|
517
510
|
const pageCount = Math.ceil(totalItems / pageSize);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
} else {
|
|
522
|
-
displayRows = flatRows.slice(
|
|
511
|
+
const paginatedRows = useMemo(() => {
|
|
512
|
+
if (serverSide) return datasetRows;
|
|
513
|
+
return datasetRows.slice(
|
|
523
514
|
(activePage - 1) * pageSize,
|
|
524
515
|
activePage * pageSize
|
|
525
516
|
);
|
|
526
|
-
}
|
|
517
|
+
}, [serverSide, datasetRows, activePage, pageSize]);
|
|
518
|
+
const displayRows = useMemo(() => {
|
|
519
|
+
if (!groupedData) return paginatedRows.map((row) => ({ type: "data", row }));
|
|
520
|
+
const pageRows = new Set(paginatedRows);
|
|
521
|
+
const rows = [];
|
|
522
|
+
groupedData.forEach((group) => {
|
|
523
|
+
const groupPageRows = group.rows.filter((row) => pageRows.has(row));
|
|
524
|
+
if (groupPageRows.length === 0) return;
|
|
525
|
+
rows.push({ type: "group-header", group });
|
|
526
|
+
if (expandedGroups.has(group.key)) {
|
|
527
|
+
groupPageRows.forEach((row) => rows.push({ type: "data", row }));
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
return rows;
|
|
531
|
+
}, [groupedData, paginatedRows, expandedGroups]);
|
|
527
532
|
const footerData = serverSide ? data : filteredData;
|
|
528
533
|
const activeChips = useMemo(() => {
|
|
529
534
|
const chips = [];
|
|
@@ -634,8 +639,8 @@ var DataTable = ({
|
|
|
634
639
|
return displayRows.filter((r) => r.type === "data").map((r) => r.row[rowIdField]).filter((id) => id != null);
|
|
635
640
|
}, [serverSide, data, displayRows, rowIdField]);
|
|
636
641
|
const allRowIds = useMemo(
|
|
637
|
-
() =>
|
|
638
|
-
[
|
|
642
|
+
() => datasetRows.map((row) => row[rowIdField]).filter((id) => id != null),
|
|
643
|
+
[datasetRows, rowIdField]
|
|
639
644
|
);
|
|
640
645
|
const handleSelectRow = useCallback((rowId, checked) => {
|
|
641
646
|
const next = new Set(selectedIds);
|
|
@@ -682,19 +687,25 @@ var DataTable = ({
|
|
|
682
687
|
if (row) onEditStart(row, field, currentValue);
|
|
683
688
|
}
|
|
684
689
|
}, [onEditStart, data, rowIdField]);
|
|
685
|
-
const commitEdit = useCallback((row, field, value) => {
|
|
690
|
+
const commitEdit = useCallback((row, field, value, options = {}) => {
|
|
691
|
+
const { keepEditing = false } = options;
|
|
686
692
|
const col = columns.find((c) => c.field === field);
|
|
687
693
|
if (col == null ? void 0 : col.editValidate) {
|
|
688
694
|
const result = col.editValidate(value, row);
|
|
689
695
|
if (result !== true && result !== void 0 && result !== null) {
|
|
690
696
|
setEditError(typeof result === "string" ? result : "Invalid value");
|
|
691
|
-
return;
|
|
697
|
+
return false;
|
|
692
698
|
}
|
|
693
699
|
}
|
|
694
700
|
if (onRowEdit) onRowEdit(row, field, value);
|
|
695
|
-
|
|
696
|
-
|
|
701
|
+
if (!keepEditing) {
|
|
702
|
+
setEditingCell(null);
|
|
703
|
+
setEditValue(null);
|
|
704
|
+
} else {
|
|
705
|
+
setEditValue(value);
|
|
706
|
+
}
|
|
697
707
|
setEditError(null);
|
|
708
|
+
return true;
|
|
698
709
|
}, [onRowEdit, columns]);
|
|
699
710
|
const renderEditControl = (col, row) => {
|
|
700
711
|
const type = col.editType || "text";
|
|
@@ -753,12 +764,12 @@ var DataTable = ({
|
|
|
753
764
|
case "datetime":
|
|
754
765
|
return /* @__PURE__ */ React.createElement(Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, /* @__PURE__ */ React.createElement(DateInput, { ...extra, name: `${fieldName}-date`, label: "", value: editValue == null ? void 0 : editValue.date, onChange: (val) => {
|
|
755
766
|
const next = { ...editValue, date: val };
|
|
756
|
-
|
|
757
|
-
|
|
767
|
+
handleInput(next);
|
|
768
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
758
769
|
}, onBlur: maybeExitDatetimeEdit }), /* @__PURE__ */ React.createElement(TimeInput, { ...extra.timeProps || {}, name: `${fieldName}-time`, label: "", value: editValue == null ? void 0 : editValue.time, onChange: (val) => {
|
|
759
770
|
const next = { ...editValue, time: val };
|
|
760
|
-
|
|
761
|
-
|
|
771
|
+
handleInput(next);
|
|
772
|
+
commitEdit(row, col.field, next, { keepEditing: true });
|
|
762
773
|
}, onBlur: maybeExitDatetimeEdit }));
|
|
763
774
|
case "toggle":
|
|
764
775
|
return /* @__PURE__ */ React.createElement(Toggle, { ...extra, name: fieldName, label: "", checked: !!editValue, onChange: commit });
|
|
@@ -1159,6 +1170,7 @@ var getEmptyValue = (field) => {
|
|
|
1159
1170
|
case "datetime":
|
|
1160
1171
|
return void 0;
|
|
1161
1172
|
case "display":
|
|
1173
|
+
case "slot":
|
|
1162
1174
|
case "crmPropertyList":
|
|
1163
1175
|
case "crmAssociationPropertyList":
|
|
1164
1176
|
return void 0;
|
|
@@ -1302,7 +1314,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
|
|
|
1302
1314
|
var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
|
|
1303
1315
|
const includeCustomValidators = options.includeCustomValidators !== false;
|
|
1304
1316
|
const msg = options.messages || {};
|
|
1305
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
1317
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
|
|
1306
1318
|
const isRequired = resolveRequired(field, allValues);
|
|
1307
1319
|
const plugin = fieldTypes && fieldTypes[field.type];
|
|
1308
1320
|
const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
|
|
@@ -1525,6 +1537,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1525
1537
|
// explicit row layout array (overrides columns + columnWidth)
|
|
1526
1538
|
sections,
|
|
1527
1539
|
// FormBuilderSection[] — accordion field grouping
|
|
1540
|
+
groups,
|
|
1541
|
+
// Record<string, FormBuilderGroupOptions> — per-group rendering options keyed by group name
|
|
1528
1542
|
gap = "sm",
|
|
1529
1543
|
// gap between fields
|
|
1530
1544
|
showRequiredIndicator = true,
|
|
@@ -1734,7 +1748,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1734
1748
|
const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
|
|
1735
1749
|
const vals = {};
|
|
1736
1750
|
for (const field of fields) {
|
|
1737
|
-
if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
1751
|
+
if (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
|
|
1738
1752
|
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
1739
1753
|
for (const item of field.items) {
|
|
1740
1754
|
const subFields = field.fields(item);
|
|
@@ -1768,9 +1782,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1768
1782
|
const inputDebounceRef = useRef2(/* @__PURE__ */ new Map());
|
|
1769
1783
|
const rowKeyRef = useRef2(/* @__PURE__ */ new WeakMap());
|
|
1770
1784
|
const rowKeyCounterRef = useRef2(0);
|
|
1785
|
+
const controlledBaselineLockedRef = useRef2(false);
|
|
1771
1786
|
const initialSnapshot = useRef2(null);
|
|
1772
1787
|
if (initialSnapshot.current === null) {
|
|
1773
|
-
initialSnapshot.current = deepClone(computeInitialValues());
|
|
1788
|
+
initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
|
|
1774
1789
|
}
|
|
1775
1790
|
const formValues = values != null ? values : internalValues;
|
|
1776
1791
|
const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
|
|
@@ -1782,6 +1797,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1782
1797
|
const draftValuesRef = useRef2(null);
|
|
1783
1798
|
formValuesRef.current = formValues;
|
|
1784
1799
|
formErrorsRef.current = formErrors;
|
|
1800
|
+
const syncDirtyBaseline = useCallback2((nextValues) => {
|
|
1801
|
+
initialSnapshot.current = deepClone(nextValues || {});
|
|
1802
|
+
prevAutoSaveValues.current = deepClone(nextValues || {});
|
|
1803
|
+
}, []);
|
|
1785
1804
|
const fieldByName = useMemo2(() => {
|
|
1786
1805
|
const map = /* @__PURE__ */ new Map();
|
|
1787
1806
|
for (const field of fields) {
|
|
@@ -1858,6 +1877,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1858
1877
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1859
1878
|
};
|
|
1860
1879
|
}, []);
|
|
1880
|
+
useEffect2(() => {
|
|
1881
|
+
if (values == null) return;
|
|
1882
|
+
if (controlledBaselineLockedRef.current) return;
|
|
1883
|
+
syncDirtyBaseline(values);
|
|
1884
|
+
}, [values, syncDirtyBaseline]);
|
|
1861
1885
|
const isDirty = useMemo2(() => {
|
|
1862
1886
|
return !deepEqual(formValues, initialSnapshot.current);
|
|
1863
1887
|
}, [formValues]);
|
|
@@ -1976,6 +2000,50 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1976
2000
|
},
|
|
1977
2001
|
[fieldTypes]
|
|
1978
2002
|
);
|
|
2003
|
+
const setRepeaterSubFieldError = useCallback2(
|
|
2004
|
+
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
2005
|
+
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
2006
|
+
const merged = { ...formErrorsRef.current };
|
|
2007
|
+
if (errorMessage) {
|
|
2008
|
+
merged[key] = errorMessage;
|
|
2009
|
+
} else {
|
|
2010
|
+
delete merged[key];
|
|
2011
|
+
}
|
|
2012
|
+
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
2013
|
+
const match = k.match(/\[(\d+)\]\./);
|
|
2014
|
+
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
2015
|
+
return { key: k, row };
|
|
2016
|
+
}).sort((a, b) => a.row - b.row);
|
|
2017
|
+
if (subErrors.length > 0) {
|
|
2018
|
+
const first = subErrors[0];
|
|
2019
|
+
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
2020
|
+
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
2021
|
+
delete merged[fieldName];
|
|
2022
|
+
}
|
|
2023
|
+
replaceErrors(merged);
|
|
2024
|
+
},
|
|
2025
|
+
[replaceErrors]
|
|
2026
|
+
);
|
|
2027
|
+
const expandValidationFields = useCallback2(
|
|
2028
|
+
(fieldSubset) => {
|
|
2029
|
+
const toValidate = fieldSubset || visibleFields;
|
|
2030
|
+
const expanded = [];
|
|
2031
|
+
for (const field of toValidate) {
|
|
2032
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
2033
|
+
for (const item of field.items) {
|
|
2034
|
+
for (const subField of field.fields(item)) {
|
|
2035
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
2036
|
+
expanded.push(subField);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
expanded.push(field);
|
|
2042
|
+
}
|
|
2043
|
+
return expanded;
|
|
2044
|
+
},
|
|
2045
|
+
[visibleFields, formValues]
|
|
2046
|
+
);
|
|
1979
2047
|
const validateField = useCallback2(
|
|
1980
2048
|
(name, value) => {
|
|
1981
2049
|
const field = fieldByName.get(name);
|
|
@@ -1995,7 +2063,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1995
2063
|
);
|
|
1996
2064
|
const validateVisibleFields = useCallback2(
|
|
1997
2065
|
(fieldSubset) => {
|
|
1998
|
-
const toValidate = fieldSubset
|
|
2066
|
+
const toValidate = expandValidationFields(fieldSubset);
|
|
1999
2067
|
const errors = {};
|
|
2000
2068
|
let hasErrors = false;
|
|
2001
2069
|
for (const field of toValidate) {
|
|
@@ -2015,52 +2083,54 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2015
2083
|
}
|
|
2016
2084
|
return { errors, hasErrors };
|
|
2017
2085
|
},
|
|
2018
|
-
[
|
|
2086
|
+
[expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
|
|
2019
2087
|
);
|
|
2020
|
-
const
|
|
2021
|
-
(
|
|
2022
|
-
const field =
|
|
2023
|
-
if (!field || field.type === "repeater") return null;
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
2088
|
+
const runAsyncValidationTarget = useCallback2(
|
|
2089
|
+
(target) => {
|
|
2090
|
+
const { validationKey, field, value, allValues, applyError } = target || {};
|
|
2091
|
+
if (!field || !validationKey || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
2092
|
+
const syncError = runValidators(value, field, allValues, fieldTypes, {
|
|
2093
|
+
includeCustomValidators: false,
|
|
2094
|
+
messages: validationMessages
|
|
2095
|
+
});
|
|
2096
|
+
const prevController = asyncAbortRef.current.get(validationKey);
|
|
2027
2097
|
if (prevController) prevController.abort();
|
|
2028
|
-
asyncAbortRef.current.delete(
|
|
2098
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2029
2099
|
setValidatingFields((prev) => {
|
|
2030
|
-
if (!prev[
|
|
2100
|
+
if (!prev[validationKey]) return prev;
|
|
2031
2101
|
const next = { ...prev };
|
|
2032
|
-
delete next[
|
|
2102
|
+
delete next[validationKey];
|
|
2033
2103
|
return next;
|
|
2034
2104
|
});
|
|
2035
2105
|
if (syncError) return null;
|
|
2036
|
-
const version = (asyncValidationVersionRef.current.get(
|
|
2037
|
-
asyncValidationVersionRef.current.set(
|
|
2106
|
+
const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
|
|
2107
|
+
asyncValidationVersionRef.current.set(validationKey, version);
|
|
2038
2108
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
2039
|
-
if (controller) asyncAbortRef.current.set(
|
|
2109
|
+
if (controller) asyncAbortRef.current.set(validationKey, controller);
|
|
2040
2110
|
let asyncPromises;
|
|
2041
2111
|
try {
|
|
2042
2112
|
asyncPromises = collectAsyncValidatorPromises(
|
|
2043
|
-
|
|
2113
|
+
value,
|
|
2044
2114
|
field,
|
|
2045
|
-
|
|
2115
|
+
allValues,
|
|
2046
2116
|
controller ? { signal: controller.signal } : void 0
|
|
2047
2117
|
);
|
|
2048
2118
|
} catch (err) {
|
|
2049
|
-
|
|
2119
|
+
applyError((err == null ? void 0 : err.message) || "Validation failed");
|
|
2050
2120
|
return null;
|
|
2051
2121
|
}
|
|
2052
2122
|
if (asyncPromises.length === 0) {
|
|
2053
|
-
asyncAbortRef.current.delete(
|
|
2123
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2054
2124
|
return null;
|
|
2055
2125
|
}
|
|
2056
2126
|
const validationPromise = Promise.all(asyncPromises).then(
|
|
2057
2127
|
(results) => {
|
|
2058
|
-
if (asyncValidationVersionRef.current.get(
|
|
2059
|
-
asyncValidationRef.current.delete(
|
|
2060
|
-
asyncAbortRef.current.delete(
|
|
2128
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2129
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2130
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2061
2131
|
setValidatingFields((prev) => {
|
|
2062
2132
|
const next = { ...prev };
|
|
2063
|
-
delete next[
|
|
2133
|
+
delete next[validationKey];
|
|
2064
2134
|
return next;
|
|
2065
2135
|
});
|
|
2066
2136
|
let err = null;
|
|
@@ -2071,50 +2141,128 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2071
2141
|
break;
|
|
2072
2142
|
}
|
|
2073
2143
|
}
|
|
2074
|
-
|
|
2144
|
+
applyError(err);
|
|
2075
2145
|
},
|
|
2076
2146
|
(rejection) => {
|
|
2077
|
-
if (asyncValidationVersionRef.current.get(
|
|
2078
|
-
asyncValidationRef.current.delete(
|
|
2079
|
-
asyncAbortRef.current.delete(
|
|
2147
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2148
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2149
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2080
2150
|
setValidatingFields((prev) => {
|
|
2081
2151
|
const next = { ...prev };
|
|
2082
|
-
delete next[
|
|
2152
|
+
delete next[validationKey];
|
|
2083
2153
|
return next;
|
|
2084
2154
|
});
|
|
2085
2155
|
if (rejection && rejection.name === "AbortError") return;
|
|
2086
|
-
|
|
2156
|
+
applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
|
|
2087
2157
|
}
|
|
2088
2158
|
);
|
|
2089
|
-
asyncValidationRef.current.set(
|
|
2090
|
-
setValidatingFields((prev) => ({ ...prev, [
|
|
2159
|
+
asyncValidationRef.current.set(validationKey, validationPromise);
|
|
2160
|
+
setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
|
|
2091
2161
|
return validationPromise;
|
|
2092
2162
|
},
|
|
2093
|
-
[
|
|
2163
|
+
[fieldTypes, validationMessages]
|
|
2094
2164
|
);
|
|
2095
|
-
const
|
|
2165
|
+
const runAsyncValidation = useCallback2(
|
|
2096
2166
|
(name, value) => {
|
|
2097
2167
|
const field = fieldByName.get(name);
|
|
2098
|
-
if (!field || field.type === "repeater") return;
|
|
2099
|
-
|
|
2168
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return null;
|
|
2169
|
+
return runAsyncValidationTarget({
|
|
2170
|
+
validationKey: name,
|
|
2171
|
+
field,
|
|
2172
|
+
value: value != null ? value : formValues[name],
|
|
2173
|
+
allValues: formValues,
|
|
2174
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
2175
|
+
});
|
|
2176
|
+
},
|
|
2177
|
+
[fieldByName, formValues, runAsyncValidationTarget, updateErrors]
|
|
2178
|
+
);
|
|
2179
|
+
const triggerAsyncValidationTarget = useCallback2(
|
|
2180
|
+
(target) => {
|
|
2181
|
+
if (!(target == null ? void 0 : target.field) || !target.validationKey) return;
|
|
2182
|
+
const debounceMs = target.field.validateDebounce;
|
|
2100
2183
|
if (debounceMs && debounceMs > 0) {
|
|
2101
|
-
const existing = debounceTimersRef.current.get(
|
|
2184
|
+
const existing = debounceTimersRef.current.get(target.validationKey);
|
|
2102
2185
|
if (existing) clearTimeout(existing);
|
|
2103
2186
|
const timer = setTimeout(() => {
|
|
2104
|
-
debounceTimersRef.current.delete(
|
|
2105
|
-
|
|
2187
|
+
debounceTimersRef.current.delete(target.validationKey);
|
|
2188
|
+
runAsyncValidationTarget(target);
|
|
2106
2189
|
}, debounceMs);
|
|
2107
|
-
debounceTimersRef.current.set(
|
|
2190
|
+
debounceTimersRef.current.set(target.validationKey, timer);
|
|
2108
2191
|
} else {
|
|
2109
|
-
|
|
2192
|
+
runAsyncValidationTarget(target);
|
|
2110
2193
|
}
|
|
2111
2194
|
},
|
|
2112
|
-
[
|
|
2195
|
+
[runAsyncValidationTarget]
|
|
2196
|
+
);
|
|
2197
|
+
const triggerAsyncValidation = useCallback2(
|
|
2198
|
+
(name, value) => {
|
|
2199
|
+
const field = fieldByName.get(name);
|
|
2200
|
+
if (!field || field.type === "repeater" || field.type === "fieldGroup") return;
|
|
2201
|
+
triggerAsyncValidationTarget({
|
|
2202
|
+
validationKey: name,
|
|
2203
|
+
field,
|
|
2204
|
+
value: value != null ? value : formValuesRef.current[name],
|
|
2205
|
+
allValues: formValuesRef.current,
|
|
2206
|
+
applyError: (errorMessage) => updateErrors({ [name]: errorMessage })
|
|
2207
|
+
});
|
|
2208
|
+
},
|
|
2209
|
+
[fieldByName, triggerAsyncValidationTarget, updateErrors]
|
|
2210
|
+
);
|
|
2211
|
+
const getAsyncValidationTargets = useCallback2(
|
|
2212
|
+
(fieldSubset) => {
|
|
2213
|
+
const toValidate = fieldSubset || visibleFields;
|
|
2214
|
+
const targets = [];
|
|
2215
|
+
for (const field of toValidate) {
|
|
2216
|
+
if (field.type === "fieldGroup" && field.items && field.fields) {
|
|
2217
|
+
for (const item of field.items) {
|
|
2218
|
+
for (const subField of field.fields(item)) {
|
|
2219
|
+
if (subField.visible && !subField.visible(formValues)) continue;
|
|
2220
|
+
targets.push({
|
|
2221
|
+
validationKey: subField.name,
|
|
2222
|
+
field: subField,
|
|
2223
|
+
value: formValues[subField.name],
|
|
2224
|
+
allValues: formValues,
|
|
2225
|
+
applyError: (errorMessage) => updateErrors({ [subField.name]: errorMessage })
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
if (field.type === "repeater") {
|
|
2232
|
+
const rows = Array.isArray(formValues[field.name]) ? formValues[field.name] : [];
|
|
2233
|
+
const subFields = field.fields || [];
|
|
2234
|
+
rows.forEach((row, rowIdx) => {
|
|
2235
|
+
const rowValues = { ...formValues, [field.name]: rows };
|
|
2236
|
+
subFields.forEach((subField) => {
|
|
2237
|
+
if (subField.visible && !subField.visible(rowValues)) return;
|
|
2238
|
+
targets.push({
|
|
2239
|
+
validationKey: getRepeaterErrorKey(field.name, rowIdx, subField.name),
|
|
2240
|
+
field: subField,
|
|
2241
|
+
value: row == null ? void 0 : row[subField.name],
|
|
2242
|
+
allValues: rowValues,
|
|
2243
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
2244
|
+
});
|
|
2245
|
+
});
|
|
2246
|
+
});
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
targets.push({
|
|
2250
|
+
validationKey: field.name,
|
|
2251
|
+
field,
|
|
2252
|
+
value: formValues[field.name],
|
|
2253
|
+
allValues: formValues,
|
|
2254
|
+
applyError: (errorMessage) => updateErrors({ [field.name]: errorMessage })
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
return targets;
|
|
2258
|
+
},
|
|
2259
|
+
[visibleFields, formValues, setRepeaterSubFieldError, updateErrors]
|
|
2113
2260
|
);
|
|
2114
2261
|
const commitValues = useCallback2(
|
|
2115
2262
|
(nextValues) => {
|
|
2116
2263
|
formValuesRef.current = nextValues;
|
|
2117
2264
|
if (values != null) {
|
|
2265
|
+
controlledBaselineLockedRef.current = true;
|
|
2118
2266
|
if (onChange) onChange(nextValues);
|
|
2119
2267
|
} else {
|
|
2120
2268
|
setInternalValues(nextValues);
|
|
@@ -2132,7 +2280,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2132
2280
|
[commitValues]
|
|
2133
2281
|
);
|
|
2134
2282
|
const handleFieldChange = useCallback2(
|
|
2135
|
-
(name, value) => {
|
|
2283
|
+
(name, value, options = {}) => {
|
|
2284
|
+
const { clearNestedErrors = true } = options;
|
|
2136
2285
|
const newValues = { ...formValuesRef.current, [name]: value };
|
|
2137
2286
|
const queue = [name];
|
|
2138
2287
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2173,9 +2322,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2173
2322
|
if (formErrorsRef.current[name] != null) {
|
|
2174
2323
|
clearedErrors[name] = null;
|
|
2175
2324
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2325
|
+
if (clearNestedErrors) {
|
|
2326
|
+
for (const key of Object.keys(formErrorsRef.current)) {
|
|
2327
|
+
if (key.startsWith(`${name}[`)) {
|
|
2328
|
+
clearedErrors[key] = null;
|
|
2329
|
+
}
|
|
2179
2330
|
}
|
|
2180
2331
|
}
|
|
2181
2332
|
draftValuesRef.current = newValues;
|
|
@@ -2244,7 +2395,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2244
2395
|
replaceErrors(errors);
|
|
2245
2396
|
return;
|
|
2246
2397
|
}
|
|
2247
|
-
const asyncSubmitValidations = allVisibleFields.map((
|
|
2398
|
+
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2248
2399
|
if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2249
2400
|
const pendingValidations = [
|
|
2250
2401
|
.../* @__PURE__ */ new Set([
|
|
@@ -2259,6 +2410,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2259
2410
|
const reset = () => {
|
|
2260
2411
|
const fresh = computeInitialValues();
|
|
2261
2412
|
if (values == null) setInternalValues(fresh);
|
|
2413
|
+
controlledBaselineLockedRef.current = false;
|
|
2262
2414
|
replaceErrors({});
|
|
2263
2415
|
initialSnapshot.current = deepClone(fresh);
|
|
2264
2416
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2266,7 +2418,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2266
2418
|
const rawValues = {};
|
|
2267
2419
|
for (const key of Object.keys(formValues)) {
|
|
2268
2420
|
const f = fieldByName.get(key);
|
|
2269
|
-
if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
2421
|
+
if (f && (f.type === "display" || f.type === "slot" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
|
|
2270
2422
|
rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
|
|
2271
2423
|
}
|
|
2272
2424
|
for (const f of fields) {
|
|
@@ -2298,7 +2450,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2298
2450
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2299
2451
|
}
|
|
2300
2452
|
},
|
|
2301
|
-
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName,
|
|
2453
|
+
[validateOnSubmit, allVisibleFields, validateVisibleFields, replaceErrors, onSubmit, values, controlledLoading, transformValues, onBeforeSubmit, onSubmitSuccess, onSubmitError, resetOnSuccess, formValues, fieldByName, getAsyncValidationTargets, runAsyncValidationTarget]
|
|
2302
2454
|
);
|
|
2303
2455
|
const handleNext = useCallback2(async () => {
|
|
2304
2456
|
if (!isMultiStep) return;
|
|
@@ -2310,7 +2462,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2310
2462
|
replaceErrors({ ...formErrorsRef.current, ...errors });
|
|
2311
2463
|
return;
|
|
2312
2464
|
}
|
|
2313
|
-
const asyncStepValidations = stepFields.map((
|
|
2465
|
+
const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2314
2466
|
if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2315
2467
|
const pendingValidations = [
|
|
2316
2468
|
.../* @__PURE__ */ new Set([
|
|
@@ -2335,7 +2487,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2335
2487
|
} else {
|
|
2336
2488
|
setInternalStep(nextStep);
|
|
2337
2489
|
}
|
|
2338
|
-
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields,
|
|
2490
|
+
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
|
|
2339
2491
|
const handleBack = useCallback2(() => {
|
|
2340
2492
|
if (!isMultiStep) return;
|
|
2341
2493
|
const prevStep = Math.max(currentStep - 1, 0);
|
|
@@ -2367,6 +2519,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2367
2519
|
reset: () => {
|
|
2368
2520
|
const fresh = computeInitialValues();
|
|
2369
2521
|
if (values == null) setInternalValues(fresh);
|
|
2522
|
+
controlledBaselineLockedRef.current = false;
|
|
2370
2523
|
replaceErrors({});
|
|
2371
2524
|
initialSnapshot.current = deepClone(fresh);
|
|
2372
2525
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2379,30 +2532,6 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2379
2532
|
replaceErrors(errors);
|
|
2380
2533
|
}
|
|
2381
2534
|
}));
|
|
2382
|
-
const setRepeaterSubFieldError = useCallback2(
|
|
2383
|
-
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
2384
|
-
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
2385
|
-
const merged = { ...formErrorsRef.current };
|
|
2386
|
-
if (errorMessage) {
|
|
2387
|
-
merged[key] = errorMessage;
|
|
2388
|
-
} else {
|
|
2389
|
-
delete merged[key];
|
|
2390
|
-
}
|
|
2391
|
-
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
2392
|
-
const match = k.match(/\[(\d+)\]\./);
|
|
2393
|
-
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
2394
|
-
return { key: k, row };
|
|
2395
|
-
}).sort((a, b) => a.row - b.row);
|
|
2396
|
-
if (subErrors.length > 0) {
|
|
2397
|
-
const first = subErrors[0];
|
|
2398
|
-
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
2399
|
-
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
2400
|
-
delete merged[fieldName];
|
|
2401
|
-
}
|
|
2402
|
-
replaceErrors(merged);
|
|
2403
|
-
},
|
|
2404
|
-
[replaceErrors]
|
|
2405
|
-
);
|
|
2406
2535
|
const renderField = (field) => {
|
|
2407
2536
|
const fieldError = formErrors[field.name] || null;
|
|
2408
2537
|
const rendered = renderFieldInner(field);
|
|
@@ -2417,7 +2546,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2417
2546
|
const isReadOnly = field.readOnly || formReadOnly;
|
|
2418
2547
|
const isDisabled = disabled || resolveDisabled(field, formValues) || formReadOnly;
|
|
2419
2548
|
const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
|
|
2420
|
-
if (field.type === "display") {
|
|
2549
|
+
if (field.type === "display" || field.type === "slot") {
|
|
2421
2550
|
if (field.render) {
|
|
2422
2551
|
return field.render({
|
|
2423
2552
|
values: formValues,
|
|
@@ -2882,12 +3011,13 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2882
3011
|
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
2883
3012
|
const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
|
|
2884
3013
|
setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
|
|
3014
|
+
return err;
|
|
2885
3015
|
};
|
|
2886
3016
|
const handleSubFieldChange = (rowIdx, subField, subValue) => {
|
|
2887
3017
|
const updated = rows.map(
|
|
2888
3018
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2889
3019
|
);
|
|
2890
|
-
handleFieldChange(field.name, updated);
|
|
3020
|
+
handleFieldChange(field.name, updated, { clearNestedErrors: false });
|
|
2891
3021
|
if (validateOnChange) {
|
|
2892
3022
|
validateSubField(rowIdx, subField, subValue, updated);
|
|
2893
3023
|
}
|
|
@@ -2897,13 +3027,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2897
3027
|
const nextRows = rows.map(
|
|
2898
3028
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2899
3029
|
);
|
|
2900
|
-
validateSubField(rowIdx, subField, subValue, nextRows);
|
|
3030
|
+
const err = validateSubField(rowIdx, subField, subValue, nextRows);
|
|
3031
|
+
if (err) return;
|
|
3032
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, subField.name);
|
|
3033
|
+
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
3034
|
+
triggerAsyncValidationTarget({
|
|
3035
|
+
validationKey,
|
|
3036
|
+
field: subField,
|
|
3037
|
+
value: subValue,
|
|
3038
|
+
allValues: rowValues,
|
|
3039
|
+
applyError: (errorMessage) => setRepeaterSubFieldError(field.name, rowIdx, subField.name, errorMessage)
|
|
3040
|
+
});
|
|
2901
3041
|
};
|
|
2902
3042
|
return /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, field.label, isRequired ? " *" : ""), field.description && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, field.description), rows.map((row, rowIdx) => /* @__PURE__ */ React2.createElement(Flex2, { key: getRowKey(field.name, row, rowIdx), direction: "row", gap: "xs", align: "end" }, subFields.map((sf) => {
|
|
2903
3043
|
const sfValue = row[sf.name];
|
|
2904
3044
|
const sfLabel = rowIdx === 0 ? sf.label : void 0;
|
|
2905
3045
|
const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
|
|
2906
3046
|
const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
|
|
3047
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
|
|
2907
3048
|
const sfProps = {
|
|
2908
3049
|
name: `${field.name}-${rowIdx}-${sf.name}`,
|
|
2909
3050
|
label: sfLabel,
|
|
@@ -2912,6 +3053,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2912
3053
|
disabled: resolveDisabled(sf, formValues) || isDisabled,
|
|
2913
3054
|
error: !!sfError,
|
|
2914
3055
|
validationMessage: sfError || void 0,
|
|
3056
|
+
...validatingFields[validationKey] ? { loading: true } : {},
|
|
2915
3057
|
...sf.fieldProps || {}
|
|
2916
3058
|
};
|
|
2917
3059
|
let sfElement;
|
|
@@ -2989,7 +3131,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2989
3131
|
const getFieldColSpan = (field) => {
|
|
2990
3132
|
if (field.colSpan != null) return Math.min(field.colSpan, columns);
|
|
2991
3133
|
if (field.width === "full" && columns > 1) return columns;
|
|
2992
|
-
if (columns > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
3134
|
+
if (columns > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return columns;
|
|
2993
3135
|
return 1;
|
|
2994
3136
|
};
|
|
2995
3137
|
const getDependents = (parentField) => visibleFields.filter(
|
|
@@ -3014,7 +3156,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3014
3156
|
const colSpan = (field) => {
|
|
3015
3157
|
if (field.colSpan != null) return Math.min(field.colSpan, cols);
|
|
3016
3158
|
if (field.width === "full" && cols > 1) return cols;
|
|
3017
|
-
if (cols > 1 && (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
3159
|
+
if (cols > 1 && (field.type === "display" || field.type === "slot" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList")) return cols;
|
|
3018
3160
|
return 1;
|
|
3019
3161
|
};
|
|
3020
3162
|
const flushRow = () => {
|
|
@@ -3178,13 +3320,23 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
3178
3320
|
const elements = [];
|
|
3179
3321
|
for (let i = 0; i < chunks.length; i++) {
|
|
3180
3322
|
const chunk = chunks[i];
|
|
3181
|
-
|
|
3323
|
+
const opts = chunk.group && groups && groups[chunk.group] || {};
|
|
3324
|
+
const showDivider = opts.showDivider !== false;
|
|
3325
|
+
const showLabel = opts.showLabel !== false;
|
|
3326
|
+
if (i > 0 && showDivider) {
|
|
3182
3327
|
elements.push(/* @__PURE__ */ React2.createElement(Divider, { key: `group-div-${i}` }));
|
|
3183
3328
|
}
|
|
3184
|
-
if (chunk.group) {
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3329
|
+
if (chunk.group && showLabel) {
|
|
3330
|
+
if (typeof opts.renderHeader === "function") {
|
|
3331
|
+
const header = opts.renderHeader(chunk.group, chunk.fields, formValues);
|
|
3332
|
+
if (header) elements.push(
|
|
3333
|
+
/* @__PURE__ */ React2.createElement(React2.Fragment, { key: `group-header-${i}` }, header)
|
|
3334
|
+
);
|
|
3335
|
+
} else {
|
|
3336
|
+
elements.push(
|
|
3337
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `group-label-${i}`, format: { fontWeight: "demibold" } }, opts.label || chunk.group)
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3188
3340
|
}
|
|
3189
3341
|
const chunkElements = renderFn(chunk.fields);
|
|
3190
3342
|
if (Array.isArray(chunkElements)) {
|