diff --git a/frontend/src/lib/assets/app.css b/frontend/src/lib/assets/app.css
index 99bc17ae8e..6a386c559d 100644
--- a/frontend/src/lib/assets/app.css
+++ b/frontend/src/lib/assets/app.css
@@ -145,3 +145,23 @@ svelte-virtual-list-contents > * + * {
rgba(255, 69, 58, 0.2) 20px
);
}
+
+.bg-draggedover {
+ background-image: repeating-linear-gradient(
+ -45deg,
+ rgba(0, 0, 128, 0.2),
+ rgba(0, 0, 192, 0.2) 10px,
+ rgba(0, 0, 128, 0.2) 10px,
+ rgba(0, 0, 192, 0.2) 20px
+ );
+}
+
+.bg-draggedover-dark {
+ background-image: repeating-linear-gradient(
+ -45deg,
+ rgba(0, 0, 128, 0.6),
+ rgba(0, 0, 192, 0.6) 10px,
+ rgba(0, 0, 128, 0.6) 10px,
+ rgba(0, 0, 192, 0.6) 20px
+ );
+}
diff --git a/frontend/src/lib/components/apps/editor/AppEditor.svelte b/frontend/src/lib/components/apps/editor/AppEditor.svelte
index fe3faab844..9eb1afc069 100644
--- a/frontend/src/lib/components/apps/editor/AppEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/AppEditor.svelte
@@ -19,11 +19,11 @@
import AppEditorHeader from './AppEditorHeader.svelte'
import GridEditor from './GridEditor.svelte'
- import { Button, Tab } from '$lib/components/common'
+ import { Alert, Button, Tab } from '$lib/components/common'
import TabContent from '$lib/components/common/tabs/TabContent.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import { userStore, workspaceStore } from '$lib/stores'
- import { classNames, encodeState, sendUserToast } from '$lib/utils'
+ import { classNames, encodeState, getModifierKey, sendUserToast } from '$lib/utils'
import AppPreview from './AppPreview.svelte'
import ComponentList from './componentsPanel/ComponentList.svelte'
import ContextPanel from './contextPanel/ContextPanel.svelte'
@@ -693,7 +693,15 @@
{#if $componentActive}
+ >
+
+
{/if}
diff --git a/frontend/src/lib/components/apps/editor/GridEditor.svelte b/frontend/src/lib/components/apps/editor/GridEditor.svelte
index 926c5fe26f..c7e93d55dc 100644
--- a/frontend/src/lib/components/apps/editor/GridEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/GridEditor.svelte
@@ -1,13 +1,21 @@
@@ -134,10 +190,37 @@
push(history, $app)
$app.grid = e.detail
}}
+ root
let:dataItem
let:hidden
let:overlapped
+ let:moveMode
+ let:componentDraggedId
cols={columnConfiguration}
+ on:dropped={(e) => {
+ const { id, overlapped, x, y } = e.detail
+
+ const overlappedComponent = findGridItem($app, overlapped)
+
+ if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
+ return
+ }
+
+ if (!overlapped) {
+ return
+ }
+
+ if (id === overlapped) {
+ return
+ }
+
+ moveComponentBetweenSubgrids(
+ id,
+ overlapped,
+ subGridIndexKey(overlappedComponent?.data?.type, overlapped, $worldStore),
+ { x, y }
+ )
+ }}
>
{
handleFillHeight(dataItem.id)
}}
- overlapped={overlapped !== undefined}
+ {overlapped}
+ {moveMode}
+ {componentDraggedId}
/>
diff --git a/frontend/src/lib/components/apps/editor/SubGridEditor.svelte b/frontend/src/lib/components/apps/editor/SubGridEditor.svelte
index 30b5d76b86..5e16b51a3f 100644
--- a/frontend/src/lib/components/apps/editor/SubGridEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/SubGridEditor.svelte
@@ -3,10 +3,19 @@
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
import { twMerge } from 'tailwind-merge'
- import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
+ import { columnConfiguration, gridColumns, isFixed, toggleFixed } from '../gridUtils'
import Grid from '../svelte-grid/Grid.svelte'
import type { AppEditorContext, AppViewerContext, GridItem } from '../types'
- import { expandGriditem, findGridItem, maxHeight, selectId } from './appUtils'
+ import {
+ expandGriditem,
+ findGridItem,
+ findGridItemParentGrid,
+ insertNewGridItem,
+ isContainer,
+ maxHeight,
+ selectId,
+ subGridIndexKey
+ } from './appUtils'
import Component from './component/Component.svelte'
import ComponentWrapper from './component/ComponentWrapper.svelte'
import GridViewer from './GridViewer.svelte'
@@ -15,6 +24,7 @@
export let containerHeight: number | undefined = undefined
export let containerWidth: number | undefined = undefined
let classes = ''
+
export { classes as class }
export let style = ''
export let noPadding = false
@@ -34,7 +44,8 @@
mode,
parentWidth,
breakpoint,
- allIdsInPath
+ allIdsInPath,
+ worldStore
} = getContext
('AppViewerContext')
const editorContext = getContext('AppEditorContext')
@@ -68,6 +79,98 @@
let container: HTMLElement | undefined = undefined
$: maxRow = maxHeight($app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
+
+ export function moveComponentBetweenSubgrids(
+ componentId: string,
+ parentComponentId: string,
+ subGridIndex: number,
+ position?: { x: number; y: number }
+ ) {
+ // Find the component in the source subgrid
+ const component = findGridItem($app, componentId)
+
+ if (!component) {
+ return
+ }
+
+ let parentGrid = findGridItemParentGrid($app, component.id)
+
+ if (parentGrid) {
+ $app.subgrids &&
+ ($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
+ (item) => item.id !== component?.id
+ ))
+ } else {
+ $app.grid = $app.grid.filter((item) => item.id !== component?.id)
+ }
+
+ const gridItem = component
+
+ insertNewGridItem(
+ $app,
+ (id) => ({ ...gridItem.data, id }),
+ { parentComponentId: parentComponentId, subGridIndex: subGridIndex },
+ Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
+ component.id,
+ position,
+ undefined,
+ undefined,
+ undefined,
+ true
+ )
+
+ // Update the app state
+ $app = { ...$app }
+
+ if (parentGrid) {
+ $focusedGrid = {
+ parentComponentId,
+ subGridIndex
+ }
+
+ $selectedComponent = [parentComponentId]
+ } else {
+ $focusedGrid = undefined
+ }
+ }
+
+ export function moveToRoot(componentId: string, position?: { x: number; y: number }) {
+ // Find the component in the source subgrid
+ const component = findGridItem($app, componentId)
+
+ if (!component) {
+ return
+ }
+
+ let parentGrid = findGridItemParentGrid($app, component.id)
+
+ if (parentGrid) {
+ $app.subgrids &&
+ ($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
+ (item) => item.id !== component?.id
+ ))
+ } else {
+ $app.grid = $app.grid.filter((item) => item.id !== component?.id)
+ }
+
+ const gridItem = component
+
+ insertNewGridItem(
+ $app,
+ (id) => ({ ...gridItem.data, id }),
+ undefined,
+ Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
+ component.id,
+ position,
+ undefined,
+ undefined,
+ undefined,
+ true
+ )
+
+ // Update the app state
+ $app = { ...$app }
+ }
{
+ const { id, overlapped, x, y } = e.detail
+
+ if (!overlapped) {
+ moveToRoot(id, { x, y })
+ } else {
+ const overlappedComponent = findGridItem($app, overlapped)
+
+ if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
+ return
+ }
+ if (!overlapped) {
+ return
+ }
+
+ if (id === overlapped) {
+ return
+ }
+
+ moveComponentBetweenSubgrids(
+ id,
+ overlapped,
+ subGridIndexKey(overlappedComponent?.data?.type, overlapped, $worldStore),
+ { x, y }
+ )
+ }
+ }}
>
diff --git a/frontend/src/lib/components/apps/editor/appUtils.ts b/frontend/src/lib/components/apps/editor/appUtils.ts
index 602a5bad0a..998d3d8a1c 100644
--- a/frontend/src/lib/components/apps/editor/appUtils.ts
+++ b/frontend/src/lib/components/apps/editor/appUtils.ts
@@ -20,7 +20,6 @@ import {
import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
import type { Output, World } from '../rx'
-import gridHelp from '../svelte-grid/utils/helper'
import type { FilledItem, Size } from '../svelte-grid/types'
import type {
StaticAppInput,
@@ -35,6 +34,7 @@ import { deepMergeWithPriority } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { getNextId } from '$lib/components/flows/idUtils'
import { enterpriseLicense } from '$lib/stores'
+import gridHelp from '../svelte-grid/utils/helper'
export function findComponentSettings(app: App, id: string | undefined) {
if (!id) return undefined
@@ -245,7 +245,8 @@ export function createNewGridItem(
columns?: Record
,
initialPosition: { x: number; y: number } = { x: 0, y: 0 },
recOverride?: Record,
- fixed?: boolean
+ fixed?: boolean,
+ shouldNotFindSpace?: boolean
): GridItem {
const newComponent = {
fixed: fixed ?? false,
@@ -272,11 +273,28 @@ export function createNewGridItem(
h: rec.h
}
} else {
- newItem[column] = columns[column]
+ newItem[column] = {
+ ...columns[column],
+ x: initialPosition.x,
+ y: initialPosition.y
+ }
}
- const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
- newItem[column] = { ...newItem[column], ...position }
+ let shouldComputePosition: boolean = false
+
+ // Fallback to avoid component disapearing
+ if (initialPosition.x === undefined || initialPosition.y === undefined) {
+ newItem[column].x = 0
+ newItem[column].y = 0
+ shouldComputePosition = true
+ }
+
+ // Either the final position is controlled using initialPosition or the position is computed because the component positions are wrong
+ if (!shouldNotFindSpace || shouldComputePosition) {
+ const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
+
+ newItem[column] = { ...newItem[column], ...position }
+ }
})
return newItem
@@ -392,7 +410,8 @@ export function insertNewGridItem(
initialPosition: { x: number; y: number } = { x: 0, y: 0 },
recOverride?: Record,
keepSubgrids?: boolean,
- fixed?: boolean
+ fixed?: boolean,
+ shouldNotFindSpace?: boolean
): string {
const id = keepId ?? getNextGridItemId(app)
@@ -439,7 +458,16 @@ export function insertNewGridItem(
let grid = focusedGrid ? app.subgrids[key!] : app.grid
- const newItem = createNewGridItem(grid, id, data, columns, initialPosition, recOverride, fixed)
+ const newItem = createNewGridItem(
+ grid,
+ id,
+ data,
+ columns,
+ initialPosition,
+ recOverride,
+ fixed,
+ shouldNotFindSpace
+ )
grid.push(newItem)
return id
}
@@ -1135,3 +1163,120 @@ export function setUpTopBarComponentContent(id: string, app: App) {
}
)
}
+
+export function isContainer(type: string): boolean {
+ return (
+ type === 'containercomponent' ||
+ type === 'tabscomponent' ||
+ type === 'verticalsplitpanescomponent' ||
+ type === 'horizontalsplitpanescomponent' ||
+ type === 'steppercomponent' ||
+ type === 'listcomponent' ||
+ type === 'decisiontreecomponent'
+ )
+}
+
+export function subGridIndexKey(type: string | undefined, id: string, world: World): number {
+ switch (type) {
+ case 'containercomponent':
+ case 'verticalsplitpanescomponent':
+ case 'horizontalsplitpanescomponent':
+ case 'listcomponent':
+ return 0
+ case 'tabscomponent': {
+ return (world?.outputsById?.[id]?.selectedTabIndex?.peak() as number) ?? 0
+ }
+ case 'steppercomponent': {
+ return (world?.outputsById?.[id]?.currentStepIndex?.peak() as number) ?? 0
+ }
+ case 'decisiontreecomponent': {
+ return (world?.outputsById?.[id]?.currentNodeIndex?.peak() as number) ?? 0
+ }
+ }
+
+ return 0
+}
+
+export function computePosition(
+ clientX: number,
+ clientY: number,
+ xPerPx: number,
+ yPerPx: number,
+ overlapped?: string,
+ element?: HTMLElement
+) {
+ const overlappedElement = overlapped
+ ? document.getElementById(`component-${overlapped}`)
+ : document.getElementById('root-grid')
+
+ const xRelativeToElement = element ? clientX - element.getBoundingClientRect().left : 0
+ const yRelativeToElement = element ? clientY - element.getBoundingClientRect().top : 0
+
+ const xRelativeToOverlappedElement = overlappedElement
+ ? clientX - overlappedElement.getBoundingClientRect().left - xRelativeToElement
+ : 0
+ const yRelativeToOverlappedElement = overlappedElement
+ ? clientY - overlappedElement.getBoundingClientRect().top - yRelativeToElement
+ : 0
+
+ const gridX = Math.max(Math.round(xRelativeToOverlappedElement / xPerPx) ?? 0, 0)
+ const gridY = Math.max(Math.round(yRelativeToOverlappedElement / yPerPx) ?? 0, 0)
+
+ return {
+ x: gridX,
+ y: gridY
+ }
+}
+
+export function getDeltaYByComponent(type: string) {
+ switch (type) {
+ case 'steppercomponent': {
+ return '36px + 0.5rem'
+ }
+ case 'tabscomponent': {
+ return '32px'
+ }
+ default:
+ return '0px'
+ }
+}
+
+export function getDeltaXByComponent(type: string) {
+ switch (type) {
+ case 'steppercomponent': {
+ return '0.5rem'
+ }
+ case 'tabscomponent': {
+ return '0px'
+ }
+ default:
+ return '0px'
+ }
+}
+
+export type GridShadow = {
+ x: number
+ y: number
+ xPerPx: number
+ yPerPx: number
+ w: number
+ h: number
+}
+
+export function areShadowsTheSame(
+ shadow1: GridShadow | undefined,
+ shadow2: GridShadow | undefined
+) {
+ if (!shadow1 || !shadow2) {
+ return false
+ }
+
+ return (
+ shadow1.x === shadow2.x &&
+ shadow1.y === shadow2.y &&
+ shadow1.xPerPx === shadow2.xPerPx &&
+ shadow1.yPerPx === shadow2.yPerPx &&
+ shadow1.w === shadow2.w &&
+ shadow1.h === shadow2.h
+ )
+}
diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte
index 6561c1b1a5..56c39ed072 100644
--- a/frontend/src/lib/components/apps/editor/component/Component.svelte
+++ b/frontend/src/lib/components/apps/editor/component/Component.svelte
@@ -81,6 +81,7 @@
import AppRecomputeAll from '../../components/display/AppRecomputeAll.svelte'
import AppUserResource from '../../components/inputs/AppUserResource.svelte'
import { Anchor } from 'lucide-svelte'
+ import { findGridItemParentGrid, isContainer } from '../appUtils'
export let component: AppComponent
export let selected: boolean
@@ -88,9 +89,10 @@
export let render: boolean
export let hidden: boolean
export let fullHeight: boolean
- export let overlapped: boolean = false
-
- const { mode, app, hoverStore, connectingInput, selectedComponent } =
+ export let overlapped: string | undefined = undefined
+ export let moveMode: string | undefined = undefined
+ export let componentDraggedId: string | undefined = undefined
+ const { mode, app, hoverStore, connectingInput } =
getContext('AppViewerContext')
const editorContext = getContext('AppEditorContext')
@@ -121,6 +123,36 @@
}
}, 50)
}
+
+ function componentDraggedIsNotChild(componentDraggedId: string, componentId: string) {
+ let parentGrid = findGridItemParentGrid($app, componentDraggedId)
+
+ return !parentGrid?.startsWith(`${componentId}-`)
+ }
+
+ function areOnTheSameSubgrid(componentDraggedId: string, componentId: string) {
+ return (
+ findGridItemParentGrid($app, componentDraggedId) === findGridItemParentGrid($app, componentId)
+ )
+ }
+
+ let cachedComponentDraggedIsNotChild: boolean | undefined
+ let cachedAreOnTheSameSubgrid: boolean | undefined
+
+ function updateCache(componentDraggedId: string | undefined) {
+ if (componentDraggedId) {
+ cachedComponentDraggedIsNotChild = componentDraggedIsNotChild(
+ componentDraggedId,
+ component.id
+ )
+ cachedAreOnTheSameSubgrid = areOnTheSameSubgrid(componentDraggedId, component.id)
+ } else {
+ cachedComponentDraggedIsNotChild = undefined
+ cachedAreOnTheSameSubgrid = undefined
+ }
+ }
+
+ $: updateCache(componentDraggedId)
@@ -139,18 +171,23 @@
hidden && $mode === 'preview' ? 'hidden' : ''
)}
>
- {#if locked && componentActive && $componentActive && $selectedComponent?.[0] !== component.id && overlapped}
+ {#if locked && componentActive && $componentActive && moveMode === 'move' && componentDraggedId && componentDraggedId !== component.id && cachedAreOnTheSameSubgrid}
-
Anchored: The component cannot be moved
+
Anchored: The component cannot be moved.
+ {:else if moveMode === 'insert' && isContainer(component.type) && componentDraggedId && componentDraggedId !== component.id && cachedComponentDraggedIsNotChild}
+
{/if}
{#if $mode !== 'preview'}
+ import { writable } from 'svelte/store'
+
+ const componentDraggedIdStore = writable(undefined)
+ const componentDraggedParentIdStore = writable(undefined)
+ const overlappedStore = writable(undefined)
+ const fakeShadowStore = writable(undefined)
+ const isCtrlOrMetaPressedStore = writable(false)
+
+
-
+
+
+
+
+ {#if $isCtrlOrMetaPressedStore && root && $overlappedStore !== $componentDraggedParentIdStore}
+
+ {#if $overlappedStore === undefined && $componentDraggedIdStore && $fakeShadowStore}
+ {@const columnGap = gapX}
+
+ {@const containerBorder = 0.5 * 16}
+
+ {@const gridTotalWidth = containerWidth ? containerWidth - 2 * containerBorder : 0}
+
+ {@const availableWidth = gridTotalWidth - 11 * columnGap}
+
+ {@const columnWidthPx = availableWidth / 12}
+
+ {@const maxX = Math.floor(availableWidth / columnWidthPx) - $fakeShadowStore.w}
+
+
+ {/if}
+ {/if}
+
{#each sortedItems as item (item.id)}
{#if item[getComputedCols] != undefined}
+ {#if $isCtrlOrMetaPressedStore && item.id === $overlappedStore && $componentDraggedIdStore && $componentDraggedParentIdStore !== item.id && $fakeShadowStore}
+ {@const columnGap = gapX}
+
+ {@const containerBorder = 0.5 * 16}
+
+ {@const gridTotalWidth = containerWidth ? containerWidth - 2 * containerBorder : 0}
+
+ {@const availableWidth = gridTotalWidth - 11 * columnGap}
+
+ {@const columnWidthPx = availableWidth / 12}
+
+ {@const maxX = Math.floor(availableWidth / columnWidthPx) - $fakeShadowStore.w}
+
+
+ {/if}
+
handleInitMove(item.id)}
on:move={handleMove}
bind:shadow={shadows[item.id]}
bind:this={moveResizes[item.id]}
on:repaint={handleRepaint}
+ on:resizeStart={() => (resizing = true)}
+ on:resizeEnd={() => (resizing = false)}
onTop={Boolean(allIdsInPath?.includes(item.id))}
id={item.id}
{xPerPx}
{yPerPx}
+ fakeShadow={$fakeShadowStore}
+ on:dropped={(e) => {
+ $componentDraggedIdStore = undefined
+ $componentDraggedParentIdStore = undefined
+ $overlappedStore = undefined
+ $fakeShadowStore = undefined
+ lastDetail = undefined
+
+ if (!$isCtrlOrMetaPressedStore) {
+ return
+ }
+
+ dispatch('dropped', e.detail)
+ }}
width={xPerPx == 0
? 0
: Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) * xPerPx -
@@ -205,10 +464,18 @@
{sensor}
container={scroller}
nativeContainer={container}
- {overlapped}
+ overlapped={$overlappedStore}
+ moveMode={$isCtrlOrMetaPressedStore ? 'insert' : 'move'}
+ type={item.data['type']}
>
{#if item[getComputedCols]}
-
+
{/if}
{/if}
diff --git a/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte b/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte
index 7c8654e60c..c7fcc94d04 100644
--- a/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte
+++ b/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte
@@ -3,6 +3,13 @@
import type { AppEditorContext, AppViewerContext } from '../types'
import { writable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
+ import {
+ computePosition,
+ findGridItemParentGrid,
+ isContainer,
+ type GridShadow
+ } from '../editor/appUtils'
+ import { throttle } from './utils/other'
const dispatch = createEventDispatcher()
@@ -28,9 +35,12 @@
export let onTop
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
export let overlapped: string | undefined = undefined
+ export let moveMode: 'move' | 'insert' = 'move'
+ export let type: string | undefined = undefined
+ export let fakeShadow: GridShadow | undefined = undefined
const ctx = getContext
('AppEditorContext')
- const { mode } = getContext('AppViewerContext')
+ const { mode, app } = getContext('AppViewerContext')
const scale = ctx ? ctx.scale : writable(100)
@@ -75,6 +85,7 @@
x: (moveX / $scale) * 100 - initX,
y: (moveY / $scale) * 100 - initY
}
+
dispatch('move', { cordDiff, clientY: clientY })
}
return x
@@ -168,7 +179,7 @@
}
let dragClosure: (() => void) | undefined = undefined
- const pointerdown = ({ clientX, clientY, pageX, pageY }) => {
+ const pointerdown = ({ clientX, clientY }) => {
dragClosure = () => {
dragClosure = undefined
ctx.componentActive.set(true)
@@ -223,16 +234,45 @@
}
}
+ // Shared state for the shadow position
+ let currentShadowPosition:
+ | { x: number; y: number; xPerPx: number; yPerPx: number; h: number; w: number }
+ | undefined = undefined
+ let currentIntersectingElementId: string | undefined = undefined
+
+ let moving: boolean = false
+
const pointermove = (event) => {
dragClosure && dragClosure()
event.preventDefault()
event.stopPropagation()
event.stopImmediatePropagation()
+ moving = true
+
const { clientX, clientY } = event
const cordDiff = { x: (clientX / $scale) * 100 - initX, y: (clientY / $scale) * 100 - initY }
- dispatch('move', { cordDiff, clientY })
+ if (moveMode === 'move') {
+ dispatch('move', {
+ cordDiff,
+ clientY,
+ intersectingElement: undefined,
+ shadow: undefined
+ })
+
+ return
+ }
+
+ throttledComputeShadow(clientX, clientY)
+
+ dispatch('move', {
+ cordDiff,
+ clientY,
+ intersectingElement: currentIntersectingElementId,
+ shadow: currentShadowPosition,
+ overlapped
+ })
}
export function updateMove(newCoordDiff, clientY) {
@@ -274,6 +314,56 @@
update()
}
}
+
+ let element: HTMLElement | undefined = undefined
+
+ function computeShadow(clientX: number, clientY: number) {
+ const elementsAtPoint = document.elementsFromPoint(clientX, clientY)
+ const intersectingElement = elementsAtPoint.find(
+ (el) =>
+ el.id !== divId &&
+ el.classList.contains('svlt-grid-item') &&
+ el.getAttribute('data-iscontainer') === 'true'
+ )
+
+ const newOverlapped = intersectingElement ? intersectingElement?.id.split('-')[1] : undefined
+
+ const container = newOverlapped
+ ? intersectingElement?.querySelector('.svlt-grid-container')
+ : document.getElementById('root-grid')
+
+ const xPerPxComputed = Number(container?.getAttribute('data-xperpx')) ?? xPerPx
+
+ const position = computePosition(
+ clientX,
+ clientY,
+ xPerPxComputed,
+ yPerPx,
+ newOverlapped,
+ element
+ )
+
+ if (overlapped !== newOverlapped) {
+ currentShadowPosition = undefined
+ } else {
+ // Update shared state
+ currentShadowPosition = {
+ x: position.x,
+ y: position.y,
+ xPerPx: xPerPxComputed,
+ yPerPx,
+ h: item.h,
+ w: item.w
+ }
+ }
+
+ currentIntersectingElementId = newOverlapped
+ }
+
+ const throttledComputeShadow = throttle((clientX, clientY) => {
+ computeShadow(clientX, clientY)
+ }, 50)
+
const pointerup = (e) => {
ctx.componentActive.set(false)
stopAutoscroll()
@@ -281,11 +371,29 @@
window.removeEventListener('pointerdown', pointerdown)
window.removeEventListener('pointermove', pointermove)
window.removeEventListener('pointerup', pointerup)
+
if (!dragClosure) {
repaint(true, true)
} else {
dragClosure = undefined
}
+
+ if (!moving) {
+ return
+ }
+
+ const parent = findGridItemParentGrid($app, id)
+
+ if (overlapped && (overlapped === parent || parent?.startsWith(overlapped))) {
+ return
+ }
+
+ dispatch('dropped', {
+ id,
+ overlapped,
+ x: fakeShadow?.x,
+ y: fakeShadow?.y
+ })
}
let resizeInitPos = { x: 0, y: 0 }
@@ -315,6 +423,7 @@
window.addEventListener('pointermove', resizePointerMove)
window.addEventListener('pointerup', resizePointerUp)
+ dispatch('resizeStart')
}
const resizePointerMove = ({ pageX, pageY }) => {
@@ -350,15 +459,36 @@
window.removeEventListener('pointermove', resizePointerMove)
window.removeEventListener('pointerup', resizePointerUp)
+ dispatch('resizeEnd')
+ }
+
+ function shouldDisplayShadow(moveMode: 'insert' | 'move', overlapped: string | undefined) {
+ if (moveMode === 'move') {
+ return true
+ }
+
+ const parent = findGridItemParentGrid($app, id)
+
+ if (parent === undefined) {
+ return overlapped === undefined
+ } else if (overlapped) {
+ return parent.startsWith(overlapped)
+ }
+
+ return false
}
+
-
resizePointerDown(e, 'vertical')} />
-
resizePointerDown(e, 'horizontal')} />
-
resizePointerDown(e, 'both')} />
+ {#if moveMode === 'move'}
+
resizePointerDown(e, 'vertical')}
+ />
+
resizePointerDown(e, 'horizontal')}
+ />
+
resizePointerDown(e, 'both')} />
+ {/if}
{#if xPerPx > 0 && (active || trans) && shadow}