@dxos/react-ui-table 0.8.4-main.3c1ae3b → 0.8.4-main.3fbcb4aa9b

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