@dxos/react-ui-table 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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 (176) hide show
  1. package/dist/lib/browser/chunk-FBURZBPH.mjs +70 -0
  2. package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  4. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-YGC7LZJN.mjs +1695 -0
  6. package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +1139 -1193
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/testing/index.mjs +33 -42
  11. package/dist/lib/browser/testing/index.mjs.map +3 -3
  12. package/dist/lib/browser/translations.mjs +36 -0
  13. package/dist/lib/browser/translations.mjs.map +7 -0
  14. package/dist/lib/browser/types/index.mjs +4 -3
  15. package/dist/lib/node-esm/chunk-DKKMSVKP.mjs +71 -0
  16. package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
  17. package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs +1697 -0
  18. package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +1139 -1193
  22. package/dist/lib/node-esm/index.mjs.map +4 -4
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/testing/index.mjs +33 -42
  25. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  26. package/dist/lib/node-esm/translations.mjs +37 -0
  27. package/dist/lib/node-esm/translations.mjs.map +7 -0
  28. package/dist/lib/node-esm/types/index.mjs +4 -3
  29. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
  34. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +23 -39
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Relations.stories.d.ts +96 -29
  38. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/Table.d.ts +20 -18
  41. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  42. package/dist/types/src/components/Table/Table.stories.d.ts +96 -31
  43. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/Table/TableContent.d.ts +17 -0
  45. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  46. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  48. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  50. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -12
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  52. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -7
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +25 -28
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  57. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  58. package/dist/types/src/components/index.d.ts +0 -1
  59. package/dist/types/src/components/index.d.ts.map +1 -1
  60. package/dist/types/src/components/testing.d.ts +23 -16
  61. package/dist/types/src/components/testing.d.ts.map +1 -1
  62. package/dist/types/src/hooks/index.d.ts +2 -1
  63. package/dist/types/src/hooks/index.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  65. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  66. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  67. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  68. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  69. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  70. package/dist/types/src/index.d.ts +0 -2
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/model/modal-controller.d.ts +5 -8
  73. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  74. package/dist/types/src/model/selection-model.d.ts +18 -9
  75. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  76. package/dist/types/src/model/table-model.d.ts +106 -26
  77. package/dist/types/src/model/table-model.d.ts.map +1 -1
  78. package/dist/types/src/model/table-presentation.d.ts +3 -1
  79. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  80. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  81. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  82. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  83. package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
  84. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  85. package/dist/types/src/translations.d.ts +21 -23
  86. package/dist/types/src/translations.d.ts.map +1 -1
  87. package/dist/types/src/types/Table.d.ts +26 -0
  88. package/dist/types/src/types/Table.d.ts.map +1 -0
  89. package/dist/types/src/types/index.d.ts +1 -1
  90. package/dist/types/src/types/index.d.ts.map +1 -1
  91. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  92. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  93. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  94. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  95. package/dist/types/src/util/index.d.ts +2 -0
  96. package/dist/types/src/util/index.d.ts.map +1 -1
  97. package/dist/types/src/util/query-ast.d.ts +7 -0
  98. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  99. package/dist/types/src/util/schema.d.ts +1 -1
  100. package/dist/types/src/util/schema.d.ts.map +1 -1
  101. package/dist/types/src/util/signals.d.ts.map +1 -1
  102. package/dist/types/src/util/sort.d.ts +12 -0
  103. package/dist/types/src/util/sort.d.ts.map +1 -0
  104. package/dist/types/src/util/table-buttons.d.ts +20 -20
  105. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  106. package/dist/types/src/util/table-controls.d.ts +4 -4
  107. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  108. package/dist/types/src/util/tag.d.ts.map +1 -1
  109. package/dist/types/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +67 -76
  111. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  112. package/src/components/Table/ColumnSettings.tsx +8 -9
  113. package/src/components/Table/DynamicTable.stories.tsx +34 -33
  114. package/src/components/Table/DynamicTable.tsx +33 -36
  115. package/src/components/Table/Relations.stories.tsx +196 -48
  116. package/src/components/Table/RowActionsMenu.tsx +27 -17
  117. package/src/components/Table/Table.stories.tsx +144 -127
  118. package/src/components/Table/Table.tsx +43 -403
  119. package/src/components/Table/TableContent.tsx +421 -0
  120. package/src/components/Table/TableToolbar.tsx +111 -0
  121. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  122. package/src/components/TableCellEditor/FormCellEditor.tsx +125 -131
  123. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +32 -55
  124. package/src/components/TableCellEditor/TableCellEditor.tsx +39 -124
  125. package/src/components/TableCellEditor/index.ts +1 -0
  126. package/src/components/index.ts +0 -1
  127. package/src/components/testing.ts +44 -18
  128. package/src/hooks/index.ts +2 -1
  129. package/src/hooks/useAddRow.ts +8 -10
  130. package/src/hooks/useProjectionModel.ts +39 -0
  131. package/src/hooks/useTableModel.ts +36 -34
  132. package/src/index.ts +0 -4
  133. package/src/model/modal-controller.ts +23 -40
  134. package/src/model/selection-model.ts +75 -44
  135. package/src/model/table-model.test.ts +93 -56
  136. package/src/model/table-model.ts +437 -170
  137. package/src/model/table-presentation.test.ts +67 -56
  138. package/src/model/table-presentation.ts +45 -41
  139. package/src/playwright/TableManager.ts +13 -6
  140. package/src/playwright/playwright.config.ts +1 -1
  141. package/src/playwright/smoke.spec.ts +59 -13
  142. package/src/testing/useTestTableModel.ts +48 -39
  143. package/src/translations.ts +20 -22
  144. package/src/types/Table.ts +79 -0
  145. package/src/types/index.ts +1 -1
  146. package/src/util/dynamic-table.test.ts +70 -0
  147. package/src/util/dynamic-table.ts +43 -31
  148. package/src/util/index.ts +2 -0
  149. package/src/util/query-ast.ts +22 -0
  150. package/src/util/schema.ts +4 -4
  151. package/src/util/sort.ts +90 -0
  152. package/src/util/table-buttons.ts +1 -1
  153. package/src/util/table-controls.ts +2 -2
  154. package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
  155. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-GPZSXPSA.mjs +0 -1822
  157. package/dist/lib/browser/chunk-GPZSXPSA.mjs.map +0 -7
  158. package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
  159. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  160. package/dist/lib/node-esm/chunk-XUD6W42C.mjs +0 -1823
  161. package/dist/lib/node-esm/chunk-XUD6W42C.mjs.map +0 -7
  162. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  163. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  164. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  165. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  166. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  167. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  168. package/dist/types/src/model/table-sorting.d.ts +0 -52
  169. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  170. package/dist/types/src/types/schema.d.ts +0 -28
  171. package/dist/types/src/types/schema.d.ts.map +0 -1
  172. package/src/components/Table/CreateRefPanel.tsx +0 -103
  173. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  174. package/src/components/TableToolbar/index.ts +0 -5
  175. package/src/model/table-sorting.ts +0 -225
  176. package/src/types/schema.ts +0 -73
@@ -2,435 +2,75 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect/Schema';
6
- import React, {
7
- type MouseEvent,
8
- type PropsWithChildren,
9
- type WheelEvent,
10
- forwardRef,
11
- useCallback,
12
- useEffect,
13
- useImperativeHandle,
14
- useMemo,
15
- useState,
16
- } from 'react';
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import { createContext } from '@radix-ui/react-context';
7
+ import React, { type PropsWithChildren, type RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
17
8
 
18
- import { type Client } from '@dxos/react-client';
19
- // TODO(wittjosiah): Remove dependency on react-client.
20
- import { useAttention } from '@dxos/react-ui-attention';
21
- import {
22
- type DxGridElement,
23
- type DxGridPlane,
24
- type DxGridPlaneRange,
25
- type DxGridPosition,
26
- Grid,
27
- type GridContentProps,
28
- closestCell,
29
- gridSeparatorBlockEnd,
30
- gridSeparatorInlineEnd,
31
- } from '@dxos/react-ui-grid';
32
- import { DxEditRequest } from '@dxos/react-ui-grid';
33
- import { mx } from '@dxos/react-ui-theme';
9
+ import { type DxGridAxisMeta, type DxGridPosition } from '@dxos/react-ui-grid';
34
10
 
35
- import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
36
- import { tableButtons, tableControls } from '../../util';
37
- import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
38
-
39
- import { ColumnActionsMenu } from './ColumnActionsMenu';
40
- import { ColumnSettings } from './ColumnSettings';
41
- import { CreateRefPanel } from './CreateRefPanel';
42
- import { RowActionsMenu } from './RowActionsMenu';
11
+ import { type InsertRowResult } from '../../model';
12
+ import { TableContent } from './TableContent';
13
+ import { TableToolbar } from './TableToolbar';
43
14
 
44
15
  const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
45
16
  const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
17
+ const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
46
18
 
47
19
  //
48
- // Table.Root
20
+ // Context
49
21
  //
50
22
 
51
- export type TableRootProps = PropsWithChildren<{ role?: string }>;
52
-
53
- const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
54
- return (
55
- <div
56
- role='none'
57
- className={mx(
58
- 'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
59
- role === 'card--popover' && 'popover-card-height',
60
- role === 'section' && 'attention-surface',
61
- role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
62
- ['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
63
- ['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
64
- )}
65
- >
66
- {children}
67
- </div>
68
- );
23
+ type TableContextValue = {
24
+ /** Mutable ref populated by Content so Root can expose the controller. */
25
+ controllerRef: RefObject<TableController>;
69
26
  };
70
27
 
28
+ const [TableContextProvider, useTableContext] = createContext<TableContextValue>('Table');
29
+
71
30
  //
72
- // Table.Main
31
+ // Controller
73
32
  //
74
33
 
75
- export type TableController = {
34
+ type TableController = {
76
35
  update?: (cell?: DxGridPosition) => void;
77
36
  handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
78
37
  };
79
38
 
80
- export type TableMainProps = {
81
- model?: TableModel;
82
- presentation?: TablePresentation;
83
- schema?: Schema.AnyNoContext;
84
- client?: Client;
85
- // TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
86
- ignoreAttention?: boolean;
87
- onRowClick?: (row: any) => void;
88
- };
89
-
90
- const TableMain = forwardRef<TableController, TableMainProps>(
91
- ({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
92
- const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
93
- const { hasAttention } = useAttention(model?.id ?? 'table');
94
- const modals = useMemo(() => new ModalController(), []);
95
-
96
- const draftRowCount = model?.getDraftRowCount() ?? 0;
97
-
98
- const handleSave = useCallback(() => {
99
- dxGrid?.updateCells(true);
100
- dxGrid?.requestUpdate();
101
- }, [dxGrid]);
102
-
103
- const frozen = useMemo(() => {
104
- const noActionColumn =
105
- model?.features.dataEditable === false &&
106
- model?.features.schemaEditable === false &&
107
- model.rowActions.length === 0;
108
-
109
- return {
110
- frozenRowsStart: 1,
111
- frozenRowsEnd: Math.max(1, draftRowCount),
112
- frozenColsStart: model?.features.selection.enabled ? 1 : 0,
113
- frozenColsEnd: noActionColumn ? 0 : 1,
114
- };
115
- }, [model, draftRowCount]);
116
-
117
- const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
118
- (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
119
- [presentation],
120
- );
121
-
122
- useEffect(() => {
123
- if (!presentation || !dxGrid) {
124
- return;
125
- }
126
- dxGrid.getCells = getCells;
127
- }, [presentation, dxGrid, getCells]);
128
-
129
- const handleInsertRowResult = useCallback(
130
- (insertResult?: InsertRowResult) => {
131
- if (insertResult === 'draft') {
132
- requestAnimationFrame(() => {
133
- dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
134
- dxGrid?.refocus();
135
- });
136
- } else {
137
- requestAnimationFrame(() => {
138
- dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
139
- dxGrid?.refocus();
140
- });
141
- }
142
- },
143
- [model, dxGrid],
144
- );
145
-
146
- /**
147
- * Provides an external controller that can be called to repaint the table.
148
- */
149
- useImperativeHandle<TableController, TableController>(forwardedRef, () => {
150
- if (!presentation || !dxGrid) {
151
- return {};
152
- }
153
-
154
- return {
155
- update: (cell) => {
156
- if (cell) {
157
- dxGrid.updateIfWithinBounds(cell, true);
158
- } else {
159
- dxGrid.updateCells(true);
160
- dxGrid.requestUpdate();
161
- }
162
- },
163
- handleInsertRowResult,
164
- };
165
- }, [presentation, dxGrid, model]);
166
-
167
- const handleSaveDraftRow = useCallback(
168
- (rowIndex = 0) => {
169
- if (model && dxGrid) {
170
- const didCommitSuccessfully = model.commitDraftRow(rowIndex);
171
- if (didCommitSuccessfully) {
172
- requestAnimationFrame(() => {
173
- dxGrid.scrollToEndRow();
174
- });
175
- }
176
- }
177
- },
178
- [model, dxGrid],
179
- );
180
-
181
- const handleGridClick = useCallback(
182
- (event: MouseEvent) => {
183
- const cell = closestCell(event.target as HTMLElement);
184
- if (cell) {
185
- const { row: rowIndex, plane } = cell;
186
- if (onRowClick) {
187
- if (plane === 'grid') {
188
- const row = model?.getRowAt(rowIndex);
189
- row && onRowClick(row);
190
- } else {
191
- onRowClick(cell);
192
- }
193
- }
194
-
195
- if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
196
- model.selection.toggleSelectionForRowIndex(rowIndex);
197
- }
198
- }
199
-
200
- if (!modals) {
201
- return;
202
- }
203
-
204
- const buttonSelector = Object.values(tableButtons)
205
- .map((button) => `button[${button.attr}]`)
206
- .join(',');
207
-
208
- const tableButtonElement = (event.target as HTMLElement).closest(buttonSelector) as HTMLElement;
209
- if (tableButtonElement) {
210
- const button = Object.values(tableButtons).find((btn) => tableButtonElement.hasAttribute(btn.attr));
211
- if (!button) {
212
- return;
213
- }
214
-
215
- modals.setTrigger(tableButtonElement);
216
- const data = button.getData(tableButtonElement);
217
- switch (data.type) {
218
- case 'rowMenu': {
219
- modals.showRowMenu(data.rowIndex);
220
- break;
221
- }
222
- case 'columnSettings': {
223
- modals.showColumnMenu(data.fieldId);
224
- break;
225
- }
226
- case 'newColumn': {
227
- modals.showColumnCreator();
228
- break;
229
- }
230
- case 'sort': {
231
- model?.sorting?.toggleSort(data.fieldId);
232
- break;
233
- }
234
- case 'saveDraftRow': {
235
- handleSaveDraftRow();
236
- break;
237
- }
238
- }
239
- return;
240
- }
241
-
242
- const controlSelector = Object.values(tableControls)
243
- .map((control) => `input[${control.attr}]`)
244
- .join(',');
245
-
246
- const controlElement = (event.target as HTMLElement).closest(controlSelector) as HTMLElement;
247
- if (controlElement) {
248
- const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
249
- if (!control) {
250
- return;
251
- }
252
-
253
- const data = control.getData(controlElement);
254
- switch (data.type) {
255
- case 'checkbox': {
256
- if (data.header) {
257
- model?.selection.setSelection(model.selection.allRowsSeleted.value ? 'none' : 'all');
258
- } else {
259
- model?.selection.toggleSelectionForRowIndex(data.rowIndex);
260
- }
261
- break;
262
- }
263
- case 'switch': {
264
- if (model) {
265
- model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
266
- }
267
- break;
268
- }
269
- }
270
- }
271
- },
272
- [model, modals, dxGrid],
273
- );
274
-
275
- const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
276
- (increment, delta, cell) => {
277
- if (dxGrid && model) {
278
- if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
279
- handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
280
- } else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
281
- handleSaveDraftRow(cell.row);
282
- handleInsertRowResult(model.insertRow());
283
- } else {
284
- dxGrid.refocus(increment, delta);
285
- }
286
- }
287
- },
288
- [dxGrid, model],
289
- );
290
-
291
- const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
292
- (event) => {
293
- if (!model?.features.dataEditable) {
294
- return;
295
- }
296
-
297
- const cell = closestCell(event.target);
298
- if (!model || !cell) {
299
- return;
300
- }
39
+ //
40
+ // Root
41
+ //
301
42
 
302
- // Handle Meta+C (Copy) and Meta+V (Paste) commands
303
- if (event.metaKey || event.ctrlKey) {
304
- switch (event.key) {
305
- case 'c': {
306
- // Copy focused cell's text content to clipboard
307
- try {
308
- const cellData = model.getCellData(cell);
309
- const textContent = cellData?.toString() ?? '';
310
- void navigator.clipboard.writeText(textContent);
311
- event.preventDefault();
312
- } catch (error) {
313
- console.warn('Failed to copy cell content:', error);
314
- }
315
- break;
316
- }
317
- case 'v': {
318
- // Paste clipboard content to focused cell
319
- event.preventDefault();
320
- void navigator.clipboard.readText().then((clipboardText) => {
321
- try {
322
- // Attempt to set the cell's content to clipboard content
323
- model.setCellData(cell, clipboardText);
324
- handleSave();
325
- } catch {
326
- // If validation fails, emit a DxEditRequest event with initialContent from clipboard
327
- // TODO(thure): Should `dx-grid` expose a method like this?
328
- const cellElement = (event.target as HTMLElement).closest(
329
- '[data-dx-grid-action="cell"]',
330
- ) as HTMLElement;
331
- if (cellElement) {
332
- const rect = cellElement.getBoundingClientRect();
333
- const editRequest = new DxEditRequest({
334
- cellIndex: `${cell.plane},${cell.col},${cell.row}`,
335
- cellBox: {
336
- insetInlineStart: rect.left,
337
- insetBlockStart: rect.top,
338
- inlineSize: rect.width,
339
- blockSize: rect.height,
340
- },
341
- cellElement,
342
- initialContent: clipboardText,
343
- });
344
- cellElement.dispatchEvent(editRequest);
345
- }
346
- }
347
- });
348
- break;
349
- }
350
- }
351
- }
43
+ const TABLE_ROOT_NAME = 'Table.Root';
352
44
 
353
- switch (event.key) {
354
- case 'Backspace':
355
- case 'Delete': {
356
- try {
357
- model.setCellData(cell, undefined);
358
- event.preventDefault();
359
- } catch {
360
- // Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
361
- }
362
- break;
363
- }
364
- }
365
- },
366
- [model, dxGrid, handleSave],
367
- );
45
+ type TableRootProps = PropsWithChildren;
368
46
 
369
- const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
370
- (event) => {
371
- if (event.axis === 'col') {
372
- const columnIndex = parseInt(event.index, 10);
373
- model?.setColumnWidth(columnIndex, event.size);
374
- }
375
- },
376
- [model],
377
- );
47
+ const TableRoot = forwardRef<TableController, TableRootProps>(({ children }, forwardedRef) => {
48
+ const controllerRef = useRef<TableController>({});
378
49
 
379
- const handleWheel = useCallback(
380
- (event: WheelEvent) => {
381
- if (!ignoreAttention && !hasAttention) {
382
- event.stopPropagation();
383
- }
384
- },
385
- [hasAttention, ignoreAttention],
386
- );
50
+ useImperativeHandle(
51
+ forwardedRef,
52
+ () => ({
53
+ update: (cell) => controllerRef.current?.update?.(cell),
54
+ handleInsertRowResult: (result) => controllerRef.current?.handleInsertRowResult?.(result),
55
+ }),
56
+ [],
57
+ );
387
58
 
388
- const handleNewColumn = useCallback(() => {
389
- const columns = model?.getColumnCount();
390
- if (dxGrid && columns) {
391
- dxGrid.scrollToColumn(columns - 1);
392
- }
393
- }, [model, dxGrid]);
59
+ return <TableContextProvider controllerRef={controllerRef}>{children}</TableContextProvider>;
60
+ });
394
61
 
395
- if (!model || !modals) {
396
- return <span role='none' className='attention-surface' />;
397
- }
62
+ TableRoot.displayName = TABLE_ROOT_NAME;
398
63
 
399
- return (
400
- <Grid.Root id={model.id ?? 'table-grid'}>
401
- <TableValueEditor
402
- model={model}
403
- modals={modals}
404
- schema={schema}
405
- onFocus={handleFocus}
406
- onSave={handleSave}
407
- client={client}
408
- />
409
- <Grid.Content
410
- className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
411
- frozen={frozen}
412
- columns={model.columnMeta.value}
413
- columnDefault={columnDefault}
414
- rowDefault={rowDefault}
415
- limitRows={model.getRowCount() ?? 0}
416
- limitColumns={model.projection.fields.length}
417
- overscroll='trap'
418
- onAxisResize={handleAxisResize}
419
- onClick={handleGridClick}
420
- onKeyDownCapture={handleKeyDown}
421
- onWheelCapture={handleWheel}
422
- ref={setDxGrid}
423
- />
424
- <RowActionsMenu model={model} modals={modals} />
425
- <ColumnActionsMenu model={model} modals={modals} />
426
- <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
427
- <CreateRefPanel model={model} modals={modals} />
428
- </Grid.Root>
429
- );
430
- },
431
- );
64
+ //
65
+ // Table
66
+ //
432
67
 
433
68
  export const Table = {
434
69
  Root: TableRoot,
435
- Main: TableMain,
70
+ Toolbar: TableToolbar,
71
+ Content: TableContent,
436
72
  };
73
+
74
+ export type { TableController, TableRootProps };
75
+
76
+ export { useTableContext };