@budibase/frontend-core 2.5.8 → 2.5.10-alpha.0

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 (68) 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 +91 -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 +165 -0
  9. package/src/components/grid/cells/GutterCell.svelte +127 -0
  10. package/src/components/grid/cells/HeaderCell.svelte +248 -0
  11. package/src/components/grid/cells/JSONCell.svelte +36 -0
  12. package/src/components/grid/cells/LongFormCell.svelte +122 -0
  13. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  14. package/src/components/grid/cells/NumberCell.svelte +14 -0
  15. package/src/components/grid/cells/OptionsCell.svelte +249 -0
  16. package/src/components/grid/cells/RelationshipCell.svelte +518 -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 +129 -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 +59 -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/KeyboardShortcut.svelte +53 -0
  34. package/src/components/grid/layout/NewRow.svelte +334 -0
  35. package/src/components/grid/layout/RowCount.svelte +9 -0
  36. package/src/components/grid/layout/StickyColumn.svelte +176 -0
  37. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  38. package/src/components/grid/lib/constants.js +14 -0
  39. package/src/components/grid/lib/events.js +29 -0
  40. package/src/components/grid/lib/renderers.js +29 -0
  41. package/src/components/grid/lib/utils.js +26 -0
  42. package/src/components/grid/lib/websocket.js +63 -0
  43. package/src/components/grid/overlays/KeyboardManager.svelte +232 -0
  44. package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
  45. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  46. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  47. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  48. package/src/components/grid/stores/bounds.js +16 -0
  49. package/src/components/grid/stores/clipboard.js +34 -0
  50. package/src/components/grid/stores/columns.js +206 -0
  51. package/src/components/grid/stores/index.js +48 -0
  52. package/src/components/grid/stores/menu.js +49 -0
  53. package/src/components/grid/stores/pagination.js +24 -0
  54. package/src/components/grid/stores/reorder.js +155 -0
  55. package/src/components/grid/stores/resize.js +124 -0
  56. package/src/components/grid/stores/rows.js +467 -0
  57. package/src/components/grid/stores/scroll.js +201 -0
  58. package/src/components/grid/stores/ui.js +159 -0
  59. package/src/components/grid/stores/users.js +115 -0
  60. package/src/components/grid/stores/validation.js +45 -0
  61. package/src/components/grid/stores/viewport.js +145 -0
  62. package/src/components/index.js +1 -0
  63. package/src/constants.js +1 -0
  64. package/src/fetch/DataFetch.js +27 -21
  65. package/src/fetch/QueryFetch.js +1 -1
  66. package/src/themes/midnight.css +3 -0
  67. package/src/themes/nord.css +3 -0
  68. package/src/utils/utils.js +20 -0
@@ -0,0 +1,232 @@
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
+ const ignoredOriginSelectors = [
19
+ ".spectrum-Modal",
20
+ "#builder-side-panel-container",
21
+ ]
22
+
23
+ // Global key listener which intercepts all key events
24
+ const handleKeyDown = e => {
25
+ // Avoid processing events sourced from certain origins
26
+ if (e.target?.closest) {
27
+ for (let selector of ignoredOriginSelectors) {
28
+ if (e.target.closest(selector)) {
29
+ return
30
+ }
31
+ }
32
+ }
33
+
34
+ // If nothing selected avoid processing further key presses
35
+ if (!$focusedCellId) {
36
+ if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
37
+ e.preventDefault()
38
+ focusFirstCell()
39
+ } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
40
+ e.preventDefault()
41
+ dispatch("add-row-inline")
42
+ } else if (e.key === "Delete" || e.key === "Backspace") {
43
+ if (Object.keys($selectedRows).length) {
44
+ dispatch("request-bulk-delete")
45
+ }
46
+ }
47
+ return
48
+ }
49
+
50
+ // Always intercept certain key presses
51
+ const api = $focusedCellAPI
52
+ if (e.key === "Escape") {
53
+ // By setting a tiny timeout here we can ensure that other listeners
54
+ // which depend on being able to read cell state on an escape keypress
55
+ // get a chance to observe the true state before we blur
56
+ if (api?.isActive()) {
57
+ setTimeout(api?.blur, 10)
58
+ } else {
59
+ $focusedCellId = null
60
+ }
61
+ return
62
+ } else if (e.key === "Tab") {
63
+ e.preventDefault()
64
+ api?.blur?.()
65
+ changeFocusedColumn(1)
66
+ return
67
+ }
68
+
69
+ // Pass the key event to the selected cell and let it decide whether to
70
+ // capture it or not
71
+ if (!api?.isReadonly()) {
72
+ const handled = api?.onKeyDown?.(e)
73
+ if (handled) {
74
+ return
75
+ }
76
+ }
77
+ e.preventDefault()
78
+
79
+ // Handle the key ourselves
80
+ if (e.metaKey || e.ctrlKey) {
81
+ switch (e.key) {
82
+ case "c":
83
+ clipboard.actions.copy()
84
+ break
85
+ case "v":
86
+ if (!api?.isReadonly()) {
87
+ clipboard.actions.paste()
88
+ }
89
+ break
90
+ case "Enter":
91
+ dispatch("add-row-inline")
92
+ }
93
+ } else {
94
+ switch (e.key) {
95
+ case "ArrowLeft":
96
+ changeFocusedColumn(-1)
97
+ break
98
+ case "ArrowRight":
99
+ changeFocusedColumn(1)
100
+ break
101
+ case "ArrowUp":
102
+ changeFocusedRow(-1)
103
+ break
104
+ case "ArrowDown":
105
+ changeFocusedRow(1)
106
+ break
107
+ case "Delete":
108
+ case "Backspace":
109
+ if (Object.keys($selectedRows).length) {
110
+ dispatch("request-bulk-delete")
111
+ } else {
112
+ deleteSelectedCell()
113
+ }
114
+ break
115
+ case "Enter":
116
+ focusCell()
117
+ break
118
+ case " ":
119
+ case "Space":
120
+ toggleSelectRow()
121
+ break
122
+ default:
123
+ startEnteringValue(e.key, e.which)
124
+ }
125
+ }
126
+ }
127
+
128
+ // Focuses the first cell in the grid
129
+ const focusFirstCell = () => {
130
+ const firstRow = $enrichedRows[0]
131
+ if (!firstRow) {
132
+ return
133
+ }
134
+ const firstColumn = $stickyColumn || $visibleColumns[0]
135
+ if (!firstColumn) {
136
+ return
137
+ }
138
+ focusedCellId.set(`${firstRow._id}-${firstColumn.name}`)
139
+ }
140
+
141
+ // Changes the focused cell by moving it left or right to a different column
142
+ const changeFocusedColumn = delta => {
143
+ if (!$focusedCellId) {
144
+ return
145
+ }
146
+ const cols = $visibleColumns
147
+ const split = $focusedCellId.split("-")
148
+ const columnName = split[1]
149
+ let newColumnName
150
+ if (columnName === $stickyColumn?.name) {
151
+ const index = delta - 1
152
+ newColumnName = cols[index]?.name
153
+ } else {
154
+ const index = cols.findIndex(col => col.name === columnName) + delta
155
+ if (index === -1) {
156
+ newColumnName = $stickyColumn?.name
157
+ } else {
158
+ newColumnName = cols[index]?.name
159
+ }
160
+ }
161
+ if (newColumnName) {
162
+ $focusedCellId = `${split[0]}-${newColumnName}`
163
+ }
164
+ }
165
+
166
+ // Changes the focused cell by moving it up or down to a new row
167
+ const changeFocusedRow = delta => {
168
+ if (!$focusedRow) {
169
+ return
170
+ }
171
+ const newRow = $enrichedRows[$focusedRow.__idx + delta]
172
+ if (newRow) {
173
+ const split = $focusedCellId.split("-")
174
+ $focusedCellId = `${newRow._id}-${split[1]}`
175
+ }
176
+ }
177
+
178
+ // Debounce to avoid holding down delete and spamming requests
179
+ const deleteSelectedCell = debounce(() => {
180
+ if ($focusedCellAPI?.isReadonly()) {
181
+ return
182
+ }
183
+ $focusedCellAPI.setValue(null)
184
+ }, 100)
185
+
186
+ // Focuses the current cell for editing
187
+ const focusCell = () => {
188
+ if ($focusedCellAPI?.isReadonly()) {
189
+ return
190
+ }
191
+ $focusedCellAPI?.focus?.()
192
+ }
193
+
194
+ // Utils to identify a key code
195
+ const keyCodeIsNumber = keyCode => keyCode >= 48 && keyCode <= 57
196
+ const keyCodeIsLetter = keyCode => keyCode >= 65 && keyCode <= 90
197
+
198
+ // Focuses the cell and starts entering a new value
199
+ const startEnteringValue = (key, keyCode) => {
200
+ if ($focusedCellAPI && !$focusedCellAPI.isReadonly()) {
201
+ const type = $focusedCellAPI.getType()
202
+ if (type === "number" && keyCodeIsNumber(keyCode)) {
203
+ $focusedCellAPI.setValue(parseInt(key))
204
+ $focusedCellAPI.focus()
205
+ } else if (
206
+ ["string", "barcodeqr", "longform"].includes(type) &&
207
+ (keyCodeIsLetter(keyCode) || keyCodeIsNumber(keyCode))
208
+ ) {
209
+ $focusedCellAPI.setValue(key)
210
+ $focusedCellAPI.focus()
211
+ }
212
+ }
213
+ }
214
+
215
+ const toggleSelectRow = () => {
216
+ const id = $focusedRow?._id
217
+ if (!id || id === NewRowID) {
218
+ return
219
+ }
220
+ selectedRows.update(state => {
221
+ state[id] = !state[id]
222
+ return state
223
+ })
224
+ }
225
+
226
+ onMount(() => {
227
+ document.addEventListener("keydown", handleKeyDown)
228
+ return () => {
229
+ document.removeEventListener("keydown", handleKeyDown)
230
+ }
231
+ })
232
+ </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
+ }