@dxos/react-ui-table 0.8.3 → 0.8.4-main.c1de068
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-5XLSHLOG.mjs +1751 -0
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +7 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +240 -1639
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +121 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +1752 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +240 -1639
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +122 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.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 +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +7 -6
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +56 -20
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +8 -0
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -2
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +3 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table.d.ts +8 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -40
- package/src/components/Table/ColumnSettings.tsx +14 -10
- package/src/components/Table/CreateRefPanel.tsx +5 -3
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/DynamicTable.tsx +13 -7
- package/src/components/Table/Table.stories.tsx +104 -148
- package/src/components/Table/Table.tsx +50 -32
- package/src/components/Table/relations.stories.tsx +27 -35
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -28
- package/src/components/TableCellEditor/TableCellEditor.tsx +79 -21
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useTableModel.ts +20 -16
- package/src/index.ts +1 -2
- package/src/model/selection-model.ts +4 -2
- package/src/model/table-model.test.ts +7 -8
- package/src/model/table-model.ts +267 -78
- package/src/model/table-presentation.test.ts +6 -7
- package/src/model/table-presentation.ts +215 -132
- package/src/model/table-sorting.ts +9 -9
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +21 -20
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +122 -0
- package/src/translations.ts +5 -3
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +19 -17
- package/src/util/table-buttons.ts +24 -1
- package/src/util/table.ts +33 -111
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
|
@@ -0,0 +1,1751 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TableView
|
|
3
|
+
} from "./chunk-L2LSB6KV.mjs";
|
|
4
|
+
|
|
5
|
+
// src/util/dynamic-table.ts
|
|
6
|
+
import { Obj } from "@dxos/echo";
|
|
7
|
+
import { getTypename, toJsonSchema } from "@dxos/echo-schema";
|
|
8
|
+
import { createView, getSchemaFromPropertyDefinitions, ProjectionModel } from "@dxos/schema";
|
|
9
|
+
var getBaseSchema = ({ typename, properties, jsonSchema, schema }) => {
|
|
10
|
+
if (typename && properties) {
|
|
11
|
+
const schema2 = getSchemaFromPropertyDefinitions(typename, properties);
|
|
12
|
+
return {
|
|
13
|
+
typename: schema2.typename,
|
|
14
|
+
jsonSchema: schema2.jsonSchema
|
|
15
|
+
};
|
|
16
|
+
} else if (schema) {
|
|
17
|
+
return {
|
|
18
|
+
typename: getTypename(schema),
|
|
19
|
+
jsonSchema: toJsonSchema(schema)
|
|
20
|
+
};
|
|
21
|
+
} else if (typename && jsonSchema) {
|
|
22
|
+
return {
|
|
23
|
+
typename,
|
|
24
|
+
jsonSchema
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error("invalid properties");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var makeDynamicTable = ({ typename, jsonSchema, properties }) => {
|
|
31
|
+
const table = Obj.make(TableView, {
|
|
32
|
+
sizes: {}
|
|
33
|
+
});
|
|
34
|
+
const view = createView({
|
|
35
|
+
typename,
|
|
36
|
+
jsonSchema,
|
|
37
|
+
presentation: table,
|
|
38
|
+
...properties && {
|
|
39
|
+
fields: properties.map((property) => property.name)
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const projection = new ProjectionModel(jsonSchema, view.projection);
|
|
43
|
+
if (properties && projection.fields) {
|
|
44
|
+
setProperties(view, projection, table, properties);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
projection,
|
|
48
|
+
view
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
var setProperties = (view, projection, table, properties) => {
|
|
52
|
+
for (const property of properties) {
|
|
53
|
+
const field = projection.fields.find((field2) => field2.path === property.name);
|
|
54
|
+
if (field) {
|
|
55
|
+
if (property.size !== void 0) {
|
|
56
|
+
table.sizes[field.path] = property.size;
|
|
57
|
+
}
|
|
58
|
+
if (property.title !== void 0) {
|
|
59
|
+
const fieldProjection = projection.getFieldProjection(field.id);
|
|
60
|
+
projection.setFieldProjection({
|
|
61
|
+
...fieldProjection,
|
|
62
|
+
props: {
|
|
63
|
+
...fieldProjection.props,
|
|
64
|
+
title: property.title
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (property.sort) {
|
|
69
|
+
const fieldId = field.id;
|
|
70
|
+
view.query.sort = [
|
|
71
|
+
{
|
|
72
|
+
fieldId,
|
|
73
|
+
direction: property.sort
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/util/signals.ts
|
|
82
|
+
var touch = (value) => value;
|
|
83
|
+
|
|
84
|
+
// src/util/table.ts
|
|
85
|
+
import { Match } from "effect";
|
|
86
|
+
import { Obj as Obj2 } from "@dxos/echo";
|
|
87
|
+
import { toEffectSchema } from "@dxos/echo-schema";
|
|
88
|
+
import { createViewFromSpace, getSchemaProperties } from "@dxos/schema";
|
|
89
|
+
var createTable = async ({ sizes, ...props }) => {
|
|
90
|
+
const table = Obj2.make(TableView, {
|
|
91
|
+
sizes: {}
|
|
92
|
+
});
|
|
93
|
+
const { jsonSchema, view } = await createViewFromSpace({
|
|
94
|
+
...props,
|
|
95
|
+
presentation: table
|
|
96
|
+
});
|
|
97
|
+
const schema = toEffectSchema(jsonSchema);
|
|
98
|
+
const shouldIncludeId = props.fields?.find((field) => field === "id") !== void 0;
|
|
99
|
+
const properties = getSchemaProperties(schema.ast, {}, shouldIncludeId);
|
|
100
|
+
for (const property of properties) {
|
|
101
|
+
if (sizes?.[property.name]) {
|
|
102
|
+
table.sizes[property.name] = sizes[property.name];
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
Match.type().pipe(Match.when("boolean", () => {
|
|
106
|
+
table.sizes[property.name] = 100;
|
|
107
|
+
}), Match.when("number", () => {
|
|
108
|
+
table.sizes[property.name] = 100;
|
|
109
|
+
}), Match.orElse(() => {
|
|
110
|
+
}))(property.type);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
jsonSchema,
|
|
114
|
+
view
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/util/table-buttons.ts
|
|
119
|
+
var BUTTON_IDENTIFIERS = {
|
|
120
|
+
columnSettings: "data-table-column-settings-button",
|
|
121
|
+
newColumn: "data-table-new-column-button",
|
|
122
|
+
referencedCell: "data-table-ref-cell-button",
|
|
123
|
+
rowMenu: "data-table-row-menu-button",
|
|
124
|
+
saveDraftRow: "data-table-save-draft-row-button",
|
|
125
|
+
sort: "data-table-sort-button"
|
|
126
|
+
};
|
|
127
|
+
var createButton = ({ attr, data, disabled = false, icon, testId, type = "primary" }) => {
|
|
128
|
+
const dataAttrs = Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
129
|
+
const positionClass = type === "primary" ? "inline-end-2" : "inline-end-9";
|
|
130
|
+
return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? "disabled" : ""}><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
|
|
131
|
+
};
|
|
132
|
+
var addColumnButton = {
|
|
133
|
+
attr: BUTTON_IDENTIFIERS.newColumn,
|
|
134
|
+
icon: "ph--plus--regular",
|
|
135
|
+
render: ({ disabled }) => {
|
|
136
|
+
return createButton({
|
|
137
|
+
attr: BUTTON_IDENTIFIERS.newColumn,
|
|
138
|
+
icon: addColumnButton.icon,
|
|
139
|
+
disabled,
|
|
140
|
+
data: {},
|
|
141
|
+
testId: "table-new-column-button"
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
getData: (_el) => ({
|
|
145
|
+
type: "newColumn"
|
|
146
|
+
})
|
|
147
|
+
};
|
|
148
|
+
var columnSettingsButton = {
|
|
149
|
+
attr: BUTTON_IDENTIFIERS.columnSettings,
|
|
150
|
+
icon: "ph--caret-down--regular",
|
|
151
|
+
render: ({ fieldId }) => {
|
|
152
|
+
return createButton({
|
|
153
|
+
attr: BUTTON_IDENTIFIERS.columnSettings,
|
|
154
|
+
icon: columnSettingsButton.icon,
|
|
155
|
+
data: {
|
|
156
|
+
"data-field-id": fieldId
|
|
157
|
+
},
|
|
158
|
+
testId: "table-column-settings-button"
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
getData: (el) => ({
|
|
162
|
+
type: "columnSettings",
|
|
163
|
+
fieldId: el.getAttribute("data-field-id")
|
|
164
|
+
})
|
|
165
|
+
};
|
|
166
|
+
var rowMenuButton = {
|
|
167
|
+
attr: BUTTON_IDENTIFIERS.rowMenu,
|
|
168
|
+
icon: "ph--dots-three--regular",
|
|
169
|
+
render: ({ rowIndex }) => {
|
|
170
|
+
return createButton({
|
|
171
|
+
attr: BUTTON_IDENTIFIERS.rowMenu,
|
|
172
|
+
icon: rowMenuButton.icon,
|
|
173
|
+
data: {
|
|
174
|
+
"data-row-index": rowIndex.toString()
|
|
175
|
+
},
|
|
176
|
+
testId: "table-row-menu-button"
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
getData: (el) => ({
|
|
180
|
+
type: "rowMenu",
|
|
181
|
+
rowIndex: Number(el.getAttribute("data-row-index"))
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
var sortButton = {
|
|
185
|
+
attr: BUTTON_IDENTIFIERS.sort,
|
|
186
|
+
icon: "ph--sort-ascending--regular",
|
|
187
|
+
render: ({ fieldId, direction }) => {
|
|
188
|
+
return createButton({
|
|
189
|
+
attr: BUTTON_IDENTIFIERS.sort,
|
|
190
|
+
icon: direction === "desc" ? "ph--sort-descending--regular" : "ph--sort-ascending--regular",
|
|
191
|
+
data: {
|
|
192
|
+
"data-field-id": fieldId,
|
|
193
|
+
"data-direction": direction ?? ""
|
|
194
|
+
},
|
|
195
|
+
testId: "table-sort-button",
|
|
196
|
+
// TODO(ZaymonFC): All buttons should be rendered into an absolutely positioned flex container.
|
|
197
|
+
// This is a bit hacky.
|
|
198
|
+
type: "secondary"
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
getData: (el) => ({
|
|
202
|
+
type: "sort",
|
|
203
|
+
fieldId: el.getAttribute("data-field-id"),
|
|
204
|
+
direction: el.getAttribute("data-direction")
|
|
205
|
+
})
|
|
206
|
+
};
|
|
207
|
+
var saveDraftRowButton = {
|
|
208
|
+
attr: BUTTON_IDENTIFIERS.saveDraftRow,
|
|
209
|
+
icon: "ph--floppy-disk--regular",
|
|
210
|
+
render: ({ rowIndex, disabled }) => {
|
|
211
|
+
return createButton({
|
|
212
|
+
attr: BUTTON_IDENTIFIERS.saveDraftRow,
|
|
213
|
+
icon: saveDraftRowButton.icon,
|
|
214
|
+
disabled,
|
|
215
|
+
data: {
|
|
216
|
+
"data-row-index": rowIndex.toString()
|
|
217
|
+
},
|
|
218
|
+
testId: "table-save-draft-row-button"
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
getData: (el) => ({
|
|
222
|
+
type: "saveDraftRow",
|
|
223
|
+
rowIndex: Number(el.getAttribute("data-row-index"))
|
|
224
|
+
})
|
|
225
|
+
};
|
|
226
|
+
var tableButtons = {
|
|
227
|
+
addColumn: addColumnButton,
|
|
228
|
+
columnSettings: columnSettingsButton,
|
|
229
|
+
rowMenu: rowMenuButton,
|
|
230
|
+
saveDraftRow: saveDraftRowButton,
|
|
231
|
+
sort: sortButton
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/util/table-controls.ts
|
|
235
|
+
var CONTROL_IDENTIFIERS = {
|
|
236
|
+
checkbox: "data-table-checkbox",
|
|
237
|
+
switch: "data-table-switch"
|
|
238
|
+
};
|
|
239
|
+
var BASE_CLASSES = {
|
|
240
|
+
checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox",
|
|
241
|
+
switch: "absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch"
|
|
242
|
+
};
|
|
243
|
+
var renderAttributes = (data) => {
|
|
244
|
+
return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
245
|
+
};
|
|
246
|
+
var renderInput = (baseClass, attrs, checked = false, disabled = false, preventToggle = false, testId) => {
|
|
247
|
+
return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? "checked" : ""} ${preventToggle ? 'onclick="return false"' : ""} ${disabled ? "disabled" : ""} data-testid="${testId}"/>`;
|
|
248
|
+
};
|
|
249
|
+
var CheckboxStory = ({ rowIndex, header = false, checked = false, disabled = false }) => {
|
|
250
|
+
const attrs = {
|
|
251
|
+
[CONTROL_IDENTIFIERS.checkbox]: "",
|
|
252
|
+
"data-row-index": rowIndex.toString(),
|
|
253
|
+
...header && {
|
|
254
|
+
"data-header": ""
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return renderInput(BASE_CLASSES.checkbox, attrs, checked, disabled, true, "table-selection");
|
|
258
|
+
};
|
|
259
|
+
var SwitchStory = ({ colIndex, rowIndex, checked = false, disabled = false }) => {
|
|
260
|
+
const attrs = {
|
|
261
|
+
[CONTROL_IDENTIFIERS.switch]: "",
|
|
262
|
+
"data-row-index": rowIndex.toString(),
|
|
263
|
+
"data-col-index": colIndex.toString()
|
|
264
|
+
};
|
|
265
|
+
return renderInput(BASE_CLASSES.switch, attrs, checked, disabled, true, "table-switch");
|
|
266
|
+
};
|
|
267
|
+
var tableControls = {
|
|
268
|
+
checkbox: {
|
|
269
|
+
attr: CONTROL_IDENTIFIERS.checkbox,
|
|
270
|
+
render: CheckboxStory,
|
|
271
|
+
getData: (el) => ({
|
|
272
|
+
type: "checkbox",
|
|
273
|
+
rowIndex: Number(el.getAttribute("data-row-index")),
|
|
274
|
+
header: el.hasAttribute("data-header")
|
|
275
|
+
})
|
|
276
|
+
},
|
|
277
|
+
switch: {
|
|
278
|
+
attr: CONTROL_IDENTIFIERS.switch,
|
|
279
|
+
render: SwitchStory,
|
|
280
|
+
getData: (el) => ({
|
|
281
|
+
type: "switch",
|
|
282
|
+
rowIndex: Number(el.getAttribute("data-row-index")),
|
|
283
|
+
colIndex: Number(el.getAttribute("data-col-index"))
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/util/schema.ts
|
|
289
|
+
import { Schema } from "effect";
|
|
290
|
+
import { isTypeLiteral, TypeLiteral } from "effect/SchemaAST";
|
|
291
|
+
var narrowSchema = (schema, paths) => {
|
|
292
|
+
const ast = schema?.ast;
|
|
293
|
+
if (isTypeLiteral(ast)) {
|
|
294
|
+
const propertySignatures = ast.propertySignatures.filter((signature) => paths.includes(signature.name.toString()));
|
|
295
|
+
if (propertySignatures.length > 0) {
|
|
296
|
+
const narrowType = new TypeLiteral(propertySignatures, []);
|
|
297
|
+
return Schema.make(narrowType);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return void 0;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/model/table-model.ts
|
|
304
|
+
import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
|
|
305
|
+
import { Resource as Resource2 } from "@dxos/context";
|
|
306
|
+
import { Obj as Obj3, Ref } from "@dxos/echo";
|
|
307
|
+
import { FormatEnum as FormatEnum2, getValue as getValue2, setValue, getSnapshot, getSchema, toEffectSchema as toEffectSchema2 } from "@dxos/echo-schema";
|
|
308
|
+
import { invariant } from "@dxos/invariant";
|
|
309
|
+
import { ObjectId } from "@dxos/keys";
|
|
310
|
+
import { isLiveObject } from "@dxos/live-object";
|
|
311
|
+
import { fullyQualifiedId } from "@dxos/react-client/echo";
|
|
312
|
+
import { formatForEditing, parseValue } from "@dxos/react-ui-form";
|
|
313
|
+
import { ProjectionModel as ProjectionModel2, validateSchema } from "@dxos/schema";
|
|
314
|
+
|
|
315
|
+
// src/model/selection-model.ts
|
|
316
|
+
import { computed, effect, signal } from "@preact/signals-core";
|
|
317
|
+
import { Resource } from "@dxos/context";
|
|
318
|
+
var SelectionModel = class extends Resource {
|
|
319
|
+
constructor(_rows, _selectionMode, _initialSelection, _onSelectionChanged) {
|
|
320
|
+
super(), this._rows = _rows, this._selectionMode = _selectionMode, this._initialSelection = _initialSelection, this._onSelectionChanged = _onSelectionChanged, this._validSelectedIds = computed(() => {
|
|
321
|
+
const rows = this._rows.value;
|
|
322
|
+
if (!rows) {
|
|
323
|
+
return /* @__PURE__ */ new Set();
|
|
324
|
+
}
|
|
325
|
+
const validIds = new Set(rows.map((row) => row.id));
|
|
326
|
+
return new Set([
|
|
327
|
+
...this._selection.value
|
|
328
|
+
].filter((id) => validIds.has(id)));
|
|
329
|
+
}), this._hasSelection = computed(() => this._validSelectedIds.value.size > 0), this._allSelected = computed(() => {
|
|
330
|
+
const rows = this._rows.value;
|
|
331
|
+
if (rows.length === 0) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
return rows.every((row) => this._selection.value.has(row.id));
|
|
335
|
+
}), this.getSelectedRows = () => {
|
|
336
|
+
const selectedIds = this._validSelectedIds.value;
|
|
337
|
+
return this._rows.value.filter((row) => selectedIds.has(row.id));
|
|
338
|
+
}, this.isRowIndexSelected = (rowIndex) => {
|
|
339
|
+
const row = this._rows.value[rowIndex];
|
|
340
|
+
return this._selection.value.has(row.id);
|
|
341
|
+
}, this.isObjectSelected = (object) => {
|
|
342
|
+
return this._selection.value.has(object.id);
|
|
343
|
+
}, this.toggleSelectionForRowIndex = (rowIndex) => {
|
|
344
|
+
const row = this._rows.value[rowIndex];
|
|
345
|
+
const newSelection = new Set(this._selection.value);
|
|
346
|
+
if (newSelection.has(row.id)) {
|
|
347
|
+
newSelection.delete(row.id);
|
|
348
|
+
} else {
|
|
349
|
+
if (this._selectionMode === "single") {
|
|
350
|
+
newSelection.clear();
|
|
351
|
+
}
|
|
352
|
+
newSelection.add(row.id);
|
|
353
|
+
}
|
|
354
|
+
this._selection.value = newSelection;
|
|
355
|
+
}, this.setSelection = (mode) => {
|
|
356
|
+
switch (mode) {
|
|
357
|
+
case "all": {
|
|
358
|
+
this._selection.value = new Set(this._rows.value.map((row) => row.id));
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case "none": {
|
|
362
|
+
this._selection.value = /* @__PURE__ */ new Set();
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
this._selection = signal(new Set(this._initialSelection));
|
|
368
|
+
}
|
|
369
|
+
async _open() {
|
|
370
|
+
const selectionWatcher = effect(() => {
|
|
371
|
+
touch(this._selection.value);
|
|
372
|
+
this._onSelectionChanged?.();
|
|
373
|
+
});
|
|
374
|
+
this._ctx.onDispose(selectionWatcher);
|
|
375
|
+
}
|
|
376
|
+
//
|
|
377
|
+
// Getters
|
|
378
|
+
//
|
|
379
|
+
get selection() {
|
|
380
|
+
return this._selection;
|
|
381
|
+
}
|
|
382
|
+
get hasSelection() {
|
|
383
|
+
return this._hasSelection;
|
|
384
|
+
}
|
|
385
|
+
get allRowsSeleted() {
|
|
386
|
+
return this._allSelected;
|
|
387
|
+
}
|
|
388
|
+
get selectionMode() {
|
|
389
|
+
return this._selectionMode;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// src/model/table-sorting.ts
|
|
394
|
+
import { computed as computed2, signal as signal2 } from "@preact/signals-core";
|
|
395
|
+
import orderBy from "lodash.orderby";
|
|
396
|
+
import { getValue, FormatEnum, TypeEnum } from "@dxos/echo-schema";
|
|
397
|
+
import { formatForDisplay } from "@dxos/react-ui-form";
|
|
398
|
+
var TableSorting = class {
|
|
399
|
+
constructor(rows, _view, _projection) {
|
|
400
|
+
this._view = _view;
|
|
401
|
+
this._projection = _projection;
|
|
402
|
+
this._displayToDataIndex = /* @__PURE__ */ new Map();
|
|
403
|
+
this._localSort = signal2(void 0);
|
|
404
|
+
this._rows = rows;
|
|
405
|
+
this._isDirty = computed2(() => {
|
|
406
|
+
const local = this._localSort.value;
|
|
407
|
+
const viewSort = this._view.query.sort?.[0];
|
|
408
|
+
if (local?.type === "cleared") {
|
|
409
|
+
return viewSort !== void 0;
|
|
410
|
+
}
|
|
411
|
+
if (local?.type === "active") {
|
|
412
|
+
if (!viewSort) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
return local.sort.fieldId !== viewSort.fieldId || local.sort.direction !== viewSort.direction;
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
});
|
|
419
|
+
this.sortedRows = this.initialiseSortedRows();
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* @reactive
|
|
423
|
+
* @returns Local sort if present, falls back to view sort.
|
|
424
|
+
*/
|
|
425
|
+
get sorting() {
|
|
426
|
+
const local = this._localSort.value;
|
|
427
|
+
if (local?.type === "cleared") {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
if (local?.type === "active") {
|
|
431
|
+
return local.sort;
|
|
432
|
+
}
|
|
433
|
+
return this._view.query.sort?.[0];
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* @reactive
|
|
437
|
+
* @returns Whether local sort differs from view sort.
|
|
438
|
+
*/
|
|
439
|
+
get isDirty() {
|
|
440
|
+
return this._isDirty.value;
|
|
441
|
+
}
|
|
442
|
+
getDataIndex(displayIndex) {
|
|
443
|
+
return this._displayToDataIndex.get(displayIndex) ?? displayIndex;
|
|
444
|
+
}
|
|
445
|
+
setSort(fieldId, direction) {
|
|
446
|
+
this._localSort.value = {
|
|
447
|
+
type: "active",
|
|
448
|
+
sort: {
|
|
449
|
+
fieldId,
|
|
450
|
+
direction
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
toggleSort(fieldId) {
|
|
455
|
+
if (!this.sorting || this.sorting.fieldId !== fieldId) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
switch (this.sorting.direction) {
|
|
459
|
+
case "asc": {
|
|
460
|
+
this.setSort(fieldId, "desc");
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "desc": {
|
|
464
|
+
this.setSort(fieldId, "asc");
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
clearSort() {
|
|
470
|
+
this._localSort.value = {
|
|
471
|
+
type: "cleared"
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
save() {
|
|
475
|
+
if (this._localSort.value !== void 0) {
|
|
476
|
+
if (this._localSort.value.type === "active") {
|
|
477
|
+
this._view.query.sort = [
|
|
478
|
+
this._localSort.value.sort
|
|
479
|
+
];
|
|
480
|
+
} else {
|
|
481
|
+
this._view.query.sort = [];
|
|
482
|
+
}
|
|
483
|
+
this._localSort.value = void 0;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
//
|
|
487
|
+
// Initialisation.
|
|
488
|
+
//
|
|
489
|
+
initialiseSortedRows() {
|
|
490
|
+
return computed2(() => {
|
|
491
|
+
this._displayToDataIndex.clear();
|
|
492
|
+
const sort = this.sorting;
|
|
493
|
+
if (!sort) {
|
|
494
|
+
return this._rows.value;
|
|
495
|
+
}
|
|
496
|
+
const field = this._projection.fields.find((f) => f.id === sort.fieldId);
|
|
497
|
+
if (!field) {
|
|
498
|
+
return this._rows.value;
|
|
499
|
+
}
|
|
500
|
+
const { props } = this._projection.getFieldProjection(field.id);
|
|
501
|
+
const dataWithIndices = this._rows.value.map((item, index) => {
|
|
502
|
+
const sortValue = this.getSortValue(props, field, item);
|
|
503
|
+
const isEmpty = !(props.type === TypeEnum.Boolean) && (sortValue == null || sortValue === "");
|
|
504
|
+
return {
|
|
505
|
+
item,
|
|
506
|
+
index,
|
|
507
|
+
value: sortValue,
|
|
508
|
+
isEmpty
|
|
509
|
+
};
|
|
510
|
+
});
|
|
511
|
+
const ordered = orderBy(dataWithIndices, [
|
|
512
|
+
"isEmpty",
|
|
513
|
+
"value"
|
|
514
|
+
], [
|
|
515
|
+
"asc",
|
|
516
|
+
sort.direction
|
|
517
|
+
]);
|
|
518
|
+
ordered.forEach(({ index: dataIndex }, displayIndex) => {
|
|
519
|
+
if (displayIndex !== dataIndex) {
|
|
520
|
+
this._displayToDataIndex.set(displayIndex, dataIndex);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
return ordered.map(({ item }) => item);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
getSortValue(props, field, item) {
|
|
527
|
+
const { type, format } = props;
|
|
528
|
+
const value = getValue(item, field.path);
|
|
529
|
+
if (type === TypeEnum.Boolean) {
|
|
530
|
+
return !!value;
|
|
531
|
+
}
|
|
532
|
+
if (type === TypeEnum.Number) {
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
535
|
+
if (value == null) {
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
if (format === FormatEnum.SingleSelect) {
|
|
539
|
+
const options = props.options;
|
|
540
|
+
if (!options) {
|
|
541
|
+
return void 0;
|
|
542
|
+
}
|
|
543
|
+
const option = options.find((option2) => option2.id === value);
|
|
544
|
+
return option ? options.indexOf(option) : void 0;
|
|
545
|
+
}
|
|
546
|
+
if (format === FormatEnum.Ref && field.referencePath) {
|
|
547
|
+
const refValue = getValue(value.target, field.referencePath);
|
|
548
|
+
return refValue;
|
|
549
|
+
}
|
|
550
|
+
return formatForDisplay({
|
|
551
|
+
type,
|
|
552
|
+
format,
|
|
553
|
+
value
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// src/util/tag.ts
|
|
559
|
+
var TAG_PATTERN_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
560
|
+
var extractTagIds = (value) => {
|
|
561
|
+
if (typeof value !== "string") {
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
const ids = [];
|
|
565
|
+
let match;
|
|
566
|
+
while ((match = TAG_PATTERN_REGEX.exec(value)) !== null) {
|
|
567
|
+
if (match[2]) {
|
|
568
|
+
ids.push(match[2]);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return ids.length > 0 ? ids : void 0;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// src/model/table-model.ts
|
|
575
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
|
|
576
|
+
var defaultFeatures = {
|
|
577
|
+
selection: {
|
|
578
|
+
enabled: true,
|
|
579
|
+
mode: "multiple"
|
|
580
|
+
},
|
|
581
|
+
dataEditable: false,
|
|
582
|
+
schemaEditable: false
|
|
583
|
+
};
|
|
584
|
+
var TableModel = class extends Resource2 {
|
|
585
|
+
constructor({ view, schema, projection, features = {}, sorting = [], initialSelection = [], pinnedRows = {
|
|
586
|
+
top: [],
|
|
587
|
+
bottom: []
|
|
588
|
+
}, rowActions = [], onCellUpdate, onDeleteColumn, onDeleteRows, onInsertRow, onRowOrderChange, onRowAction }) {
|
|
589
|
+
super();
|
|
590
|
+
this._visibleRange = signal3({
|
|
591
|
+
start: {
|
|
592
|
+
row: 0,
|
|
593
|
+
col: 0
|
|
594
|
+
},
|
|
595
|
+
end: {
|
|
596
|
+
row: 0,
|
|
597
|
+
col: 0
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
this._rows = signal3([]);
|
|
601
|
+
this._draftRows = signal3([]);
|
|
602
|
+
//
|
|
603
|
+
// Data
|
|
604
|
+
//
|
|
605
|
+
this.setRows = (obj) => {
|
|
606
|
+
this._rows.value = obj;
|
|
607
|
+
};
|
|
608
|
+
this.getRowCount = () => this._rows.value.length;
|
|
609
|
+
/**
|
|
610
|
+
* @reactive
|
|
611
|
+
*/
|
|
612
|
+
this.getDraftRowCount = () => this._draftRows.value.length;
|
|
613
|
+
this.getColumnCount = () => this._projection?.fields.length ?? 0;
|
|
614
|
+
this.insertRow = () => {
|
|
615
|
+
const result = this._onInsertRow?.();
|
|
616
|
+
if (result === false && this._draftRows.value.length === 0) {
|
|
617
|
+
this.createDraftRow();
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
this.commitDraftRow = (draftRowIndex) => {
|
|
621
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
625
|
+
if (!draftRow.valid) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
const success = this._onInsertRow?.(draftRow.data);
|
|
629
|
+
if (success) {
|
|
630
|
+
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
631
|
+
this._draftRows.value = newDraftRows;
|
|
632
|
+
}
|
|
633
|
+
return success ?? false;
|
|
634
|
+
};
|
|
635
|
+
this.deleteRow = (rowIndex) => {
|
|
636
|
+
const row = this._sorting.getDataIndex(rowIndex);
|
|
637
|
+
const obj = this._rows.value[row];
|
|
638
|
+
const objectsToDelete = [];
|
|
639
|
+
if (this.features.selection.enabled && this._selection.hasSelection.value) {
|
|
640
|
+
const selectedRows = this._selection.getSelectedRows();
|
|
641
|
+
objectsToDelete.push(...selectedRows);
|
|
642
|
+
} else {
|
|
643
|
+
objectsToDelete.push(obj);
|
|
644
|
+
}
|
|
645
|
+
this._onDeleteRows?.(row, objectsToDelete);
|
|
646
|
+
};
|
|
647
|
+
this.handleRowAction = (actionId, rowIndex) => {
|
|
648
|
+
if (!this._onRowAction) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const row = this._sorting.getDataIndex(rowIndex);
|
|
652
|
+
const data = this._rows.value[row];
|
|
653
|
+
if (data) {
|
|
654
|
+
this._onRowAction(actionId, data);
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
this.getCellData = (cell) => {
|
|
658
|
+
const { col, row, plane } = cell;
|
|
659
|
+
const fields = this._projection?.fields ?? [];
|
|
660
|
+
if (col < 0 || col >= fields.length) {
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
const field = fields[col];
|
|
664
|
+
let value;
|
|
665
|
+
if (plane === "frozenRowsEnd") {
|
|
666
|
+
if (row >= 0 && row < this._draftRows.value.length) {
|
|
667
|
+
value = getValue2(this._draftRows.value[row].data, field.path);
|
|
668
|
+
} else {
|
|
669
|
+
return void 0;
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
const dataIndex = this._sorting.getDataIndex(row);
|
|
673
|
+
value = getValue2(this._rows.value[dataIndex], field.path);
|
|
674
|
+
}
|
|
675
|
+
if (value == null) {
|
|
676
|
+
return "";
|
|
677
|
+
}
|
|
678
|
+
const { props } = this._projection.getFieldProjection(field.id);
|
|
679
|
+
switch (props.format) {
|
|
680
|
+
case FormatEnum2.Ref: {
|
|
681
|
+
if (!field.referencePath) {
|
|
682
|
+
return "";
|
|
683
|
+
}
|
|
684
|
+
return getValue2(value.target, field.referencePath);
|
|
685
|
+
}
|
|
686
|
+
default: {
|
|
687
|
+
return formatForEditing({
|
|
688
|
+
type: props.type,
|
|
689
|
+
format: props.format,
|
|
690
|
+
value
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
this.validateCellData = async ({ col, row, plane }, value) => {
|
|
696
|
+
const fields = this._projection?.fields ?? [];
|
|
697
|
+
if (col < 0 || col >= fields.length) {
|
|
698
|
+
return {
|
|
699
|
+
valid: false,
|
|
700
|
+
error: "Invalid column index"
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
const field = fields[col];
|
|
704
|
+
const { props } = this._projection.getFieldProjection(field.id);
|
|
705
|
+
const transformedValue = editorTextToCellValue(props, value);
|
|
706
|
+
const isDraftRow = plane === "frozenRowsEnd";
|
|
707
|
+
if (isDraftRow) {
|
|
708
|
+
const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
|
|
709
|
+
if (isInvalidDraftRowIndex) {
|
|
710
|
+
return {
|
|
711
|
+
valid: false,
|
|
712
|
+
error: "Invalid draft row index"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
const draftRow = this._draftRows.value[row];
|
|
716
|
+
const snapshot = {
|
|
717
|
+
...draftRow.data
|
|
718
|
+
};
|
|
719
|
+
setValue(snapshot, field.path, transformedValue);
|
|
720
|
+
const validationErrors = this.validateDraftRowData(snapshot);
|
|
721
|
+
if (validationErrors.length > 0) {
|
|
722
|
+
const error = validationErrors.find((err) => err.path === field.path);
|
|
723
|
+
if (error) {
|
|
724
|
+
return {
|
|
725
|
+
valid: false,
|
|
726
|
+
error: error.message
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
valid: true
|
|
732
|
+
};
|
|
733
|
+
} else {
|
|
734
|
+
const rowIdx = this._sorting.getDataIndex(row);
|
|
735
|
+
const currentRow = this._rows.value[rowIdx];
|
|
736
|
+
invariant(currentRow, "Invalid row index", {
|
|
737
|
+
F: __dxlog_file,
|
|
738
|
+
L: 510,
|
|
739
|
+
S: this,
|
|
740
|
+
A: [
|
|
741
|
+
"currentRow",
|
|
742
|
+
"'Invalid row index'"
|
|
743
|
+
]
|
|
744
|
+
});
|
|
745
|
+
const snapshot = getSnapshot(currentRow);
|
|
746
|
+
setValue(snapshot, field.path, transformedValue);
|
|
747
|
+
const schema = getSchema(currentRow);
|
|
748
|
+
invariant(schema, void 0, {
|
|
749
|
+
F: __dxlog_file,
|
|
750
|
+
L: 516,
|
|
751
|
+
S: this,
|
|
752
|
+
A: [
|
|
753
|
+
"schema",
|
|
754
|
+
""
|
|
755
|
+
]
|
|
756
|
+
});
|
|
757
|
+
const validationResult = validateSchema(schema, snapshot);
|
|
758
|
+
if (validationResult && validationResult.length > 0) {
|
|
759
|
+
const error = validationResult[0];
|
|
760
|
+
invariant(error.path === field.path, void 0, {
|
|
761
|
+
F: __dxlog_file,
|
|
762
|
+
L: 521,
|
|
763
|
+
S: this,
|
|
764
|
+
A: [
|
|
765
|
+
"error.path === field.path",
|
|
766
|
+
""
|
|
767
|
+
]
|
|
768
|
+
});
|
|
769
|
+
return {
|
|
770
|
+
valid: false,
|
|
771
|
+
error: error.message
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
valid: true
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
this.setCellData = (cell, value) => {
|
|
780
|
+
const { col, row, plane } = cell;
|
|
781
|
+
const fields = this._projection?.fields ?? [];
|
|
782
|
+
if (col < 0 || col >= fields.length) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const field = fields[col];
|
|
786
|
+
const { props } = this._projection.getFieldProjection(field.id);
|
|
787
|
+
const transformedValue = editorTextToCellValue(props, value);
|
|
788
|
+
if (props.format === FormatEnum2.Ref && !isLiveObject(value)) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (plane === "frozenRowsEnd") {
|
|
792
|
+
if (row >= 0 && row < this._draftRows.value.length) {
|
|
793
|
+
setValue(this._draftRows.value[row].data, field.path, transformedValue);
|
|
794
|
+
this.validateDraftRow(row);
|
|
795
|
+
}
|
|
796
|
+
} else {
|
|
797
|
+
const rowIdx = this._sorting.getDataIndex(row);
|
|
798
|
+
setValue(this._rows.value[rowIdx], field.path, transformedValue);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
this._view = view;
|
|
802
|
+
this._projection = projection ?? new ProjectionModel2(schema, view.projection);
|
|
803
|
+
this._features = {
|
|
804
|
+
...defaultFeatures,
|
|
805
|
+
...features
|
|
806
|
+
};
|
|
807
|
+
invariant(!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === "single"), "Single selection is not compatible with editable tables.", {
|
|
808
|
+
F: __dxlog_file,
|
|
809
|
+
L: 139,
|
|
810
|
+
S: this,
|
|
811
|
+
A: [
|
|
812
|
+
"!(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single')",
|
|
813
|
+
"'Single selection is not compatible with editable tables.'"
|
|
814
|
+
]
|
|
815
|
+
});
|
|
816
|
+
this._sorting = new TableSorting(this._rows, this._view, this._projection);
|
|
817
|
+
if (sorting.length > 0) {
|
|
818
|
+
const [sort] = sorting;
|
|
819
|
+
this._sorting.setSort(sort.fieldId, sort.direction);
|
|
820
|
+
}
|
|
821
|
+
this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? "multiple", initialSelection, () => this._onRowOrderChange?.());
|
|
822
|
+
this._pinnedRows = pinnedRows;
|
|
823
|
+
this._rowActions = rowActions;
|
|
824
|
+
this._onInsertRow = onInsertRow;
|
|
825
|
+
this._onDeleteRows = onDeleteRows;
|
|
826
|
+
this._onDeleteColumn = onDeleteColumn;
|
|
827
|
+
this._onCellUpdate = onCellUpdate;
|
|
828
|
+
this._onRowOrderChange = onRowOrderChange;
|
|
829
|
+
this._onRowAction = onRowAction;
|
|
830
|
+
}
|
|
831
|
+
get id() {
|
|
832
|
+
return fullyQualifiedId(this._view);
|
|
833
|
+
}
|
|
834
|
+
get view() {
|
|
835
|
+
return this._view;
|
|
836
|
+
}
|
|
837
|
+
get table() {
|
|
838
|
+
invariant(this._table, "Model not initialized", {
|
|
839
|
+
F: __dxlog_file,
|
|
840
|
+
L: 177,
|
|
841
|
+
S: this,
|
|
842
|
+
A: [
|
|
843
|
+
"this._table",
|
|
844
|
+
"'Model not initialized'"
|
|
845
|
+
]
|
|
846
|
+
});
|
|
847
|
+
return this._table;
|
|
848
|
+
}
|
|
849
|
+
get projection() {
|
|
850
|
+
return this._projection;
|
|
851
|
+
}
|
|
852
|
+
get rows() {
|
|
853
|
+
return this.sorting.sortedRows;
|
|
854
|
+
}
|
|
855
|
+
get features() {
|
|
856
|
+
return this._features;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* @reactive
|
|
860
|
+
*/
|
|
861
|
+
get sorting() {
|
|
862
|
+
return this._sorting;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Gets the row data at the specified display index.
|
|
866
|
+
*/
|
|
867
|
+
getRowAt(displayIndex) {
|
|
868
|
+
if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
|
|
869
|
+
return void 0;
|
|
870
|
+
}
|
|
871
|
+
return this.sorting.sortedRows.value[displayIndex];
|
|
872
|
+
}
|
|
873
|
+
get pinnedRows() {
|
|
874
|
+
return this._pinnedRows;
|
|
875
|
+
}
|
|
876
|
+
get columnMeta() {
|
|
877
|
+
invariant(this._columnMeta, void 0, {
|
|
878
|
+
F: __dxlog_file,
|
|
879
|
+
L: 216,
|
|
880
|
+
S: this,
|
|
881
|
+
A: [
|
|
882
|
+
"this._columnMeta",
|
|
883
|
+
""
|
|
884
|
+
]
|
|
885
|
+
});
|
|
886
|
+
return this._columnMeta;
|
|
887
|
+
}
|
|
888
|
+
get selection() {
|
|
889
|
+
return this._selection;
|
|
890
|
+
}
|
|
891
|
+
/** @reactive */
|
|
892
|
+
get isViewDirty() {
|
|
893
|
+
return this.sorting.isDirty;
|
|
894
|
+
}
|
|
895
|
+
get rowActions() {
|
|
896
|
+
return this._rowActions;
|
|
897
|
+
}
|
|
898
|
+
//
|
|
899
|
+
// Initialisation
|
|
900
|
+
//
|
|
901
|
+
async _open() {
|
|
902
|
+
const presentation = this._view.presentation.target ?? await this._view.presentation.load();
|
|
903
|
+
invariant(Obj3.instanceOf(TableView, presentation), void 0, {
|
|
904
|
+
F: __dxlog_file,
|
|
905
|
+
L: 239,
|
|
906
|
+
S: this,
|
|
907
|
+
A: [
|
|
908
|
+
"Obj.instanceOf(TableView, presentation)",
|
|
909
|
+
""
|
|
910
|
+
]
|
|
911
|
+
});
|
|
912
|
+
this._table = presentation;
|
|
913
|
+
this.initializeColumnMeta();
|
|
914
|
+
this.initializeEffects();
|
|
915
|
+
await this._selection.open(this._ctx);
|
|
916
|
+
}
|
|
917
|
+
initializeColumnMeta() {
|
|
918
|
+
this._columnMeta = computed3(() => {
|
|
919
|
+
const fields = this._projection?.fields ?? [];
|
|
920
|
+
const meta = Object.fromEntries(fields.map((field, index) => [
|
|
921
|
+
index,
|
|
922
|
+
{
|
|
923
|
+
size: this.table.sizes[field.path] ?? 256,
|
|
924
|
+
resizeable: true
|
|
925
|
+
}
|
|
926
|
+
]));
|
|
927
|
+
return {
|
|
928
|
+
grid: meta,
|
|
929
|
+
frozenColsStart: {
|
|
930
|
+
0: {
|
|
931
|
+
size: 30,
|
|
932
|
+
resizeable: false
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
frozenColsEnd: {
|
|
936
|
+
0: {
|
|
937
|
+
size: 40,
|
|
938
|
+
resizeable: false
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
initializeEffects() {
|
|
945
|
+
const rowOrderWatcher = effect2(() => {
|
|
946
|
+
touch(this.sorting.sortedRows.value);
|
|
947
|
+
this._onRowOrderChange?.();
|
|
948
|
+
});
|
|
949
|
+
this._ctx.onDispose(rowOrderWatcher);
|
|
950
|
+
const rowEffectManager = effect2(() => {
|
|
951
|
+
const { start, end } = touch(this._visibleRange.value);
|
|
952
|
+
const rowEffects = [];
|
|
953
|
+
for (let row = start.row; row <= end.row; row++) {
|
|
954
|
+
rowEffects.push(effect2(() => {
|
|
955
|
+
const obj = this.sorting.sortedRows.value[row];
|
|
956
|
+
this._projection?.fields.forEach((field) => touch(getValue2(obj, field.path)));
|
|
957
|
+
this._onCellUpdate?.({
|
|
958
|
+
row,
|
|
959
|
+
col: start.col,
|
|
960
|
+
plane: "grid"
|
|
961
|
+
});
|
|
962
|
+
}));
|
|
963
|
+
}
|
|
964
|
+
return () => rowEffects.forEach((cleanup) => cleanup());
|
|
965
|
+
});
|
|
966
|
+
this._ctx.onDispose(rowEffectManager);
|
|
967
|
+
const draftRowsWatcher = effect2(() => {
|
|
968
|
+
touch(this._draftRows.value);
|
|
969
|
+
this._onRowOrderChange?.();
|
|
970
|
+
});
|
|
971
|
+
this._ctx.onDispose(draftRowsWatcher);
|
|
972
|
+
}
|
|
973
|
+
// TODO(ZaymonFC): Return .value instead of exposing the signal.
|
|
974
|
+
get draftRows() {
|
|
975
|
+
return this._draftRows;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Checks if a specific field path has validation errors for a given draft row.
|
|
979
|
+
* @param draftRowIndex - The index of the draft row to check
|
|
980
|
+
* @param fieldPath - The path of the field to check for validation errors
|
|
981
|
+
* @returns true if the field has validation errors, false otherwise
|
|
982
|
+
*/
|
|
983
|
+
hasDraftRowValidationError(draftRowIndex, fieldPath) {
|
|
984
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
988
|
+
const validationErrors = draftRow.validationErrors || [];
|
|
989
|
+
return validationErrors.some((error) => error.path === fieldPath);
|
|
990
|
+
}
|
|
991
|
+
createDraftRow() {
|
|
992
|
+
if (!this._projection) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const draftData = {
|
|
996
|
+
id: ObjectId.random()
|
|
997
|
+
};
|
|
998
|
+
const validationErrors = this.validateDraftRowData(draftData);
|
|
999
|
+
const draftRow = {
|
|
1000
|
+
data: draftData,
|
|
1001
|
+
valid: validationErrors.length === 0,
|
|
1002
|
+
validationErrors
|
|
1003
|
+
};
|
|
1004
|
+
this._draftRows.value = [
|
|
1005
|
+
...this._draftRows.value,
|
|
1006
|
+
draftRow
|
|
1007
|
+
];
|
|
1008
|
+
}
|
|
1009
|
+
validateDraftRow(draftRowIndex) {
|
|
1010
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
1014
|
+
const validationErrors = this.validateDraftRowData(draftRow.data);
|
|
1015
|
+
const updatedDraftRow = {
|
|
1016
|
+
...draftRow,
|
|
1017
|
+
valid: validationErrors.length === 0,
|
|
1018
|
+
validationErrors
|
|
1019
|
+
};
|
|
1020
|
+
const newDraftRows = [
|
|
1021
|
+
...this._draftRows.value
|
|
1022
|
+
];
|
|
1023
|
+
newDraftRows[draftRowIndex] = updatedDraftRow;
|
|
1024
|
+
this._draftRows.value = newDraftRows;
|
|
1025
|
+
}
|
|
1026
|
+
validateDraftRowData(data) {
|
|
1027
|
+
const schema = toEffectSchema2(this._projection.baseSchema);
|
|
1028
|
+
return validateSchema(schema, data) || [];
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Updates a cell's value using a transform function.
|
|
1032
|
+
* @param {DxGridPlanePosition} position - The position of the cell to update.
|
|
1033
|
+
* @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
|
|
1034
|
+
*/
|
|
1035
|
+
updateCellData({ col, row }, update) {
|
|
1036
|
+
const dataRow = this._sorting.getDataIndex(row);
|
|
1037
|
+
const fields = this._projection?.fields ?? [];
|
|
1038
|
+
const field = fields[col];
|
|
1039
|
+
const value = getValue2(this._rows.value[dataRow], field.path);
|
|
1040
|
+
const updatedValue = update(value);
|
|
1041
|
+
setValue(this._rows.value[dataRow], field.path, updatedValue);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Gets the domain-appropriate cell type for a given cell position.
|
|
1045
|
+
* This abstracts away the low-level grid plane concepts and provides domain-specific types.
|
|
1046
|
+
* @param cell - The cell position to classify
|
|
1047
|
+
* @returns The domain-appropriate cell type
|
|
1048
|
+
*/
|
|
1049
|
+
getCellType(cell) {
|
|
1050
|
+
switch (cell.plane) {
|
|
1051
|
+
case "frozenRowsEnd":
|
|
1052
|
+
return "draft";
|
|
1053
|
+
case "frozenRowsStart":
|
|
1054
|
+
return "header";
|
|
1055
|
+
case "grid":
|
|
1056
|
+
default:
|
|
1057
|
+
return "standard";
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Convenience method to check if a cell is a draft cell.
|
|
1062
|
+
* @param cell - The cell position to check
|
|
1063
|
+
* @returns true if the cell is a draft cell, false otherwise
|
|
1064
|
+
*/
|
|
1065
|
+
isDraftCell(cell) {
|
|
1066
|
+
return this.getCellType(cell) === "draft";
|
|
1067
|
+
}
|
|
1068
|
+
//
|
|
1069
|
+
// Column Operations
|
|
1070
|
+
//
|
|
1071
|
+
deleteColumn(fieldId) {
|
|
1072
|
+
if (!this._projection) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const field = this._projection?.fields.find((field2) => field2.id === fieldId);
|
|
1076
|
+
if (field && this._onDeleteColumn) {
|
|
1077
|
+
this._onDeleteColumn(field.id);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
//
|
|
1081
|
+
// Resize
|
|
1082
|
+
//
|
|
1083
|
+
setColumnWidth(columnIndex, width) {
|
|
1084
|
+
const fields = this._projection?.fields ?? [];
|
|
1085
|
+
if (columnIndex < fields.length) {
|
|
1086
|
+
const newWidth = Math.max(0, width);
|
|
1087
|
+
const field = fields[columnIndex];
|
|
1088
|
+
if (field) {
|
|
1089
|
+
this.table.sizes[field.path] = newWidth;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
//
|
|
1094
|
+
// Pinning
|
|
1095
|
+
//
|
|
1096
|
+
// TODO(burdon): Change to setPinned(on/off).
|
|
1097
|
+
pinRow(rowIndex, side) {
|
|
1098
|
+
this._pinnedRows[side].push(rowIndex);
|
|
1099
|
+
}
|
|
1100
|
+
unpinRow(rowIndex) {
|
|
1101
|
+
this._pinnedRows.top = this._pinnedRows.top.filter((index) => index !== rowIndex);
|
|
1102
|
+
this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
|
|
1103
|
+
}
|
|
1104
|
+
//
|
|
1105
|
+
// View operations
|
|
1106
|
+
//
|
|
1107
|
+
saveView() {
|
|
1108
|
+
this._sorting.save();
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
var editorTextToCellValue = (props, value) => {
|
|
1112
|
+
switch (props.format) {
|
|
1113
|
+
case FormatEnum2.Ref: {
|
|
1114
|
+
if (isLiveObject(value)) {
|
|
1115
|
+
return Ref.make(value);
|
|
1116
|
+
} else {
|
|
1117
|
+
return value;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
case FormatEnum2.SingleSelect: {
|
|
1121
|
+
const ids = extractTagIds(value);
|
|
1122
|
+
if (ids && ids.length > 0) {
|
|
1123
|
+
return ids[0];
|
|
1124
|
+
} else {
|
|
1125
|
+
return value;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
case FormatEnum2.MultiSelect: {
|
|
1129
|
+
const ids = extractTagIds(value);
|
|
1130
|
+
return ids || value;
|
|
1131
|
+
}
|
|
1132
|
+
default: {
|
|
1133
|
+
return parseValue({
|
|
1134
|
+
type: props.type,
|
|
1135
|
+
format: props.format,
|
|
1136
|
+
value
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// src/model/modal-controller.ts
|
|
1143
|
+
import { signal as signal4 } from "@preact/signals-core";
|
|
1144
|
+
import { log } from "@dxos/log";
|
|
1145
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
|
|
1146
|
+
var ModalController = class {
|
|
1147
|
+
constructor() {
|
|
1148
|
+
this._state = signal4({
|
|
1149
|
+
type: "closed"
|
|
1150
|
+
});
|
|
1151
|
+
this._triggerRef = {
|
|
1152
|
+
current: null
|
|
1153
|
+
};
|
|
1154
|
+
this.setTrigger = (trigger) => {
|
|
1155
|
+
this._triggerRef.current = trigger;
|
|
1156
|
+
};
|
|
1157
|
+
this.showRowMenu = (rowIndex) => {
|
|
1158
|
+
this._state.value = {
|
|
1159
|
+
type: "row",
|
|
1160
|
+
rowIndex
|
|
1161
|
+
};
|
|
1162
|
+
};
|
|
1163
|
+
this.showColumnMenu = (fieldId) => {
|
|
1164
|
+
this._state.value = {
|
|
1165
|
+
type: "column",
|
|
1166
|
+
fieldId
|
|
1167
|
+
};
|
|
1168
|
+
};
|
|
1169
|
+
this.showColumnCreator = () => {
|
|
1170
|
+
this._state.value = {
|
|
1171
|
+
type: "columnSettings",
|
|
1172
|
+
mode: {
|
|
1173
|
+
type: "create"
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
};
|
|
1177
|
+
this.showReferencePanel = (targetId, schemaId) => {
|
|
1178
|
+
this._state.value = {
|
|
1179
|
+
type: "refPanel",
|
|
1180
|
+
targetId,
|
|
1181
|
+
typename: schemaId
|
|
1182
|
+
};
|
|
1183
|
+
};
|
|
1184
|
+
this.openColumnSettings = () => {
|
|
1185
|
+
if (this._state.value.type === "column") {
|
|
1186
|
+
const fieldId = this._state.value.fieldId;
|
|
1187
|
+
requestAnimationFrame(() => {
|
|
1188
|
+
this._state.value = {
|
|
1189
|
+
type: "columnSettings",
|
|
1190
|
+
mode: {
|
|
1191
|
+
type: "edit",
|
|
1192
|
+
fieldId
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
this.openCreateRef = (typename, anchorCell, initialValues, onCreate) => {
|
|
1199
|
+
if (anchorCell) {
|
|
1200
|
+
this._triggerRef.current = anchorCell;
|
|
1201
|
+
this._state.value = {
|
|
1202
|
+
type: "createRefPanel",
|
|
1203
|
+
typename,
|
|
1204
|
+
initialValues,
|
|
1205
|
+
onCreate
|
|
1206
|
+
};
|
|
1207
|
+
} else {
|
|
1208
|
+
log.warn("anchor cell not found while creating new ref", void 0, {
|
|
1209
|
+
F: __dxlog_file2,
|
|
1210
|
+
L: 86,
|
|
1211
|
+
S: this,
|
|
1212
|
+
C: (f, a) => f(...a)
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
this.close = () => {
|
|
1217
|
+
this._state.value = {
|
|
1218
|
+
type: "closed"
|
|
1219
|
+
};
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
get state() {
|
|
1223
|
+
return this._state;
|
|
1224
|
+
}
|
|
1225
|
+
get trigger() {
|
|
1226
|
+
return this._triggerRef;
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// src/model/table-presentation.ts
|
|
1231
|
+
import { signal as signal5 } from "@preact/signals-core";
|
|
1232
|
+
import { isNotNullable } from "effect/Predicate";
|
|
1233
|
+
import { Obj as Obj4 } from "@dxos/echo";
|
|
1234
|
+
import { FormatEnum as FormatEnum3, getValue as getValue3, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
|
|
1235
|
+
import { cellClassesForFieldType, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
|
|
1236
|
+
import { toPlaneCellIndex } from "@dxos/react-ui-grid";
|
|
1237
|
+
import { mx } from "@dxos/react-ui-theme";
|
|
1238
|
+
import { VIEW_FIELD_LIMIT } from "@dxos/schema";
|
|
1239
|
+
var TablePresentation = class {
|
|
1240
|
+
constructor(model, _visibleRange = signal5({
|
|
1241
|
+
start: {
|
|
1242
|
+
row: 0,
|
|
1243
|
+
col: 0
|
|
1244
|
+
},
|
|
1245
|
+
end: {
|
|
1246
|
+
row: 0,
|
|
1247
|
+
col: 0
|
|
1248
|
+
}
|
|
1249
|
+
})) {
|
|
1250
|
+
this.model = model;
|
|
1251
|
+
this._visibleRange = _visibleRange;
|
|
1252
|
+
this.fieldProjectionCache = /* @__PURE__ */ new Map();
|
|
1253
|
+
}
|
|
1254
|
+
getCells(range, plane) {
|
|
1255
|
+
this.fieldProjectionCache.clear();
|
|
1256
|
+
let cells;
|
|
1257
|
+
switch (plane) {
|
|
1258
|
+
case "grid":
|
|
1259
|
+
this._visibleRange.value = range;
|
|
1260
|
+
cells = this.getMainGridCells(range);
|
|
1261
|
+
break;
|
|
1262
|
+
case "frozenRowsStart":
|
|
1263
|
+
cells = this.getHeaderCells(range);
|
|
1264
|
+
break;
|
|
1265
|
+
case "frozenColsStart":
|
|
1266
|
+
cells = this.getSelectionColumnCells(range);
|
|
1267
|
+
break;
|
|
1268
|
+
case "frozenColsEnd":
|
|
1269
|
+
cells = this.getActionColumnCells(range);
|
|
1270
|
+
break;
|
|
1271
|
+
case "fixedStartStart":
|
|
1272
|
+
cells = this.getSelectAllCell();
|
|
1273
|
+
break;
|
|
1274
|
+
case "fixedStartEnd":
|
|
1275
|
+
cells = this.getNewColumnCell();
|
|
1276
|
+
break;
|
|
1277
|
+
case "frozenRowsEnd":
|
|
1278
|
+
cells = this.getDraftRowCells(range);
|
|
1279
|
+
break;
|
|
1280
|
+
case "fixedEndEnd":
|
|
1281
|
+
cells = this.getDraftActionCells(range);
|
|
1282
|
+
break;
|
|
1283
|
+
default:
|
|
1284
|
+
cells = {};
|
|
1285
|
+
}
|
|
1286
|
+
if (plane === "grid" && this.model.features.dataEditable === false) {
|
|
1287
|
+
Object.values(cells).forEach((cell) => {
|
|
1288
|
+
cell.readonly = "text-select";
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
return cells;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Gets a field projection, using cache if available within the current render pass.
|
|
1295
|
+
*/
|
|
1296
|
+
getFieldProjection(fieldId) {
|
|
1297
|
+
let projection = this.fieldProjectionCache.get(fieldId);
|
|
1298
|
+
if (!projection) {
|
|
1299
|
+
projection = this.model.projection.getFieldProjection(fieldId);
|
|
1300
|
+
this.fieldProjectionCache.set(fieldId, projection);
|
|
1301
|
+
}
|
|
1302
|
+
return projection;
|
|
1303
|
+
}
|
|
1304
|
+
createDataCell(cells, obj, field, colIndex, displayIndex) {
|
|
1305
|
+
const { props } = this.getFieldProjection(field.id);
|
|
1306
|
+
const cell = {
|
|
1307
|
+
get value() {
|
|
1308
|
+
const value = getValue3(obj, field.path);
|
|
1309
|
+
if (value == null) {
|
|
1310
|
+
return "";
|
|
1311
|
+
}
|
|
1312
|
+
if (props.type === "array") {
|
|
1313
|
+
return "";
|
|
1314
|
+
}
|
|
1315
|
+
switch (props.format) {
|
|
1316
|
+
case FormatEnum3.Boolean:
|
|
1317
|
+
case FormatEnum3.SingleSelect:
|
|
1318
|
+
case FormatEnum3.MultiSelect: {
|
|
1319
|
+
return "";
|
|
1320
|
+
}
|
|
1321
|
+
case FormatEnum3.Ref: {
|
|
1322
|
+
if (!field.referencePath) {
|
|
1323
|
+
return "";
|
|
1324
|
+
}
|
|
1325
|
+
return getValue3(value.target, field.referencePath);
|
|
1326
|
+
}
|
|
1327
|
+
default: {
|
|
1328
|
+
return formatForDisplay2({
|
|
1329
|
+
type: props.type,
|
|
1330
|
+
format: props.format,
|
|
1331
|
+
value
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
const classes = [];
|
|
1338
|
+
const formatClasses = cellClassesForFieldType({
|
|
1339
|
+
type: props.type,
|
|
1340
|
+
format: props.format
|
|
1341
|
+
});
|
|
1342
|
+
if (formatClasses) {
|
|
1343
|
+
classes.push(formatClasses);
|
|
1344
|
+
}
|
|
1345
|
+
const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj), this.model.selection.selectionMode);
|
|
1346
|
+
if (rowSelectionClasses) {
|
|
1347
|
+
classes.push(rowSelectionClasses);
|
|
1348
|
+
}
|
|
1349
|
+
if (classes.length > 0) {
|
|
1350
|
+
cell.className = mx(classes.flat());
|
|
1351
|
+
}
|
|
1352
|
+
if (props.type === TypeEnum2.Array) {
|
|
1353
|
+
const targetArray = getValue3(obj, field.path);
|
|
1354
|
+
if (targetArray && Array.isArray(targetArray)) {
|
|
1355
|
+
const getLabel = (value) => {
|
|
1356
|
+
if (!value) {
|
|
1357
|
+
return void 0;
|
|
1358
|
+
}
|
|
1359
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1360
|
+
return String(value);
|
|
1361
|
+
}
|
|
1362
|
+
if (Array.isArray(value)) {
|
|
1363
|
+
return "Array";
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof value === "object" && value.name) {
|
|
1366
|
+
return value.name;
|
|
1367
|
+
}
|
|
1368
|
+
if (typeof value === "object" && value.value) {
|
|
1369
|
+
return `${value.value}`;
|
|
1370
|
+
}
|
|
1371
|
+
return "Object";
|
|
1372
|
+
};
|
|
1373
|
+
const tags = targetArray.map(getLabel).filter(isNotNullable).map((title) => {
|
|
1374
|
+
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
1375
|
+
}).join("");
|
|
1376
|
+
cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (props.format === FormatEnum3.Ref && props.referenceSchema) {
|
|
1380
|
+
const targetObj = getValue3(obj, field.path)?.target;
|
|
1381
|
+
if (targetObj) {
|
|
1382
|
+
const dxn = Obj4.getDXN(targetObj)?.toString();
|
|
1383
|
+
cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (props.format === FormatEnum3.Boolean) {
|
|
1387
|
+
const value = getValue3(obj, field.path);
|
|
1388
|
+
cell.accessoryHtml = tableControls.switch.render({
|
|
1389
|
+
colIndex,
|
|
1390
|
+
rowIndex: displayIndex,
|
|
1391
|
+
checked: value ?? false
|
|
1392
|
+
});
|
|
1393
|
+
cell.readonly = "no-text-select";
|
|
1394
|
+
}
|
|
1395
|
+
if (props.format === FormatEnum3.SingleSelect) {
|
|
1396
|
+
const value = getValue3(obj, field.path);
|
|
1397
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
1398
|
+
if (options) {
|
|
1399
|
+
const option = options.find((o) => o.id === value);
|
|
1400
|
+
if (option) {
|
|
1401
|
+
cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (props.format === FormatEnum3.MultiSelect) {
|
|
1406
|
+
const values = getValue3(obj, field.path);
|
|
1407
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
1408
|
+
if (options && values && values.length > 0) {
|
|
1409
|
+
const tags = values.map((value) => {
|
|
1410
|
+
const option = options.find((o) => o.id === value);
|
|
1411
|
+
if (option) {
|
|
1412
|
+
return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
1413
|
+
}
|
|
1414
|
+
return null;
|
|
1415
|
+
}).filter(Boolean).join("");
|
|
1416
|
+
if (tags) {
|
|
1417
|
+
cell.accessoryHtml = tags;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const idx = toPlaneCellIndex({
|
|
1422
|
+
col: colIndex,
|
|
1423
|
+
row: displayIndex
|
|
1424
|
+
});
|
|
1425
|
+
cells[idx] = cell;
|
|
1426
|
+
}
|
|
1427
|
+
getMainGridCells(range) {
|
|
1428
|
+
const cells = {};
|
|
1429
|
+
const fields = this.model.projection?.fields ?? [];
|
|
1430
|
+
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
1431
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
1432
|
+
const field = fields[col];
|
|
1433
|
+
if (!field) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
this.createDataCell(cells, this.model.rows.value[row], field, col, row);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return cells;
|
|
1440
|
+
}
|
|
1441
|
+
getDraftRowCells(range) {
|
|
1442
|
+
const cells = {};
|
|
1443
|
+
const fields = this.model.projection?.fields ?? [];
|
|
1444
|
+
const draftRows = this.model.draftRows.value;
|
|
1445
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
1446
|
+
const draftRow = draftRows[row];
|
|
1447
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
1448
|
+
const field = fields[col];
|
|
1449
|
+
if (!field) {
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
1453
|
+
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
1454
|
+
const cellIndex = toPlaneCellIndex({
|
|
1455
|
+
col,
|
|
1456
|
+
row
|
|
1457
|
+
});
|
|
1458
|
+
const cellValue = cells[cellIndex];
|
|
1459
|
+
if (cellValue) {
|
|
1460
|
+
const existingClasses = cellValue.className || "";
|
|
1461
|
+
const draftClasses = "dx-grid__cell--flagged";
|
|
1462
|
+
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return cells;
|
|
1468
|
+
}
|
|
1469
|
+
getHeaderCells(range) {
|
|
1470
|
+
const cells = {};
|
|
1471
|
+
const fields = this.model.projection?.fields ?? [];
|
|
1472
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
1473
|
+
const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
|
|
1474
|
+
const sorting = this.model.sorting?.sorting;
|
|
1475
|
+
const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
|
|
1476
|
+
cells[toPlaneCellIndex({
|
|
1477
|
+
col,
|
|
1478
|
+
row: 0
|
|
1479
|
+
})] = {
|
|
1480
|
+
// TODO(burdon): Use same logic as form for fallback title.
|
|
1481
|
+
value: props.title ?? field.path,
|
|
1482
|
+
readonly: true,
|
|
1483
|
+
resizeHandle: "col",
|
|
1484
|
+
accessoryHtml: `
|
|
1485
|
+
${direction !== void 0 ? tableButtons.sort.render({
|
|
1486
|
+
fieldId: field.id,
|
|
1487
|
+
direction
|
|
1488
|
+
}) : ""}
|
|
1489
|
+
${tableButtons.columnSettings.render({
|
|
1490
|
+
fieldId: field.id
|
|
1491
|
+
})}
|
|
1492
|
+
`,
|
|
1493
|
+
className: "!bg-toolbarSurface"
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
return cells;
|
|
1497
|
+
}
|
|
1498
|
+
getSelectionColumnCells(range) {
|
|
1499
|
+
const cells = {};
|
|
1500
|
+
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
1501
|
+
const isSelected = this.model.selection.isRowIndexSelected(row);
|
|
1502
|
+
const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
|
|
1503
|
+
cells[toPlaneCellIndex({
|
|
1504
|
+
col: 0,
|
|
1505
|
+
row
|
|
1506
|
+
})] = {
|
|
1507
|
+
value: "",
|
|
1508
|
+
readonly: "no-text-select",
|
|
1509
|
+
className: classes ? mx(classes) : void 0,
|
|
1510
|
+
accessoryHtml: tableControls.checkbox.render({
|
|
1511
|
+
rowIndex: row,
|
|
1512
|
+
checked: isSelected
|
|
1513
|
+
})
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return cells;
|
|
1517
|
+
}
|
|
1518
|
+
getActionColumnCells(range) {
|
|
1519
|
+
const cells = {};
|
|
1520
|
+
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
1521
|
+
const isSelected = this.model.selection.isRowIndexSelected(row);
|
|
1522
|
+
const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
|
|
1523
|
+
cells[toPlaneCellIndex({
|
|
1524
|
+
col: 0,
|
|
1525
|
+
row
|
|
1526
|
+
})] = {
|
|
1527
|
+
value: "",
|
|
1528
|
+
readonly: true,
|
|
1529
|
+
className: classes ? mx(classes) : void 0,
|
|
1530
|
+
accessoryHtml: tableButtons.rowMenu.render({
|
|
1531
|
+
rowIndex: row
|
|
1532
|
+
})
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return cells;
|
|
1536
|
+
}
|
|
1537
|
+
getSelectAllCell() {
|
|
1538
|
+
if (!this.model.features.selection.enabled || this.model.selection.selectionMode === "single") {
|
|
1539
|
+
return {
|
|
1540
|
+
[toPlaneCellIndex({
|
|
1541
|
+
col: 0,
|
|
1542
|
+
row: 0
|
|
1543
|
+
})]: {
|
|
1544
|
+
className: "!bg-toolbarSurface",
|
|
1545
|
+
readonly: true,
|
|
1546
|
+
value: ""
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
return {
|
|
1551
|
+
[toPlaneCellIndex({
|
|
1552
|
+
col: 0,
|
|
1553
|
+
row: 0
|
|
1554
|
+
})]: {
|
|
1555
|
+
accessoryHtml: tableControls.checkbox.render({
|
|
1556
|
+
rowIndex: 0,
|
|
1557
|
+
header: true,
|
|
1558
|
+
checked: this.model.selection.allRowsSeleted.value
|
|
1559
|
+
}),
|
|
1560
|
+
className: "!bg-toolbarSurface",
|
|
1561
|
+
readonly: true,
|
|
1562
|
+
value: ""
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
getNewColumnCell() {
|
|
1567
|
+
return {
|
|
1568
|
+
[toPlaneCellIndex({
|
|
1569
|
+
col: 0,
|
|
1570
|
+
row: 0
|
|
1571
|
+
})]: {
|
|
1572
|
+
accessoryHtml: this.model.features.schemaEditable ? tableButtons.addColumn.render({
|
|
1573
|
+
disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
|
|
1574
|
+
}) : void 0,
|
|
1575
|
+
className: "!bg-toolbarSurface",
|
|
1576
|
+
readonly: true,
|
|
1577
|
+
value: ""
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
getDraftActionCells(range) {
|
|
1582
|
+
const cells = {};
|
|
1583
|
+
const draftRows = this.model.draftRows.value;
|
|
1584
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
1585
|
+
const draftRow = draftRows[row];
|
|
1586
|
+
const disabled = !draftRow.valid;
|
|
1587
|
+
cells[toPlaneCellIndex({
|
|
1588
|
+
col: 0,
|
|
1589
|
+
row
|
|
1590
|
+
})] = {
|
|
1591
|
+
value: "",
|
|
1592
|
+
readonly: true,
|
|
1593
|
+
accessoryHtml: tableButtons.saveDraftRow.render({
|
|
1594
|
+
rowIndex: row,
|
|
1595
|
+
disabled
|
|
1596
|
+
})
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
return cells;
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
var cellClassesForRowSelection = (selected, selectionMode) => {
|
|
1603
|
+
if (!selected) {
|
|
1604
|
+
if (selectionMode === "single") {
|
|
1605
|
+
return [
|
|
1606
|
+
"!cursor-pointer"
|
|
1607
|
+
];
|
|
1608
|
+
} else {
|
|
1609
|
+
return void 0;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
switch (selectionMode) {
|
|
1613
|
+
case "single":
|
|
1614
|
+
return [
|
|
1615
|
+
"!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer"
|
|
1616
|
+
];
|
|
1617
|
+
case "multiple":
|
|
1618
|
+
return [
|
|
1619
|
+
"!bg-gridCellSelected"
|
|
1620
|
+
];
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// src/hooks/useTableModel.ts
|
|
1625
|
+
import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
|
|
1626
|
+
import { effect as effect3 } from "@preact/signals-core";
|
|
1627
|
+
import orderBy2 from "lodash.orderby";
|
|
1628
|
+
import { useEffect, useMemo, useState } from "react";
|
|
1629
|
+
import { fullyQualifiedId as fullyQualifiedId2 } from "@dxos/react-client/echo";
|
|
1630
|
+
import { useSelected, useSelectionActions } from "@dxos/react-ui-attention";
|
|
1631
|
+
import { isNonNullable } from "@dxos/util";
|
|
1632
|
+
var useTableModel = ({ view, schema, projection, rows, rowActions, features, onSelectionChanged, onRowAction, ...props }) => {
|
|
1633
|
+
var _effect = _useSignals();
|
|
1634
|
+
try {
|
|
1635
|
+
const selected = useSelected(view && fullyQualifiedId2(view), "multi");
|
|
1636
|
+
const initialSelection = useMemo(() => selected, [
|
|
1637
|
+
view
|
|
1638
|
+
]);
|
|
1639
|
+
const [model, setModel] = useState();
|
|
1640
|
+
useEffect(() => {
|
|
1641
|
+
if (!view || !schema) {
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
let model2;
|
|
1645
|
+
const t = setTimeout(async () => {
|
|
1646
|
+
model2 = new TableModel({
|
|
1647
|
+
view,
|
|
1648
|
+
schema,
|
|
1649
|
+
projection,
|
|
1650
|
+
features,
|
|
1651
|
+
rowActions,
|
|
1652
|
+
initialSelection,
|
|
1653
|
+
onRowAction,
|
|
1654
|
+
...props
|
|
1655
|
+
});
|
|
1656
|
+
await model2.open();
|
|
1657
|
+
setModel(model2);
|
|
1658
|
+
});
|
|
1659
|
+
return () => {
|
|
1660
|
+
clearTimeout(t);
|
|
1661
|
+
void model2?.close();
|
|
1662
|
+
};
|
|
1663
|
+
}, [
|
|
1664
|
+
view,
|
|
1665
|
+
schema,
|
|
1666
|
+
projection,
|
|
1667
|
+
features,
|
|
1668
|
+
rowActions,
|
|
1669
|
+
initialSelection
|
|
1670
|
+
]);
|
|
1671
|
+
useEffect(() => {
|
|
1672
|
+
if (rows) {
|
|
1673
|
+
const sortedRows = orderBy2(rows, [
|
|
1674
|
+
(row) => String(row.id)
|
|
1675
|
+
], [
|
|
1676
|
+
"asc"
|
|
1677
|
+
]);
|
|
1678
|
+
model?.setRows(sortedRows);
|
|
1679
|
+
}
|
|
1680
|
+
}, [
|
|
1681
|
+
model,
|
|
1682
|
+
rows
|
|
1683
|
+
]);
|
|
1684
|
+
const { multiSelect, clear } = useSelectionActions([
|
|
1685
|
+
model?.id
|
|
1686
|
+
].filter(isNonNullable));
|
|
1687
|
+
useEffect(() => {
|
|
1688
|
+
if (!model) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
const unsubscribe = effect3(() => {
|
|
1692
|
+
const selectedItems = [
|
|
1693
|
+
...model.selection.selection.value
|
|
1694
|
+
];
|
|
1695
|
+
multiSelect(selectedItems);
|
|
1696
|
+
onSelectionChanged?.(selectedItems);
|
|
1697
|
+
});
|
|
1698
|
+
return () => {
|
|
1699
|
+
clear();
|
|
1700
|
+
unsubscribe();
|
|
1701
|
+
};
|
|
1702
|
+
}, [
|
|
1703
|
+
model,
|
|
1704
|
+
onSelectionChanged
|
|
1705
|
+
]);
|
|
1706
|
+
return model;
|
|
1707
|
+
} finally {
|
|
1708
|
+
_effect.f();
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
// src/hooks/useAddRow.ts
|
|
1713
|
+
import { useCallback } from "react";
|
|
1714
|
+
import { Obj as Obj5 } from "@dxos/echo";
|
|
1715
|
+
var useAddRow = ({ space, schema }) => {
|
|
1716
|
+
return useCallback((data) => {
|
|
1717
|
+
if (space && schema) {
|
|
1718
|
+
try {
|
|
1719
|
+
space.db.add(Obj5.make(schema, data ?? {}));
|
|
1720
|
+
return true;
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
return false;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return false;
|
|
1726
|
+
}, [
|
|
1727
|
+
space,
|
|
1728
|
+
schema
|
|
1729
|
+
]);
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
export {
|
|
1733
|
+
getBaseSchema,
|
|
1734
|
+
makeDynamicTable,
|
|
1735
|
+
touch,
|
|
1736
|
+
createTable,
|
|
1737
|
+
BUTTON_IDENTIFIERS,
|
|
1738
|
+
tableButtons,
|
|
1739
|
+
CONTROL_IDENTIFIERS,
|
|
1740
|
+
CheckboxStory,
|
|
1741
|
+
SwitchStory,
|
|
1742
|
+
tableControls,
|
|
1743
|
+
narrowSchema,
|
|
1744
|
+
TableModel,
|
|
1745
|
+
ModalController,
|
|
1746
|
+
TablePresentation,
|
|
1747
|
+
cellClassesForRowSelection,
|
|
1748
|
+
useTableModel,
|
|
1749
|
+
useAddRow
|
|
1750
|
+
};
|
|
1751
|
+
//# sourceMappingURL=chunk-5XLSHLOG.mjs.map
|