@budibase/frontend-core 2.23.12 → 2.24.1

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 (33) hide show
  1. package/package.json +5 -5
  2. package/src/api/backups.js +0 -3
  3. package/src/components/grid/cells/AttachmentCell.svelte +27 -28
  4. package/src/components/grid/cells/BBReferenceCell.svelte +6 -3
  5. package/src/components/grid/cells/DateCell.svelte +90 -60
  6. package/src/components/grid/cells/GridCell.svelte +3 -0
  7. package/src/components/grid/cells/HeaderCell.svelte +99 -102
  8. package/src/components/grid/cells/LongFormCell.svelte +30 -34
  9. package/src/components/grid/cells/OptionsCell.svelte +20 -35
  10. package/src/components/grid/cells/RelationshipCell.svelte +17 -64
  11. package/src/components/grid/controls/HideColumnsButton.svelte +1 -0
  12. package/src/components/grid/controls/MigrationModal.svelte +7 -3
  13. package/src/components/grid/layout/Grid.svelte +13 -7
  14. package/src/components/grid/layout/GridScrollWrapper.svelte +4 -0
  15. package/src/components/grid/layout/NewColumnButton.svelte +23 -21
  16. package/src/components/grid/layout/NewRow.svelte +6 -1
  17. package/src/components/grid/lib/constants.js +9 -4
  18. package/src/components/grid/lib/utils.js +7 -0
  19. package/src/components/grid/overlays/GridPopover.svelte +71 -0
  20. package/src/components/grid/overlays/KeyboardManager.svelte +1 -0
  21. package/src/components/grid/overlays/MenuOverlay.svelte +68 -66
  22. package/src/components/grid/overlays/PopoverOverlay.svelte +9 -0
  23. package/src/components/grid/overlays/ResizeOverlay.svelte +2 -0
  24. package/src/components/grid/overlays/ScrollOverlay.svelte +10 -14
  25. package/src/components/grid/stores/columns.js +44 -20
  26. package/src/components/grid/stores/menu.js +2 -2
  27. package/src/components/grid/stores/reorder.js +26 -16
  28. package/src/components/grid/stores/resize.js +13 -2
  29. package/src/components/grid/stores/rows.js +41 -8
  30. package/src/components/grid/stores/ui.js +1 -1
  31. package/src/components/grid/stores/viewport.js +4 -5
  32. package/src/constants.js +3 -3
  33. package/src/utils/rows.js +4 -0
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "2.23.12",
3
+ "version": "2.24.1",
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.23.12",
10
- "@budibase/shared-core": "2.23.12",
11
- "@budibase/types": "2.23.12",
9
+ "@budibase/bbui": "2.24.1",
10
+ "@budibase/shared-core": "2.24.1",
11
+ "@budibase/types": "2.24.1",
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": "35dcb2465a0a116c4a6169a01b6154990747627a"
17
+ "gitHead": "5cb6ac027c7f45e174e3545a7399d425822970d8"
18
18
  }
@@ -1,7 +1,4 @@
1
1
  export const buildBackupsEndpoints = API => ({
2
- /**
3
- * Gets a list of users in the current tenant.
4
- */
5
2
  searchBackups: async ({ appId, trigger, type, page, startDate, endDate }) => {
6
3
  const opts = {}
7
4
  if (page) {
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import { onMount, getContext } from "svelte"
3
3
  import { Dropzone } from "@budibase/bbui"
4
+ import GridPopover from "../overlays/GridPopover.svelte"
4
5
 
5
6
  export let value
6
7
  export let focused = false
@@ -8,7 +9,6 @@
8
9
  export let readonly = false
9
10
  export let api
10
11
  export let invertX = false
11
- export let invertY = false
12
12
  export let schema
13
13
  export let maximum
14
14
 
@@ -16,6 +16,7 @@
16
16
  const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
17
17
 
18
18
  let isOpen = false
19
+ let anchor
19
20
 
20
21
  $: editable = focused && !readonly
21
22
  $: {
@@ -73,7 +74,12 @@
73
74
 
74
75
  <!-- svelte-ignore a11y-no-static-element-interactions -->
75
76
  <!-- svelte-ignore a11y-click-events-have-key-events -->
76
- <div class="attachment-cell" class:editable on:click={editable ? open : null}>
77
+ <div
78
+ class="attachment-cell"
79
+ class:editable
80
+ on:click={editable ? open : null}
81
+ bind:this={anchor}
82
+ >
77
83
  {#each value || [] as attachment}
78
84
  {#if isImage(attachment.extension)}
79
85
  <img src={attachment.url} alt={attachment.extension} />
@@ -86,16 +92,24 @@
86
92
  </div>
87
93
 
88
94
  {#if isOpen}
89
- <div class="dropzone" class:invertX class:invertY>
90
- <Dropzone
91
- {value}
92
- compact
93
- on:change={e => onChange(e.detail)}
94
- maximum={maximum || schema.constraints?.length?.maximum}
95
- {processFiles}
96
- {handleFileTooLarge}
97
- />
98
- </div>
95
+ <GridPopover
96
+ open={isOpen}
97
+ {anchor}
98
+ {invertX}
99
+ maxHeight={null}
100
+ on:close={close}
101
+ >
102
+ <div class="dropzone">
103
+ <Dropzone
104
+ {value}
105
+ compact
106
+ on:change={e => onChange(e.detail)}
107
+ maximum={maximum || schema.constraints?.length?.maximum}
108
+ {processFiles}
109
+ {handleFileTooLarge}
110
+ />
111
+ </div>
112
+ </GridPopover>
99
113
  {/if}
100
114
 
101
115
  <style>
@@ -129,23 +143,8 @@
129
143
  user-select: none;
130
144
  }
131
145
  .dropzone {
132
- position: absolute;
133
- top: 100%;
134
- left: 0;
135
- width: 320px;
136
146
  background: var(--grid-background-alt);
137
- border: var(--cell-border);
147
+ width: 320px;
138
148
  padding: var(--cell-padding);
139
- box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
140
- border-bottom-left-radius: 2px;
141
- border-bottom-right-radius: 2px;
142
- }
143
- .dropzone.invertX {
144
- left: auto;
145
- right: 0;
146
- }
147
- .dropzone.invertY {
148
- transform: translateY(-100%);
149
- top: 0;
150
149
  }
151
150
  </style>
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import { getContext } from "svelte"
3
3
  import RelationshipCell from "./RelationshipCell.svelte"
4
- import { FieldSubtype, RelationshipType } from "@budibase/types"
4
+ import { BBReferenceFieldSubType, RelationshipType } from "@budibase/types"
5
5
 
6
6
  export let api
7
7
 
@@ -13,13 +13,16 @@
13
13
  // This is not really used, just adding some content to be able to render the relationship cell
14
14
  tableId: "external",
15
15
  relationshipType:
16
- subtype === FieldSubtype.USER
16
+ subtype === BBReferenceFieldSubType.USER
17
17
  ? RelationshipType.ONE_TO_MANY
18
18
  : RelationshipType.MANY_TO_MANY,
19
19
  }
20
20
 
21
21
  async function searchFunction(searchParams) {
22
- if (subtype !== FieldSubtype.USER && subtype !== FieldSubtype.USERS) {
22
+ if (
23
+ subtype !== BBReferenceFieldSubType.USER &&
24
+ subtype !== BBReferenceFieldSubType.USERS
25
+ ) {
23
26
  throw `Search for '${subtype}' not implemented`
24
27
  }
25
28
 
@@ -1,7 +1,8 @@
1
1
  <script>
2
- import dayjs from "dayjs"
3
- import { CoreDatePicker, Icon } from "@budibase/bbui"
2
+ import { CoreDatePickerPopoverContents, Icon, Helpers } from "@budibase/bbui"
4
3
  import { onMount } from "svelte"
4
+ import dayjs from "dayjs"
5
+ import GridPopover from "../overlays/GridPopover.svelte"
5
6
 
6
7
  export let value
7
8
  export let schema
@@ -9,83 +10,117 @@
9
10
  export let focused = false
10
11
  export let readonly = false
11
12
  export let api
13
+ export let invertX = false
12
14
 
13
- let flatpickr
14
15
  let isOpen
16
+ let anchor
15
17
 
16
- // Adding the 0- will turn a string like 00:00:00 into a valid ISO
17
- // date, but will make actual ISO dates invalid
18
- $: isTimeValue = !isNaN(new Date(`0-${value}`))
19
- $: timeOnly = isTimeValue || schema?.timeOnly
20
- $: dateOnly = schema?.dateOnly
21
- $: format = timeOnly
22
- ? "HH:mm:ss"
23
- : dateOnly
24
- ? "MMM D YYYY"
25
- : "MMM D YYYY, HH:mm"
18
+ $: timeOnly = schema?.timeOnly
19
+ $: enableTime = !schema?.dateOnly
20
+ $: ignoreTimezones = schema?.ignoreTimezones
26
21
  $: editable = focused && !readonly
27
- $: displayValue = getDisplayValue(value, format, timeOnly, isTimeValue)
28
-
29
- const getDisplayValue = (value, format, timeOnly, isTimeValue) => {
30
- if (!value) {
31
- return ""
32
- }
33
- // Parse full date strings
34
- if (!timeOnly || !isTimeValue) {
35
- return dayjs(value).format(format)
22
+ $: parsedValue = Helpers.parseDate(value, {
23
+ timeOnly,
24
+ enableTime,
25
+ ignoreTimezones,
26
+ })
27
+ $: displayValue = getDisplayValue(parsedValue, timeOnly, enableTime)
28
+ // Ensure open state matches desired state
29
+ $: {
30
+ if (!focused && isOpen) {
31
+ close()
36
32
  }
37
- // Otherwise must be a time string
38
- return dayjs(`0-${value}`).format(format)
39
33
  }
40
34
 
41
- // Ensure we close flatpickr when unselected
42
- $: {
43
- if (!focused) {
44
- flatpickr?.close()
35
+ const getDisplayValue = (value, timeOnly, enableTime) => {
36
+ return Helpers.getDateDisplayValue(value, {
37
+ enableTime,
38
+ timeOnly,
39
+ })
40
+ }
41
+
42
+ const open = () => {
43
+ isOpen = true
44
+ }
45
+
46
+ const close = () => {
47
+ isOpen = false
48
+
49
+ // Only save the changed value when closing. If the value is unchanged then
50
+ // this is handled upstream and no action is taken.
51
+ onChange(value)
52
+ }
53
+
54
+ const onKeyDown = e => {
55
+ if (!isOpen) {
56
+ return false
57
+ }
58
+ e.preventDefault()
59
+ if (e.key === "ArrowUp") {
60
+ changeDate(-1, "week")
61
+ } else if (e.key === "ArrowDown") {
62
+ changeDate(1, "week")
63
+ } else if (e.key === "ArrowLeft") {
64
+ changeDate(-1, "day")
65
+ } else if (e.key === "ArrowRight") {
66
+ changeDate(1, "day")
67
+ } else if (e.key === "Enter") {
68
+ close()
45
69
  }
70
+ return true
46
71
  }
47
72
 
48
- const onKeyDown = () => {
49
- return isOpen
73
+ const changeDate = (quantity, unit) => {
74
+ let newValue
75
+ if (!value) {
76
+ newValue = dayjs()
77
+ } else {
78
+ newValue = dayjs(value).add(quantity, unit)
79
+ }
80
+ value = Helpers.stringifyDate(newValue, {
81
+ enableTime,
82
+ timeOnly,
83
+ ignoreTimezones,
84
+ })
50
85
  }
51
86
 
52
87
  onMount(() => {
53
88
  api = {
54
89
  onKeyDown,
55
- focus: () => flatpickr?.open(),
56
- blur: () => flatpickr?.close(),
90
+ focus: open,
91
+ blur: close,
57
92
  isActive: () => isOpen,
58
93
  }
59
94
  })
60
95
  </script>
61
96
 
62
- <div class="container">
97
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
98
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
99
+ <div
100
+ class="container"
101
+ class:editable
102
+ on:click={editable ? open : null}
103
+ bind:this={anchor}
104
+ >
63
105
  <div class="value">
64
- {#if value}
65
- {displayValue}
66
- {/if}
106
+ {displayValue}
67
107
  </div>
68
108
  {#if editable}
69
109
  <Icon name="Calendar" />
70
110
  {/if}
71
111
  </div>
72
112
 
73
- {#if editable}
74
- <div class="picker">
75
- <CoreDatePicker
76
- {value}
77
- on:change={e => onChange(e.detail)}
78
- appendTo={document.documentElement}
79
- enableTime={!dateOnly}
80
- {timeOnly}
81
- time24hr
82
- ignoreTimezones={schema.ignoreTimezones}
83
- bind:flatpickr
84
- on:open={() => (isOpen = true)}
85
- on:close={() => (isOpen = false)}
113
+ {#if isOpen}
114
+ <GridPopover {anchor} {invertX} maxHeight={null} on:close={close}>
115
+ <CoreDatePickerPopoverContents
116
+ value={parsedValue}
86
117
  useKeyboardShortcuts={false}
118
+ on:change={e => (value = e.detail)}
119
+ {enableTime}
120
+ {timeOnly}
121
+ {ignoreTimezones}
87
122
  />
88
- </div>
123
+ </GridPopover>
89
124
  {/if}
90
125
 
91
126
  <style>
@@ -97,6 +132,10 @@
97
132
  align-items: center;
98
133
  flex: 1 1 auto;
99
134
  gap: var(--cell-spacing);
135
+ user-select: none;
136
+ }
137
+ .container.editable:hover {
138
+ cursor: pointer;
100
139
  }
101
140
  .value {
102
141
  flex: 1 1 auto;
@@ -105,15 +144,6 @@
105
144
  text-overflow: ellipsis;
106
145
  white-space: nowrap;
107
146
  line-height: 20px;
108
- }
109
- .picker {
110
- position: absolute;
111
- opacity: 0;
112
- }
113
- .picker :global(.flatpickr) {
114
- min-width: 0;
115
- }
116
- .picker :global(.spectrum-Textfield-input) {
117
- width: 100%;
147
+ height: 20px;
118
148
  }
119
149
  </style>
@@ -43,6 +43,9 @@
43
43
  on:mouseup
44
44
  on:click
45
45
  on:contextmenu
46
+ on:touchstart
47
+ on:touchend
48
+ on:touchcancel
46
49
  {style}
47
50
  >
48
51
  {#if error}
@@ -1,30 +1,22 @@
1
1
  <script>
2
2
  import { getContext, onMount, tick } from "svelte"
3
3
  import { canBeDisplayColumn, canBeSortColumn } from "@budibase/shared-core"
4
- import {
5
- Icon,
6
- Popover,
7
- Menu,
8
- MenuItem,
9
- clickOutside,
10
- Modal,
11
- } from "@budibase/bbui"
4
+ import { Icon, Menu, MenuItem, Modal } from "@budibase/bbui"
12
5
  import GridCell from "./GridCell.svelte"
13
6
  import { getColumnIcon } from "../lib/utils"
14
7
  import MigrationModal from "../controls/MigrationModal.svelte"
15
8
  import { debounce } from "../../../utils/utils"
16
9
  import { FieldType, FormulaType } from "@budibase/types"
17
10
  import { TableNames } from "../../../constants"
11
+ import GridPopover from "../overlays/GridPopover.svelte"
18
12
 
19
13
  export let column
20
14
  export let idx
21
- export let orderable = true
22
15
 
23
16
  const {
24
17
  reorder,
25
18
  isReordering,
26
19
  isResizing,
27
- rand,
28
20
  sort,
29
21
  visibleColumns,
30
22
  dispatch,
@@ -53,7 +45,6 @@
53
45
  let open = false
54
46
  let editIsOpen = false
55
47
  let timeout
56
- let popover
57
48
  let migrationModal
58
49
  let searchValue
59
50
  let input
@@ -66,6 +57,12 @@
66
57
  $: resetSearchValue(column.name)
67
58
  $: searching = searchValue != null
68
59
  $: debouncedUpdateFilter(searchValue)
60
+ $: orderable = !column.primaryDisplay
61
+
62
+ const close = () => {
63
+ open = false
64
+ editIsOpen = false
65
+ }
69
66
 
70
67
  const getSortingLabels = type => {
71
68
  switch (type) {
@@ -106,22 +103,19 @@
106
103
  dispatch("edit-column", column.schema)
107
104
  }
108
105
 
109
- const cancelEdit = () => {
110
- popover.hide()
111
- editIsOpen = false
112
- }
113
-
114
106
  const onMouseDown = e => {
115
- if (e.button === 0 && orderable) {
107
+ ui.actions.blur()
108
+ if ((e.touches?.length || e.button === 0) && orderable) {
116
109
  timeout = setTimeout(() => {
117
110
  reorder.actions.startReordering(column.name, e)
118
111
  }, 200)
119
112
  }
120
113
  }
121
114
 
122
- const onMouseUp = e => {
123
- if (e.button === 0 && orderable) {
115
+ const onMouseUp = () => {
116
+ if (timeout) {
124
117
  clearTimeout(timeout)
118
+ timeout = null
125
119
  }
126
120
  }
127
121
 
@@ -236,7 +230,7 @@
236
230
  }
237
231
  const debouncedUpdateFilter = debounce(updateFilter, 250)
238
232
 
239
- onMount(() => subscribe("close-edit-column", cancelEdit))
233
+ onMount(() => subscribe("close-edit-column", close))
240
234
  </script>
241
235
 
242
236
  <Modal bind:this={migrationModal}>
@@ -258,6 +252,9 @@
258
252
  <GridCell
259
253
  on:mousedown={onMouseDown}
260
254
  on:mouseup={onMouseUp}
255
+ on:touchstart={onMouseDown}
256
+ on:touchend={onMouseUp}
257
+ on:touchcancel={onMouseUp}
261
258
  on:contextmenu={onContextMenu}
262
259
  width={column.width}
263
260
  left={column.left}
@@ -310,88 +307,88 @@
310
307
  </GridCell>
311
308
  </div>
312
309
 
313
- <Popover
314
- bind:open
315
- bind:this={popover}
316
- {anchor}
317
- align="right"
318
- offset={0}
319
- popoverTarget={document.getElementById(`grid-${rand}`)}
320
- customZindex={50}
321
- >
322
- {#if editIsOpen}
323
- <div
324
- use:clickOutside={() => {
325
- editIsOpen = false
326
- }}
327
- class="content"
328
- >
329
- <slot />
330
- </div>
331
- {:else}
332
- <Menu>
333
- <MenuItem
334
- icon="Edit"
335
- on:click={editColumn}
336
- disabled={!$config.canEditColumns || column.schema.disabled}
337
- >
338
- Edit column
339
- </MenuItem>
340
- <MenuItem
341
- icon="Duplicate"
342
- on:click={duplicateColumn}
343
- disabled={!$config.canEditColumns}
344
- >
345
- Duplicate column
346
- </MenuItem>
347
- <MenuItem
348
- icon="Label"
349
- on:click={makeDisplayColumn}
350
- disabled={idx === "sticky" || !canBeDisplayColumn(column.schema.type)}
351
- >
352
- Use as display column
353
- </MenuItem>
354
- <MenuItem
355
- icon="SortOrderUp"
356
- on:click={sortAscending}
357
- disabled={!canBeSortColumn(column.schema.type) ||
358
- (column.name === $sort.column && $sort.order === "ascending")}
359
- >
360
- Sort {sortingLabels.ascending}
361
- </MenuItem>
362
- <MenuItem
363
- icon="SortOrderDown"
364
- on:click={sortDescending}
365
- disabled={!canBeSortColumn(column.schema.type) ||
366
- (column.name === $sort.column && $sort.order === "descending")}
367
- >
368
- Sort {sortingLabels.descending}
369
- </MenuItem>
370
- <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
371
- Move left
372
- </MenuItem>
373
- <MenuItem
374
- disabled={!canMoveRight}
375
- icon="ChevronRight"
376
- on:click={moveRight}
377
- >
378
- Move right
379
- </MenuItem>
380
- <MenuItem
381
- disabled={idx === "sticky" || !$config.showControls}
382
- icon="VisibilityOff"
383
- on:click={hideColumn}
384
- >
385
- Hide column
386
- </MenuItem>
387
- {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS}
388
- <MenuItem icon="User" on:click={openMigrationModal}>
389
- Migrate to user column
310
+ {#if open}
311
+ <GridPopover
312
+ {anchor}
313
+ align="right"
314
+ on:close={close}
315
+ maxHeight={null}
316
+ resizable
317
+ >
318
+ {#if editIsOpen}
319
+ <div class="content">
320
+ <slot />
321
+ </div>
322
+ {:else}
323
+ <Menu>
324
+ <MenuItem
325
+ icon="Edit"
326
+ on:click={editColumn}
327
+ disabled={!$config.canEditColumns || column.schema.disabled}
328
+ >
329
+ Edit column
390
330
  </MenuItem>
391
- {/if}
392
- </Menu>
393
- {/if}
394
- </Popover>
331
+ <MenuItem
332
+ icon="Duplicate"
333
+ on:click={duplicateColumn}
334
+ disabled={!$config.canEditColumns}
335
+ >
336
+ Duplicate column
337
+ </MenuItem>
338
+ <MenuItem
339
+ icon="Label"
340
+ on:click={makeDisplayColumn}
341
+ disabled={column.primaryDisplay ||
342
+ !canBeDisplayColumn(column.schema.type)}
343
+ >
344
+ Use as display column
345
+ </MenuItem>
346
+ <MenuItem
347
+ icon="SortOrderUp"
348
+ on:click={sortAscending}
349
+ disabled={!canBeSortColumn(column.schema.type) ||
350
+ (column.name === $sort.column && $sort.order === "ascending")}
351
+ >
352
+ Sort {sortingLabels.ascending}
353
+ </MenuItem>
354
+ <MenuItem
355
+ icon="SortOrderDown"
356
+ on:click={sortDescending}
357
+ disabled={!canBeSortColumn(column.schema.type) ||
358
+ (column.name === $sort.column && $sort.order === "descending")}
359
+ >
360
+ Sort {sortingLabels.descending}
361
+ </MenuItem>
362
+ <MenuItem
363
+ disabled={!canMoveLeft}
364
+ icon="ChevronLeft"
365
+ on:click={moveLeft}
366
+ >
367
+ Move left
368
+ </MenuItem>
369
+ <MenuItem
370
+ disabled={!canMoveRight}
371
+ icon="ChevronRight"
372
+ on:click={moveRight}
373
+ >
374
+ Move right
375
+ </MenuItem>
376
+ <MenuItem
377
+ disabled={column.primaryDisplay || !$config.showControls}
378
+ icon="VisibilityOff"
379
+ on:click={hideColumn}
380
+ >
381
+ Hide column
382
+ </MenuItem>
383
+ {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS}
384
+ <MenuItem icon="User" on:click={openMigrationModal}>
385
+ Migrate to user column
386
+ </MenuItem>
387
+ {/if}
388
+ </Menu>
389
+ {/if}
390
+ </GridPopover>
391
+ {/if}
395
392
 
396
393
  <style>
397
394
  .header-cell {
@@ -485,7 +482,7 @@
485
482
  }
486
483
 
487
484
  .content {
488
- width: 300px;
485
+ width: 360px;
489
486
  padding: 20px;
490
487
  display: flex;
491
488
  flex-direction: column;