Improve row vs column detection to fix any edge cases
This commit is contained in:
parent
9df86362b9
commit
fd3662e6b2
|
@ -28,7 +28,6 @@
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
export let isBlock = false
|
export let isBlock = false
|
||||||
export let parent = null
|
export let parent = null
|
||||||
export let index = 0
|
|
||||||
|
|
||||||
// Get parent contexts
|
// Get parent contexts
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -463,14 +462,13 @@
|
||||||
data-name={name}
|
data-name={name}
|
||||||
data-icon={icon}
|
data-icon={icon}
|
||||||
data-parent={parent}
|
data-parent={parent}
|
||||||
data-index={index}
|
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||||
{#if hasMissingRequiredSettings}
|
{#if hasMissingRequiredSettings}
|
||||||
<ComponentPlaceholder />
|
<ComponentPlaceholder />
|
||||||
{:else if children.length}
|
{:else if children.length}
|
||||||
{#each children as child, idx (child._id)}
|
{#each children as child (child._id)}
|
||||||
<svelte:self instance={child} parent={id} index={idx} />
|
<svelte:self instance={child} parent={id} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else if emptyState}
|
{:else if emptyState}
|
||||||
{#if isScreen}
|
{#if isScreen}
|
||||||
|
|
|
@ -9,32 +9,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { get } from "svelte/store"
|
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import { builderStore } from "stores"
|
||||||
import { builderStore, componentStore } from "stores"
|
|
||||||
import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
|
import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
|
||||||
|
|
||||||
let dragInfo
|
let dragInfo
|
||||||
let dropInfo
|
let dropInfo
|
||||||
let placeholderInfo
|
let placeholderInfo
|
||||||
|
|
||||||
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 getDOMNode = id => {
|
const getDOMNode = id => {
|
||||||
const component = document.getElementsByClassName(id)[0]
|
const component = document.getElementsByClassName(id)[0]
|
||||||
return [...component.children][0]
|
return [...component.children][0]
|
||||||
|
@ -93,83 +75,16 @@
|
||||||
}
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
let { id, parent, node, index, acceptsChildren, empty } = dropInfo
|
let { id, parent, node, acceptsChildren, empty } = dropInfo
|
||||||
const mouseY = e.clientY
|
const mouseY = e.clientY
|
||||||
const mouseX = e.clientX
|
const mouseX = e.clientX
|
||||||
|
|
||||||
// if (!dropInfo.bounds) {
|
// If we're over something that does not accept children then we go up a
|
||||||
// } else {
|
// level and consider the mouse position relative to the parent
|
||||||
// dropInfo.bounds.top = node.offsetTop
|
|
||||||
// dropInfo.bounds.left = node.offsetLeft
|
|
||||||
// console.log(node.offsetTop)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log("calc")
|
|
||||||
// dropInfo.bounds = bounds
|
|
||||||
|
|
||||||
// If we're over something that does not accept children then we must go
|
|
||||||
// above or below this component
|
|
||||||
if (!acceptsChildren) {
|
if (!acceptsChildren) {
|
||||||
id = parent
|
id = parent
|
||||||
acceptsChildren = true
|
|
||||||
empty = false
|
empty = false
|
||||||
node = getDOMNode(parent)
|
node = getDOMNode(parent)
|
||||||
//
|
|
||||||
//
|
|
||||||
// const bounds = node.getBoundingClientRect()
|
|
||||||
// const { top, left, height, width } = bounds
|
|
||||||
// const snapFactor = 0.5
|
|
||||||
// const snapLimitV = Math.min(40, height * snapFactor)
|
|
||||||
// const snapLimitH = Math.min(40, width * snapFactor)
|
|
||||||
//
|
|
||||||
// // Determine all sides 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)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // If we're somehow not in range of any side, do nothing
|
|
||||||
// if (!sides.length) {
|
|
||||||
// console.log("no sides match")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let side
|
|
||||||
// if (sides.length === 1) {
|
|
||||||
// // When one edge matches, use that edge
|
|
||||||
// side = sides[0]
|
|
||||||
// } else {
|
|
||||||
// // 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)
|
|
||||||
// side = delta1 < delta2 ? sides[0] : sides[1]
|
|
||||||
// }
|
|
||||||
// if ([Sides.Top, Sides.Left].includes(side)) {
|
|
||||||
// // Before, so use the current index
|
|
||||||
// console.log("before")
|
|
||||||
// placeholderInfo = {
|
|
||||||
// parent: parent,
|
|
||||||
// index: index,
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// console.log("after")
|
|
||||||
// // After, so use the next index
|
|
||||||
// placeholderInfo = {
|
|
||||||
// parent: parent,
|
|
||||||
// index: index + 1,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're now hovering over something which does accept children.
|
// We're now hovering over something which does accept children.
|
||||||
|
@ -184,72 +99,56 @@
|
||||||
|
|
||||||
// We're now hovering over something which accepts children and is not
|
// We're now hovering over something which accepts children and is not
|
||||||
// empty, so we need to work out where to inside the placeholder
|
// empty, so we need to work out where to inside the placeholder
|
||||||
|
// Calculate the coordinates of various locations on each child.
|
||||||
// Check we're actually inside
|
// We include the placeholder component in this as it guarantees we have
|
||||||
// if (
|
// at least 2 child components, and therefore guarantee there is no
|
||||||
// mouseY < top ||
|
// ambiguity in the layout.
|
||||||
// mouseY > top + height ||
|
const childCoords = [...(node.children || [])].map(node => {
|
||||||
// mouseX < left ||
|
const bounds = node.children[0].getBoundingClientRect()
|
||||||
// mouseX > left + width
|
return {
|
||||||
// ) {
|
placeholder: node.classList.contains("placeholder"),
|
||||||
// console.log("not inside")
|
centerX: bounds.left + bounds.width / 2,
|
||||||
// return
|
centerY: bounds.top + bounds.height / 2,
|
||||||
// }
|
left: bounds.left,
|
||||||
|
right: bounds.right,
|
||||||
// Get all DOM nodes of children of this component.
|
top: bounds.top,
|
||||||
// Filter out the placeholder as we don't want it to affect the index of
|
bottom: bounds.bottom,
|
||||||
// the new placeholder.
|
}
|
||||||
const children = [...(node.children || [])]
|
|
||||||
.filter(x => !x.classList.contains("placeholder"))
|
|
||||||
.map(x => x.children[0])
|
|
||||||
|
|
||||||
// Calculate centers of each child
|
|
||||||
const centers = children.map(child => {
|
|
||||||
const childBounds = child.getBoundingClientRect()
|
|
||||||
return [
|
|
||||||
childBounds.left + childBounds.width / 2,
|
|
||||||
childBounds.top + childBounds.height / 2,
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Calculate variance of X and Y centers to determine layout
|
// Calculate the variance between each set of positions on the children
|
||||||
const xCoords = centers.map(x => x[0])
|
const variances = Object.keys(childCoords[0]).map(key => {
|
||||||
const yCoords = centers.map(x => x[1])
|
const coords = childCoords.map(x => x[key])
|
||||||
const xVariance = variance(xCoords)
|
return {
|
||||||
const yVariance = variance(yCoords)
|
variance: variance(coords),
|
||||||
const column = xVariance <= yVariance
|
side: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort by variance. The lowest variance position indicates whether we are
|
||||||
|
// in a row or column layout
|
||||||
|
variances.sort((a, b) => {
|
||||||
|
return a.variance < b.variance ? -1 : 1
|
||||||
|
})
|
||||||
|
const column = ["centerX", "left", "right"].includes(variances[0].side)
|
||||||
console.log(column ? "COL" : "ROW")
|
console.log(column ? "COL" : "ROW")
|
||||||
|
|
||||||
// Now that we know the layout, find which children in this axis we are
|
// Find the correct index to drop in based on the midpoints of each child
|
||||||
// between
|
// in their primary axis.
|
||||||
const childPositions = column ? yCoords : xCoords
|
// Here we filter out the placeholder component as we do not want it to
|
||||||
|
// affect the determination of the new index.
|
||||||
|
const childPositions = column
|
||||||
|
? childCoords.filter(x => !x.placeholder).map(x => x.centerY)
|
||||||
|
: childCoords.filter(x => !x.placeholder).map(x => x.centerX)
|
||||||
const mousePosition = column ? mouseY : mouseX
|
const mousePosition = column ? mouseY : mouseX
|
||||||
|
|
||||||
let idx = 0
|
let idx = 0
|
||||||
while (idx < children.length && childPositions[idx] < mousePosition) {
|
while (idx < childPositions.length && childPositions[idx] < mousePosition) {
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
placeholderInfo = {
|
placeholderInfo = {
|
||||||
parent: id,
|
parent: id,
|
||||||
index: idx,
|
index: idx,
|
||||||
}
|
}
|
||||||
// // When no edges match, drop inside if possible
|
|
||||||
// if (!sides.length) {
|
|
||||||
// if (empty) {
|
|
||||||
// console.log("allowed inside")
|
|
||||||
// return {
|
|
||||||
// ...dropInfo,
|
|
||||||
// mode: "inside",
|
|
||||||
// side: null,
|
|
||||||
// bounds,
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // No sides but also not empty?
|
|
||||||
// console.log("no sides match, but not empty")
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when on top of a component
|
// Callback when on top of a component
|
||||||
|
@ -268,12 +167,6 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do nothing if this is the placeholder
|
|
||||||
// if (element.dataset.id === "placeholder") {
|
|
||||||
// console.log("placeholder")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
const component = e.target.closest(".component:not(.block)")
|
const component = e.target.closest(".component:not(.block)")
|
||||||
if (
|
if (
|
||||||
component &&
|
component &&
|
||||||
|
@ -285,38 +178,17 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the dragging flag is always set.
|
|
||||||
// There's a bit of a race condition between the app reinitialisation
|
|
||||||
// after selecting the DND component and setting this the first time
|
|
||||||
// if (!get(builderStore).isDragging) {
|
|
||||||
// builderStore.actions.setDragging(true)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Precompute and store some info to avoid recalculating everything in
|
// Precompute and store some info to avoid recalculating everything in
|
||||||
// dragOver
|
// dragOver
|
||||||
dropInfo = {
|
dropInfo = {
|
||||||
id: component.dataset.id,
|
id: component.dataset.id,
|
||||||
parent: component.dataset.parent,
|
parent: component.dataset.parent,
|
||||||
index: parseInt(component.dataset.index),
|
|
||||||
node: getDOMNode(component.dataset.id),
|
node: getDOMNode(component.dataset.id),
|
||||||
empty: component.classList.contains("empty"),
|
empty: component.classList.contains("empty"),
|
||||||
acceptsChildren: component.classList.contains("parent"),
|
acceptsChildren: component.classList.contains("parent"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// "enter",
|
|
||||||
// component.dataset.name,
|
|
||||||
// "id",
|
|
||||||
// dropInfo.id,
|
|
||||||
// "parent",
|
|
||||||
// dropInfo.parent,
|
|
||||||
// "index",
|
|
||||||
// dropInfo.index
|
|
||||||
// )
|
|
||||||
|
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
} else {
|
|
||||||
// dropInfo = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,10 +252,3 @@
|
||||||
{#if $builderStore.isDragging}
|
{#if $builderStore.isDragging}
|
||||||
<PlaceholderOverlay />
|
<PlaceholderOverlay />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!--<DNDPositionIndicator-->
|
|
||||||
<!-- {dropInfo}-->
|
|
||||||
<!-- color="var(--spectrum-global-color-static-green-500)"-->
|
|
||||||
<!-- zIndex="940"-->
|
|
||||||
<!-- transition-->
|
|
||||||
<!--/>-->
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
<script>
|
|
||||||
import Indicator from "./Indicator.svelte"
|
|
||||||
import { Sides } from "./DNDHandler.svelte"
|
|
||||||
|
|
||||||
export let dropInfo
|
|
||||||
export let zIndex
|
|
||||||
export let color
|
|
||||||
export let transition
|
|
||||||
|
|
||||||
$: dimensions = getDimensions(dropInfo)
|
|
||||||
$: prefix = dropInfo?.mode === "above" ? "Before" : "After"
|
|
||||||
$: text = `${prefix} ${dropInfo?.name}`
|
|
||||||
$: icon = dropInfo?.icon
|
|
||||||
$: renderKey = `${dropInfo?.target}-${dropInfo?.side}`
|
|
||||||
|
|
||||||
const getDimensions = info => {
|
|
||||||
const { bounds, side } = info ?? {}
|
|
||||||
if (!bounds || !side) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get preview offset
|
|
||||||
const root = document.getElementById("clip-root")
|
|
||||||
const rootBounds = root.getBoundingClientRect()
|
|
||||||
|
|
||||||
// Subtract preview offset from bounds
|
|
||||||
let { left, top, width, height } = bounds
|
|
||||||
left -= rootBounds.left
|
|
||||||
top -= rootBounds.top
|
|
||||||
|
|
||||||
// Determine position
|
|
||||||
if (side === Sides.Top || side === Sides.Bottom) {
|
|
||||||
return {
|
|
||||||
top: side === Sides.Top ? top - 4 : top + height,
|
|
||||||
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>
|
|
||||||
|
|
||||||
{#key renderKey}
|
|
||||||
{#if dimensions && dropInfo?.mode !== "inside"}
|
|
||||||
<Indicator
|
|
||||||
left={Math.round(dimensions.left)}
|
|
||||||
top={Math.round(dimensions.top)}
|
|
||||||
width={dimensions.width}
|
|
||||||
height={dimensions.height}
|
|
||||||
{text}
|
|
||||||
{icon}
|
|
||||||
{zIndex}
|
|
||||||
{color}
|
|
||||||
{transition}
|
|
||||||
alignRight={dropInfo?.side === Sides.Right}
|
|
||||||
line
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
Loading…
Reference in New Issue