@vuu-ui/vuu-data-editing 3.3.7 → 3.3.9

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": "3.3.7",
2
+ "version": "3.3.9",
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.54.1",
15
- "@vuu-ui/vuu-ui-controls": "3.3.7",
16
- "@vuu-ui/vuu-utils": "3.3.7"
15
+ "@vuu-ui/vuu-ui-controls": "3.3.9",
16
+ "@vuu-ui/vuu-utils": "3.3.9"
17
17
  },
18
18
  "devDependencies": {
19
- "@vuu-ui/vuu-data-types": "3.3.7",
20
- "@vuu-ui/vuu-protocol-types": "3.3.7",
21
- "@vuu-ui/vuu-table-types": "3.3.7"
19
+ "@vuu-ui/vuu-data-types": "3.3.9",
20
+ "@vuu-ui/vuu-protocol-types": "3.3.9",
21
+ "@vuu-ui/vuu-table-types": "3.3.9"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19.2.3"
@@ -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,42 +45,22 @@ 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";
@@ -101,6 +78,22 @@ class EditSession extends EventEmitter {
101
78
  this.#emitEditStateChange(oldState);
102
79
  }
103
80
  }
81
+ #setDeleteCount(val) {
82
+ if (val !== this.#deleteCount) {
83
+ const oldState = this.editState;
84
+ const oldCount = this.#deleteCount;
85
+ this.#deleteCount = val;
86
+ this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
87
+ }
88
+ }
89
+ #setAddCount(val) {
90
+ if (val !== this.#addCount) {
91
+ const oldState = this.editState;
92
+ const oldCount = this.#addCount;
93
+ this.#addCount = val;
94
+ this.#emitEditStateChange(oldState, 0 === oldCount || 0 === val);
95
+ }
96
+ }
104
97
  #setStale(isStale) {
105
98
  if (isStale !== this.#isStale) {
106
99
  const oldState = this.editState;
@@ -108,13 +101,6 @@ class EditSession extends EventEmitter {
108
101
  this.#emitEditStateChange(oldState);
109
102
  }
110
103
  }
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
104
  get newRowState() {
119
105
  return this.#newRowState;
120
106
  }
@@ -236,20 +222,14 @@ class EditSession extends EventEmitter {
236
222
  this.emit("newRow", newRowState);
237
223
  }
238
224
  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
- }
225
+ const deleteSelectedRows = this.dataSource?.deleteSelectedRows;
226
+ if (void 0 === deleteSelectedRows) throw Error("[EditSession] datasource does not support deleting rows");
227
+ const selectedRowsCount = this.dataSource?.selectedRowsCount ?? 0;
228
+ const response = await deleteSelectedRows.call(this.dataSource, this.#deleteMode);
229
+ if (void 0 === response) throw Error("[EditSession] datasource returned no response when deleting rows");
230
+ if (isRpcError(response)) return response;
231
+ if (selectedRowsCount > 0) this.#setDeleteCount(this.#deleteCount + selectedRowsCount);
232
+ return response;
253
233
  }
254
234
  async addRow(rowData = {}) {
255
235
  const addRow = this.dataSource?.addRow;
@@ -259,54 +239,50 @@ class EditSession extends EventEmitter {
259
239
  ...rowData
260
240
  });
261
241
  if (void 0 === response) throw Error("[EditSession] datasource returned no response when adding row");
262
- if (!isRpcError(response)) this.addCount = this.#addCount + 1;
242
+ if (!isRpcError(response)) this.#setAddCount(this.#addCount + 1);
263
243
  return response;
264
244
  }
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
245
  hasRowChanges(key) {
273
- return this.#rowEdits.has(key) || this.#deletedRows.has(key);
246
+ return this.#rowEdits.has(key);
274
247
  }
275
248
  isCellEdited(key, columnName) {
276
249
  const cellEdit = this.#rowEdits.get(key)?.cellEdits.get(columnName);
277
250
  return cellEdit?.isValid === true && cellEdit.originalValue !== cellEdit.editedValue;
278
251
  }
279
- async undoRowChange(key) {
252
+ async undoRowChange(key, action) {
280
253
  if (!this.inEditMode) return;
281
254
  const undoRevision = this.#commitRevision;
282
- const deleteRevisionAtRequest = this.#rowDeleteRevisions.get(key);
283
255
  const rowEditsAtRequest = this.#rowEdits.get(key);
284
256
  const columnsAtRequest = new Set(rowEditsAtRequest?.cellEdits.keys());
285
- const wasDeleted = this.#deletedRows.has(key);
286
257
  const response = await this.dataSource?.undoRowChange?.(key);
287
- if (isRpcError(response)) return;
258
+ if (void 0 === response) return;
259
+ if (isRpcError(response)) return response;
260
+ const oldState = this.editState;
288
261
  const rowEdits = this.#rowEdits.get(key);
289
262
  if (rowEdits) {
290
263
  const changedColumns = [];
264
+ let removedValidCount = 0;
265
+ let removedInvalidCount = 0;
291
266
  for (const columnName of columnsAtRequest){
292
267
  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
- }
268
+ if (latestRevision > undoRevision) continue;
269
+ const existing = rowEdits.cellEdits.get(columnName);
270
+ if (existing) if (existing.isValid) removedValidCount++;
271
+ else removedInvalidCount++;
272
+ if (this.isCellEdited(key, columnName)) changedColumns.push(columnName);
273
+ rowEdits.cellEdits.delete(columnName);
274
+ this.#setCellCommitRevision(key, columnName);
298
275
  }
299
276
  if (0 === rowEdits.cellEdits.size) this.#rowEdits.delete(key);
300
- this.#refreshEditCounts();
277
+ this.#editCount -= removedValidCount;
278
+ this.#invalidCount -= removedInvalidCount;
301
279
  for (const columnName of changedColumns)this.emit("cellEditChanged", key, columnName);
302
280
  }
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;
281
+ if ("deleteRow" === action) this.#deleteCount--;
282
+ const wasInsertedRow = "addRow" === action || response?.data?.wasInsertedRow === true;
283
+ if (wasInsertedRow) this.#addCount--;
284
+ this.#emitEditStateChange(oldState);
285
+ return response;
310
286
  }
311
287
  #clearEdits() {
312
288
  const oldState = this.editState;
@@ -319,8 +295,6 @@ class EditSession extends EventEmitter {
319
295
  columnName
320
296
  ]));
321
297
  this.#rowEdits.clear();
322
- this.#deletedRows.clear();
323
- this.#rowDeleteRevisions.clear();
324
298
  this.#cellCommitRevisions.clear();
325
299
  this.#editCount = 0;
326
300
  this.#deleteCount = 0;
@@ -463,9 +437,24 @@ class EditSession extends EventEmitter {
463
437
  editedValue,
464
438
  isValid
465
439
  };
466
- if (isValid && cellEdit.originalValue === editedValue) cellEdits.delete(column);
467
- else cellEdits.set(column, cellEdit);
468
- this.#refreshEditCounts();
440
+ let prevValidCount = 0;
441
+ let prevInvalidCount = 0;
442
+ if (existingCellEdit) if (existingCellEdit.isValid) prevValidCount = 1;
443
+ else prevInvalidCount = 1;
444
+ let nextValidCount = 0;
445
+ let nextInvalidCount = 0;
446
+ const isRevertedToOriginal = isValid && cellEdit.originalValue === editedValue;
447
+ if (isRevertedToOriginal) {
448
+ cellEdits.delete(column);
449
+ if (0 === cellEdits.size) this.#rowEdits.delete(key);
450
+ } else {
451
+ cellEdits.set(column, cellEdit);
452
+ if (isValid) nextValidCount = 1;
453
+ else nextInvalidCount = 1;
454
+ }
455
+ const editCountDiff = nextValidCount - prevValidCount;
456
+ const invalidCountDiff = nextInvalidCount - prevInvalidCount;
457
+ if (0 !== editCountDiff || 0 !== invalidCountDiff) this.#setEditCounts(this.#editCount + editCountDiff, this.#invalidCount + invalidCountDiff);
469
458
  if (wasEdited !== this.isCellEdited(key, column)) this.emit("cellEditChanged", key, column);
470
459
  return cellEdit;
471
460
  }
@@ -488,6 +477,10 @@ class EditSession extends EventEmitter {
488
477
  const cellEdit = this.#storeCellEdit(key, cellEdits, columnName, originalValue, typedValue, isValid);
489
478
  if (this.dataSource?.editCell) {
490
479
  const response = await this.dataSource.editCell(key, columnName, typedValue);
480
+ if (!this.inEditMode) return {
481
+ data: void 0,
482
+ type: "SUCCESS_RESULT"
483
+ };
491
484
  if (!this.#isLatestCellCommit(key, columnName, revision)) throw new SupersededEditError(`Edit response superseded for ${key}:${columnName}`);
492
485
  const currentCellEdits = this.#getOrCreateRowEdits(key).cellEdits;
493
486
  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%"
@@ -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(()=>{
@@ -1,12 +1,13 @@
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";
5
6
  /** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
6
7
  export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
7
8
  export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
8
9
  export type EditSessionConstructorProps = {
9
- dataSource: EditApi;
10
+ dataSource: DataSource;
10
11
  /** @default "soft" */
11
12
  deleteMode?: DeleteRowMode;
12
13
  /** @default "createSessionDataSource" */
@@ -53,13 +54,9 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
53
54
  static readonly newRowKey = "__vuu_new_row__";
54
55
  constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
55
56
  get editCount(): number;
56
- set editCount(val: number);
57
57
  get invalidCount(): number;
58
- set invalidCount(val: number);
59
58
  get deleteCount(): number;
60
- set deleteCount(val: number);
61
59
  get addCount(): number;
62
- set addCount(val: number);
63
60
  get editState(): EditState;
64
61
  get newRowState(): NewRowState;
65
62
  isNewRow(key: string): key is "__vuu_new_row__";
@@ -68,19 +65,18 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
68
65
  configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
69
66
  setNewRowValue(column: string, value: VuuRowDataItemType): void;
70
67
  addNewRow(): Promise<RpcResult>;
71
- deleteSelectedRows(): Promise<void>;
68
+ deleteSelectedRows(): Promise<RpcResult>;
72
69
  addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
73
- restoreRows(keys: string[]): void;
74
70
  hasRowChanges(key: string): boolean;
75
71
  isCellEdited(key: string, columnName: string): boolean;
76
- undoRowChange(key: string): Promise<void>;
72
+ undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
77
73
  begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
78
74
  /**
79
75
  * Safe to call repeatedly - schema of a remote session table typically arrives
80
76
  * after begin() resolves, so callers re-invoke this once 'subscribed' fires.
81
77
  */
82
78
  reconcileWithSessionSchema(): void;
83
- get dataSource(): EditApi<import("@vuu-ui/vuu-data-types").DataSourceRow<VuuRowDataItemType>> | undefined;
79
+ get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
84
80
  get sessionDataSource(): DataSource | undefined;
85
81
  end(saveChanges?: boolean, force?: boolean): Promise<void>;
86
82
  get lifecycle(): EditLifecycle;
package/types/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { useCellEdited } from "./useCellEdited";
3
3
  export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
4
4
  export { EditButtons, type EditButtonProps } from "./EditButtons";
5
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";
6
+ export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
7
7
  export { StaleUpdateError } from "@vuu-ui/vuu-utils";
8
8
  export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
9
9
  export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
@@ -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;