@budibase/frontend-core 2.5.9 → 2.5.10-alpha.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.
- package/package.json +6 -4
- package/src/api/other.js +0 -9
- package/src/components/grid/cells/AttachmentCell.svelte +154 -0
- package/src/components/grid/cells/BooleanCell.svelte +44 -0
- package/src/components/grid/cells/DataCell.svelte +91 -0
- package/src/components/grid/cells/DateCell.svelte +77 -0
- package/src/components/grid/cells/FormulaCell.svelte +5 -0
- package/src/components/grid/cells/GridCell.svelte +165 -0
- package/src/components/grid/cells/GutterCell.svelte +127 -0
- package/src/components/grid/cells/HeaderCell.svelte +248 -0
- package/src/components/grid/cells/JSONCell.svelte +36 -0
- package/src/components/grid/cells/LongFormCell.svelte +122 -0
- package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
- package/src/components/grid/cells/NumberCell.svelte +14 -0
- package/src/components/grid/cells/OptionsCell.svelte +249 -0
- package/src/components/grid/cells/RelationshipCell.svelte +518 -0
- package/src/components/grid/cells/TextCell.svelte +111 -0
- package/src/components/grid/controls/AddColumnButton.svelte +16 -0
- package/src/components/grid/controls/AddRowButton.svelte +18 -0
- package/src/components/grid/controls/BetaButton.svelte +46 -0
- package/src/components/grid/controls/ColumnWidthButton.svelte +91 -0
- package/src/components/grid/controls/DeleteButton.svelte +66 -0
- package/src/components/grid/controls/HideColumnsButton.svelte +95 -0
- package/src/components/grid/controls/RowHeightButton.svelte +70 -0
- package/src/components/grid/controls/SortButton.svelte +129 -0
- package/src/components/grid/index.js +1 -0
- package/src/components/grid/layout/Avatar.svelte +24 -0
- package/src/components/grid/layout/Grid.svelte +254 -0
- package/src/components/grid/layout/GridBody.svelte +73 -0
- package/src/components/grid/layout/GridRow.svelte +59 -0
- package/src/components/grid/layout/GridScrollWrapper.svelte +81 -0
- package/src/components/grid/layout/HeaderRow.svelte +62 -0
- package/src/components/grid/layout/KeyboardShortcut.svelte +53 -0
- package/src/components/grid/layout/NewRow.svelte +334 -0
- package/src/components/grid/layout/RowCount.svelte +9 -0
- package/src/components/grid/layout/StickyColumn.svelte +176 -0
- package/src/components/grid/layout/UserAvatars.svelte +20 -0
- package/src/components/grid/lib/constants.js +14 -0
- package/src/components/grid/lib/events.js +29 -0
- package/src/components/grid/lib/renderers.js +29 -0
- package/src/components/grid/lib/utils.js +26 -0
- package/src/components/grid/lib/websocket.js +63 -0
- package/src/components/grid/overlays/KeyboardManager.svelte +232 -0
- package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
- package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
- package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
- package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
- package/src/components/grid/stores/bounds.js +16 -0
- package/src/components/grid/stores/clipboard.js +34 -0
- package/src/components/grid/stores/columns.js +206 -0
- package/src/components/grid/stores/index.js +48 -0
- package/src/components/grid/stores/menu.js +49 -0
- package/src/components/grid/stores/pagination.js +24 -0
- package/src/components/grid/stores/reorder.js +155 -0
- package/src/components/grid/stores/resize.js +124 -0
- package/src/components/grid/stores/rows.js +467 -0
- package/src/components/grid/stores/scroll.js +201 -0
- package/src/components/grid/stores/ui.js +159 -0
- package/src/components/grid/stores/users.js +115 -0
- package/src/components/grid/stores/validation.js +45 -0
- package/src/components/grid/stores/viewport.js +145 -0
- package/src/components/index.js +1 -0
- package/src/constants.js +1 -0
- package/src/fetch/DataFetch.js +27 -21
- package/src/fetch/QueryFetch.js +1 -1
- package/src/themes/midnight.css +3 -0
- package/src/themes/nord.css +3 -0
- package/src/utils/utils.js +20 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
<script context="module">
|
|
2
|
+
// We can create a module level cache for all relationship cells to avoid
|
|
3
|
+
// having to fetch the table definition one time for each cell
|
|
4
|
+
let primaryDisplayCache = {}
|
|
5
|
+
|
|
6
|
+
const getPrimaryDisplayForTableId = async (API, tableId) => {
|
|
7
|
+
if (primaryDisplayCache[tableId]) {
|
|
8
|
+
return primaryDisplayCache[tableId]
|
|
9
|
+
}
|
|
10
|
+
const definition = await API.fetchTableDefinition(tableId)
|
|
11
|
+
const primaryDisplay =
|
|
12
|
+
definition?.primaryDisplay || definition?.schema?.[0]?.name
|
|
13
|
+
primaryDisplayCache[tableId] = primaryDisplay
|
|
14
|
+
return primaryDisplay
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { getColor } from "../lib/utils"
|
|
20
|
+
import { onMount, getContext } from "svelte"
|
|
21
|
+
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
|
|
22
|
+
import { debounce } from "../../../utils/utils"
|
|
23
|
+
|
|
24
|
+
export let value
|
|
25
|
+
export let api
|
|
26
|
+
export let readonly
|
|
27
|
+
export let focused
|
|
28
|
+
export let schema
|
|
29
|
+
export let onChange
|
|
30
|
+
export let invertX = false
|
|
31
|
+
export let invertY = false
|
|
32
|
+
export let contentLines = 1
|
|
33
|
+
|
|
34
|
+
const { API, dispatch } = getContext("grid")
|
|
35
|
+
const color = getColor(0)
|
|
36
|
+
|
|
37
|
+
let isOpen = false
|
|
38
|
+
let searchResults
|
|
39
|
+
let searchString
|
|
40
|
+
let lastSearchString
|
|
41
|
+
let primaryDisplay
|
|
42
|
+
let candidateIndex
|
|
43
|
+
let lastSearchId
|
|
44
|
+
let searching = false
|
|
45
|
+
|
|
46
|
+
$: oneRowOnly = schema?.relationshipType === "one-to-many"
|
|
47
|
+
$: editable = focused && !readonly
|
|
48
|
+
$: lookupMap = buildLookupMap(value, isOpen)
|
|
49
|
+
$: debouncedSearch(searchString)
|
|
50
|
+
$: {
|
|
51
|
+
if (!focused) {
|
|
52
|
+
close()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Builds a lookup map to quickly check which rows are selected
|
|
57
|
+
const buildLookupMap = (value, isOpen) => {
|
|
58
|
+
let map = {}
|
|
59
|
+
if (!isOpen || !value?.length) {
|
|
60
|
+
return map
|
|
61
|
+
}
|
|
62
|
+
for (let i = 0; i < value.length; i++) {
|
|
63
|
+
map[value[i]._id] = true
|
|
64
|
+
}
|
|
65
|
+
return map
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Checks if a certain row is currently selected
|
|
69
|
+
const isRowSelected = row => {
|
|
70
|
+
if (!row?._id) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
return lookupMap?.[row._id] === true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Search for rows based on the search string
|
|
77
|
+
const search = async (searchString, force = false) => {
|
|
78
|
+
// Avoid update state at all if we've already handled the update and this is
|
|
79
|
+
// a wasted search due to svelte reactivity
|
|
80
|
+
if (!force && !searchString && !lastSearchString) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Reset state if this search is invalid
|
|
85
|
+
if (!schema?.tableId || !isOpen) {
|
|
86
|
+
lastSearchString = null
|
|
87
|
+
candidateIndex = null
|
|
88
|
+
searchResults = []
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Search for results, using IDs to track invocations and ensure we're
|
|
93
|
+
// handling the latest update
|
|
94
|
+
lastSearchId = Math.random()
|
|
95
|
+
searching = true
|
|
96
|
+
const thisSearchId = lastSearchId
|
|
97
|
+
const results = await API.searchTable({
|
|
98
|
+
paginate: false,
|
|
99
|
+
tableId: schema.tableId,
|
|
100
|
+
limit: 20,
|
|
101
|
+
query: {
|
|
102
|
+
string: {
|
|
103
|
+
[`1:${primaryDisplay}`]: searchString || "",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
searching = false
|
|
108
|
+
|
|
109
|
+
// In case searching takes longer than our debounced update, abandon these
|
|
110
|
+
// results
|
|
111
|
+
if (thisSearchId !== lastSearchId) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Sort and process results
|
|
116
|
+
searchResults = sortRows(
|
|
117
|
+
results.rows?.map(row => ({
|
|
118
|
+
...row,
|
|
119
|
+
primaryDisplay: row[primaryDisplay],
|
|
120
|
+
}))
|
|
121
|
+
)
|
|
122
|
+
candidateIndex = searchResults?.length ? 0 : null
|
|
123
|
+
lastSearchString = searchString
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Debounced version of searching
|
|
127
|
+
const debouncedSearch = debounce(search, 250)
|
|
128
|
+
|
|
129
|
+
// Alphabetically sorts rows by their primary display column
|
|
130
|
+
const sortRows = rows => {
|
|
131
|
+
if (!rows?.length) {
|
|
132
|
+
return []
|
|
133
|
+
}
|
|
134
|
+
return rows.slice().sort((a, b) => {
|
|
135
|
+
return a.primaryDisplay < b.primaryDisplay ? -1 : 1
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const open = async () => {
|
|
140
|
+
isOpen = true
|
|
141
|
+
|
|
142
|
+
// Find the primary display for the related table
|
|
143
|
+
if (!primaryDisplay) {
|
|
144
|
+
searching = true
|
|
145
|
+
primaryDisplay = await getPrimaryDisplayForTableId(API, schema.tableId)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Show initial list of results
|
|
149
|
+
await search(null, true)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const close = () => {
|
|
153
|
+
isOpen = false
|
|
154
|
+
searchResults = []
|
|
155
|
+
searchString = null
|
|
156
|
+
lastSearchString = null
|
|
157
|
+
candidateIndex = null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const onKeyDown = e => {
|
|
161
|
+
if (!isOpen) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
if (e.key === "ArrowDown") {
|
|
165
|
+
// Select next result on down arrow
|
|
166
|
+
e.preventDefault()
|
|
167
|
+
if (candidateIndex == null) {
|
|
168
|
+
candidateIndex = 0
|
|
169
|
+
} else {
|
|
170
|
+
candidateIndex = Math.min(searchResults.length - 1, candidateIndex + 1)
|
|
171
|
+
}
|
|
172
|
+
} else if (e.key === "ArrowUp") {
|
|
173
|
+
// Select previous result on up array
|
|
174
|
+
e.preventDefault()
|
|
175
|
+
if (candidateIndex === 0) {
|
|
176
|
+
candidateIndex = null
|
|
177
|
+
} else if (candidateIndex != null) {
|
|
178
|
+
candidateIndex = Math.max(0, candidateIndex - 1)
|
|
179
|
+
}
|
|
180
|
+
} else if (e.key === "Enter") {
|
|
181
|
+
// Toggle the highlighted result on enter press
|
|
182
|
+
if (candidateIndex != null && searchResults[candidateIndex] != null) {
|
|
183
|
+
toggleRow(searchResults[candidateIndex])
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Toggles whether a row is included in the relationship or not
|
|
190
|
+
const toggleRow = async row => {
|
|
191
|
+
if (value?.some(x => x._id === row._id)) {
|
|
192
|
+
// If the row is already included, remove it and update the candidate
|
|
193
|
+
// row to be the the same position if possible
|
|
194
|
+
if (oneRowOnly) {
|
|
195
|
+
await onChange([])
|
|
196
|
+
} else {
|
|
197
|
+
const newValue = value.filter(x => x._id !== row._id)
|
|
198
|
+
if (!newValue.length) {
|
|
199
|
+
candidateIndex = null
|
|
200
|
+
} else {
|
|
201
|
+
candidateIndex = Math.min(candidateIndex, newValue.length - 1)
|
|
202
|
+
}
|
|
203
|
+
await onChange(newValue)
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
// If we don't have this row, include it
|
|
207
|
+
if (oneRowOnly) {
|
|
208
|
+
await onChange([row])
|
|
209
|
+
} else {
|
|
210
|
+
await onChange(sortRows([...(value || []), row]))
|
|
211
|
+
}
|
|
212
|
+
candidateIndex = null
|
|
213
|
+
}
|
|
214
|
+
close()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const showRelationship = async id => {
|
|
218
|
+
const relatedRow = await API.fetchRow({
|
|
219
|
+
tableId: schema.tableId,
|
|
220
|
+
rowId: id,
|
|
221
|
+
})
|
|
222
|
+
dispatch("edit-row", relatedRow)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const readable = value => {
|
|
226
|
+
if (value == null) {
|
|
227
|
+
return ""
|
|
228
|
+
}
|
|
229
|
+
if (value instanceof Object) {
|
|
230
|
+
return JSON.stringify(value)
|
|
231
|
+
}
|
|
232
|
+
return value
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onMount(() => {
|
|
236
|
+
api = {
|
|
237
|
+
focus: open,
|
|
238
|
+
blur: close,
|
|
239
|
+
isActive: () => isOpen,
|
|
240
|
+
onKeyDown,
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
</script>
|
|
244
|
+
|
|
245
|
+
<div class="wrapper" class:editable class:focused style="--color:{color};">
|
|
246
|
+
<div class="container">
|
|
247
|
+
<div
|
|
248
|
+
class="values"
|
|
249
|
+
class:wrap={editable || contentLines > 1}
|
|
250
|
+
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
|
251
|
+
>
|
|
252
|
+
{#each value || [] as relationship, idx}
|
|
253
|
+
{#if relationship.primaryDisplay}
|
|
254
|
+
<div class="badge">
|
|
255
|
+
<span
|
|
256
|
+
on:click={editable
|
|
257
|
+
? () => showRelationship(relationship._id)
|
|
258
|
+
: null}
|
|
259
|
+
>
|
|
260
|
+
{readable(relationship.primaryDisplay)}
|
|
261
|
+
</span>
|
|
262
|
+
{#if editable}
|
|
263
|
+
<Icon
|
|
264
|
+
name="Close"
|
|
265
|
+
size="XS"
|
|
266
|
+
hoverable
|
|
267
|
+
on:click={() => toggleRow(relationship)}
|
|
268
|
+
/>
|
|
269
|
+
{/if}
|
|
270
|
+
</div>
|
|
271
|
+
{/if}
|
|
272
|
+
{/each}
|
|
273
|
+
{#if editable}
|
|
274
|
+
<div class="add" on:click={open}>
|
|
275
|
+
<Icon name="Add" size="S" />
|
|
276
|
+
</div>
|
|
277
|
+
{/if}
|
|
278
|
+
</div>
|
|
279
|
+
{#if value?.length}
|
|
280
|
+
<div class="count">
|
|
281
|
+
{value?.length || 0}
|
|
282
|
+
</div>
|
|
283
|
+
{/if}
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
{#if isOpen}
|
|
287
|
+
<div
|
|
288
|
+
class="dropdown"
|
|
289
|
+
class:invertX
|
|
290
|
+
class:invertY
|
|
291
|
+
on:wheel|stopPropagation
|
|
292
|
+
use:clickOutside={close}
|
|
293
|
+
>
|
|
294
|
+
<div class="search">
|
|
295
|
+
<Input
|
|
296
|
+
autofocus
|
|
297
|
+
quiet
|
|
298
|
+
type="text"
|
|
299
|
+
bind:value={searchString}
|
|
300
|
+
placeholder={primaryDisplay ? `Search by ${primaryDisplay}` : null}
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
{#if searching}
|
|
304
|
+
<div class="searching">
|
|
305
|
+
<ProgressCircle size="S" />
|
|
306
|
+
</div>
|
|
307
|
+
{:else if searchResults?.length}
|
|
308
|
+
<div class="results">
|
|
309
|
+
{#each searchResults as row, idx}
|
|
310
|
+
<div
|
|
311
|
+
class="result"
|
|
312
|
+
on:click={() => toggleRow(row)}
|
|
313
|
+
class:candidate={idx === candidateIndex}
|
|
314
|
+
on:mouseenter={() => (candidateIndex = idx)}
|
|
315
|
+
>
|
|
316
|
+
<div class="badge">
|
|
317
|
+
<span>
|
|
318
|
+
{readable(row.primaryDisplay)}
|
|
319
|
+
</span>
|
|
320
|
+
</div>
|
|
321
|
+
{#if isRowSelected(row)}
|
|
322
|
+
<Icon
|
|
323
|
+
size="S"
|
|
324
|
+
name="Checkmark"
|
|
325
|
+
color="var(--spectrum-global-color-blue-400)"
|
|
326
|
+
/>
|
|
327
|
+
{/if}
|
|
328
|
+
</div>
|
|
329
|
+
{/each}
|
|
330
|
+
</div>
|
|
331
|
+
{/if}
|
|
332
|
+
</div>
|
|
333
|
+
{/if}
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<style>
|
|
337
|
+
.wrapper {
|
|
338
|
+
flex: 1 1 auto;
|
|
339
|
+
align-self: flex-start;
|
|
340
|
+
min-height: var(--row-height);
|
|
341
|
+
max-height: var(--row-height);
|
|
342
|
+
overflow: hidden;
|
|
343
|
+
--max-relationship-height: 120px;
|
|
344
|
+
}
|
|
345
|
+
.wrapper.focused {
|
|
346
|
+
position: absolute;
|
|
347
|
+
top: 0;
|
|
348
|
+
left: 0;
|
|
349
|
+
width: 100%;
|
|
350
|
+
background: var(--cell-background);
|
|
351
|
+
z-index: 1;
|
|
352
|
+
max-height: none;
|
|
353
|
+
overflow: visible;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.container {
|
|
357
|
+
min-height: var(--row-height);
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: flex-start;
|
|
361
|
+
}
|
|
362
|
+
.focused .container {
|
|
363
|
+
overflow-y: auto;
|
|
364
|
+
border-radius: 2px;
|
|
365
|
+
max-height: var(--max-relationship-height);
|
|
366
|
+
}
|
|
367
|
+
.focused .container:after {
|
|
368
|
+
content: " ";
|
|
369
|
+
position: absolute;
|
|
370
|
+
top: 0;
|
|
371
|
+
left: 0;
|
|
372
|
+
height: 100%;
|
|
373
|
+
width: 100%;
|
|
374
|
+
border: 2px solid var(--cell-color);
|
|
375
|
+
pointer-events: none;
|
|
376
|
+
border-radius: 2px;
|
|
377
|
+
box-sizing: border-box;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.values {
|
|
381
|
+
display: flex;
|
|
382
|
+
flex-direction: row;
|
|
383
|
+
justify-content: flex-start;
|
|
384
|
+
align-items: flex-start;
|
|
385
|
+
flex: 1 1 auto;
|
|
386
|
+
grid-column-gap: var(--cell-spacing);
|
|
387
|
+
grid-row-gap: var(--cell-padding);
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
padding: var(--cell-padding);
|
|
390
|
+
flex-wrap: nowrap;
|
|
391
|
+
}
|
|
392
|
+
.values.wrap {
|
|
393
|
+
flex-wrap: wrap;
|
|
394
|
+
}
|
|
395
|
+
.count {
|
|
396
|
+
display: none;
|
|
397
|
+
position: absolute;
|
|
398
|
+
top: 0;
|
|
399
|
+
right: 0;
|
|
400
|
+
color: var(--spectrum-global-color-gray-500);
|
|
401
|
+
padding: var(--cell-padding) var(--cell-padding) var(--cell-padding) 20px;
|
|
402
|
+
background: linear-gradient(
|
|
403
|
+
to right,
|
|
404
|
+
transparent 0%,
|
|
405
|
+
var(--cell-background) 40%
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
.wrapper:hover:not(.focused) .count {
|
|
409
|
+
display: block;
|
|
410
|
+
}
|
|
411
|
+
.badge {
|
|
412
|
+
flex: 0 0 auto;
|
|
413
|
+
padding: 0 var(--cell-padding);
|
|
414
|
+
background: var(--color);
|
|
415
|
+
border-radius: var(--cell-padding);
|
|
416
|
+
user-select: none;
|
|
417
|
+
display: flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
gap: var(--cell-spacing);
|
|
420
|
+
height: 20px;
|
|
421
|
+
max-width: 100%;
|
|
422
|
+
}
|
|
423
|
+
.badge span {
|
|
424
|
+
overflow: hidden;
|
|
425
|
+
white-space: nowrap;
|
|
426
|
+
text-overflow: ellipsis;
|
|
427
|
+
}
|
|
428
|
+
.editable .values .badge span:hover {
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
text-decoration: underline;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.add {
|
|
434
|
+
background: var(--spectrum-global-color-gray-200);
|
|
435
|
+
padding: 4px;
|
|
436
|
+
border-radius: 4px;
|
|
437
|
+
}
|
|
438
|
+
.add :global(.spectrum-Icon) {
|
|
439
|
+
width: 12px;
|
|
440
|
+
height: 12px;
|
|
441
|
+
}
|
|
442
|
+
.add:hover {
|
|
443
|
+
background: var(--spectrum-global-color-gray-300);
|
|
444
|
+
cursor: pointer;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.dropdown {
|
|
448
|
+
position: absolute;
|
|
449
|
+
top: 100%;
|
|
450
|
+
left: 0;
|
|
451
|
+
width: 100%;
|
|
452
|
+
max-height: calc(
|
|
453
|
+
var(--max-cell-render-height) + var(--row-height) -
|
|
454
|
+
var(--max-relationship-height)
|
|
455
|
+
);
|
|
456
|
+
background: var(--background);
|
|
457
|
+
border: var(--cell-border);
|
|
458
|
+
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
459
|
+
display: flex;
|
|
460
|
+
flex-direction: column;
|
|
461
|
+
align-items: stretch;
|
|
462
|
+
padding: 0 0 8px 0;
|
|
463
|
+
}
|
|
464
|
+
.dropdown.invertY {
|
|
465
|
+
transform: translateY(-100%);
|
|
466
|
+
top: -1px;
|
|
467
|
+
}
|
|
468
|
+
.dropdown.invertX {
|
|
469
|
+
left: auto;
|
|
470
|
+
right: 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.searching {
|
|
474
|
+
padding: var(--cell-padding);
|
|
475
|
+
display: flex;
|
|
476
|
+
justify-content: center;
|
|
477
|
+
}
|
|
478
|
+
.results {
|
|
479
|
+
overflow-y: auto;
|
|
480
|
+
overflow-x: hidden;
|
|
481
|
+
display: flex;
|
|
482
|
+
flex-direction: column;
|
|
483
|
+
align-items: stretch;
|
|
484
|
+
}
|
|
485
|
+
.result {
|
|
486
|
+
padding: 0 var(--cell-padding);
|
|
487
|
+
flex: 0 0 var(--default-row-height);
|
|
488
|
+
display: flex;
|
|
489
|
+
gap: var(--cell-spacing);
|
|
490
|
+
justify-content: space-between;
|
|
491
|
+
align-items: center;
|
|
492
|
+
}
|
|
493
|
+
.result.candidate {
|
|
494
|
+
background-color: var(--spectrum-global-color-gray-200);
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
}
|
|
497
|
+
.result .badge {
|
|
498
|
+
max-width: calc(100% - 30px);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.search {
|
|
502
|
+
flex: 0 0 calc(var(--default-row-height) - 1px);
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
margin: 4px var(--cell-padding);
|
|
506
|
+
width: calc(100% - 2 * var(--cell-padding));
|
|
507
|
+
}
|
|
508
|
+
.search :global(.spectrum-Textfield) {
|
|
509
|
+
min-width: 0;
|
|
510
|
+
width: 100%;
|
|
511
|
+
}
|
|
512
|
+
.search :global(.spectrum-Textfield-input) {
|
|
513
|
+
font-size: 13px;
|
|
514
|
+
}
|
|
515
|
+
.search :global(.spectrum-Form-item) {
|
|
516
|
+
flex: 1 1 auto;
|
|
517
|
+
}
|
|
518
|
+
</style>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from "svelte"
|
|
3
|
+
|
|
4
|
+
export let value
|
|
5
|
+
export let focused = false
|
|
6
|
+
export let onChange
|
|
7
|
+
export let type = "text"
|
|
8
|
+
export let readonly = false
|
|
9
|
+
export let api
|
|
10
|
+
|
|
11
|
+
let input
|
|
12
|
+
let active = false
|
|
13
|
+
|
|
14
|
+
$: editable = focused && !readonly
|
|
15
|
+
|
|
16
|
+
const handleChange = e => {
|
|
17
|
+
onChange(e.target.value)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const onKeyDown = e => {
|
|
21
|
+
if (!active) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
if (e.key === "Enter") {
|
|
25
|
+
input?.blur()
|
|
26
|
+
const event = new KeyboardEvent("keydown", { key: "ArrowDown" })
|
|
27
|
+
document.dispatchEvent(event)
|
|
28
|
+
}
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onMount(() => {
|
|
33
|
+
api = {
|
|
34
|
+
focus: () => input?.focus(),
|
|
35
|
+
blur: () => input?.blur(),
|
|
36
|
+
isActive: () => active,
|
|
37
|
+
onKeyDown,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if editable}
|
|
43
|
+
<input
|
|
44
|
+
bind:this={input}
|
|
45
|
+
on:focus={() => (active = true)}
|
|
46
|
+
on:blur={() => (active = false)}
|
|
47
|
+
{type}
|
|
48
|
+
value={value || ""}
|
|
49
|
+
on:change={handleChange}
|
|
50
|
+
spellcheck="false"
|
|
51
|
+
/>
|
|
52
|
+
{:else}
|
|
53
|
+
<div class="text-cell" class:number={type === "number"}>
|
|
54
|
+
<div class="value">
|
|
55
|
+
{value || ""}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.text-cell {
|
|
62
|
+
flex: 1 1 auto;
|
|
63
|
+
padding: var(--cell-padding);
|
|
64
|
+
align-self: stretch;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: flex-start;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
.text-cell.number {
|
|
70
|
+
justify-content: flex-end;
|
|
71
|
+
}
|
|
72
|
+
.value {
|
|
73
|
+
display: -webkit-box;
|
|
74
|
+
-webkit-line-clamp: var(--content-lines);
|
|
75
|
+
-webkit-box-orient: vertical;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
line-height: 20px;
|
|
78
|
+
}
|
|
79
|
+
.number .value {
|
|
80
|
+
-webkit-line-clamp: 1;
|
|
81
|
+
}
|
|
82
|
+
input {
|
|
83
|
+
flex: 1 1 auto;
|
|
84
|
+
border: none;
|
|
85
|
+
padding: var(--cell-padding);
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
text-overflow: ellipsis;
|
|
88
|
+
white-space: nowrap;
|
|
89
|
+
background: none;
|
|
90
|
+
font-size: var(--cell-font-size);
|
|
91
|
+
font-family: var(--font-sans);
|
|
92
|
+
color: inherit;
|
|
93
|
+
line-height: 20px;
|
|
94
|
+
}
|
|
95
|
+
input:focus {
|
|
96
|
+
outline: none;
|
|
97
|
+
}
|
|
98
|
+
input[type="number"] {
|
|
99
|
+
text-align: right;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Hide arrows for number fields */
|
|
103
|
+
input::-webkit-outer-spin-button,
|
|
104
|
+
input::-webkit-inner-spin-button {
|
|
105
|
+
-webkit-appearance: none;
|
|
106
|
+
margin: 0;
|
|
107
|
+
}
|
|
108
|
+
input[type="number"] {
|
|
109
|
+
-moz-appearance: textfield;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { ActionButton } from "@budibase/bbui"
|
|
3
|
+
import { getContext } from "svelte"
|
|
4
|
+
|
|
5
|
+
const { config, dispatch } = getContext("grid")
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<ActionButton
|
|
9
|
+
icon="TableColumnAddRight"
|
|
10
|
+
quiet
|
|
11
|
+
size="M"
|
|
12
|
+
on:click={() => dispatch("add-column")}
|
|
13
|
+
disabled={!$config.allowAddColumns}
|
|
14
|
+
>
|
|
15
|
+
Add column
|
|
16
|
+
</ActionButton>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { ActionButton } from "@budibase/bbui"
|
|
3
|
+
import { getContext } from "svelte"
|
|
4
|
+
|
|
5
|
+
const { dispatch, columns, stickyColumn, config, loaded } = getContext("grid")
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<ActionButton
|
|
9
|
+
icon="TableRowAddBottom"
|
|
10
|
+
quiet
|
|
11
|
+
size="M"
|
|
12
|
+
on:click={() => dispatch("add-row-inline")}
|
|
13
|
+
disabled={!loaded ||
|
|
14
|
+
!$config.allowAddRows ||
|
|
15
|
+
(!$columns.length && !$stickyColumn)}
|
|
16
|
+
>
|
|
17
|
+
Add row
|
|
18
|
+
</ActionButton>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Button } from "@budibase/bbui"
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="beta-background" />
|
|
6
|
+
<div class="beta">
|
|
7
|
+
Enjoying the Grid?
|
|
8
|
+
<Button
|
|
9
|
+
size="M"
|
|
10
|
+
cta
|
|
11
|
+
on:click={() => window.open("https://t.maze.co/156382627", "_blank")}
|
|
12
|
+
>
|
|
13
|
+
Give Feedback
|
|
14
|
+
</Button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
.beta {
|
|
19
|
+
position: absolute;
|
|
20
|
+
bottom: 32px;
|
|
21
|
+
right: 32px;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 16px;
|
|
26
|
+
}
|
|
27
|
+
.beta :global(.spectrum-Button) {
|
|
28
|
+
background: var(--spectrum-global-color-magenta-400);
|
|
29
|
+
border-color: var(--spectrum-global-color-magenta-400);
|
|
30
|
+
}
|
|
31
|
+
.beta-background {
|
|
32
|
+
z-index: 0;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
position: absolute;
|
|
35
|
+
bottom: -230px;
|
|
36
|
+
right: -105px;
|
|
37
|
+
width: 1400px;
|
|
38
|
+
height: 320px;
|
|
39
|
+
transform: rotate(-22deg);
|
|
40
|
+
background: linear-gradient(
|
|
41
|
+
to top,
|
|
42
|
+
var(--cell-background) 20%,
|
|
43
|
+
transparent
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
</style>
|