@budibase/frontend-core 2.5.6-alpha.5 → 2.5.6-alpha.7

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 (63) hide show
  1. package/package.json +6 -4
  2. package/src/components/grid/cells/AttachmentCell.svelte +153 -0
  3. package/src/components/grid/cells/BooleanCell.svelte +44 -0
  4. package/src/components/grid/cells/DataCell.svelte +86 -0
  5. package/src/components/grid/cells/DateCell.svelte +77 -0
  6. package/src/components/grid/cells/FormulaCell.svelte +5 -0
  7. package/src/components/grid/cells/GridCell.svelte +159 -0
  8. package/src/components/grid/cells/HeaderCell.svelte +237 -0
  9. package/src/components/grid/cells/JSONCell.svelte +36 -0
  10. package/src/components/grid/cells/LongFormCell.svelte +117 -0
  11. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  12. package/src/components/grid/cells/NumberCell.svelte +7 -0
  13. package/src/components/grid/cells/OptionsCell.svelte +239 -0
  14. package/src/components/grid/cells/RelationshipCell.svelte +503 -0
  15. package/src/components/grid/cells/TextCell.svelte +110 -0
  16. package/src/components/grid/controls/AddColumnButton.svelte +16 -0
  17. package/src/components/grid/controls/AddRowButton.svelte +18 -0
  18. package/src/components/grid/controls/BetaButton.svelte +46 -0
  19. package/src/components/grid/controls/DeleteButton.svelte +72 -0
  20. package/src/components/grid/controls/HideColumnsButton.svelte +91 -0
  21. package/src/components/grid/controls/RowHeightButton.svelte +70 -0
  22. package/src/components/grid/controls/SortButton.svelte +107 -0
  23. package/src/components/grid/index.js +1 -0
  24. package/src/components/grid/layout/Avatar.svelte +24 -0
  25. package/src/components/grid/layout/Grid.svelte +250 -0
  26. package/src/components/grid/layout/GridBody.svelte +38 -0
  27. package/src/components/grid/layout/GridRow.svelte +57 -0
  28. package/src/components/grid/layout/GridScrollWrapper.svelte +84 -0
  29. package/src/components/grid/layout/HeaderRow.svelte +30 -0
  30. package/src/components/grid/layout/NewRowTop.svelte +247 -0
  31. package/src/components/grid/layout/RowCount.svelte +9 -0
  32. package/src/components/grid/layout/StickyColumn.svelte +245 -0
  33. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  34. package/src/components/grid/lib/constants.js +11 -0
  35. package/src/components/grid/lib/events.js +29 -0
  36. package/src/components/grid/lib/renderers.js +29 -0
  37. package/src/components/grid/lib/utils.js +26 -0
  38. package/src/components/grid/lib/websocket.js +63 -0
  39. package/src/components/grid/overlays/KeyboardManager.svelte +180 -0
  40. package/src/components/grid/overlays/MenuOverlay.svelte +95 -0
  41. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  42. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  43. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  44. package/src/components/grid/stores/bounds.js +16 -0
  45. package/src/components/grid/stores/clipboard.js +34 -0
  46. package/src/components/grid/stores/columns.js +206 -0
  47. package/src/components/grid/stores/index.js +48 -0
  48. package/src/components/grid/stores/menu.js +49 -0
  49. package/src/components/grid/stores/pagination.js +24 -0
  50. package/src/components/grid/stores/reorder.js +155 -0
  51. package/src/components/grid/stores/resize.js +124 -0
  52. package/src/components/grid/stores/rows.js +462 -0
  53. package/src/components/grid/stores/scroll.js +201 -0
  54. package/src/components/grid/stores/ui.js +168 -0
  55. package/src/components/grid/stores/users.js +115 -0
  56. package/src/components/grid/stores/validation.js +45 -0
  57. package/src/components/grid/stores/viewport.js +141 -0
  58. package/src/components/index.js +1 -0
  59. package/src/fetch/DataFetch.js +27 -21
  60. package/src/fetch/QueryFetch.js +1 -1
  61. package/src/themes/midnight.css +3 -0
  62. package/src/themes/nord.css +3 -0
  63. package/src/utils/utils.js +20 -0
@@ -0,0 +1,26 @@
1
+ export const getColor = (idx, opacity = 0.3) => {
2
+ if (idx == null || idx === -1) {
3
+ return null
4
+ }
5
+ return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
6
+ }
7
+
8
+ const TypeIconMap = {
9
+ text: "Text",
10
+ options: "Dropdown",
11
+ datetime: "Date",
12
+ barcodeqr: "Camera",
13
+ longform: "TextAlignLeft",
14
+ array: "Dropdown",
15
+ number: "123",
16
+ boolean: "Boolean",
17
+ attachment: "AppleFiles",
18
+ link: "Link",
19
+ formula: "Calculator",
20
+ json: "Brackets",
21
+ }
22
+
23
+ export const getColumnIcon = column => {
24
+ const type = column.schema.type
25
+ return TypeIconMap[type] || "Text"
26
+ }
@@ -0,0 +1,63 @@
1
+ import { get } from "svelte/store"
2
+ import { io } from "socket.io-client"
3
+
4
+ export const createWebsocket = context => {
5
+ const { rows, tableId, users, userId, focusedCellId } = context
6
+
7
+ // Determine connection info
8
+ const tls = location.protocol === "https:"
9
+ const proto = tls ? "wss:" : "ws:"
10
+ const host = location.hostname
11
+ const port = location.port || (tls ? 443 : 80)
12
+ const socket = io(`${proto}//${host}:${port}`, {
13
+ path: "/socket/grid",
14
+ // Cap reconnection attempts to 3 (total of 15 seconds before giving up)
15
+ reconnectionAttempts: 3,
16
+ // Delay reconnection attempt by 5 seconds
17
+ reconnectionDelay: 5000,
18
+ reconnectionDelayMax: 5000,
19
+ // Timeout after 4 seconds so we never stack requests
20
+ timeout: 4000,
21
+ })
22
+
23
+ const connectToTable = tableId => {
24
+ if (!socket.connected) {
25
+ return
26
+ }
27
+ // Identify which table we are editing
28
+ socket.emit("select-table", tableId, response => {
29
+ // handle initial connection info
30
+ users.set(response.users)
31
+ userId.set(response.id)
32
+ })
33
+ }
34
+
35
+ // Event handlers
36
+ socket.on("connect", () => {
37
+ connectToTable(get(tableId))
38
+ })
39
+ socket.on("row-update", data => {
40
+ if (data.id) {
41
+ rows.actions.refreshRow(data.id)
42
+ }
43
+ })
44
+ socket.on("user-update", user => {
45
+ users.actions.updateUser(user)
46
+ })
47
+ socket.on("user-disconnect", user => {
48
+ users.actions.removeUser(user)
49
+ })
50
+ socket.on("connect_error", err => {
51
+ console.log("Failed to connect to grid websocket:", err.message)
52
+ })
53
+
54
+ // Change websocket connection when table changes
55
+ tableId.subscribe(connectToTable)
56
+
57
+ // Notify selected cell changes
58
+ focusedCellId.subscribe($focusedCellId => {
59
+ socket.emit("select-cell", $focusedCellId)
60
+ })
61
+
62
+ return () => socket?.disconnect()
63
+ }
@@ -0,0 +1,180 @@
1
+ <script>
2
+ import { getContext, onMount } from "svelte"
3
+ import { debounce } from "../../../utils/utils"
4
+
5
+ const {
6
+ enrichedRows,
7
+ focusedCellId,
8
+ visibleColumns,
9
+ focusedRow,
10
+ stickyColumn,
11
+ focusedCellAPI,
12
+ clipboard,
13
+ } = getContext("grid")
14
+
15
+ // Global key listener which intercepts all key events
16
+ const handleKeyDown = e => {
17
+ // If nothing selected avoid processing further key presses
18
+ if (!$focusedCellId) {
19
+ if (e.key === "Tab") {
20
+ e.preventDefault()
21
+ focusFirstCell()
22
+ }
23
+ return
24
+ }
25
+
26
+ // Always intercept certain key presses
27
+ const api = $focusedCellAPI
28
+ if (e.key === "Escape") {
29
+ api?.blur?.()
30
+ } else if (e.key === "Tab") {
31
+ api?.blur?.()
32
+ changeFocusedColumn(1)
33
+ }
34
+
35
+ // Pass the key event to the selected cell and let it decide whether to
36
+ // capture it or not
37
+ if (!api?.isReadonly()) {
38
+ const handled = api?.onKeyDown?.(e)
39
+ if (handled) {
40
+ return
41
+ }
42
+ }
43
+
44
+ // Avoid processing events sourced from modals
45
+ if (e.target?.closest?.(".spectrum-Modal")) {
46
+ return
47
+ }
48
+ e.preventDefault()
49
+
50
+ // Handle the key ourselves
51
+ if (e.metaKey || e.ctrlKey) {
52
+ switch (e.key) {
53
+ case "c":
54
+ clipboard.actions.copy()
55
+ break
56
+ case "v":
57
+ clipboard.actions.paste()
58
+ break
59
+ }
60
+ } else {
61
+ switch (e.key) {
62
+ case "ArrowLeft":
63
+ changeFocusedColumn(-1)
64
+ break
65
+ case "ArrowRight":
66
+ changeFocusedColumn(1)
67
+ break
68
+ case "ArrowUp":
69
+ changeFocusedRow(-1)
70
+ break
71
+ case "ArrowDown":
72
+ changeFocusedRow(1)
73
+ break
74
+ case "Delete":
75
+ case "Backspace":
76
+ deleteSelectedCell()
77
+ break
78
+ case "Enter":
79
+ focusCell()
80
+ break
81
+ default:
82
+ startEnteringValue(e.key, e.which)
83
+ }
84
+ }
85
+ }
86
+
87
+ // Focuses the first cell in the grid
88
+ const focusFirstCell = () => {
89
+ const firstRow = $enrichedRows[0]
90
+ if (!firstRow) {
91
+ return
92
+ }
93
+ const firstColumn = $stickyColumn || $visibleColumns[0]
94
+ if (!firstColumn) {
95
+ return
96
+ }
97
+ focusedCellId.set(`${firstRow._id}-${firstColumn.name}`)
98
+ }
99
+
100
+ // Changes the focused cell by moving it left or right to a different column
101
+ const changeFocusedColumn = delta => {
102
+ if (!$focusedCellId) {
103
+ return
104
+ }
105
+ const cols = $visibleColumns
106
+ const split = $focusedCellId.split("-")
107
+ const columnName = split[1]
108
+ let newColumnName
109
+ if (columnName === $stickyColumn?.name) {
110
+ const index = delta - 1
111
+ newColumnName = cols[index]?.name
112
+ } else {
113
+ const index = cols.findIndex(col => col.name === columnName) + delta
114
+ if (index === -1) {
115
+ newColumnName = $stickyColumn?.name
116
+ } else {
117
+ newColumnName = cols[index]?.name
118
+ }
119
+ }
120
+ if (newColumnName) {
121
+ $focusedCellId = `${split[0]}-${newColumnName}`
122
+ }
123
+ }
124
+
125
+ // Changes the focused cell by moving it up or down to a new row
126
+ const changeFocusedRow = delta => {
127
+ if (!$focusedRow) {
128
+ return
129
+ }
130
+ const newRow = $enrichedRows[$focusedRow.__idx + delta]
131
+ if (newRow) {
132
+ const split = $focusedCellId.split("-")
133
+ $focusedCellId = `${newRow._id}-${split[1]}`
134
+ }
135
+ }
136
+
137
+ // Debounce to avoid holding down delete and spamming requests
138
+ const deleteSelectedCell = debounce(() => {
139
+ if ($focusedCellAPI?.isReadonly()) {
140
+ return
141
+ }
142
+ $focusedCellAPI.setValue(null)
143
+ }, 100)
144
+
145
+ // Focuses the current cell for editing
146
+ const focusCell = () => {
147
+ if ($focusedCellAPI?.isReadonly()) {
148
+ return
149
+ }
150
+ $focusedCellAPI?.focus?.()
151
+ }
152
+
153
+ // Utils to identify a key code
154
+ const keyCodeIsNumber = keyCode => keyCode >= 48 && keyCode <= 57
155
+ const keyCodeIsLetter = keyCode => keyCode >= 65 && keyCode <= 90
156
+
157
+ // Focuses the cell and starts entering a new value
158
+ const startEnteringValue = (key, keyCode) => {
159
+ if ($focusedCellAPI) {
160
+ const type = $focusedCellAPI.getType()
161
+ if (type === "number" && keyCodeIsNumber(keyCode)) {
162
+ $focusedCellAPI.setValue(parseInt(key))
163
+ $focusedCellAPI.focus()
164
+ } else if (
165
+ ["string", "barcodeqr", "longform"].includes(type) &&
166
+ (keyCodeIsLetter(keyCode) || keyCodeIsNumber(keyCode))
167
+ ) {
168
+ $focusedCellAPI.setValue(key)
169
+ $focusedCellAPI.focus()
170
+ }
171
+ }
172
+ }
173
+
174
+ onMount(() => {
175
+ document.addEventListener("keydown", handleKeyDown)
176
+ return () => {
177
+ document.removeEventListener("keydown", handleKeyDown)
178
+ }
179
+ })
180
+ </script>
@@ -0,0 +1,95 @@
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
+ } = getContext("grid")
17
+
18
+ $: style = makeStyle($menu)
19
+
20
+ const makeStyle = menu => {
21
+ return `left:${menu.left}px; top:${menu.top}px;`
22
+ }
23
+
24
+ const deleteRow = () => {
25
+ rows.actions.deleteRows([$focusedRow])
26
+ menu.actions.close()
27
+ notifications.success("Deleted 1 row")
28
+ }
29
+
30
+ const duplicate = async () => {
31
+ menu.actions.close()
32
+ const newRow = await rows.actions.duplicateRow($focusedRow)
33
+ if (newRow) {
34
+ const column = $stickyColumn?.name || $columns[0].name
35
+ $focusedCellId = `${newRow._id}-${column}`
36
+ }
37
+ }
38
+ </script>
39
+
40
+ {#if $menu.visible}
41
+ <div class="menu" {style} use:clickOutside={() => menu.actions.close()}>
42
+ <Menu>
43
+ <MenuItem
44
+ icon="Copy"
45
+ on:click={clipboard.actions.copy}
46
+ on:click={menu.actions.close}
47
+ >
48
+ Copy
49
+ </MenuItem>
50
+ <MenuItem
51
+ icon="Paste"
52
+ disabled={$copiedCell == null}
53
+ on:click={clipboard.actions.paste}
54
+ on:click={menu.actions.close}
55
+ >
56
+ Paste
57
+ </MenuItem>
58
+ <MenuItem
59
+ icon="Maximize"
60
+ disabled={!$config.allowEditRows}
61
+ on:click={() => dispatch("edit-row", $focusedRow)}
62
+ on:click={menu.actions.close}
63
+ >
64
+ Edit row in modal
65
+ </MenuItem>
66
+ <MenuItem
67
+ icon="Duplicate"
68
+ disabled={!$config.allowAddRows}
69
+ on:click={duplicate}
70
+ >
71
+ Duplicate row
72
+ </MenuItem>
73
+ <MenuItem
74
+ icon="Delete"
75
+ disabled={!$config.allowDeleteRows}
76
+ on:click={deleteRow}
77
+ >
78
+ Delete row
79
+ </MenuItem>
80
+ </Menu>
81
+ </div>
82
+ {/if}
83
+
84
+ <style>
85
+ .menu {
86
+ position: absolute;
87
+ background: var(--cell-background);
88
+ border: 1px solid var(--spectrum-global-color-gray-300);
89
+ width: 180px;
90
+ border-radius: 4px;
91
+ display: flex;
92
+ flex-direction: column;
93
+ box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
94
+ }
95
+ </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
+ }