@budibase/frontend-core 2.20.14 → 2.21.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 +4 -4
- package/src/components/grid/cells/DataCell.svelte +2 -2
- package/src/components/grid/overlays/KeyboardManager.svelte +2 -2
- package/src/components/grid/stores/rows.js +65 -39
- package/src/components/grid/stores/ui.js +10 -0
- package/src/components/grid/stores/validation.js +21 -1
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.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.
|
|
10
|
-
"@budibase/shared-core": "2.
|
|
9
|
+
"@budibase/bbui": "2.21.1",
|
|
10
|
+
"@budibase/shared-core": "2.21.1",
|
|
11
11
|
"dayjs": "^1.10.8",
|
|
12
12
|
"lodash": "4.17.21",
|
|
13
13
|
"socket.io-client": "^4.6.1"
|
|
14
14
|
},
|
|
15
|
-
"gitHead": "
|
|
15
|
+
"gitHead": "8714f99f1e39db6863117b1421af8c0a15e73915"
|
|
16
16
|
}
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
isReadonly: () => readonly,
|
|
60
60
|
getType: () => column.schema.type,
|
|
61
61
|
getValue: () => row[column.name],
|
|
62
|
-
setValue: (value, options = {
|
|
62
|
+
setValue: (value, options = { apply: true }) => {
|
|
63
63
|
validation.actions.setError(cellId, null)
|
|
64
64
|
updateValue({
|
|
65
65
|
rowId: row._id,
|
|
66
66
|
column: column.name,
|
|
67
67
|
value,
|
|
68
|
-
|
|
68
|
+
apply: options?.apply,
|
|
69
69
|
})
|
|
70
70
|
},
|
|
71
71
|
}
|
|
@@ -217,14 +217,14 @@
|
|
|
217
217
|
const type = $focusedCellAPI.getType()
|
|
218
218
|
if (type === "number" && keyCodeIsNumber(keyCode)) {
|
|
219
219
|
// Update the value locally but don't save it yet
|
|
220
|
-
$focusedCellAPI.setValue(parseInt(key), {
|
|
220
|
+
$focusedCellAPI.setValue(parseInt(key), { apply: false })
|
|
221
221
|
$focusedCellAPI.focus()
|
|
222
222
|
} else if (
|
|
223
223
|
["string", "barcodeqr", "longform"].includes(type) &&
|
|
224
224
|
(keyCodeIsLetter(keyCode) || keyCodeIsNumber(keyCode))
|
|
225
225
|
) {
|
|
226
226
|
// Update the value locally but don't save it yet
|
|
227
|
-
$focusedCellAPI.setValue(key, {
|
|
227
|
+
$focusedCellAPI.setValue(key, { apply: false })
|
|
228
228
|
$focusedCellAPI.focus()
|
|
229
229
|
}
|
|
230
230
|
}
|
|
@@ -327,29 +327,31 @@ export const createActions = context => {
|
|
|
327
327
|
get(fetch)?.getInitialData()
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
//
|
|
331
|
-
const
|
|
330
|
+
// Checks if a changeset for a row actually mutates the row or not
|
|
331
|
+
const changesAreValid = (row, changes) => {
|
|
332
|
+
const columns = Object.keys(changes || {})
|
|
333
|
+
if (!row || !columns.length) {
|
|
334
|
+
return false
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Ensure there is at least 1 column that creates a difference
|
|
338
|
+
return columns.some(column => row[column] !== changes[column])
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Patches a row with some changes in local state, and returns whether a
|
|
342
|
+
// valid pending change was made or not
|
|
343
|
+
const stashRowChanges = (rowId, changes) => {
|
|
332
344
|
const $rows = get(rows)
|
|
333
345
|
const $rowLookupMap = get(rowLookupMap)
|
|
334
346
|
const index = $rowLookupMap[rowId]
|
|
335
347
|
const row = $rows[index]
|
|
336
|
-
if (index == null || !Object.keys(changes || {}).length) {
|
|
337
|
-
return
|
|
338
|
-
}
|
|
339
348
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (row[column] !== changes[column]) {
|
|
344
|
-
same = false
|
|
345
|
-
break
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (same) {
|
|
349
|
-
return
|
|
349
|
+
// Check this is a valid change
|
|
350
|
+
if (!row || !changesAreValid(row, changes)) {
|
|
351
|
+
return false
|
|
350
352
|
}
|
|
351
353
|
|
|
352
|
-
//
|
|
354
|
+
// Add change to cache
|
|
353
355
|
rowChangeCache.update(state => ({
|
|
354
356
|
...state,
|
|
355
357
|
[rowId]: {
|
|
@@ -357,26 +359,30 @@ export const createActions = context => {
|
|
|
357
359
|
...changes,
|
|
358
360
|
},
|
|
359
361
|
}))
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
360
364
|
|
|
361
|
-
|
|
362
|
-
|
|
365
|
+
// Saves any pending changes to a row
|
|
366
|
+
const applyRowChanges = async rowId => {
|
|
367
|
+
const $rows = get(rows)
|
|
368
|
+
const $rowLookupMap = get(rowLookupMap)
|
|
369
|
+
const index = $rowLookupMap[rowId]
|
|
370
|
+
const row = $rows[index]
|
|
371
|
+
if (row == null) {
|
|
363
372
|
return
|
|
364
373
|
}
|
|
365
374
|
|
|
366
375
|
// Save change
|
|
367
376
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
[rowId]: true,
|
|
371
|
-
}))
|
|
377
|
+
// Mark as in progress
|
|
378
|
+
inProgressChanges.update(state => ({ ...state, [rowId]: true }))
|
|
372
379
|
|
|
373
380
|
// Update row
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
})
|
|
381
|
+
const changes = get(rowChangeCache)[rowId]
|
|
382
|
+
const newRow = { ...cleanRow(row), ...changes }
|
|
383
|
+
const saved = await datasource.actions.updateRow(newRow)
|
|
378
384
|
|
|
379
|
-
// Update state after a successful change
|
|
385
|
+
// Update row state after a successful change
|
|
380
386
|
if (saved?._id) {
|
|
381
387
|
rows.update(state => {
|
|
382
388
|
state[index] = saved
|
|
@@ -386,6 +392,8 @@ export const createActions = context => {
|
|
|
386
392
|
// Handle users table edge case
|
|
387
393
|
await refreshRow(saved.id)
|
|
388
394
|
}
|
|
395
|
+
|
|
396
|
+
// Wipe row change cache now that we've saved the row
|
|
389
397
|
rowChangeCache.update(state => {
|
|
390
398
|
delete state[rowId]
|
|
391
399
|
return state
|
|
@@ -393,15 +401,17 @@ export const createActions = context => {
|
|
|
393
401
|
} catch (error) {
|
|
394
402
|
handleValidationError(rowId, error)
|
|
395
403
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}))
|
|
404
|
+
|
|
405
|
+
// Mark as completed
|
|
406
|
+
inProgressChanges.update(state => ({ ...state, [rowId]: false }))
|
|
400
407
|
}
|
|
401
408
|
|
|
402
409
|
// Updates a value of a row
|
|
403
|
-
const updateValue = async ({ rowId, column, value,
|
|
404
|
-
|
|
410
|
+
const updateValue = async ({ rowId, column, value, apply = true }) => {
|
|
411
|
+
const success = stashRowChanges(rowId, { [column]: value })
|
|
412
|
+
if (success && apply) {
|
|
413
|
+
await applyRowChanges(rowId)
|
|
414
|
+
}
|
|
405
415
|
}
|
|
406
416
|
|
|
407
417
|
// Deletes an array of rows
|
|
@@ -411,9 +421,7 @@ export const createActions = context => {
|
|
|
411
421
|
}
|
|
412
422
|
|
|
413
423
|
// Actually delete rows
|
|
414
|
-
rowsToDelete.forEach(row =>
|
|
415
|
-
delete row.__idx
|
|
416
|
-
})
|
|
424
|
+
rowsToDelete.forEach(row => delete row.__idx)
|
|
417
425
|
await datasource.actions.deleteRows(rowsToDelete)
|
|
418
426
|
|
|
419
427
|
// Update state
|
|
@@ -433,7 +441,7 @@ export const createActions = context => {
|
|
|
433
441
|
newRow = newRows[i]
|
|
434
442
|
|
|
435
443
|
// Ensure we have a unique _id.
|
|
436
|
-
// This means generating one for non DS+,
|
|
444
|
+
// This means generating one for non DS+, overwriting any that may already
|
|
437
445
|
// exist as we cannot allow duplicates.
|
|
438
446
|
if (!$isDatasourcePlus) {
|
|
439
447
|
newRow._id = Helpers.uuid()
|
|
@@ -494,7 +502,7 @@ export const createActions = context => {
|
|
|
494
502
|
duplicateRow,
|
|
495
503
|
getRow,
|
|
496
504
|
updateValue,
|
|
497
|
-
|
|
505
|
+
applyRowChanges,
|
|
498
506
|
deleteRows,
|
|
499
507
|
hasRow,
|
|
500
508
|
loadNextPage,
|
|
@@ -508,7 +516,14 @@ export const createActions = context => {
|
|
|
508
516
|
}
|
|
509
517
|
|
|
510
518
|
export const initialise = context => {
|
|
511
|
-
const {
|
|
519
|
+
const {
|
|
520
|
+
rowChangeCache,
|
|
521
|
+
inProgressChanges,
|
|
522
|
+
previousFocusedRowId,
|
|
523
|
+
previousFocusedCellId,
|
|
524
|
+
rows,
|
|
525
|
+
validation,
|
|
526
|
+
} = context
|
|
512
527
|
|
|
513
528
|
// Wipe the row change cache when changing row
|
|
514
529
|
previousFocusedRowId.subscribe(id => {
|
|
@@ -519,4 +534,15 @@ export const initialise = context => {
|
|
|
519
534
|
})
|
|
520
535
|
}
|
|
521
536
|
})
|
|
537
|
+
|
|
538
|
+
// Ensure any unsaved changes are saved when changing cell
|
|
539
|
+
previousFocusedCellId.subscribe(async id => {
|
|
540
|
+
const rowId = id?.split("-")[0]
|
|
541
|
+
const hasErrors = validation.actions.rowHasErrors(rowId)
|
|
542
|
+
const hasChanges = Object.keys(get(rowChangeCache)[rowId] || {}).length > 0
|
|
543
|
+
const isSavingChanges = get(inProgressChanges)[rowId]
|
|
544
|
+
if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
|
|
545
|
+
await rows.actions.applyRowChanges(rowId)
|
|
546
|
+
}
|
|
547
|
+
})
|
|
522
548
|
}
|
|
@@ -16,6 +16,7 @@ export const createStores = context => {
|
|
|
16
16
|
const hoveredRowId = writable(null)
|
|
17
17
|
const rowHeight = writable(get(props).fixedRowHeight || DefaultRowHeight)
|
|
18
18
|
const previousFocusedRowId = writable(null)
|
|
19
|
+
const previousFocusedCellId = writable(null)
|
|
19
20
|
const gridFocused = writable(false)
|
|
20
21
|
const isDragging = writable(false)
|
|
21
22
|
const buttonColumnWidth = writable(0)
|
|
@@ -48,6 +49,7 @@ export const createStores = context => {
|
|
|
48
49
|
focusedCellAPI,
|
|
49
50
|
focusedRowId,
|
|
50
51
|
previousFocusedRowId,
|
|
52
|
+
previousFocusedCellId,
|
|
51
53
|
hoveredRowId,
|
|
52
54
|
rowHeight,
|
|
53
55
|
gridFocused,
|
|
@@ -129,6 +131,7 @@ export const initialise = context => {
|
|
|
129
131
|
const {
|
|
130
132
|
focusedRowId,
|
|
131
133
|
previousFocusedRowId,
|
|
134
|
+
previousFocusedCellId,
|
|
132
135
|
rows,
|
|
133
136
|
focusedCellId,
|
|
134
137
|
selectedRows,
|
|
@@ -181,6 +184,13 @@ export const initialise = context => {
|
|
|
181
184
|
lastFocusedRowId = id
|
|
182
185
|
})
|
|
183
186
|
|
|
187
|
+
// Remember the last focused cell ID so that we can store the previous one
|
|
188
|
+
let lastFocusedCellId = null
|
|
189
|
+
focusedCellId.subscribe(id => {
|
|
190
|
+
previousFocusedCellId.set(lastFocusedCellId)
|
|
191
|
+
lastFocusedCellId = id
|
|
192
|
+
})
|
|
193
|
+
|
|
184
194
|
// Remove hovered row when a cell is selected
|
|
185
195
|
focusedCellId.subscribe(cell => {
|
|
186
196
|
if (cell && get(hoveredRowId)) {
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { writable, get } from "svelte/store"
|
|
1
|
+
import { writable, get, derived } from "svelte/store"
|
|
2
2
|
|
|
3
|
+
// Normally we would break out actions into the explicit "createActions"
|
|
4
|
+
// function, but for validation all these actions are pure so can go into
|
|
5
|
+
// "createStores" instead to make dependency ordering simpler
|
|
3
6
|
export const createStores = () => {
|
|
4
7
|
const validation = writable({})
|
|
5
8
|
|
|
9
|
+
// Derive which rows have errors so that we can use that info later
|
|
10
|
+
const rowErrorMap = derived(validation, $validation => {
|
|
11
|
+
let map = {}
|
|
12
|
+
Object.entries($validation).forEach(([key, error]) => {
|
|
13
|
+
// Extract row ID from all errored cell IDs
|
|
14
|
+
if (error) {
|
|
15
|
+
map[key.split("-")[0]] = true
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
return map
|
|
19
|
+
})
|
|
20
|
+
|
|
6
21
|
const setError = (cellId, error) => {
|
|
7
22
|
if (!cellId) {
|
|
8
23
|
return
|
|
@@ -13,11 +28,16 @@ export const createStores = () => {
|
|
|
13
28
|
}))
|
|
14
29
|
}
|
|
15
30
|
|
|
31
|
+
const rowHasErrors = rowId => {
|
|
32
|
+
return get(rowErrorMap)[rowId]
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
return {
|
|
17
36
|
validation: {
|
|
18
37
|
...validation,
|
|
19
38
|
actions: {
|
|
20
39
|
setError,
|
|
40
|
+
rowHasErrors,
|
|
21
41
|
},
|
|
22
42
|
},
|
|
23
43
|
}
|