1
0
Fork 0
mirror of synced 2024-09-10 22:46:09 +12:00

Refactor resizing logic into store and improve UX around hover events when resizing/reordering

This commit is contained in:
Andrew Kingston 2023-03-06 15:39:50 +00:00
parent b5a72438e1
commit f0ac9e9d9c
5 changed files with 121 additions and 83 deletions

View file

@ -1,77 +1,23 @@
<script>
import { getContext } from "svelte"
const { columns, rand, scroll, visibleColumns, stickyColumn, isReordering } =
getContext("sheet")
const MinColumnWidth = 100
let initialMouseX = null
let initialWidth = null
let columnIdx = null
let width = 0
let left = 0
let columnCount = 0
const {
columns,
resize,
scroll,
visibleColumns,
stickyColumn,
isReordering,
} = getContext("sheet")
$: scrollLeft = $scroll.left
$: cutoff = scrollLeft + 40 + ($columns[0]?.width || 0)
$: offset = 40 + ($stickyColumn?.width || 0)
$: columnIdx = $resize.columnIdx
const startResizing = (idx, e) => {
// Prevent propagation to stop reordering triggering
e.stopPropagation()
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;`
const getStyle = (column, offset, scrollLeft) => {
const left = offset + column.left + column.width - scrollLeft
return `left:${left}px;`
}
</script>
@ -80,18 +26,18 @@
<div
class="resize-slider sticky"
class:visible={columnIdx === "sticky"}
on:mousedown={e => startResizing("sticky", e)}
style="--left:{40 + $stickyColumn.width}px;"
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
style="left:{40 + $stickyColumn.width}px;"
>
<div class="resize-indicator" />
</div>
{/if}
{#each $visibleColumns as col}
{#each $visibleColumns as column}
<div
class="resize-slider"
class:visible={columnIdx === col.idx}
on:mousedown={e => startResizing(col.idx, e)}
style={getStyle(col, offset, scrollLeft)}
class:visible={columnIdx === column.idx}
on:mousedown={e => resize.actions.startResizing(column, e)}
style={getStyle(column, offset, scrollLeft)}
>
<div class="resize-indicator" />
</div>
@ -104,7 +50,6 @@
top: 0;
z-index: 1;
height: var(--cell-height);
left: var(--left);
opacity: 0;
padding: 0 8px;
transform: translateX(-50%);
@ -124,11 +69,4 @@
height: 100%;
background: var(--spectrum-global-color-blue-400);
}
:global(.sheet.is-resizing *) {
cursor: col-resize !important;
}
:global(.sheet.is-reordering .resize-slider) {
display: none;
}
</style>

View file

@ -12,6 +12,7 @@
export { createUserStores } from "./stores/users"
import { createWebsocket } from "./websocket"
import { createUserStores } from "./stores/users"
import { createResizeStores } from "./stores/resize"
import DeleteButton from "./DeleteButton.svelte"
import SheetBody from "./SheetBody.svelte"
import ResizeOverlay from "./ResizeOverlay.svelte"
@ -50,6 +51,7 @@
}
context = { ...context, ...createRowsStore(context) }
context = { ...context, ...createColumnsStores(context) }
context = { ...context, ...createResizeStores(context) }
context = { ...context, ...createBoundsStores(context) }
context = { ...context, ...createScrollStores(context) }
context = { ...context, ...createViewportStores(context) }
@ -57,6 +59,9 @@
context = { ...context, ...createInterfaceStores(context) }
context = { ...context, ...createUserStores(context) }
// Reference some stores for local use
const isResizing = context.isResizing
// Keep config store up to date
$: config.set({
tableId,
@ -72,7 +77,12 @@
onMount(() => createWebsocket(context))
</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-left">
<slot name="controls" />
@ -116,6 +126,9 @@
.sheet :global(*) {
box-sizing: border-box;
}
.sheet.is-resizing :global(*) {
cursor: col-resize !important;
}
.sheet-data {
flex: 1 1 auto;

View file

@ -7,7 +7,7 @@
export let column
export let orderable = true
const { reorder, isReordering, rand } = getContext("sheet")
const { reorder, isReordering, isResizing, rand } = getContext("sheet")
let timeout
let anchor
@ -39,7 +39,7 @@
class:open
style="flex: 0 0 {column.width}px;"
bind:this={anchor}
class:disabled={$isReordering}
class:disabled={$isReordering || $isResizing}
>
<SheetCell
reorderSource={$reorder.sourceColumn === column.name}

View file

@ -55,6 +55,7 @@ export const createColumnsStores = context => {
width: same ? existingWidth : defaultWidth,
left: 40,
schema: primaryDisplay[1],
idx: "sticky",
})
})

View file

@ -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,
}
}