@adecore/database 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/console/QueryConsole.js +2 -10
  2. package/dist/console/SchemaPicker.js +0 -1
  3. package/dist/console/StatementResultView.js +1 -1
  4. package/dist/ddl/diff.js +1 -1
  5. package/dist/explorer/tree.js +0 -1
  6. package/dist/export-formats.d.ts +2 -0
  7. package/dist/export-formats.js +1 -0
  8. package/dist/grid/CopyAsMenu.js +0 -1
  9. package/dist/grid/DataGrid.js +10 -10
  10. package/dist/grid/column-layout.d.ts +0 -1
  11. package/dist/grid/column-layout.js +1 -2
  12. package/dist/grid/column-view.js +3 -2
  13. package/dist/grid/display.js +1 -2
  14. package/dist/host/host.js +1 -2
  15. package/dist/host/validate.d.ts +3 -0
  16. package/dist/host/validate.js +18 -15
  17. package/dist/same-list.d.ts +1 -0
  18. package/dist/same-list.js +1 -0
  19. package/dist/stored-size.d.ts +1 -0
  20. package/dist/stored-size.js +10 -0
  21. package/dist/table/TableToolbar.js +1 -1
  22. package/dist/table/TableView.js +2 -10
  23. package/package.json +15 -15
  24. package/src/DatabaseProvider.tsx +2 -0
  25. package/src/KeyIcon.tsx +1 -0
  26. package/src/client/client.ts +13 -0
  27. package/src/client-context.ts +2 -0
  28. package/src/connections/ConnectionForm.tsx +11 -0
  29. package/src/connections/ConnectionManager.tsx +9 -0
  30. package/src/connections/connection-config.ts +21 -0
  31. package/src/connections/docker.ts +2 -0
  32. package/src/connections/use-docker-containers.ts +3 -0
  33. package/src/console/DestructiveDialog.tsx +1 -0
  34. package/src/console/QueryConsole.tsx +38 -10
  35. package/src/console/SchemaPicker.tsx +1 -1
  36. package/src/console/SqlEditor.tsx +10 -0
  37. package/src/console/StatementResultView.tsx +7 -2
  38. package/src/console/TransactionControls.tsx +1 -0
  39. package/src/console/destructive.ts +11 -0
  40. package/src/console/editing.ts +10 -0
  41. package/src/console/history.ts +8 -0
  42. package/src/console/labels.ts +1 -0
  43. package/src/console/outcome.ts +1 -0
  44. package/src/console/result-keys.ts +16 -0
  45. package/src/console/shortcuts.ts +2 -0
  46. package/src/ddl/alter-mysql.ts +16 -0
  47. package/src/ddl/alter-sqlite.ts +21 -0
  48. package/src/ddl/alter.ts +2 -0
  49. package/src/ddl/column.ts +11 -0
  50. package/src/ddl/create.ts +15 -0
  51. package/src/ddl/dialect.ts +4 -0
  52. package/src/ddl/diff.ts +22 -2
  53. package/src/ddl/draft.ts +1 -0
  54. package/src/ddl/parse.ts +31 -0
  55. package/src/ddl/types.ts +3 -0
  56. package/src/ddl/validate.ts +12 -0
  57. package/src/designer/ColumnsEditor.tsx +3 -0
  58. package/src/designer/DesignerView.tsx +2 -0
  59. package/src/designer/ForeignKeysEditor.tsx +4 -0
  60. package/src/designer/IndexesEditor.tsx +1 -0
  61. package/src/designer/NamePicker.tsx +2 -0
  62. package/src/designer/OptionsEditor.tsx +4 -0
  63. package/src/designer/SqlPreview.tsx +2 -0
  64. package/src/designer/TableDesigner.tsx +14 -0
  65. package/src/designer/edit.ts +13 -0
  66. package/src/designer/plan.ts +3 -0
  67. package/src/designer/recover.ts +1 -0
  68. package/src/designer/useReferenceColumns.ts +5 -0
  69. package/src/explorer/DatabaseExplorer.tsx +40 -0
  70. package/src/explorer/RowMenu.tsx +2 -0
  71. package/src/explorer/TableDialog.tsx +6 -0
  72. package/src/explorer/expansion.ts +8 -0
  73. package/src/explorer/tree.ts +73 -2
  74. package/src/explorer/use-explorer-loads.ts +15 -0
  75. package/src/explorer/use-stored-expansion.ts +4 -0
  76. package/src/export-formats.ts +3 -0
  77. package/src/grid/CellEditor.tsx +1 -0
  78. package/src/grid/CopyAsMenu.tsx +1 -1
  79. package/src/grid/DataGrid.tsx +93 -23
  80. package/src/grid/EnumPicker.tsx +6 -0
  81. package/src/grid/GridHeaderCell.tsx +11 -0
  82. package/src/grid/GridRowView.tsx +3 -0
  83. package/src/grid/RecordView.tsx +19 -0
  84. package/src/grid/ValueDock.tsx +2 -0
  85. package/src/grid/aggregates.ts +12 -0
  86. package/src/grid/column-layout.ts +12 -4
  87. package/src/grid/column-selection.ts +9 -0
  88. package/src/grid/column-view.ts +6 -2
  89. package/src/grid/copy-formats.ts +13 -0
  90. package/src/grid/display.ts +17 -3
  91. package/src/grid/edit-value.ts +11 -0
  92. package/src/grid/enum-type.ts +15 -0
  93. package/src/grid/focused-value.ts +4 -0
  94. package/src/grid/layout.ts +12 -0
  95. package/src/grid/navigation.ts +5 -0
  96. package/src/grid/row-selection.ts +7 -0
  97. package/src/grid/sort.ts +1 -0
  98. package/src/grid/whole-cells.ts +8 -0
  99. package/src/host/host.ts +16 -3
  100. package/src/host/spawn.ts +2 -0
  101. package/src/host/validate.ts +37 -15
  102. package/src/same-list.ts +2 -0
  103. package/src/sql-split.ts +28 -0
  104. package/src/sql.ts +20 -0
  105. package/src/stored-size.ts +10 -0
  106. package/src/structure/StructureView.tsx +18 -0
  107. package/src/table/CommandField.tsx +11 -0
  108. package/src/table/ImportDialog.tsx +4 -0
  109. package/src/table/TableStatusBar.tsx +1 -0
  110. package/src/table/TableToolbar.tsx +2 -2
  111. package/src/table/TableView.tsx +50 -10
  112. package/src/table/command-field.ts +40 -0
  113. package/src/table/grid-rows.ts +13 -0
  114. package/src/table/import-mapping.ts +5 -0
  115. package/src/table/layout-store.ts +7 -0
  116. package/src/table/paging.ts +1 -0
  117. package/src/table/pending.ts +18 -0
  118. package/src/table/references.ts +4 -0
  119. package/src/table/useLoaded.ts +3 -0
  120. package/src/table/useStoredLayout.ts +2 -0
  121. package/src/table/useTableTransfer.ts +17 -1
  122. package/src/testing/fake.ts +24 -0
  123. package/src/use-database-resources.ts +1 -0
  124. package/src/use-popup-press.ts +3 -0
  125. package/src/value/ValueEditor.tsx +6 -0
  126. package/src/value/draft.ts +7 -0
  127. package/src/value/hex.ts +8 -0
  128. package/src/value/json.ts +16 -0
  129. package/src/value/views.ts +14 -0
  130. package/src/workbench/DatabaseWorkbench.tsx +22 -0
  131. package/src/workbench/tabs.ts +34 -0
@@ -60,6 +60,7 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
60
60
  started.current.delete(key);
61
61
  }
62
62
  }
63
+
63
64
  setLoads((current) => ({
64
65
  entries: new Map([...current.entries].filter(([, entry]) => !within(target, entry.target))),
65
66
  versions: target.schema === undefined ? new Map([...current.versions].filter(([id]) => id !== target.connectionId)) : current.versions
@@ -71,16 +72,20 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
71
72
  if (!silent) {
72
73
  store(target, { status: 'loading' });
73
74
  }
75
+
74
76
  const session = client.session(connection);
77
+
75
78
  const request: Promise<Loaded> =
76
79
  target.schema === undefined
77
80
  ? session.schemas()
78
81
  : target.table === undefined
79
82
  ? session.tables(target.schema)
80
83
  : session.structure(target.schema, target.table);
84
+
81
85
  request.then(
82
86
  (value) => {
83
87
  store(target, { status: 'ready', value });
88
+
84
89
  if (target.schema === undefined) {
85
90
  // Chained after the schemas so the version never opens a session by itself.
86
91
  session.server().then(
@@ -95,13 +100,17 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
95
100
 
96
101
  const start = (target: LoadTarget): void => {
97
102
  const key = loadKey(target);
103
+
98
104
  if (started.current.has(key)) {
99
105
  return;
100
106
  }
107
+
101
108
  const connection = connections.find((entry) => entry.id === target.connectionId);
109
+
102
110
  if (connection === undefined) {
103
111
  return;
104
112
  }
113
+
105
114
  started.current.set(key, target);
106
115
  run(target, connection, false);
107
116
  };
@@ -110,10 +119,12 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
110
119
  const reload = (change: SchemaChange): void => {
111
120
  const connection = connections.find((entry) => entry.id === change.connectionId);
112
121
  const dropped: LoadTarget[] = [];
122
+
113
123
  for (const [key, began] of [...started.current]) {
114
124
  if (!touches(change, began)) {
115
125
  continue;
116
126
  }
127
+
117
128
  if (began.table !== undefined) {
118
129
  started.current.delete(key);
119
130
  dropped.push(began);
@@ -121,6 +132,7 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
121
132
  run(began, connection, true);
122
133
  }
123
134
  }
135
+
124
136
  if (dropped.length > 0) {
125
137
  const gone = new Set(dropped.map(loadKey));
126
138
  setLoads((current) => ({ ...current, entries: new Map([...current.entries].filter(([key]) => !gone.has(key))) }));
@@ -132,13 +144,16 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
132
144
  // A connection that was edited or removed is a different session, so what it listed is stale.
133
145
  useEffect(() => {
134
146
  const known = configs.current;
147
+
135
148
  for (const [id, config] of known) {
136
149
  const now = connections.find((entry) => entry.id === id);
150
+
137
151
  if (now === undefined || now.config !== config) {
138
152
  known.delete(id);
139
153
  reset({ connectionId: id });
140
154
  }
141
155
  }
156
+
142
157
  for (const connection of connections) {
143
158
  known.set(connection.id, connection.config);
144
159
  }
@@ -15,15 +15,18 @@ const restore = (storage: DatabaseStorage | undefined, connections: readonly Con
15
15
  const expanded = new Set(from.expanded);
16
16
  const collapsed = new Set(from.collapsed);
17
17
  const loaded = new Set(from.loaded);
18
+
18
19
  for (const { id } of connections) {
19
20
  if (loaded.has(id)) {
20
21
  continue;
21
22
  }
23
+
22
24
  loaded.add(id);
23
25
  const stored = parseExpansion(storage?.get(expansionKey(id)) ?? null, id);
24
26
  stored.expanded.forEach((key) => expanded.add(key));
25
27
  stored.collapsed.forEach((key) => collapsed.add(key));
26
28
  }
29
+
27
30
  return { expanded, collapsed, loaded };
28
31
  };
29
32
 
@@ -51,6 +54,7 @@ export function useStoredExpansion(connections: readonly Connection[]): StoredEx
51
54
  const next = serializeExpansion(id, state);
52
55
  const last = written.current.get(id);
53
56
  written.current.set(id, next);
57
+
54
58
  if (last !== undefined && last !== next) {
55
59
  storage?.set(expansionKey(id), next);
56
60
  }
@@ -0,0 +1,3 @@
1
+ import type { FileFormat } from './protocol/index.ts';
2
+
3
+ export const EXPORT_FORMATS: readonly FileFormat[] = ['csv', 'tsv', 'json', 'sql'];
@@ -27,6 +27,7 @@ export function CellEditor({ value, label, onValueChange, onCommit, onCancel }:
27
27
  if (event.nativeEvent.isComposing) {
28
28
  return;
29
29
  }
30
+
30
31
  if (event.key === 'Enter') {
31
32
  event.preventDefault();
32
33
  onCommit();
@@ -11,9 +11,9 @@ export interface CopyAsMenuProps {
11
11
  onCopy(format: CopyFormat): void;
12
12
  }
13
13
 
14
- /* The "Copy as" submenu of the grid's context menu. */
15
14
  export function CopyAsMenu({ disabled, formats, onCopy }: CopyAsMenuProps) {
16
15
  const { t } = useTranslation('database');
16
+
17
17
  return (
18
18
  <ContextMenu.SubmenuRoot>
19
19
  <ContextMenu.SubmenuTrigger disabled={disabled}>
@@ -133,14 +133,11 @@ const signatureOf = (columns: readonly GridColumn[]): string => columns.map((col
133
133
 
134
134
  const textOf = (view: CellView): string => (view.suffix === undefined ? view.text : `${view.text} ${view.suffix}`);
135
135
 
136
+ const columnTexts = (column: GridColumn, index: number, rows: readonly GridRow[], notation: NumberNotation): string[] =>
137
+ rows.map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation))));
138
+
136
139
  const estimateWidths = (columns: readonly GridColumn[], rows: readonly GridRow[], notation: NumberNotation): number[] =>
137
- columns.map((column, index) =>
138
- estimateColumnWidth(
139
- column.name,
140
- rows.slice(0, 50).map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation)))),
141
- column.primaryKey === true
142
- )
143
- );
140
+ columns.map((column, index) => estimateColumnWidth(column.name, columnTexts(column, index, rows.slice(0, 50), notation), column.primaryKey === true));
144
141
 
145
142
  /*
146
143
  * The table of the table view and of the console, and of any rows an app has on its own: it needs no
@@ -201,6 +198,7 @@ export function DataGrid({
201
198
  const [menuTarget, setMenuTarget] = useState<MenuTarget | null>(null);
202
199
  const [editing, setEditing] = useState<CellEdit | null>(null);
203
200
  const [ownSelection, setOwnSelection] = useState<ReadonlySet<string>>(NO_KEYS);
201
+
204
202
  const [layout, setLayout] = useState<Layout>(() => ({
205
203
  signature: signatureOf(columns),
206
204
  rows,
@@ -208,13 +206,16 @@ export function DataGrid({
208
206
  estimated: estimateWidths(columns, rows, notation),
209
207
  ...restoreLayout(columns, initialLayout)
210
208
  }));
209
+
211
210
  const reportedLayout = useRef<GridLayout>(captureLayout(columns, layout));
212
211
 
213
212
  const signature = signatureOf(columns);
214
213
  let current = layout;
214
+
215
215
  if (layout.signature !== signature || layout.rows !== rows || layout.notation !== notation) {
216
216
  const same = layout.signature === signature;
217
217
  const estimated = estimateWidths(columns, rows, notation);
218
+
218
219
  current = {
219
220
  signature,
220
221
  rows,
@@ -222,6 +223,7 @@ export function DataGrid({
222
223
  estimated: same ? growWidths(layout.estimated, estimated) : estimated,
223
224
  ...(same ? { resized: layout.resized, view: layout.view } : restoreLayout(columns, initialLayout))
224
225
  };
226
+
225
227
  setLayout(current);
226
228
  }
227
229
 
@@ -229,10 +231,12 @@ export function DataGrid({
229
231
  const order = useMemo(() => displayOrder(columns.length, current.view), [columns.length, current.view]);
230
232
  const pinnedShown = pinnedCount(columns.length, current.view);
231
233
  const displayOf = useMemo(() => new Map(order.map((index, position) => [index, position])), [order]);
234
+
232
235
  const widths = useMemo(
233
236
  () => columns.map((_, index) => current.resized[index] ?? current.estimated[index] ?? clampColumnWidth(0)),
234
237
  [columns, current.resized, current.estimated]
235
238
  );
239
+
236
240
  const shownWidths = useMemo(() => order.map((index) => widths[index]!), [order, widths]);
237
241
  const offsets = columnOffsets(shownWidths);
238
242
  const gutter = gutterWidth(rows.length);
@@ -241,12 +245,17 @@ export function DataGrid({
241
245
  const viewportHeight = size.height > 0 ? size.height : FALLBACK_VIEWPORT_HEIGHT;
242
246
  const range = drawnRange(scrolledBlock, viewportHeight, rows.length);
243
247
  const selection = selectedKeys ?? ownSelection;
248
+
244
249
  const isShown = (position: CellPosition | null): position is CellPosition =>
245
250
  position !== null && position.row < rows.length && displayOf.has(position.column);
251
+
246
252
  const focusCell = isShown(focused) ? focused : null;
247
253
  const anchorCell = isShown(rangeAnchor) ? rangeAnchor : null;
254
+
248
255
  const toDisplay = (position: CellPosition): CellPosition => ({ row: position.row, column: displayOf.get(position.column)! });
256
+
249
257
  const fromDisplay = (position: CellPosition): CellPosition => ({ row: position.row, column: order[position.column]! });
258
+
250
259
  const rect = focusCell === null ? null : rectBetween(toDisplay(anchorCell ?? focusCell), toDisplay(focusCell));
251
260
  const page = Math.max(1, Math.floor((viewportHeight - HEADER_HEIGHT) / ROW_HEIGHT));
252
261
  const sortable = onSortsChange !== undefined;
@@ -259,7 +268,9 @@ export function DataGrid({
259
268
  const cellBlock: RangeBlock | null = rect === null ? null : { top: rect.top, bottom: rect.bottom, columns: order.slice(rect.left, rect.right + 1) };
260
269
  const block = columnBlock ?? cellBlock;
261
270
  const blockKey = block === null ? '' : `${block.top}:${block.bottom}:${block.columns.join(',')}`;
271
+
262
272
  const headerId = (column: number): string => `${gridId}-h-${column}`;
273
+
263
274
  const enumTypes = useMemo(() => columns.map((column) => parseEnumType(column.type)), [columns]);
264
275
 
265
276
  useEffect(() => {
@@ -270,6 +281,7 @@ export function DataGrid({
270
281
 
271
282
  useEffect(() => {
272
283
  const captured = captureLayout(columns, current);
284
+
273
285
  if (!sameLayout(captured, reportedLayout.current)) {
274
286
  reportedLayout.current = captured;
275
287
  reportLayout.current?.(captured);
@@ -283,8 +295,10 @@ export function DataGrid({
283
295
  useEffect(() => {
284
296
  if (blockKey === '') {
285
297
  reportRange.current?.(null);
298
+
286
299
  return;
287
300
  }
301
+
288
302
  const [top, bottom, list] = blockKey.split(':');
289
303
  reportRange.current?.({ top: Number(top), bottom: Number(bottom), columns: list === '' ? [] : list!.split(',').map(Number) });
290
304
  }, [blockKey]);
@@ -293,9 +307,12 @@ export function DataGrid({
293
307
  if (focusRequest === null || focusRequest === undefined) {
294
308
  return;
295
309
  }
310
+
296
311
  const index = rows.findIndex((row) => row.key === focusRequest.rowKey);
312
+
297
313
  if (index >= 0) {
298
314
  focusAt({ row: index, column: focusRequest.column });
315
+
299
316
  if (focusRequest.quiet !== true) {
300
317
  scroller.current?.focus();
301
318
  }
@@ -318,8 +335,10 @@ export function DataGrid({
318
335
  const stop = (): void => {
319
336
  dragging.current = false;
320
337
  };
338
+
321
339
  window.addEventListener('pointerup', stop);
322
340
  window.addEventListener('pointercancel', stop);
341
+
323
342
  return () => {
324
343
  window.removeEventListener('pointerup', stop);
325
344
  window.removeEventListener('pointercancel', stop);
@@ -340,19 +359,24 @@ export function DataGrid({
340
359
  const canEdit = (position: CellPosition): boolean => {
341
360
  const row = rows[position.row];
342
361
  const column = columns[position.column];
362
+
343
363
  if (!editable || row === undefined || column === undefined || row.locked === true || row.state === 'deleted' || column.readOnly === true) {
344
364
  return false;
345
365
  }
366
+
346
367
  return !isPreview(row.cells[position.column] ?? null) || loadValue !== undefined;
347
368
  };
348
369
 
349
370
  const revealColumn = (column: number): void => {
350
371
  const element = scroller.current;
351
372
  const at = displayOf.get(column);
373
+
352
374
  if (element === null || at === undefined || at < pinnedShown) {
353
375
  return;
354
376
  }
377
+
355
378
  const left = scrollToReveal(gutter + (offsets[at] ?? 0), shownWidths[at] ?? 0, element.scrollLeft, element.clientWidth, gutter + pinnedWidth);
379
+
356
380
  if (left !== element.scrollLeft) {
357
381
  element.scrollLeft = left;
358
382
  }
@@ -360,13 +384,17 @@ export function DataGrid({
360
384
 
361
385
  const reveal = (position: CellPosition): void => {
362
386
  const element = scroller.current;
387
+
363
388
  if (element === null) {
364
389
  return;
365
390
  }
391
+
366
392
  const top = scrollToReveal(HEADER_HEIGHT + position.row * ROW_HEIGHT, ROW_HEIGHT, element.scrollTop, element.clientHeight, HEADER_HEIGHT);
393
+
367
394
  if (top !== element.scrollTop) {
368
395
  element.scrollTop = top;
369
396
  }
397
+
370
398
  revealColumn(position.column);
371
399
  };
372
400
 
@@ -385,6 +413,7 @@ export function DataGrid({
385
413
 
386
414
  const step = (from: CellPosition | null, input: NavigationKey): CellPosition | null => {
387
415
  const next = moveFocus(isShown(from) ? toDisplay(from) : null, input, { rows: rows.length, columns: order.length, page });
416
+
388
417
  return next === null ? null : fromDisplay(next);
389
418
  };
390
419
 
@@ -392,31 +421,41 @@ export function DataGrid({
392
421
  if (selectedKeys === undefined) {
393
422
  setOwnSelection(next);
394
423
  }
424
+
395
425
  onSelectedKeysChange?.(next);
396
426
  };
397
427
 
398
428
  const beginEdit = async (position: CellPosition): Promise<void> => {
399
429
  const row = rows[position.row];
430
+
400
431
  if (row === undefined || !canEdit(position)) {
401
432
  return;
402
433
  }
434
+
403
435
  const cell = row.cells[position.column] ?? null;
404
436
  settled.current = false;
437
+
405
438
  if (!isPreview(cell)) {
406
439
  const text = draftOf(cell);
407
440
  setEditing({ ...position, draft: text, initial: text, loading: false });
441
+
408
442
  return;
409
443
  }
444
+
410
445
  setEditing({ ...position, draft: '', initial: '', loading: true });
411
446
  const value = await loadValue?.(row.key, position.column);
447
+
412
448
  setEditing((now) => {
413
449
  if (now === null || !now.loading || now.row !== position.row || now.column !== position.column) {
414
450
  return now;
415
451
  }
452
+
416
453
  if (value === undefined) {
417
454
  return null;
418
455
  }
456
+
419
457
  const text = draftOf(value);
458
+
420
459
  return { ...now, draft: text, initial: text, loading: false };
421
460
  });
422
461
  };
@@ -425,16 +464,21 @@ export function DataGrid({
425
464
  if (editing === null || settled.current) {
426
465
  return;
427
466
  }
467
+
428
468
  settled.current = true;
429
469
  const column = columns[editing.column];
430
470
  const row = rows[editing.row];
471
+
431
472
  if (commit && !editing.loading && editing.draft !== editing.initial && column !== undefined && row !== undefined) {
432
473
  onCommit?.(row.key, editing.column, parseDraft(editing.draft, column.kind));
433
474
  }
475
+
434
476
  setEditing(null);
435
477
  scroller.current?.focus();
478
+
436
479
  if (move !== undefined) {
437
480
  const next = step(editing, { key: 'Tab', shiftKey: move === 'previous', mod: false });
481
+
438
482
  if (next !== null) {
439
483
  focusAt(next);
440
484
  }
@@ -445,33 +489,40 @@ export function DataGrid({
445
489
  if (editing === null || settled.current) {
446
490
  return;
447
491
  }
492
+
448
493
  settled.current = true;
449
494
  const row = rows[editing.row];
450
495
  const before = row?.cells[editing.column] ?? null;
496
+
451
497
  if (result !== undefined && row !== undefined && result.value !== (typeof before === 'string' ? before : null)) {
452
498
  onCommit?.(row.key, editing.column, result.value);
453
499
  }
500
+
454
501
  setEditing(null);
455
502
  scroller.current?.focus();
456
503
  };
457
504
 
458
505
  const followCell = (cell: FocusedCell): boolean => onFollow !== undefined && (canFollow?.(cell) ?? true);
459
506
 
507
+ const menuCell: CellPosition | null = menuTarget === null || menuTarget.column === null ? null : { row: menuTarget.row, column: menuTarget.column };
508
+
460
509
  const commitValue = (value: EditValue): void => {
461
- const position = menuTarget === null || menuTarget.column === null ? null : { row: menuTarget.row, column: menuTarget.column };
462
- const row = position === null ? undefined : rows[position.row];
463
- if (position !== null && row !== undefined && canEdit(position)) {
464
- onCommit?.(row.key, position.column, value);
510
+ const row = menuCell === null ? undefined : rows[menuCell.row];
511
+
512
+ if (menuCell !== null && row !== undefined && canEdit(menuCell)) {
513
+ onCommit?.(row.key, menuCell.column, value);
465
514
  }
466
515
  };
467
516
 
468
517
  /* The cells from row `top` to `bottom` in the given columns, with the whole value of every preview `loadValue` can fetch. */
469
518
  const wholeCellsOf = async (top: number, bottom: number, indexes: readonly number[]): Promise<readonly (readonly Shown[])[]> => {
470
519
  const shown = rows.slice(top, bottom + 1);
520
+
471
521
  const whole = await loadWholeCells(
472
522
  shown.map((row) => indexes.map((index) => row.cells[index] ?? null)),
473
523
  loadValue === undefined ? undefined : (row, column) => loadValue(shown[row]!.key, indexes[column]!)
474
524
  );
525
+
475
526
  if (whole.previews > 0) {
476
527
  onNotice?.({
477
528
  tone: 'error',
@@ -479,6 +530,7 @@ export function DataGrid({
479
530
  description: t(whole.tooLarge ? 'grid.copyPreviews.tooLarge' : 'grid.copyPreviews.unavailable')
480
531
  });
481
532
  }
533
+
482
534
  return whole.rows;
483
535
  };
484
536
 
@@ -486,8 +538,10 @@ export function DataGrid({
486
538
  if (block === null) {
487
539
  return;
488
540
  }
541
+
489
542
  const picked = block.columns;
490
543
  const cells = await wholeCellsOf(block.top, block.bottom, picked);
544
+
491
545
  await copyText(
492
546
  formatCopy(format, {
493
547
  columns: picked.map((index) => ({ name: columns[index]!.name, kind: columns[index]!.kind })),
@@ -512,13 +566,14 @@ export function DataGrid({
512
566
  };
513
567
 
514
568
  const copyCell = (): void => {
515
- if (menuTarget !== null && menuTarget.column !== null && rows[menuTarget.row] !== undefined) {
516
- void copyOne(menuTarget.row, menuTarget.column);
569
+ if (menuCell !== null && rows[menuCell.row] !== undefined) {
570
+ void copyOne(menuCell.row, menuCell.column);
517
571
  }
518
572
  };
519
573
 
520
574
  const selectAll = (): void => {
521
575
  const all = fullRect(rows.length, order.length);
576
+
522
577
  if (all !== null) {
523
578
  setColumnSelection(NO_COLUMN_SELECTION);
524
579
  setRangeAnchor(fromDisplay({ row: all.top, column: all.left }));
@@ -534,8 +589,10 @@ export function DataGrid({
534
589
  index,
535
590
  click
536
591
  );
592
+
537
593
  anchor.current = result.anchor;
538
594
  setSelection(result.selected);
595
+
539
596
  if (!click.mod && order.length > 0) {
540
597
  setColumnSelection(NO_COLUMN_SELECTION);
541
598
  setRangeAnchor({ row: result.anchor, column: order[order.length - 1]! });
@@ -547,14 +604,18 @@ export function DataGrid({
547
604
  if (event.target !== event.currentTarget || event.altKey) {
548
605
  return;
549
606
  }
607
+
550
608
  if (editing !== null) {
551
609
  if (event.key === 'Escape') {
552
610
  finishEdit(false);
553
611
  }
612
+
554
613
  return;
555
614
  }
615
+
556
616
  const mod = isModHeld(event, isApplePlatform());
557
617
  const key = event.key.toLowerCase();
618
+
558
619
  if (mod && key === 'c') {
559
620
  event.preventDefault();
560
621
  copyRange();
@@ -563,8 +624,10 @@ export function DataGrid({
563
624
  selectAll();
564
625
  } else if (mod && key === 'd' && onDuplicateRows !== undefined) {
565
626
  event.preventDefault();
627
+
566
628
  const keys =
567
629
  selection.size > 0 ? rows.filter((row) => selection.has(row.key)).map((row) => row.key) : focusedRowKey === null ? [] : [focusedRowKey];
630
+
568
631
  if (keys.length > 0) {
569
632
  onDuplicateRows(keys);
570
633
  }
@@ -582,6 +645,7 @@ export function DataGrid({
582
645
  setColumnSelection(NO_COLUMN_SELECTION);
583
646
  } else {
584
647
  const next = step(focusCell, { key: event.key, shiftKey: event.shiftKey, mod });
648
+
585
649
  if (next !== null) {
586
650
  event.preventDefault();
587
651
  focusAt(next, event.shiftKey && event.key !== 'Tab');
@@ -593,12 +657,16 @@ export function DataGrid({
593
657
  if (event.button !== 0 || isEditing) {
594
658
  return;
595
659
  }
660
+
596
661
  const row = rows[position.row];
662
+
597
663
  if (row !== undefined && isModHeld(event, isApplePlatform()) && followCell({ rowKey: row.key, column: position.column })) {
598
664
  focusAt(position);
599
665
  onFollow?.({ rowKey: row.key, column: position.column });
666
+
600
667
  return;
601
668
  }
669
+
602
670
  const extend = event.shiftKey && focusCell !== null;
603
671
  dragOrigin.current = extend ? (anchorCell ?? focusCell) : position;
604
672
  dragging.current = true;
@@ -616,14 +684,17 @@ export function DataGrid({
616
684
  if (!rectContains(rect, position.row, displayOf.get(position.column)!)) {
617
685
  focusAt(position);
618
686
  }
687
+
619
688
  setMenuTarget(position);
620
689
  };
621
690
 
622
691
  const openRowMenu = (index: number): void => {
623
692
  const row = rows[index];
693
+
624
694
  if (row !== undefined && !selection.has(row.key)) {
625
695
  selectRowAt(index, { shiftKey: false, mod: false });
626
696
  }
697
+
627
698
  setMenuTarget({ row: index, column: null });
628
699
  };
629
700
 
@@ -633,15 +704,9 @@ export function DataGrid({
633
704
 
634
705
  const fitColumn = (index: number): void => {
635
706
  const column = columns[index];
707
+
636
708
  if (column !== undefined) {
637
- resizeColumn(
638
- index,
639
- fitColumnWidth(
640
- column.name,
641
- rows.map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation)))),
642
- column.primaryKey === true
643
- )
644
- );
709
+ resizeColumn(index, fitColumnWidth(column.name, columnTexts(column, index, rows, notation), column.primaryKey === true));
645
710
  }
646
711
  };
647
712
 
@@ -684,14 +749,17 @@ export function DataGrid({
684
749
  const stableDragTo = useStableCallback(dragTo);
685
750
  const stableBeginEdit = useStableCallback((position: CellPosition) => void beginEdit(position));
686
751
  const stableOpenCellMenu = useStableCallback(openCellMenu);
752
+
687
753
  const stablePressRowNumber = useStableCallback((index: number, event: MouseEvent) => {
688
754
  if (!pressedInPopup()) {
689
755
  selectRowAt(index, { shiftKey: event.shiftKey, mod: isModHeld(event, isApplePlatform()) });
690
756
  }
691
757
  });
758
+
692
759
  const stableOpenRowMenu = useStableCallback(openRowMenu);
693
760
  const stableFinishPick = useStableCallback(finishPick);
694
761
  const stableFinishEdit = useStableCallback(finishEdit);
762
+
695
763
  const rowActions = useMemo(
696
764
  (): RowActions => ({
697
765
  pressCell: stablePressCell,
@@ -710,6 +778,7 @@ export function DataGrid({
710
778
  const renderRow = (index: number): ReactNode => {
711
779
  const row = rows[index]!;
712
780
  const inRect = columnBlock === null && rect !== null && index >= rect.top && index <= rect.bottom;
781
+
713
782
  return (
714
783
  <GridRowView
715
784
  key={row.key}
@@ -728,6 +797,7 @@ export function DataGrid({
728
797
 
729
798
  const headerCells = order.map((columnIndex, position) => {
730
799
  const column = columns[columnIndex]!;
800
+
731
801
  return (
732
802
  <GridHeaderCell
733
803
  key={columnIndex}
@@ -752,11 +822,11 @@ export function DataGrid({
752
822
  });
753
823
 
754
824
  const menuRow = menuTarget === null ? undefined : rows[menuTarget.row];
755
- const menuCell: CellPosition | null = menuTarget === null || menuTarget.column === null ? null : { row: menuTarget.row, column: menuTarget.column };
756
825
  const settable = menuCell !== null && canEdit(menuCell);
757
- const nullable = menuCell !== null && columns[menuCell.column]?.nullable !== false;
758
826
  const menuColumn = menuCell === null ? undefined : columns[menuCell.column];
827
+ const nullable = menuColumn?.nullable !== false;
759
828
  const hasDefault = menuColumn !== undefined && ((menuColumn.defaultValue ?? null) !== null || menuColumn.autoIncrement === true);
829
+
760
830
  const menuContext: GridMenuContext | null =
761
831
  menuRow === undefined || menuTarget === null
762
832
  ? null
@@ -38,7 +38,9 @@ const LOOK = {
38
38
  export function EnumPicker({ type, value, nullable, label, autoOpen = false, disabled = false, onDone, look = 'cell', className, ref }: EnumPickerProps) {
39
39
  const { t } = useTranslation('database');
40
40
  const set = type.kind === 'set';
41
+
41
42
  const membersOfValue = (): ReadonlySet<string> => new Set(set && value !== null ? membersOf(value) : []);
43
+
42
44
  const settled = useRef(false);
43
45
  const [members, setMembers] = useState(membersOfValue);
44
46
 
@@ -53,8 +55,10 @@ export function EnumPicker({ type, value, nullable, label, autoOpen = false, dis
53
55
  if (open) {
54
56
  settled.current = false;
55
57
  setMembers(membersOfValue());
58
+
56
59
  return;
57
60
  }
61
+
58
62
  const joined = joinMembers(type, members);
59
63
  settle(set && joined !== value ? { value: joined } : undefined);
60
64
  };
@@ -62,11 +66,13 @@ export function EnumPicker({ type, value, nullable, label, autoOpen = false, dis
62
66
  const toggle = (member: string, checked: boolean): void => {
63
67
  setMembers((now) => {
64
68
  const next = new Set(now);
69
+
65
70
  if (checked) {
66
71
  next.add(member);
67
72
  } else {
68
73
  next.delete(member);
69
74
  }
75
+
70
76
  return next;
71
77
  });
72
78
  };