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>
|
||||
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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -55,6 +55,7 @@ export const createColumnsStores = context => {
|
|||
width: same ? existingWidth : defaultWidth,
|
||||
left: 40,
|
||||
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