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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.3.11",
2
+ "version": "4.0.0-alpha.1",
3
3
  "description": "Shared data editing APIs and React components for VUU UI",
4
4
  "main": "./src/index.js",
5
5
  "author": "heswell",
@@ -11,14 +11,14 @@
11
11
  "type-defs": "node ../../scripts/build-type-defs.mjs"
12
12
  },
13
13
  "dependencies": {
14
- "@salt-ds/core": "1.54.1",
15
- "@vuu-ui/vuu-ui-controls": "3.3.11",
16
- "@vuu-ui/vuu-utils": "3.3.11"
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"
17
17
  },
18
18
  "devDependencies": {
19
- "@vuu-ui/vuu-data-types": "3.3.11",
20
- "@vuu-ui/vuu-protocol-types": "3.3.11",
21
- "@vuu-ui/vuu-table-types": "3.3.11"
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"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19.2.3"
@@ -16,6 +16,9 @@ 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();
19
22
  #editCount = 0;
20
23
  #deleteCount = 0;
21
24
  #addCount = 0;
@@ -45,22 +48,42 @@ class EditSession extends EventEmitter {
45
48
  this.#sourceTableDataSource = dataSource;
46
49
  this.#deleteMode = deleteMode;
47
50
  this.#editSessionApi = editSessionApi;
48
- this.#rowDefaults = {
49
- ...rowDefaults
50
- };
51
+ this.#rowDefaults = rowDefaults;
51
52
  }
52
53
  get editCount() {
53
54
  return this.#editCount;
54
55
  }
56
+ set editCount(val) {
57
+ this.#setEditCounts(val, this.#invalidCount);
58
+ }
55
59
  get invalidCount() {
56
60
  return this.#invalidCount;
57
61
  }
62
+ set invalidCount(val) {
63
+ this.#setEditCounts(this.#editCount, val);
64
+ }
58
65
  get deleteCount() {
59
66
  return this.#deleteCount;
60
67
  }
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
+ }
61
76
  get addCount() {
62
77
  return this.#addCount;
63
78
  }
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
+ }
64
87
  get editState() {
65
88
  if (this.#invalidCount > 0) return "invalid";
66
89
  if (this.#isStale) return "stale";
@@ -78,22 +101,6 @@ class EditSession extends EventEmitter {
78
101
  this.#emitEditStateChange(oldState);
79
102
  }
80
103
  }
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
- }
97
104
  #setStale(isStale) {
98
105
  if (isStale !== this.#isStale) {
99
106
  const oldState = this.editState;
@@ -101,6 +108,13 @@ class EditSession extends EventEmitter {
101
108
  this.#emitEditStateChange(oldState);
102
109
  }
103
110
  }
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
+ }
104
118
  get newRowState() {
105
119
  return this.#newRowState;
106
120
  }
@@ -222,14 +236,20 @@ class EditSession extends EventEmitter {
222
236
  this.emit("newRow", newRowState);
223
237
  }
224
238
  async deleteSelectedRows() {
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;
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
+ }
233
253
  }
234
254
  async addRow(rowData = {}) {
235
255
  const addRow = this.dataSource?.addRow;
@@ -239,50 +259,54 @@ class EditSession extends EventEmitter {
239
259
  ...rowData
240
260
  });
241
261
  if (void 0 === response) throw Error("[EditSession] datasource returned no response when adding row");
242
- if (!isRpcError(response)) this.#setAddCount(this.#addCount + 1);
262
+ if (!isRpcError(response)) this.addCount = this.#addCount + 1;
243
263
  return response;
244
264
  }
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
+ }
245
272
  hasRowChanges(key) {
246
- return this.#rowEdits.has(key);
273
+ return this.#rowEdits.has(key) || this.#deletedRows.has(key);
247
274
  }
248
275
  isCellEdited(key, columnName) {
249
276
  const cellEdit = this.#rowEdits.get(key)?.cellEdits.get(columnName);
250
277
  return cellEdit?.isValid === true && cellEdit.originalValue !== cellEdit.editedValue;
251
278
  }
252
- async undoRowChange(key, action) {
279
+ async undoRowChange(key) {
253
280
  if (!this.inEditMode) return;
254
281
  const undoRevision = this.#commitRevision;
282
+ const deleteRevisionAtRequest = this.#rowDeleteRevisions.get(key);
255
283
  const rowEditsAtRequest = this.#rowEdits.get(key);
256
284
  const columnsAtRequest = new Set(rowEditsAtRequest?.cellEdits.keys());
285
+ const wasDeleted = this.#deletedRows.has(key);
257
286
  const response = await this.dataSource?.undoRowChange?.(key);
258
- if (void 0 === response) return;
259
- if (isRpcError(response)) return response;
260
- const oldState = this.editState;
287
+ if (isRpcError(response)) return;
261
288
  const rowEdits = this.#rowEdits.get(key);
262
289
  if (rowEdits) {
263
290
  const changedColumns = [];
264
- let removedValidCount = 0;
265
- let removedInvalidCount = 0;
266
291
  for (const columnName of columnsAtRequest){
267
292
  const latestRevision = this.#cellCommitRevisions.get(key)?.get(columnName) ?? 0;
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);
293
+ if (!(latestRevision > undoRevision)) {
294
+ if (this.isCellEdited(key, columnName)) changedColumns.push(columnName);
295
+ rowEdits.cellEdits.delete(columnName);
296
+ this.#setCellCommitRevision(key, columnName);
297
+ }
275
298
  }
276
299
  if (0 === rowEdits.cellEdits.size) this.#rowEdits.delete(key);
277
- this.#editCount -= removedValidCount;
278
- this.#invalidCount -= removedInvalidCount;
300
+ this.#refreshEditCounts();
279
301
  for (const columnName of changedColumns)this.emit("cellEditChanged", key, columnName);
280
302
  }
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;
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;
286
310
  }
287
311
  #clearEdits() {
288
312
  const oldState = this.editState;
@@ -295,6 +319,8 @@ class EditSession extends EventEmitter {
295
319
  columnName
296
320
  ]));
297
321
  this.#rowEdits.clear();
322
+ this.#deletedRows.clear();
323
+ this.#rowDeleteRevisions.clear();
298
324
  this.#cellCommitRevisions.clear();
299
325
  this.#editCount = 0;
300
326
  this.#deleteCount = 0;
@@ -437,24 +463,9 @@ class EditSession extends EventEmitter {
437
463
  editedValue,
438
464
  isValid
439
465
  };
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);
466
+ if (isValid && cellEdit.originalValue === editedValue) cellEdits.delete(column);
467
+ else cellEdits.set(column, cellEdit);
468
+ this.#refreshEditCounts();
458
469
  if (wasEdited !== this.isCellEdited(key, column)) this.emit("cellEditChanged", key, column);
459
470
  return cellEdit;
460
471
  }
@@ -477,10 +488,6 @@ class EditSession extends EventEmitter {
477
488
  const cellEdit = this.#storeCellEdit(key, cellEdits, columnName, originalValue, typedValue, isValid);
478
489
  if (this.dataSource?.editCell) {
479
490
  const response = await this.dataSource.editCell(key, columnName, typedValue);
480
- if (!this.inEditMode) return {
481
- data: void 0,
482
- type: "SUCCESS_RESULT"
483
- };
484
491
  if (!this.#isLatestCellCommit(key, columnName, revision)) throw new SupersededEditError(`Edit response superseded for ${key}:${columnName}`);
485
492
  const currentCellEdits = this.#getOrCreateRowEdits(key).cellEdits;
486
493
  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, dataRow.vuuAction),
23
+ onClick: ()=>editSession?.undoRowChange(dataRow.key),
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, action)=>void editSession.undoRowChange(key, action), [
77
+ const handleUndoRowChange = useCallback((key)=>void editSession.undoRowChange(key), [
78
78
  editSession
79
79
  ]);
80
80
  useEffect(()=>{
@@ -3,6 +3,6 @@ import type { EditSession } from "./EditSession";
3
3
  export declare const DataEditingProvider: ({ children, editSession, }: {
4
4
  children: ReactNode;
5
5
  editSession: EditSession;
6
- }) => import("react/jsx-runtime").JSX.Element;
6
+ }) => import("react").JSX.Element;
7
7
  export declare function useEditSession(throwIfUnavailable?: false): EditSession | undefined;
8
8
  export declare function useEditSession(throwIfUnavailable: true): EditSession;
@@ -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-runtime").JSX.Element;
14
+ export declare const EditButtons: ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
@@ -9,5 +9,5 @@ export interface EditModeContextProps {
9
9
  */
10
10
  export declare const EditModeProvider: ({ children }: {
11
11
  children: ReactNode;
12
- }) => import("react/jsx-runtime").JSX.Element;
12
+ }) => import("react").JSX.Element;
13
13
  export declare const useEditMode: () => EditModeContextProps;
@@ -1,13 +1,12 @@
1
- import type { CopyOption, DataSource, DeleteRowMode, SessionType } from "@vuu-ui/vuu-data-types";
1
+ import type { CopyOption, DataSource, DeleteRowMode, EditApi, 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
5
  /** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
7
6
  export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
8
7
  export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
9
8
  export type EditSessionConstructorProps = {
10
- dataSource: DataSource;
9
+ dataSource: EditApi;
11
10
  /** @default "soft" */
12
11
  deleteMode?: DeleteRowMode;
13
12
  /** @default "createSessionDataSource" */
@@ -54,9 +53,13 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
54
53
  static readonly newRowKey = "__vuu_new_row__";
55
54
  constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
56
55
  get editCount(): number;
56
+ set editCount(val: number);
57
57
  get invalidCount(): number;
58
+ set invalidCount(val: number);
58
59
  get deleteCount(): number;
60
+ set deleteCount(val: number);
59
61
  get addCount(): number;
62
+ set addCount(val: number);
60
63
  get editState(): EditState;
61
64
  get newRowState(): NewRowState;
62
65
  isNewRow(key: string): key is "__vuu_new_row__";
@@ -65,18 +68,19 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
65
68
  configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
66
69
  setNewRowValue(column: string, value: VuuRowDataItemType): void;
67
70
  addNewRow(): Promise<RpcResult>;
68
- deleteSelectedRows(): Promise<RpcResult>;
71
+ deleteSelectedRows(): Promise<void>;
69
72
  addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
73
+ restoreRows(keys: string[]): void;
70
74
  hasRowChanges(key: string): boolean;
71
75
  isCellEdited(key: string, columnName: string): boolean;
72
- undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
76
+ undoRowChange(key: string): Promise<void>;
73
77
  begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
74
78
  /**
75
79
  * Safe to call repeatedly - schema of a remote session table typically arrives
76
80
  * after begin() resolves, so callers re-invoke this once 'subscribed' fires.
77
81
  */
78
82
  reconcileWithSessionSchema(): void;
79
- get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
83
+ get dataSource(): EditApi<import("@vuu-ui/vuu-data-types").DataSourceRow<VuuRowDataItemType>> | undefined;
80
84
  get sessionDataSource(): DataSource | undefined;
81
85
  end(saveChanges?: boolean, force?: boolean): Promise<void>;
82
86
  get lifecycle(): EditLifecycle;
@@ -9,4 +9,4 @@ export declare const getUndoButtonContent: (componentProps?: UndoCellRendererCom
9
9
  text: string | undefined;
10
10
  };
11
11
  export declare const getUndoTooltipContent: (action: unknown) => "Undo delete row" | "Undo insert row" | "Undo row edits" | undefined;
12
- export declare const UndoCellRenderer: ({ column, dataRow, }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element | null;
12
+ export declare const UndoCellRenderer: ({ column, dataRow, }: TableCellRendererProps) => import("react").JSX.Element | null;
@@ -2,4 +2,4 @@ import type { DataRow, RuntimeColumnDescriptor } from "@vuu-ui/vuu-table-types";
2
2
  export { isInlineEditingSession } from "@vuu-ui/vuu-utils";
3
3
  export declare const isEditRowReadOnly: (dataRow: DataRow) => boolean;
4
4
  export declare const withDataRowEditErrors: <T extends DataRow>(dataRow: T, errors: Record<string, string>) => T;
5
- export declare const getVuuEditMessage: (dataRow: DataRow, column: RuntimeColumnDescriptor, originalValue: string) => string | import("react/jsx-runtime").JSX.Element | undefined;
5
+ export declare const getVuuEditMessage: (dataRow: DataRow, column: RuntimeColumnDescriptor, originalValue: string) => string | import("react").JSX.Element | undefined;
@@ -1,3 +1,3 @@
1
1
  import type { DataRow } from "@vuu-ui/vuu-table-types";
2
2
  export declare const EDIT_ACTION_ROW_CLASS_NAME_GENERATOR = "vuu-edit-actions";
3
- export declare const editActionRowClassNameGenerator: (dataRow: DataRow) => "vuuTableRow-inserted" | "vuuTableRow-deleted" | undefined;
3
+ export declare const editActionRowClassNameGenerator: (dataRow: DataRow) => "vuuTableRow-deleted" | "vuuTableRow-inserted" | undefined;
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 EditActionType, type EditLifecycle, type EditSessionConstructorProps, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
6
+ export { EditError, EditSession, SupersededEditError, 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 EditActionType, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
3
+ import { EditSession, 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, action: EditActionType) => undefined;
40
+ onUndoRowChange: (key: string) => 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;