Only allow dropping inside empty components that accept children to massively reduce the amount of unwanted drop targets due to parent container components
This commit is contained in:
parent
2c7e93423e
commit
c2aeefae7b
|
@ -171,18 +171,23 @@
|
||||||
conditionalSettings = result.settingUpdates
|
conditionalSettings = result.settingUpdates
|
||||||
visible = nextVisible
|
visible = nextVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag and drop helper tags
|
||||||
|
$: draggable = interactive && !isLayout && !isScreen
|
||||||
|
$: droppable = interactive
|
||||||
|
$: dropInside = interactive && definition?.hasChildren && !children.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key propsHash}
|
{#key propsHash}
|
||||||
{#if constructor && componentSettings && (visible || inSelectedPath)}
|
{#if constructor && componentSettings && (visible || inSelectedPath)}
|
||||||
<div
|
<div
|
||||||
class={`component ${id}`}
|
class={`component ${id}`}
|
||||||
data-type={interactive ? "component" : ""}
|
data-type={interactive ? "component" : "readonly"}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
data-draggable={interactive && !isLayout && !isScreen ? "true" : "false"}
|
data-draggable={draggable}
|
||||||
data-droppable={interactive ? "true" : "false"}
|
data-droppable={droppable}
|
||||||
data-droppable-inside={definition?.hasChildren ? "true" : "false"}
|
data-droppable-inside={dropInside}
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} {...componentSettings}>
|
<svelte:component this={constructor} {...componentSettings}>
|
||||||
{#if children.length}
|
{#if children.length}
|
||||||
|
@ -202,6 +207,6 @@
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
[data-draggable="true"] :global(*:hover) {
|
[data-draggable="true"] :global(*:hover) {
|
||||||
cursor: grab !important;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||||
import { builderStore } from "stores"
|
import { builderStore } from "stores"
|
||||||
|
|
||||||
let dragTarget
|
let dragInfo
|
||||||
let dropTarget
|
|
||||||
let dropMode
|
|
||||||
let dropInfo
|
let dropInfo
|
||||||
|
|
||||||
const getDOMNodeForComponent = component => {
|
const getDOMNodeForComponent = component => {
|
||||||
|
@ -18,26 +16,28 @@
|
||||||
|
|
||||||
// Callback when initially starting a drag on a draggable component
|
// Callback when initially starting a drag on a draggable component
|
||||||
const onDragStart = e => {
|
const onDragStart = e => {
|
||||||
if (!e.target?.dataset?.componentId) {
|
const parent = e.target.closest("[data-type='component']")
|
||||||
|
const child = getDOMNodeForComponent(e.target)
|
||||||
|
if (!parent?.dataset?.id || !child) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
dragTarget = e.target.dataset.componentId
|
dragInfo = {
|
||||||
builderStore.actions.selectComponent(dragTarget)
|
target: parent.dataset.id,
|
||||||
|
parent: parent.dataset.parent,
|
||||||
|
}
|
||||||
|
builderStore.actions.selectComponent(dragInfo.target)
|
||||||
builderStore.actions.setDragging(true)
|
builderStore.actions.setDragging(true)
|
||||||
|
|
||||||
// Highlight being dragged by setting opacity
|
// Highlight being dragged by setting opacity
|
||||||
const child = getDOMNodeForComponent(e.target)
|
child.style.opacity = "0.5"
|
||||||
if (child) {
|
|
||||||
child.style.opacity = "0.5"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when drag stops (whether dropped or not)
|
// Callback when drag stops (whether dropped or not)
|
||||||
const onDragEnd = e => {
|
const onDragEnd = e => {
|
||||||
// Reset opacity style
|
// Reset opacity style
|
||||||
if (dragTarget) {
|
if (dragInfo) {
|
||||||
const child = getDOMNodeForComponent(e.target)
|
const child = getDOMNodeForComponent(e.target)
|
||||||
if (child) {
|
if (child) {
|
||||||
child.style.opacity = ""
|
child.style.opacity = ""
|
||||||
|
@ -45,8 +45,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset state and styles
|
// Reset state and styles
|
||||||
dragTarget = null
|
dragInfo = null
|
||||||
dropTarget = null
|
|
||||||
dropInfo = null
|
dropInfo = null
|
||||||
builderStore.actions.setDragging(false)
|
builderStore.actions.setDragging(false)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +53,7 @@
|
||||||
// Callback when on top of a component
|
// Callback when on top of a component
|
||||||
const onDragOver = e => {
|
const onDragOver = e => {
|
||||||
// Skip if we aren't validly dragging currently
|
// Skip if we aren't validly dragging currently
|
||||||
if (!dragTarget || !dropInfo) {
|
if (!dragInfo || !dropInfo) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@
|
||||||
// If not available to drop inside, just check whether we are closer
|
// If not available to drop inside, just check whether we are closer
|
||||||
// to the top or bottom
|
// to the top or bottom
|
||||||
if (!droppableInside) {
|
if (!droppableInside) {
|
||||||
dropMode = nearestEdge
|
dropInfo.mode = nearestEdge
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise determine whether the user wants to drop inside or at
|
// Otherwise determine whether the user wants to drop inside or at
|
||||||
|
@ -89,9 +88,9 @@
|
||||||
Math.round(top + height - edgeLimit),
|
Math.round(top + height - edgeLimit),
|
||||||
]
|
]
|
||||||
if (mouseY >= insideLimit[0] && mouseY <= insideLimit[1]) {
|
if (mouseY >= insideLimit[0] && mouseY <= insideLimit[1]) {
|
||||||
dropMode = "inside"
|
dropInfo.mode = "inside"
|
||||||
} else {
|
} else {
|
||||||
dropMode = nearestEdge
|
dropInfo.mode = nearestEdge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +98,7 @@
|
||||||
// Callback when entering a potential drop target
|
// Callback when entering a potential drop target
|
||||||
const onDragEnter = e => {
|
const onDragEnter = e => {
|
||||||
// Skip if we aren't validly dragging currently
|
// Skip if we aren't validly dragging currently
|
||||||
if (!dragTarget) {
|
if (!dragInfo) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +106,7 @@
|
||||||
if (
|
if (
|
||||||
element &&
|
element &&
|
||||||
element.dataset.droppable &&
|
element.dataset.droppable &&
|
||||||
element.dataset.id !== dragTarget
|
element.dataset.id !== dragInfo.target
|
||||||
) {
|
) {
|
||||||
// Ensure the dragging flag is always set.
|
// Ensure the dragging flag is always set.
|
||||||
// There's a bit of a race condition between the app reinitialisation
|
// There's a bit of a race condition between the app reinitialisation
|
||||||
|
@ -117,20 +116,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store target ID
|
// Store target ID
|
||||||
dropTarget = element.dataset.id
|
const target = element.dataset.id
|
||||||
|
|
||||||
// Precompute and store some info to avoid recalculating everything in
|
// Precompute and store some info to avoid recalculating everything in
|
||||||
// dragOver
|
// dragOver
|
||||||
const child = getDOMNodeForComponent(e.target)
|
const child = getDOMNodeForComponent(e.target)
|
||||||
const bounds = child.getBoundingClientRect()
|
const bounds = child.getBoundingClientRect()
|
||||||
dropInfo = {
|
dropInfo = {
|
||||||
|
target,
|
||||||
name: element.dataset.name,
|
name: element.dataset.name,
|
||||||
droppableInside: element.dataset.droppableInside === "true",
|
droppableInside: element.dataset.droppableInside === "true",
|
||||||
bounds,
|
bounds,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dropInfo = null
|
dropInfo = null
|
||||||
dropTarget = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +140,12 @@
|
||||||
// Callback when dropping a drag on top of some component
|
// Callback when dropping a drag on top of some component
|
||||||
const onDrop = e => {
|
const onDrop = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (dropTarget && dropMode) {
|
if (dropInfo) {
|
||||||
builderStore.actions.moveComponent(dragTarget, dropTarget, dropMode)
|
builderStore.actions.moveComponent(
|
||||||
|
dragInfo.target,
|
||||||
|
dropInfo.target,
|
||||||
|
dropInfo.mode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +175,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={dropMode === "inside" ? dropTarget : null}
|
componentId={dropInfo?.mode === "inside" ? dropInfo.target : null}
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
color="var(--spectrum-global-color-static-green-500)"
|
||||||
zIndex="930"
|
zIndex="930"
|
||||||
transition
|
transition
|
||||||
|
@ -180,9 +183,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DNDPositionIndicator
|
<DNDPositionIndicator
|
||||||
componentId={dropTarget}
|
|
||||||
{dropInfo}
|
{dropInfo}
|
||||||
mode={dropMode}
|
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
color="var(--spectrum-global-color-static-green-500)"
|
||||||
zIndex="940"
|
zIndex="940"
|
||||||
transition
|
transition
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import Indicator from "./Indicator.svelte"
|
import Indicator from "./Indicator.svelte"
|
||||||
|
|
||||||
export let componentId
|
|
||||||
export let dropInfo
|
export let dropInfo
|
||||||
export let mode
|
|
||||||
export let zIndex
|
export let zIndex
|
||||||
export let color
|
export let color
|
||||||
export let transition
|
export let transition
|
||||||
|
|
||||||
$: dimensions = getDimensions(dropInfo?.bounds, mode)
|
$: dimensions = getDimensions(dropInfo?.bounds, dropInfo?.mode)
|
||||||
$: prefix = mode === "above" ? "Above" : "Below"
|
$: prefix = dropInfo?.mode === "above" ? "Above" : "Below"
|
||||||
$: text = `${prefix} ${dropInfo?.name}`
|
$: text = `${prefix} ${dropInfo?.name}`
|
||||||
|
|
||||||
const getDimensions = (bounds, mode) => {
|
const getDimensions = (bounds, mode) => {
|
||||||
|
@ -25,8 +23,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key `${componentId}-${mode}`}
|
{#key `${dropInfo?.target}-${dropInfo?.mode}`}
|
||||||
{#if dimensions && mode !== "inside"}
|
{#if dimensions && dropInfo?.mode !== "inside"}
|
||||||
<Indicator
|
<Indicator
|
||||||
left={dimensions.left}
|
left={dimensions.left}
|
||||||
top={dimensions.top}
|
top={dimensions.top}
|
||||||
|
|
Loading…
Reference in New Issue