Enable left/right side targetting for DND
This commit is contained in:
parent
7663bdb534
commit
ee2e2799d9
|
@ -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"
|
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"
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
} else {
|
||||||
|
dropInfo.mode = "below"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
if (side === Sides.Top || side === Sides.Bottom) {
|
||||||
return {
|
return {
|
||||||
top: mode === "above" ? top - 4 : top + height,
|
top: side === Sides.Top ? top - 4 : top + height,
|
||||||
left: left - 2,
|
left: left - 2,
|
||||||
width: width + 4,
|
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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue