Enable left/right side targetting for DND

This commit is contained in:
Andrew Kingston 2021-09-21 08:36:02 +01:00
parent 7663bdb534
commit ee2e2799d9
3 changed files with 103 additions and 24 deletions

View File

@ -1,3 +1,12 @@
<script context="module">
export const Sides = {
Top: "Top",
Right: "Right",
Bottom: "Bottom",
Left: "Left",
}
</script>
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { get } from "svelte/store" import { get } from "svelte/store"
@ -8,6 +17,22 @@
let dragInfo let dragInfo
let dropInfo let dropInfo
const getEdges = (bounds, mousePoint) => {
const { width, height, top, left } = bounds
return {
[Sides.Top]: [mousePoint[0], top],
[Sides.Right]: [left + width, mousePoint[1]],
[Sides.Bottom]: [mousePoint[0], top + height],
[Sides.Left]: [left, mousePoint[1]],
}
}
const calculatePointDelta = (point1, point2) => {
const deltaX = Math.abs(point1[0] - point2[0])
const deltaY = Math.abs(point1[1] - point2[1])
return Math.sqrt(deltaX * deltaX + deltaY * deltaY)
}
const getDOMNodeForComponent = component => { const getDOMNodeForComponent = component => {
const parent = component.closest(".component") const parent = component.closest(".component")
const children = Array.from(parent.childNodes) const children = Array.from(parent.childNodes)
@ -61,23 +86,57 @@
e.preventDefault() e.preventDefault()
const { droppableInside, bounds } = dropInfo const { droppableInside, bounds } = dropInfo
const { top, height } = bounds const { top, left, height, width } = bounds
const mouseY = e.clientY const mouseY = e.clientY
const mouseX = e.clientX
const snapFactor = droppableInside ? 0.33 : 0.5 const snapFactor = droppableInside ? 0.33 : 0.5
const snapLimit = Math.min(40, height * snapFactor) const snapLimitV = Math.min(40, height * snapFactor)
const edgeLimits = [ const snapLimitH = Math.min(40, width * snapFactor)
Math.round(top + snapLimit),
Math.round(top + height - snapLimit),
]
if (mouseY <= edgeLimits[0]) { // Determine all sies we are within snap range of
let sides = []
if (mouseY <= top + snapLimitV) {
sides.push(Sides.Top)
} else if (mouseY >= top + height - snapLimitV) {
sides.push(Sides.Bottom)
}
if (mouseX < left + snapLimitH) {
sides.push(Sides.Left)
} else if (mouseX > left + width - snapLimitH) {
sides.push(Sides.Right)
}
// When no edges match, drop inside if possible
if (!sides.length) {
dropInfo.mode = droppableInside ? "inside" : null
dropInfo.side = null
return
}
// When one edge matches, use that edge
if (sides.length === 1) {
dropInfo.side = sides[0]
if ([Sides.Top, Sides.Left].includes(sides[0])) {
dropInfo.mode = "above"
} else {
dropInfo.mode = "below"
}
return
}
// When 2 edges match, work out which is closer
const mousePoint = [mouseX, mouseY]
const edges = getEdges(bounds, mousePoint)
const edge1 = edges[sides[0]]
const delta1 = calculatePointDelta(mousePoint, edge1)
const edge2 = edges[sides[1]]
const delta2 = calculatePointDelta(mousePoint, edge2)
const edge = delta1 < delta2 ? sides[0] : sides[1]
dropInfo.side = edge
if ([Sides.Top, Sides.Left].includes(edge)) {
dropInfo.mode = "above" dropInfo.mode = "above"
} else if (mouseY >= edgeLimits[1]) {
dropInfo.mode = "below"
} else if (droppableInside) {
dropInfo.mode = "inside"
} else { } else {
dropInfo.mode = null dropInfo.mode = "below"
} }
} }

View File

@ -1,39 +1,53 @@
<script> <script>
import Indicator from "./Indicator.svelte" import Indicator from "./Indicator.svelte"
import { Sides } from "./DNDHandler.svelte"
export let dropInfo export let dropInfo
export let zIndex export let zIndex
export let color export let color
export let transition export let transition
$: dimensions = getDimensions(dropInfo?.bounds, dropInfo?.mode) $: dimensions = getDimensions(dropInfo)
$: prefix = dropInfo?.mode === "above" ? "Before" : "After" $: prefix = dropInfo?.mode === "above" ? "Before" : "After"
$: text = `${prefix} ${dropInfo?.name}` $: text = `${prefix} ${dropInfo?.name}`
$: renderKey = `${dropInfo?.target}-${dropInfo?.side}`
const getDimensions = (bounds, mode) => { const getDimensions = info => {
if (!bounds || !mode) { const { bounds, side } = info ?? {}
if (!bounds || !side) {
return null return null
} }
const { left, top, width, height } = bounds const { left, top, width, height } = bounds
return { if (side === Sides.Top || side === Sides.Bottom) {
top: mode === "above" ? top - 4 : top + height, return {
left: left - 2, top: side === Sides.Top ? top - 4 : top + height,
width: width + 4, left: left - 2,
width: width + 4,
height: 0,
}
} else {
return {
top: top - 2,
left: side === Sides.Left ? left - 4 : left + width,
width: 0,
height: height + 4,
}
} }
} }
</script> </script>
{#key `${dropInfo?.target}-${dropInfo?.mode}`} {#key renderKey}
{#if dimensions && dropInfo?.mode !== "inside"} {#if dimensions && dropInfo?.mode !== "inside"}
<Indicator <Indicator
left={dimensions.left} left={Math.round(dimensions.left)}
top={dimensions.top} top={Math.round(dimensions.top)}
width={dimensions.width} width={dimensions.width}
height={0} height={dimensions.height}
{text} {text}
{zIndex} {zIndex}
{color} {color}
{transition} {transition}
alignRight={dropInfo?.side === Sides.Right}
line line
/> />
{/if} {/if}

View File

@ -10,6 +10,7 @@
export let zIndex export let zIndex
export let transition = false export let transition = false
export let line = false export let line = false
export let alignRight = false
$: flipped = top < 20 $: flipped = top < 20
</script> </script>
@ -26,7 +27,7 @@
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};" style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
> >
{#if text} {#if text}
<div class="text" class:flipped class:line> <div class="text" class:flipped class:line class:right={alignRight}>
{text} {text}
</div> </div>
{/if} {/if}
@ -34,6 +35,7 @@
<style> <style>
.indicator { .indicator {
right: 0;
position: absolute; position: absolute;
z-index: var(--zIndex); z-index: var(--zIndex);
border: 2px solid var(--color); border: 2px solid var(--color);
@ -78,4 +80,8 @@
transform: translateY(-50%) !important; transform: translateY(-50%) !important;
border-radius: 4px !important; border-radius: 4px !important;
} }
.text.right {
right: -2px !important;
left: auto !important;
}
</style> </style>