hs-uix 1.4.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 +204 -76
- package/dist/form.mjs +204 -76
- package/dist/index.js +243 -104
- package/dist/index.mjs +243 -104
- 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 });
|
|
@@ -1771,9 +1782,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1771
1782
|
const inputDebounceRef = useRef2(/* @__PURE__ */ new Map());
|
|
1772
1783
|
const rowKeyRef = useRef2(/* @__PURE__ */ new WeakMap());
|
|
1773
1784
|
const rowKeyCounterRef = useRef2(0);
|
|
1785
|
+
const controlledBaselineLockedRef = useRef2(false);
|
|
1774
1786
|
const initialSnapshot = useRef2(null);
|
|
1775
1787
|
if (initialSnapshot.current === null) {
|
|
1776
|
-
initialSnapshot.current = deepClone(computeInitialValues());
|
|
1788
|
+
initialSnapshot.current = deepClone(values != null ? values : computeInitialValues());
|
|
1777
1789
|
}
|
|
1778
1790
|
const formValues = values != null ? values : internalValues;
|
|
1779
1791
|
const formErrors = controlledErrors != null ? controlledErrors : internalErrors;
|
|
@@ -1785,6 +1797,10 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1785
1797
|
const draftValuesRef = useRef2(null);
|
|
1786
1798
|
formValuesRef.current = formValues;
|
|
1787
1799
|
formErrorsRef.current = formErrors;
|
|
1800
|
+
const syncDirtyBaseline = useCallback2((nextValues) => {
|
|
1801
|
+
initialSnapshot.current = deepClone(nextValues || {});
|
|
1802
|
+
prevAutoSaveValues.current = deepClone(nextValues || {});
|
|
1803
|
+
}, []);
|
|
1788
1804
|
const fieldByName = useMemo2(() => {
|
|
1789
1805
|
const map = /* @__PURE__ */ new Map();
|
|
1790
1806
|
for (const field of fields) {
|
|
@@ -1861,6 +1877,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1861
1877
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1862
1878
|
};
|
|
1863
1879
|
}, []);
|
|
1880
|
+
useEffect2(() => {
|
|
1881
|
+
if (values == null) return;
|
|
1882
|
+
if (controlledBaselineLockedRef.current) return;
|
|
1883
|
+
syncDirtyBaseline(values);
|
|
1884
|
+
}, [values, syncDirtyBaseline]);
|
|
1864
1885
|
const isDirty = useMemo2(() => {
|
|
1865
1886
|
return !deepEqual(formValues, initialSnapshot.current);
|
|
1866
1887
|
}, [formValues]);
|
|
@@ -1979,6 +2000,50 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1979
2000
|
},
|
|
1980
2001
|
[fieldTypes]
|
|
1981
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
|
+
);
|
|
1982
2047
|
const validateField = useCallback2(
|
|
1983
2048
|
(name, value) => {
|
|
1984
2049
|
const field = fieldByName.get(name);
|
|
@@ -1998,7 +2063,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
1998
2063
|
);
|
|
1999
2064
|
const validateVisibleFields = useCallback2(
|
|
2000
2065
|
(fieldSubset) => {
|
|
2001
|
-
const toValidate = fieldSubset
|
|
2066
|
+
const toValidate = expandValidationFields(fieldSubset);
|
|
2002
2067
|
const errors = {};
|
|
2003
2068
|
let hasErrors = false;
|
|
2004
2069
|
for (const field of toValidate) {
|
|
@@ -2018,52 +2083,54 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2018
2083
|
}
|
|
2019
2084
|
return { errors, hasErrors };
|
|
2020
2085
|
},
|
|
2021
|
-
[
|
|
2086
|
+
[expandValidationFields, formValues, validateRepeaterField, fieldTypes, validationMessages]
|
|
2022
2087
|
);
|
|
2023
|
-
const
|
|
2024
|
-
(
|
|
2025
|
-
const field =
|
|
2026
|
-
if (!field || field.type === "repeater") return null;
|
|
2027
|
-
const
|
|
2028
|
-
|
|
2029
|
-
|
|
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);
|
|
2030
2097
|
if (prevController) prevController.abort();
|
|
2031
|
-
asyncAbortRef.current.delete(
|
|
2098
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2032
2099
|
setValidatingFields((prev) => {
|
|
2033
|
-
if (!prev[
|
|
2100
|
+
if (!prev[validationKey]) return prev;
|
|
2034
2101
|
const next = { ...prev };
|
|
2035
|
-
delete next[
|
|
2102
|
+
delete next[validationKey];
|
|
2036
2103
|
return next;
|
|
2037
2104
|
});
|
|
2038
2105
|
if (syncError) return null;
|
|
2039
|
-
const version = (asyncValidationVersionRef.current.get(
|
|
2040
|
-
asyncValidationVersionRef.current.set(
|
|
2106
|
+
const version = (asyncValidationVersionRef.current.get(validationKey) || 0) + 1;
|
|
2107
|
+
asyncValidationVersionRef.current.set(validationKey, version);
|
|
2041
2108
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
2042
|
-
if (controller) asyncAbortRef.current.set(
|
|
2109
|
+
if (controller) asyncAbortRef.current.set(validationKey, controller);
|
|
2043
2110
|
let asyncPromises;
|
|
2044
2111
|
try {
|
|
2045
2112
|
asyncPromises = collectAsyncValidatorPromises(
|
|
2046
|
-
|
|
2113
|
+
value,
|
|
2047
2114
|
field,
|
|
2048
|
-
|
|
2115
|
+
allValues,
|
|
2049
2116
|
controller ? { signal: controller.signal } : void 0
|
|
2050
2117
|
);
|
|
2051
2118
|
} catch (err) {
|
|
2052
|
-
|
|
2119
|
+
applyError((err == null ? void 0 : err.message) || "Validation failed");
|
|
2053
2120
|
return null;
|
|
2054
2121
|
}
|
|
2055
2122
|
if (asyncPromises.length === 0) {
|
|
2056
|
-
asyncAbortRef.current.delete(
|
|
2123
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2057
2124
|
return null;
|
|
2058
2125
|
}
|
|
2059
2126
|
const validationPromise = Promise.all(asyncPromises).then(
|
|
2060
2127
|
(results) => {
|
|
2061
|
-
if (asyncValidationVersionRef.current.get(
|
|
2062
|
-
asyncValidationRef.current.delete(
|
|
2063
|
-
asyncAbortRef.current.delete(
|
|
2128
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2129
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2130
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2064
2131
|
setValidatingFields((prev) => {
|
|
2065
2132
|
const next = { ...prev };
|
|
2066
|
-
delete next[
|
|
2133
|
+
delete next[validationKey];
|
|
2067
2134
|
return next;
|
|
2068
2135
|
});
|
|
2069
2136
|
let err = null;
|
|
@@ -2074,50 +2141,128 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2074
2141
|
break;
|
|
2075
2142
|
}
|
|
2076
2143
|
}
|
|
2077
|
-
|
|
2144
|
+
applyError(err);
|
|
2078
2145
|
},
|
|
2079
2146
|
(rejection) => {
|
|
2080
|
-
if (asyncValidationVersionRef.current.get(
|
|
2081
|
-
asyncValidationRef.current.delete(
|
|
2082
|
-
asyncAbortRef.current.delete(
|
|
2147
|
+
if (asyncValidationVersionRef.current.get(validationKey) !== version) return;
|
|
2148
|
+
asyncValidationRef.current.delete(validationKey);
|
|
2149
|
+
asyncAbortRef.current.delete(validationKey);
|
|
2083
2150
|
setValidatingFields((prev) => {
|
|
2084
2151
|
const next = { ...prev };
|
|
2085
|
-
delete next[
|
|
2152
|
+
delete next[validationKey];
|
|
2086
2153
|
return next;
|
|
2087
2154
|
});
|
|
2088
2155
|
if (rejection && rejection.name === "AbortError") return;
|
|
2089
|
-
|
|
2156
|
+
applyError((rejection == null ? void 0 : rejection.message) || "Validation failed");
|
|
2090
2157
|
}
|
|
2091
2158
|
);
|
|
2092
|
-
asyncValidationRef.current.set(
|
|
2093
|
-
setValidatingFields((prev) => ({ ...prev, [
|
|
2159
|
+
asyncValidationRef.current.set(validationKey, validationPromise);
|
|
2160
|
+
setValidatingFields((prev) => ({ ...prev, [validationKey]: true }));
|
|
2094
2161
|
return validationPromise;
|
|
2095
2162
|
},
|
|
2096
|
-
[
|
|
2163
|
+
[fieldTypes, validationMessages]
|
|
2097
2164
|
);
|
|
2098
|
-
const
|
|
2165
|
+
const runAsyncValidation = useCallback2(
|
|
2099
2166
|
(name, value) => {
|
|
2100
2167
|
const field = fieldByName.get(name);
|
|
2101
|
-
if (!field || field.type === "repeater") return;
|
|
2102
|
-
|
|
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;
|
|
2103
2183
|
if (debounceMs && debounceMs > 0) {
|
|
2104
|
-
const existing = debounceTimersRef.current.get(
|
|
2184
|
+
const existing = debounceTimersRef.current.get(target.validationKey);
|
|
2105
2185
|
if (existing) clearTimeout(existing);
|
|
2106
2186
|
const timer = setTimeout(() => {
|
|
2107
|
-
debounceTimersRef.current.delete(
|
|
2108
|
-
|
|
2187
|
+
debounceTimersRef.current.delete(target.validationKey);
|
|
2188
|
+
runAsyncValidationTarget(target);
|
|
2109
2189
|
}, debounceMs);
|
|
2110
|
-
debounceTimersRef.current.set(
|
|
2190
|
+
debounceTimersRef.current.set(target.validationKey, timer);
|
|
2111
2191
|
} else {
|
|
2112
|
-
|
|
2192
|
+
runAsyncValidationTarget(target);
|
|
2113
2193
|
}
|
|
2114
2194
|
},
|
|
2115
|
-
[
|
|
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]
|
|
2116
2260
|
);
|
|
2117
2261
|
const commitValues = useCallback2(
|
|
2118
2262
|
(nextValues) => {
|
|
2119
2263
|
formValuesRef.current = nextValues;
|
|
2120
2264
|
if (values != null) {
|
|
2265
|
+
controlledBaselineLockedRef.current = true;
|
|
2121
2266
|
if (onChange) onChange(nextValues);
|
|
2122
2267
|
} else {
|
|
2123
2268
|
setInternalValues(nextValues);
|
|
@@ -2135,7 +2280,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2135
2280
|
[commitValues]
|
|
2136
2281
|
);
|
|
2137
2282
|
const handleFieldChange = useCallback2(
|
|
2138
|
-
(name, value) => {
|
|
2283
|
+
(name, value, options = {}) => {
|
|
2284
|
+
const { clearNestedErrors = true } = options;
|
|
2139
2285
|
const newValues = { ...formValuesRef.current, [name]: value };
|
|
2140
2286
|
const queue = [name];
|
|
2141
2287
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2176,9 +2322,11 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2176
2322
|
if (formErrorsRef.current[name] != null) {
|
|
2177
2323
|
clearedErrors[name] = null;
|
|
2178
2324
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2325
|
+
if (clearNestedErrors) {
|
|
2326
|
+
for (const key of Object.keys(formErrorsRef.current)) {
|
|
2327
|
+
if (key.startsWith(`${name}[`)) {
|
|
2328
|
+
clearedErrors[key] = null;
|
|
2329
|
+
}
|
|
2182
2330
|
}
|
|
2183
2331
|
}
|
|
2184
2332
|
draftValuesRef.current = newValues;
|
|
@@ -2247,7 +2395,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2247
2395
|
replaceErrors(errors);
|
|
2248
2396
|
return;
|
|
2249
2397
|
}
|
|
2250
|
-
const asyncSubmitValidations = allVisibleFields.map((
|
|
2398
|
+
const asyncSubmitValidations = getAsyncValidationTargets(allVisibleFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2251
2399
|
if (asyncSubmitValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2252
2400
|
const pendingValidations = [
|
|
2253
2401
|
.../* @__PURE__ */ new Set([
|
|
@@ -2262,6 +2410,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2262
2410
|
const reset = () => {
|
|
2263
2411
|
const fresh = computeInitialValues();
|
|
2264
2412
|
if (values == null) setInternalValues(fresh);
|
|
2413
|
+
controlledBaselineLockedRef.current = false;
|
|
2265
2414
|
replaceErrors({});
|
|
2266
2415
|
initialSnapshot.current = deepClone(fresh);
|
|
2267
2416
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2301,7 +2450,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2301
2450
|
if (controlledLoading == null) setInternalLoading(false);
|
|
2302
2451
|
}
|
|
2303
2452
|
},
|
|
2304
|
-
[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]
|
|
2305
2454
|
);
|
|
2306
2455
|
const handleNext = useCallback2(async () => {
|
|
2307
2456
|
if (!isMultiStep) return;
|
|
@@ -2313,7 +2462,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2313
2462
|
replaceErrors({ ...formErrorsRef.current, ...errors });
|
|
2314
2463
|
return;
|
|
2315
2464
|
}
|
|
2316
|
-
const asyncStepValidations = stepFields.map((
|
|
2465
|
+
const asyncStepValidations = getAsyncValidationTargets(stepFields).map((target) => runAsyncValidationTarget(target)).filter(Boolean);
|
|
2317
2466
|
if (asyncStepValidations.length > 0 || asyncValidationRef.current.size > 0) {
|
|
2318
2467
|
const pendingValidations = [
|
|
2319
2468
|
.../* @__PURE__ */ new Set([
|
|
@@ -2338,7 +2487,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2338
2487
|
} else {
|
|
2339
2488
|
setInternalStep(nextStep);
|
|
2340
2489
|
}
|
|
2341
|
-
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields,
|
|
2490
|
+
}, [isMultiStep, validateStepOnNext, steps, currentStep, formValues, validateVisibleFields, controlledStep, onStepChange, replaceErrors, allVisibleFields, getAsyncValidationTargets, runAsyncValidationTarget]);
|
|
2342
2491
|
const handleBack = useCallback2(() => {
|
|
2343
2492
|
if (!isMultiStep) return;
|
|
2344
2493
|
const prevStep = Math.max(currentStep - 1, 0);
|
|
@@ -2370,6 +2519,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2370
2519
|
reset: () => {
|
|
2371
2520
|
const fresh = computeInitialValues();
|
|
2372
2521
|
if (values == null) setInternalValues(fresh);
|
|
2522
|
+
controlledBaselineLockedRef.current = false;
|
|
2373
2523
|
replaceErrors({});
|
|
2374
2524
|
initialSnapshot.current = deepClone(fresh);
|
|
2375
2525
|
prevAutoSaveValues.current = deepClone(fresh);
|
|
@@ -2382,30 +2532,6 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2382
2532
|
replaceErrors(errors);
|
|
2383
2533
|
}
|
|
2384
2534
|
}));
|
|
2385
|
-
const setRepeaterSubFieldError = useCallback2(
|
|
2386
|
-
(fieldName, rowIdx, subFieldName, errorMessage) => {
|
|
2387
|
-
const key = getRepeaterErrorKey(fieldName, rowIdx, subFieldName);
|
|
2388
|
-
const merged = { ...formErrorsRef.current };
|
|
2389
|
-
if (errorMessage) {
|
|
2390
|
-
merged[key] = errorMessage;
|
|
2391
|
-
} else {
|
|
2392
|
-
delete merged[key];
|
|
2393
|
-
}
|
|
2394
|
-
const subErrors = Object.keys(merged).filter((k) => k.startsWith(`${fieldName}[`)).map((k) => {
|
|
2395
|
-
const match = k.match(/\[(\d+)\]\./);
|
|
2396
|
-
const row = match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
|
|
2397
|
-
return { key: k, row };
|
|
2398
|
-
}).sort((a, b) => a.row - b.row);
|
|
2399
|
-
if (subErrors.length > 0) {
|
|
2400
|
-
const first = subErrors[0];
|
|
2401
|
-
merged[fieldName] = `Row ${first.row + 1}: ${merged[first.key]}`;
|
|
2402
|
-
} else if (!merged[fieldName] || merged[fieldName].startsWith("Row ")) {
|
|
2403
|
-
delete merged[fieldName];
|
|
2404
|
-
}
|
|
2405
|
-
replaceErrors(merged);
|
|
2406
|
-
},
|
|
2407
|
-
[replaceErrors]
|
|
2408
|
-
);
|
|
2409
2535
|
const renderField = (field) => {
|
|
2410
2536
|
const fieldError = formErrors[field.name] || null;
|
|
2411
2537
|
const rendered = renderFieldInner(field);
|
|
@@ -2885,12 +3011,13 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2885
3011
|
const rowValues = { ...formValues, [field.name]: nextRows };
|
|
2886
3012
|
const err = runValidators(subValue, subField, rowValues, fieldTypes, { messages: validationMessages });
|
|
2887
3013
|
setRepeaterSubFieldError(field.name, rowIdx, subField.name, err);
|
|
3014
|
+
return err;
|
|
2888
3015
|
};
|
|
2889
3016
|
const handleSubFieldChange = (rowIdx, subField, subValue) => {
|
|
2890
3017
|
const updated = rows.map(
|
|
2891
3018
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2892
3019
|
);
|
|
2893
|
-
handleFieldChange(field.name, updated);
|
|
3020
|
+
handleFieldChange(field.name, updated, { clearNestedErrors: false });
|
|
2894
3021
|
if (validateOnChange) {
|
|
2895
3022
|
validateSubField(rowIdx, subField, subValue, updated);
|
|
2896
3023
|
}
|
|
@@ -2900,13 +3027,24 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2900
3027
|
const nextRows = rows.map(
|
|
2901
3028
|
(row, i) => i === rowIdx ? { ...row, [subField.name]: subValue } : row
|
|
2902
3029
|
);
|
|
2903
|
-
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
|
+
});
|
|
2904
3041
|
};
|
|
2905
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) => {
|
|
2906
3043
|
const sfValue = row[sf.name];
|
|
2907
3044
|
const sfLabel = rowIdx === 0 ? sf.label : void 0;
|
|
2908
3045
|
const sfOptions = resolveOptions(sf, { ...formValues, [field.name]: rows });
|
|
2909
3046
|
const sfError = formErrors[getRepeaterErrorKey(field.name, rowIdx, sf.name)] || null;
|
|
3047
|
+
const validationKey = getRepeaterErrorKey(field.name, rowIdx, sf.name);
|
|
2910
3048
|
const sfProps = {
|
|
2911
3049
|
name: `${field.name}-${rowIdx}-${sf.name}`,
|
|
2912
3050
|
label: sfLabel,
|
|
@@ -2915,6 +3053,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
|
|
|
2915
3053
|
disabled: resolveDisabled(sf, formValues) || isDisabled,
|
|
2916
3054
|
error: !!sfError,
|
|
2917
3055
|
validationMessage: sfError || void 0,
|
|
3056
|
+
...validatingFields[validationKey] ? { loading: true } : {},
|
|
2918
3057
|
...sf.fieldProps || {}
|
|
2919
3058
|
};
|
|
2920
3059
|
let sfElement;
|