@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,223 @@
1
+ <script>
2
+ import { getContext, onMount } from "svelte"
3
+ import { debounce } from "../../../utils/utils"
4
+ import { NewRowID } from "../lib/constants"
5
+
6
+ const {
7
+ enrichedRows,
8
+ focusedCellId,
9
+ visibleColumns,
10
+ focusedRow,
11
+ stickyColumn,
12
+ focusedCellAPI,
13
+ clipboard,
14
+ dispatch,
15
+ selectedRows,
16
+ } = getContext("grid")
17
+
18
+ // Global key listener which intercepts all key events
19
+ const handleKeyDown = e => {
20
+ // If nothing selected avoid processing further key presses
21
+ if (!$focusedCellId) {
22
+ if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
23
+ e.preventDefault()
24
+ focusFirstCell()
25
+ } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
26
+ e.preventDefault()
27
+ dispatch("add-row-inline")
28
+ } else if (e.key === "Delete" || e.key === "Backspace") {
29
+ if (Object.keys($selectedRows).length) {
30
+ dispatch("request-bulk-delete")
31
+ }
32
+ }
33
+ return
34
+ }
35
+
36
+ // Always intercept certain key presses
37
+ const api = $focusedCellAPI
38
+ if (e.key === "Escape") {
39
+ // By setting a tiny timeout here we can ensure that other listeners
40
+ // which depend on being able to read cell state on an escape keypress
41
+ // get a chance to observe the true state before we blur
42
+ if (api?.isActive()) {
43
+ setTimeout(api?.blur, 10)
44
+ } else {
45
+ $focusedCellId = null
46
+ }
47
+ return
48
+ } else if (e.key === "Tab") {
49
+ e.preventDefault()
50
+ api?.blur?.()
51
+ changeFocusedColumn(1)
52
+ return
53
+ }
54
+
55
+ // Pass the key event to the selected cell and let it decide whether to
56
+ // capture it or not
57
+ if (!api?.isReadonly()) {
58
+ const handled = api?.onKeyDown?.(e)
59
+ if (handled) {
60
+ return
61
+ }
62
+ }
63
+
64
+ // Avoid processing events sourced from modals
65
+ if (e.target?.closest?.(".spectrum-Modal")) {
66
+ return
67
+ }
68
+ e.preventDefault()
69
+
70
+ // Handle the key ourselves
71
+ if (e.metaKey || e.ctrlKey) {
72
+ switch (e.key) {
73
+ case "c":
74
+ clipboard.actions.copy()
75
+ break
76
+ case "v":
77
+ if (!api?.isReadonly()) {
78
+ clipboard.actions.paste()
79
+ }
80
+ break
81
+ case "Enter":
82
+ dispatch("add-row-inline")
83
+ }
84
+ } else {
85
+ switch (e.key) {
86
+ case "ArrowLeft":
87
+ changeFocusedColumn(-1)
88
+ break
89
+ case "ArrowRight":
90
+ changeFocusedColumn(1)
91
+ break
92
+ case "ArrowUp":
93
+ changeFocusedRow(-1)
94
+ break
95
+ case "ArrowDown":
96
+ changeFocusedRow(1)
97
+ break
98
+ case "Delete":
99
+ case "Backspace":
100
+ if (Object.keys($selectedRows).length) {
101
+ dispatch("request-bulk-delete")
102
+ } else {
103
+ deleteSelectedCell()
104
+ }
105
+ break
106
+ case "Enter":
107
+ focusCell()
108
+ break
109
+ case " ":
110
+ case "Space":
111
+ toggleSelectRow()
112
+ break
113
+ default:
114
+ startEnteringValue(e.key, e.which)
115
+ }
116
+ }
117
+ }
118
+
119
+ // Focuses the first cell in the grid
120
+ const focusFirstCell = () => {
121
+ const firstRow = $enrichedRows[0]
122
+ if (!firstRow) {
123
+ return
124
+ }
125
+ const firstColumn = $stickyColumn || $visibleColumns[0]
126
+ if (!firstColumn) {
127
+ return
128
+ }
129
+ focusedCellId.set(`${firstRow._id}-${firstColumn.name}`)
130
+ }
131
+
132
+ // Changes the focused cell by moving it left or right to a different column
133
+ const changeFocusedColumn = delta => {
134
+ if (!$focusedCellId) {
135
+ return
136
+ }
137
+ const cols = $visibleColumns
138
+ const split = $focusedCellId.split("-")
139
+ const columnName = split[1]
140
+ let newColumnName
141
+ if (columnName === $stickyColumn?.name) {
142
+ const index = delta - 1
143
+ newColumnName = cols[index]?.name
144
+ } else {
145
+ const index = cols.findIndex(col => col.name === columnName) + delta
146
+ if (index === -1) {
147
+ newColumnName = $stickyColumn?.name
148
+ } else {
149
+ newColumnName = cols[index]?.name
150
+ }
151
+ }
152
+ if (newColumnName) {
153
+ $focusedCellId = `${split[0]}-${newColumnName}`
154
+ }
155
+ }
156
+
157
+ // Changes the focused cell by moving it up or down to a new row
158
+ const changeFocusedRow = delta => {
159
+ if (!$focusedRow) {
160
+ return
161
+ }
162
+ const newRow = $enrichedRows[$focusedRow.__idx + delta]
163
+ if (newRow) {
164
+ const split = $focusedCellId.split("-")
165
+ $focusedCellId = `${newRow._id}-${split[1]}`
166
+ }
167
+ }
168
+
169
+ // Debounce to avoid holding down delete and spamming requests
170
+ const deleteSelectedCell = debounce(() => {
171
+ if ($focusedCellAPI?.isReadonly()) {
172
+ return
173
+ }
174
+ $focusedCellAPI.setValue(null)
175
+ }, 100)
176
+
177
+ // Focuses the current cell for editing
178
+ const focusCell = () => {
179
+ if ($focusedCellAPI?.isReadonly()) {
180
+ return
181
+ }
182
+ $focusedCellAPI?.focus?.()
183
+ }
184
+
185
+ // Utils to identify a key code
186
+ const keyCodeIsNumber = keyCode => keyCode >= 48 && keyCode <= 57
187
+ const keyCodeIsLetter = keyCode => keyCode >= 65 && keyCode <= 90
188
+
189
+ // Focuses the cell and starts entering a new value
190
+ const startEnteringValue = (key, keyCode) => {
191
+ if ($focusedCellAPI && !$focusedCellAPI.isReadonly()) {
192
+ const type = $focusedCellAPI.getType()
193
+ if (type === "number" && keyCodeIsNumber(keyCode)) {
194
+ $focusedCellAPI.setValue(parseInt(key))
195
+ $focusedCellAPI.focus()
196
+ } else if (
197
+ ["string", "barcodeqr", "longform"].includes(type) &&
198
+ (keyCodeIsLetter(keyCode) || keyCodeIsNumber(keyCode))
199
+ ) {
200
+ $focusedCellAPI.setValue(key)
201
+ $focusedCellAPI.focus()
202
+ }
203
+ }
204
+ }
205
+
206
+ const toggleSelectRow = () => {
207
+ const id = $focusedRow?._id
208
+ if (!id || id === NewRowID) {
209
+ return
210
+ }
211
+ selectedRows.update(state => {
212
+ state[id] = !state[id]
213
+ return state
214
+ })
215
+ }
216
+
217
+ onMount(() => {
218
+ document.addEventListener("keydown", handleKeyDown)
219
+ return () => {
220
+ document.removeEventListener("keydown", handleKeyDown)
221
+ }
222
+ })
223
+ </script>
@@ -0,0 +1,96 @@
1
+ <script>
2
+ import { clickOutside, Menu, MenuItem, notifications } from "@budibase/bbui"
3
+ import { getContext } from "svelte"
4
+
5
+ const {
6
+ focusedRow,
7
+ menu,
8
+ rows,
9
+ columns,
10
+ focusedCellId,
11
+ stickyColumn,
12
+ config,
13
+ copiedCell,
14
+ clipboard,
15
+ dispatch,
16
+ focusedCellAPI,
17
+ } = getContext("grid")
18
+
19
+ $: style = makeStyle($menu)
20
+
21
+ const makeStyle = menu => {
22
+ return `left:${menu.left}px; top:${menu.top}px;`
23
+ }
24
+
25
+ const deleteRow = () => {
26
+ rows.actions.deleteRows([$focusedRow])
27
+ menu.actions.close()
28
+ notifications.success("Deleted 1 row")
29
+ }
30
+
31
+ const duplicate = async () => {
32
+ menu.actions.close()
33
+ const newRow = await rows.actions.duplicateRow($focusedRow)
34
+ if (newRow) {
35
+ const column = $stickyColumn?.name || $columns[0].name
36
+ $focusedCellId = `${newRow._id}-${column}`
37
+ }
38
+ }
39
+ </script>
40
+
41
+ {#if $menu.visible}
42
+ <div class="menu" {style} use:clickOutside={() => menu.actions.close()}>
43
+ <Menu>
44
+ <MenuItem
45
+ icon="Copy"
46
+ on:click={clipboard.actions.copy}
47
+ on:click={menu.actions.close}
48
+ >
49
+ Copy
50
+ </MenuItem>
51
+ <MenuItem
52
+ icon="Paste"
53
+ disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
54
+ on:click={clipboard.actions.paste}
55
+ on:click={menu.actions.close}
56
+ >
57
+ Paste
58
+ </MenuItem>
59
+ <MenuItem
60
+ icon="Maximize"
61
+ disabled={!$config.allowEditRows}
62
+ on:click={() => dispatch("edit-row", $focusedRow)}
63
+ on:click={menu.actions.close}
64
+ >
65
+ Edit row in modal
66
+ </MenuItem>
67
+ <MenuItem
68
+ icon="Duplicate"
69
+ disabled={!$config.allowAddRows}
70
+ on:click={duplicate}
71
+ >
72
+ Duplicate row
73
+ </MenuItem>
74
+ <MenuItem
75
+ icon="Delete"
76
+ disabled={!$config.allowDeleteRows}
77
+ on:click={deleteRow}
78
+ >
79
+ Delete row
80
+ </MenuItem>
81
+ </Menu>
82
+ </div>
83
+ {/if}
84
+
85
+ <style>
86
+ .menu {
87
+ position: absolute;
88
+ background: var(--cell-background);
89
+ border: 1px solid var(--spectrum-global-color-gray-300);
90
+ width: 180px;
91
+ border-radius: 4px;
92
+ display: flex;
93
+ flex-direction: column;
94
+ box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
95
+ }
96
+ </style>
@@ -0,0 +1,63 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import GridScrollWrapper from "../layout/GridScrollWrapper.svelte"
4
+ import { DefaultRowHeight, GutterWidth } from "../lib/constants"
5
+
6
+ const {
7
+ isReordering,
8
+ reorder,
9
+ visibleColumns,
10
+ stickyColumn,
11
+ rowHeight,
12
+ renderedRows,
13
+ scrollLeft,
14
+ } = getContext("grid")
15
+
16
+ $: targetColumn = $reorder.targetColumn
17
+ $: minLeft = GutterWidth + ($stickyColumn?.width || 0)
18
+ $: left = getLeft(targetColumn, $stickyColumn, $visibleColumns, $scrollLeft)
19
+ $: height = $rowHeight * $renderedRows.length + DefaultRowHeight
20
+ $: style = `left:${left}px; height:${height}px;`
21
+ $: visible = $isReordering && left >= minLeft
22
+
23
+ const getLeft = (targetColumn, stickyColumn, visibleColumns, scrollLeft) => {
24
+ let left = GutterWidth + (stickyColumn?.width || 0) - scrollLeft
25
+
26
+ // If this is not the sticky column, add additional left space
27
+ if (targetColumn !== stickyColumn?.name) {
28
+ const column = visibleColumns.find(x => x.name === targetColumn)
29
+ if (!column) {
30
+ return left
31
+ }
32
+ left += column.left + column.width
33
+ }
34
+
35
+ return left
36
+ }
37
+ </script>
38
+
39
+ {#if visible}
40
+ <div class="reorder-wrapper">
41
+ <GridScrollWrapper scrollVertically>
42
+ <div class="reorder-overlay" {style} />
43
+ </GridScrollWrapper>
44
+ </div>
45
+ {/if}
46
+
47
+ <style>
48
+ .reorder-wrapper {
49
+ position: absolute;
50
+ top: 0;
51
+ left: 0;
52
+ height: 100%;
53
+ width: 100%;
54
+ pointer-events: none;
55
+ }
56
+ .reorder-overlay {
57
+ position: absolute;
58
+ top: 0;
59
+ width: 2px;
60
+ background: var(--spectrum-global-color-blue-400);
61
+ margin-left: -2px;
62
+ }
63
+ </style>
@@ -0,0 +1,70 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import { GutterWidth } from "../lib/constants"
4
+
5
+ const {
6
+ columns,
7
+ resize,
8
+ renderedColumns,
9
+ stickyColumn,
10
+ isReordering,
11
+ scrollLeft,
12
+ } = getContext("grid")
13
+
14
+ $: cutoff = $scrollLeft + GutterWidth + ($columns[0]?.width || 0)
15
+ $: offset = GutterWidth + ($stickyColumn?.width || 0)
16
+ $: activeColumn = $resize.column
17
+
18
+ const getStyle = (column, offset, scrollLeft) => {
19
+ const left = offset + column.left + column.width - scrollLeft
20
+ return `left:${left}px;`
21
+ }
22
+ </script>
23
+
24
+ {#if !$isReordering}
25
+ {#if $stickyColumn}
26
+ <div
27
+ class="resize-slider"
28
+ class:visible={activeColumn === $stickyColumn.name}
29
+ on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
30
+ on:dblclick={() => resize.actions.resetSize($stickyColumn)}
31
+ style="left:{GutterWidth + $stickyColumn.width}px;"
32
+ >
33
+ <div class="resize-indicator" />
34
+ </div>
35
+ {/if}
36
+ {#each $renderedColumns as column}
37
+ <div
38
+ class="resize-slider"
39
+ class:visible={activeColumn === column.name}
40
+ on:mousedown={e => resize.actions.startResizing(column, e)}
41
+ on:dblclick={() => resize.actions.resetSize(column)}
42
+ style={getStyle(column, offset, $scrollLeft)}
43
+ >
44
+ <div class="resize-indicator" />
45
+ </div>
46
+ {/each}
47
+ {/if}
48
+
49
+ <style>
50
+ .resize-slider {
51
+ position: absolute;
52
+ top: 0;
53
+ height: var(--default-row-height);
54
+ opacity: 0;
55
+ padding: 0 8px;
56
+ transform: translateX(-50%);
57
+ user-select: none;
58
+ }
59
+ .resize-slider:hover,
60
+ .resize-slider.visible {
61
+ cursor: col-resize;
62
+ opacity: 1;
63
+ }
64
+ .resize-indicator {
65
+ margin-left: -1px;
66
+ width: 2px;
67
+ height: 100%;
68
+ background: var(--spectrum-global-color-blue-400);
69
+ }
70
+ </style>
@@ -0,0 +1,121 @@
1
+ <script>
2
+ import { getContext } from "svelte"
3
+ import { domDebounce } from "../../../utils/utils"
4
+ import { DefaultRowHeight, ScrollBarSize } from "../lib/constants"
5
+
6
+ const {
7
+ scroll,
8
+ contentHeight,
9
+ maxScrollTop,
10
+ contentWidth,
11
+ maxScrollLeft,
12
+ screenWidth,
13
+ showHScrollbar,
14
+ showVScrollbar,
15
+ scrollLeft,
16
+ scrollTop,
17
+ height,
18
+ } = getContext("grid")
19
+
20
+ // State for dragging bars
21
+ let initialMouse
22
+ let initialScroll
23
+
24
+ // Calculate V scrollbar size and offset
25
+ // Terminology is the same for both axes:
26
+ // renderX - the space available to render the bar in, edge to edge
27
+ // availX - the space available to render the bar in, until the edge
28
+ $: renderHeight = $height - 2 * ScrollBarSize
29
+ $: barHeight = Math.max(50, ($height / $contentHeight) * renderHeight)
30
+ $: availHeight = renderHeight - barHeight
31
+ $: barTop =
32
+ ScrollBarSize +
33
+ DefaultRowHeight +
34
+ availHeight * ($scrollTop / $maxScrollTop)
35
+
36
+ // Calculate H scrollbar size and offset
37
+ $: renderWidth = $screenWidth - 2 * ScrollBarSize
38
+ $: barWidth = Math.max(50, ($screenWidth / $contentWidth) * renderWidth)
39
+ $: availWidth = renderWidth - barWidth
40
+ $: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
41
+
42
+ // V scrollbar drag handlers
43
+ const startVDragging = e => {
44
+ e.preventDefault()
45
+ initialMouse = e.clientY
46
+ initialScroll = $scrollTop
47
+ document.addEventListener("mousemove", moveVDragging)
48
+ document.addEventListener("mouseup", stopVDragging)
49
+ }
50
+ const moveVDragging = domDebounce(e => {
51
+ const delta = e.clientY - initialMouse
52
+ const weight = delta / availHeight
53
+ const newScrollTop = initialScroll + weight * $maxScrollTop
54
+ scroll.update(state => ({
55
+ ...state,
56
+ top: Math.max(0, Math.min(newScrollTop, $maxScrollTop)),
57
+ }))
58
+ })
59
+ const stopVDragging = () => {
60
+ document.removeEventListener("mousemove", moveVDragging)
61
+ document.removeEventListener("mouseup", stopVDragging)
62
+ }
63
+
64
+ // H scrollbar drag handlers
65
+ const startHDragging = e => {
66
+ e.preventDefault()
67
+ initialMouse = e.clientX
68
+ initialScroll = $scrollLeft
69
+ document.addEventListener("mousemove", moveHDragging)
70
+ document.addEventListener("mouseup", stopHDragging)
71
+ }
72
+ const moveHDragging = domDebounce(e => {
73
+ const delta = e.clientX - initialMouse
74
+ const weight = delta / availWidth
75
+ const newScrollLeft = initialScroll + weight * $maxScrollLeft
76
+ scroll.update(state => ({
77
+ ...state,
78
+ left: Math.max(0, Math.min(newScrollLeft, $maxScrollLeft)),
79
+ }))
80
+ })
81
+ const stopHDragging = () => {
82
+ document.removeEventListener("mousemove", moveHDragging)
83
+ document.removeEventListener("mouseup", stopHDragging)
84
+ }
85
+ </script>
86
+
87
+ {#if $showVScrollbar}
88
+ <div
89
+ class="v-scrollbar"
90
+ style="--size:{ScrollBarSize}px; top:{barTop}px; height:{barHeight}px;"
91
+ on:mousedown={startVDragging}
92
+ />
93
+ {/if}
94
+ {#if $showHScrollbar}
95
+ <div
96
+ class="h-scrollbar"
97
+ style="--size:{ScrollBarSize}px; left:{barLeft}px; width:{barWidth}px;"
98
+ on:mousedown={startHDragging}
99
+ />
100
+ {/if}
101
+
102
+ <style>
103
+ div {
104
+ position: absolute;
105
+ background: var(--spectrum-global-color-gray-500);
106
+ opacity: 0.7;
107
+ border-radius: 4px;
108
+ transition: opacity 130ms ease-out;
109
+ }
110
+ div:hover {
111
+ opacity: 1;
112
+ }
113
+ .v-scrollbar {
114
+ width: var(--size);
115
+ right: var(--size);
116
+ }
117
+ .h-scrollbar {
118
+ height: var(--size);
119
+ bottom: var(--size);
120
+ }
121
+ </style>
@@ -0,0 +1,16 @@
1
+ import { derived, writable } from "svelte/store"
2
+
3
+ export const createStores = () => {
4
+ const bounds = writable({
5
+ left: 0,
6
+ top: 0,
7
+ width: 0,
8
+ height: 0,
9
+ })
10
+
11
+ // Derive height and width as primitives to avoid wasted computation
12
+ const width = derived(bounds, $bounds => $bounds.width, 0)
13
+ const height = derived(bounds, $bounds => $bounds.height, 0)
14
+
15
+ return { bounds, height, width }
16
+ }
@@ -0,0 +1,34 @@
1
+ import { writable, get } from "svelte/store"
2
+
3
+ export const createStores = () => {
4
+ const copiedCell = writable(null)
5
+
6
+ return {
7
+ copiedCell,
8
+ }
9
+ }
10
+
11
+ export const deriveStores = context => {
12
+ const { copiedCell, focusedCellAPI } = context
13
+
14
+ const copy = () => {
15
+ copiedCell.set(get(focusedCellAPI)?.getValue())
16
+ }
17
+
18
+ const paste = () => {
19
+ const $copiedCell = get(copiedCell)
20
+ const $focusedCellAPI = get(focusedCellAPI)
21
+ if ($copiedCell != null && $focusedCellAPI) {
22
+ $focusedCellAPI.setValue($copiedCell)
23
+ }
24
+ }
25
+
26
+ return {
27
+ clipboard: {
28
+ actions: {
29
+ copy,
30
+ paste,
31
+ },
32
+ },
33
+ }
34
+ }