@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
@@ -0,0 +1,157 @@
1
+ import { writable, get } from "svelte/store"
2
+ import { derivedMemo, QueryUtils } from "../../../utils"
3
+ import { FieldType, EmptyFilterOption } from "@budibase/types"
4
+
5
+ export const createStores = () => {
6
+ const metadata = writable({})
7
+ return {
8
+ metadata,
9
+ }
10
+ }
11
+
12
+ export const deriveStores = context => {
13
+ const { columns } = context
14
+
15
+ // Derive and memoize the cell conditions present in our columns so that we
16
+ // only recompute condition metadata when absolutely necessary
17
+ const conditions = derivedMemo(columns, $columns => {
18
+ let newConditions = []
19
+ for (let column of $columns) {
20
+ for (let condition of column.conditions || []) {
21
+ newConditions.push({
22
+ ...condition,
23
+ column: column.name,
24
+ type: column.schema.type,
25
+ })
26
+ }
27
+ }
28
+ return newConditions
29
+ })
30
+
31
+ return {
32
+ conditions,
33
+ }
34
+ }
35
+
36
+ export const initialise = context => {
37
+ const { metadata, conditions, rows } = context
38
+
39
+ // Recompute all metadata if conditions change
40
+ conditions.subscribe($conditions => {
41
+ let newMetadata = {}
42
+ if ($conditions?.length) {
43
+ for (let row of get(rows)) {
44
+ newMetadata[row._id] = evaluateConditions(row, $conditions)
45
+ }
46
+ }
47
+ metadata.set(newMetadata)
48
+ })
49
+
50
+ // Recompute metadata for specific rows when they change
51
+ rows.subscribe($rows => {
52
+ const $conditions = get(conditions)
53
+ if (!$conditions?.length) {
54
+ return
55
+ }
56
+ const $metadata = get(metadata)
57
+ let metadataUpdates = {}
58
+ for (let row of $rows) {
59
+ if (!row._rev || $metadata[row._id]?.version !== row._rev) {
60
+ metadataUpdates[row._id] = evaluateConditions(row, $conditions)
61
+ }
62
+ }
63
+ if (Object.keys(metadataUpdates).length) {
64
+ metadata.update(state => ({
65
+ ...state,
66
+ ...metadataUpdates,
67
+ }))
68
+ }
69
+ })
70
+ }
71
+
72
+ const TypeCoercionMap = {
73
+ [FieldType.NUMBER]: parseFloat,
74
+ [FieldType.DATETIME]: val => {
75
+ if (val) {
76
+ return new Date(val).toISOString()
77
+ }
78
+ return null
79
+ },
80
+ [FieldType.BOOLEAN]: val => {
81
+ if (`${val}`.toLowerCase().trim() === "true") {
82
+ return true
83
+ }
84
+ if (`${val}`.toLowerCase().trim() === "false") {
85
+ return false
86
+ }
87
+ return null
88
+ },
89
+ }
90
+
91
+ // Evaluates an array of cell conditions against a certain row and returns the
92
+ // resultant metadata
93
+ const evaluateConditions = (row, conditions) => {
94
+ let metadata = {
95
+ version: row._rev,
96
+ row: {},
97
+ cell: {},
98
+ }
99
+ for (let condition of conditions) {
100
+ try {
101
+ let {
102
+ column,
103
+ type,
104
+ referenceValue,
105
+ operator,
106
+ metadataKey,
107
+ metadataValue,
108
+ target,
109
+ } = condition
110
+ let value = row[column]
111
+
112
+ // Coerce values into correct types for primitives
113
+ let coercedType = type
114
+ if (type === FieldType.FORMULA) {
115
+ // For formulas we want to ensure that the reference type matches the
116
+ // real type
117
+ if (value === true || value === false) {
118
+ coercedType = FieldType.BOOLEAN
119
+ } else if (typeof value === "number") {
120
+ coercedType = FieldType.NUMBER
121
+ }
122
+ }
123
+ const coerce = TypeCoercionMap[coercedType]
124
+ if (coerce) {
125
+ value = coerce(value)
126
+ referenceValue = coerce(referenceValue)
127
+ }
128
+
129
+ // Build lucene compatible condition expression
130
+ const luceneFilter = {
131
+ operator,
132
+ type,
133
+ field: "value",
134
+ value: referenceValue,
135
+ }
136
+ let query = QueryUtils.buildQuery([luceneFilter])
137
+ query.onEmptyFilter = EmptyFilterOption.RETURN_NONE
138
+ const result = QueryUtils.runQuery([{ value }], query)
139
+ if (result.length > 0) {
140
+ if (target === "row") {
141
+ metadata.row = {
142
+ ...metadata.row,
143
+ [metadataKey]: metadataValue,
144
+ }
145
+ } else {
146
+ metadata.cell[column] = {
147
+ ...metadata.cell[column],
148
+ [metadataKey]: metadataValue,
149
+ }
150
+ }
151
+ }
152
+ } catch {
153
+ // Swallow
154
+ }
155
+ }
156
+ return metadata
157
+ }
@@ -12,9 +12,9 @@ export const createStores = context => {
12
12
  const initialFilter = getProp("initialFilter")
13
13
  const fixedRowHeight = getProp("fixedRowHeight")
14
14
  const schemaOverrides = getProp("schemaOverrides")
15
- const columnWhitelist = getProp("columnWhitelist")
16
15
  const notifySuccess = getProp("notifySuccess")
17
16
  const notifyError = getProp("notifyError")
17
+ const rowConditions = getProp("rowConditions")
18
18
 
19
19
  return {
20
20
  datasource,
@@ -23,9 +23,9 @@ export const createStores = context => {
23
23
  initialFilter,
24
24
  fixedRowHeight,
25
25
  schemaOverrides,
26
- columnWhitelist,
27
26
  notifySuccess,
28
27
  notifyError,
28
+ rowConditions,
29
29
  }
30
30
  }
31
31
 
@@ -13,14 +13,8 @@ export const createStores = () => {
13
13
  }
14
14
 
15
15
  export const deriveStores = context => {
16
- const {
17
- API,
18
- definition,
19
- schemaOverrides,
20
- columnWhitelist,
21
- datasource,
22
- schemaMutations,
23
- } = context
16
+ const { API, definition, schemaOverrides, datasource, schemaMutations } =
17
+ context
24
18
 
25
19
  const schema = derived(definition, $definition => {
26
20
  let schema = getDatasourceSchema({
@@ -46,17 +40,13 @@ export const deriveStores = context => {
46
40
  // Derives the total enriched schema, made up of the saved schema and any
47
41
  // prop and user overrides
48
42
  const enrichedSchema = derived(
49
- [schema, schemaOverrides, schemaMutations, columnWhitelist],
50
- ([$schema, $schemaOverrides, $schemaMutations, $columnWhitelist]) => {
43
+ [schema, schemaOverrides, schemaMutations],
44
+ ([$schema, $schemaOverrides, $schemaMutations]) => {
51
45
  if (!$schema) {
52
46
  return null
53
47
  }
54
48
  let enrichedSchema = {}
55
49
  Object.keys($schema).forEach(field => {
56
- // Apply whitelist if provided
57
- if ($columnWhitelist?.length && !$columnWhitelist.includes(field)) {
58
- return
59
- }
60
50
  enrichedSchema[field] = {
61
51
  ...$schema[field],
62
52
  ...$schemaOverrides?.[field],
@@ -1,7 +1,7 @@
1
1
  import { get } from "svelte/store"
2
2
 
3
3
  export const createActions = context => {
4
- const { columns, stickyColumn, table, viewV2 } = context
4
+ const { columns, table, viewV2 } = context
5
5
 
6
6
  const saveDefinition = async () => {
7
7
  throw "This datasource does not support updating the definition"
@@ -30,9 +30,7 @@ export const createActions = context => {
30
30
  }
31
31
 
32
32
  const canUseColumn = name => {
33
- const $columns = get(columns)
34
- const $sticky = get(stickyColumn)
35
- return $columns.some(col => col.name === name) || $sticky?.name === name
33
+ return get(columns).some(col => col.name === name)
36
34
  }
37
35
 
38
36
  return {
@@ -3,14 +3,17 @@ import { get } from "svelte/store"
3
3
  const SuppressErrors = true
4
4
 
5
5
  export const createActions = context => {
6
- const { API, datasource, columns, stickyColumn } = context
6
+ const { API, datasource, columns } = context
7
7
 
8
8
  const saveDefinition = async newDefinition => {
9
9
  await API.saveTable(newDefinition)
10
10
  }
11
11
 
12
12
  const saveRow = async row => {
13
- row.tableId = get(datasource)?.tableId
13
+ row = {
14
+ ...row,
15
+ tableId: get(datasource)?.tableId,
16
+ }
14
17
  return await API.saveRow(row, SuppressErrors)
15
18
  }
16
19
 
@@ -40,9 +43,7 @@ export const createActions = context => {
40
43
  }
41
44
 
42
45
  const canUseColumn = name => {
43
- const $columns = get(columns)
44
- const $sticky = get(stickyColumn)
45
- return $columns.some(col => col.name === name) || $sticky?.name === name
46
+ return get(columns).some(col => col.name === name)
46
47
  }
47
48
 
48
49
  return {
@@ -3,7 +3,7 @@ import { get } from "svelte/store"
3
3
  const SuppressErrors = true
4
4
 
5
5
  export const createActions = context => {
6
- const { API, datasource, columns, stickyColumn } = context
6
+ const { API, datasource, columns } = context
7
7
 
8
8
  const saveDefinition = async newDefinition => {
9
9
  await API.viewV2.update(newDefinition)
@@ -11,8 +11,11 @@ export const createActions = context => {
11
11
 
12
12
  const saveRow = async row => {
13
13
  const $datasource = get(datasource)
14
- row.tableId = $datasource?.tableId
15
- row._viewId = $datasource?.id
14
+ row = {
15
+ ...row,
16
+ tableId: $datasource?.tableId,
17
+ _viewId: $datasource?.id,
18
+ }
16
19
  return {
17
20
  ...(await API.saveRow(row, SuppressErrors)),
18
21
  _viewId: row._viewId,
@@ -37,12 +40,7 @@ export const createActions = context => {
37
40
  }
38
41
 
39
42
  const canUseColumn = name => {
40
- const $columns = get(columns)
41
- const $sticky = get(stickyColumn)
42
- return (
43
- $columns.some(col => col.name === name && col.visible) ||
44
- $sticky?.name === name
45
- )
43
+ return get(columns).some(col => col.name === name && col.visible)
46
44
  }
47
45
 
48
46
  return {
@@ -20,28 +20,30 @@ import * as Table from "./datasources/table"
20
20
  import * as ViewV2 from "./datasources/viewV2"
21
21
  import * as NonPlus from "./datasources/nonPlus"
22
22
  import * as Cache from "./cache"
23
+ import * as Conditions from "./conditions"
23
24
 
24
25
  const DependencyOrderedStores = [
25
26
  Sort,
26
27
  Filter,
27
28
  Bounds,
28
- Scroll,
29
29
  Table,
30
30
  ViewV2,
31
31
  NonPlus,
32
32
  Datasource,
33
33
  Columns,
34
+ Scroll,
35
+ Validation,
34
36
  Rows,
37
+ Conditions,
35
38
  UI,
36
- Validation,
37
39
  Resize,
38
40
  Viewport,
39
41
  Reorder,
40
42
  Users,
41
43
  Menu,
42
44
  Pagination,
43
- Clipboard,
44
45
  Config,
46
+ Clipboard,
45
47
  Notifications,
46
48
  Cache,
47
49
  ]
@@ -1,11 +1,13 @@
1
- import { writable } from "svelte/store"
1
+ import { writable, get } from "svelte/store"
2
+ import { parseCellID } from "../lib/utils"
2
3
 
3
4
  export const createStores = () => {
4
5
  const menu = writable({
5
- x: 0,
6
- y: 0,
6
+ left: 0,
7
+ top: 0,
7
8
  visible: false,
8
- selectedRow: null,
9
+ multiRowMode: false,
10
+ multiCellMode: false,
9
11
  })
10
12
  return {
11
13
  menu,
@@ -13,7 +15,15 @@ export const createStores = () => {
13
15
  }
14
16
 
15
17
  export const createActions = context => {
16
- const { menu, focusedCellId, gridID } = context
18
+ const {
19
+ menu,
20
+ focusedCellId,
21
+ gridID,
22
+ selectedRows,
23
+ selectedRowCount,
24
+ selectedCellMap,
25
+ selectedCellCount,
26
+ } = context
17
27
 
18
28
  const open = (cellId, e) => {
19
29
  e.preventDefault()
@@ -29,11 +39,35 @@ export const createActions = context => {
29
39
  // Compute bounds of cell relative to outer data node
30
40
  const targetBounds = e.target.getBoundingClientRect()
31
41
  const dataBounds = dataNode.getBoundingClientRect()
32
- focusedCellId.set(cellId)
42
+
43
+ // Check if there are multiple rows selected, and if this is one of them
44
+ let multiRowMode = false
45
+ if (get(selectedRowCount) > 1) {
46
+ const { rowId } = parseCellID(cellId)
47
+ if (get(selectedRows)[rowId]) {
48
+ multiRowMode = true
49
+ }
50
+ }
51
+
52
+ // Check if there are multiple cells selected, and if this is one of them
53
+ let multiCellMode = false
54
+ if (!multiRowMode && get(selectedCellCount) > 1) {
55
+ if (get(selectedCellMap)[cellId]) {
56
+ multiCellMode = true
57
+ }
58
+ }
59
+
60
+ // Only focus this cell if not in multi row mode
61
+ if (!multiRowMode && !multiCellMode) {
62
+ focusedCellId.set(cellId)
63
+ }
64
+
33
65
  menu.set({
34
66
  left: targetBounds.left - dataBounds.left + e.offsetX,
35
67
  top: targetBounds.top - dataBounds.top + e.offsetY,
36
68
  visible: true,
69
+ multiRowMode,
70
+ multiCellMode,
37
71
  })
38
72
  }
39
73
 
@@ -1,4 +1,4 @@
1
- import { derived, get } from "svelte/store"
1
+ import { derived } from "svelte/store"
2
2
 
3
3
  export const initialise = context => {
4
4
  const { scrolledRowCount, rows, visualRowCapacity } = context
@@ -15,8 +15,14 @@ export const initialise = context => {
15
15
  )
16
16
 
17
17
  // Fetch next page when fewer than 25 remaining rows to scroll
18
- remainingRows.subscribe(remaining => {
19
- if (remaining < 25 && get(rowCount)) {
18
+ const needsNewPage = derived(
19
+ [remainingRows, rowCount],
20
+ ([$remainingRows, $rowCount]) => {
21
+ return $remainingRows < 25 && $rowCount
22
+ }
23
+ )
24
+ needsNewPage.subscribe($needsNewPage => {
25
+ if ($needsNewPage) {
20
26
  rows.actions.loadNextPage()
21
27
  }
22
28
  })
@@ -4,10 +4,10 @@ import { parseEventLocation } from "../lib/utils"
4
4
  const reorderInitialState = {
5
5
  sourceColumn: null,
6
6
  targetColumn: null,
7
+ insertAfter: false,
7
8
  breakpoints: [],
8
9
  gridLeft: 0,
9
10
  width: 0,
10
- latestX: 0,
11
11
  increment: 0,
12
12
  }
13
13
 
@@ -28,38 +28,41 @@ export const createActions = context => {
28
28
  const {
29
29
  reorder,
30
30
  columns,
31
- visibleColumns,
31
+ columnLookupMap,
32
+ scrollableColumns,
32
33
  scroll,
33
34
  bounds,
34
- stickyColumn,
35
- maxScrollLeft,
36
- width,
35
+ visibleColumns,
37
36
  datasource,
37
+ stickyWidth,
38
+ width,
39
+ scrollLeft,
40
+ maxScrollLeft,
38
41
  } = context
39
-
42
+ let latestX = 0
40
43
  let autoScrollInterval
41
44
  let isAutoScrolling
42
45
 
43
46
  // Callback when dragging on a colum header and starting reordering
44
47
  const startReordering = (column, e) => {
45
- const $visibleColumns = get(visibleColumns)
48
+ const $scrollableColumns = get(scrollableColumns)
46
49
  const $bounds = get(bounds)
47
- const $stickyColumn = get(stickyColumn)
50
+ const $stickyWidth = get(stickyWidth)
48
51
 
49
52
  // Generate new breakpoints for the current columns
50
- let breakpoints = $visibleColumns.map(col => ({
51
- x: col.left + col.width,
53
+ const breakpoints = $scrollableColumns.map(col => ({
54
+ x: col.__left - $stickyWidth,
52
55
  column: col.name,
56
+ insertAfter: false,
53
57
  }))
54
- if ($stickyColumn) {
55
- breakpoints.unshift({
56
- x: 0,
57
- column: $stickyColumn.name,
58
- })
59
- } else if (!$visibleColumns[0].primaryDisplay) {
60
- breakpoints.unshift({
61
- x: 0,
62
- column: null,
58
+
59
+ // Add a very left breakpoint as well
60
+ const lastCol = $scrollableColumns[$scrollableColumns.length - 1]
61
+ if (lastCol) {
62
+ breakpoints.push({
63
+ x: lastCol.__left + lastCol.width - $stickyWidth,
64
+ column: lastCol.name,
65
+ insertAfter: true,
63
66
  })
64
67
  }
65
68
 
@@ -87,24 +90,23 @@ export const createActions = context => {
87
90
  const onReorderMouseMove = e => {
88
91
  // Immediately handle the current position
89
92
  const { x } = parseEventLocation(e)
90
- reorder.update(state => ({
91
- ...state,
92
- latestX: x,
93
- }))
93
+ latestX = x
94
94
  considerReorderPosition()
95
95
 
96
96
  // Check if we need to start auto-scrolling
97
+ const $scrollLeft = get(scrollLeft)
98
+ const $maxScrollLeft = get(maxScrollLeft)
97
99
  const $reorder = get(reorder)
98
100
  const proximityCutoff = Math.min(140, get(width) / 6)
99
101
  const speedFactor = 16
100
102
  const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
101
103
  const leftProximity = Math.max(0, x - $reorder.gridLeft)
102
- if (rightProximity < proximityCutoff) {
104
+ if (rightProximity < proximityCutoff && $scrollLeft < $maxScrollLeft) {
103
105
  const weight = proximityCutoff - rightProximity
104
106
  const increment = (weight / proximityCutoff) * speedFactor
105
107
  reorder.update(state => ({ ...state, increment }))
106
108
  startAutoScroll()
107
- } else if (leftProximity < proximityCutoff) {
109
+ } else if (leftProximity < proximityCutoff && $scrollLeft > 0) {
108
110
  const weight = -1 * (proximityCutoff - leftProximity)
109
111
  const increment = (weight / proximityCutoff) * speedFactor
110
112
  reorder.update(state => ({ ...state, increment }))
@@ -117,23 +119,28 @@ export const createActions = context => {
117
119
  // Actual logic to consider the current position and determine the new order
118
120
  const considerReorderPosition = () => {
119
121
  const $reorder = get(reorder)
120
- const $scroll = get(scroll)
122
+ const $scrollLeft = get(scrollLeft)
121
123
 
122
124
  // Compute the closest breakpoint to the current position
123
- let targetColumn
125
+ let breakpoint
124
126
  let minDistance = Number.MAX_SAFE_INTEGER
125
- const mouseX = $reorder.latestX - $reorder.gridLeft + $scroll.left
127
+ const mouseX = latestX - $reorder.gridLeft + $scrollLeft
126
128
  $reorder.breakpoints.forEach(point => {
127
129
  const distance = Math.abs(point.x - mouseX)
128
130
  if (distance < minDistance) {
129
131
  minDistance = distance
130
- targetColumn = point.column
132
+ breakpoint = point
131
133
  }
132
134
  })
133
- if (targetColumn !== $reorder.targetColumn) {
135
+ if (
136
+ breakpoint &&
137
+ (breakpoint.column !== $reorder.targetColumn ||
138
+ breakpoint.insertAfter !== $reorder.insertAfter)
139
+ ) {
134
140
  reorder.update(state => ({
135
141
  ...state,
136
- targetColumn,
142
+ targetColumn: breakpoint.column,
143
+ insertAfter: breakpoint.insertAfter,
137
144
  }))
138
145
  }
139
146
  }
@@ -175,20 +182,29 @@ export const createActions = context => {
175
182
  document.removeEventListener("touchcancel", stopReordering)
176
183
 
177
184
  // Ensure there's actually a change before saving
178
- const { sourceColumn, targetColumn } = get(reorder)
185
+ const { sourceColumn, targetColumn, insertAfter } = get(reorder)
179
186
  reorder.set(reorderInitialState)
180
187
  if (sourceColumn !== targetColumn) {
181
- await moveColumn(sourceColumn, targetColumn)
188
+ await moveColumn({ sourceColumn, targetColumn, insertAfter })
182
189
  }
183
190
  }
184
191
 
185
192
  // Moves a column after another columns.
186
193
  // An undefined target column will move the source to index 0.
187
- const moveColumn = async (sourceColumn, targetColumn) => {
188
- let $columns = get(columns)
189
- let sourceIdx = $columns.findIndex(x => x.name === sourceColumn)
190
- let targetIdx = $columns.findIndex(x => x.name === targetColumn)
191
- targetIdx++
194
+ const moveColumn = async ({
195
+ sourceColumn,
196
+ targetColumn,
197
+ insertAfter = false,
198
+ }) => {
199
+ // Find the indices in the overall columns array
200
+ const $columns = get(columns)
201
+ let sourceIdx = $columns.findIndex(col => col.name === sourceColumn)
202
+ let targetIdx = $columns.findIndex(col => col.name === targetColumn)
203
+ if (insertAfter) {
204
+ targetIdx++
205
+ }
206
+
207
+ // Reorder columns
192
208
  columns.update(state => {
193
209
  const removed = state.splice(sourceIdx, 1)
194
210
  if (--targetIdx < sourceIdx) {
@@ -209,18 +225,27 @@ export const createActions = context => {
209
225
  // Moves a column one place left (as appears visually)
210
226
  const moveColumnLeft = async column => {
211
227
  const $visibleColumns = get(visibleColumns)
212
- const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
213
- await moveColumn(column, $visibleColumns[sourceIdx - 2]?.name)
228
+ const $columnLookupMap = get(columnLookupMap)
229
+ const sourceIdx = $columnLookupMap[column].__idx
230
+ await moveColumn({
231
+ sourceColumn: column,
232
+ targetColumn: $visibleColumns[sourceIdx - 1]?.name,
233
+ })
214
234
  }
215
235
 
216
236
  // Moves a column one place right (as appears visually)
217
237
  const moveColumnRight = async column => {
218
238
  const $visibleColumns = get(visibleColumns)
219
- const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
239
+ const $columnLookupMap = get(columnLookupMap)
240
+ const sourceIdx = $columnLookupMap[column].__idx
220
241
  if (sourceIdx === $visibleColumns.length - 1) {
221
242
  return
222
243
  }
223
- await moveColumn(column, $visibleColumns[sourceIdx + 1]?.name)
244
+ await moveColumn({
245
+ sourceColumn: column,
246
+ targetColumn: $visibleColumns[sourceIdx + 1]?.name,
247
+ insertAfter: true,
248
+ })
224
249
  }
225
250
 
226
251
  return {
@@ -34,7 +34,7 @@ export const createActions = context => {
34
34
  // Set initial store state
35
35
  resize.set({
36
36
  width: column.width,
37
- left: column.left,
37
+ left: column.__left,
38
38
  initialWidth: column.width,
39
39
  initialMouseX: x,
40
40
  column: column.name,