@budibase/frontend-core 2.29.21 → 2.29.23
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 +5 -5
- package/src/components/FilterBuilder.svelte +1 -5
- package/src/components/grid/cells/DataCell.svelte +63 -12
- package/src/components/grid/cells/GridCell.svelte +36 -16
- package/src/components/grid/cells/GutterCell.svelte +15 -8
- package/src/components/grid/cells/HeaderCell.svelte +3 -3
- package/src/components/grid/cells/RelationshipCell.svelte +16 -8
- package/src/components/grid/controls/BulkDeleteHandler.svelte +98 -13
- package/src/components/grid/controls/BulkDuplicationHandler.svelte +79 -0
- package/src/components/grid/controls/ClipboardHandler.svelte +67 -0
- package/src/components/grid/controls/ColumnsSettingButton.svelte +5 -10
- package/src/components/grid/controls/SizeButton.svelte +6 -13
- package/src/components/grid/controls/SortButton.svelte +8 -22
- package/src/components/grid/layout/ButtonColumn.svelte +12 -6
- package/src/components/grid/layout/Grid.svelte +11 -7
- package/src/components/grid/layout/GridBody.svelte +2 -2
- package/src/components/grid/layout/GridRow.svelte +12 -6
- package/src/components/grid/layout/GridScrollWrapper.svelte +9 -7
- package/src/components/grid/layout/HeaderRow.svelte +2 -2
- package/src/components/grid/layout/NewColumnButton.svelte +11 -5
- package/src/components/grid/layout/NewRow.svelte +16 -12
- package/src/components/grid/layout/StickyColumn.svelte +24 -14
- package/src/components/grid/lib/utils.js +4 -4
- package/src/components/grid/overlays/KeyboardManager.svelte +144 -95
- package/src/components/grid/overlays/MenuOverlay.svelte +114 -63
- package/src/components/grid/overlays/ReorderOverlay.svelte +14 -18
- package/src/components/grid/overlays/ResizeOverlay.svelte +8 -21
- package/src/components/grid/stores/clipboard.js +215 -18
- package/src/components/grid/stores/columns.js +78 -97
- package/src/components/grid/stores/conditions.js +157 -0
- package/src/components/grid/stores/config.js +2 -2
- package/src/components/grid/stores/datasource.js +4 -14
- package/src/components/grid/stores/datasources/nonPlus.js +2 -4
- package/src/components/grid/stores/datasources/table.js +6 -5
- package/src/components/grid/stores/datasources/viewV2.js +7 -9
- package/src/components/grid/stores/index.js +5 -3
- package/src/components/grid/stores/menu.js +40 -6
- package/src/components/grid/stores/pagination.js +9 -3
- package/src/components/grid/stores/reorder.js +67 -42
- package/src/components/grid/stores/resize.js +1 -1
- package/src/components/grid/stores/rows.js +220 -85
- package/src/components/grid/stores/scroll.js +31 -28
- package/src/components/grid/stores/ui.js +295 -70
- package/src/components/grid/stores/users.js +2 -2
- package/src/components/grid/stores/validation.js +43 -16
- package/src/components/grid/stores/viewport.js +30 -24
- package/src/components/index.js +1 -0
- package/src/constants.js +3 -0
- package/src/themes/midnight.css +18 -17
- package/src/themes/nord.css +2 -1
- package/src/utils/utils.js +2 -0
|
@@ -4,6 +4,8 @@ import { NewRowID, RowPageSize } from "../lib/constants"
|
|
|
4
4
|
import { getCellID, parseCellID } from "../lib/utils"
|
|
5
5
|
import { tick } from "svelte"
|
|
6
6
|
import { Helpers } from "@budibase/bbui"
|
|
7
|
+
import { sleep } from "../../../utils/utils"
|
|
8
|
+
import { FieldType } from "@budibase/types"
|
|
7
9
|
|
|
8
10
|
export const createStores = () => {
|
|
9
11
|
const rows = writable([])
|
|
@@ -16,15 +18,6 @@ export const createStores = () => {
|
|
|
16
18
|
const error = writable(null)
|
|
17
19
|
const fetch = writable(null)
|
|
18
20
|
|
|
19
|
-
// Generate a lookup map to quick find a row by ID
|
|
20
|
-
const rowLookupMap = derived(rows, $rows => {
|
|
21
|
-
let map = {}
|
|
22
|
-
for (let i = 0; i < $rows.length; i++) {
|
|
23
|
-
map[$rows[i]._id] = i
|
|
24
|
-
}
|
|
25
|
-
return map
|
|
26
|
-
})
|
|
27
|
-
|
|
28
21
|
// Mark loaded as true if we've ever stopped loading
|
|
29
22
|
let hasStartedLoading = false
|
|
30
23
|
loading.subscribe($loading => {
|
|
@@ -35,25 +28,9 @@ export const createStores = () => {
|
|
|
35
28
|
}
|
|
36
29
|
})
|
|
37
30
|
|
|
38
|
-
// Enrich rows with an index property and any pending changes
|
|
39
|
-
const enrichedRows = derived(
|
|
40
|
-
[rows, rowChangeCache],
|
|
41
|
-
([$rows, $rowChangeCache]) => {
|
|
42
|
-
return $rows.map((row, idx) => ({
|
|
43
|
-
...row,
|
|
44
|
-
...$rowChangeCache[row._id],
|
|
45
|
-
__idx: idx,
|
|
46
|
-
}))
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
|
|
50
31
|
return {
|
|
51
|
-
rows
|
|
52
|
-
...rows,
|
|
53
|
-
subscribe: enrichedRows.subscribe,
|
|
54
|
-
},
|
|
32
|
+
rows,
|
|
55
33
|
fetch,
|
|
56
|
-
rowLookupMap,
|
|
57
34
|
loaded,
|
|
58
35
|
refreshing,
|
|
59
36
|
loading,
|
|
@@ -64,6 +41,35 @@ export const createStores = () => {
|
|
|
64
41
|
}
|
|
65
42
|
}
|
|
66
43
|
|
|
44
|
+
export const deriveStores = context => {
|
|
45
|
+
const { rows } = context
|
|
46
|
+
|
|
47
|
+
// Enrich rows with an index property and any pending changes
|
|
48
|
+
const enrichedRows = derived(rows, $rows => {
|
|
49
|
+
return $rows.map((row, idx) => ({
|
|
50
|
+
...row,
|
|
51
|
+
__idx: idx,
|
|
52
|
+
}))
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Generate a lookup map to quick find a row by ID
|
|
56
|
+
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
|
57
|
+
let map = {}
|
|
58
|
+
for (let i = 0; i < $enrichedRows.length; i++) {
|
|
59
|
+
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
|
60
|
+
}
|
|
61
|
+
return map
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
rows: {
|
|
66
|
+
...rows,
|
|
67
|
+
subscribe: enrichedRows.subscribe,
|
|
68
|
+
},
|
|
69
|
+
rowLookupMap,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
export const createActions = context => {
|
|
68
74
|
const {
|
|
69
75
|
rows,
|
|
@@ -86,6 +92,7 @@ export const createActions = context => {
|
|
|
86
92
|
fetch,
|
|
87
93
|
hasBudibaseIdentifiers,
|
|
88
94
|
refreshing,
|
|
95
|
+
columnLookupMap,
|
|
89
96
|
} = context
|
|
90
97
|
const instanceLoaded = writable(false)
|
|
91
98
|
|
|
@@ -188,12 +195,6 @@ export const createActions = context => {
|
|
|
188
195
|
fetch.set(newFetch)
|
|
189
196
|
})
|
|
190
197
|
|
|
191
|
-
// Gets a row by ID
|
|
192
|
-
const getRow = id => {
|
|
193
|
-
const index = get(rowLookupMap)[id]
|
|
194
|
-
return index >= 0 ? get(rows)[index] : null
|
|
195
|
-
}
|
|
196
|
-
|
|
197
198
|
// Handles validation errors from the rows API and updates local validation
|
|
198
199
|
// state, storing error messages against relevant cells
|
|
199
200
|
const handleValidationError = (rowId, error) => {
|
|
@@ -263,22 +264,15 @@ export const createActions = context => {
|
|
|
263
264
|
for (let column of missingColumns) {
|
|
264
265
|
get(notifications).error(`${column} is required but is missing`)
|
|
265
266
|
}
|
|
266
|
-
|
|
267
|
-
// Focus the first cell with an error
|
|
268
|
-
if (erroredColumns.length) {
|
|
269
|
-
focusedCellId.set(getCellID(rowId, erroredColumns[0]))
|
|
270
|
-
}
|
|
271
267
|
} else {
|
|
272
268
|
get(notifications).error(errorString || "An unknown error occurred")
|
|
273
269
|
}
|
|
274
270
|
}
|
|
275
271
|
|
|
276
272
|
// Adds a new row
|
|
277
|
-
const addRow = async (row, idx, bubble = false) => {
|
|
273
|
+
const addRow = async ({ row, idx, bubble = false, notify = true }) => {
|
|
278
274
|
try {
|
|
279
|
-
|
|
280
|
-
let newRow = { ...row }
|
|
281
|
-
newRow = await datasource.actions.addRow(newRow)
|
|
275
|
+
const newRow = await datasource.actions.addRow(row)
|
|
282
276
|
|
|
283
277
|
// Update state
|
|
284
278
|
if (idx != null) {
|
|
@@ -291,38 +285,94 @@ export const createActions = context => {
|
|
|
291
285
|
handleNewRows([newRow])
|
|
292
286
|
}
|
|
293
287
|
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
if (notify) {
|
|
289
|
+
get(notifications).success("Row created successfully")
|
|
290
|
+
}
|
|
296
291
|
return newRow
|
|
297
292
|
} catch (error) {
|
|
298
293
|
if (bubble) {
|
|
299
294
|
throw error
|
|
300
295
|
} else {
|
|
301
296
|
handleValidationError(NewRowID, error)
|
|
297
|
+
validation.actions.focusFirstRowError(NewRowID)
|
|
302
298
|
}
|
|
303
299
|
}
|
|
304
300
|
}
|
|
305
301
|
|
|
306
302
|
// Duplicates a row, inserting the duplicate row after the existing one
|
|
307
303
|
const duplicateRow = async row => {
|
|
308
|
-
let clone =
|
|
304
|
+
let clone = cleanRow(row)
|
|
309
305
|
delete clone._id
|
|
310
306
|
delete clone._rev
|
|
311
|
-
delete clone.__idx
|
|
312
307
|
try {
|
|
313
|
-
|
|
308
|
+
const duped = await addRow({
|
|
309
|
+
row: clone,
|
|
310
|
+
idx: row.__idx + 1,
|
|
311
|
+
bubble: true,
|
|
312
|
+
notify: false,
|
|
313
|
+
})
|
|
314
|
+
get(notifications).success("Duplicated 1 row")
|
|
315
|
+
return duped
|
|
314
316
|
} catch (error) {
|
|
315
317
|
handleValidationError(row._id, error)
|
|
318
|
+
validation.actions.focusFirstRowError(row._id)
|
|
316
319
|
}
|
|
317
320
|
}
|
|
318
321
|
|
|
322
|
+
// Duplicates multiple rows, inserting them after the last source row
|
|
323
|
+
const bulkDuplicate = async (rowsToDupe, progressCallback) => {
|
|
324
|
+
// Find index of last row
|
|
325
|
+
const $rowLookupMap = get(rowLookupMap)
|
|
326
|
+
const indices = rowsToDupe.map(row => $rowLookupMap[row._id]?.__idx)
|
|
327
|
+
const index = Math.max(...indices)
|
|
328
|
+
const count = rowsToDupe.length
|
|
329
|
+
|
|
330
|
+
// Clone and clean rows
|
|
331
|
+
const clones = rowsToDupe.map(row => {
|
|
332
|
+
let clone = cleanRow(row)
|
|
333
|
+
delete clone._id
|
|
334
|
+
delete clone._rev
|
|
335
|
+
return clone
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Create rows
|
|
339
|
+
let saved = []
|
|
340
|
+
let failed = 0
|
|
341
|
+
for (let i = 0; i < count; i++) {
|
|
342
|
+
try {
|
|
343
|
+
saved.push(await datasource.actions.addRow(clones[i]))
|
|
344
|
+
rowCacheMap[saved._id] = true
|
|
345
|
+
await sleep(50) // Small sleep to ensure we avoid rate limiting
|
|
346
|
+
} catch (error) {
|
|
347
|
+
failed++
|
|
348
|
+
console.error("Duplicating row failed", error)
|
|
349
|
+
}
|
|
350
|
+
progressCallback?.((i + 1) / count)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Add to state
|
|
354
|
+
if (saved.length) {
|
|
355
|
+
rows.update(state => {
|
|
356
|
+
return state.toSpliced(index + 1, 0, ...saved)
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Notify user
|
|
361
|
+
if (failed) {
|
|
362
|
+
get(notifications).error(`Failed to duplicate ${failed} of ${count} rows`)
|
|
363
|
+
} else if (saved.length) {
|
|
364
|
+
get(notifications).success(`Duplicated ${saved.length} rows`)
|
|
365
|
+
}
|
|
366
|
+
return saved
|
|
367
|
+
}
|
|
368
|
+
|
|
319
369
|
// Replaces a row in state with the newly defined row, handling updates,
|
|
320
370
|
// addition and deletion
|
|
321
371
|
const replaceRow = (id, row) => {
|
|
322
372
|
// Get index of row to check if it exists
|
|
323
373
|
const $rows = get(rows)
|
|
324
374
|
const $rowLookupMap = get(rowLookupMap)
|
|
325
|
-
const index = $rowLookupMap[id]
|
|
375
|
+
const index = $rowLookupMap[id]?.__idx
|
|
326
376
|
|
|
327
377
|
// Process as either an update, addition or deletion
|
|
328
378
|
if (row) {
|
|
@@ -371,10 +421,21 @@ export const createActions = context => {
|
|
|
371
421
|
// Patches a row with some changes in local state, and returns whether a
|
|
372
422
|
// valid pending change was made or not
|
|
373
423
|
const stashRowChanges = (rowId, changes) => {
|
|
374
|
-
const $rows = get(rows)
|
|
375
424
|
const $rowLookupMap = get(rowLookupMap)
|
|
376
|
-
const
|
|
377
|
-
const row = $
|
|
425
|
+
const $columnLookupMap = get(columnLookupMap)
|
|
426
|
+
const row = $rowLookupMap[rowId]
|
|
427
|
+
|
|
428
|
+
// Coerce some values into the correct types
|
|
429
|
+
for (let column of Object.keys(changes || {})) {
|
|
430
|
+
const type = $columnLookupMap[column]?.schema?.type
|
|
431
|
+
|
|
432
|
+
// Stringify objects
|
|
433
|
+
if (type === FieldType.STRING || type == FieldType.LONGFORM) {
|
|
434
|
+
if (changes[column] != null && typeof changes[column] !== "string") {
|
|
435
|
+
changes[column] = JSON.stringify(changes[column])
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
378
439
|
|
|
379
440
|
// Check this is a valid change
|
|
380
441
|
if (!row || !changesAreValid(row, changes)) {
|
|
@@ -392,15 +453,20 @@ export const createActions = context => {
|
|
|
392
453
|
return true
|
|
393
454
|
}
|
|
394
455
|
|
|
395
|
-
// Saves any pending changes to a row
|
|
396
|
-
|
|
397
|
-
|
|
456
|
+
// Saves any pending changes to a row, as well as any additional changes
|
|
457
|
+
// specified
|
|
458
|
+
const applyRowChanges = async ({
|
|
459
|
+
rowId,
|
|
460
|
+
changes = null,
|
|
461
|
+
updateState = true,
|
|
462
|
+
handleErrors = true,
|
|
463
|
+
}) => {
|
|
398
464
|
const $rowLookupMap = get(rowLookupMap)
|
|
399
|
-
const
|
|
400
|
-
const row = $rows[index]
|
|
465
|
+
const row = $rowLookupMap[rowId]
|
|
401
466
|
if (row == null) {
|
|
402
467
|
return
|
|
403
468
|
}
|
|
469
|
+
let savedRow
|
|
404
470
|
|
|
405
471
|
// Save change
|
|
406
472
|
try {
|
|
@@ -411,33 +477,38 @@ export const createActions = context => {
|
|
|
411
477
|
}))
|
|
412
478
|
|
|
413
479
|
// Update row
|
|
414
|
-
const
|
|
415
|
-
const newRow = { ...cleanRow(row), ...changes }
|
|
416
|
-
|
|
480
|
+
const stashedChanges = get(rowChangeCache)[rowId]
|
|
481
|
+
const newRow = { ...cleanRow(row), ...stashedChanges, ...changes }
|
|
482
|
+
savedRow = await datasource.actions.updateRow(newRow)
|
|
417
483
|
|
|
418
484
|
// Update row state after a successful change
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
state
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
485
|
+
if (savedRow?._id) {
|
|
486
|
+
if (updateState) {
|
|
487
|
+
rows.update(state => {
|
|
488
|
+
state[row.__idx] = savedRow
|
|
489
|
+
return state.slice()
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
} else if (savedRow?.id) {
|
|
425
493
|
// Handle users table edge case
|
|
426
|
-
await refreshRow(
|
|
494
|
+
await refreshRow(savedRow.id)
|
|
427
495
|
}
|
|
428
496
|
|
|
429
497
|
// Wipe row change cache for any values which have been saved
|
|
430
498
|
const liveChanges = get(rowChangeCache)[rowId]
|
|
431
499
|
rowChangeCache.update(state => {
|
|
432
|
-
Object.keys(
|
|
433
|
-
if (
|
|
500
|
+
Object.keys(stashedChanges || {}).forEach(key => {
|
|
501
|
+
if (stashedChanges[key] === liveChanges?.[key]) {
|
|
434
502
|
delete state[rowId][key]
|
|
435
503
|
}
|
|
436
504
|
})
|
|
437
505
|
return state
|
|
438
506
|
})
|
|
439
507
|
} catch (error) {
|
|
440
|
-
|
|
508
|
+
if (handleErrors) {
|
|
509
|
+
handleValidationError(rowId, error)
|
|
510
|
+
validation.actions.focusFirstRowError(rowId)
|
|
511
|
+
}
|
|
441
512
|
}
|
|
442
513
|
|
|
443
514
|
// Decrement change count for this row
|
|
@@ -445,13 +516,82 @@ export const createActions = context => {
|
|
|
445
516
|
...state,
|
|
446
517
|
[rowId]: (state[rowId] || 1) - 1,
|
|
447
518
|
}))
|
|
519
|
+
return savedRow
|
|
448
520
|
}
|
|
449
521
|
|
|
450
522
|
// Updates a value of a row
|
|
451
523
|
const updateValue = async ({ rowId, column, value, apply = true }) => {
|
|
452
524
|
const success = stashRowChanges(rowId, { [column]: value })
|
|
453
525
|
if (success && apply) {
|
|
454
|
-
await applyRowChanges(rowId)
|
|
526
|
+
await applyRowChanges({ rowId })
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const bulkUpdate = async (changeMap, progressCallback) => {
|
|
531
|
+
const rowIds = Object.keys(changeMap || {})
|
|
532
|
+
const count = rowIds.length
|
|
533
|
+
if (!count) {
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Update rows
|
|
538
|
+
const $columnLookupMap = get(columnLookupMap)
|
|
539
|
+
let updated = []
|
|
540
|
+
let failed = 0
|
|
541
|
+
for (let i = 0; i < count; i++) {
|
|
542
|
+
const rowId = rowIds[i]
|
|
543
|
+
let changes = changeMap[rowId] || {}
|
|
544
|
+
|
|
545
|
+
// Strip any readonly fields from the change set
|
|
546
|
+
for (let field of Object.keys(changes)) {
|
|
547
|
+
const column = $columnLookupMap[field]
|
|
548
|
+
if (columns.actions.isReadonly(column)) {
|
|
549
|
+
delete changes[field]
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (!Object.keys(changes).length) {
|
|
553
|
+
progressCallback?.((i + 1) / count)
|
|
554
|
+
continue
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
const updatedRow = await applyRowChanges({
|
|
558
|
+
rowId,
|
|
559
|
+
changes: changeMap[rowId],
|
|
560
|
+
updateState: false,
|
|
561
|
+
handleErrors: false,
|
|
562
|
+
})
|
|
563
|
+
if (updatedRow) {
|
|
564
|
+
updated.push(updatedRow)
|
|
565
|
+
} else {
|
|
566
|
+
failed++
|
|
567
|
+
}
|
|
568
|
+
await sleep(50) // Small sleep to ensure we avoid rate limiting
|
|
569
|
+
} catch (error) {
|
|
570
|
+
failed++
|
|
571
|
+
console.error("Failed to update row", error)
|
|
572
|
+
}
|
|
573
|
+
progressCallback?.((i + 1) / count)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Update state
|
|
577
|
+
if (updated.length) {
|
|
578
|
+
const $rowLookupMap = get(rowLookupMap)
|
|
579
|
+
rows.update(state => {
|
|
580
|
+
for (let row of updated) {
|
|
581
|
+
const index = $rowLookupMap[row._id].__idx
|
|
582
|
+
state[index] = row
|
|
583
|
+
}
|
|
584
|
+
return state.slice()
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Notify user
|
|
589
|
+
if (failed) {
|
|
590
|
+
const unit = `row${count === 1 ? "" : "s"}`
|
|
591
|
+
get(notifications).error(`Failed to update ${failed} of ${count} ${unit}`)
|
|
592
|
+
} else if (updated.length) {
|
|
593
|
+
const unit = `row${updated.length === 1 ? "" : "s"}`
|
|
594
|
+
get(notifications).success(`Updated ${updated.length} ${unit}`)
|
|
455
595
|
}
|
|
456
596
|
}
|
|
457
597
|
|
|
@@ -516,19 +656,12 @@ export const createActions = context => {
|
|
|
516
656
|
get(fetch)?.nextPage()
|
|
517
657
|
}
|
|
518
658
|
|
|
519
|
-
// Checks if we have a row with a certain ID
|
|
520
|
-
const hasRow = id => {
|
|
521
|
-
if (id === NewRowID) {
|
|
522
|
-
return true
|
|
523
|
-
}
|
|
524
|
-
return get(rowLookupMap)[id] != null
|
|
525
|
-
}
|
|
526
|
-
|
|
527
659
|
// Cleans a row by removing any internal grid metadata from it.
|
|
528
660
|
// Call this before passing a row to any sort of external flow.
|
|
529
661
|
const cleanRow = row => {
|
|
530
662
|
let clone = { ...row }
|
|
531
663
|
delete clone.__idx
|
|
664
|
+
delete clone.__metadata
|
|
532
665
|
if (!get(hasBudibaseIdentifiers)) {
|
|
533
666
|
delete clone._id
|
|
534
667
|
}
|
|
@@ -541,16 +674,16 @@ export const createActions = context => {
|
|
|
541
674
|
actions: {
|
|
542
675
|
addRow,
|
|
543
676
|
duplicateRow,
|
|
544
|
-
|
|
677
|
+
bulkDuplicate,
|
|
545
678
|
updateValue,
|
|
546
679
|
applyRowChanges,
|
|
547
680
|
deleteRows,
|
|
548
|
-
hasRow,
|
|
549
681
|
loadNextPage,
|
|
550
682
|
refreshRow,
|
|
551
683
|
replaceRow,
|
|
552
684
|
refreshData,
|
|
553
685
|
cleanRow,
|
|
686
|
+
bulkUpdate,
|
|
554
687
|
},
|
|
555
688
|
},
|
|
556
689
|
}
|
|
@@ -569,10 +702,12 @@ export const initialise = context => {
|
|
|
569
702
|
// Wipe the row change cache when changing row
|
|
570
703
|
previousFocusedRowId.subscribe(id => {
|
|
571
704
|
if (id && !get(inProgressChanges)[id]) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
705
|
+
if (Object.keys(get(rowChangeCache)[id] || {}).length) {
|
|
706
|
+
rowChangeCache.update(state => {
|
|
707
|
+
delete state[id]
|
|
708
|
+
return state
|
|
709
|
+
})
|
|
710
|
+
}
|
|
576
711
|
}
|
|
577
712
|
})
|
|
578
713
|
|
|
@@ -581,12 +716,12 @@ export const initialise = context => {
|
|
|
581
716
|
if (!id) {
|
|
582
717
|
return
|
|
583
718
|
}
|
|
584
|
-
const {
|
|
719
|
+
const { rowId, field } = parseCellID(id)
|
|
585
720
|
const hasChanges = field in (get(rowChangeCache)[rowId] || {})
|
|
586
721
|
const hasErrors = validation.actions.rowHasErrors(rowId)
|
|
587
722
|
const isSavingChanges = get(inProgressChanges)[rowId]
|
|
588
723
|
if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
|
|
589
|
-
await rows.actions.applyRowChanges(rowId)
|
|
724
|
+
await rows.actions.applyRowChanges({ rowId })
|
|
590
725
|
}
|
|
591
726
|
})
|
|
592
727
|
}
|
|
@@ -16,8 +16,8 @@ export const createStores = () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
// Derive height and width as primitives to avoid wasted computation
|
|
19
|
-
const scrollTop = derived(scroll, $scroll => $scroll.top
|
|
20
|
-
const scrollLeft = derived(scroll, $scroll => $scroll.left
|
|
19
|
+
const scrollTop = derived(scroll, $scroll => Math.round($scroll.top))
|
|
20
|
+
const scrollLeft = derived(scroll, $scroll => Math.round($scroll.left))
|
|
21
21
|
|
|
22
22
|
return {
|
|
23
23
|
scroll,
|
|
@@ -30,7 +30,7 @@ export const deriveStores = context => {
|
|
|
30
30
|
const {
|
|
31
31
|
rows,
|
|
32
32
|
visibleColumns,
|
|
33
|
-
|
|
33
|
+
displayColumn,
|
|
34
34
|
rowHeight,
|
|
35
35
|
width,
|
|
36
36
|
height,
|
|
@@ -38,31 +38,32 @@ export const deriveStores = context => {
|
|
|
38
38
|
} = context
|
|
39
39
|
|
|
40
40
|
// Memoize store primitives
|
|
41
|
-
const
|
|
41
|
+
const stickyWidth = derived(displayColumn, $displayColumn => {
|
|
42
|
+
return ($displayColumn?.width || 0) + GutterWidth
|
|
43
|
+
})
|
|
42
44
|
|
|
43
45
|
// Derive horizontal limits
|
|
44
46
|
const contentWidth = derived(
|
|
45
|
-
[visibleColumns,
|
|
46
|
-
([$visibleColumns, $
|
|
47
|
-
let width = GutterWidth + $buttonColumnWidth
|
|
47
|
+
[visibleColumns, buttonColumnWidth],
|
|
48
|
+
([$visibleColumns, $buttonColumnWidth]) => {
|
|
49
|
+
let width = GutterWidth + Math.max($buttonColumnWidth, HPadding)
|
|
48
50
|
$visibleColumns.forEach(col => {
|
|
49
51
|
width += col.width
|
|
50
52
|
})
|
|
51
|
-
return width
|
|
52
|
-
}
|
|
53
|
-
0
|
|
53
|
+
return width
|
|
54
|
+
}
|
|
54
55
|
)
|
|
55
56
|
const screenWidth = derived(
|
|
56
|
-
[width,
|
|
57
|
-
([$width, $
|
|
58
|
-
|
|
57
|
+
[width, stickyWidth],
|
|
58
|
+
([$width, $stickyWidth]) => {
|
|
59
|
+
return $width + $stickyWidth
|
|
60
|
+
}
|
|
59
61
|
)
|
|
60
62
|
const maxScrollLeft = derived(
|
|
61
63
|
[contentWidth, screenWidth],
|
|
62
64
|
([$contentWidth, $screenWidth]) => {
|
|
63
|
-
return Math.max($contentWidth - $screenWidth, 0)
|
|
64
|
-
}
|
|
65
|
-
0
|
|
65
|
+
return Math.round(Math.max($contentWidth - $screenWidth, 0))
|
|
66
|
+
}
|
|
66
67
|
)
|
|
67
68
|
const showHScrollbar = derived(
|
|
68
69
|
[contentWidth, screenWidth],
|
|
@@ -80,13 +81,12 @@ export const deriveStores = context => {
|
|
|
80
81
|
height += ScrollBarSize * 2
|
|
81
82
|
}
|
|
82
83
|
return height
|
|
83
|
-
}
|
|
84
|
-
0
|
|
84
|
+
}
|
|
85
85
|
)
|
|
86
86
|
const maxScrollTop = derived(
|
|
87
87
|
[height, contentHeight],
|
|
88
|
-
([$height, $contentHeight]) =>
|
|
89
|
-
|
|
88
|
+
([$height, $contentHeight]) =>
|
|
89
|
+
Math.round(Math.max($contentHeight - $height, 0))
|
|
90
90
|
)
|
|
91
91
|
const showVScrollbar = derived(
|
|
92
92
|
[contentHeight, height],
|
|
@@ -96,6 +96,7 @@ export const deriveStores = context => {
|
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
|
+
stickyWidth,
|
|
99
100
|
contentHeight,
|
|
100
101
|
contentWidth,
|
|
101
102
|
screenWidth,
|
|
@@ -113,12 +114,13 @@ export const initialise = context => {
|
|
|
113
114
|
scroll,
|
|
114
115
|
bounds,
|
|
115
116
|
rowHeight,
|
|
116
|
-
|
|
117
|
+
stickyWidth,
|
|
117
118
|
scrollTop,
|
|
118
119
|
maxScrollTop,
|
|
119
120
|
scrollLeft,
|
|
120
121
|
maxScrollLeft,
|
|
121
122
|
buttonColumnWidth,
|
|
123
|
+
columnLookupMap,
|
|
122
124
|
} = context
|
|
123
125
|
|
|
124
126
|
// Ensure scroll state never goes invalid, which can happen when changing
|
|
@@ -186,15 +188,16 @@ export const initialise = context => {
|
|
|
186
188
|
|
|
187
189
|
// Ensure horizontal position is viewable
|
|
188
190
|
// Check horizontal position of columns next
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
if (!column) {
|
|
191
|
+
const { field } = parseCellID($focusedCellId)
|
|
192
|
+
const column = get(columnLookupMap)[field]
|
|
193
|
+
if (!column || column.primaryDisplay) {
|
|
193
194
|
return
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
// Ensure column is not cutoff on left edge
|
|
197
|
-
|
|
198
|
+
const $stickyWidth = get(stickyWidth)
|
|
199
|
+
let delta =
|
|
200
|
+
$scroll.left - column.__left + FocusedCellMinOffset + $stickyWidth
|
|
198
201
|
if (delta > 0) {
|
|
199
202
|
scroll.update(state => ({
|
|
200
203
|
...state,
|
|
@@ -205,10 +208,10 @@ export const initialise = context => {
|
|
|
205
208
|
// Ensure column is not cutoff on right edge
|
|
206
209
|
else {
|
|
207
210
|
const $buttonColumnWidth = get(buttonColumnWidth)
|
|
208
|
-
const rightEdge = column.
|
|
211
|
+
const rightEdge = column.__left + column.width
|
|
209
212
|
const rightBound =
|
|
210
213
|
$bounds.width + $scroll.left - FocusedCellMinOffset - $buttonColumnWidth
|
|
211
|
-
delta = rightEdge - rightBound
|
|
214
|
+
delta = rightEdge - rightBound - $stickyWidth
|
|
212
215
|
if (delta > 0) {
|
|
213
216
|
scroll.update(state => ({
|
|
214
217
|
...state,
|