@wavemaker-ai/react-runtime 1.0.0-rc.647712 → 12.0.0-rc.335

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.
Files changed (106) hide show
  1. package/components/basic/richtexteditor/index.d.ts +1 -0
  2. package/components/basic/search/index.js +5 -4
  3. package/components/basic/search/utils.d.ts +1 -0
  4. package/components/basic/search/utils.js +9 -0
  5. package/components/common/index.d.ts +2 -0
  6. package/components/common/index.js +2 -0
  7. package/components/common/partial-content-wrapper.d.ts +15 -0
  8. package/components/common/partial-content-wrapper.js +11 -0
  9. package/components/container/index.js +5 -2
  10. package/components/container/tabs/index.js +5 -3
  11. package/components/container/wizard/index.js +36 -3
  12. package/components/container/wizard/props.d.ts +2 -2
  13. package/components/data/form/dynamic-fields/WmxDynamicFormField.js +9 -23
  14. package/components/data/form/form-controller/props.d.ts +2 -0
  15. package/components/data/form/form-controller/utils.js +3 -2
  16. package/components/data/form/form-controller/validation-contrustor.js +112 -1
  17. package/components/data/form/form-controller/withFormController.js +1 -1
  18. package/components/data/form/form-field/index.js +6 -3
  19. package/components/data/form/form-field/props.d.ts +1 -0
  20. package/components/data/list/components/ListItemWithTemplate.js +1 -1
  21. package/components/data/list/index.js +1 -1
  22. package/components/data/table/components/EditableCell.js +47 -24
  23. package/components/data/table/components/FieldValidationError.js +1 -1
  24. package/components/data/table/components/TableHeader.js +9 -3
  25. package/components/data/table/hooks/useFormWidget.js +11 -0
  26. package/components/data/table/hooks/useRowSelection.js +58 -1
  27. package/components/data/table/hooks/useTableEdit.d.ts +1 -1
  28. package/components/data/table/hooks/useTableEdit.js +115 -75
  29. package/components/data/table/hooks/useTableEditForms.d.ts +25 -0
  30. package/components/data/table/hooks/useTableEditForms.js +30 -0
  31. package/components/data/table/index.js +68 -32
  32. package/components/data/table/props.d.ts +42 -23
  33. package/components/data/table/utils/buildSelectionColumns.js +3 -3
  34. package/components/data/table/utils/columnProxy.d.ts +7 -1
  35. package/components/data/table/utils/columnProxy.js +21 -1
  36. package/components/data/table/utils/columnValidation.d.ts +19 -0
  37. package/components/data/table/utils/columnValidation.js +76 -0
  38. package/components/data/table/utils/constants.d.ts +2 -0
  39. package/components/data/table/utils/constants.js +2 -0
  40. package/components/data/table/utils/index.d.ts +4 -12
  41. package/components/data/table/utils/index.js +13 -77
  42. package/components/data/table/utils/selectionUtils.js +13 -5
  43. package/components/input/chips/index.d.ts +1 -0
  44. package/components/input/color-picker/index.d.ts +1 -0
  45. package/components/input/currency/index.d.ts +1 -0
  46. package/components/input/default/checkbox/index.d.ts +1 -0
  47. package/components/input/default/checkboxset/index.d.ts +1 -0
  48. package/components/input/default/radioset/index.d.ts +1 -0
  49. package/components/input/default/switch/index.d.ts +1 -0
  50. package/components/input/epoch/datetime/index.js +10 -2
  51. package/components/input/number/index.d.ts +1 -0
  52. package/components/input/rating/index.d.ts +1 -0
  53. package/components/input/select/index.d.ts +1 -0
  54. package/components/input/slider/index.d.ts +1 -0
  55. package/components/input/text/index.d.ts +1 -0
  56. package/components/input/textarea/index.d.ts +1 -0
  57. package/components/input/upload/index.d.ts +1 -0
  58. package/components/layout/footer/index.js +4 -2
  59. package/components/layout/footer/props.d.ts +1 -0
  60. package/components/layout/header/index.js +29 -3
  61. package/components/layout/header/props.d.ts +1 -0
  62. package/components/layout/leftnav/index.js +22 -6
  63. package/components/layout/leftnav/props.d.ts +1 -0
  64. package/components/layout/leftnav/utils/page-class-util.d.ts +1 -0
  65. package/components/layout/leftnav/utils/page-class-util.js +4 -3
  66. package/components/layout/rightnav/index.js +4 -2
  67. package/components/layout/rightnav/props.d.ts +1 -0
  68. package/components/layout/topnav/index.js +4 -2
  69. package/components/layout/topnav/props.d.ts +1 -0
  70. package/components/navbar/index.js +32 -7
  71. package/components/navigation/menu/index.js +6 -2
  72. package/components/page/index.js +60 -19
  73. package/components/page/page-context.d.ts +4 -0
  74. package/core/proxy-service.js +86 -19
  75. package/higherOrder/BaseApp.js +1 -1
  76. package/higherOrder/BasePage.js +2 -1
  77. package/higherOrder/withBaseWrapper.js +10 -2
  78. package/hooks/useHttp.d.ts +14 -4
  79. package/hooks/useHttp.js +47 -9
  80. package/package-lock.json +314 -308
  81. package/package.json +3 -3
  82. package/runtime-dynamic/App.js +21 -2
  83. package/runtime-dynamic/app-initializer.js +8 -1
  84. package/runtime-dynamic/factories/dynamic-component.js +4 -3
  85. package/runtime-dynamic/main.d.ts +1 -0
  86. package/runtime-dynamic/main.js +9 -1
  87. package/runtime-dynamic/services/compile-render.js +7 -0
  88. package/runtime-dynamic/services/css-scoping.d.ts +1 -1
  89. package/runtime-dynamic/services/css-scoping.js +2 -2
  90. package/runtime-dynamic/services/variable-factory.js +2 -1
  91. package/runtime-dynamic/services/variable-transpiler.js +4 -1
  92. package/runtime-dynamic/wmx-shared.d.ts +40 -0
  93. package/runtime-dynamic/wmx-shared.js +33 -0
  94. package/utils/style-utils.js +1 -1
  95. package/utils/wmx.utils.d.ts +0 -1
  96. package/utils/wmx.utils.js +0 -25
  97. package/variables/crud-variable.d.ts +10 -0
  98. package/variables/crud-variable.js +53 -2
  99. package/variables/live-variable.d.ts +1 -0
  100. package/variables/live-variable.js +2 -1
  101. package/variables/operation-params.d.ts +29 -0
  102. package/variables/operation-params.js +69 -0
  103. package/variables/service-variable.d.ts +9 -0
  104. package/variables/service-variable.js +21 -2
  105. package/components/data/table/utils/validation.d.ts +0 -13
  106. package/components/data/table/utils/validation.js +0 -82
@@ -1,7 +1,21 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect } from "react";
3
3
  import { Box } from "@mui/material";
4
+ import { useFormState } from "react-hook-form";
5
+ import { get, isString } from "lodash-es";
4
6
  import { FieldValidationError } from "./FieldValidationError";
7
+ import { isColumnValidatable } from "../utils/columnValidation";
8
+ const CELL_SX = {
9
+ position: "relative",
10
+ "& > .wi-error": {
11
+ position: "absolute",
12
+ // Centres the 16px glyph on the editor without depending on the editor's height.
13
+ top: "calc(50% - 10px)",
14
+ right: 0,
15
+ // Above the input, which would otherwise paint over the icon.
16
+ zIndex: 3
17
+ }
18
+ };
5
19
  const EditableCell = ({
6
20
  column,
7
21
  rowData,
@@ -9,8 +23,9 @@ const EditableCell = ({
9
23
  fieldName,
10
24
  widgetType,
11
25
  editValue,
12
- fieldValidationErrorsRef,
13
- cellUpdateCallbacksRef,
26
+ formRef,
27
+ cellErrorsRef,
28
+ columnValidatorsRef,
14
29
  fieldRefs,
15
30
  renderFormWidget,
16
31
  updateFieldValue,
@@ -19,31 +34,32 @@ const EditableCell = ({
19
34
  editMode = "inline"
20
35
  }) => {
21
36
  var _a;
22
- const cellRef = useRef(null);
23
- const [, setForceUpdate] = useState(0);
24
37
  const fieldKey = `${rowId}_${fieldName}`;
25
- const cellRefCallback = (element) => {
26
- cellRef.current = element;
27
- if (element && fieldRefs.current) {
28
- fieldRefs.current[fieldKey] = element;
29
- }
30
- };
38
+ const { errors } = useFormState({ control: formRef.control, name: fieldName, exact: true });
39
+ const error = get(errors, fieldName);
40
+ const showError = Boolean(error);
41
+ const cellRefCallback = useCallback(
42
+ (element) => {
43
+ if (!fieldRefs.current) return;
44
+ if (element) fieldRefs.current[fieldKey] = element;
45
+ else delete fieldRefs.current[fieldKey];
46
+ },
47
+ [fieldKey, fieldRefs]
48
+ );
31
49
  useEffect(() => {
32
- if (cellUpdateCallbacksRef.current) {
33
- cellUpdateCallbacksRef.current[fieldKey] = () => setForceUpdate((prev) => prev + 1);
34
- }
50
+ const cellErrors = cellErrorsRef == null ? void 0 : cellErrorsRef.current;
51
+ if (!cellErrors) return;
52
+ if (showError) cellErrors[fieldKey] = true;
53
+ else delete cellErrors[fieldKey];
35
54
  return () => {
36
- if (cellUpdateCallbacksRef.current) {
37
- delete cellUpdateCallbacksRef.current[fieldKey];
38
- }
55
+ delete cellErrors[fieldKey];
39
56
  };
40
- }, [fieldKey, cellUpdateCallbacksRef]);
41
- const showError = ((_a = fieldValidationErrorsRef.current) == null ? void 0 : _a[fieldKey]) === true;
57
+ }, [showError, fieldKey, cellErrorsRef]);
42
58
  const handleKeyDown = (e) => {
43
- if (onKeyDown) {
44
- onKeyDown(e, rowId);
45
- }
59
+ onKeyDown == null ? void 0 : onKeyDown(e, rowId);
46
60
  };
61
+ const ignoreInForm = !isColumnValidatable(column);
62
+ const columnValidators = (_a = columnValidatorsRef == null ? void 0 : columnValidatorsRef.current) == null ? void 0 : _a[fieldName];
47
63
  return /* @__PURE__ */ jsxs(
48
64
  Box,
49
65
  {
@@ -51,6 +67,7 @@ const EditableCell = ({
51
67
  onKeyDown: editMode === "quickedit" || editMode === "inline" ? handleKeyDown : void 0,
52
68
  "data-field-name": fieldName,
53
69
  "data-row-id": rowId,
70
+ sx: CELL_SX,
54
71
  children: [
55
72
  renderFormWidget(
56
73
  fieldName,
@@ -64,14 +81,20 @@ const EditableCell = ({
64
81
  defaultvalue: column.defaultvalue,
65
82
  sessionKey: `${sessionKey}_${rowId}_${fieldName}`,
66
83
  rowData,
67
- column
84
+ column,
85
+ formRef,
86
+ // formKey stays stable; widget name keeps session-unique value
87
+ formKey: fieldName,
88
+ validators: columnValidators == null ? void 0 : columnValidators.validators,
89
+ asyncValidators: columnValidators == null ? void 0 : columnValidators.asyncValidators,
90
+ ignoreInForm
68
91
  }
69
92
  ),
70
93
  showError && /* @__PURE__ */ jsx(
71
94
  FieldValidationError,
72
95
  {
73
96
  showError: true,
74
- title: column.validationmessage ? column.validationmessage : column.required ? "This field is required" : "Field validation failed"
97
+ title: isString(error == null ? void 0 : error.message) && error.message || column.validationmessage || (column.required ? "This field is required" : "Field validation failed")
75
98
  }
76
99
  )
77
100
  ]
@@ -9,7 +9,7 @@ const FieldValidationError = ({
9
9
  return /* @__PURE__ */ jsx(
10
10
  "span",
11
11
  {
12
- className: "text-danger wi wi-error",
12
+ className: "app-label text-danger wi wi-error",
13
13
  "data-placement": "top",
14
14
  "data-container": "body",
15
15
  title
@@ -36,6 +36,7 @@ import { flexRender } from "@tanstack/react-table";
36
36
  import { TABLE_CSS_CLASSES, getDomHiddenCellStyle } from "../utils";
37
37
  import { TableFilterRow } from "./TableFilters";
38
38
  import { calculateMaxDepth, generateHeaderRows, hasTableGroups } from "../utils/groupHeaderUtils";
39
+ import { getWidgetForCallback } from "../utils/selectionUtils";
39
40
  const StandardHeaderCell = memo(
40
41
  (_a) => {
41
42
  var _b = _a, {
@@ -169,6 +170,7 @@ const TableHeaderComponent = memo(
169
170
  onColumnFilterChange,
170
171
  renderFormWidget,
171
172
  listener,
173
+ viewParent,
172
174
  tableStructure,
173
175
  onColumnSelect,
174
176
  onColumnDeselect,
@@ -342,7 +344,7 @@ const TableHeaderComponent = memo(
342
344
  const columnId = header.column.id;
343
345
  const isSystemColumn = columnId === "multiSelect" || columnId === "radioSelect" || columnId === "row-index" || columnId === "rowIndex" || columnId === "actions";
344
346
  const handleHeaderClick = (event) => {
345
- var _a2, _b2;
347
+ var _a2;
346
348
  if (onHeaderclick) {
347
349
  const colDef = header.column.columnDef;
348
350
  const columnData = {
@@ -350,7 +352,11 @@ const TableHeaderComponent = memo(
350
352
  caption: colDef.header || columnId,
351
353
  colDef
352
354
  };
353
- onHeaderclick(event, (_a2 = listener == null ? void 0 : listener.Widgets) == null ? void 0 : _a2[tableName || ""], columnData);
355
+ onHeaderclick(
356
+ event,
357
+ getWidgetForCallback(listener, viewParent, tableName || ""),
358
+ columnData
359
+ );
354
360
  }
355
361
  if (enablecolumnselection && !isSystemColumn) {
356
362
  const target = event.currentTarget;
@@ -376,7 +382,7 @@ const TableHeaderComponent = memo(
376
382
  return;
377
383
  }
378
384
  if (enablesort && header.column.getCanSort()) {
379
- (_b2 = header.column.getToggleSortingHandler()) == null ? void 0 : _b2(event);
385
+ (_a2 = header.column.getToggleSortingHandler()) == null ? void 0 : _a2(event);
380
386
  }
381
387
  };
382
388
  const isClickable = enablecolumnselection && !isSystemColumn || enablesort && header.column.getCanSort();
@@ -66,6 +66,17 @@ const useFormWidget = ({ listener }) => {
66
66
  key: uniqueName,
67
67
  // Add key to force component recreation
68
68
  name: uniqueName,
69
+ // Wiring the cell into the table's own react-hook-form instance. withFormController
70
+ // prefers this `formRef` over the page form's context, keys the field by `formKey`, and
71
+ // turns `validators` (plus the column's required/regexp/maxchars/min/max) into rules —
72
+ // including the date/time bounds it hands straight back to the picker. `ignoreInForm`
73
+ // opts a cell out entirely, leaving the widget uncontrolled.
74
+ formRef: widgetProps == null ? void 0 : widgetProps.formRef,
75
+ formKey: widgetProps == null ? void 0 : widgetProps.formKey,
76
+ validators: widgetProps == null ? void 0 : widgetProps.validators,
77
+ asyncValidators: widgetProps == null ? void 0 : widgetProps.asyncValidators,
78
+ ignoreInForm: widgetProps == null ? void 0 : widgetProps.ignoreInForm,
79
+ hideValidationMessage: true,
69
80
  datavalue: effectiveValue,
70
81
  required: (widgetProps == null ? void 0 : widgetProps.required) || false,
71
82
  disabled,
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import isEqual from "lodash-es/isEqual";
2
3
  import {
3
4
  isInteractiveElement,
4
5
  isSelectionCheckboxRenamed,
@@ -12,6 +13,36 @@ import {
12
13
  import { saveTableState, getTableState } from "../utils/table-helpers";
13
14
  import { cleanRowData, UNSUPPORTED_STATE_PERSISTENCE_TYPES } from "../utils";
14
15
  const getRowFallbackId = (row) => row._wmTableRowId || String(row.id) || String(0);
16
+ const omitEmptyArrays = (row) => {
17
+ const result = {};
18
+ Object.keys(row).forEach((key) => {
19
+ const value = row[key];
20
+ if (Array.isArray(value) && value.length === 0) return;
21
+ if (typeof value === "function") return;
22
+ result[key] = value;
23
+ });
24
+ return result;
25
+ };
26
+ const resolveRowFromDataset = (dataset, itemOrIndex) => {
27
+ var _a, _b, _c;
28
+ const rows = dataset || [];
29
+ if (typeof itemOrIndex === "number") {
30
+ return (_a = rows[itemOrIndex]) != null ? _a : null;
31
+ }
32
+ if (!itemOrIndex || typeof itemOrIndex !== "object") {
33
+ return null;
34
+ }
35
+ if (rows.indexOf(itemOrIndex) >= 0) {
36
+ return itemOrIndex;
37
+ }
38
+ const candidateId = (_b = itemOrIndex._wmTableRowId) != null ? _b : itemOrIndex.id;
39
+ if (candidateId !== void 0 && candidateId !== null) {
40
+ const byId = rows.find((row) => row._wmTableRowId === candidateId || row.id === candidateId);
41
+ if (byId) return byId;
42
+ }
43
+ const needle = omitEmptyArrays(itemOrIndex);
44
+ return (_c = rows.find((row) => isEqual(omitEmptyArrays(row), needle))) != null ? _c : null;
45
+ };
15
46
  const getSelectedIndices = (selectedIds, dataset) => selectedIds.reduce((indices, rowId) => {
16
47
  const index = dataset.findIndex((row) => row._wmTableRowId === rowId || row.id === rowId);
17
48
  if (index >= 0) {
@@ -354,6 +385,30 @@ const useRowSelection = ({
354
385
  useMultiSelect,
355
386
  applySelectionForMode
356
387
  ]);
388
+ const selectItem = useCallback(
389
+ (itemOrIndex) => {
390
+ const row = resolveRowFromDataset(datasetRef.current, itemOrIndex);
391
+ if (!row) return;
392
+ const rowId = getRowFallbackId(row);
393
+ if (selectionStateHelpers.isRowSelected(cellState, rowId)) return;
394
+ if (useMultiSelect) {
395
+ handleMultiSelection(rowId, row, true);
396
+ } else {
397
+ handleRadioSelection(rowId, row);
398
+ }
399
+ },
400
+ [cellState, useMultiSelect, handleMultiSelection, handleRadioSelection]
401
+ );
402
+ const deselectItem = useCallback(
403
+ (itemOrIndex) => {
404
+ const row = resolveRowFromDataset(datasetRef.current, itemOrIndex);
405
+ if (!row) return;
406
+ const rowId = getRowFallbackId(row);
407
+ if (!selectionStateHelpers.isRowSelected(cellState, rowId)) return;
408
+ handleMultiSelection(rowId, row, false);
409
+ },
410
+ [cellState, handleMultiSelection]
411
+ );
357
412
  return {
358
413
  selectedRowIds,
359
414
  useMultiSelect,
@@ -363,7 +418,9 @@ const useRowSelection = ({
363
418
  handleSelectAll,
364
419
  handleRowSelectionClick,
365
420
  isRowSelected,
366
- isInteractiveElement
421
+ isInteractiveElement,
422
+ selectItem,
423
+ deselectItem
367
424
  };
368
425
  };
369
426
  export {
@@ -1,4 +1,4 @@
1
1
  import { UseFormWidgetReturn, UseTableEditProps, UseTableEditReturn } from "../props";
2
- export declare const useTableEdit: ({ editMode, internalDataset, setInternalDataset, wmTableColumns, cellState, renderFormWidget, listener, onRowUpdate, onRowDelete, onNewRowAdded, showrowindex, radioselect, multiselect, rowActions, formposition, insertmessage, updatemessage, errormessage, showToast, hasRowExpansion, expansionPosition, datasource, binddataset, onSuccess, onError, onRowinsert, onRowupdate, tableRef, isServerSidePagination, tableName, }: UseTableEditProps & {
2
+ export declare const useTableEdit: ({ editMode, internalDataset, setInternalDataset, wmTableColumns, cellState, renderFormWidget, listener, onRowUpdate, onRowDelete, onNewRowAdded, showrowindex, radioselect, multiselect, rowActions, formposition, insertmessage, updatemessage, errormessage, showToast, hasRowExpansion, expansionPosition, datasource, binddataset, onSuccess, onError, onRowinsert, onRowupdate, tableRef, isServerSidePagination, tableName, columnValidatorsRef, }: UseTableEditProps & {
3
3
  renderFormWidget: UseFormWidgetReturn["renderFormWidget"];
4
4
  }) => UseTableEditReturn;
@@ -19,22 +19,38 @@ var __spreadValues = (a, b) => {
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
  import { Fragment, jsx } from "react/jsx-runtime";
21
21
  import { useCallback, useRef, useEffect } from "react";
22
- import { find, forEach, entries, get, every } from "lodash-es";
22
+ import { get } from "lodash-es";
23
23
  import { EditableCell } from "../components";
24
24
  import { AddNewRow } from "../components/AddNewRow";
25
- import { validateEditingFields, TABLE_MESSAGES, INTERNAL_PROPERTIES } from "../utils";
25
+ import { TABLE_MESSAGES, INTERNAL_PROPERTIES } from "../utils";
26
26
  import { useEditingState } from "./useEditingState";
27
27
  import {
28
- validateField,
29
- resetValidationState as resetValidation,
30
- updateValidationErrors
31
- } from "../utils/validation";
28
+ isColumnValidatable,
29
+ collectNativeValidityErrors,
30
+ focusFirstInvalidCell
31
+ } from "../utils/columnValidation";
32
+ import { useTableEditForms } from "./useTableEditForms";
32
33
  import { handleServerOperation } from "../utils/crud-handlers";
33
34
  import { useUpdateEditedCell, useEditedRowsContext } from "./use-edited-rows";
34
35
  import {
35
36
  useFormContext,
36
37
  useParentFormData
37
38
  } from "../../form/form-context";
39
+ const DEFERRED_COMMIT_INPUT_TYPES = /* @__PURE__ */ new Set([
40
+ "text",
41
+ "number",
42
+ "email",
43
+ "url",
44
+ "tel",
45
+ "search",
46
+ "password"
47
+ ]);
48
+ const readPendingInputValue = (element) => {
49
+ if (element.tagName === "TEXTAREA") return element.value;
50
+ if (element.tagName !== "INPUT") return void 0;
51
+ const input = element;
52
+ return DEFERRED_COMMIT_INPUT_TYPES.has(input.type) ? input.value : void 0;
53
+ };
38
54
  const MODE_CONFIGS = {
39
55
  inline: {
40
56
  showNewRowFormByDefault: false,
@@ -97,7 +113,8 @@ const useTableEdit = ({
97
113
  onRowupdate,
98
114
  tableRef,
99
115
  isServerSidePagination,
100
- tableName
116
+ tableName,
117
+ columnValidatorsRef
101
118
  }) => {
102
119
  const updateCell = useUpdateEditedCell();
103
120
  const { getEdits, removeRowEdits } = useEditedRowsContext();
@@ -118,20 +135,24 @@ const useTableEdit = ({
118
135
  const parentFormDataContext = useParentFormData();
119
136
  const parentDataPath = (parentFormDataContext == null ? void 0 : parentFormDataContext.parentDataPath) !== void 0 ? parentFormDataContext.parentDataPath : void 0;
120
137
  const fieldRefs = useRef({});
121
- const fieldValidationErrorsRef = useRef({});
122
- const cellUpdateCallbacksRef = useRef({});
138
+ const cellErrorsRef = useRef({});
123
139
  const originalRowDataRef = useRef({});
124
- const validationState = {
125
- fieldRefs,
126
- fieldValidationErrors: fieldValidationErrorsRef,
127
- cellUpdateCallbacks: cellUpdateCallbacksRef
128
- };
140
+ const { editForm, newRowForm } = useTableEditForms(tableName);
141
+ const usesSeparateNewRowSession = editMode === "quickedit";
142
+ const formFor = useCallback(
143
+ (isNewRowSession) => isNewRowSession && usesSeparateNewRowSession ? newRowForm : editForm,
144
+ [editForm, newRowForm, usesSeparateNewRowSession]
145
+ );
146
+ const dataRefFor = useCallback(
147
+ (isNewRowSession) => isNewRowSession && usesSeparateNewRowSession ? newRowDataRef : editingRowDataRef,
148
+ [usesSeparateNewRowSession, newRowDataRef, editingRowDataRef]
149
+ );
129
150
  const config = MODE_CONFIGS[editMode] || MODE_CONFIGS.none;
130
151
  const resetValidationState = useCallback(
131
- (context) => {
132
- resetValidation(context, editingRowId, validationState);
152
+ (isNewRowSession = false) => {
153
+ formFor(isNewRowSession).reset();
133
154
  },
134
- [editingRowId]
155
+ [formFor]
135
156
  );
136
157
  useEffect(() => {
137
158
  setIsAddingNewRow(config.showNewRowFormByDefault);
@@ -150,7 +171,7 @@ const useTableEdit = ({
150
171
  setEditingRowId(rowId);
151
172
  const existingEdits = getEdits(rowId) || {};
152
173
  editingRowDataRef.current = __spreadValues(__spreadValues({}, rowData), existingEdits);
153
- resetValidationState(editMode === "quickedit" ? "all" : void 0);
174
+ resetValidationState();
154
175
  incrementSessionKey();
155
176
  },
156
177
  [
@@ -177,7 +198,7 @@ const useTableEdit = ({
177
198
  editingRowDataRef.current = {};
178
199
  }
179
200
  resetEditingData();
180
- resetValidationState(editMode === "quickedit" ? "all" : void 0);
201
+ resetValidationState();
181
202
  incrementSessionKey();
182
203
  }, [
183
204
  editMode,
@@ -191,27 +212,43 @@ const useTableEdit = ({
191
212
  cellState
192
213
  ]);
193
214
  const saveEditingInternal = useCallback(
194
- async (rowId, isNewRow, rowDataRef, onSaveSuccess) => {
215
+ async (rowId, isNewRow, rowDataRef, form, onSaveSuccess) => {
216
+ var _a, _b;
195
217
  const currentRowId = rowId || (isNewRow ? "new-row" : null);
196
218
  if (!currentRowId) return Promise.resolve(false);
197
219
  const activeElement = document.activeElement;
198
220
  if (activeElement) {
199
- const editableCellContainer = activeElement.closest("[data-field-name]");
200
- if (editableCellContainer) {
201
- const fieldName = editableCellContainer.getAttribute("data-field-name");
202
- const inputValue = activeElement.value;
203
- if (fieldName && inputValue !== void 0) {
204
- rowDataRef.current = __spreadProps(__spreadValues({}, rowDataRef.current), {
205
- [fieldName]: inputValue
206
- });
207
- }
208
- }
209
- if (activeElement.blur) {
210
- activeElement.blur();
221
+ const fieldName = (_a = activeElement.closest("[data-field-name]")) == null ? void 0 : _a.getAttribute("data-field-name");
222
+ const pendingValue = readPendingInputValue(activeElement);
223
+ if (fieldName && pendingValue !== void 0) {
224
+ rowDataRef.current = __spreadProps(__spreadValues({}, rowDataRef.current), {
225
+ [fieldName]: pendingValue
226
+ });
227
+ form.setFieldValue(fieldName, pendingValue);
211
228
  }
229
+ (_b = activeElement.blur) == null ? void 0 : _b.call(activeElement);
212
230
  }
213
231
  const currentEditingData = rowDataRef.current;
214
232
  const originalData = originalRowDataRef.current;
233
+ const validatableFields = wmTableColumns.filter(isColumnValidatable).map((column) => column.field);
234
+ if (!await form.validate()) {
235
+ focusFirstInvalidCell(
236
+ fieldRefs.current,
237
+ currentRowId,
238
+ form.getInvalidFields(validatableFields)
239
+ );
240
+ return false;
241
+ }
242
+ const nativeErrors = collectNativeValidityErrors(fieldRefs.current, currentRowId);
243
+ if (nativeErrors.length > 0) {
244
+ nativeErrors.forEach(({ fieldName, message }) => form.setFieldError(fieldName, message));
245
+ focusFirstInvalidCell(
246
+ fieldRefs.current,
247
+ currentRowId,
248
+ nativeErrors.map(({ fieldName }) => fieldName)
249
+ );
250
+ return false;
251
+ }
215
252
  if (!isNewRow && originalData && currentEditingData) {
216
253
  const hasChanges = wmTableColumns.some((column) => {
217
254
  if (!column.field || column.readonly) return false;
@@ -230,17 +267,6 @@ const useTableEdit = ({
230
267
  return true;
231
268
  }
232
269
  }
233
- const relevantFieldRefs = {};
234
- forEach(entries(fieldRefs.current), ([fieldKey, fieldElement]) => {
235
- if (fieldKey.startsWith(`${currentRowId}_`)) {
236
- relevantFieldRefs[fieldKey] = fieldElement;
237
- }
238
- });
239
- const validationResult = validateEditingFields(relevantFieldRefs, currentRowId);
240
- if (!validationResult.isValid) {
241
- updateValidationErrors(validationResult, relevantFieldRefs, validationState);
242
- return false;
243
- }
244
270
  const result = await handleServerOperation({
245
271
  isNewRow,
246
272
  rowId,
@@ -315,19 +341,23 @@ const useTableEdit = ({
315
341
  const saveEditing = useCallback(async () => {
316
342
  const isNewRow = editMode === "inline" ? isAddingNewRow : false;
317
343
  const rowId = editingRowId || (isNewRow ? "new-row" : null);
318
- const dataRef = editMode === "quickedit" && !editingRowId ? newRowDataRef : editingRowDataRef;
319
- const success = await saveEditingInternal(rowId, isNewRow, dataRef, () => {
320
- setEditingRowId(null);
321
- if (editMode === "inline") {
322
- setIsAddingNewRow(false);
323
- editingRowDataRef.current = {};
344
+ const isNewRowSession = usesSeparateNewRowSession ? !editingRowId : isAddingNewRow;
345
+ const success = await saveEditingInternal(
346
+ rowId,
347
+ isNewRow,
348
+ dataRefFor(isNewRowSession),
349
+ formFor(isNewRowSession),
350
+ () => {
351
+ setEditingRowId(null);
352
+ if (editMode === "inline") {
353
+ setIsAddingNewRow(false);
354
+ editingRowDataRef.current = {};
355
+ }
356
+ resetEditingData();
357
+ resetValidationState(isNewRowSession);
358
+ incrementSessionKey();
324
359
  }
325
- resetEditingData();
326
- resetValidationState(
327
- editMode === "quickedit" ? editingRowId ? "editing" : "new-row" : void 0
328
- );
329
- incrementSessionKey();
330
- });
360
+ );
331
361
  return success;
332
362
  }, [
333
363
  editMode,
@@ -338,7 +368,10 @@ const useTableEdit = ({
338
368
  setEditingRowId,
339
369
  setIsAddingNewRow,
340
370
  incrementSessionKey,
341
- saveEditingInternal
371
+ saveEditingInternal,
372
+ formFor,
373
+ dataRefFor,
374
+ usesSeparateNewRowSession
342
375
  ]);
343
376
  const updateFieldValue = useCallback(
344
377
  (fieldName, newValue, rowId) => {
@@ -361,26 +394,22 @@ const useTableEdit = ({
361
394
  newRowDataRef.current = __spreadProps(__spreadValues({}, newRowDataRef.current), {
362
395
  [fieldName]: newValue
363
396
  });
364
- const column = find(wmTableColumns, (col) => col.field === fieldName);
365
- validateField("new-row", fieldName, newValue, column, validationState);
366
397
  } else {
367
398
  editingRowDataRef.current = __spreadProps(__spreadValues({}, editingRowDataRef.current), {
368
399
  [fieldName]: newValue
369
400
  });
370
- const column = find(wmTableColumns, (col) => col.field === fieldName);
371
- validateField(effectiveRowId, fieldName, newValue, column, validationState);
372
401
  }
373
402
  },
374
- [editMode, editingRowId, wmTableColumns, cellState, setInternalDataset]
403
+ [editMode, editingRowId, cellState, setInternalDataset]
375
404
  );
376
405
  const saveNewRow = useCallback(() => {
377
- const success = saveEditingInternal(null, true, newRowDataRef, () => {
406
+ const success = saveEditingInternal(null, true, newRowDataRef, formFor(true), () => {
378
407
  newRowDataRef.current = {};
379
- resetValidationState("new-row");
408
+ resetValidationState(true);
380
409
  incrementSessionKey();
381
410
  });
382
411
  return success;
383
- }, [resetValidationState, incrementSessionKey, saveEditingInternal]);
412
+ }, [resetValidationState, incrementSessionKey, saveEditingInternal, formFor]);
384
413
  const handleKeyDown = useCallback(
385
414
  (e, sourceRowId) => {
386
415
  if (editMode === "inline" && e.key === "Enter" && !e.shiftKey) {
@@ -407,7 +436,7 @@ const useTableEdit = ({
407
436
  e.stopPropagation();
408
437
  if (isFromNewRow) {
409
438
  newRowDataRef.current = {};
410
- resetValidationState("new-row");
439
+ resetValidationState(true);
411
440
  incrementSessionKey();
412
441
  } else if (isEditingRow) {
413
442
  cancelEditing();
@@ -441,8 +470,9 @@ const useTableEdit = ({
441
470
  fieldName,
442
471
  widgetType,
443
472
  editValue,
444
- fieldValidationErrorsRef,
445
- cellUpdateCallbacksRef,
473
+ formRef: formFor(false).formRef,
474
+ cellErrorsRef,
475
+ columnValidatorsRef,
446
476
  fieldRefs,
447
477
  renderFormWidget,
448
478
  updateFieldValue,
@@ -466,17 +496,22 @@ const useTableEdit = ({
466
496
  updateFieldValue,
467
497
  sessionKey,
468
498
  handleKeyDown,
469
- config.hasKeyboardNavigation
499
+ config.hasKeyboardNavigation,
500
+ columnValidatorsRef,
501
+ formFor
470
502
  ]
471
503
  );
472
504
  const handleRowClick = useCallback(
473
505
  (rowData, rowId) => {
474
506
  if (!config.startEditOnRowClick) return;
475
- if (!isRowEditing(rowId) && (editMode === "quickedit" || editMode === "inline" ? every(validationState.fieldValidationErrors.current, (error) => error === false) : true)) {
507
+ const editingRowHasErrors = !!editingRowId && Object.keys(cellErrorsRef.current).some(
508
+ (fieldKey) => fieldKey.startsWith(`${editingRowId}_`)
509
+ );
510
+ if (!isRowEditing(rowId) && !editingRowHasErrors) {
476
511
  startEditing(rowData, rowId);
477
512
  }
478
513
  },
479
- [config.startEditOnRowClick, isRowEditing, startEditing]
514
+ [config.startEditOnRowClick, isRowEditing, startEditing, editingRowId]
480
515
  );
481
516
  const handleAddNewRowClick = useCallback(() => {
482
517
  if (editMode !== "inline") {
@@ -515,7 +550,8 @@ const useTableEdit = ({
515
550
  const renderAddNewRow = useCallback(() => {
516
551
  const shouldShowNewRow = editMode === "inline" && isAddingNewRow || editMode === "quickedit" && config.showNewRowFormByDefault;
517
552
  if (!shouldShowNewRow) return null;
518
- const dataRef = editMode === "quickedit" ? newRowDataRef : editingRowDataRef;
553
+ const dataRef = dataRefFor(true);
554
+ const newRowFormRef = formFor(true).formRef;
519
555
  const onKeyDownHandler = config.hasKeyboardNavigation ? (e) => handleKeyDown(e, "new-row") : void 0;
520
556
  const renderCell = (column, fieldName, widgetType, editValue, rowId) => {
521
557
  return /* @__PURE__ */ jsx(
@@ -527,8 +563,9 @@ const useTableEdit = ({
527
563
  fieldName,
528
564
  widgetType,
529
565
  editValue,
530
- fieldValidationErrorsRef,
531
- cellUpdateCallbacksRef,
566
+ formRef: newRowFormRef,
567
+ cellErrorsRef,
568
+ columnValidatorsRef,
532
569
  fieldRefs,
533
570
  renderFormWidget,
534
571
  updateFieldValue,
@@ -557,7 +594,7 @@ const useTableEdit = ({
557
594
  handleCancel: () => {
558
595
  if (editMode === "quickedit") {
559
596
  newRowDataRef.current = {};
560
- resetValidationState("new-row");
597
+ resetValidationState(true);
561
598
  incrementSessionKey();
562
599
  } else {
563
600
  cancelEditing();
@@ -588,7 +625,10 @@ const useTableEdit = ({
588
625
  incrementSessionKey,
589
626
  sessionKey,
590
627
  hasRowExpansion,
591
- expansionPosition
628
+ expansionPosition,
629
+ columnValidatorsRef,
630
+ formFor,
631
+ dataRefFor
592
632
  ]);
593
633
  return {
594
634
  editingRowId,
@@ -0,0 +1,25 @@
1
+ import { Control, FieldValues, UseFormTrigger } from "react-hook-form";
2
+ export interface TableEditFormRef {
3
+ control: Control<FieldValues>;
4
+ trigger: UseFormTrigger<FieldValues>;
5
+ name?: string;
6
+ validationtype: "default";
7
+ }
8
+ export interface TableEditForm {
9
+ /** Spread onto every edit widget of the session this form backs. */
10
+ formRef: TableEditFormRef;
11
+ /** Validate every registered cell; resolves false when any rule fails. */
12
+ validate: () => Promise<boolean>;
13
+ /** Which of fieldNames have errors. */
14
+ getInvalidFields: (fieldNames: string[]) => string[];
15
+ /** Update field value without re-validating. */
16
+ setFieldValue: (fieldName: string, value: unknown) => void;
17
+ /** Set error from browser validity. */
18
+ setFieldError: (fieldName: string, message: string) => void;
19
+ /** Clear all values and errors. */
20
+ reset: () => void;
21
+ }
22
+ export declare const useTableEditForms: (tableName?: string) => {
23
+ editForm: TableEditForm;
24
+ newRowForm: TableEditForm;
25
+ };