@budibase/frontend-core 2.27.3 → 2.27.5

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 CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "2.27.3",
3
+ "version": "2.27.5",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
7
7
  "svelte": "src/index.js",
8
8
  "dependencies": {
9
- "@budibase/bbui": "2.27.3",
10
- "@budibase/shared-core": "2.27.3",
11
- "@budibase/types": "2.27.3",
9
+ "@budibase/bbui": "2.27.5",
10
+ "@budibase/shared-core": "2.27.5",
11
+ "@budibase/types": "2.27.5",
12
12
  "dayjs": "^1.10.8",
13
13
  "lodash": "4.17.21",
14
14
  "shortid": "2.2.15",
15
15
  "socket.io-client": "^4.6.1"
16
16
  },
17
- "gitHead": "7edd7b2ee3284719cd17f7004a17f4ed5d818c65"
17
+ "gitHead": "e4db515ecf2c0a19cd188cf4d646946e903717b1"
18
18
  }
@@ -16,11 +16,13 @@
16
16
  import { LuceneUtils, Constants } from "@budibase/frontend-core"
17
17
  import { getContext } from "svelte"
18
18
  import FilterUsers from "./FilterUsers.svelte"
19
+ import { getFields } from "../utils/searchFields"
19
20
 
20
21
  const { OperatorOptions } = Constants
21
22
 
22
23
  export let schemaFields
23
24
  export let filters = []
25
+ export let tables = []
24
26
  export let datasource
25
27
  export let behaviourFilters = false
26
28
  export let allowBindings = false
@@ -45,12 +47,12 @@
45
47
 
46
48
  const context = getContext("context")
47
49
 
48
- $: fieldOptions = (schemaFields ?? [])
49
- .filter(field => getValidOperatorsForType(field).length)
50
- .map(field => ({
51
- label: field.displayName || field.name,
52
- value: field.name,
53
- }))
50
+ $: fieldOptions = getFields(tables, schemaFields || [], {
51
+ allowLinks: true,
52
+ }).map(field => ({
53
+ label: field.displayName || field.name,
54
+ value: field.name,
55
+ }))
54
56
 
55
57
  const addFilter = () => {
56
58
  filters = [
@@ -29,6 +29,7 @@
29
29
  focusedCellId,
30
30
  filter,
31
31
  inlineFilters,
32
+ keyboardBlocked,
32
33
  } = getContext("grid")
33
34
 
34
35
  const searchableTypes = [
@@ -57,6 +58,8 @@
57
58
  $: searching = searchValue != null
58
59
  $: debouncedUpdateFilter(searchValue)
59
60
  $: orderable = !column.primaryDisplay
61
+ $: editable = $config.canEditColumns && !column.schema.disabled
62
+ $: keyboardBlocked.set(open)
60
63
 
61
64
  const close = () => {
62
65
  open = false
@@ -231,6 +234,14 @@
231
234
  }
232
235
  const debouncedUpdateFilter = debounce(updateFilter, 250)
233
236
 
237
+ const handleDoubleClick = () => {
238
+ if (!editable || searching) {
239
+ return
240
+ }
241
+ open = true
242
+ editColumn()
243
+ }
244
+
234
245
  onMount(() => subscribe("close-edit-column", close))
235
246
  </script>
236
247
 
@@ -241,14 +252,15 @@
241
252
  <!-- svelte-ignore a11y-no-static-element-interactions -->
242
253
  <!-- svelte-ignore a11y-click-events-have-key-events -->
243
254
  <div
255
+ bind:this={anchor}
244
256
  class="header-cell"
257
+ style="flex: 0 0 {column.width}px;"
245
258
  class:open
246
259
  class:searchable
247
260
  class:searching
248
- style="flex: 0 0 {column.width}px;"
249
- bind:this={anchor}
250
261
  class:disabled={$isReordering || $isResizing}
251
262
  class:sticky={idx === "sticky"}
263
+ on:dblclick={handleDoubleClick}
252
264
  >
253
265
  <GridCell
254
266
  on:mousedown={onMouseDown}
@@ -311,7 +323,7 @@
311
323
  {#if open}
312
324
  <GridPopover
313
325
  {anchor}
314
- align="right"
326
+ align="left"
315
327
  on:close={close}
316
328
  maxHeight={null}
317
329
  resizable
@@ -322,11 +334,7 @@
322
334
  </div>
323
335
  {:else}
324
336
  <Menu>
325
- <MenuItem
326
- icon="Edit"
327
- on:click={editColumn}
328
- disabled={!$config.canEditColumns || column.schema.disabled}
329
- >
337
+ <MenuItem icon="Edit" on:click={editColumn} disabled={!editable}>
330
338
  Edit column
331
339
  </MenuItem>
332
340
  <MenuItem
@@ -1,8 +1,8 @@
1
1
  <script>
2
2
  import { Icon } from "@budibase/bbui"
3
- import { getColor } from "../lib/utils"
4
3
  import { onMount } from "svelte"
5
4
  import GridPopover from "../overlays/GridPopover.svelte"
5
+ import { OptionColours } from "../../../constants"
6
6
 
7
7
  export let value
8
8
  export let schema
@@ -13,6 +13,8 @@
13
13
  export let api
14
14
  export let contentLines = 1
15
15
 
16
+ const InvalidColor = "hsla(0, 0%, 70%, 0.3)"
17
+
16
18
  let isOpen = false
17
19
  let focusedOptionIdx = null
18
20
  let anchor
@@ -38,8 +40,11 @@
38
40
  }
39
41
 
40
42
  const getOptionColor = value => {
41
- const index = value ? options.indexOf(value) : null
42
- return getColor(index)
43
+ let idx = value ? options.indexOf(value) : null
44
+ if (idx == null || idx === -1) {
45
+ return InvalidColor
46
+ }
47
+ return OptionColours[idx % OptionColours.length]
43
48
  }
44
49
 
45
50
  const toggleOption = option => {
@@ -1,9 +1,9 @@
1
1
  <script>
2
- import { getColor } from "../lib/utils"
3
2
  import { onMount, getContext } from "svelte"
4
3
  import { Icon, Input, ProgressCircle } from "@budibase/bbui"
5
4
  import { debounce } from "../../../utils/utils"
6
5
  import GridPopover from "../overlays/GridPopover.svelte"
6
+ import { OptionColours } from "../../../constants"
7
7
 
8
8
  const { API, cache } = getContext("grid")
9
9
 
@@ -18,7 +18,7 @@
18
18
  export let primaryDisplay
19
19
  export let hideCounter = false
20
20
 
21
- const color = getColor(0)
21
+ const color = OptionColours[0]
22
22
 
23
23
  let isOpen = false
24
24
  let searchResults
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { getContext } from "svelte"
3
- import { ActionButton, Popover, Toggle, Icon } from "@budibase/bbui"
3
+ import { ActionButton, Popover, Icon } from "@budibase/bbui"
4
4
  import { getColumnIcon } from "../lib/utils"
5
+ import ToggleActionButtonGroup from "./ToggleActionButtonGroup.svelte"
5
6
 
6
7
  const { columns, datasource, stickyColumn, dispatch } = getContext("grid")
7
8
 
@@ -11,31 +12,45 @@
11
12
  $: anyHidden = $columns.some(col => !col.visible)
12
13
  $: text = getText($columns)
13
14
 
14
- const toggleColumn = async (column, visible) => {
15
- datasource.actions.addSchemaMutation(column.name, { visible })
16
- await datasource.actions.saveSchemaMutations()
17
- dispatch(visible ? "show-column" : "hide-column")
18
- }
15
+ const toggleColumn = async (column, permission) => {
16
+ const visible = permission !== PERMISSION_OPTIONS.HIDDEN
19
17
 
20
- const toggleAll = async visible => {
21
- let mutations = {}
22
- $columns.forEach(column => {
23
- mutations[column.name] = { visible }
24
- })
25
- datasource.actions.addSchemaMutations(mutations)
18
+ datasource.actions.addSchemaMutation(column.name, { visible })
26
19
  await datasource.actions.saveSchemaMutations()
27
20
  dispatch(visible ? "show-column" : "hide-column")
28
21
  }
29
22
 
30
23
  const getText = columns => {
31
24
  const hidden = columns.filter(col => !col.visible).length
32
- return hidden ? `Hide columns (${hidden})` : "Hide columns"
25
+ return hidden ? `Columns (${hidden} restricted)` : "Columns"
26
+ }
27
+
28
+ const PERMISSION_OPTIONS = {
29
+ WRITABLE: "writable",
30
+ HIDDEN: "hidden",
31
+ }
32
+
33
+ const options = [
34
+ { icon: "Edit", value: PERMISSION_OPTIONS.WRITABLE, tooltip: "Writable" },
35
+ {
36
+ icon: "VisibilityOff",
37
+ value: PERMISSION_OPTIONS.HIDDEN,
38
+ tooltip: "Hidden",
39
+ },
40
+ ]
41
+
42
+ function columnToPermissionOptions(column) {
43
+ if (!column.visible) {
44
+ return PERMISSION_OPTIONS.HIDDEN
45
+ }
46
+
47
+ return PERMISSION_OPTIONS.WRITABLE
33
48
  }
34
49
  </script>
35
50
 
36
51
  <div bind:this={anchor}>
37
52
  <ActionButton
38
- icon="VisibilityOff"
53
+ icon="ColumnSettings"
39
54
  quiet
40
55
  size="M"
41
56
  on:click={() => (open = !open)}
@@ -54,25 +69,25 @@
54
69
  <Icon size="S" name={getColumnIcon($stickyColumn)} />
55
70
  {$stickyColumn.label}
56
71
  </div>
57
- <Toggle disabled size="S" value={true} />
72
+
73
+ <ToggleActionButtonGroup
74
+ disabled
75
+ value={PERMISSION_OPTIONS.WRITABLE}
76
+ {options}
77
+ />
58
78
  {/if}
59
79
  {#each $columns as column}
60
80
  <div class="column">
61
81
  <Icon size="S" name={getColumnIcon(column)} />
62
82
  {column.label}
63
83
  </div>
64
- <Toggle
65
- size="S"
66
- value={column.visible}
67
- on:change={e => toggleColumn(column, e.detail)}
68
- disabled={column.primaryDisplay}
84
+ <ToggleActionButtonGroup
85
+ on:click={e => toggleColumn(column, e.detail)}
86
+ value={columnToPermissionOptions(column)}
87
+ {options}
69
88
  />
70
89
  {/each}
71
90
  </div>
72
- <div class="buttons">
73
- <ActionButton on:click={() => toggleAll(true)}>Show all</ActionButton>
74
- <ActionButton on:click={() => toggleAll(false)}>Hide all</ActionButton>
75
- </div>
76
91
  </div>
77
92
  </Popover>
78
93
 
@@ -83,15 +98,11 @@
83
98
  flex-direction: column;
84
99
  gap: 12px;
85
100
  }
86
- .buttons {
87
- display: flex;
88
- flex-direction: row;
89
- gap: 8px;
90
- }
91
101
  .columns {
92
102
  display: grid;
93
103
  align-items: center;
94
104
  grid-template-columns: 1fr auto;
105
+ gap: 8px;
95
106
  }
96
107
  .columns :global(.spectrum-Switch) {
97
108
  margin-right: 0;
@@ -0,0 +1,43 @@
1
+ <script>
2
+ import { createEventDispatcher } from "svelte"
3
+
4
+ let dispatch = createEventDispatcher()
5
+
6
+ import { ActionButton, AbsTooltip, TooltipType } from "@budibase/bbui"
7
+
8
+ export let value
9
+ export let options
10
+ export let disabled
11
+ </script>
12
+
13
+ <div class="permissionPicker">
14
+ {#each options as option}
15
+ <AbsTooltip text={option.tooltip} type={TooltipType.Info}>
16
+ <ActionButton
17
+ on:click={() => dispatch("click", option.value)}
18
+ {disabled}
19
+ size="S"
20
+ icon={option.icon}
21
+ quiet
22
+ selected={option.value === value}
23
+ noPadding
24
+ />
25
+ </AbsTooltip>
26
+ {/each}
27
+ </div>
28
+
29
+ <style>
30
+ .permissionPicker {
31
+ display: flex;
32
+ gap: var(--spacing-xs);
33
+ padding-left: calc(var(--spacing-xl) * 2);
34
+ }
35
+
36
+ .permissionPicker :global(.spectrum-Icon) {
37
+ width: 14px;
38
+ }
39
+ .permissionPicker :global(.spectrum-ActionButton) {
40
+ width: 28px;
41
+ height: 28px;
42
+ }
43
+ </style>
@@ -16,9 +16,10 @@
16
16
  scroll,
17
17
  isDragging,
18
18
  buttonColumnWidth,
19
+ showVScrollbar,
19
20
  } = getContext("grid")
20
21
 
21
- let measureContainer
22
+ let container
22
23
 
23
24
  $: buttons = $props.buttons?.slice(0, 3) || []
24
25
  $: columnsWidth = $visibleColumns.reduce(
@@ -39,23 +40,10 @@
39
40
  const width = entries?.[0]?.contentRect?.width ?? 0
40
41
  buttonColumnWidth.set(width)
41
42
  })
42
- observer.observe(measureContainer)
43
+ observer.observe(container)
43
44
  })
44
45
  </script>
45
46
 
46
- <!-- Hidden copy of buttons to measure -->
47
- <div class="measure" bind:this={measureContainer}>
48
- <GridCell width="auto">
49
- <div class="buttons">
50
- {#each buttons as button}
51
- <Button size="S">
52
- {button.text || "Button"}
53
- </Button>
54
- {/each}
55
- </div>
56
- </GridCell>
57
- </div>
58
-
59
47
  <!-- svelte-ignore a11y-no-static-element-interactions -->
60
48
  <div
61
49
  class="button-column"
@@ -63,7 +51,7 @@
63
51
  class:hidden={$buttonColumnWidth === 0}
64
52
  >
65
53
  <div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
66
- <GridScrollWrapper scrollVertically attachHandlers>
54
+ <GridScrollWrapper scrollVertically attachHandlers bind:ref={container}>
67
55
  {#each $renderedRows as row}
68
56
  {@const rowSelected = !!$selectedRows[row._id]}
69
57
  {@const rowHovered = $hoveredRowId === row._id}
@@ -79,7 +67,7 @@
79
67
  selected={rowSelected}
80
68
  highlighted={rowHovered || rowFocused}
81
69
  >
82
- <div class="buttons">
70
+ <div class="buttons" class:offset={$showVScrollbar}>
83
71
  {#each buttons as button}
84
72
  <Button
85
73
  newStyles
@@ -91,6 +79,9 @@
91
79
  overBackground={button.type === "overBackground"}
92
80
  on:click={() => handleClick(button, row)}
93
81
  >
82
+ {#if button.icon}
83
+ <i class="{button.icon} S" />
84
+ {/if}
94
85
  {button.text || "Button"}
95
86
  </Button>
96
87
  {/each}
@@ -130,16 +121,17 @@
130
121
  gap: var(--cell-padding);
131
122
  height: inherit;
132
123
  }
124
+ .buttons.offset {
125
+ padding-right: calc(var(--cell-padding) + 2 * var(--scroll-bar-size) - 2px);
126
+ }
127
+ .buttons :global(.spectrum-Button-Label) {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 4px;
131
+ }
133
132
 
134
133
  /* Add left cell border */
135
134
  .button-column :global(.cell) {
136
135
  border-left: var(--cell-border);
137
136
  }
138
-
139
- /* Hidden copy of buttons to measure width against */
140
- .measure {
141
- position: absolute;
142
- opacity: 0;
143
- pointer-events: none;
144
- }
145
137
  </style>
@@ -18,7 +18,7 @@
18
18
  import UserAvatars from "./UserAvatars.svelte"
19
19
  import KeyboardManager from "../overlays/KeyboardManager.svelte"
20
20
  import SortButton from "../controls/SortButton.svelte"
21
- import HideColumnsButton from "../controls/HideColumnsButton.svelte"
21
+ import ColumnsSettingButton from "../controls/ColumnsSettingButton.svelte"
22
22
  import SizeButton from "../controls/SizeButton.svelte"
23
23
  import NewRow from "./NewRow.svelte"
24
24
  import { createGridWebsocket } from "../lib/websocket"
@@ -29,6 +29,7 @@
29
29
  Padding,
30
30
  SmallRowHeight,
31
31
  ControlsHeight,
32
+ ScrollBarSize,
32
33
  } from "../lib/constants"
33
34
 
34
35
  export let API = null
@@ -145,14 +146,14 @@
145
146
  class:quiet
146
147
  on:mouseenter={() => gridFocused.set(true)}
147
148
  on:mouseleave={() => gridFocused.set(false)}
148
- style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{$minHeight}px; --controls-height:{ControlsHeight}px;"
149
+ style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{$minHeight}px; --controls-height:{ControlsHeight}px; --scroll-bar-size:{ScrollBarSize}px;"
149
150
  >
150
151
  {#if showControls}
151
152
  <div class="controls">
152
153
  <div class="controls-left">
153
154
  <slot name="filter" />
154
155
  <SortButton />
155
- <HideColumnsButton />
156
+ <ColumnsSettingButton />
156
157
  <SizeButton />
157
158
  <slot name="controls" />
158
159
  </div>
@@ -18,6 +18,7 @@
18
18
  export let scrollVertically = false
19
19
  export let scrollHorizontally = false
20
20
  export let attachHandlers = false
21
+ export let ref
21
22
 
22
23
  // Used for tracking touch events
23
24
  let initialTouchX
@@ -109,7 +110,7 @@
109
110
  on:touchmove={attachHandlers ? handleTouchMove : null}
110
111
  on:click|self={() => ($focusedCellId = null)}
111
112
  >
112
- <div {style} class="inner">
113
+ <div {style} class="inner" bind:this={ref}>
113
114
  <slot />
114
115
  </div>
115
116
  </div>
@@ -3,7 +3,8 @@
3
3
  import { Icon } from "@budibase/bbui"
4
4
  import GridPopover from "../overlays/GridPopover.svelte"
5
5
 
6
- const { visibleColumns, scroll, width, subscribe, ui } = getContext("grid")
6
+ const { visibleColumns, scroll, width, subscribe, ui, keyboardBlocked } =
7
+ getContext("grid")
7
8
 
8
9
  let anchor
9
10
  let isOpen = false
@@ -14,6 +15,7 @@
14
15
  )
15
16
  $: end = columnsWidth - 1 - $scroll.left
16
17
  $: left = Math.min($width - 40, end)
18
+ $: keyboardBlocked.set(isOpen)
17
19
 
18
20
  const open = () => {
19
21
  ui.actions.blur()
@@ -209,7 +209,7 @@
209
209
  <GridScrollWrapper scrollHorizontally attachHandlers>
210
210
  <div class="row">
211
211
  {#each $visibleColumns as column}
212
- {@const cellId = `new-${column.name}`}
212
+ {@const cellId = getCellID(NewRowID, column.name)}
213
213
  <DataCell
214
214
  {cellId}
215
215
  {column}
@@ -66,7 +66,7 @@
66
66
 
67
67
  <!-- svelte-ignore a11y-no-static-element-interactions -->
68
68
  <!-- svelte-ignore a11y-click-events-have-key-events -->
69
- <div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
69
+ <div class="content">
70
70
  <GridScrollWrapper scrollVertically attachHandlers>
71
71
  {#each $renderedRows as row, idx}
72
72
  {@const rowSelected = !!$selectedRows[row._id]}
@@ -18,13 +18,6 @@ export const getCellID = (rowId, fieldName) => {
18
18
  return `${rowId}${JOINING_CHARACTER}${fieldName}`
19
19
  }
20
20
 
21
- export const getColor = (idx, opacity = 0.3) => {
22
- if (idx == null || idx === -1) {
23
- idx = 0
24
- }
25
- return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
26
- }
27
-
28
21
  export const getColumnIcon = column => {
29
22
  if (column.schema.autocolumn) {
30
23
  return "MagicWand"
@@ -17,6 +17,7 @@
17
17
  config,
18
18
  menu,
19
19
  gridFocused,
20
+ keyboardBlocked,
20
21
  } = getContext("grid")
21
22
 
22
23
  const ignoredOriginSelectors = [
@@ -29,7 +30,7 @@
29
30
  // Global key listener which intercepts all key events
30
31
  const handleKeyDown = e => {
31
32
  // Ignore completely if the grid is not focused
32
- if (!$gridFocused) {
33
+ if (!$gridFocused || $keyboardBlocked) {
33
34
  return
34
35
  }
35
36
 
@@ -119,7 +119,7 @@
119
119
  <!-- svelte-ignore a11y-no-static-element-interactions -->
120
120
  <div
121
121
  class="v-scrollbar"
122
- style="--size:{ScrollBarSize}px; top:{barTop}px; height:{barHeight}px;"
122
+ style="top:{barTop}px; height:{barHeight}px;"
123
123
  on:mousedown={startVDragging}
124
124
  on:touchstart={startVDragging}
125
125
  class:dragging={isDraggingV}
@@ -129,7 +129,7 @@
129
129
  <!-- svelte-ignore a11y-no-static-element-interactions -->
130
130
  <div
131
131
  class="h-scrollbar"
132
- style="--size:{ScrollBarSize}px; left:{barLeft}px; width:{barWidth}px;"
132
+ style="left:{barLeft}px; width:{barWidth}px;"
133
133
  on:mousedown={startHDragging}
134
134
  on:touchstart={startHDragging}
135
135
  class:dragging={isDraggingH}
@@ -149,11 +149,11 @@
149
149
  opacity: 1;
150
150
  }
151
151
  .v-scrollbar {
152
- width: var(--size);
153
- right: var(--size);
152
+ width: var(--scroll-bar-size);
153
+ right: var(--scroll-bar-size);
154
154
  }
155
155
  .h-scrollbar {
156
- height: var(--size);
157
- bottom: var(--size);
156
+ height: var(--scroll-bar-size);
157
+ bottom: var(--scroll-bar-size);
158
158
  }
159
159
  </style>
@@ -404,8 +404,11 @@ export const createActions = context => {
404
404
 
405
405
  // Save change
406
406
  try {
407
- // Mark as in progress
408
- inProgressChanges.update(state => ({ ...state, [rowId]: true }))
407
+ // Increment change count for this row
408
+ inProgressChanges.update(state => ({
409
+ ...state,
410
+ [rowId]: (state[rowId] || 0) + 1,
411
+ }))
409
412
 
410
413
  // Update row
411
414
  const changes = get(rowChangeCache)[rowId]
@@ -423,17 +426,25 @@ export const createActions = context => {
423
426
  await refreshRow(saved.id)
424
427
  }
425
428
 
426
- // Wipe row change cache now that we've saved the row
429
+ // Wipe row change cache for any values which have been saved
430
+ const liveChanges = get(rowChangeCache)[rowId]
427
431
  rowChangeCache.update(state => {
428
- delete state[rowId]
432
+ Object.keys(changes || {}).forEach(key => {
433
+ if (changes[key] === liveChanges?.[key]) {
434
+ delete state[rowId][key]
435
+ }
436
+ })
429
437
  return state
430
438
  })
431
439
  } catch (error) {
432
440
  handleValidationError(rowId, error)
433
441
  }
434
442
 
435
- // Mark as completed
436
- inProgressChanges.update(state => ({ ...state, [rowId]: false }))
443
+ // Decrement change count for this row
444
+ inProgressChanges.update(state => ({
445
+ ...state,
446
+ [rowId]: (state[rowId] || 1) - 1,
447
+ }))
437
448
  }
438
449
 
439
450
  // Updates a value of a row
@@ -553,7 +564,6 @@ export const initialise = context => {
553
564
  previousFocusedCellId,
554
565
  rows,
555
566
  validation,
556
- focusedCellId,
557
567
  } = context
558
568
 
559
569
  // Wipe the row change cache when changing row
@@ -571,20 +581,12 @@ export const initialise = context => {
571
581
  if (!id) {
572
582
  return
573
583
  }
574
- // Stop if we changed row
575
- const split = parseCellID(id)
576
- const oldRowId = split.id
577
- const oldColumn = split.field
578
- const { id: newRowId } = parseCellID(get(focusedCellId))
579
- if (oldRowId !== newRowId) {
580
- return
581
- }
582
- // Otherwise we just changed cell in the same row
583
- const hasChanges = oldColumn in (get(rowChangeCache)[oldRowId] || {})
584
- const hasErrors = validation.actions.rowHasErrors(oldRowId)
585
- const isSavingChanges = get(inProgressChanges)[oldRowId]
586
- if (oldRowId && !hasErrors && hasChanges && !isSavingChanges) {
587
- await rows.actions.applyRowChanges(oldRowId)
584
+ const { id: rowId, field } = parseCellID(id)
585
+ const hasChanges = field in (get(rowChangeCache)[rowId] || {})
586
+ const hasErrors = validation.actions.rowHasErrors(rowId)
587
+ const isSavingChanges = get(inProgressChanges)[rowId]
588
+ if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
589
+ await rows.actions.applyRowChanges(rowId)
588
590
  }
589
591
  })
590
592
  }
@@ -109,6 +109,7 @@ export const initialise = context => {
109
109
  maxScrollTop,
110
110
  scrollLeft,
111
111
  maxScrollLeft,
112
+ buttonColumnWidth,
112
113
  } = context
113
114
 
114
115
  // Ensure scroll state never goes invalid, which can happen when changing
@@ -194,8 +195,10 @@ export const initialise = context => {
194
195
 
195
196
  // Ensure column is not cutoff on right edge
196
197
  else {
198
+ const $buttonColumnWidth = get(buttonColumnWidth)
197
199
  const rightEdge = column.left + column.width
198
- const rightBound = $bounds.width + $scroll.left - FocusedCellMinOffset
200
+ const rightBound =
201
+ $bounds.width + $scroll.left - FocusedCellMinOffset - $buttonColumnWidth
199
202
  delta = rightEdge - rightBound
200
203
  if (delta > 0) {
201
204
  scroll.update(state => ({
@@ -19,6 +19,7 @@ export const createStores = context => {
19
19
  const previousFocusedRowId = writable(null)
20
20
  const previousFocusedCellId = writable(null)
21
21
  const gridFocused = writable(false)
22
+ const keyboardBlocked = writable(false)
22
23
  const isDragging = writable(false)
23
24
  const buttonColumnWidth = writable(0)
24
25
 
@@ -54,6 +55,7 @@ export const createStores = context => {
54
55
  hoveredRowId,
55
56
  rowHeight,
56
57
  gridFocused,
58
+ keyboardBlocked,
57
59
  isDragging,
58
60
  buttonColumnWidth,
59
61
  selectedRows: {
package/src/constants.js CHANGED
@@ -6,6 +6,15 @@ export { Feature as Features } from "@budibase/types"
6
6
  import { BpmCorrelationKey } from "@budibase/shared-core"
7
7
  import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
8
8
 
9
+ export const BannedSearchTypes = [
10
+ FieldType.LINK,
11
+ FieldType.ATTACHMENTS,
12
+ FieldType.FORMULA,
13
+ FieldType.JSON,
14
+ "jsonarray",
15
+ "queryarray",
16
+ ]
17
+
9
18
  // Cookie names
10
19
  export const Cookies = {
11
20
  Auth: "budibase:auth",
@@ -141,3 +150,7 @@ export const TypeIconMap = {
141
150
  [BBReferenceFieldSubType.USER]: "User",
142
151
  },
143
152
  }
153
+
154
+ export const OptionColours = [...new Array(12).keys()].map(idx => {
155
+ return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
156
+ })
@@ -4,6 +4,7 @@ export * as CookieUtils from "./cookies"
4
4
  export * as RoleUtils from "./roles"
5
5
  export * as Utils from "./utils"
6
6
  export * as RowUtils from "./rows"
7
+ export * as search from "./searchFields"
7
8
  export { memo, derivedMemo } from "./memo"
8
9
  export { createWebsocket } from "./websocket"
9
10
  export * from "./download"
@@ -0,0 +1,37 @@
1
+ import { BannedSearchTypes } from "../constants"
2
+
3
+ export function getTableFields(tables, linkField) {
4
+ const table = tables.find(table => table._id === linkField.tableId)
5
+ // TODO: mdrury - add support for this with SQS at some point
6
+ if (!table || !table.sql) {
7
+ return []
8
+ }
9
+ const linkFields = getFields(tables, Object.values(table.schema), {
10
+ allowLinks: false,
11
+ })
12
+ return linkFields.map(field => ({
13
+ ...field,
14
+ name: `${table.name}.${field.name}`,
15
+ }))
16
+ }
17
+
18
+ export function getFields(
19
+ tables,
20
+ fields,
21
+ { allowLinks } = { allowLinks: true }
22
+ ) {
23
+ let filteredFields = fields.filter(
24
+ field => !BannedSearchTypes.includes(field.type)
25
+ )
26
+ if (allowLinks) {
27
+ const linkFields = fields.filter(field => field.type === "link")
28
+ for (let linkField of linkFields) {
29
+ // only allow one depth of SQL relationship filtering
30
+ filteredFields = filteredFields.concat(getTableFields(tables, linkField))
31
+ }
32
+ }
33
+ const staticFormulaFields = fields.filter(
34
+ field => field.type === "formula" && field.formulaType === "static"
35
+ )
36
+ return filteredFields.concat(staticFormulaFields)
37
+ }