Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/drop-position-collapse-indirection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@portabletext/editor': patch
---

fix: keep `drag.dragover` cheap and depth-aware
66 changes: 66 additions & 0 deletions packages/editor/src/behaviors/behavior.core.dnd.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {EditorSnapshot} from '../editor/editor-snapshot'
import {getCompoundClientRect} from '../internal-utils/compound-client-rect'
import {getEnclosingBlock} from '../node-traversal/get-enclosing-block'
import {getDragSelection} from '../selectors/drag-selection'
import {getSelectedBlocks} from '../selectors/selector.get-selected-blocks'
import {isOverlappingSelection} from '../selectors/selector.is-overlapping-selection'
import {isSelectingEntireBlocks} from '../selectors/selector.is-selecting-entire-blocks'
import {pathEquals} from '../slate/path/path-equals'
import {comparePoints} from '../slate/point/compare-points'
import {isCollapsedRange} from '../slate/range/is-collapsed-range'
import {rangeEdges} from '../slate/range/range-edges'
Expand Down Expand Up @@ -246,6 +248,70 @@ export const coreDndBehaviors = [
actions: [],
}),

/**
* Suppress the browser's native dragover caret while an internal drag is
* moving entire blocks onto a block that isn't part of the drag origin.
* The drop indicator (rendered by `useDropPosition`) takes over the visual
* feedback for the drop target.
*
* Consumer plugins can override `drag.dragover` with a higher-priority
* behavior to opt out (e.g. return `forward(event)` to keep the native
* caret).
*/
defineBehavior({
on: 'drag.dragover',
guard: ({snapshot, event}) => {
const dragOrigin = event.dragOrigin
if (!dragOrigin) {
return false
}

const dragSelection = getDragSelection({
eventSelection: dragOrigin.selection,
snapshot,
})
const draggingEntireBlocks = isSelectingEntireBlocks({
...snapshot,
context: {
...snapshot.context,
selection: dragSelection,
},
})

if (!draggingEntireBlocks) {
return false
}

const dropFocusBlock = getEnclosingBlock(
snapshot,
event.position.selection.focus.path,
)

if (!dropFocusBlock) {
return false
}

const draggedBlocks = getSelectedBlocks({
...snapshot,
context: {
...snapshot.context,
selection: dragSelection,
},
})

if (
draggedBlocks.some((block) =>
pathEquals(block.path, dropFocusBlock.path),
)
) {
return false
}

return true
},
actions: [],
}),

/**
* If the drop position overlaps the drag origin, then the event should be
* cancelled.
Expand Down
129 changes: 0 additions & 129 deletions packages/editor/src/behaviors/behavior.core.drop-position.ts

This file was deleted.

6 changes: 4 additions & 2 deletions packages/editor/src/editor/Editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export const PortableTextEditable = forwardRef<
rangeDecorationsActor,
(s) => s.context.decorate?.fn,
)
const dropPosition = useDropPosition()
const {dropPosition, updateDropPosition} = useDropPosition()

useEffect(() => {
rangeDecorationsActor.send({
Expand Down Expand Up @@ -836,6 +836,8 @@ export const PortableTextEditable = forwardRef<
return
}

updateDropPosition(position)

editorActor.send({
type: 'behavior event',
behaviorEvent: {
Expand All @@ -853,7 +855,7 @@ export const PortableTextEditable = forwardRef<
// Prevent Slate from handling the event
return true
},
[onDragOver, editorActor, slateEditor],
[onDragOver, editorActor, slateEditor, updateDropPosition],
)

const handleDrop = useCallback(
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/editor/render.block-object.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {PortableTextObject} from '@portabletext/schema'
import {useContext, useRef, type ReactElement} from 'react'
import type {DropPosition} from '../behaviors/behavior.core.drop-position'
import {serializePath} from '../paths/serialize-path'
import type {LeafConfig} from '../renderers/renderer.types'
import type {Path} from '../slate/interfaces/path'
Expand All @@ -11,6 +10,7 @@ import {RenderDefaultBlockObject} from './render.default-object'
import {DropIndicator} from './render.drop-indicator'
import {RenderLeafConfig} from './render.leaf-config'
import {SelectionStateContext} from './selection-state-context'
import type {DropPosition} from './use-drop-position'

export function RenderBlockObject(props: {
attributes: RenderElementProps['attributes']
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/editor/render.element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type {
import {isTextBlock} from '@portabletext/schema'
import {useSelector} from '@xstate/react'
import {useContext, type ReactElement} from 'react'
import type {DropPosition} from '../behaviors/behavior.core.drop-position'
import {isInline} from '../node-traversal/is-inline'
import type {ContainerConfig} from '../renderers/renderer.types'
import {lookupContainer} from '../schema/lookup-container'
Expand All @@ -27,6 +26,7 @@ import {RenderInlineObject} from './render.inline-object'
import {useLeafConfig} from './render.leaf-config'
import {RenderTextBlock} from './render.text-block'
import {resolveElementDropPosition} from './resolve-element-drop-position'
import type {DropPosition} from './use-drop-position'

export function RenderElement(props: {
attributes: RenderElementProps['attributes']
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/src/editor/render.text-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
PortableTextTextBlock,
} from '@portabletext/schema'
import {useContext, useRef, type ReactElement} from 'react'
import type {DropPosition} from '../behaviors/behavior.core.drop-position'
import {serializePath} from '../paths/serialize-path'
import type {Path} from '../slate/interfaces/path'
import type {RenderElementProps} from '../slate/react/components/editable'
Expand All @@ -20,6 +19,7 @@ import type {EditorSchema} from './editor-schema'
import {DropIndicator} from './render.drop-indicator'
import {SelectionStateContext} from './selection-state-context'
import {useBlockSubSchema} from './use-block-sub-schema'
import type {DropPosition} from './use-drop-position'

export function RenderTextBlock(props: {
attributes: RenderElementProps['attributes']
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {DropPosition} from '../behaviors/behavior.core.drop-position'
import type {Path} from '../slate/interfaces/path'
import {pathEquals} from '../slate/path/path-equals'
import type {DropPosition} from './use-drop-position'

/**
* Returns the `position` of a drop hover when the hover targets the given
Expand Down
Loading
Loading