@dxos/react-ui-table 0.8.4-main.d05673bc65 → 0.8.4-main.d9fc60f731

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