@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.
Files changed (51) hide show
  1. package/package.json +5 -5
  2. package/src/components/FilterBuilder.svelte +1 -5
  3. package/src/components/grid/cells/DataCell.svelte +63 -12
  4. package/src/components/grid/cells/GridCell.svelte +36 -16
  5. package/src/components/grid/cells/GutterCell.svelte +15 -8
  6. package/src/components/grid/cells/HeaderCell.svelte +3 -3
  7. package/src/components/grid/cells/RelationshipCell.svelte +16 -8
  8. package/src/components/grid/controls/BulkDeleteHandler.svelte +98 -13
  9. package/src/components/grid/controls/BulkDuplicationHandler.svelte +79 -0
  10. package/src/components/grid/controls/ClipboardHandler.svelte +67 -0
  11. package/src/components/grid/controls/ColumnsSettingButton.svelte +5 -10
  12. package/src/components/grid/controls/SizeButton.svelte +6 -13
  13. package/src/components/grid/controls/SortButton.svelte +8 -22
  14. package/src/components/grid/layout/ButtonColumn.svelte +12 -6
  15. package/src/components/grid/layout/Grid.svelte +11 -7
  16. package/src/components/grid/layout/GridBody.svelte +2 -2
  17. package/src/components/grid/layout/GridRow.svelte +12 -6
  18. package/src/components/grid/layout/GridScrollWrapper.svelte +9 -7
  19. package/src/components/grid/layout/HeaderRow.svelte +2 -2
  20. package/src/components/grid/layout/NewColumnButton.svelte +11 -5
  21. package/src/components/grid/layout/NewRow.svelte +16 -12
  22. package/src/components/grid/layout/StickyColumn.svelte +24 -14
  23. package/src/components/grid/lib/utils.js +4 -4
  24. package/src/components/grid/overlays/KeyboardManager.svelte +144 -95
  25. package/src/components/grid/overlays/MenuOverlay.svelte +114 -63
  26. package/src/components/grid/overlays/ReorderOverlay.svelte +14 -18
  27. package/src/components/grid/overlays/ResizeOverlay.svelte +8 -21
  28. package/src/components/grid/stores/clipboard.js +215 -18
  29. package/src/components/grid/stores/columns.js +78 -97
  30. package/src/components/grid/stores/conditions.js +157 -0
  31. package/src/components/grid/stores/config.js +2 -2
  32. package/src/components/grid/stores/datasource.js +4 -14
  33. package/src/components/grid/stores/datasources/nonPlus.js +2 -4
  34. package/src/components/grid/stores/datasources/table.js +6 -5
  35. package/src/components/grid/stores/datasources/viewV2.js +7 -9
  36. package/src/components/grid/stores/index.js +5 -3
  37. package/src/components/grid/stores/menu.js +40 -6
  38. package/src/components/grid/stores/pagination.js +9 -3
  39. package/src/components/grid/stores/reorder.js +67 -42
  40. package/src/components/grid/stores/resize.js +1 -1
  41. package/src/components/grid/stores/rows.js +220 -85
  42. package/src/components/grid/stores/scroll.js +31 -28
  43. package/src/components/grid/stores/ui.js +295 -70
  44. package/src/components/grid/stores/users.js +2 -2
  45. package/src/components/grid/stores/validation.js +43 -16
  46. package/src/components/grid/stores/viewport.js +30 -24
  47. package/src/components/index.js +1 -0
  48. package/src/constants.js +3 -0
  49. package/src/themes/midnight.css +18 -17
  50. package/src/themes/nord.css +2 -1
  51. 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
- // Derive the current focused row ID
27
- const focusedRowId = derived(
28
- focusedCellId,
29
- $focusedCellId => {
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
- ...selectedRows,
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 { focusedCellId, rows, rowLookupMap, rowHeight, stickyColumn, width } =
72
- context
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
- [focusedCellId, rowLookupMap, rows],
77
- ([$focusedCellId, $rowLookupMap, $rows]) => {
78
- const rowId = parseCellID($focusedCellId)?.id
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
- // All normal rows
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([stickyColumn, width], ([$stickyColumn, $width]) => {
104
- return ($stickyColumn?.width || 0) + $width + GutterWidth < 800
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 { focusedCellId, hoveredRowId } = context
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
- rows,
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
- rows.subscribe(async () => {
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 $focusedCellId = get(focusedCellId)
332
+ const $focusedRowId = get(focusedRowId)
152
333
  const $selectedRows = get(selectedRows)
153
334
  const $hoveredRowId = get(hoveredRowId)
154
- const hasRow = rows.actions.hasRow
335
+ const hasRow = id => $rowLookupMap[id] != null
155
336
 
156
- // Check selected cell
157
- const selectedRowId = parseCellID($focusedCellId)?.id
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
- for (let i = 0; i < selectedIds.length; i++) {
172
- if (!hasRow(selectedIds[i])) {
173
- delete newSelectedRows[selectedIds[i]]
174
- selectedRowsNeedsUpdate = true
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
- // Remove hovered row when a cell is selected
197
- focusedCellId.subscribe(cell => {
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 selectedCellMap = derived(
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
- selectedCellMap,
43
+ userCellMap,
44
44
  }
45
45
  }
46
46
 
@@ -1,5 +1,5 @@
1
1
  import { writable, get, derived } from "svelte/store"
2
- import { getCellID, parseCellID } from "../lib/utils"
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 rowErrorMap = derived(validation, $validation => {
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
- map[parseCellID(key).id] = true
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(rowErrorMap)[rowId]
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, columns, stickyColumn } = context
77
+ const { validation, previousFocusedRowId, validationRowLookupMap } = context
49
78
 
50
- // Remove validation errors from previous focused row
79
+ // Remove validation errors when changing rows
51
80
  previousFocusedRowId.subscribe(id => {
52
81
  if (id) {
53
- const $columns = get(columns)
54
- const $stickyColumn = get(stickyColumn)
55
- validation.update(state => {
56
- $columns.forEach(column => {
57
- state[getCellID(id, column.name)] = null
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
- if ($stickyColumn) {
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
- visibleColumns,
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
- ([$rows, $scrolledRowCount, $visualRowCapacity]) => {
35
- return $rows.slice(
36
- $scrolledRowCount,
37
- $scrolledRowCount + $visualRowCapacity
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
- [visibleColumns, scrollLeftRounded, width],
50
- ([$visibleColumns, $scrollLeft, $width]) => {
51
- if (!$visibleColumns.length) {
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 = $visibleColumns[0].width
63
+ let rightEdge = $scrollableColumns[0].width
56
64
  while (
57
65
  rightEdge < $scrollLeft &&
58
- startColIdx < $visibleColumns.length - 1
66
+ startColIdx < $scrollableColumns.length - 1
59
67
  ) {
60
68
  startColIdx++
61
- rightEdge += $visibleColumns[startColIdx].width
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 < $visibleColumns.length
75
+ endColIdx < $scrollableColumns.length
68
76
  ) {
69
- leftEdge += $visibleColumns[endColIdx].width
77
+ leftEdge += $scrollableColumns[endColIdx].width
70
78
  endColIdx++
71
79
  }
72
-
73
- // Only update the store if different
74
80
  let next = {}
75
- $visibleColumns
81
+ $scrollableColumns
76
82
  .slice(Math.max(0, startColIdx), endColIdx)
77
83
  .forEach(col => {
78
84
  next[col.name] = true
@@ -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"