@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
@@ -4,6 +4,8 @@ import { NewRowID, RowPageSize } from "../lib/constants"
4
4
  import { getCellID, parseCellID } from "../lib/utils"
5
5
  import { tick } from "svelte"
6
6
  import { Helpers } from "@budibase/bbui"
7
+ import { sleep } from "../../../utils/utils"
8
+ import { FieldType } from "@budibase/types"
7
9
 
8
10
  export const createStores = () => {
9
11
  const rows = writable([])
@@ -16,15 +18,6 @@ export const createStores = () => {
16
18
  const error = writable(null)
17
19
  const fetch = writable(null)
18
20
 
19
- // Generate a lookup map to quick find a row by ID
20
- const rowLookupMap = derived(rows, $rows => {
21
- let map = {}
22
- for (let i = 0; i < $rows.length; i++) {
23
- map[$rows[i]._id] = i
24
- }
25
- return map
26
- })
27
-
28
21
  // Mark loaded as true if we've ever stopped loading
29
22
  let hasStartedLoading = false
30
23
  loading.subscribe($loading => {
@@ -35,25 +28,9 @@ export const createStores = () => {
35
28
  }
36
29
  })
37
30
 
38
- // Enrich rows with an index property and any pending changes
39
- const enrichedRows = derived(
40
- [rows, rowChangeCache],
41
- ([$rows, $rowChangeCache]) => {
42
- return $rows.map((row, idx) => ({
43
- ...row,
44
- ...$rowChangeCache[row._id],
45
- __idx: idx,
46
- }))
47
- }
48
- )
49
-
50
31
  return {
51
- rows: {
52
- ...rows,
53
- subscribe: enrichedRows.subscribe,
54
- },
32
+ rows,
55
33
  fetch,
56
- rowLookupMap,
57
34
  loaded,
58
35
  refreshing,
59
36
  loading,
@@ -64,6 +41,35 @@ export const createStores = () => {
64
41
  }
65
42
  }
66
43
 
44
+ export const deriveStores = context => {
45
+ const { rows } = context
46
+
47
+ // Enrich rows with an index property and any pending changes
48
+ const enrichedRows = derived(rows, $rows => {
49
+ return $rows.map((row, idx) => ({
50
+ ...row,
51
+ __idx: idx,
52
+ }))
53
+ })
54
+
55
+ // Generate a lookup map to quick find a row by ID
56
+ const rowLookupMap = derived(enrichedRows, $enrichedRows => {
57
+ let map = {}
58
+ for (let i = 0; i < $enrichedRows.length; i++) {
59
+ map[$enrichedRows[i]._id] = $enrichedRows[i]
60
+ }
61
+ return map
62
+ })
63
+
64
+ return {
65
+ rows: {
66
+ ...rows,
67
+ subscribe: enrichedRows.subscribe,
68
+ },
69
+ rowLookupMap,
70
+ }
71
+ }
72
+
67
73
  export const createActions = context => {
68
74
  const {
69
75
  rows,
@@ -86,6 +92,7 @@ export const createActions = context => {
86
92
  fetch,
87
93
  hasBudibaseIdentifiers,
88
94
  refreshing,
95
+ columnLookupMap,
89
96
  } = context
90
97
  const instanceLoaded = writable(false)
91
98
 
@@ -188,12 +195,6 @@ export const createActions = context => {
188
195
  fetch.set(newFetch)
189
196
  })
190
197
 
191
- // Gets a row by ID
192
- const getRow = id => {
193
- const index = get(rowLookupMap)[id]
194
- return index >= 0 ? get(rows)[index] : null
195
- }
196
-
197
198
  // Handles validation errors from the rows API and updates local validation
198
199
  // state, storing error messages against relevant cells
199
200
  const handleValidationError = (rowId, error) => {
@@ -263,22 +264,15 @@ export const createActions = context => {
263
264
  for (let column of missingColumns) {
264
265
  get(notifications).error(`${column} is required but is missing`)
265
266
  }
266
-
267
- // Focus the first cell with an error
268
- if (erroredColumns.length) {
269
- focusedCellId.set(getCellID(rowId, erroredColumns[0]))
270
- }
271
267
  } else {
272
268
  get(notifications).error(errorString || "An unknown error occurred")
273
269
  }
274
270
  }
275
271
 
276
272
  // Adds a new row
277
- const addRow = async (row, idx, bubble = false) => {
273
+ const addRow = async ({ row, idx, bubble = false, notify = true }) => {
278
274
  try {
279
- // Create row. Spread row so we can mutate and enrich safely.
280
- let newRow = { ...row }
281
- newRow = await datasource.actions.addRow(newRow)
275
+ const newRow = await datasource.actions.addRow(row)
282
276
 
283
277
  // Update state
284
278
  if (idx != null) {
@@ -291,38 +285,94 @@ export const createActions = context => {
291
285
  handleNewRows([newRow])
292
286
  }
293
287
 
294
- // Refresh row to ensure data is in the correct format
295
- get(notifications).success("Row created successfully")
288
+ if (notify) {
289
+ get(notifications).success("Row created successfully")
290
+ }
296
291
  return newRow
297
292
  } catch (error) {
298
293
  if (bubble) {
299
294
  throw error
300
295
  } else {
301
296
  handleValidationError(NewRowID, error)
297
+ validation.actions.focusFirstRowError(NewRowID)
302
298
  }
303
299
  }
304
300
  }
305
301
 
306
302
  // Duplicates a row, inserting the duplicate row after the existing one
307
303
  const duplicateRow = async row => {
308
- let clone = { ...row }
304
+ let clone = cleanRow(row)
309
305
  delete clone._id
310
306
  delete clone._rev
311
- delete clone.__idx
312
307
  try {
313
- return await addRow(clone, row.__idx + 1, true)
308
+ const duped = await addRow({
309
+ row: clone,
310
+ idx: row.__idx + 1,
311
+ bubble: true,
312
+ notify: false,
313
+ })
314
+ get(notifications).success("Duplicated 1 row")
315
+ return duped
314
316
  } catch (error) {
315
317
  handleValidationError(row._id, error)
318
+ validation.actions.focusFirstRowError(row._id)
316
319
  }
317
320
  }
318
321
 
322
+ // Duplicates multiple rows, inserting them after the last source row
323
+ const bulkDuplicate = async (rowsToDupe, progressCallback) => {
324
+ // Find index of last row
325
+ const $rowLookupMap = get(rowLookupMap)
326
+ const indices = rowsToDupe.map(row => $rowLookupMap[row._id]?.__idx)
327
+ const index = Math.max(...indices)
328
+ const count = rowsToDupe.length
329
+
330
+ // Clone and clean rows
331
+ const clones = rowsToDupe.map(row => {
332
+ let clone = cleanRow(row)
333
+ delete clone._id
334
+ delete clone._rev
335
+ return clone
336
+ })
337
+
338
+ // Create rows
339
+ let saved = []
340
+ let failed = 0
341
+ for (let i = 0; i < count; i++) {
342
+ try {
343
+ saved.push(await datasource.actions.addRow(clones[i]))
344
+ rowCacheMap[saved._id] = true
345
+ await sleep(50) // Small sleep to ensure we avoid rate limiting
346
+ } catch (error) {
347
+ failed++
348
+ console.error("Duplicating row failed", error)
349
+ }
350
+ progressCallback?.((i + 1) / count)
351
+ }
352
+
353
+ // Add to state
354
+ if (saved.length) {
355
+ rows.update(state => {
356
+ return state.toSpliced(index + 1, 0, ...saved)
357
+ })
358
+ }
359
+
360
+ // Notify user
361
+ if (failed) {
362
+ get(notifications).error(`Failed to duplicate ${failed} of ${count} rows`)
363
+ } else if (saved.length) {
364
+ get(notifications).success(`Duplicated ${saved.length} rows`)
365
+ }
366
+ return saved
367
+ }
368
+
319
369
  // Replaces a row in state with the newly defined row, handling updates,
320
370
  // addition and deletion
321
371
  const replaceRow = (id, row) => {
322
372
  // Get index of row to check if it exists
323
373
  const $rows = get(rows)
324
374
  const $rowLookupMap = get(rowLookupMap)
325
- const index = $rowLookupMap[id]
375
+ const index = $rowLookupMap[id]?.__idx
326
376
 
327
377
  // Process as either an update, addition or deletion
328
378
  if (row) {
@@ -371,10 +421,21 @@ export const createActions = context => {
371
421
  // Patches a row with some changes in local state, and returns whether a
372
422
  // valid pending change was made or not
373
423
  const stashRowChanges = (rowId, changes) => {
374
- const $rows = get(rows)
375
424
  const $rowLookupMap = get(rowLookupMap)
376
- const index = $rowLookupMap[rowId]
377
- const row = $rows[index]
425
+ const $columnLookupMap = get(columnLookupMap)
426
+ const row = $rowLookupMap[rowId]
427
+
428
+ // Coerce some values into the correct types
429
+ for (let column of Object.keys(changes || {})) {
430
+ const type = $columnLookupMap[column]?.schema?.type
431
+
432
+ // Stringify objects
433
+ if (type === FieldType.STRING || type == FieldType.LONGFORM) {
434
+ if (changes[column] != null && typeof changes[column] !== "string") {
435
+ changes[column] = JSON.stringify(changes[column])
436
+ }
437
+ }
438
+ }
378
439
 
379
440
  // Check this is a valid change
380
441
  if (!row || !changesAreValid(row, changes)) {
@@ -392,15 +453,20 @@ export const createActions = context => {
392
453
  return true
393
454
  }
394
455
 
395
- // Saves any pending changes to a row
396
- const applyRowChanges = async rowId => {
397
- const $rows = get(rows)
456
+ // Saves any pending changes to a row, as well as any additional changes
457
+ // specified
458
+ const applyRowChanges = async ({
459
+ rowId,
460
+ changes = null,
461
+ updateState = true,
462
+ handleErrors = true,
463
+ }) => {
398
464
  const $rowLookupMap = get(rowLookupMap)
399
- const index = $rowLookupMap[rowId]
400
- const row = $rows[index]
465
+ const row = $rowLookupMap[rowId]
401
466
  if (row == null) {
402
467
  return
403
468
  }
469
+ let savedRow
404
470
 
405
471
  // Save change
406
472
  try {
@@ -411,33 +477,38 @@ export const createActions = context => {
411
477
  }))
412
478
 
413
479
  // Update row
414
- const changes = get(rowChangeCache)[rowId]
415
- const newRow = { ...cleanRow(row), ...changes }
416
- const saved = await datasource.actions.updateRow(newRow)
480
+ const stashedChanges = get(rowChangeCache)[rowId]
481
+ const newRow = { ...cleanRow(row), ...stashedChanges, ...changes }
482
+ savedRow = await datasource.actions.updateRow(newRow)
417
483
 
418
484
  // Update row state after a successful change
419
- if (saved?._id) {
420
- rows.update(state => {
421
- state[index] = saved
422
- return state.slice()
423
- })
424
- } else if (saved?.id) {
485
+ if (savedRow?._id) {
486
+ if (updateState) {
487
+ rows.update(state => {
488
+ state[row.__idx] = savedRow
489
+ return state.slice()
490
+ })
491
+ }
492
+ } else if (savedRow?.id) {
425
493
  // Handle users table edge case
426
- await refreshRow(saved.id)
494
+ await refreshRow(savedRow.id)
427
495
  }
428
496
 
429
497
  // Wipe row change cache for any values which have been saved
430
498
  const liveChanges = get(rowChangeCache)[rowId]
431
499
  rowChangeCache.update(state => {
432
- Object.keys(changes || {}).forEach(key => {
433
- if (changes[key] === liveChanges?.[key]) {
500
+ Object.keys(stashedChanges || {}).forEach(key => {
501
+ if (stashedChanges[key] === liveChanges?.[key]) {
434
502
  delete state[rowId][key]
435
503
  }
436
504
  })
437
505
  return state
438
506
  })
439
507
  } catch (error) {
440
- handleValidationError(rowId, error)
508
+ if (handleErrors) {
509
+ handleValidationError(rowId, error)
510
+ validation.actions.focusFirstRowError(rowId)
511
+ }
441
512
  }
442
513
 
443
514
  // Decrement change count for this row
@@ -445,13 +516,82 @@ export const createActions = context => {
445
516
  ...state,
446
517
  [rowId]: (state[rowId] || 1) - 1,
447
518
  }))
519
+ return savedRow
448
520
  }
449
521
 
450
522
  // Updates a value of a row
451
523
  const updateValue = async ({ rowId, column, value, apply = true }) => {
452
524
  const success = stashRowChanges(rowId, { [column]: value })
453
525
  if (success && apply) {
454
- await applyRowChanges(rowId)
526
+ await applyRowChanges({ rowId })
527
+ }
528
+ }
529
+
530
+ const bulkUpdate = async (changeMap, progressCallback) => {
531
+ const rowIds = Object.keys(changeMap || {})
532
+ const count = rowIds.length
533
+ if (!count) {
534
+ return
535
+ }
536
+
537
+ // Update rows
538
+ const $columnLookupMap = get(columnLookupMap)
539
+ let updated = []
540
+ let failed = 0
541
+ for (let i = 0; i < count; i++) {
542
+ const rowId = rowIds[i]
543
+ let changes = changeMap[rowId] || {}
544
+
545
+ // Strip any readonly fields from the change set
546
+ for (let field of Object.keys(changes)) {
547
+ const column = $columnLookupMap[field]
548
+ if (columns.actions.isReadonly(column)) {
549
+ delete changes[field]
550
+ }
551
+ }
552
+ if (!Object.keys(changes).length) {
553
+ progressCallback?.((i + 1) / count)
554
+ continue
555
+ }
556
+ try {
557
+ const updatedRow = await applyRowChanges({
558
+ rowId,
559
+ changes: changeMap[rowId],
560
+ updateState: false,
561
+ handleErrors: false,
562
+ })
563
+ if (updatedRow) {
564
+ updated.push(updatedRow)
565
+ } else {
566
+ failed++
567
+ }
568
+ await sleep(50) // Small sleep to ensure we avoid rate limiting
569
+ } catch (error) {
570
+ failed++
571
+ console.error("Failed to update row", error)
572
+ }
573
+ progressCallback?.((i + 1) / count)
574
+ }
575
+
576
+ // Update state
577
+ if (updated.length) {
578
+ const $rowLookupMap = get(rowLookupMap)
579
+ rows.update(state => {
580
+ for (let row of updated) {
581
+ const index = $rowLookupMap[row._id].__idx
582
+ state[index] = row
583
+ }
584
+ return state.slice()
585
+ })
586
+ }
587
+
588
+ // Notify user
589
+ if (failed) {
590
+ const unit = `row${count === 1 ? "" : "s"}`
591
+ get(notifications).error(`Failed to update ${failed} of ${count} ${unit}`)
592
+ } else if (updated.length) {
593
+ const unit = `row${updated.length === 1 ? "" : "s"}`
594
+ get(notifications).success(`Updated ${updated.length} ${unit}`)
455
595
  }
456
596
  }
457
597
 
@@ -516,19 +656,12 @@ export const createActions = context => {
516
656
  get(fetch)?.nextPage()
517
657
  }
518
658
 
519
- // Checks if we have a row with a certain ID
520
- const hasRow = id => {
521
- if (id === NewRowID) {
522
- return true
523
- }
524
- return get(rowLookupMap)[id] != null
525
- }
526
-
527
659
  // Cleans a row by removing any internal grid metadata from it.
528
660
  // Call this before passing a row to any sort of external flow.
529
661
  const cleanRow = row => {
530
662
  let clone = { ...row }
531
663
  delete clone.__idx
664
+ delete clone.__metadata
532
665
  if (!get(hasBudibaseIdentifiers)) {
533
666
  delete clone._id
534
667
  }
@@ -541,16 +674,16 @@ export const createActions = context => {
541
674
  actions: {
542
675
  addRow,
543
676
  duplicateRow,
544
- getRow,
677
+ bulkDuplicate,
545
678
  updateValue,
546
679
  applyRowChanges,
547
680
  deleteRows,
548
- hasRow,
549
681
  loadNextPage,
550
682
  refreshRow,
551
683
  replaceRow,
552
684
  refreshData,
553
685
  cleanRow,
686
+ bulkUpdate,
554
687
  },
555
688
  },
556
689
  }
@@ -569,10 +702,12 @@ export const initialise = context => {
569
702
  // Wipe the row change cache when changing row
570
703
  previousFocusedRowId.subscribe(id => {
571
704
  if (id && !get(inProgressChanges)[id]) {
572
- rowChangeCache.update(state => {
573
- delete state[id]
574
- return state
575
- })
705
+ if (Object.keys(get(rowChangeCache)[id] || {}).length) {
706
+ rowChangeCache.update(state => {
707
+ delete state[id]
708
+ return state
709
+ })
710
+ }
576
711
  }
577
712
  })
578
713
 
@@ -581,12 +716,12 @@ export const initialise = context => {
581
716
  if (!id) {
582
717
  return
583
718
  }
584
- const { id: rowId, field } = parseCellID(id)
719
+ const { rowId, field } = parseCellID(id)
585
720
  const hasChanges = field in (get(rowChangeCache)[rowId] || {})
586
721
  const hasErrors = validation.actions.rowHasErrors(rowId)
587
722
  const isSavingChanges = get(inProgressChanges)[rowId]
588
723
  if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
589
- await rows.actions.applyRowChanges(rowId)
724
+ await rows.actions.applyRowChanges({ rowId })
590
725
  }
591
726
  })
592
727
  }
@@ -16,8 +16,8 @@ export const createStores = () => {
16
16
  })
17
17
 
18
18
  // Derive height and width as primitives to avoid wasted computation
19
- const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
20
- const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
19
+ const scrollTop = derived(scroll, $scroll => Math.round($scroll.top))
20
+ const scrollLeft = derived(scroll, $scroll => Math.round($scroll.left))
21
21
 
22
22
  return {
23
23
  scroll,
@@ -30,7 +30,7 @@ export const deriveStores = context => {
30
30
  const {
31
31
  rows,
32
32
  visibleColumns,
33
- stickyColumn,
33
+ displayColumn,
34
34
  rowHeight,
35
35
  width,
36
36
  height,
@@ -38,31 +38,32 @@ export const deriveStores = context => {
38
38
  } = context
39
39
 
40
40
  // Memoize store primitives
41
- const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
41
+ const stickyWidth = derived(displayColumn, $displayColumn => {
42
+ return ($displayColumn?.width || 0) + GutterWidth
43
+ })
42
44
 
43
45
  // Derive horizontal limits
44
46
  const contentWidth = derived(
45
- [visibleColumns, stickyColumnWidth, buttonColumnWidth],
46
- ([$visibleColumns, $stickyColumnWidth, $buttonColumnWidth]) => {
47
- let width = GutterWidth + $buttonColumnWidth + $stickyColumnWidth
47
+ [visibleColumns, buttonColumnWidth],
48
+ ([$visibleColumns, $buttonColumnWidth]) => {
49
+ let width = GutterWidth + Math.max($buttonColumnWidth, HPadding)
48
50
  $visibleColumns.forEach(col => {
49
51
  width += col.width
50
52
  })
51
- return width + HPadding
52
- },
53
- 0
53
+ return width
54
+ }
54
55
  )
55
56
  const screenWidth = derived(
56
- [width, stickyColumnWidth],
57
- ([$width, $stickyColumnWidth]) => $width + GutterWidth + $stickyColumnWidth,
58
- 0
57
+ [width, stickyWidth],
58
+ ([$width, $stickyWidth]) => {
59
+ return $width + $stickyWidth
60
+ }
59
61
  )
60
62
  const maxScrollLeft = derived(
61
63
  [contentWidth, screenWidth],
62
64
  ([$contentWidth, $screenWidth]) => {
63
- return Math.max($contentWidth - $screenWidth, 0)
64
- },
65
- 0
65
+ return Math.round(Math.max($contentWidth - $screenWidth, 0))
66
+ }
66
67
  )
67
68
  const showHScrollbar = derived(
68
69
  [contentWidth, screenWidth],
@@ -80,13 +81,12 @@ export const deriveStores = context => {
80
81
  height += ScrollBarSize * 2
81
82
  }
82
83
  return height
83
- },
84
- 0
84
+ }
85
85
  )
86
86
  const maxScrollTop = derived(
87
87
  [height, contentHeight],
88
- ([$height, $contentHeight]) => Math.max($contentHeight - $height, 0),
89
- 0
88
+ ([$height, $contentHeight]) =>
89
+ Math.round(Math.max($contentHeight - $height, 0))
90
90
  )
91
91
  const showVScrollbar = derived(
92
92
  [contentHeight, height],
@@ -96,6 +96,7 @@ export const deriveStores = context => {
96
96
  )
97
97
 
98
98
  return {
99
+ stickyWidth,
99
100
  contentHeight,
100
101
  contentWidth,
101
102
  screenWidth,
@@ -113,12 +114,13 @@ export const initialise = context => {
113
114
  scroll,
114
115
  bounds,
115
116
  rowHeight,
116
- visibleColumns,
117
+ stickyWidth,
117
118
  scrollTop,
118
119
  maxScrollTop,
119
120
  scrollLeft,
120
121
  maxScrollLeft,
121
122
  buttonColumnWidth,
123
+ columnLookupMap,
122
124
  } = context
123
125
 
124
126
  // Ensure scroll state never goes invalid, which can happen when changing
@@ -186,15 +188,16 @@ export const initialise = context => {
186
188
 
187
189
  // Ensure horizontal position is viewable
188
190
  // Check horizontal position of columns next
189
- const $visibleColumns = get(visibleColumns)
190
- const { field: columnName } = parseCellID($focusedCellId)
191
- const column = $visibleColumns.find(col => col.name === columnName)
192
- if (!column) {
191
+ const { field } = parseCellID($focusedCellId)
192
+ const column = get(columnLookupMap)[field]
193
+ if (!column || column.primaryDisplay) {
193
194
  return
194
195
  }
195
196
 
196
197
  // Ensure column is not cutoff on left edge
197
- let delta = $scroll.left - column.left + FocusedCellMinOffset
198
+ const $stickyWidth = get(stickyWidth)
199
+ let delta =
200
+ $scroll.left - column.__left + FocusedCellMinOffset + $stickyWidth
198
201
  if (delta > 0) {
199
202
  scroll.update(state => ({
200
203
  ...state,
@@ -205,10 +208,10 @@ export const initialise = context => {
205
208
  // Ensure column is not cutoff on right edge
206
209
  else {
207
210
  const $buttonColumnWidth = get(buttonColumnWidth)
208
- const rightEdge = column.left + column.width
211
+ const rightEdge = column.__left + column.width
209
212
  const rightBound =
210
213
  $bounds.width + $scroll.left - FocusedCellMinOffset - $buttonColumnWidth
211
- delta = rightEdge - rightBound
214
+ delta = rightEdge - rightBound - $stickyWidth
212
215
  if (delta > 0) {
213
216
  scroll.update(state => ({
214
217
  ...state,