@budibase/frontend-core 2.29.21 → 2.29.23
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/package.json +5 -5
- package/src/components/FilterBuilder.svelte +1 -5
- package/src/components/grid/cells/DataCell.svelte +63 -12
- package/src/components/grid/cells/GridCell.svelte +36 -16
- package/src/components/grid/cells/GutterCell.svelte +15 -8
- package/src/components/grid/cells/HeaderCell.svelte +3 -3
- package/src/components/grid/cells/RelationshipCell.svelte +16 -8
- package/src/components/grid/controls/BulkDeleteHandler.svelte +98 -13
- package/src/components/grid/controls/BulkDuplicationHandler.svelte +79 -0
- package/src/components/grid/controls/ClipboardHandler.svelte +67 -0
- package/src/components/grid/controls/ColumnsSettingButton.svelte +5 -10
- package/src/components/grid/controls/SizeButton.svelte +6 -13
- package/src/components/grid/controls/SortButton.svelte +8 -22
- package/src/components/grid/layout/ButtonColumn.svelte +12 -6
- package/src/components/grid/layout/Grid.svelte +11 -7
- package/src/components/grid/layout/GridBody.svelte +2 -2
- package/src/components/grid/layout/GridRow.svelte +12 -6
- package/src/components/grid/layout/GridScrollWrapper.svelte +9 -7
- package/src/components/grid/layout/HeaderRow.svelte +2 -2
- package/src/components/grid/layout/NewColumnButton.svelte +11 -5
- package/src/components/grid/layout/NewRow.svelte +16 -12
- package/src/components/grid/layout/StickyColumn.svelte +24 -14
- package/src/components/grid/lib/utils.js +4 -4
- package/src/components/grid/overlays/KeyboardManager.svelte +144 -95
- package/src/components/grid/overlays/MenuOverlay.svelte +114 -63
- package/src/components/grid/overlays/ReorderOverlay.svelte +14 -18
- package/src/components/grid/overlays/ResizeOverlay.svelte +8 -21
- package/src/components/grid/stores/clipboard.js +215 -18
- package/src/components/grid/stores/columns.js +78 -97
- package/src/components/grid/stores/conditions.js +157 -0
- package/src/components/grid/stores/config.js +2 -2
- package/src/components/grid/stores/datasource.js +4 -14
- package/src/components/grid/stores/datasources/nonPlus.js +2 -4
- package/src/components/grid/stores/datasources/table.js +6 -5
- package/src/components/grid/stores/datasources/viewV2.js +7 -9
- package/src/components/grid/stores/index.js +5 -3
- package/src/components/grid/stores/menu.js +40 -6
- package/src/components/grid/stores/pagination.js +9 -3
- package/src/components/grid/stores/reorder.js +67 -42
- package/src/components/grid/stores/resize.js +1 -1
- package/src/components/grid/stores/rows.js +220 -85
- package/src/components/grid/stores/scroll.js +31 -28
- package/src/components/grid/stores/ui.js +295 -70
- package/src/components/grid/stores/users.js +2 -2
- package/src/components/grid/stores/validation.js +43 -16
- package/src/components/grid/stores/viewport.js +30 -24
- package/src/components/index.js +1 -0
- package/src/constants.js +3 -0
- package/src/themes/midnight.css +18 -17
- package/src/themes/nord.css +2 -1
- package/src/utils/utils.js +2 -0
|
@@ -2,12 +2,11 @@ import { writable, get, derived } from "svelte/store"
|
|
|
2
2
|
import { tick } from "svelte"
|
|
3
3
|
import {
|
|
4
4
|
DefaultRowHeight,
|
|
5
|
-
GutterWidth,
|
|
6
5
|
LargeRowHeight,
|
|
7
6
|
MediumRowHeight,
|
|
8
7
|
NewRowID,
|
|
9
8
|
} from "../lib/constants"
|
|
10
|
-
import { parseCellID } from "../lib/utils"
|
|
9
|
+
import { getCellID, parseCellID } from "../lib/utils"
|
|
11
10
|
|
|
12
11
|
export const createStores = context => {
|
|
13
12
|
const { props } = context
|
|
@@ -22,34 +21,15 @@ export const createStores = context => {
|
|
|
22
21
|
const keyboardBlocked = writable(false)
|
|
23
22
|
const isDragging = writable(false)
|
|
24
23
|
const buttonColumnWidth = writable(0)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return parseCellID($focusedCellId)?.id
|
|
31
|
-
},
|
|
32
|
-
null
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
// Toggles whether a certain row ID is selected or not
|
|
36
|
-
const toggleSelectedRow = id => {
|
|
37
|
-
selectedRows.update(state => {
|
|
38
|
-
let newState = {
|
|
39
|
-
...state,
|
|
40
|
-
[id]: !state[id],
|
|
41
|
-
}
|
|
42
|
-
if (!newState[id]) {
|
|
43
|
-
delete newState[id]
|
|
44
|
-
}
|
|
45
|
-
return newState
|
|
46
|
-
})
|
|
47
|
-
}
|
|
24
|
+
const cellSelection = writable({
|
|
25
|
+
active: false,
|
|
26
|
+
sourceCellId: null,
|
|
27
|
+
targetCellId: null,
|
|
28
|
+
})
|
|
48
29
|
|
|
49
30
|
return {
|
|
50
31
|
focusedCellId,
|
|
51
32
|
focusedCellAPI,
|
|
52
|
-
focusedRowId,
|
|
53
33
|
previousFocusedRowId,
|
|
54
34
|
previousFocusedCellId,
|
|
55
35
|
hoveredRowId,
|
|
@@ -58,35 +38,38 @@ export const createStores = context => {
|
|
|
58
38
|
keyboardBlocked,
|
|
59
39
|
isDragging,
|
|
60
40
|
buttonColumnWidth,
|
|
61
|
-
selectedRows
|
|
62
|
-
|
|
63
|
-
actions: {
|
|
64
|
-
toggleRow: toggleSelectedRow,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
41
|
+
selectedRows,
|
|
42
|
+
cellSelection,
|
|
67
43
|
}
|
|
68
44
|
}
|
|
69
45
|
|
|
70
46
|
export const deriveStores = context => {
|
|
71
|
-
const {
|
|
72
|
-
|
|
47
|
+
const {
|
|
48
|
+
focusedCellId,
|
|
49
|
+
rows,
|
|
50
|
+
rowLookupMap,
|
|
51
|
+
rowHeight,
|
|
52
|
+
width,
|
|
53
|
+
selectedRows,
|
|
54
|
+
cellSelection,
|
|
55
|
+
columnLookupMap,
|
|
56
|
+
visibleColumns,
|
|
57
|
+
} = context
|
|
58
|
+
|
|
59
|
+
// Derive the current focused row ID
|
|
60
|
+
const focusedRowId = derived(focusedCellId, $focusedCellId => {
|
|
61
|
+
return parseCellID($focusedCellId).rowId
|
|
62
|
+
})
|
|
73
63
|
|
|
74
64
|
// Derive the row that contains the selected cell
|
|
75
65
|
const focusedRow = derived(
|
|
76
|
-
[
|
|
77
|
-
([$
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// Edge case for new rows
|
|
81
|
-
if (rowId === NewRowID) {
|
|
66
|
+
[focusedRowId, rowLookupMap],
|
|
67
|
+
([$focusedRowId, $rowLookupMap]) => {
|
|
68
|
+
if ($focusedRowId === NewRowID) {
|
|
82
69
|
return { _id: NewRowID }
|
|
83
70
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const index = $rowLookupMap[rowId]
|
|
87
|
-
return $rows[index]
|
|
88
|
-
},
|
|
89
|
-
null
|
|
71
|
+
return $rowLookupMap[$focusedRowId]
|
|
72
|
+
}
|
|
90
73
|
)
|
|
91
74
|
|
|
92
75
|
// Derive the amount of content lines to show in cells depending on row height
|
|
@@ -100,24 +83,200 @@ export const deriveStores = context => {
|
|
|
100
83
|
})
|
|
101
84
|
|
|
102
85
|
// Derive whether we should use the compact UI, depending on width
|
|
103
|
-
const compact = derived(
|
|
104
|
-
return
|
|
86
|
+
const compact = derived(width, $width => {
|
|
87
|
+
return $width < 600
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Derive we have any selected rows or not
|
|
91
|
+
const selectedRowCount = derived(selectedRows, $selectedRows => {
|
|
92
|
+
return Object.keys($selectedRows).length
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Derive whether or not we're actively selecting cells
|
|
96
|
+
const isSelectingCells = derived(cellSelection, $cellSelection => {
|
|
97
|
+
return $cellSelection.active
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Derive the full extent of all selected cells
|
|
101
|
+
const selectedCells = derived(
|
|
102
|
+
[cellSelection, rowLookupMap, columnLookupMap],
|
|
103
|
+
([$cellSelection, $rowLookupMap, $columnLookupMap]) => {
|
|
104
|
+
const { sourceCellId, targetCellId } = $cellSelection
|
|
105
|
+
if (!sourceCellId || !targetCellId || sourceCellId === targetCellId) {
|
|
106
|
+
return []
|
|
107
|
+
}
|
|
108
|
+
const $rows = get(rows)
|
|
109
|
+
const $visibleColumns = get(visibleColumns)
|
|
110
|
+
|
|
111
|
+
// Get source and target row and column indices
|
|
112
|
+
const sourceInfo = parseCellID(sourceCellId)
|
|
113
|
+
const targetInfo = parseCellID(targetCellId)
|
|
114
|
+
if (sourceInfo.rowId === NewRowID) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Row indices
|
|
119
|
+
const sourceRowIndex = $rowLookupMap[sourceInfo.rowId]?.__idx
|
|
120
|
+
const targetRowIndex = $rowLookupMap[targetInfo.rowId]?.__idx
|
|
121
|
+
if (sourceRowIndex == null || targetRowIndex == null) {
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
const lowerRowIndex = Math.min(sourceRowIndex, targetRowIndex)
|
|
125
|
+
let upperRowIndex = Math.max(sourceRowIndex, targetRowIndex)
|
|
126
|
+
|
|
127
|
+
// Cap rows at 50
|
|
128
|
+
upperRowIndex = Math.min(upperRowIndex, lowerRowIndex + 49)
|
|
129
|
+
|
|
130
|
+
// Column indices
|
|
131
|
+
const sourceColIndex = $columnLookupMap[sourceInfo.field].__idx
|
|
132
|
+
const targetColIndex = $columnLookupMap[targetInfo.field].__idx
|
|
133
|
+
const lowerColIndex = Math.min(sourceColIndex, targetColIndex)
|
|
134
|
+
const upperColIndex = Math.max(sourceColIndex, targetColIndex)
|
|
135
|
+
|
|
136
|
+
// Build 2 dimensional array of all cells inside these bounds
|
|
137
|
+
let cells = []
|
|
138
|
+
let rowId, colName
|
|
139
|
+
for (let rowIdx = lowerRowIndex; rowIdx <= upperRowIndex; rowIdx++) {
|
|
140
|
+
let rowCells = []
|
|
141
|
+
for (let colIdx = lowerColIndex; colIdx <= upperColIndex; colIdx++) {
|
|
142
|
+
rowId = $rows[rowIdx]._id
|
|
143
|
+
colName = $visibleColumns[colIdx].name
|
|
144
|
+
rowCells.push(getCellID(rowId, colName))
|
|
145
|
+
}
|
|
146
|
+
cells.push(rowCells)
|
|
147
|
+
}
|
|
148
|
+
return cells
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// Derive a quick lookup map of the selected cells
|
|
153
|
+
const selectedCellMap = derived(selectedCells, $selectedCells => {
|
|
154
|
+
let map = {}
|
|
155
|
+
for (let row of $selectedCells) {
|
|
156
|
+
for (let cell of row) {
|
|
157
|
+
map[cell] = true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return map
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Derive the count of the selected cells
|
|
164
|
+
const selectedCellCount = derived(selectedCellMap, $selectedCellMap => {
|
|
165
|
+
return Object.keys($selectedCellMap).length
|
|
105
166
|
})
|
|
106
167
|
|
|
107
168
|
return {
|
|
169
|
+
focusedRowId,
|
|
108
170
|
focusedRow,
|
|
109
171
|
contentLines,
|
|
110
172
|
compact,
|
|
173
|
+
selectedRowCount,
|
|
174
|
+
isSelectingCells,
|
|
175
|
+
selectedCells,
|
|
176
|
+
selectedCellMap,
|
|
177
|
+
selectedCellCount,
|
|
111
178
|
}
|
|
112
179
|
}
|
|
113
180
|
|
|
114
181
|
export const createActions = context => {
|
|
115
|
-
const {
|
|
182
|
+
const {
|
|
183
|
+
focusedCellId,
|
|
184
|
+
hoveredRowId,
|
|
185
|
+
selectedRows,
|
|
186
|
+
rowLookupMap,
|
|
187
|
+
rows,
|
|
188
|
+
selectedRowCount,
|
|
189
|
+
cellSelection,
|
|
190
|
+
selectedCells,
|
|
191
|
+
} = context
|
|
192
|
+
// Keep the last selected index to use with bulk selection
|
|
193
|
+
let lastSelectedIndex = null
|
|
116
194
|
|
|
117
195
|
// Callback when leaving the grid, deselecting all focussed or selected items
|
|
118
196
|
const blur = () => {
|
|
119
197
|
focusedCellId.set(null)
|
|
120
198
|
hoveredRowId.set(null)
|
|
199
|
+
clearCellSelection()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Toggles whether a certain row ID is selected or not
|
|
203
|
+
const toggleSelectedRow = id => {
|
|
204
|
+
selectedRows.update(state => {
|
|
205
|
+
let newState = {
|
|
206
|
+
...state,
|
|
207
|
+
[id]: !state[id],
|
|
208
|
+
}
|
|
209
|
+
if (!newState[id]) {
|
|
210
|
+
delete newState[id]
|
|
211
|
+
} else {
|
|
212
|
+
lastSelectedIndex = get(rowLookupMap)[id].__idx
|
|
213
|
+
}
|
|
214
|
+
return newState
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const bulkSelectRows = id => {
|
|
219
|
+
if (!get(selectedRowCount)) {
|
|
220
|
+
toggleSelectedRow(id)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
if (lastSelectedIndex == null) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
const thisIndex = get(rowLookupMap)[id].__idx
|
|
227
|
+
|
|
228
|
+
// Skip if indices are the same
|
|
229
|
+
if (lastSelectedIndex === thisIndex) {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const from = Math.min(lastSelectedIndex, thisIndex)
|
|
234
|
+
const to = Math.max(lastSelectedIndex, thisIndex)
|
|
235
|
+
const $rows = get(rows)
|
|
236
|
+
selectedRows.update(state => {
|
|
237
|
+
for (let i = from; i <= to; i++) {
|
|
238
|
+
state[$rows[i]._id] = true
|
|
239
|
+
}
|
|
240
|
+
return state
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const startCellSelection = sourceCellId => {
|
|
245
|
+
cellSelection.set({
|
|
246
|
+
active: true,
|
|
247
|
+
sourceCellId,
|
|
248
|
+
targetCellId: sourceCellId,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const updateCellSelection = targetCellId => {
|
|
253
|
+
cellSelection.update(state => ({
|
|
254
|
+
...state,
|
|
255
|
+
targetCellId,
|
|
256
|
+
}))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const stopCellSelection = () => {
|
|
260
|
+
cellSelection.update(state => ({
|
|
261
|
+
...state,
|
|
262
|
+
active: false,
|
|
263
|
+
}))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const selectCellRange = (source, target) => {
|
|
267
|
+
cellSelection.set({
|
|
268
|
+
active: false,
|
|
269
|
+
sourceCellId: source,
|
|
270
|
+
targetCellId: target,
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const clearCellSelection = () => {
|
|
275
|
+
cellSelection.set({
|
|
276
|
+
active: false,
|
|
277
|
+
sourceCellId: null,
|
|
278
|
+
targetCellId: null,
|
|
279
|
+
})
|
|
121
280
|
}
|
|
122
281
|
|
|
123
282
|
return {
|
|
@@ -126,6 +285,23 @@ export const createActions = context => {
|
|
|
126
285
|
blur,
|
|
127
286
|
},
|
|
128
287
|
},
|
|
288
|
+
selectedRows: {
|
|
289
|
+
...selectedRows,
|
|
290
|
+
actions: {
|
|
291
|
+
toggleRow: toggleSelectedRow,
|
|
292
|
+
bulkSelectRows,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
selectedCells: {
|
|
296
|
+
...selectedCells,
|
|
297
|
+
actions: {
|
|
298
|
+
startSelecting: startCellSelection,
|
|
299
|
+
updateTarget: updateCellSelection,
|
|
300
|
+
stopSelecting: stopCellSelection,
|
|
301
|
+
selectRange: selectCellRange,
|
|
302
|
+
clear: clearCellSelection,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
129
305
|
}
|
|
130
306
|
}
|
|
131
307
|
|
|
@@ -134,28 +310,32 @@ export const initialise = context => {
|
|
|
134
310
|
focusedRowId,
|
|
135
311
|
previousFocusedRowId,
|
|
136
312
|
previousFocusedCellId,
|
|
137
|
-
|
|
313
|
+
rowLookupMap,
|
|
138
314
|
focusedCellId,
|
|
139
315
|
selectedRows,
|
|
140
316
|
hoveredRowId,
|
|
141
317
|
definition,
|
|
142
318
|
rowHeight,
|
|
143
319
|
fixedRowHeight,
|
|
320
|
+
selectedRowCount,
|
|
321
|
+
menu,
|
|
322
|
+
selectedCellCount,
|
|
323
|
+
selectedCells,
|
|
324
|
+
cellSelection,
|
|
144
325
|
} = context
|
|
145
326
|
|
|
146
327
|
// Ensure we clear invalid rows from state if they disappear
|
|
147
|
-
|
|
328
|
+
rowLookupMap.subscribe(async $rowLookupMap => {
|
|
148
329
|
// We tick here to ensure other derived stores have properly updated.
|
|
149
330
|
// We depend on the row lookup map which is a derived store,
|
|
150
331
|
await tick()
|
|
151
|
-
const $
|
|
332
|
+
const $focusedRowId = get(focusedRowId)
|
|
152
333
|
const $selectedRows = get(selectedRows)
|
|
153
334
|
const $hoveredRowId = get(hoveredRowId)
|
|
154
|
-
const hasRow =
|
|
335
|
+
const hasRow = id => $rowLookupMap[id] != null
|
|
155
336
|
|
|
156
|
-
// Check
|
|
157
|
-
|
|
158
|
-
if (selectedRowId && !hasRow(selectedRowId)) {
|
|
337
|
+
// Check focused cell
|
|
338
|
+
if ($focusedRowId && !hasRow($focusedRowId)) {
|
|
159
339
|
focusedCellId.set(null)
|
|
160
340
|
}
|
|
161
341
|
|
|
@@ -165,17 +345,19 @@ export const initialise = context => {
|
|
|
165
345
|
}
|
|
166
346
|
|
|
167
347
|
// Check selected rows
|
|
168
|
-
let newSelectedRows = { ...$selectedRows }
|
|
169
|
-
let selectedRowsNeedsUpdate = false
|
|
170
348
|
const selectedIds = Object.keys($selectedRows)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
349
|
+
if (selectedIds.length) {
|
|
350
|
+
let newSelectedRows = { ...$selectedRows }
|
|
351
|
+
let selectedRowsNeedsUpdate = false
|
|
352
|
+
for (let i = 0; i < selectedIds.length; i++) {
|
|
353
|
+
if (!hasRow(selectedIds[i])) {
|
|
354
|
+
delete newSelectedRows[selectedIds[i]]
|
|
355
|
+
selectedRowsNeedsUpdate = true
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (selectedRowsNeedsUpdate) {
|
|
359
|
+
selectedRows.set(newSelectedRows)
|
|
175
360
|
}
|
|
176
|
-
}
|
|
177
|
-
if (selectedRowsNeedsUpdate) {
|
|
178
|
-
selectedRows.set(newSelectedRows)
|
|
179
361
|
}
|
|
180
362
|
})
|
|
181
363
|
|
|
@@ -186,18 +368,29 @@ export const initialise = context => {
|
|
|
186
368
|
lastFocusedRowId = id
|
|
187
369
|
})
|
|
188
370
|
|
|
189
|
-
// Remember the last focused cell ID so that we can store the previous one
|
|
190
371
|
let lastFocusedCellId = null
|
|
191
372
|
focusedCellId.subscribe(id => {
|
|
373
|
+
// Remember the last focused cell ID so that we can store the previous one
|
|
192
374
|
previousFocusedCellId.set(lastFocusedCellId)
|
|
193
375
|
lastFocusedCellId = id
|
|
194
|
-
})
|
|
195
376
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (cell && get(hoveredRowId)) {
|
|
377
|
+
// Remove hovered row when a cell is selected
|
|
378
|
+
if (id && get(hoveredRowId)) {
|
|
199
379
|
hoveredRowId.set(null)
|
|
200
380
|
}
|
|
381
|
+
|
|
382
|
+
// Clear row selection when focusing a cell
|
|
383
|
+
if (id && get(selectedRowCount)) {
|
|
384
|
+
selectedRows.set({})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Clear cell selection when focusing a cell
|
|
388
|
+
if (id && get(selectedCellCount)) {
|
|
389
|
+
selectedCells.actions.clear()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Close the menu if it was open
|
|
393
|
+
menu.actions.close()
|
|
201
394
|
})
|
|
202
395
|
|
|
203
396
|
// Pull row height from table as long as we don't have a fixed height
|
|
@@ -215,4 +408,36 @@ export const initialise = context => {
|
|
|
215
408
|
rowHeight.set(get(definition)?.rowHeight || DefaultRowHeight)
|
|
216
409
|
}
|
|
217
410
|
})
|
|
411
|
+
|
|
412
|
+
// Clear focused cell when selecting rows
|
|
413
|
+
selectedRowCount.subscribe(count => {
|
|
414
|
+
if (count) {
|
|
415
|
+
if (get(focusedCellId)) {
|
|
416
|
+
focusedCellId.set(null)
|
|
417
|
+
}
|
|
418
|
+
if (get(selectedCellCount)) {
|
|
419
|
+
selectedCells.actions.clear()
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// Clear state when selecting cells
|
|
425
|
+
selectedCellCount.subscribe($selectedCellCount => {
|
|
426
|
+
if ($selectedCellCount) {
|
|
427
|
+
if (get(selectedRowCount)) {
|
|
428
|
+
selectedRows.set({})
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
// Ensure the source of cell selection is focused
|
|
434
|
+
cellSelection.subscribe(async ({ sourceCellId, targetCellId }) => {
|
|
435
|
+
if (
|
|
436
|
+
sourceCellId &&
|
|
437
|
+
sourceCellId !== targetCellId &&
|
|
438
|
+
get(focusedCellId) !== sourceCellId
|
|
439
|
+
) {
|
|
440
|
+
focusedCellId.set(sourceCellId)
|
|
441
|
+
}
|
|
442
|
+
})
|
|
218
443
|
}
|
|
@@ -25,7 +25,7 @@ export const deriveStores = context => {
|
|
|
25
25
|
|
|
26
26
|
// Generate a lookup map of cell ID to the user that has it selected, to make
|
|
27
27
|
// lookups inside cells extremely fast
|
|
28
|
-
const
|
|
28
|
+
const userCellMap = derived(
|
|
29
29
|
[users, focusedCellId],
|
|
30
30
|
([$users, $focusedCellId]) => {
|
|
31
31
|
let map = {}
|
|
@@ -40,7 +40,7 @@ export const deriveStores = context => {
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
return {
|
|
43
|
-
|
|
43
|
+
userCellMap,
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writable, get, derived } from "svelte/store"
|
|
2
|
-
import {
|
|
2
|
+
import { parseCellID } from "../lib/utils"
|
|
3
3
|
|
|
4
4
|
// Normally we would break out actions into the explicit "createActions"
|
|
5
5
|
// function, but for validation all these actions are pure so can go into
|
|
@@ -7,18 +7,38 @@ import { getCellID, parseCellID } from "../lib/utils"
|
|
|
7
7
|
export const createStores = () => {
|
|
8
8
|
const validation = writable({})
|
|
9
9
|
|
|
10
|
+
return {
|
|
11
|
+
validation,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const deriveStores = context => {
|
|
16
|
+
const { validation } = context
|
|
17
|
+
|
|
10
18
|
// Derive which rows have errors so that we can use that info later
|
|
11
|
-
const
|
|
19
|
+
const validationRowLookupMap = derived(validation, $validation => {
|
|
12
20
|
let map = {}
|
|
13
21
|
Object.entries($validation).forEach(([key, error]) => {
|
|
14
22
|
// Extract row ID from all errored cell IDs
|
|
15
23
|
if (error) {
|
|
16
|
-
|
|
24
|
+
const { rowId } = parseCellID(key)
|
|
25
|
+
if (!map[rowId]) {
|
|
26
|
+
map[rowId] = []
|
|
27
|
+
}
|
|
28
|
+
map[rowId].push(key)
|
|
17
29
|
}
|
|
18
30
|
})
|
|
19
31
|
return map
|
|
20
32
|
})
|
|
21
33
|
|
|
34
|
+
return {
|
|
35
|
+
validationRowLookupMap,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const createActions = context => {
|
|
40
|
+
const { validation, focusedCellId, validationRowLookupMap } = context
|
|
41
|
+
|
|
22
42
|
const setError = (cellId, error) => {
|
|
23
43
|
if (!cellId) {
|
|
24
44
|
return
|
|
@@ -30,7 +50,15 @@ export const createStores = () => {
|
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
const rowHasErrors = rowId => {
|
|
33
|
-
return get(
|
|
53
|
+
return get(validationRowLookupMap)[rowId]?.length > 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const focusFirstRowError = rowId => {
|
|
57
|
+
const errorCells = get(validationRowLookupMap)[rowId]
|
|
58
|
+
const cellId = errorCells?.[0]
|
|
59
|
+
if (cellId) {
|
|
60
|
+
focusedCellId.set(cellId)
|
|
61
|
+
}
|
|
34
62
|
}
|
|
35
63
|
|
|
36
64
|
return {
|
|
@@ -39,28 +67,27 @@ export const createStores = () => {
|
|
|
39
67
|
actions: {
|
|
40
68
|
setError,
|
|
41
69
|
rowHasErrors,
|
|
70
|
+
focusFirstRowError,
|
|
42
71
|
},
|
|
43
72
|
},
|
|
44
73
|
}
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
export const initialise = context => {
|
|
48
|
-
const { validation, previousFocusedRowId,
|
|
77
|
+
const { validation, previousFocusedRowId, validationRowLookupMap } = context
|
|
49
78
|
|
|
50
|
-
// Remove validation errors
|
|
79
|
+
// Remove validation errors when changing rows
|
|
51
80
|
previousFocusedRowId.subscribe(id => {
|
|
52
81
|
if (id) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
const errorCells = get(validationRowLookupMap)[id]
|
|
83
|
+
if (errorCells?.length) {
|
|
84
|
+
validation.update(state => {
|
|
85
|
+
for (let cellId of errorCells) {
|
|
86
|
+
delete state[cellId]
|
|
87
|
+
}
|
|
88
|
+
return state
|
|
58
89
|
})
|
|
59
|
-
|
|
60
|
-
state[getCellID(id, stickyColumn.name)] = null
|
|
61
|
-
}
|
|
62
|
-
return state
|
|
63
|
-
})
|
|
90
|
+
}
|
|
64
91
|
}
|
|
65
92
|
})
|
|
66
93
|
}
|
|
@@ -4,12 +4,14 @@ import { MinColumnWidth } from "../lib/constants"
|
|
|
4
4
|
export const deriveStores = context => {
|
|
5
5
|
const {
|
|
6
6
|
rowHeight,
|
|
7
|
-
|
|
7
|
+
scrollableColumns,
|
|
8
8
|
rows,
|
|
9
9
|
scrollTop,
|
|
10
10
|
scrollLeft,
|
|
11
11
|
width,
|
|
12
12
|
height,
|
|
13
|
+
rowChangeCache,
|
|
14
|
+
metadata,
|
|
13
15
|
} = context
|
|
14
16
|
|
|
15
17
|
// Derive visible rows
|
|
@@ -19,25 +21,31 @@ export const deriveStores = context => {
|
|
|
19
21
|
[scrollTop, rowHeight],
|
|
20
22
|
([$scrollTop, $rowHeight]) => {
|
|
21
23
|
return Math.floor($scrollTop / $rowHeight)
|
|
22
|
-
}
|
|
23
|
-
0
|
|
24
|
+
}
|
|
24
25
|
)
|
|
25
26
|
const visualRowCapacity = derived(
|
|
26
27
|
[height, rowHeight],
|
|
27
28
|
([$height, $rowHeight]) => {
|
|
28
29
|
return Math.ceil($height / $rowHeight) + 1
|
|
29
|
-
}
|
|
30
|
-
0
|
|
30
|
+
}
|
|
31
31
|
)
|
|
32
32
|
const renderedRows = derived(
|
|
33
|
-
[rows, scrolledRowCount, visualRowCapacity],
|
|
34
|
-
([
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
[rows, scrolledRowCount, visualRowCapacity, rowChangeCache, metadata],
|
|
34
|
+
([
|
|
35
|
+
$rows,
|
|
36
|
+
$scrolledRowCount,
|
|
37
|
+
$visualRowCapacity,
|
|
38
|
+
$rowChangeCache,
|
|
39
|
+
$metadata,
|
|
40
|
+
]) => {
|
|
41
|
+
return $rows
|
|
42
|
+
.slice($scrolledRowCount, $scrolledRowCount + $visualRowCapacity)
|
|
43
|
+
.map(row => ({
|
|
44
|
+
...row,
|
|
45
|
+
...$rowChangeCache[row._id],
|
|
46
|
+
__metadata: $metadata[row._id],
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
41
49
|
)
|
|
42
50
|
|
|
43
51
|
// Derive visible columns
|
|
@@ -46,33 +54,31 @@ export const deriveStores = context => {
|
|
|
46
54
|
return Math.round($scrollLeft / interval) * interval
|
|
47
55
|
})
|
|
48
56
|
const columnRenderMap = derived(
|
|
49
|
-
[
|
|
50
|
-
([$
|
|
51
|
-
if (!$
|
|
57
|
+
[scrollableColumns, scrollLeftRounded, width],
|
|
58
|
+
([$scrollableColumns, $scrollLeft, $width]) => {
|
|
59
|
+
if (!$scrollableColumns.length) {
|
|
52
60
|
return {}
|
|
53
61
|
}
|
|
54
62
|
let startColIdx = 0
|
|
55
|
-
let rightEdge = $
|
|
63
|
+
let rightEdge = $scrollableColumns[0].width
|
|
56
64
|
while (
|
|
57
65
|
rightEdge < $scrollLeft &&
|
|
58
|
-
startColIdx < $
|
|
66
|
+
startColIdx < $scrollableColumns.length - 1
|
|
59
67
|
) {
|
|
60
68
|
startColIdx++
|
|
61
|
-
rightEdge += $
|
|
69
|
+
rightEdge += $scrollableColumns[startColIdx].width
|
|
62
70
|
}
|
|
63
71
|
let endColIdx = startColIdx + 1
|
|
64
72
|
let leftEdge = rightEdge
|
|
65
73
|
while (
|
|
66
74
|
leftEdge < $width + $scrollLeft &&
|
|
67
|
-
endColIdx < $
|
|
75
|
+
endColIdx < $scrollableColumns.length
|
|
68
76
|
) {
|
|
69
|
-
leftEdge += $
|
|
77
|
+
leftEdge += $scrollableColumns[endColIdx].width
|
|
70
78
|
endColIdx++
|
|
71
79
|
}
|
|
72
|
-
|
|
73
|
-
// Only update the store if different
|
|
74
80
|
let next = {}
|
|
75
|
-
$
|
|
81
|
+
$scrollableColumns
|
|
76
82
|
.slice(Math.max(0, startColIdx), endColIdx)
|
|
77
83
|
.forEach(col => {
|
|
78
84
|
next[col.name] = true
|
package/src/components/index.js
CHANGED
|
@@ -8,3 +8,4 @@ export { default as Updating } from "./Updating.svelte"
|
|
|
8
8
|
export { Grid } from "./grid"
|
|
9
9
|
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
|
10
10
|
export { default as FilterBuilder } from "./FilterBuilder.svelte"
|
|
11
|
+
export { default as FilterUsers } from "./FilterUsers.svelte"
|