@budibase/frontend-core 2.5.6-alpha.5 → 2.5.6-alpha.7

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