@vuu-ui/vuu-data-editing 4.0.0-alpha.1 → 4.0.0-alpha.11

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0-alpha.1",
2
+ "version": "4.0.0-alpha.11",
3
3
  "description": "Shared data editing APIs and React components for VUU UI",
4
4
  "main": "./src/index.js",
5
5
  "author": "heswell",
@@ -12,13 +12,13 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@salt-ds/core": "1.67.0",
15
- "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.1",
16
- "@vuu-ui/vuu-utils": "4.0.0-alpha.1"
15
+ "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.11",
16
+ "@vuu-ui/vuu-utils": "4.0.0-alpha.11"
17
17
  },
18
18
  "devDependencies": {
19
- "@vuu-ui/vuu-data-types": "4.0.0-alpha.1",
20
- "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.1",
21
- "@vuu-ui/vuu-table-types": "4.0.0-alpha.1"
19
+ "@vuu-ui/vuu-data-types": "4.0.0-alpha.11",
20
+ "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.11",
21
+ "@vuu-ui/vuu-table-types": "4.0.0-alpha.11"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19.2.3"
@@ -32,6 +32,7 @@
32
32
  "exports": {
33
33
  ".": {
34
34
  "import": "./src/index.js",
35
+ "default": "./src/index.js",
35
36
  "types": "./types/index.d.ts"
36
37
  }
37
38
  },
@@ -1,8 +1,10 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Button } from "@salt-ds/core";
3
- import { useCallback, useEffect, useState } from "react";
4
- const EditButtons = ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection = false, onCancel, onDelete, onSave, saveLabel = "Save" })=>{
5
- const [editState, setEditState] = useState(()=>editSession?.editState ?? "clean");
3
+ import { useCallback } from "react";
4
+ import { useEditState } from "./useEditState.js";
5
+ const EditButtons = ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection = false, onCancel, onDelete, onSave, saveLabel = "Save" })=>{
6
+ const editState = useEditState(editSession);
7
+ const isDirty = editSession?.isDirty ?? false;
6
8
  const handleSave = useCallback(async ()=>{
7
9
  if (confirmSave) {
8
10
  const confirmed = await confirmSave();
@@ -15,24 +17,16 @@ const EditButtons = ({ canCancel, canSave, confirmCancel, confirmSave, editSessi
15
17
  onSave
16
18
  ]);
17
19
  const handleCancel = useCallback(async ()=>{
18
- if (confirmCancel) {
20
+ if (confirmCancel && isDirty) {
19
21
  const confirmed = await confirmCancel();
20
22
  if (!confirmed) return;
21
23
  }
22
24
  onCancel?.();
23
25
  }, [
24
26
  confirmCancel,
27
+ isDirty,
25
28
  onCancel
26
29
  ]);
27
- useEffect(()=>{
28
- if (editSession) {
29
- setEditState(editSession.editState);
30
- editSession.on("editState", setEditState);
31
- return ()=>editSession.removeListener("editState", setEditState);
32
- }
33
- }, [
34
- editSession
35
- ]);
36
30
  return /*#__PURE__*/ jsxs(Fragment, {
37
31
  children: [
38
32
  onDelete && /*#__PURE__*/ jsx(Button, {
@@ -42,7 +36,7 @@ const EditButtons = ({ canCancel, canSave, confirmCancel, confirmSave, editSessi
42
36
  children: "Delete"
43
37
  }),
44
38
  /*#__PURE__*/ jsx(Button, {
45
- disabled: !canSave,
39
+ disabled: !editSession?.canSave,
46
40
  onClick: handleSave,
47
41
  sentiment: "accented",
48
42
  children: "stale" === editState ? `${saveLabel} (force)` : saveLabel
@@ -1,11 +1,16 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useState } from "react";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
3
  const EditModeContext = /*#__PURE__*/ createContext({
4
4
  isEditMode: false,
5
5
  setEditMode: ()=>"EditModeProvider in place"
6
6
  });
7
- const EditModeProvider = ({ children })=>{
8
- const [isEditMode, setEditMode] = useState(false);
7
+ const EditModeProvider = ({ children, isEditMode: isEditModeProp = false })=>{
8
+ const [isEditMode, setEditMode] = useState(isEditModeProp);
9
+ useEffect(()=>{
10
+ setEditMode(isEditModeProp);
11
+ }, [
12
+ isEditModeProp
13
+ ]);
9
14
  return /*#__PURE__*/ jsx(EditModeContext.Provider, {
10
15
  value: {
11
16
  isEditMode,
@@ -16,9 +16,6 @@ class SupersededEditError extends Error {
16
16
  class EditSession extends EventEmitter {
17
17
  static newRowKey = "__vuu_new_row__";
18
18
  #rowEdits = new Map();
19
- #deletedRows = new Set();
20
- #deleteRevision = 0;
21
- #rowDeleteRevisions = new Map();
22
19
  #editCount = 0;
23
20
  #deleteCount = 0;
24
21
  #addCount = 0;
@@ -48,47 +45,36 @@ class EditSession extends EventEmitter {
48
45
  this.#sourceTableDataSource = dataSource;
49
46
  this.#deleteMode = deleteMode;
50
47
  this.#editSessionApi = editSessionApi;
51
- this.#rowDefaults = rowDefaults;
48
+ this.#rowDefaults = {
49
+ ...rowDefaults
50
+ };
52
51
  }
53
52
  get editCount() {
54
53
  return this.#editCount;
55
54
  }
56
- set editCount(val) {
57
- this.#setEditCounts(val, this.#invalidCount);
58
- }
59
55
  get invalidCount() {
60
56
  return this.#invalidCount;
61
57
  }
62
- set invalidCount(val) {
63
- this.#setEditCounts(this.#editCount, val);
64
- }
65
58
  get deleteCount() {
66
59
  return this.#deleteCount;
67
60
  }
68
- set deleteCount(val) {
69
- if (val !== this.#deleteCount) {
70
- const oldState = this.editState;
71
- const oldCount = this.#deleteCount;
72
- this.#deleteCount = val;
73
- this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
74
- }
75
- }
76
61
  get addCount() {
77
62
  return this.#addCount;
78
63
  }
79
- set addCount(val) {
80
- if (val !== this.#addCount) {
81
- const oldState = this.editState;
82
- const oldCount = this.#addCount;
83
- this.#addCount = val;
84
- this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
85
- }
86
- }
87
64
  get editState() {
88
65
  if (this.#invalidCount > 0) return "invalid";
89
66
  if (this.#isStale) return "stale";
90
67
  return 0 === this.#editCount && 0 === this.#deleteCount && 0 === this.#addCount ? "clean" : "dirty";
91
68
  }
69
+ get isDirty() {
70
+ return "clean" !== this.editState;
71
+ }
72
+ get canCancel() {
73
+ return "active" === this.lifecycle.status || "error" === this.lifecycle.status && "end" === this.lifecycle.operation;
74
+ }
75
+ get canSave() {
76
+ return this.canCancel && ("dirty" === this.editState || "stale" === this.editState) && 0 === this.invalidCount;
77
+ }
92
78
  #emitEditStateChange(oldState, force = false) {
93
79
  const newState = this.editState;
94
80
  if (force || newState !== oldState) this.emit("editState", newState);
@@ -101,6 +87,22 @@ class EditSession extends EventEmitter {
101
87
  this.#emitEditStateChange(oldState);
102
88
  }
103
89
  }
90
+ #setDeleteCount(val) {
91
+ if (val !== this.#deleteCount) {
92
+ const oldState = this.editState;
93
+ const oldCount = this.#deleteCount;
94
+ this.#deleteCount = val;
95
+ this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
96
+ }
97
+ }
98
+ #setAddCount(val) {
99
+ if (val !== this.#addCount) {
100
+ const oldState = this.editState;
101
+ const oldCount = this.#addCount;
102
+ this.#addCount = val;
103
+ this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
104
+ }
105
+ }
104
106
  #setStale(isStale) {
105
107
  if (isStale !== this.#isStale) {
106
108
  const oldState = this.editState;
@@ -108,13 +110,6 @@ class EditSession extends EventEmitter {
108
110
  this.#emitEditStateChange(oldState);
109
111
  }
110
112
  }
111
- #refreshEditCounts() {
112
- let editCount = 0;
113
- let invalidCount = 0;
114
- for (const { cellEdits } of this.#rowEdits.values())for (const cellEdit of cellEdits.values())if (cellEdit.isValid) editCount++;
115
- else invalidCount++;
116
- this.#setEditCounts(editCount, invalidCount);
117
- }
118
113
  get newRowState() {
119
114
  return this.#newRowState;
120
115
  }
@@ -236,20 +231,14 @@ class EditSession extends EventEmitter {
236
231
  this.emit("newRow", newRowState);
237
232
  }
238
233
  async deleteSelectedRows() {
239
- const response = await this.dataSource?.deleteSelectedRows?.(this.#deleteMode);
240
- if (isRpcError(response)) return;
241
- const deletedKeys = response?.data?.deletedKeys;
242
- if (deletedKeys && deletedKeys.length > 0) {
243
- let newCount = 0;
244
- for (const key of deletedKeys){
245
- this.#rowDeleteRevisions.set(key, ++this.#deleteRevision);
246
- if (!this.#deletedRows.has(key)) {
247
- this.#deletedRows.add(key);
248
- newCount++;
249
- }
250
- }
251
- if (newCount > 0) this.deleteCount = this.#deleteCount + newCount;
252
- }
234
+ const deleteSelectedRows = this.dataSource?.deleteSelectedRows;
235
+ if (void 0 === deleteSelectedRows) throw Error("[EditSession] datasource does not support deleting rows");
236
+ const selectedRowsCount = this.dataSource?.selectedRowsCount ?? 0;
237
+ const response = await deleteSelectedRows.call(this.dataSource, this.#deleteMode);
238
+ if (void 0 === response) throw Error("[EditSession] datasource returned no response when deleting rows");
239
+ if (isRpcError(response)) return response;
240
+ if (selectedRowsCount > 0) this.#setDeleteCount(this.#deleteCount + selectedRowsCount);
241
+ return response;
253
242
  }
254
243
  async addRow(rowData = {}) {
255
244
  const addRow = this.dataSource?.addRow;
@@ -259,54 +248,50 @@ class EditSession extends EventEmitter {
259
248
  ...rowData
260
249
  });
261
250
  if (void 0 === response) throw Error("[EditSession] datasource returned no response when adding row");
262
- if (!isRpcError(response)) this.addCount = this.#addCount + 1;
251
+ if (!isRpcError(response)) this.#setAddCount(this.#addCount + 1);
263
252
  return response;
264
253
  }
265
- restoreRows(keys) {
266
- for (const key of keys)if (this.#deletedRows.has(key)) {
267
- this.#deletedRows.delete(key);
268
- this.#rowDeleteRevisions.delete(key);
269
- this.deleteCount = this.#deleteCount - 1;
270
- }
271
- }
272
254
  hasRowChanges(key) {
273
- return this.#rowEdits.has(key) || this.#deletedRows.has(key);
255
+ return this.#rowEdits.has(key);
274
256
  }
275
257
  isCellEdited(key, columnName) {
276
258
  const cellEdit = this.#rowEdits.get(key)?.cellEdits.get(columnName);
277
259
  return cellEdit?.isValid === true && cellEdit.originalValue !== cellEdit.editedValue;
278
260
  }
279
- async undoRowChange(key) {
261
+ async undoRowChange(key, action) {
280
262
  if (!this.inEditMode) return;
281
263
  const undoRevision = this.#commitRevision;
282
- const deleteRevisionAtRequest = this.#rowDeleteRevisions.get(key);
283
264
  const rowEditsAtRequest = this.#rowEdits.get(key);
284
265
  const columnsAtRequest = new Set(rowEditsAtRequest?.cellEdits.keys());
285
- const wasDeleted = this.#deletedRows.has(key);
286
266
  const response = await this.dataSource?.undoRowChange?.(key);
287
- if (isRpcError(response)) return;
267
+ if (void 0 === response) return;
268
+ if (isRpcError(response)) return response;
269
+ const oldState = this.editState;
288
270
  const rowEdits = this.#rowEdits.get(key);
289
271
  if (rowEdits) {
290
272
  const changedColumns = [];
273
+ let removedValidCount = 0;
274
+ let removedInvalidCount = 0;
291
275
  for (const columnName of columnsAtRequest){
292
276
  const latestRevision = this.#cellCommitRevisions.get(key)?.get(columnName) ?? 0;
293
- if (!(latestRevision > undoRevision)) {
294
- if (this.isCellEdited(key, columnName)) changedColumns.push(columnName);
295
- rowEdits.cellEdits.delete(columnName);
296
- this.#setCellCommitRevision(key, columnName);
297
- }
277
+ if (latestRevision > undoRevision) continue;
278
+ const existing = rowEdits.cellEdits.get(columnName);
279
+ if (existing) if (existing.isValid) removedValidCount++;
280
+ else removedInvalidCount++;
281
+ if (this.isCellEdited(key, columnName)) changedColumns.push(columnName);
282
+ rowEdits.cellEdits.delete(columnName);
283
+ this.#setCellCommitRevision(key, columnName);
298
284
  }
299
285
  if (0 === rowEdits.cellEdits.size) this.#rowEdits.delete(key);
300
- this.#refreshEditCounts();
286
+ this.#editCount -= removedValidCount;
287
+ this.#invalidCount -= removedInvalidCount;
301
288
  for (const columnName of changedColumns)this.emit("cellEditChanged", key, columnName);
302
289
  }
303
- if (wasDeleted && this.#deletedRows.has(key) && this.#rowDeleteRevisions.get(key) === deleteRevisionAtRequest) {
304
- this.#deletedRows.delete(key);
305
- this.#rowDeleteRevisions.delete(key);
306
- this.deleteCount = this.#deleteCount - 1;
307
- }
308
- const wasInsertedRow = response?.data?.wasInsertedRow === true;
309
- if (wasInsertedRow) this.addCount = this.#addCount - 1;
290
+ if ("deleteRow" === action) this.#deleteCount--;
291
+ const wasInsertedRow = "addRow" === action || response?.data?.wasInsertedRow === true;
292
+ if (wasInsertedRow) this.#addCount--;
293
+ this.#emitEditStateChange(oldState);
294
+ return response;
310
295
  }
311
296
  #clearEdits() {
312
297
  const oldState = this.editState;
@@ -319,8 +304,6 @@ class EditSession extends EventEmitter {
319
304
  columnName
320
305
  ]));
321
306
  this.#rowEdits.clear();
322
- this.#deletedRows.clear();
323
- this.#rowDeleteRevisions.clear();
324
307
  this.#cellCommitRevisions.clear();
325
308
  this.#editCount = 0;
326
309
  this.#deleteCount = 0;
@@ -419,7 +402,8 @@ class EditSession extends EventEmitter {
419
402
  sessionDataSource
420
403
  });
421
404
  try {
422
- await this.dataSource?.endEditSession?.(saveChanges, force);
405
+ const response = await this.dataSource?.endEditSession?.(saveChanges, force);
406
+ if (isRpcError(response)) throw new Error(response.errorMessage);
423
407
  this.#clearEdits();
424
408
  this.#sessionDataSource = void 0;
425
409
  this.#setLifecycle({
@@ -463,9 +447,24 @@ class EditSession extends EventEmitter {
463
447
  editedValue,
464
448
  isValid
465
449
  };
466
- if (isValid && cellEdit.originalValue === editedValue) cellEdits.delete(column);
467
- else cellEdits.set(column, cellEdit);
468
- this.#refreshEditCounts();
450
+ let prevValidCount = 0;
451
+ let prevInvalidCount = 0;
452
+ if (existingCellEdit) if (existingCellEdit.isValid) prevValidCount = 1;
453
+ else prevInvalidCount = 1;
454
+ let nextValidCount = 0;
455
+ let nextInvalidCount = 0;
456
+ const isRevertedToOriginal = isValid && cellEdit.originalValue === editedValue;
457
+ if (isRevertedToOriginal) {
458
+ cellEdits.delete(column);
459
+ if (0 === cellEdits.size) this.#rowEdits.delete(key);
460
+ } else {
461
+ cellEdits.set(column, cellEdit);
462
+ if (isValid) nextValidCount = 1;
463
+ else nextInvalidCount = 1;
464
+ }
465
+ const editCountDiff = nextValidCount - prevValidCount;
466
+ const invalidCountDiff = nextInvalidCount - prevInvalidCount;
467
+ if (0 !== editCountDiff || 0 !== invalidCountDiff) this.#setEditCounts(this.#editCount + editCountDiff, this.#invalidCount + invalidCountDiff);
469
468
  if (wasEdited !== this.isCellEdited(key, column)) this.emit("cellEditChanged", key, column);
470
469
  return cellEdit;
471
470
  }
@@ -479,15 +478,21 @@ class EditSession extends EventEmitter {
479
478
  #isLatestCellCommit(key, columnName, revision) {
480
479
  return this.#cellCommitRevisions.get(key)?.get(columnName) === revision;
481
480
  }
482
- async commit(key, columnName, originalValue, typedValue, isValid) {
481
+ async commit(key, columnName, originalValue, typedValue, isValid, { dataSourceValue, syncToDataSource = true } = {}) {
483
482
  if ("active" !== this.#lifecycle.status && !("error" === this.#lifecycle.status && "end" === this.#lifecycle.operation)) throw new Error("No edit session in progress");
484
483
  const revision = this.#setCellCommitRevision(key, columnName);
485
484
  const rowEditDetails = this.#getOrCreateRowEdits(key);
486
485
  const { cellEdits } = rowEditDetails;
487
486
  if (isValid) {
488
487
  const cellEdit = this.#storeCellEdit(key, cellEdits, columnName, originalValue, typedValue, isValid);
489
- if (this.dataSource?.editCell) {
490
- const response = await this.dataSource.editCell(key, columnName, typedValue);
488
+ if (syncToDataSource && this.dataSource?.editCell) {
489
+ const valueForDataSource = dataSourceValue ?? typedValue;
490
+ if ("object" == typeof valueForDataSource && null !== valueForDataSource) throw new Error("Custom edit values require a scalar dataSourceValue.");
491
+ const response = await this.dataSource.editCell(key, columnName, valueForDataSource);
492
+ if (!this.inEditMode) return {
493
+ data: void 0,
494
+ type: "SUCCESS_RESULT"
495
+ };
491
496
  if (!this.#isLatestCellCommit(key, columnName, revision)) throw new SupersededEditError(`Edit response superseded for ${key}:${columnName}`);
492
497
  const currentCellEdits = this.#getOrCreateRowEdits(key).cellEdits;
493
498
  if (isRpcError(response)) this.#storeCellEdit(key, currentCellEdits, columnName, cellEdit.originalValue, typedValue, false);
@@ -20,7 +20,7 @@ const UndoCellRenderer = ({ column, dataRow })=>{
20
20
  children: /*#__PURE__*/ jsxs(Button, {
21
21
  appearance: "transparent",
22
22
  "aria-label": tooltipContent,
23
- onClick: ()=>editSession?.undoRowChange(dataRow.key),
23
+ onClick: ()=>editSession?.undoRowChange(dataRow.key, dataRow.vuuAction),
24
24
  style: {
25
25
  height: "100%",
26
26
  width: "100%"
@@ -0,0 +1,13 @@
1
+ const css = `
2
+ .vuuEditField {
3
+ & .saltDropdown {
4
+ --vuu-icon-height: 15px;
5
+ --vuu-icon-left: calc(100% - var(--salt-spacing-300));
6
+ --vuu-icon-size: 8px;
7
+ --vuu-icon-top: -3px;
8
+ }
9
+ }
10
+
11
+
12
+ `;
13
+ export default css;
@@ -0,0 +1,102 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Checkbox, Dropdown, FormField, FormFieldLabel, Input, Option } from "@salt-ds/core";
3
+ import { useCallback } from "react";
4
+ import { useEditField } from "./useEditField.js";
5
+ import { useEditSession } from "../DataEditingProvider.js";
6
+ import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils/src/data-utils";
7
+ import { useEditMode } from "../EditModeProvider.js";
8
+ import { useLookupValues } from "../lookup-values/useLookupValues.js";
9
+ import "./EditField.css";
10
+ const classBase = "vuuEditField";
11
+ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false, required, type, ...options })=>{
12
+ const editSession = useEditSession();
13
+ const { isEditMode } = useEditMode();
14
+ const lookupValues = useLookupValues({
15
+ enabled: "dropdown" === type,
16
+ ...options
17
+ });
18
+ const onEdit = useCallback(async (editState, editPhase)=>{
19
+ const { isValid = true, previousValue = "", value } = editState;
20
+ if ("commit" === editPhase && editSession) {
21
+ if (editSession.isNewRow(dataRow.key)) {
22
+ const isEmptyValue = "string" == typeof value && "" === value.trim();
23
+ if (!isValid && !isEmptyValue) return {
24
+ errorMessage: "Invalid value",
25
+ type: "ERROR_RESULT"
26
+ };
27
+ editSession.setNewRowValue(name, value);
28
+ if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
29
+ return {
30
+ data: void 0,
31
+ type: "SUCCESS_RESULT"
32
+ };
33
+ }
34
+ return editSession.commit(dataRow.key, name, previousValue, value, isValid);
35
+ }
36
+ if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
37
+ editSession.setNewRowValue(name, value);
38
+ return {
39
+ data: void 0,
40
+ type: "SUCCESS_RESULT"
41
+ };
42
+ }
43
+ }, [
44
+ dataRow,
45
+ deferNewRow,
46
+ editSession,
47
+ name
48
+ ]);
49
+ const onCommit = useCallback(()=>{
50
+ console.log("onCommit");
51
+ }, []);
52
+ const necessity = required ? "asterisk" : void 0;
53
+ const dataValue = dataRow?.[name] ?? "";
54
+ const column = {
55
+ label,
56
+ name,
57
+ serverDataType: "string"
58
+ };
59
+ const { editing, inputProps, onDropdownSelectionChange, onKeyDown, previousValue = "", value, warningMessage, ...editProps } = useEditField({
60
+ column,
61
+ textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
62
+ onEdit,
63
+ type: dataDescriptorTypeToVuuRowDataItemType(column),
64
+ value: dataValue
65
+ });
66
+ return /*#__PURE__*/ jsxs(FormField, {
67
+ className: classBase,
68
+ "data-field": name,
69
+ necessity: necessity,
70
+ children: [
71
+ /*#__PURE__*/ jsx(FormFieldLabel, {
72
+ children: label
73
+ }),
74
+ false === isEditMode ? /*#__PURE__*/ jsx(Input, {
75
+ bordered: true,
76
+ inputProps: {
77
+ readOnly: true
78
+ },
79
+ value: String(dataValue)
80
+ }) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
81
+ checked: Boolean(dataValue),
82
+ onChange: onCommit
83
+ }) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
84
+ "data-icon": "triangle-down",
85
+ onSelectionChange: onDropdownSelectionChange,
86
+ placeholder: "Please select value",
87
+ value: value,
88
+ children: lookupValues.map((listOption)=>/*#__PURE__*/ jsx(Option, {
89
+ value: listOption,
90
+ children: listOption.label
91
+ }, listOption.value))
92
+ }) : /*#__PURE__*/ jsx(Input, {
93
+ ...editProps,
94
+ bordered: true,
95
+ onKeyDown: onKeyDown,
96
+ readOnly: readOnly,
97
+ value: value
98
+ })
99
+ ]
100
+ });
101
+ };
102
+ export { EditField };
@@ -0,0 +1,223 @@
1
+ import { DataValidationError, dispatchCustomEvent, getTypedValue, isRpcError, isRpcSuccess } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ const stringValueOf = (value)=>value?.toString() ?? "";
4
+ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
5
+ const [editState, setEditState] = useState({
6
+ editing: false,
7
+ value: stringValueOf(value)
8
+ });
9
+ const initialValueRef = useRef(value?.toString() ?? "");
10
+ const isDirtyRef = useRef(false);
11
+ useEffect(()=>{
12
+ const nextValue = stringValueOf(value);
13
+ if (initialValueRef.current !== nextValue) {
14
+ initialValueRef.current = nextValue;
15
+ isDirtyRef.current = false;
16
+ setEditState((editState)=>({
17
+ ...editState,
18
+ message: void 0,
19
+ value: nextValue
20
+ }));
21
+ }
22
+ }, [
23
+ value
24
+ ]);
25
+ const commit = useCallback(async (value = editState.value)=>{
26
+ const result = column.clientSideEditValidationCheck?.(value, "*");
27
+ if (result?.ok === false) setEditState((state)=>({
28
+ ...state,
29
+ message: result?.messages?.join(",")
30
+ }));
31
+ else {
32
+ const { current: initialValue } = initialValueRef;
33
+ const previousValue = getTypedValue(initialValue, type);
34
+ try {
35
+ const typedValue = getTypedValue(value, type, true);
36
+ const response = await onEdit?.({
37
+ editType: "commit",
38
+ isValid: true,
39
+ previousValue,
40
+ value: typedValue
41
+ }, "commit");
42
+ if (isRpcSuccess(response)) {
43
+ isDirtyRef.current = false;
44
+ setEditState((state)=>({
45
+ editing: false,
46
+ previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
47
+ value
48
+ }));
49
+ } else if (isRpcError(response)) {
50
+ setEditState((state)=>({
51
+ ...state,
52
+ message: response.errorMessage
53
+ }));
54
+ return false;
55
+ }
56
+ initialValueRef.current = value;
57
+ return true;
58
+ } catch (e) {
59
+ if (e instanceof DataValidationError) {
60
+ const { actualType, expectedType, message } = e;
61
+ if (column) {
62
+ const { name, label = name } = column;
63
+ const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
64
+ setEditState((state)=>({
65
+ ...state,
66
+ message
67
+ }));
68
+ onEdit?.({
69
+ editType: "commit",
70
+ isValid: false,
71
+ previousValue,
72
+ value
73
+ }, "commit");
74
+ } else setEditState((state)=>({
75
+ ...state,
76
+ message
77
+ }));
78
+ }
79
+ }
80
+ }
81
+ return false;
82
+ }, [
83
+ column,
84
+ editState,
85
+ onEdit,
86
+ type
87
+ ]);
88
+ const toggleActivation = useCallback(async (input, cancel = false)=>{
89
+ if (editState.editing) if (isDirtyRef.current) if (cancel) {
90
+ const { value: previousValue } = editState;
91
+ isDirtyRef.current = false;
92
+ setEditState({
93
+ editing: false,
94
+ value: initialValueRef.current,
95
+ message: void 0
96
+ });
97
+ onEdit?.({
98
+ editType: "cancel",
99
+ isValid: true,
100
+ previousValue,
101
+ value: getTypedValue(initialValueRef.current, type, true)
102
+ }, "commit");
103
+ } else {
104
+ const commitSuccessful = await commit();
105
+ if (commitSuccessful) {
106
+ setEditState((editState)=>({
107
+ ...editState,
108
+ editing: false
109
+ }));
110
+ dispatchCustomEvent(input, "vuu-commit");
111
+ }
112
+ }
113
+ else setEditState((editState)=>({
114
+ ...editState,
115
+ editing: false
116
+ }));
117
+ else if (!cancel) {
118
+ setEditState((editState)=>({
119
+ ...editState,
120
+ editing: true
121
+ }));
122
+ input.select();
123
+ }
124
+ }, [
125
+ commit,
126
+ editState,
127
+ onEdit,
128
+ type
129
+ ]);
130
+ const handleKeyDown = useCallback(async (evt)=>{
131
+ const { key, target } = evt;
132
+ const input = target;
133
+ if ("Enter" === key) toggleActivation(input);
134
+ else if ("ArrowRight" === key || "ArrowLeft" === key || "ArrowUp" === key || "ArrowDown" === key) {
135
+ if (editState.editing) evt.stopPropagation();
136
+ } else if ("Escape" === evt.key) toggleActivation(input, true);
137
+ }, [
138
+ editState,
139
+ toggleActivation
140
+ ]);
141
+ const handleFocus = useCallback((e)=>{
142
+ const input = e.target;
143
+ const source = e.relatedTarget;
144
+ if (source?.classList.contains("vuuTableCell") && source.contains(input)) toggleActivation(input);
145
+ }, [
146
+ toggleActivation
147
+ ]);
148
+ const handleBlur = useCallback(async ()=>{
149
+ if (editState.editing) {
150
+ if (isDirtyRef.current) {
151
+ const commitSuccessful = await commit();
152
+ console.log({
153
+ commitSuccessful
154
+ });
155
+ }
156
+ setEditState((editState)=>({
157
+ ...editState,
158
+ editing: false
159
+ }));
160
+ }
161
+ }, [
162
+ commit,
163
+ editState
164
+ ]);
165
+ const handleChange = useCallback((evt)=>{
166
+ const { value } = evt.target;
167
+ isDirtyRef.current = value !== initialValueRef.current;
168
+ const result = column.clientSideEditValidationCheck?.(value, "change");
169
+ setEditState({
170
+ editing: true,
171
+ value
172
+ });
173
+ onEdit?.({
174
+ editType: "change",
175
+ isValid: result?.ok !== false,
176
+ previousValue: initialValueRef.current,
177
+ value
178
+ }, "change");
179
+ if (result?.ok === false) setEditState({
180
+ editing: true,
181
+ value,
182
+ message: result.messages?.join(",")
183
+ });
184
+ }, [
185
+ column,
186
+ onEdit
187
+ ]);
188
+ const handleDropdownSelectionChange = useCallback((_evt, [selectedOption])=>{
189
+ if (!selectedOption) return;
190
+ const value = String(selectedOption.value);
191
+ isDirtyRef.current = value !== initialValueRef.current;
192
+ setEditState((state)=>({
193
+ ...state,
194
+ editing: true,
195
+ message: void 0,
196
+ value
197
+ }));
198
+ commit(value).then((committed)=>{
199
+ if (committed) setEditState((state)=>({
200
+ ...state,
201
+ editing: false,
202
+ value
203
+ }));
204
+ });
205
+ }, [
206
+ commit
207
+ ]);
208
+ return {
209
+ editing: editState.editing,
210
+ inputProps: {
211
+ onBlur: handleBlur,
212
+ onFocus: handleFocus,
213
+ type: textType
214
+ },
215
+ onKeyDown: handleKeyDown,
216
+ previousValue: editState.previousValue,
217
+ value: editState.value,
218
+ warningMessage: editState.message,
219
+ onChange: handleChange,
220
+ onDropdownSelectionChange: handleDropdownSelectionChange
221
+ };
222
+ };
223
+ export { useEditField };
package/src/index.js CHANGED
@@ -1,10 +1,15 @@
1
+ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
1
2
  export { DataEditingProvider, useEditSession } from "./DataEditingProvider.js";
2
- export { useCellEdited } from "./useCellEdited.js";
3
+ export { EditField } from "./edit-field/EditField.js";
4
+ export { useEditField } from "./edit-field/useEditField.js";
5
+ export { lookupOptionsFromRows, useLookupValues } from "./lookup-values/useLookupValues.js";
3
6
  export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
7
+ export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
4
8
  export { EditButtons } from "./EditButtons.js";
5
9
  export { EditModeProvider, useEditMode } from "./EditModeProvider.js";
6
10
  export { EditError, EditSession, SupersededEditError } from "./EditSession.js";
7
- export { StaleUpdateError } from "@vuu-ui/vuu-utils";
8
- export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
9
11
  export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
12
+ export { useCellEdited } from "./useCellEdited.js";
13
+ export { useEditable } from "./useEditable.js";
10
14
  export { useEditableTable } from "./useEditableTable.js";
15
+ export { useEditState } from "./useEditState.js";
@@ -0,0 +1,78 @@
1
+ import { Range, useData } from "@vuu-ui/vuu-utils";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ const DATA_ROW_COLUMN_OFFSET = 10;
4
+ const lookupOptionsFromRows = (columns, rows, optionMap)=>{
5
+ const requestedColumns = [
6
+ optionMap.value,
7
+ optionMap.label,
8
+ ...optionMap.additionalFields ?? []
9
+ ];
10
+ const missingColumns = requestedColumns.filter((column)=>!columns.includes(column));
11
+ if (missingColumns.length > 0) throw Error(`[useLookupValues] lookup columns not found: ${requestedColumns.join(", ")}`);
12
+ return rows.map((row)=>{
13
+ const metadata = Object.fromEntries((optionMap.additionalFields ?? []).map((field)=>[
14
+ field,
15
+ String(row[columns.indexOf(field) + DATA_ROW_COLUMN_OFFSET])
16
+ ]));
17
+ return {
18
+ value: String(row[columns.indexOf(optionMap.value) + DATA_ROW_COLUMN_OFFSET]),
19
+ label: String(row[columns.indexOf(optionMap.label) + DATA_ROW_COLUMN_OFFSET]),
20
+ ...Object.keys(metadata).length > 0 ? {
21
+ metadata
22
+ } : {}
23
+ };
24
+ });
25
+ };
26
+ const useLookupValues = ({ enabled, optionMap, table })=>{
27
+ if (enabled && (void 0 === optionMap || void 0 === table)) throw Error("[useLookupValues] optionsMap and Table must be provided");
28
+ const { VuuDataSource } = useData();
29
+ const [options, setOptions] = useState([]);
30
+ const lookupColumns = useMemo(()=>optionMap ? [
31
+ optionMap.value,
32
+ optionMap.label,
33
+ ...optionMap.additionalFields ?? []
34
+ ] : [], [
35
+ optionMap
36
+ ]);
37
+ const dataSource = useMemo(()=>enabled && optionMap && table ? new VuuDataSource({
38
+ columns: lookupColumns,
39
+ table
40
+ }) : void 0, [
41
+ VuuDataSource,
42
+ enabled,
43
+ lookupColumns,
44
+ optionMap,
45
+ table
46
+ ]);
47
+ useEffect(()=>{
48
+ setOptions([]);
49
+ if (!dataSource || !optionMap) return;
50
+ let active = true;
51
+ let columns;
52
+ const subscribe = (message)=>{
53
+ if (!active) return;
54
+ if ("subscribed" === message.type) {
55
+ const schemaColumns = new Set(message.tableSchema.columns.map(({ name })=>name));
56
+ if (!lookupColumns.every((column)=>message.columns.includes(column) && schemaColumns.has(column))) throw Error(`[useLookupValues] lookup columns not found: ${lookupColumns.join(", ")}`);
57
+ columns = message.columns;
58
+ } else if ("viewport-update" === message.type && columns) setOptions(lookupOptionsFromRows(columns, message.rows ?? [], optionMap));
59
+ else if ("subscribe-failed" === message.type) console.error(`Lookup table ${table?.table} subscription failed: ${message.msg}`);
60
+ };
61
+ dataSource.subscribe({
62
+ range: Range(0, 100)
63
+ }, subscribe).catch((cause)=>{
64
+ if (active) console.error("Role client dropdown subscription failed:", cause);
65
+ });
66
+ return ()=>{
67
+ active = false;
68
+ dataSource.unsubscribe();
69
+ };
70
+ }, [
71
+ dataSource,
72
+ lookupColumns,
73
+ optionMap,
74
+ table
75
+ ]);
76
+ return options;
77
+ };
78
+ export { lookupOptionsFromRows, useLookupValues };
@@ -0,0 +1,15 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ function useEditState(editSession) {
3
+ const subscribe = useCallback((onStoreChange)=>{
4
+ if (!editSession) return ()=>void 0;
5
+ editSession.on("editState", onStoreChange);
6
+ return ()=>editSession.removeListener("editState", onStoreChange);
7
+ }, [
8
+ editSession
9
+ ]);
10
+ const getSnapshot = useCallback(()=>editSession?.editState ?? "clean", [
11
+ editSession
12
+ ]);
13
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
14
+ }
15
+ export { useEditState };
@@ -0,0 +1,52 @@
1
+ import { EditSession, useEditMode } from "./index.js";
2
+ import { useCallback, useEffect, useMemo } from "react";
3
+ const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=>{
4
+ const { isEditMode } = useEditMode();
5
+ const editSession = useMemo(()=>new EditSession({
6
+ dataSource
7
+ }), [
8
+ dataSource
9
+ ]);
10
+ const handleCancel = useCallback(async ()=>{
11
+ try {
12
+ await editSession.end();
13
+ onCancel();
14
+ } catch (error) {
15
+ console.error("[useEditableTable] cancel edit session failed", error);
16
+ }
17
+ }, [
18
+ editSession,
19
+ onCancel
20
+ ]);
21
+ const handleSave = useCallback(async (force = false)=>{
22
+ try {
23
+ await editSession.end(true, force);
24
+ onSave();
25
+ } catch (error) {
26
+ console.error("[useEditableTable] save edit session failed", error);
27
+ }
28
+ }, [
29
+ editSession,
30
+ onSave
31
+ ]);
32
+ useEffect(()=>{
33
+ const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
34
+ transition.catch((error)=>{
35
+ if (isEditMode) {
36
+ console.error("[useEditableTable] begin edit session failed", error);
37
+ handleCancel();
38
+ } else console.error("[useEditableTable] end edit session failed", error);
39
+ });
40
+ }, [
41
+ copyOption,
42
+ editSession,
43
+ isEditMode,
44
+ handleCancel
45
+ ]);
46
+ return {
47
+ editSession,
48
+ onCancel: handleCancel,
49
+ onSave: handleSave
50
+ };
51
+ };
52
+ export { useEditable };
@@ -74,7 +74,7 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
74
74
  }, [
75
75
  editSession
76
76
  ]);
77
- const handleUndoRowChange = useCallback((key)=>void editSession.undoRowChange(key), [
77
+ const handleUndoRowChange = useCallback((key, action)=>void editSession.undoRowChange(key, action), [
78
78
  editSession
79
79
  ]);
80
80
  useEffect(()=>{
@@ -11,4 +11,4 @@ export interface EditButtonProps {
11
11
  confirmSave?: () => boolean | Promise<boolean>;
12
12
  confirmCancel?: () => boolean | Promise<boolean>;
13
13
  }
14
- export declare const EditButtons: ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
14
+ export declare const EditButtons: ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  export interface EditModeContextProps {
3
3
  isEditMode: boolean;
4
4
  setEditMode: (inEditMode: boolean) => void;
@@ -7,7 +7,8 @@ export interface EditModeContextProps {
7
7
  * Implemented as a standalone Provider so that EditMode cna be implemented
8
8
  * at higher level than individual edit controls.
9
9
  */
10
- export declare const EditModeProvider: ({ children }: {
10
+ export declare const EditModeProvider: ({ children, isEditMode: isEditModeProp, }: {
11
11
  children: ReactNode;
12
+ isEditMode?: boolean;
12
13
  }) => import("react").JSX.Element;
13
14
  export declare const useEditMode: () => EditModeContextProps;
@@ -1,12 +1,18 @@
1
- import type { CopyOption, DataSource, DeleteRowMode, EditApi, SessionType } from "@vuu-ui/vuu-data-types";
1
+ import type { CopyOption, DataSource, DeleteRowMode, SessionType } from "@vuu-ui/vuu-data-types";
2
2
  import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
3
  import { EventEmitter } from "@vuu-ui/vuu-utils";
4
4
  export type EditState = "clean" | "dirty" | "invalid" | "stale";
5
+ export type EditActionType = "deleteRow" | "addRow" | "editCell";
6
+ export type EditSessionValue = VuuRowDataItemType | object;
7
+ export type EditCommitOptions = {
8
+ dataSourceValue?: VuuRowDataItemType;
9
+ syncToDataSource?: boolean;
10
+ };
5
11
  /** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
6
12
  export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
7
13
  export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
8
14
  export type EditSessionConstructorProps = {
9
- dataSource: EditApi;
15
+ dataSource: DataSource;
10
16
  /** @default "soft" */
11
17
  deleteMode?: DeleteRowMode;
12
18
  /** @default "createSessionDataSource" */
@@ -53,14 +59,13 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
53
59
  static readonly newRowKey = "__vuu_new_row__";
54
60
  constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
55
61
  get editCount(): number;
56
- set editCount(val: number);
57
62
  get invalidCount(): number;
58
- set invalidCount(val: number);
59
63
  get deleteCount(): number;
60
- set deleteCount(val: number);
61
64
  get addCount(): number;
62
- set addCount(val: number);
63
65
  get editState(): EditState;
66
+ get isDirty(): boolean;
67
+ get canCancel(): boolean;
68
+ get canSave(): boolean;
64
69
  get newRowState(): NewRowState;
65
70
  isNewRow(key: string): key is "__vuu_new_row__";
66
71
  isNewRowFinalColumn(columnName: string): boolean;
@@ -68,23 +73,22 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
68
73
  configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
69
74
  setNewRowValue(column: string, value: VuuRowDataItemType): void;
70
75
  addNewRow(): Promise<RpcResult>;
71
- deleteSelectedRows(): Promise<void>;
76
+ deleteSelectedRows(): Promise<RpcResult>;
72
77
  addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
73
- restoreRows(keys: string[]): void;
74
78
  hasRowChanges(key: string): boolean;
75
79
  isCellEdited(key: string, columnName: string): boolean;
76
- undoRowChange(key: string): Promise<void>;
80
+ undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
77
81
  begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
78
82
  /**
79
83
  * Safe to call repeatedly - schema of a remote session table typically arrives
80
84
  * after begin() resolves, so callers re-invoke this once 'subscribed' fires.
81
85
  */
82
86
  reconcileWithSessionSchema(): void;
83
- get dataSource(): EditApi<import("@vuu-ui/vuu-data-types").DataSourceRow<VuuRowDataItemType>> | undefined;
87
+ get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
84
88
  get sessionDataSource(): DataSource | undefined;
85
89
  end(saveChanges?: boolean, force?: boolean): Promise<void>;
86
90
  get lifecycle(): EditLifecycle;
87
91
  get inEditMode(): boolean;
88
- commit(key: string, columnName: string, originalValue: VuuRowDataItemType, typedValue: string | number | boolean, isValid: boolean): Promise<RpcResult>;
92
+ commit(key: string, columnName: string, originalValue: EditSessionValue, typedValue: EditSessionValue, isValid: boolean, { dataSourceValue, syncToDataSource }?: EditCommitOptions): Promise<RpcResult>;
89
93
  }
90
94
  export {};
@@ -0,0 +1,21 @@
1
+ import type { DataRow } from "@vuu-ui/vuu-table-types";
2
+ import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
+ import { type OptionMap } from "../lookup-values/useLookupValues";
4
+ import "./EditField.css";
5
+ export type TextFieldType = "email" | "password";
6
+ export type EditFieldType = "checkbox" | "dropdown" | TextFieldType;
7
+ export interface EditFieldProps {
8
+ dataRow: DataRow;
9
+ deferNewRow?: boolean;
10
+ label: string;
11
+ name: string;
12
+ readOnly?: boolean;
13
+ required?: boolean;
14
+ type?: EditFieldType;
15
+ }
16
+ export interface DropdownEditFieldProps extends EditFieldProps {
17
+ type: "dropdown";
18
+ optionMap: OptionMap;
19
+ table: VuuTable;
20
+ }
21
+ export declare const EditField: ({ dataRow, deferNewRow, label, name, readOnly, required, type, ...options }: EditFieldProps | DropdownEditFieldProps) => import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
+ import type { RuntimeColumnDescriptor, TableCellEditHandler } from "@vuu-ui/vuu-table-types";
3
+ import { type ChangeEventHandler, type FocusEventHandler, type KeyboardEvent, type SyntheticEvent } from "react";
4
+ import type { LookupOption } from "../lookup-values/useLookupValues";
5
+ import type { TextFieldType } from "./EditField";
6
+ export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataItemType> {
7
+ column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name">;
8
+ value?: T;
9
+ onEdit?: TableCellEditHandler;
10
+ textType?: TextFieldType;
11
+ type?: "string" | "number" | "boolean";
12
+ }
13
+ export declare const useEditField: <T extends string | number | boolean = string>({ column, value, onEdit, textType, type, }: EditFieldHookProps<T>) => {
14
+ editing: boolean;
15
+ inputProps: {
16
+ onBlur: FocusEventHandler<HTMLElement>;
17
+ onFocus: FocusEventHandler<HTMLElement>;
18
+ type: TextFieldType | undefined;
19
+ };
20
+ onKeyDown: (evt: KeyboardEvent<HTMLElement>) => Promise<void>;
21
+ previousValue: string | undefined;
22
+ value: string;
23
+ warningMessage: string | undefined;
24
+ onChange: ChangeEventHandler;
25
+ onDropdownSelectionChange: (_evt: SyntheticEvent, [selectedOption]: LookupOption[]) => void;
26
+ };
package/types/index.d.ts CHANGED
@@ -1,10 +1,15 @@
1
+ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
1
2
  export { DataEditingProvider, useEditSession } from "./DataEditingProvider";
2
- export { useCellEdited } from "./useCellEdited";
3
- export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
3
+ export { EditField } from './edit-field/EditField';
4
+ export { useEditField } from './edit-field/useEditField';
5
+ export { lookupOptionsFromRows, useLookupValues, type LookupOption, type LookupValuedHookProps, type OptionMap, } from "./lookup-values/useLookupValues";
6
+ export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils";
7
+ export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator";
4
8
  export { EditButtons, type EditButtonProps } from "./EditButtons";
5
- export { EditModeProvider, useEditMode, type EditModeContextProps, } from "./EditModeProvider";
6
- export { EditError, EditSession, SupersededEditError, type EditLifecycle, type EditSessionConstructorProps, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
7
- export { StaleUpdateError } from "@vuu-ui/vuu-utils";
8
- export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
9
- export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
10
- export { useEditableTable, type EditableTableHookProps, type EditMode, } from "./useEditableTable";
9
+ export { EditModeProvider, useEditMode, type EditModeContextProps } from "./EditModeProvider";
10
+ export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type EditState, type NewRowState, type RowDefaultDataItemValues } from "./EditSession";
11
+ export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps } from "./UndoCellRenderer";
12
+ export { useCellEdited } from "./useCellEdited";
13
+ export { useEditable } from './useEditable';
14
+ export { useEditableTable, type EditableTableHookProps, type EditMode } from "./useEditableTable";
15
+ export { useEditState } from "./useEditState";
@@ -0,0 +1,19 @@
1
+ import type { DataSourceRow } from "@vuu-ui/vuu-data-types";
2
+ import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
+ export type LookupOption = {
4
+ label: string;
5
+ metadata?: Record<string, string>;
6
+ value: number | string;
7
+ };
8
+ export type OptionMap = {
9
+ additionalFields?: readonly string[];
10
+ label: string;
11
+ value: string;
12
+ };
13
+ export interface LookupValuedHookProps {
14
+ enabled: boolean;
15
+ optionMap?: OptionMap;
16
+ table?: VuuTable;
17
+ }
18
+ export declare const lookupOptionsFromRows: (columns: string[], rows: DataSourceRow[], optionMap: OptionMap) => LookupOption[];
19
+ export declare const useLookupValues: ({ enabled, optionMap, table, }: LookupValuedHookProps) => LookupOption[];
@@ -0,0 +1,2 @@
1
+ import type { EditSession, EditState } from "./EditSession";
2
+ export declare function useEditState(editSession: EditSession | undefined): EditState;
@@ -0,0 +1,13 @@
1
+ import type { CopyOption, DataSource } from "@vuu-ui/vuu-data-types";
2
+ import { EditSession } from ".";
3
+ export interface EditableHookProps {
4
+ copyOption?: CopyOption;
5
+ dataSource: DataSource;
6
+ onCancel: () => void;
7
+ onSave: () => void;
8
+ }
9
+ export declare const useEditable: ({ copyOption, dataSource, onCancel, onSave }: EditableHookProps) => {
10
+ editSession: EditSession;
11
+ onCancel: () => Promise<void>;
12
+ onSave: (force?: boolean) => Promise<void>;
13
+ };
@@ -1,6 +1,6 @@
1
1
  import type { CopyOption, DataSource, DeleteRowMode } from "@vuu-ui/vuu-data-types";
2
2
  import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
- import { EditSession, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
3
+ import { EditSession, type EditActionType, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
4
4
  export type EditMode = "edit" | "view";
5
5
  export interface EditableTableHookProps {
6
6
  /**
@@ -37,7 +37,7 @@ export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, d
37
37
  onCancel: () => Promise<void>;
38
38
  onDelete: () => Promise<void>;
39
39
  onSave: (force?: boolean) => Promise<void>;
40
- onUndoRowChange: (key: string) => undefined;
40
+ onUndoRowChange: (key: string, action: EditActionType) => undefined;
41
41
  rowClassNameGenerators: string[] | undefined;
42
42
  sessionDataSource: import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
43
43
  sourceDataSource: DataSource;