@dxos/react-ui-table 0.7.4 → 0.7.5-labs.a279d8c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-IYCSJDMB.mjs → chunk-3FR6NHPD.mjs} +7 -5
- package/dist/lib/browser/chunk-3FR6NHPD.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1405 -1067
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{chunk-MHBMVCFZ.cjs → chunk-NB3SFHGA.cjs} +9 -7
- package/dist/lib/node/chunk-NB3SFHGA.cjs.map +7 -0
- package/dist/lib/node/index.cjs +1409 -1051
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +3 -3
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{chunk-BMWGPXPL.mjs → chunk-RLVVOZW5.mjs} +7 -5
- package/dist/lib/node-esm/chunk-RLVVOZW5.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1405 -1067
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +4 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +3 -2
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/RefPanel.d.ts +4 -3
- package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +3 -2
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +2 -1
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +18 -0
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableToolbar/index.d.ts +2 -0
- package/dist/types/src/components/TableToolbar/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +31 -71
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/index.d.ts +1 -0
- package/dist/types/src/model/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +6 -2
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +19 -31
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +22 -0
- package/dist/types/src/model/table-presentation.d.ts.map +1 -0
- package/dist/types/src/model/table-presentation.test.d.ts +2 -0
- package/dist/types/src/model/table-presentation.test.d.ts.map +1 -0
- package/dist/types/src/model/table-sorting.d.ts +53 -0
- package/dist/types/src/model/table-sorting.d.ts.map +1 -0
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +46 -154
- package/dist/types/src/types/table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +63 -19
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +32 -9
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/table.d.ts +3 -2
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -29
- package/src/components/Table/ColumnActionsMenu.tsx +12 -12
- package/src/components/Table/ColumnSettings.tsx +11 -11
- package/src/components/Table/CreateRefPanel.tsx +13 -14
- package/src/components/Table/RefPanel.tsx +9 -9
- package/src/components/Table/RowActionsMenu.tsx +6 -7
- package/src/components/Table/Table.stories.tsx +67 -38
- package/src/components/Table/Table.tsx +239 -148
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +13 -12
- package/src/components/TableCellEditor/TableCellEditor.tsx +6 -4
- package/src/components/TableToolbar/TableToolbar.tsx +72 -0
- package/src/components/{Toolbar → TableToolbar}/index.ts +1 -1
- package/src/components/index.ts +1 -1
- package/src/components/testing.ts +10 -8
- package/src/index.ts +2 -0
- package/src/model/index.ts +1 -0
- package/src/model/modal-controller.ts +15 -48
- package/src/model/selection-model.ts +12 -1
- package/src/model/table-model.test.ts +4 -76
- package/src/model/table-model.ts +73 -304
- package/src/model/table-presentation.test.ts +102 -0
- package/src/model/table-presentation.ts +223 -0
- package/src/model/table-sorting.ts +201 -0
- package/src/playwright/TableManager.ts +5 -1
- package/src/playwright/smoke.spec.ts +94 -9
- package/src/translations.ts +2 -1
- package/src/types/table.ts +4 -4
- package/src/util/index.ts +1 -0
- package/src/util/table-buttons.ts +127 -48
- package/src/util/table-controls.ts +71 -7
- package/src/util/table.ts +93 -52
- package/dist/lib/browser/chunk-IYCSJDMB.mjs.map +0 -7
- package/dist/lib/node/chunk-MHBMVCFZ.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-BMWGPXPL.mjs.map +0 -7
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +0 -21
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +0 -1
- package/dist/types/src/components/Toolbar/common.d.ts +0 -14
- package/dist/types/src/components/Toolbar/common.d.ts.map +0 -1
- package/dist/types/src/components/Toolbar/index.d.ts +0 -2
- package/dist/types/src/components/Toolbar/index.d.ts.map +0 -1
- package/src/components/Toolbar/Toolbar.tsx +0 -87
- package/src/components/Toolbar/common.tsx +0 -71
|
@@ -9,6 +9,8 @@ import React, {
|
|
|
9
9
|
useImperativeHandle,
|
|
10
10
|
useState,
|
|
11
11
|
type WheelEvent,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
useMemo,
|
|
12
14
|
} from 'react';
|
|
13
15
|
|
|
14
16
|
import { getValue } from '@dxos/echo-schema';
|
|
@@ -25,8 +27,9 @@ import { ColumnSettings } from './ColumnSettings';
|
|
|
25
27
|
import { CreateRefPanel } from './CreateRefPanel';
|
|
26
28
|
import { RefPanel } from './RefPanel';
|
|
27
29
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
28
|
-
import { type TableModel } from '../../model';
|
|
30
|
+
import { ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
29
31
|
import { translationKey } from '../../translations';
|
|
32
|
+
import { tableButtons, tableControls } from '../../util';
|
|
30
33
|
import { createOption, TableCellEditor, type TableCellEditorProps } from '../TableCellEditor';
|
|
31
34
|
|
|
32
35
|
// NOTE(Zan): These fragments add border to inline-end and block-end of the grid using pseudo-elements.
|
|
@@ -70,174 +73,262 @@ export type TableController = {
|
|
|
70
73
|
|
|
71
74
|
export type TableMainProps = {
|
|
72
75
|
model?: TableModel;
|
|
76
|
+
presentation?: TablePresentation;
|
|
73
77
|
ignoreAttention?: boolean;
|
|
74
78
|
};
|
|
75
79
|
|
|
76
|
-
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
81
|
+
({ model, presentation, ignoreAttention }, forwardedRef) => {
|
|
82
|
+
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
83
|
+
const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : 'table');
|
|
84
|
+
const { t } = useTranslation(translationKey);
|
|
85
|
+
const modals = useMemo(() => new ModalController(), []);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Provides an external controller that can be called to repaint the table.
|
|
89
|
+
*/
|
|
90
|
+
useImperativeHandle<TableController, TableController>(
|
|
91
|
+
forwardedRef,
|
|
92
|
+
() => {
|
|
93
|
+
if (!presentation || !dxGrid) {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
|
|
98
|
+
return {
|
|
99
|
+
update: (cell) => {
|
|
100
|
+
if (cell) {
|
|
101
|
+
dxGrid.updateIfWithinBounds(cell);
|
|
102
|
+
} else {
|
|
103
|
+
dxGrid.updateCells(true);
|
|
104
|
+
dxGrid.requestUpdate();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
[presentation, dxGrid],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const handleGridClick = useCallback(
|
|
113
|
+
(event: MouseEvent) => {
|
|
114
|
+
if (!modals) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
90
117
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
const buttonSelector = Object.values(tableButtons)
|
|
119
|
+
.map((button) => `button[${button.attr}]`)
|
|
120
|
+
.join(',');
|
|
121
|
+
|
|
122
|
+
const tableButtonElement = (event.target as HTMLElement).closest(buttonSelector) as HTMLElement;
|
|
123
|
+
if (tableButtonElement) {
|
|
124
|
+
const button = Object.values(tableButtons).find((btn) => tableButtonElement.hasAttribute(btn.attr));
|
|
125
|
+
if (!button) {
|
|
126
|
+
return;
|
|
98
127
|
}
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
[model, dxGrid],
|
|
103
|
-
);
|
|
104
128
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
modals.setTrigger(tableButtonElement);
|
|
130
|
+
const data = button.getData(tableButtonElement);
|
|
131
|
+
switch (data.type) {
|
|
132
|
+
case 'rowMenu': {
|
|
133
|
+
modals.showRowMenu(data.rowIndex);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case 'columnSettings': {
|
|
137
|
+
modals.showColumnMenu(data.fieldId);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'newColumn': {
|
|
141
|
+
modals.showColumnCreator();
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'referencedCell': {
|
|
145
|
+
modals.showReferencePanel(data.targetId, data.schemaId);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'sort': {
|
|
149
|
+
model?.sorting?.toggleSort(data.fieldId);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
113
155
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
156
|
+
const controlSelector = Object.values(tableControls)
|
|
157
|
+
.map((control) => `input[${control.attr}]`)
|
|
158
|
+
.join(',');
|
|
159
|
+
|
|
160
|
+
const controlElement = (event.target as HTMLElement).closest(controlSelector) as HTMLElement;
|
|
161
|
+
if (controlElement) {
|
|
162
|
+
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
163
|
+
if (!control) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const data = control.getData(controlElement);
|
|
168
|
+
switch (data.type) {
|
|
169
|
+
case 'checkbox': {
|
|
170
|
+
if (data.header) {
|
|
171
|
+
model?.selection.setSelection(model.selection.allRowsSeleted.value ? 'none' : 'all');
|
|
172
|
+
} else {
|
|
173
|
+
model?.selection.toggleSelectionForRowIndex(data.rowIndex);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'switch': {
|
|
178
|
+
if (model) {
|
|
179
|
+
model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
[model, modals],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
190
|
+
(increment, delta) => {
|
|
119
191
|
if (dxGrid) {
|
|
120
|
-
|
|
121
|
-
dxGrid?.scrollToRow(cell.row + 1);
|
|
122
|
-
dxGrid?.refocus('row', 1);
|
|
123
|
-
});
|
|
192
|
+
dxGrid.refocus(increment, delta);
|
|
124
193
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
);
|
|
194
|
+
},
|
|
195
|
+
[dxGrid],
|
|
196
|
+
);
|
|
129
197
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
198
|
+
const handleEnter = useCallback<NonNullable<TableCellEditorProps['onEnter']>>(
|
|
199
|
+
(cell) => {
|
|
200
|
+
// TODO(burdon): Insert row only if bottom row isn't completely blank already.
|
|
201
|
+
if (model && cell.row === model.getRowCount() - 1) {
|
|
202
|
+
model.insertRow(cell.row);
|
|
203
|
+
if (dxGrid) {
|
|
204
|
+
requestAnimationFrame(() => {
|
|
205
|
+
dxGrid?.scrollToRow(cell.row + 1);
|
|
206
|
+
dxGrid?.refocus('row', 1);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
[model, dxGrid],
|
|
212
|
+
);
|
|
136
213
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
214
|
+
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
215
|
+
(event) => {
|
|
216
|
+
const cell = closestCell(event.target);
|
|
217
|
+
if (!model || !cell) {
|
|
218
|
+
return;
|
|
142
219
|
}
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
[model],
|
|
146
|
-
);
|
|
147
220
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
221
|
+
switch (event.key) {
|
|
222
|
+
case 'Backspace':
|
|
223
|
+
case 'Delete': {
|
|
224
|
+
model.setCellData(cell, undefined);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
[model],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
233
|
+
(event) => {
|
|
234
|
+
if (event.axis === 'col') {
|
|
235
|
+
const columnIndex = parseInt(event.index, 10);
|
|
236
|
+
model?.setColumnWidth(columnIndex, event.size);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
[model],
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const handleWheel = useCallback(
|
|
243
|
+
(event: WheelEvent) => {
|
|
244
|
+
if (!ignoreAttention && !hasAttention) {
|
|
245
|
+
event.stopPropagation();
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
[hasAttention, ignoreAttention],
|
|
249
|
+
);
|
|
157
250
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
251
|
+
const handleNewColumn = useCallback(() => {
|
|
252
|
+
const columns = model?.getColumnCount();
|
|
253
|
+
if (dxGrid && columns) {
|
|
254
|
+
dxGrid.scrollToColumn(columns - 1);
|
|
162
255
|
}
|
|
163
|
-
},
|
|
164
|
-
[hasAttention, ignoreAttention],
|
|
165
|
-
);
|
|
256
|
+
}, [model, dxGrid]);
|
|
166
257
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
258
|
+
// TODO(burdon): Factor out?
|
|
259
|
+
// TODO(burdon): Generalize to handle other value types (e.g., enums).
|
|
260
|
+
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
261
|
+
async ({ field, props }, text) => {
|
|
262
|
+
if (model && props.referenceSchema && field.referencePath) {
|
|
263
|
+
const space = getSpace(model.table);
|
|
264
|
+
invariant(space);
|
|
265
|
+
const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
266
|
+
if (schema) {
|
|
267
|
+
const { objects } = await space.db.query(Filter.schema(schema)).run();
|
|
268
|
+
const options = objects
|
|
269
|
+
.map((obj) => {
|
|
270
|
+
const value = getValue(obj, field.referencePath!);
|
|
271
|
+
if (!value || typeof value !== 'string') {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
label: value,
|
|
277
|
+
data: obj,
|
|
278
|
+
};
|
|
279
|
+
})
|
|
280
|
+
.filter(isNotFalsy);
|
|
190
281
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
...options,
|
|
200
|
-
{
|
|
201
|
-
label: t('create new object label', { text }),
|
|
202
|
-
data: createOption(text),
|
|
203
|
-
},
|
|
204
|
-
];
|
|
282
|
+
return [
|
|
283
|
+
...options,
|
|
284
|
+
{
|
|
285
|
+
label: t('create new object label', { text }),
|
|
286
|
+
data: createOption(text),
|
|
287
|
+
},
|
|
288
|
+
];
|
|
289
|
+
}
|
|
205
290
|
}
|
|
206
|
-
}
|
|
207
291
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
292
|
+
return [];
|
|
293
|
+
},
|
|
294
|
+
[model],
|
|
295
|
+
);
|
|
212
296
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
297
|
+
if (!model || !modals) {
|
|
298
|
+
return <span role='none' className='attention-surface' />;
|
|
299
|
+
}
|
|
216
300
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
301
|
+
return (
|
|
302
|
+
<Grid.Root id={model.table.id ?? 'table-grid'}>
|
|
303
|
+
<TableCellEditor
|
|
304
|
+
model={model}
|
|
305
|
+
modals={modals}
|
|
306
|
+
onEnter={handleEnter}
|
|
307
|
+
onFocus={handleFocus}
|
|
308
|
+
onQuery={handleQuery}
|
|
309
|
+
/>
|
|
310
|
+
<Grid.Content
|
|
311
|
+
onWheelCapture={handleWheel}
|
|
312
|
+
className={mx('[--dx-grid-base:var(--surface-bg)]', inlineEndLine, blockEndLine)}
|
|
313
|
+
frozen={frozen}
|
|
314
|
+
columns={model.columnMeta.value}
|
|
315
|
+
limitRows={model.getRowCount() ?? 0}
|
|
316
|
+
limitColumns={model.table.view?.target?.fields?.length ?? 0}
|
|
317
|
+
onAxisResize={handleAxisResize}
|
|
318
|
+
onClick={handleGridClick}
|
|
319
|
+
onKeyDown={handleKeyDown}
|
|
320
|
+
overscroll='trap'
|
|
321
|
+
ref={setDxGrid}
|
|
322
|
+
/>
|
|
323
|
+
<RowActionsMenu model={model} modals={modals} />
|
|
324
|
+
<ColumnActionsMenu model={model} modals={modals} />
|
|
325
|
+
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
326
|
+
<RefPanel model={model} modals={modals} />
|
|
327
|
+
<CreateRefPanel model={model} modals={modals} />
|
|
328
|
+
</Grid.Root>
|
|
329
|
+
);
|
|
330
|
+
},
|
|
331
|
+
);
|
|
241
332
|
|
|
242
333
|
//
|
|
243
334
|
// CellEditor
|
|
@@ -7,11 +7,11 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import { type
|
|
10
|
+
import { type EchoSchema } from '@dxos/echo-schema';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { faker } from '@dxos/random';
|
|
13
|
-
import { Filter,
|
|
14
|
-
import { withClientProvider } from '@dxos/react-client/testing';
|
|
13
|
+
import { Filter, useQuery, create } from '@dxos/react-client/echo';
|
|
14
|
+
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
15
15
|
import { defaultSizeRow, Grid, type GridEditing } from '@dxos/react-ui-grid';
|
|
16
16
|
import { ViewProjection, ViewType } from '@dxos/schema';
|
|
17
17
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
@@ -27,23 +27,24 @@ type StoryProps = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const DefaultStory = ({ editing }: StoryProps) => {
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const { space } = useClientProvider();
|
|
31
|
+
invariant(space);
|
|
32
|
+
|
|
32
33
|
const tables = useQuery(space, Filter.schema(TableType));
|
|
33
34
|
const [table, setTable] = useState<TableType>();
|
|
34
|
-
const [schema, setSchema] = useState<
|
|
35
|
+
const [schema, setSchema] = useState<EchoSchema>();
|
|
35
36
|
useEffect(() => {
|
|
36
|
-
if (tables.length && !table) {
|
|
37
|
+
if (space && tables.length && !table) {
|
|
37
38
|
const table = tables[0];
|
|
38
39
|
invariant(table.view);
|
|
39
40
|
setTable(table);
|
|
40
|
-
setSchema(space.db.schemaRegistry.getSchema(table.view.query.type));
|
|
41
|
+
setSchema(space.db.schemaRegistry.getSchema(table.view.target!.query.type));
|
|
41
42
|
}
|
|
42
|
-
}, [tables]);
|
|
43
|
+
}, [space, tables]);
|
|
43
44
|
|
|
44
45
|
const projection = useMemo(() => {
|
|
45
46
|
if (schema && table?.view) {
|
|
46
|
-
return new ViewProjection(schema, table.view);
|
|
47
|
+
return new ViewProjection(schema, table.view.target!);
|
|
47
48
|
}
|
|
48
49
|
}, [schema, table?.view]);
|
|
49
50
|
|
|
@@ -83,9 +84,9 @@ const meta: Meta<StoryProps> = {
|
|
|
83
84
|
types: [TableType, ViewType],
|
|
84
85
|
createIdentity: true,
|
|
85
86
|
createSpace: true,
|
|
86
|
-
onSpaceCreated: ({ space }) => {
|
|
87
|
+
onSpaceCreated: async ({ space }) => {
|
|
87
88
|
const table = space.db.add(create(TableType, {}));
|
|
88
|
-
const schema = initializeTable({ space, table });
|
|
89
|
+
const schema = await initializeTable({ space, table });
|
|
89
90
|
Array.from({ length: 10 }).forEach(() => {
|
|
90
91
|
space.db.add(
|
|
91
92
|
create(schema, {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import { type FieldProjection } from '@dxos/schema';
|
|
25
25
|
|
|
26
26
|
import { completion } from './extension';
|
|
27
|
-
import { type TableModel } from '../../model';
|
|
27
|
+
import { type TableModel, type ModalController } from '../../model';
|
|
28
28
|
|
|
29
29
|
const newValue = Symbol.for('newValue');
|
|
30
30
|
|
|
@@ -39,6 +39,7 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
|
39
39
|
|
|
40
40
|
export type TableCellEditorProps = {
|
|
41
41
|
model?: TableModel;
|
|
42
|
+
modals?: ModalController;
|
|
42
43
|
onEnter?: (cell: DxGridPlanePosition) => void;
|
|
43
44
|
onFocus?: DxGrid['refocus'];
|
|
44
45
|
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
@@ -46,6 +47,7 @@ export type TableCellEditorProps = {
|
|
|
46
47
|
|
|
47
48
|
export const TableCellEditor = ({
|
|
48
49
|
model,
|
|
50
|
+
modals,
|
|
49
51
|
onEnter,
|
|
50
52
|
onFocus,
|
|
51
53
|
onQuery,
|
|
@@ -133,12 +135,12 @@ export const TableCellEditor = ({
|
|
|
133
135
|
completion({
|
|
134
136
|
onQuery: (text) => onQuery(fieldProjection, text),
|
|
135
137
|
onMatch: (data) => {
|
|
136
|
-
if (model && editing) {
|
|
138
|
+
if (model && editing && modals) {
|
|
137
139
|
if (isCreateOption(data)) {
|
|
138
140
|
const { field, props } = fieldProjection;
|
|
139
141
|
if (props.referenceSchema) {
|
|
140
142
|
suppressNextBlur.current = true;
|
|
141
|
-
|
|
143
|
+
modals.openCreateRef(
|
|
142
144
|
props.referenceSchema,
|
|
143
145
|
document.querySelector(cellQuery(editing.index, gridId)),
|
|
144
146
|
{
|
|
@@ -162,7 +164,7 @@ export const TableCellEditor = ({
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
return extension;
|
|
165
|
-
}, [model, editing, fieldProjection, handleClose]);
|
|
167
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
166
168
|
|
|
167
169
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
168
170
|
if (model && editing) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { create } from '@dxos/live-object';
|
|
8
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
|
+
import {
|
|
10
|
+
createGapSeparator,
|
|
11
|
+
createMenuAction,
|
|
12
|
+
type MenuAction,
|
|
13
|
+
type MenuActionHandler,
|
|
14
|
+
MenuProvider,
|
|
15
|
+
ToolbarMenu,
|
|
16
|
+
useMenuActions,
|
|
17
|
+
} from '@dxos/react-ui-menu';
|
|
18
|
+
|
|
19
|
+
import { translationKey } from '../../translations';
|
|
20
|
+
|
|
21
|
+
export type TableToolbarActionProperties = { type: TableToolbarActionType };
|
|
22
|
+
|
|
23
|
+
export type TableToolbarAction = MenuAction<TableToolbarActionProperties>;
|
|
24
|
+
|
|
25
|
+
export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
26
|
+
|
|
27
|
+
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
28
|
+
|
|
29
|
+
export type TableToolbarProps = ThemedClassName<
|
|
30
|
+
TableToolbarState & { onAction?: MenuActionHandler<TableToolbarAction>; attendableId?: string }
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
const createTableToolbarActions = (state: TableToolbarState) => {
|
|
34
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', {
|
|
35
|
+
type: 'add-row',
|
|
36
|
+
icon: 'ph--plus--regular',
|
|
37
|
+
label: ['add row', { ns: translationKey }],
|
|
38
|
+
testId: 'table.toolbar.add-row',
|
|
39
|
+
});
|
|
40
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', {
|
|
41
|
+
type: 'save-view',
|
|
42
|
+
icon: 'ph--floppy-disk--regular',
|
|
43
|
+
label: ['save view label', { ns: translationKey }],
|
|
44
|
+
testId: 'table.toolbar.save-view',
|
|
45
|
+
iconOnly: false,
|
|
46
|
+
hidden: !state.viewDirty,
|
|
47
|
+
});
|
|
48
|
+
const gap = createGapSeparator();
|
|
49
|
+
const comment = createMenuAction('comment', {
|
|
50
|
+
type: 'comment',
|
|
51
|
+
icon: 'ph--chat-text--regular',
|
|
52
|
+
label: ['create comment', { ns: translationKey }],
|
|
53
|
+
testId: 'table.toolbar.comment',
|
|
54
|
+
});
|
|
55
|
+
const nodes = [add, save, ...gap.nodes, comment];
|
|
56
|
+
return {
|
|
57
|
+
nodes,
|
|
58
|
+
edges: nodes.map(({ id: target }) => ({ source: 'root', target })),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const TableToolbar = ({ classNames, viewDirty, attendableId, onAction }: TableToolbarProps) => {
|
|
63
|
+
const state = useMemo(() => create<TableToolbarState>({ viewDirty }), []);
|
|
64
|
+
const actionsCreator = useCallback(() => createTableToolbarActions(state), [state]);
|
|
65
|
+
const menu = useMenuActions(actionsCreator);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<MenuProvider {...menu} attendableId={attendableId} onAction={onAction as MenuActionHandler}>
|
|
69
|
+
<ToolbarMenu classNames={classNames} />
|
|
70
|
+
</MenuProvider>
|
|
71
|
+
);
|
|
72
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { useEffect } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { setValue, toJsonSchema, S, TypeEnum, TypedObject, FormatEnum } from '@dxos/echo-schema';
|
|
8
8
|
import { faker } from '@dxos/random';
|
|
9
|
-
import { create } from '@dxos/react-client/echo';
|
|
9
|
+
import { create, makeRef } from '@dxos/react-client/echo';
|
|
10
10
|
import { createView, type ViewProjection } from '@dxos/schema';
|
|
11
11
|
import {} from '@dxos/schema';
|
|
12
12
|
|
|
@@ -22,11 +22,13 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
|
|
|
22
22
|
|
|
23
23
|
export const createTable = (schema = TestSchema) => {
|
|
24
24
|
return create(TableType, {
|
|
25
|
-
view:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
view: makeRef(
|
|
26
|
+
createView({
|
|
27
|
+
name: 'Test',
|
|
28
|
+
typename: schema.typename,
|
|
29
|
+
jsonSchema: toJsonSchema(schema),
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
30
32
|
});
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -71,7 +73,7 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
71
73
|
|
|
72
74
|
const i = setInterval(() => {
|
|
73
75
|
const rowIdx = Math.floor(Math.random() * items.length);
|
|
74
|
-
const fields = table.view?.fields ?? [];
|
|
76
|
+
const fields = table.view?.target?.fields ?? [];
|
|
75
77
|
const columnIdx = Math.floor(Math.random() * fields.length);
|
|
76
78
|
const projection: ViewProjection = (table as any)._projection;
|
|
77
79
|
const field = fields[columnIdx];
|
|
@@ -111,5 +113,5 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
111
113
|
}, updateInterval);
|
|
112
114
|
|
|
113
115
|
return () => clearInterval(i);
|
|
114
|
-
}, [items, table.view?.fields, updateInterval]);
|
|
116
|
+
}, [items, table.view?.target?.fields, updateInterval]);
|
|
115
117
|
};
|