@budibase/frontend-core 2.5.6-alpha.3 → 2.5.6-alpha.31

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 (66) hide show
  1. package/package.json +6 -4
  2. package/src/api/other.js +0 -9
  3. package/src/components/grid/cells/AttachmentCell.svelte +154 -0
  4. package/src/components/grid/cells/BooleanCell.svelte +44 -0
  5. package/src/components/grid/cells/DataCell.svelte +88 -0
  6. package/src/components/grid/cells/DateCell.svelte +77 -0
  7. package/src/components/grid/cells/FormulaCell.svelte +5 -0
  8. package/src/components/grid/cells/GridCell.svelte +162 -0
  9. package/src/components/grid/cells/GutterCell.svelte +127 -0
  10. package/src/components/grid/cells/HeaderCell.svelte +246 -0
  11. package/src/components/grid/cells/JSONCell.svelte +36 -0
  12. package/src/components/grid/cells/LongFormCell.svelte +120 -0
  13. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  14. package/src/components/grid/cells/NumberCell.svelte +7 -0
  15. package/src/components/grid/cells/OptionsCell.svelte +240 -0
  16. package/src/components/grid/cells/RelationshipCell.svelte +504 -0
  17. package/src/components/grid/cells/TextCell.svelte +111 -0
  18. package/src/components/grid/controls/AddColumnButton.svelte +16 -0
  19. package/src/components/grid/controls/AddRowButton.svelte +18 -0
  20. package/src/components/grid/controls/BetaButton.svelte +46 -0
  21. package/src/components/grid/controls/ColumnWidthButton.svelte +91 -0
  22. package/src/components/grid/controls/DeleteButton.svelte +66 -0
  23. package/src/components/grid/controls/HideColumnsButton.svelte +95 -0
  24. package/src/components/grid/controls/RowHeightButton.svelte +70 -0
  25. package/src/components/grid/controls/SortButton.svelte +116 -0
  26. package/src/components/grid/index.js +1 -0
  27. package/src/components/grid/layout/Avatar.svelte +24 -0
  28. package/src/components/grid/layout/Grid.svelte +254 -0
  29. package/src/components/grid/layout/GridBody.svelte +73 -0
  30. package/src/components/grid/layout/GridRow.svelte +57 -0
  31. package/src/components/grid/layout/GridScrollWrapper.svelte +81 -0
  32. package/src/components/grid/layout/HeaderRow.svelte +62 -0
  33. package/src/components/grid/layout/NewRow.svelte +265 -0
  34. package/src/components/grid/layout/RowCount.svelte +9 -0
  35. package/src/components/grid/layout/StickyColumn.svelte +171 -0
  36. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  37. package/src/components/grid/lib/constants.js +14 -0
  38. package/src/components/grid/lib/events.js +29 -0
  39. package/src/components/grid/lib/renderers.js +29 -0
  40. package/src/components/grid/lib/utils.js +26 -0
  41. package/src/components/grid/lib/websocket.js +63 -0
  42. package/src/components/grid/overlays/KeyboardManager.svelte +223 -0
  43. package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
  44. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  45. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  46. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  47. package/src/components/grid/stores/bounds.js +16 -0
  48. package/src/components/grid/stores/clipboard.js +34 -0
  49. package/src/components/grid/stores/columns.js +206 -0
  50. package/src/components/grid/stores/index.js +48 -0
  51. package/src/components/grid/stores/menu.js +49 -0
  52. package/src/components/grid/stores/pagination.js +24 -0
  53. package/src/components/grid/stores/reorder.js +155 -0
  54. package/src/components/grid/stores/resize.js +124 -0
  55. package/src/components/grid/stores/rows.js +467 -0
  56. package/src/components/grid/stores/scroll.js +201 -0
  57. package/src/components/grid/stores/ui.js +159 -0
  58. package/src/components/grid/stores/users.js +115 -0
  59. package/src/components/grid/stores/validation.js +45 -0
  60. package/src/components/grid/stores/viewport.js +145 -0
  61. package/src/components/index.js +1 -0
  62. package/src/fetch/DataFetch.js +27 -21
  63. package/src/fetch/QueryFetch.js +1 -1
  64. package/src/themes/midnight.css +3 -0
  65. package/src/themes/nord.css +3 -0
  66. package/src/utils/utils.js +20 -0
@@ -0,0 +1,246 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import GridCell from "./GridCell.svelte"
4
+ import { Icon, Popover, Menu, MenuItem } from "@budibase/bbui"
5
+ import { getColumnIcon } from "../lib/utils"
6
+
7
+ export let column
8
+ export let idx
9
+ export let orderable = true
10
+
11
+ const {
12
+ reorder,
13
+ isReordering,
14
+ isResizing,
15
+ rand,
16
+ sort,
17
+ renderedColumns,
18
+ dispatch,
19
+ config,
20
+ ui,
21
+ columns,
22
+ } = getContext("grid")
23
+
24
+ const bannedDisplayColumnTypes = [
25
+ "link",
26
+ "array",
27
+ "attachment",
28
+ "boolean",
29
+ "formula",
30
+ "json",
31
+ ]
32
+
33
+ let anchor
34
+ let open = false
35
+ let timeout
36
+
37
+ $: sortedBy = column.name === $sort.column
38
+ $: canMoveLeft = orderable && idx > 0
39
+ $: canMoveRight = orderable && idx < $renderedColumns.length - 1
40
+
41
+ const editColumn = () => {
42
+ dispatch("edit-column", column.schema)
43
+ open = false
44
+ }
45
+
46
+ const onMouseDown = e => {
47
+ if (e.button === 0 && orderable) {
48
+ timeout = setTimeout(() => {
49
+ reorder.actions.startReordering(column.name, e)
50
+ }, 200)
51
+ }
52
+ }
53
+
54
+ const onMouseUp = e => {
55
+ if (e.button === 0 && orderable) {
56
+ clearTimeout(timeout)
57
+ }
58
+ }
59
+
60
+ const onContextMenu = e => {
61
+ e.preventDefault()
62
+ ui.actions.blur()
63
+ open = !open
64
+ }
65
+
66
+ const sortAscending = () => {
67
+ sort.set({
68
+ column: column.name,
69
+ order: "ascending",
70
+ })
71
+ open = false
72
+ }
73
+
74
+ const sortDescending = () => {
75
+ sort.set({
76
+ column: column.name,
77
+ order: "descending",
78
+ })
79
+ open = false
80
+ }
81
+
82
+ const moveLeft = () => {
83
+ reorder.actions.moveColumnLeft(column.name)
84
+ open = false
85
+ }
86
+
87
+ const moveRight = () => {
88
+ reorder.actions.moveColumnRight(column.name)
89
+ open = false
90
+ }
91
+
92
+ const makeDisplayColumn = () => {
93
+ columns.actions.changePrimaryDisplay(column.name)
94
+ open = false
95
+ }
96
+
97
+ const hideColumn = () => {
98
+ columns.update(state => {
99
+ const index = state.findIndex(col => col.name === column.name)
100
+ state[index].visible = false
101
+ return state.slice()
102
+ })
103
+ columns.actions.saveChanges()
104
+ open = false
105
+ }
106
+ </script>
107
+
108
+ <div
109
+ class="header-cell"
110
+ class:open
111
+ style="flex: 0 0 {column.width}px;"
112
+ bind:this={anchor}
113
+ class:disabled={$isReordering || $isResizing}
114
+ class:sticky={idx === "sticky"}
115
+ >
116
+ <GridCell
117
+ on:mousedown={onMouseDown}
118
+ on:mouseup={onMouseUp}
119
+ on:contextmenu={onContextMenu}
120
+ width={column.width}
121
+ left={column.left}
122
+ defaultHeight
123
+ center
124
+ >
125
+ <Icon
126
+ size="S"
127
+ name={getColumnIcon(column)}
128
+ color={`var(--spectrum-global-color-gray-600)`}
129
+ />
130
+ <div class="name">
131
+ {column.label}
132
+ </div>
133
+ {#if sortedBy}
134
+ <div class="sort-indicator">
135
+ <Icon
136
+ size="S"
137
+ name={$sort.order === "descending" ? "SortOrderDown" : "SortOrderUp"}
138
+ color="var(--spectrum-global-color-gray-600)"
139
+ />
140
+ </div>
141
+ {/if}
142
+ <div class="more" on:click={() => (open = true)}>
143
+ <Icon
144
+ size="S"
145
+ name="MoreVertical"
146
+ color="var(--spectrum-global-color-gray-600)"
147
+ />
148
+ </div>
149
+ </GridCell>
150
+ </div>
151
+
152
+ <Popover
153
+ bind:open
154
+ {anchor}
155
+ align="right"
156
+ offset={0}
157
+ popoverTarget={document.getElementById(`grid-${rand}`)}
158
+ animate={false}
159
+ >
160
+ <Menu>
161
+ <MenuItem
162
+ icon="Edit"
163
+ on:click={editColumn}
164
+ disabled={!$config.allowEditColumns || column.schema.disabled}
165
+ >
166
+ Edit column
167
+ </MenuItem>
168
+ <MenuItem
169
+ icon="Label"
170
+ on:click={makeDisplayColumn}
171
+ disabled={idx === "sticky" ||
172
+ !$config.allowEditColumns ||
173
+ bannedDisplayColumnTypes.includes(column.schema.type)}
174
+ >
175
+ Use as display column
176
+ </MenuItem>
177
+ <MenuItem
178
+ icon="SortOrderUp"
179
+ on:click={sortAscending}
180
+ disabled={column.name === $sort.column && $sort.order === "ascending"}
181
+ >
182
+ Sort A-Z
183
+ </MenuItem>
184
+ <MenuItem
185
+ icon="SortOrderDown"
186
+ on:click={sortDescending}
187
+ disabled={column.name === $sort.column && $sort.order === "descending"}
188
+ >
189
+ Sort Z-A
190
+ </MenuItem>
191
+ <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
192
+ Move left
193
+ </MenuItem>
194
+ <MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
195
+ Move right
196
+ </MenuItem>
197
+ <MenuItem icon="VisibilityOff" on:click={hideColumn}>Hide column</MenuItem>
198
+ </Menu>
199
+ </Popover>
200
+
201
+ <style>
202
+ .header-cell {
203
+ display: flex;
204
+ }
205
+ .header-cell:not(.sticky):hover,
206
+ .header-cell:not(.sticky) :global(.cell:hover) {
207
+ cursor: grab;
208
+ }
209
+ .header-cell.disabled {
210
+ pointer-events: none;
211
+ }
212
+ .header-cell :global(.cell) {
213
+ padding: 0 var(--cell-padding);
214
+ gap: calc(2 * var(--cell-spacing));
215
+ background: var(--spectrum-global-color-gray-100);
216
+ }
217
+
218
+ .name {
219
+ flex: 1 1 auto;
220
+ width: 0;
221
+ white-space: nowrap;
222
+ text-overflow: ellipsis;
223
+ overflow: hidden;
224
+ }
225
+
226
+ .more {
227
+ display: none;
228
+ padding: 4px;
229
+ margin: 0 -4px;
230
+ }
231
+ .header-cell.open .more,
232
+ .header-cell:hover .more {
233
+ display: block;
234
+ }
235
+ .more:hover {
236
+ cursor: pointer;
237
+ }
238
+ .more:hover :global(.spectrum-Icon) {
239
+ color: var(--spectrum-global-color-gray-800) !important;
240
+ }
241
+
242
+ .header-cell.open .sort-indicator,
243
+ .header-cell:hover .sort-indicator {
244
+ display: none;
245
+ }
246
+ </style>
@@ -0,0 +1,36 @@
1
+ <script>
2
+ import LongFormCell from "./LongFormCell.svelte"
3
+
4
+ export let onChange
5
+ export let value
6
+ export let api
7
+
8
+ $: stringified = getStringifiedValue(value)
9
+
10
+ const getStringifiedValue = value => {
11
+ if (!value) {
12
+ return value
13
+ }
14
+ try {
15
+ return JSON.stringify(value, null, 2)
16
+ } catch (error) {
17
+ return null
18
+ }
19
+ }
20
+
21
+ const parse = value => {
22
+ const trimmed = value?.trim()
23
+ if (!trimmed) {
24
+ onChange(null)
25
+ return
26
+ }
27
+ try {
28
+ const parsed = JSON.parse(trimmed)
29
+ onChange(parsed)
30
+ } catch (error) {
31
+ // Swallow
32
+ }
33
+ }
34
+ </script>
35
+
36
+ <LongFormCell {...$$props} bind:api value={stringified} onChange={parse} />
@@ -0,0 +1,120 @@
1
+ <script>
2
+ import { onMount, tick } from "svelte"
3
+
4
+ export let value
5
+ export let focused = false
6
+ export let onChange
7
+ export let readonly = false
8
+ export let api
9
+ export let invertX = false
10
+ export let invertY = false
11
+
12
+ let textarea
13
+ let isOpen = false
14
+
15
+ $: editable = focused && !readonly
16
+ $: {
17
+ if (!focused) {
18
+ isOpen = false
19
+ }
20
+ }
21
+
22
+ const handleChange = e => {
23
+ onChange(e.target.value)
24
+ }
25
+
26
+ const onKeyDown = () => {
27
+ return isOpen
28
+ }
29
+
30
+ const open = async () => {
31
+ isOpen = true
32
+ await tick()
33
+ textarea.focus()
34
+ if (value?.length > 100) {
35
+ textarea.setSelectionRange(0, 0)
36
+ }
37
+ }
38
+
39
+ const close = () => {
40
+ textarea?.blur()
41
+ isOpen = false
42
+ }
43
+
44
+ onMount(() => {
45
+ api = {
46
+ focus: () => open(),
47
+ blur: () => close(),
48
+ isActive: () => isOpen,
49
+ onKeyDown,
50
+ }
51
+ })
52
+ </script>
53
+
54
+ {#if isOpen}
55
+ <textarea
56
+ class:invertX
57
+ class:invertY
58
+ bind:this={textarea}
59
+ value={value || ""}
60
+ on:change={handleChange}
61
+ on:wheel|stopPropagation
62
+ spellcheck="false"
63
+ />
64
+ {:else}
65
+ <div class="long-form-cell" on:click={editable ? open : null} class:editable>
66
+ <div class="value">
67
+ {value || ""}
68
+ </div>
69
+ </div>
70
+ {/if}
71
+
72
+ <style>
73
+ .long-form-cell {
74
+ flex: 1 1 auto;
75
+ padding: var(--cell-padding);
76
+ align-self: stretch;
77
+ display: flex;
78
+ align-items: flex-start;
79
+ overflow: hidden;
80
+ }
81
+ .long-form-cell.editable:hover {
82
+ cursor: text;
83
+ }
84
+ .value {
85
+ display: -webkit-box;
86
+ -webkit-line-clamp: var(--content-lines);
87
+ -webkit-box-orient: vertical;
88
+ overflow: hidden;
89
+ line-height: 20px;
90
+ }
91
+ textarea {
92
+ padding: var(--cell-padding);
93
+ margin: 0;
94
+ border: 2px solid var(--cell-color);
95
+ background: var(--cell-background);
96
+ font-size: var(--cell-font-size);
97
+ font-family: var(--font-sans);
98
+ color: inherit;
99
+ position: absolute;
100
+ top: 0;
101
+ left: 0;
102
+ width: calc(100% + var(--max-cell-render-width-overflow));
103
+ height: var(--max-cell-render-height);
104
+ z-index: 1;
105
+ border-radius: 2px;
106
+ resize: none;
107
+ line-height: 20px;
108
+ }
109
+ textarea.invertX {
110
+ left: auto;
111
+ right: 0;
112
+ }
113
+ textarea.invertY {
114
+ transform: translateY(-100%);
115
+ top: calc(100% + 1px);
116
+ }
117
+ textarea:focus {
118
+ outline: none;
119
+ }
120
+ </style>
@@ -0,0 +1,7 @@
1
+ <script>
2
+ import OptionsCell from "./OptionsCell.svelte"
3
+
4
+ export let api
5
+ </script>
6
+
7
+ <OptionsCell bind:api {...$$props} multi />
@@ -0,0 +1,7 @@
1
+ <script>
2
+ import TextCell from "./TextCell.svelte"
3
+
4
+ export let api
5
+ </script>
6
+
7
+ <TextCell {...$$props} bind:api type="number" />
@@ -0,0 +1,240 @@
1
+ <script>
2
+ import { Icon } from "@budibase/bbui"
3
+ import { getColor } from "../lib/utils"
4
+ import { onMount } from "svelte"
5
+
6
+ export let value
7
+ export let schema
8
+ export let onChange
9
+ export let focused = false
10
+ export let multi = false
11
+ export let readonly = false
12
+ export let api
13
+ export let invertX = false
14
+ export let invertY = false
15
+
16
+ let isOpen = false
17
+ let focusedOptionIdx = null
18
+
19
+ $: options = schema?.constraints?.inclusion || []
20
+ $: editable = focused && !readonly
21
+ $: values = Array.isArray(value) ? value : [value].filter(x => x != null)
22
+ $: {
23
+ // Close when deselected
24
+ if (!focused) {
25
+ close()
26
+ }
27
+ }
28
+
29
+ const open = () => {
30
+ isOpen = true
31
+ focusedOptionIdx = 0
32
+ }
33
+
34
+ const close = () => {
35
+ isOpen = false
36
+ }
37
+
38
+ const getOptionColor = value => {
39
+ const index = value ? options.indexOf(value) : null
40
+ return getColor(index)
41
+ }
42
+
43
+ const toggleOption = option => {
44
+ if (!multi) {
45
+ onChange(option === value ? null : option)
46
+ close()
47
+ } else {
48
+ const sanitizedValues = values.filter(x => options.includes(x))
49
+ if (sanitizedValues.includes(option)) {
50
+ onChange(sanitizedValues.filter(x => x !== option))
51
+ } else {
52
+ onChange([...sanitizedValues, option])
53
+ }
54
+ }
55
+ }
56
+
57
+ const onKeyDown = e => {
58
+ if (!isOpen) {
59
+ return false
60
+ }
61
+ e.preventDefault()
62
+ if (e.key === "ArrowDown") {
63
+ focusedOptionIdx = Math.min(focusedOptionIdx + 1, options.length - 1)
64
+ } else if (e.key === "ArrowUp") {
65
+ focusedOptionIdx = Math.max(focusedOptionIdx - 1, 0)
66
+ } else if (e.key === "Enter") {
67
+ toggleOption(options[focusedOptionIdx])
68
+ }
69
+ return true
70
+ }
71
+
72
+ onMount(() => {
73
+ api = {
74
+ focus: open,
75
+ blur: close,
76
+ isActive: () => isOpen,
77
+ onKeyDown,
78
+ }
79
+ })
80
+ </script>
81
+
82
+ <div
83
+ class="container"
84
+ class:multi
85
+ class:editable
86
+ class:open
87
+ on:click|self={editable ? open : null}
88
+ >
89
+ <div class="values" on:click={editable ? open : null}>
90
+ {#each values as val}
91
+ {@const color = getOptionColor(val)}
92
+ {#if color}
93
+ <div class="badge text" style="--color: {color}">
94
+ <span>
95
+ {val}
96
+ </span>
97
+ </div>
98
+ {:else}
99
+ <div class="text">
100
+ {val || ""}
101
+ </div>
102
+ {/if}
103
+ {/each}
104
+ </div>
105
+ {#if editable}
106
+ <div class="arrow" on:click={open}>
107
+ <Icon name="ChevronDown" />
108
+ </div>
109
+ {/if}
110
+ {#if isOpen}
111
+ <div
112
+ class="options"
113
+ class:invertX
114
+ class:invertY
115
+ on:wheel={e => e.stopPropagation()}
116
+ >
117
+ {#each options as option, idx}
118
+ {@const color = getOptionColor(option)}
119
+ <div
120
+ class="option"
121
+ on:click={() => toggleOption(option)}
122
+ class:focused={focusedOptionIdx === idx}
123
+ on:mouseenter={() => (focusedOptionIdx = idx)}
124
+ >
125
+ <div class="badge text" style="--color: {color}">
126
+ {option}
127
+ </div>
128
+ {#if values.includes(option)}
129
+ <Icon
130
+ name="Checkmark"
131
+ color="var(--spectrum-global-color-blue-400)"
132
+ />
133
+ {/if}
134
+ </div>
135
+ {/each}
136
+ </div>
137
+ {/if}
138
+ </div>
139
+
140
+ <style>
141
+ .container {
142
+ display: flex;
143
+ flex-direction: row;
144
+ justify-content: space-between;
145
+ align-items: flex-start;
146
+ align-self: stretch;
147
+ flex: 1 1 auto;
148
+ overflow: hidden;
149
+ }
150
+ .container.editable:hover {
151
+ cursor: pointer;
152
+ }
153
+ .values {
154
+ display: flex;
155
+ flex-direction: row;
156
+ justify-content: flex-start;
157
+ align-items: flex-start;
158
+ flex: 1 1 auto;
159
+ grid-column-gap: var(--cell-spacing);
160
+ grid-row-gap: var(--cell-padding);
161
+ overflow: hidden;
162
+ padding: var(--cell-padding);
163
+ flex-wrap: wrap;
164
+ }
165
+ .text {
166
+ overflow: hidden;
167
+ text-overflow: ellipsis;
168
+ white-space: nowrap;
169
+ }
170
+ .multi .text {
171
+ flex: 0 0 auto;
172
+ }
173
+ .badge {
174
+ padding: 0 var(--cell-padding);
175
+ background: var(--color);
176
+ border-radius: var(--cell-padding);
177
+ user-select: none;
178
+ display: flex;
179
+ align-items: center;
180
+ gap: var(--cell-spacing);
181
+ height: 20px;
182
+ max-width: 100%;
183
+ }
184
+ .badge span {
185
+ text-overflow: ellipsis;
186
+ overflow: hidden;
187
+ white-space: nowrap;
188
+ }
189
+ .arrow {
190
+ position: absolute;
191
+ right: 0;
192
+ top: 0;
193
+ height: 100%;
194
+ bottom: 2px;
195
+ padding: 0 6px 0 16px;
196
+ display: grid;
197
+ place-items: center;
198
+ background: linear-gradient(
199
+ to right,
200
+ transparent 0%,
201
+ var(--cell-background) 40%
202
+ );
203
+ }
204
+ .options {
205
+ min-width: calc(100% + 2px);
206
+ position: absolute;
207
+ top: 100%;
208
+ left: -1px;
209
+ display: flex;
210
+ flex-direction: column;
211
+ justify-content: flex-start;
212
+ align-items: stretch;
213
+ max-height: var(--max-cell-render-height);
214
+ overflow-y: auto;
215
+ border: var(--cell-border);
216
+ box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
217
+ }
218
+ .options.invertX {
219
+ left: auto;
220
+ right: 0;
221
+ }
222
+ .options.invertY {
223
+ transform: translateY(-100%);
224
+ top: 0;
225
+ }
226
+ .option {
227
+ flex: 0 0 var(--default-row-height);
228
+ padding: 0 var(--cell-padding);
229
+ display: flex;
230
+ flex-direction: row;
231
+ justify-content: space-between;
232
+ align-items: center;
233
+ gap: var(--cell-spacing);
234
+ background-color: var(--background);
235
+ }
236
+ .option:hover,
237
+ .option.focused {
238
+ background-color: var(--spectrum-global-color-gray-200);
239
+ }
240
+ </style>