@vuu-ui/vuu-data-editing 3.3.0 → 3.3.2

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.0",
2
+ "version": "3.3.2",
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.0",
16
- "@vuu-ui/vuu-utils": "3.3.0"
15
+ "@vuu-ui/vuu-ui-controls": "3.3.2",
16
+ "@vuu-ui/vuu-utils": "3.3.2"
17
17
  },
18
18
  "devDependencies": {
19
- "@vuu-ui/vuu-data-types": "3.3.0",
20
- "@vuu-ui/vuu-protocol-types": "3.3.0",
21
- "@vuu-ui/vuu-table-types": "3.3.0"
19
+ "@vuu-ui/vuu-data-types": "3.3.2",
20
+ "@vuu-ui/vuu-protocol-types": "3.3.2",
21
+ "@vuu-ui/vuu-table-types": "3.3.2"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19.2.3"
@@ -28,6 +28,7 @@ class EditSession extends EventEmitter {
28
28
  #cellCommitRevisions = new Map();
29
29
  #deleteMode;
30
30
  #editSessionApi;
31
+ #rowDefaults;
31
32
  #sourceTableDataSource;
32
33
  #sessionDataSource;
33
34
  #newRowState = {
@@ -41,11 +42,12 @@ class EditSession extends EventEmitter {
41
42
  status: "idle"
42
43
  };
43
44
  #transitionQueue = Promise.resolve();
44
- constructor(dataSource, deleteMode = "soft", editSessionApi = "createSessionDataSource"){
45
+ constructor({ dataSource, deleteMode = "soft", editSessionApi = "createSessionDataSource", rowDefaults = {} }){
45
46
  super();
46
47
  this.#sourceTableDataSource = dataSource;
47
48
  this.#deleteMode = deleteMode;
48
49
  this.#editSessionApi = editSessionApi;
50
+ this.#rowDefaults = rowDefaults;
49
51
  }
50
52
  get editCount() {
51
53
  return this.#editCount;
@@ -245,7 +247,10 @@ class EditSession extends EventEmitter {
245
247
  async addRow(rowData = {}) {
246
248
  const addRow = this.dataSource?.addRow;
247
249
  if (void 0 === addRow) throw Error("[EditSession] datasource does not support adding rows");
248
- const response = await addRow.call(this.dataSource, rowData);
250
+ const response = await addRow.call(this.dataSource, {
251
+ ...this.#rowDefaults,
252
+ ...rowData
253
+ });
249
254
  if (void 0 === response) throw Error("[EditSession] datasource returned no response when adding row");
250
255
  if (!isRpcError(response)) this.addCount = this.#addCount + 1;
251
256
  return response;
@@ -358,6 +363,7 @@ class EditSession extends EventEmitter {
358
363
  status: "active",
359
364
  sessionDataSource
360
365
  });
366
+ this.reconcileWithSessionSchema();
361
367
  return sessionDataSource;
362
368
  } catch (cause) {
363
369
  const error = cause instanceof Error ? cause : new Error(String(cause));
@@ -370,6 +376,21 @@ class EditSession extends EventEmitter {
370
376
  }
371
377
  });
372
378
  }
379
+ reconcileWithSessionSchema() {
380
+ const sessionSchema = this.#sessionDataSource?.tableSchema;
381
+ if (void 0 === sessionSchema) return;
382
+ const columnNames = new Set(sessionSchema.columns.map((c)=>c.name));
383
+ const unknown = Object.keys(this.#rowDefaults).filter((key)=>!columnNames.has(key));
384
+ if (unknown.length > 0) {
385
+ console.warn(`[EditSession] rowDefaults contains columns not in table schema, removing: ${unknown.join(", ")}`);
386
+ for (const key of unknown)delete this.#rowDefaults[key];
387
+ }
388
+ const sourceKey = this.#sourceTableDataSource?.tableSchema?.key;
389
+ if (void 0 !== sourceKey && sourceKey !== sessionSchema.key) {
390
+ console.warn(`[EditSession] source table key '${sourceKey}' differs from session table key '${sessionSchema.key}', discarding pending edits`);
391
+ this.#clearEdits();
392
+ }
393
+ }
373
394
  get dataSource() {
374
395
  return this.#sessionDataSource ?? this.#sourceTableDataSource;
375
396
  }
@@ -5,7 +5,7 @@ import { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR } from "./editActionRowClassNameGe
5
5
  const EDIT_ACTION_ROW_CLASS_NAME_GENERATORS = [
6
6
  EDIT_ACTION_ROW_CLASS_NAME_GENERATOR
7
7
  ];
8
- const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "soft", editSessionApi = "createSessionDataSource", copyOption = "All", isEditMode, onCancel, onSave, table })=>{
8
+ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "soft", editSessionApi = "createSessionDataSource", copyOption = "All", isEditMode, onCancel, onSave, rowDefaults, table })=>{
9
9
  const { VuuDataSource } = useData();
10
10
  const [selectionCount, setSelectionCount] = useState(0);
11
11
  useLayoutEffectSkipFirst(()=>{
@@ -27,9 +27,15 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
27
27
  dataSourceProp,
28
28
  table
29
29
  ]);
30
- const editSession = useMemo(()=>new EditSession(sourceDataSource, deleteMode, editSessionApi), [
30
+ const editSession = useMemo(()=>new EditSession({
31
+ dataSource: sourceDataSource,
32
+ deleteMode,
33
+ editSessionApi,
34
+ rowDefaults
35
+ }), [
31
36
  deleteMode,
32
37
  editSessionApi,
38
+ rowDefaults,
33
39
  sourceDataSource
34
40
  ]);
35
41
  const [lifecycle, setLifecycle] = useState(editSession.lifecycle);
@@ -79,12 +85,16 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
79
85
  ]);
80
86
  useEffect(()=>{
81
87
  if (!sessionDataSource) return void setSubscribedSessionDataSource(void 0);
82
- const handleSubscribed = ()=>setSubscribedSessionDataSource(sessionDataSource);
88
+ const handleSubscribed = ()=>{
89
+ editSession.reconcileWithSessionSchema();
90
+ setSubscribedSessionDataSource(sessionDataSource);
91
+ };
83
92
  setSubscribedSessionDataSource(void 0);
84
93
  sessionDataSource.on("subscribed", handleSubscribed);
85
94
  if ("subscribed" === sessionDataSource.status && sessionDataSource.tableSchema) handleSubscribed();
86
95
  return ()=>sessionDataSource.removeListener("subscribed", handleSubscribed);
87
96
  }, [
97
+ editSession,
88
98
  sessionDataSource
89
99
  ]);
90
100
  useEffect(()=>{
@@ -121,10 +131,15 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
121
131
  const canCancel = "active" === lifecycle.status || "error" === lifecycle.status && "end" === lifecycle.operation;
122
132
  const canSave = canCancel && ("dirty" === editState || "stale" === editState) && 0 === editSession.invalidCount;
123
133
  const isEditSessionReady = isEditMode && void 0 !== sessionDataSource && sessionDataSource === editSession.sessionDataSource && subscribedSessionDataSource === sessionDataSource && "subscribed" === sessionDataSource.status && void 0 !== sessionDataSource.tableSchema;
134
+ const editSchema = subscribedSessionDataSource?.tableSchema;
135
+ const viewSchema = sourceDataSource.tableSchema;
136
+ const columnsDiverge = void 0 !== editSchema && void 0 !== viewSchema && (editSchema.columns.length !== viewSchema.columns.length || editSchema.columns.some((column, index)=>column.name !== viewSchema.columns[index]?.name));
124
137
  return {
125
138
  canCancel,
126
139
  canSave,
140
+ columnsDiverge,
127
141
  dataSource,
142
+ editSchema,
128
143
  editSession,
129
144
  lifecycle,
130
145
  hasSelection: selectionCount > 0,
@@ -2,7 +2,18 @@ import type { CopyOption, DataSource, DeleteRowMode, EditApi, SessionType } from
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
+ /** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
6
+ export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
5
7
  export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
8
+ export type EditSessionConstructorProps = {
9
+ dataSource: EditApi;
10
+ /** @default "soft" */
11
+ deleteMode?: DeleteRowMode;
12
+ /** @default "createSessionDataSource" */
13
+ editSessionApi?: EditSessionApi;
14
+ /** Default column values merged into every addRow call for absent columns. Pass a stable reference. */
15
+ rowDefaults?: RowDefaultDataItemValues;
16
+ };
6
17
  export type NewRowState = {
7
18
  columns: readonly string[];
8
19
  draftRevision: number;
@@ -39,7 +50,7 @@ type EditSessionEvents = {
39
50
  export declare class EditSession extends EventEmitter<EditSessionEvents> {
40
51
  #private;
41
52
  static readonly newRowKey = "__vuu_new_row__";
42
- constructor(dataSource: EditApi, deleteMode?: DeleteRowMode, editSessionApi?: EditSessionApi);
53
+ constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
43
54
  get editCount(): number;
44
55
  set editCount(val: number);
45
56
  get invalidCount(): number;
@@ -63,6 +74,11 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
63
74
  isCellEdited(key: string, columnName: string): boolean;
64
75
  undoRowChange(key: string): Promise<void>;
65
76
  begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
77
+ /**
78
+ * Safe to call repeatedly - schema of a remote session table typically arrives
79
+ * after begin() resolves, so callers re-invoke this once 'subscribed' fires.
80
+ */
81
+ reconcileWithSessionSchema(): void;
66
82
  get dataSource(): EditApi<import("@vuu-ui/vuu-data-types").DataSourceRow<VuuRowDataItemType>> | undefined;
67
83
  get sessionDataSource(): DataSource | undefined;
68
84
  end(saveChanges?: boolean, force?: boolean): Promise<void>;
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 NewRowState, type EditState, } 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 EditLifecycle, type EditSessionApi } 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
  /**
@@ -16,16 +16,20 @@ export interface EditableTableHookProps {
16
16
  isEditMode: boolean;
17
17
  onCancel: () => void;
18
18
  onSave: () => void;
19
+ /** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
20
+ rowDefaults?: RowDefaultDataItemValues;
19
21
  /**
20
22
  * If dataSource not provided, new DataSource
21
23
  * will be created using table and columns
22
24
  */
23
25
  table?: VuuTable;
24
26
  }
25
- export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, deleteMode, editSessionApi, copyOption, isEditMode, onCancel, onSave, table, }: EditableTableHookProps) => {
27
+ export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, deleteMode, editSessionApi, copyOption, isEditMode, onCancel, onSave, rowDefaults, table, }: EditableTableHookProps) => {
26
28
  canCancel: boolean;
27
29
  canSave: boolean;
30
+ columnsDiverge: boolean;
28
31
  dataSource: DataSource;
32
+ editSchema: import("@vuu-ui/vuu-data-types").TableSchema | undefined;
29
33
  editSession: EditSession;
30
34
  lifecycle: EditLifecycle;
31
35
  hasSelection: boolean;