Refactor resizing logic into store and improve UX around hover events when resizing/reordering
This commit is contained in:
parent
b5a72438e1
commit
f0ac9e9d9c
|
@ -1,77 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { columns, rand, scroll, visibleColumns, stickyColumn, isReordering } =
|
const {
|
||||||
getContext("sheet")
|
columns,
|
||||||
const MinColumnWidth = 100
|
resize,
|
||||||
|
scroll,
|
||||||
let initialMouseX = null
|
visibleColumns,
|
||||||
let initialWidth = null
|
stickyColumn,
|
||||||
let columnIdx = null
|
isReordering,
|
||||||
let width = 0
|
} = getContext("sheet")
|
||||||
let left = 0
|
|
||||||
let columnCount = 0
|
|
||||||
|
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: cutoff = scrollLeft + 40 + ($columns[0]?.width || 0)
|
$: cutoff = scrollLeft + 40 + ($columns[0]?.width || 0)
|
||||||
$: offset = 40 + ($stickyColumn?.width || 0)
|
$: offset = 40 + ($stickyColumn?.width || 0)
|
||||||
|
$: columnIdx = $resize.columnIdx
|
||||||
|
|
||||||
const startResizing = (idx, e) => {
|
const getStyle = (column, offset, scrollLeft) => {
|
||||||
// Prevent propagation to stop reordering triggering
|
const left = offset + column.left + column.width - scrollLeft
|
||||||
e.stopPropagation()
|
return `left:${left}px;`
|
||||||
|
|
||||||
const col = idx === "sticky" ? $stickyColumn : $columns[idx]
|
|
||||||
width = col.width
|
|
||||||
left = col.left
|
|
||||||
initialWidth = width
|
|
||||||
initialMouseX = e.clientX
|
|
||||||
columnIdx = idx
|
|
||||||
columnCount = $columns.length
|
|
||||||
|
|
||||||
// Add mouse event listeners to handle resizing
|
|
||||||
document.addEventListener("mousemove", onResizeMouseMove)
|
|
||||||
document.addEventListener("mouseup", stopResizing)
|
|
||||||
document.getElementById(`sheet-${rand}`).classList.add("is-resizing")
|
|
||||||
}
|
|
||||||
|
|
||||||
const onResizeMouseMove = e => {
|
|
||||||
const dx = e.clientX - initialMouseX
|
|
||||||
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
|
|
||||||
|
|
||||||
if (Math.abs(width - newWidth) < 10) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnIdx === "sticky") {
|
|
||||||
stickyColumn.update(state => ({
|
|
||||||
...state,
|
|
||||||
width: newWidth,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
columns.update(state => {
|
|
||||||
state[columnIdx].width = newWidth
|
|
||||||
let offset = state[columnIdx].left + newWidth
|
|
||||||
for (let i = columnIdx + 1; i < state.length; i++) {
|
|
||||||
state[i].left = offset
|
|
||||||
offset += state[i].width
|
|
||||||
}
|
|
||||||
return [...state]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
width = newWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopResizing = () => {
|
|
||||||
columnIdx = null
|
|
||||||
document.removeEventListener("mousemove", onResizeMouseMove)
|
|
||||||
document.removeEventListener("mouseup", stopResizing)
|
|
||||||
document.getElementById(`sheet-${rand}`).classList.remove("is-resizing")
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStyle = (col, offset, scrollLeft) => {
|
|
||||||
const left = offset + col.left + col.width - scrollLeft
|
|
||||||
return `--left:${left}px;`
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -80,18 +26,18 @@
|
||||||
<div
|
<div
|
||||||
class="resize-slider sticky"
|
class="resize-slider sticky"
|
||||||
class:visible={columnIdx === "sticky"}
|
class:visible={columnIdx === "sticky"}
|
||||||
on:mousedown={e => startResizing("sticky", e)}
|
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
|
||||||
style="--left:{40 + $stickyColumn.width}px;"
|
style="left:{40 + $stickyColumn.width}px;"
|
||||||
>
|
>
|
||||||
<div class="resize-indicator" />
|
<div class="resize-indicator" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $visibleColumns as col}
|
{#each $visibleColumns as column}
|
||||||
<div
|
<div
|
||||||
class="resize-slider"
|
class="resize-slider"
|
||||||
class:visible={columnIdx === col.idx}
|
class:visible={columnIdx === column.idx}
|
||||||
on:mousedown={e => startResizing(col.idx, e)}
|
on:mousedown={e => resize.actions.startResizing(column, e)}
|
||||||
style={getStyle(col, offset, scrollLeft)}
|
style={getStyle(column, offset, scrollLeft)}
|
||||||
>
|
>
|
||||||
<div class="resize-indicator" />
|
<div class="resize-indicator" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,7 +50,6 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
height: var(--cell-height);
|
height: var(--cell-height);
|
||||||
left: var(--left);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
@ -124,11 +69,4 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--spectrum-global-color-blue-400);
|
background: var(--spectrum-global-color-blue-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.sheet.is-resizing *) {
|
|
||||||
cursor: col-resize !important;
|
|
||||||
}
|
|
||||||
:global(.sheet.is-reordering .resize-slider) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export { createUserStores } from "./stores/users"
|
export { createUserStores } from "./stores/users"
|
||||||
import { createWebsocket } from "./websocket"
|
import { createWebsocket } from "./websocket"
|
||||||
import { createUserStores } from "./stores/users"
|
import { createUserStores } from "./stores/users"
|
||||||
|
import { createResizeStores } from "./stores/resize"
|
||||||
import DeleteButton from "./DeleteButton.svelte"
|
import DeleteButton from "./DeleteButton.svelte"
|
||||||
import SheetBody from "./SheetBody.svelte"
|
import SheetBody from "./SheetBody.svelte"
|
||||||
import ResizeOverlay from "./ResizeOverlay.svelte"
|
import ResizeOverlay from "./ResizeOverlay.svelte"
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
}
|
}
|
||||||
context = { ...context, ...createRowsStore(context) }
|
context = { ...context, ...createRowsStore(context) }
|
||||||
context = { ...context, ...createColumnsStores(context) }
|
context = { ...context, ...createColumnsStores(context) }
|
||||||
|
context = { ...context, ...createResizeStores(context) }
|
||||||
context = { ...context, ...createBoundsStores(context) }
|
context = { ...context, ...createBoundsStores(context) }
|
||||||
context = { ...context, ...createScrollStores(context) }
|
context = { ...context, ...createScrollStores(context) }
|
||||||
context = { ...context, ...createViewportStores(context) }
|
context = { ...context, ...createViewportStores(context) }
|
||||||
|
@ -57,6 +59,9 @@
|
||||||
context = { ...context, ...createInterfaceStores(context) }
|
context = { ...context, ...createInterfaceStores(context) }
|
||||||
context = { ...context, ...createUserStores(context) }
|
context = { ...context, ...createUserStores(context) }
|
||||||
|
|
||||||
|
// Reference some stores for local use
|
||||||
|
const isResizing = context.isResizing
|
||||||
|
|
||||||
// Keep config store up to date
|
// Keep config store up to date
|
||||||
$: config.set({
|
$: config.set({
|
||||||
tableId,
|
tableId,
|
||||||
|
@ -72,7 +77,12 @@
|
||||||
onMount(() => createWebsocket(context))
|
onMount(() => createWebsocket(context))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
<div
|
||||||
|
class="sheet"
|
||||||
|
class:is-resizing={$isResizing}
|
||||||
|
style="--cell-height:{cellHeight}px;"
|
||||||
|
id="sheet-{rand}"
|
||||||
|
>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="controls-left">
|
<div class="controls-left">
|
||||||
<slot name="controls" />
|
<slot name="controls" />
|
||||||
|
@ -116,6 +126,9 @@
|
||||||
.sheet :global(*) {
|
.sheet :global(*) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.sheet.is-resizing :global(*) {
|
||||||
|
cursor: col-resize !important;
|
||||||
|
}
|
||||||
|
|
||||||
.sheet-data {
|
.sheet-data {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
export let column
|
export let column
|
||||||
export let orderable = true
|
export let orderable = true
|
||||||
|
|
||||||
const { reorder, isReordering, rand } = getContext("sheet")
|
const { reorder, isReordering, isResizing, rand } = getContext("sheet")
|
||||||
|
|
||||||
let timeout
|
let timeout
|
||||||
let anchor
|
let anchor
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
class:open
|
class:open
|
||||||
style="flex: 0 0 {column.width}px;"
|
style="flex: 0 0 {column.width}px;"
|
||||||
bind:this={anchor}
|
bind:this={anchor}
|
||||||
class:disabled={$isReordering}
|
class:disabled={$isReordering || $isResizing}
|
||||||
>
|
>
|
||||||
<SheetCell
|
<SheetCell
|
||||||
reorderSource={$reorder.sourceColumn === column.name}
|
reorderSource={$reorder.sourceColumn === column.name}
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const createColumnsStores = context => {
|
||||||
width: same ? existingWidth : defaultWidth,
|
width: same ? existingWidth : defaultWidth,
|
||||||
left: 40,
|
left: 40,
|
||||||
schema: primaryDisplay[1],
|
schema: primaryDisplay[1],
|
||||||
|
idx: "sticky",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { writable, get, derived } from "svelte/store"
|
||||||
|
|
||||||
|
export const createResizeStores = context => {
|
||||||
|
const { columns, stickyColumn } = context
|
||||||
|
const initialState = {
|
||||||
|
initialMouseX: null,
|
||||||
|
initialWidth: null,
|
||||||
|
columnIdx: null,
|
||||||
|
width: 0,
|
||||||
|
left: 0,
|
||||||
|
}
|
||||||
|
const resize = writable(initialState)
|
||||||
|
const isResizing = derived(resize, $resize => $resize.columnIdx != null)
|
||||||
|
const MinColumnWidth = 100
|
||||||
|
|
||||||
|
// Starts resizing a certain column
|
||||||
|
const startResizing = (column, e) => {
|
||||||
|
// Prevent propagation to stop reordering triggering
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
resize.set({
|
||||||
|
width: column.width,
|
||||||
|
left: column.left,
|
||||||
|
initialWidth: column.width,
|
||||||
|
initialMouseX: e.clientX,
|
||||||
|
columnIdx: column.idx,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add mouse event listeners to handle resizing
|
||||||
|
document.addEventListener("mousemove", onResizeMouseMove)
|
||||||
|
document.addEventListener("mouseup", stopResizing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for moving the mouse to resize columns
|
||||||
|
const onResizeMouseMove = e => {
|
||||||
|
const { initialMouseX, initialWidth, width, columnIdx } = get(resize)
|
||||||
|
const dx = e.clientX - initialMouseX
|
||||||
|
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
|
||||||
|
|
||||||
|
// Ignore small changes
|
||||||
|
if (Math.abs(width - newWidth) < 5) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update column state
|
||||||
|
if (columnIdx === "sticky") {
|
||||||
|
stickyColumn.update(state => ({
|
||||||
|
...state,
|
||||||
|
width: newWidth,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
columns.update(state => {
|
||||||
|
state[columnIdx].width = newWidth
|
||||||
|
let offset = state[columnIdx].left + newWidth
|
||||||
|
for (let i = columnIdx + 1; i < state.length; i++) {
|
||||||
|
state[i].left = offset
|
||||||
|
offset += state[i].width
|
||||||
|
}
|
||||||
|
return [...state]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
resize.update(state => ({
|
||||||
|
...state,
|
||||||
|
width: newWidth,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop resizing any columns
|
||||||
|
const stopResizing = () => {
|
||||||
|
resize.set(initialState)
|
||||||
|
document.removeEventListener("mousemove", onResizeMouseMove)
|
||||||
|
document.removeEventListener("mouseup", stopResizing)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resize: {
|
||||||
|
...resize,
|
||||||
|
actions: {
|
||||||
|
startResizing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isResizing,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue