@portabletext/plugin-dnd 2.0.7 → 2.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -27
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.dnd.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.dnd.tsx"],"mappings":";;;;;;;KA8CY;EACV,MAAM;EACN;;;;;;;;;;;;;;;;;;;;iBA2Tc,YAAY;EAAQ,WAAW;oBAAU,IAAA;;;;;;;;;;;iBAyCzC,gBACd,MAAM,OACL"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useEditor } from "@portabletext/editor";
|
|
|
3
3
|
import { defineBehavior, effect, forward } from "@portabletext/editor/behaviors";
|
|
4
4
|
import { BehaviorPlugin } from "@portabletext/editor/plugins";
|
|
5
5
|
import { getFocusBlock, getFocusInlineObject, getFocusSpan, getFocusTextBlock, getFragment, getSelectedBlocks, getSelectionEndBlock, getSelectionStartBlock, isOverlappingSelection, isSelectingEntireBlocks, isSelectionCollapsed, isSelectionExpanded } from "@portabletext/editor/selectors";
|
|
6
|
-
import { getBlockEndPoint, getBlockStartPoint, isEmptyTextBlock, isEqualPaths, isEqualSelectionPoints, isKeyedSegment, isSelectionCollapsed as isSelectionCollapsed$1 } from "@portabletext/editor/utils";
|
|
6
|
+
import { getBlockEndPoint, getBlockStartPoint, isEmptyTextBlock, isEqualPaths, isEqualSelectionPoints, isEqualSelections, isKeyedSegment, isSelectionCollapsed as isSelectionCollapsed$1 } from "@portabletext/editor/utils";
|
|
7
7
|
import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
/**
|
|
@@ -116,14 +116,28 @@ function createDropPositionStore(getEditorElement) {
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
-
/**
|
|
120
|
-
* The behaviors observe the public `drag.*` events and `forward` every
|
|
121
|
-
* event they handle: consumer behaviors run before the editor's own drag
|
|
122
|
-
* handling, so omitting the forward would swallow the event and break the
|
|
123
|
-
* drag itself. (The editor's internal drop-position tracking gets away
|
|
124
|
-
* without forwarding because it registers below core priority.)
|
|
125
|
-
*/
|
|
126
119
|
function createDropPositionBehaviors(setDropPosition) {
|
|
120
|
+
let dragOriginState;
|
|
121
|
+
function getDragOriginState(snapshot, dragOriginSelection) {
|
|
122
|
+
if (dragOriginState && dragOriginState.value === snapshot.context.value && isEqualSelections(dragOriginState.dragOriginSelection, dragOriginSelection)) return dragOriginState;
|
|
123
|
+
let dragSelection = getDragSelection({
|
|
124
|
+
eventSelection: dragOriginSelection,
|
|
125
|
+
snapshot
|
|
126
|
+
}), dragSnapshot = {
|
|
127
|
+
...snapshot,
|
|
128
|
+
context: {
|
|
129
|
+
...snapshot.context,
|
|
130
|
+
selection: dragSelection
|
|
131
|
+
}
|
|
132
|
+
}, draggedBlocks = getSelectedBlocks(dragSnapshot), draggedFocusBlock = getFocusBlock(dragSnapshot);
|
|
133
|
+
return dragOriginState = {
|
|
134
|
+
value: snapshot.context.value,
|
|
135
|
+
dragOriginSelection,
|
|
136
|
+
draggedBlockKeys: new Set(draggedBlocks.map((draggedBlock) => draggedBlock.node._key)),
|
|
137
|
+
draggedFocusBlockPath: draggedFocusBlock?.path,
|
|
138
|
+
draggingEntireBlocks: isSelectingEntireBlocks(dragSnapshot)
|
|
139
|
+
}, dragOriginState;
|
|
140
|
+
}
|
|
127
141
|
return [defineBehavior({
|
|
128
142
|
on: "drag.dragover",
|
|
129
143
|
guard: ({ snapshot, event }) => {
|
|
@@ -137,28 +151,13 @@ function createDropPositionBehaviors(setDropPosition) {
|
|
|
137
151
|
if (!dropFocusBlock) return !1;
|
|
138
152
|
let dragOrigin = event.dragOrigin;
|
|
139
153
|
if (!dragOrigin) return !1;
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
snapshot
|
|
143
|
-
}), dragSnapshot = {
|
|
144
|
-
...snapshot,
|
|
145
|
-
context: {
|
|
146
|
-
...snapshot.context,
|
|
147
|
-
selection: dragSelection
|
|
148
|
-
}
|
|
149
|
-
}, draggedBlocks = getSelectedBlocks(dragSnapshot), draggedFocusBlock = getFocusBlock(dragSnapshot);
|
|
150
|
-
if (draggedBlocks.some((draggedBlock) => draggedBlock.node._key === dropFocusBlock.node._key) || draggedFocusBlock !== void 0 && isEqualPaths(draggedFocusBlock.path, dropFocusBlock.path)) return {
|
|
154
|
+
let { draggedBlockKeys, draggedFocusBlockPath, draggingEntireBlocks } = getDragOriginState(snapshot, dragOrigin.selection);
|
|
155
|
+
if (draggedBlockKeys.has(dropFocusBlock.node._key) || draggedFocusBlockPath !== void 0 && isEqualPaths(draggedFocusBlockPath, dropFocusBlock.path)) return {
|
|
151
156
|
dropFocusBlock,
|
|
152
157
|
droppedInsideTextBlock: !1,
|
|
153
158
|
clear: !0
|
|
154
159
|
};
|
|
155
|
-
if (!
|
|
156
|
-
...snapshot,
|
|
157
|
-
context: {
|
|
158
|
-
...snapshot.context,
|
|
159
|
-
selection: dragSelection
|
|
160
|
-
}
|
|
161
|
-
})) return !1;
|
|
160
|
+
if (!draggingEntireBlocks) return !1;
|
|
162
161
|
let dropSnapshot = {
|
|
163
162
|
...snapshot,
|
|
164
163
|
context: {
|
|
@@ -186,7 +185,7 @@ function createDropPositionBehaviors(setDropPosition) {
|
|
|
186
185
|
}), forward(event)]]
|
|
187
186
|
}), defineBehavior({
|
|
188
187
|
on: "drag.*",
|
|
189
|
-
guard: ({ event }) => event.type !== "drag.dragover" && event.type !== "drag.drag",
|
|
188
|
+
guard: ({ event }) => event.type !== "drag.dragover" && event.type !== "drag.drag" && event.type !== "drag.dragenter",
|
|
190
189
|
actions: [({ event }) => [effect(() => {
|
|
191
190
|
setDropPosition(void 0);
|
|
192
191
|
}), forward(event)]]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n })","editor.dom","useMemo(\n () =>\n createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n }),\n [editor],\n )","createDropPositionBehaviors(store.set)","set","useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )","() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }","[store]","<BehaviorPlugin behaviors={behaviors} />","<DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>","children","DndContext.Provider","serializePath(path)","(callback: () => void) => store.subscribeKey(serializedPath, callback)","useCallback(\n (callback: () => void) => store.subscribeKey(serializedPath, callback),\n [store, serializedPath],\n )","() => store.get(serializedPath)"],"sources":["../src/drag-selection.ts","../src/plugin.dnd.tsx"],"sourcesContent":["import type {EditorSelection, EditorSnapshot} from '@portabletext/editor'\nimport {\n getFocusInlineObject,\n getFocusSpan,\n getFocusTextBlock,\n getFragment,\n getSelectionEndBlock,\n getSelectionStartBlock,\n isOverlappingSelection,\n isSelectionCollapsed,\n isSelectionExpanded,\n} from '@portabletext/editor/selectors'\nimport {getBlockEndPoint, getBlockStartPoint} from '@portabletext/editor/utils'\n\n/**\n * Given the current editor `snapshot` and an `eventSelection` representing\n * where the drag event originates from, calculate the selection in the\n * editor that should be dragged.\n *\n * Duplicated from the editor's internal `getDragSelection` rather than\n * imported: exporting it from core would add permanent public API for what\n * is an implementation detail of this plugin. Built on public selectors\n * only. Keep in sync with\n * `packages/editor/src/selectors/drag-selection.ts`.\n */\nexport function getDragSelection({\n eventSelection,\n snapshot,\n}: {\n eventSelection: NonNullable<EditorSelection>\n snapshot: EditorSnapshot\n}): NonNullable<EditorSelection> {\n let dragSelection = eventSelection\n\n const draggedInlineObject = getFocusInlineObject({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggedInlineObject) {\n return dragSelection\n }\n\n const draggingCollapsedSelection = isSelectionCollapsed({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedSpan = getFocusSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggingCollapsedSelection && draggedTextBlock && draggedSpan) {\n // Looks like we are dragging an empty span\n // Let's drag the entire block instead\n dragSelection = {\n anchor: getBlockStartPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n focus: getBlockEndPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n }\n }\n\n const selectedBlocks = getFragment(snapshot)\n\n if (\n snapshot.context.selection &&\n isSelectionExpanded(snapshot) &&\n selectedBlocks.length > 1\n ) {\n const selectionStartBlock = getSelectionStartBlock(snapshot)\n const selectionEndBlock = getSelectionEndBlock(snapshot)\n\n if (!selectionStartBlock || !selectionEndBlock) {\n return dragSelection\n }\n\n const selectionStartPoint = getBlockStartPoint({\n context: snapshot.context,\n block: selectionStartBlock,\n })\n const selectionEndPoint = getBlockEndPoint({\n context: snapshot.context,\n block: selectionEndBlock,\n })\n\n const eventSelectionInsideBlocks = isOverlappingSelection(eventSelection)({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {anchor: selectionStartPoint, focus: selectionEndPoint},\n },\n })\n\n if (eventSelectionInsideBlocks) {\n dragSelection = {\n anchor: selectionStartPoint,\n focus: selectionEndPoint,\n }\n }\n }\n\n return dragSelection\n}\n","import {useEditor, type Path} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n type Behavior,\n} from '@portabletext/editor/behaviors'\nimport {BehaviorPlugin} from '@portabletext/editor/plugins'\nimport {\n getFocusBlock,\n getFocusInlineObject,\n getFocusTextBlock,\n getSelectedBlocks,\n isSelectingEntireBlocks,\n} from '@portabletext/editor/selectors'\nimport {\n getBlockEndPoint,\n getBlockStartPoint,\n isEmptyTextBlock,\n isEqualPaths,\n isEqualSelectionPoints,\n isKeyedSegment,\n isSelectionCollapsed,\n} from '@portabletext/editor/utils'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {getDragSelection} from './drag-selection'\n\n/**\n * The block-relative position a drop would land at.\n *\n * @beta\n */\nexport type DropPosition = {\n path: Path\n position: 'start' | 'end'\n}\n\n/**\n * One store per editor: the drag behaviors below maintain the current drop\n * position, and per-path subscriber buckets make notification O(changed\n * paths) rather than O(subscribers). `dragover` fires at mousemove\n * frequency, so only the block losing and the block gaining the indicator\n * re-render.\n */\ntype DropPositionStore = {\n get: (serializedPath: string) => DropPosition['position'] | undefined\n subscribeKey: (serializedPath: string, callback: () => void) => () => void\n set: (next: DropPosition | undefined) => void\n}\n\nfunction createDropPositionStore(\n getEditorElement: () => HTMLElement | undefined,\n): DropPositionStore {\n let current:\n | {serializedPath: string; position: DropPosition['position']}\n | undefined\n let restoreCaretColor: (() => void) | undefined\n const subscribers = new Map<string, Set<() => void>>()\n\n function notify(serializedPath: string | undefined) {\n if (serializedPath === undefined) {\n return\n }\n\n const bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n return\n }\n\n for (const callback of bucket) {\n callback()\n }\n }\n\n return {\n get: (serializedPath) =>\n current?.serializedPath === serializedPath ? current.position : undefined,\n subscribeKey: (serializedPath, callback) => {\n let bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n bucket = new Set()\n subscribers.set(serializedPath, bucket)\n }\n\n bucket.add(callback)\n\n return () => {\n bucket.delete(callback)\n\n if (bucket.size === 0) {\n subscribers.delete(serializedPath)\n }\n }\n },\n set: (next) => {\n const previous = current\n\n // Swap before notifying: `useSyncExternalStore` re-reads the snapshot\n // synchronously on notification and skips the re-render when it reads\n // an unchanged (stale) value.\n current = next\n ? {serializedPath: serializePath(next.path), position: next.position}\n : undefined\n\n const editorElement = getEditorElement()\n\n if (editorElement) {\n if (current && !restoreCaretColor) {\n const previousCaretColor = editorElement.style.caretColor\n restoreCaretColor = () => {\n editorElement.style.caretColor = previousCaretColor\n }\n // The native drop caret renders at the hovered text position and\n // promises a mid-text split, but an edge indicator means the drop\n // will snap to the block edge instead.\n editorElement.style.caretColor = 'transparent'\n } else if (!current && restoreCaretColor) {\n restoreCaretColor()\n restoreCaretColor = undefined\n }\n }\n\n if (\n previous?.serializedPath === current?.serializedPath &&\n previous?.position === current?.position\n ) {\n return\n }\n\n if (previous?.serializedPath !== current?.serializedPath) {\n notify(previous?.serializedPath)\n }\n\n notify(current?.serializedPath)\n },\n }\n}\n\n/**\n * The behaviors observe the public `drag.*` events and `forward` every\n * event they handle: consumer behaviors run before the editor's own drag\n * handling, so omitting the forward would swallow the event and break the\n * drag itself. (The editor's internal drop-position tracking gets away\n * without forwarding because it registers below core priority.)\n */\nfunction createDropPositionBehaviors(\n setDropPosition: (next: DropPosition | undefined) => void,\n): Array<Behavior> {\n return [\n defineBehavior({\n on: 'drag.dragover',\n guard: ({snapshot, event}) => {\n const dropFocusBlock = getFocusBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n })\n\n if (!dropFocusBlock) {\n return false\n }\n\n const dragOrigin = event.dragOrigin\n\n if (!dragOrigin) {\n return false\n }\n\n const dragSelection = getDragSelection({\n eventSelection: dragOrigin.selection,\n snapshot,\n })\n const dragSnapshot = {\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n }\n\n const draggedBlocks = getSelectedBlocks(dragSnapshot)\n // `getSelectedBlocks` only looks at root-level children, so a drag\n // origin nested inside a container (a callout paragraph, a table\n // cell) resolves to the container, not the nested block actually\n // being dragged. `getFocusBlock` resolves the origin's own focus to\n // the same depth `dropFocusBlock` was resolved to, so comparing the\n // two catches a nested block dragged over itself too.\n const draggedFocusBlock = getFocusBlock(dragSnapshot)\n\n const selfDrop =\n draggedBlocks.some(\n (draggedBlock) =>\n draggedBlock.node._key === dropFocusBlock.node._key,\n ) ||\n (draggedFocusBlock !== undefined &&\n isEqualPaths(draggedFocusBlock.path, dropFocusBlock.path))\n\n if (selfDrop) {\n // Core cancels the drop, so no position is valid here, including\n // one left over from the previous hover.\n return {dropFocusBlock, droppedInsideTextBlock: false, clear: true}\n }\n\n const draggingEntireBlocks = isSelectingEntireBlocks({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n })\n\n if (!draggingEntireBlocks) {\n return false\n }\n\n const dropSnapshot = {\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n }\n const focusTextBlock = getFocusTextBlock(dropSnapshot)\n // A collapsed drop strictly inside a non-empty text block already\n // shows the browser's own drop caret there.\n const droppedInsideTextBlock =\n isSelectionCollapsed(event.position.selection) &&\n focusTextBlock !== undefined &&\n !isEmptyTextBlock(snapshot.context, focusTextBlock.node) &&\n getFocusInlineObject(dropSnapshot) === undefined &&\n !isEqualSelectionPoints(\n event.position.selection.focus,\n getBlockStartPoint({\n context: snapshot.context,\n block: focusTextBlock,\n }),\n ) &&\n !isEqualSelectionPoints(\n event.position.selection.focus,\n getBlockEndPoint({\n context: snapshot.context,\n block: focusTextBlock,\n }),\n )\n\n return {dropFocusBlock, droppedInsideTextBlock, clear: false}\n },\n actions: [\n ({event}, {dropFocusBlock, droppedInsideTextBlock, clear}) => [\n effect(() => {\n setDropPosition(\n clear || droppedInsideTextBlock\n ? undefined\n : {\n path: dropFocusBlock.path,\n position: event.position.block,\n },\n )\n }),\n forward(event),\n ],\n ],\n }),\n defineBehavior({\n on: 'drag.*',\n guard: ({event}) =>\n // `drag.drag` fires continuously on the drag source between\n // `dragover`s. Clearing on it would toggle the drop position (and\n // the caret-color write on the editor root) on every pointer move,\n // and each write invalidates layout for the whole page right before\n // the next drag event reads element rects: a forced reflow per move,\n // scaling with page size.\n event.type !== 'drag.dragover' && event.type !== 'drag.drag',\n actions: [\n ({event}) => [\n effect(() => {\n setDropPosition(undefined)\n }),\n forward(event),\n ],\n ],\n }),\n ]\n}\n\nconst DndContext = createContext<DropPositionStore | undefined>(undefined)\n\n/**\n * Tracks the drop position during drag and drop and serves it through\n * context. Mount inside `EditorProvider`, wrapping whatever reads the\n * position:\n *\n * ```tsx\n * <EditorProvider initialConfig={...}>\n * <DndProvider>\n * <PortableTextEditable />\n * </DndProvider>\n * </EditorProvider>\n * ```\n *\n * Reads via {@link useDropPosition} only re-render when the drop position\n * at their own path changes.\n *\n * @beta\n */\nexport function DndProvider(props: {children?: ReactNode}) {\n const editor = useEditor()\n const store = useMemo(\n () =>\n createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n }),\n [editor],\n )\n const behaviors = useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )\n\n useEffect(() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }, [store])\n\n return (\n <DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>\n )\n}\n\n/**\n * Read where a drop would land relative to the block at `path`: `'start'`,\n * `'end'`, or `undefined` when no drag is hovering this block. Re-renders\n * only when the position at this path changes.\n *\n * `path` is the keyed block path render callbacks receive, e.g.\n * `[{_key: 'b0'}]`.\n *\n * @beta\n */\nexport function useDropPosition(\n path: Path,\n): DropPosition['position'] | undefined {\n const store = useContext(DndContext)\n\n if (store === undefined) {\n throw new Error('useDropPosition must be used below a <DndProvider>')\n }\n\n // Callers typically pass a fresh `path` array every render, so the\n // serialized string, not the array, is what keeps `subscribe` stable\n // below.\n const serializedPath = serializePath(path)\n\n const subscribe = useCallback(\n (callback: () => void) => store.subscribeKey(serializedPath, callback),\n [store, serializedPath],\n )\n\n const getSnapshot = () => store.get(serializedPath)\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/**\n * Serialize a keyed path to a string using Sanity's bracket notation.\n * Duplicated from the editor's internal `serializePath`; see\n * `drag-selection.ts` for the duplication rationale.\n */\nfunction serializePath(path: Path): string {\n return path.reduce<string>((result, segment, index) => {\n if (isKeyedSegment(segment)) {\n return `${result}[_key==\"${segment._key}\"]`\n }\n\n const separator = index === 0 ? '' : '.'\n return `${result}${separator}${segment}`\n }, '')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n })","editor.dom","useMemo(\n () =>\n createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n }),\n [editor],\n )","createDropPositionBehaviors(store.set)","set","useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )","() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }","[store]","<BehaviorPlugin behaviors={behaviors} />","<DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>","children","DndContext.Provider","serializePath(path)","(callback: () => void) => store.subscribeKey(serializedPath, callback)","useCallback(\n (callback: () => void) => store.subscribeKey(serializedPath, callback),\n [store, serializedPath],\n )","() => store.get(serializedPath)"],"sources":["../src/drag-selection.ts","../src/plugin.dnd.tsx"],"sourcesContent":["import type {EditorSelection, EditorSnapshot} from '@portabletext/editor'\nimport {\n getFocusInlineObject,\n getFocusSpan,\n getFocusTextBlock,\n getFragment,\n getSelectionEndBlock,\n getSelectionStartBlock,\n isOverlappingSelection,\n isSelectionCollapsed,\n isSelectionExpanded,\n} from '@portabletext/editor/selectors'\nimport {getBlockEndPoint, getBlockStartPoint} from '@portabletext/editor/utils'\n\n/**\n * Given the current editor `snapshot` and an `eventSelection` representing\n * where the drag event originates from, calculate the selection in the\n * editor that should be dragged.\n *\n * Duplicated from the editor's internal `getDragSelection` rather than\n * imported: exporting it from core would add permanent public API for what\n * is an implementation detail of this plugin. Built on public selectors\n * only. Keep in sync with\n * `packages/editor/src/selectors/drag-selection.ts`.\n */\nexport function getDragSelection({\n eventSelection,\n snapshot,\n}: {\n eventSelection: NonNullable<EditorSelection>\n snapshot: EditorSnapshot\n}): NonNullable<EditorSelection> {\n let dragSelection = eventSelection\n\n const draggedInlineObject = getFocusInlineObject({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggedInlineObject) {\n return dragSelection\n }\n\n const draggingCollapsedSelection = isSelectionCollapsed({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedSpan = getFocusSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggingCollapsedSelection && draggedTextBlock && draggedSpan) {\n // Looks like we are dragging an empty span\n // Let's drag the entire block instead\n dragSelection = {\n anchor: getBlockStartPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n focus: getBlockEndPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n }\n }\n\n const selectedBlocks = getFragment(snapshot)\n\n if (\n snapshot.context.selection &&\n isSelectionExpanded(snapshot) &&\n selectedBlocks.length > 1\n ) {\n const selectionStartBlock = getSelectionStartBlock(snapshot)\n const selectionEndBlock = getSelectionEndBlock(snapshot)\n\n if (!selectionStartBlock || !selectionEndBlock) {\n return dragSelection\n }\n\n const selectionStartPoint = getBlockStartPoint({\n context: snapshot.context,\n block: selectionStartBlock,\n })\n const selectionEndPoint = getBlockEndPoint({\n context: snapshot.context,\n block: selectionEndBlock,\n })\n\n const eventSelectionInsideBlocks = isOverlappingSelection(eventSelection)({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {anchor: selectionStartPoint, focus: selectionEndPoint},\n },\n })\n\n if (eventSelectionInsideBlocks) {\n dragSelection = {\n anchor: selectionStartPoint,\n focus: selectionEndPoint,\n }\n }\n }\n\n return dragSelection\n}\n","import {\n useEditor,\n type EditorSelection,\n type EditorSnapshot,\n type Path,\n} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n type Behavior,\n} from '@portabletext/editor/behaviors'\nimport {BehaviorPlugin} from '@portabletext/editor/plugins'\nimport {\n getFocusBlock,\n getFocusInlineObject,\n getFocusTextBlock,\n getSelectedBlocks,\n isSelectingEntireBlocks,\n} from '@portabletext/editor/selectors'\nimport {\n getBlockEndPoint,\n getBlockStartPoint,\n isEmptyTextBlock,\n isEqualPaths,\n isEqualSelectionPoints,\n isEqualSelections,\n isKeyedSegment,\n isSelectionCollapsed,\n} from '@portabletext/editor/utils'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {getDragSelection} from './drag-selection'\n\n/**\n * The block-relative position a drop would land at.\n *\n * @beta\n */\nexport type DropPosition = {\n path: Path\n position: 'start' | 'end'\n}\n\n/**\n * One store per editor: the drag behaviors below maintain the current drop\n * position, and per-path subscriber buckets make notification O(changed\n * paths) rather than O(subscribers). `dragover` fires at mousemove\n * frequency, so only the block losing and the block gaining the indicator\n * re-render.\n */\ntype DropPositionStore = {\n get: (serializedPath: string) => DropPosition['position'] | undefined\n subscribeKey: (serializedPath: string, callback: () => void) => () => void\n set: (next: DropPosition | undefined) => void\n}\n\nfunction createDropPositionStore(\n getEditorElement: () => HTMLElement | undefined,\n): DropPositionStore {\n let current:\n | {serializedPath: string; position: DropPosition['position']}\n | undefined\n let restoreCaretColor: (() => void) | undefined\n const subscribers = new Map<string, Set<() => void>>()\n\n function notify(serializedPath: string | undefined) {\n if (serializedPath === undefined) {\n return\n }\n\n const bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n return\n }\n\n for (const callback of bucket) {\n callback()\n }\n }\n\n return {\n get: (serializedPath) =>\n current?.serializedPath === serializedPath ? current.position : undefined,\n subscribeKey: (serializedPath, callback) => {\n let bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n bucket = new Set()\n subscribers.set(serializedPath, bucket)\n }\n\n bucket.add(callback)\n\n return () => {\n bucket.delete(callback)\n\n if (bucket.size === 0) {\n subscribers.delete(serializedPath)\n }\n }\n },\n set: (next) => {\n const previous = current\n\n // Swap before notifying: `useSyncExternalStore` re-reads the snapshot\n // synchronously on notification and skips the re-render when it reads\n // an unchanged (stale) value.\n current = next\n ? {serializedPath: serializePath(next.path), position: next.position}\n : undefined\n\n const editorElement = getEditorElement()\n\n if (editorElement) {\n if (current && !restoreCaretColor) {\n const previousCaretColor = editorElement.style.caretColor\n restoreCaretColor = () => {\n editorElement.style.caretColor = previousCaretColor\n }\n // The native drop caret renders at the hovered text position and\n // promises a mid-text split, but an edge indicator means the drop\n // will snap to the block edge instead.\n editorElement.style.caretColor = 'transparent'\n } else if (!current && restoreCaretColor) {\n restoreCaretColor()\n restoreCaretColor = undefined\n }\n }\n\n if (\n previous?.serializedPath === current?.serializedPath &&\n previous?.position === current?.position\n ) {\n return\n }\n\n if (previous?.serializedPath !== current?.serializedPath) {\n notify(previous?.serializedPath)\n }\n\n notify(current?.serializedPath)\n },\n }\n}\n\n/**\n * The behaviors observe the public `drag.*` events and `forward` every\n * event they handle: consumer behaviors run before the editor's own drag\n * handling, so omitting the forward would swallow the event and break the\n * drag itself. (The editor's internal drop-position tracking gets away\n * without forwarding because it registers below core priority.)\n */\ntype DragOriginState = {\n value: EditorSnapshot['context']['value']\n dragOriginSelection: NonNullable<EditorSelection>\n draggedBlockKeys: Set<string>\n draggedFocusBlockPath: Path | undefined\n draggingEntireBlocks: boolean\n}\n\nfunction createDropPositionBehaviors(\n setDropPosition: (next: DropPosition | undefined) => void,\n): Array<Behavior> {\n // `dragover` fires at pointer frequency, but this derived state only changes with\n // the drag origin (a new drag) or the document value (a remote patch\n // landing mid-drag), so it is derived once per (value, origin) pair\n // instead of per event. The value key compares by identity (the value\n // array is replaced on every applied operation); the origin key compares\n // by selection equality, because the origin object's identity does not\n // survive the event pipeline.\n let dragOriginState: DragOriginState | undefined\n\n function getDragOriginState(\n snapshot: EditorSnapshot,\n dragOriginSelection: NonNullable<EditorSelection>,\n ): DragOriginState {\n if (\n dragOriginState &&\n dragOriginState.value === snapshot.context.value &&\n isEqualSelections(\n dragOriginState.dragOriginSelection,\n dragOriginSelection,\n )\n ) {\n return dragOriginState\n }\n\n const dragSelection = getDragSelection({\n eventSelection: dragOriginSelection,\n snapshot,\n })\n const dragSnapshot = {\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n }\n const draggedBlocks = getSelectedBlocks(dragSnapshot)\n // `getSelectedBlocks` only looks at root-level children, so a drag\n // origin nested inside a container (a callout paragraph, a table\n // cell) resolves to the container, not the nested block actually\n // being dragged. `getFocusBlock` resolves the origin's own focus to\n // the same depth drop focus blocks are resolved to, so comparing the\n // two catches a nested block dragged over itself too.\n const draggedFocusBlock = getFocusBlock(dragSnapshot)\n\n dragOriginState = {\n value: snapshot.context.value,\n dragOriginSelection,\n draggedBlockKeys: new Set(\n draggedBlocks.map((draggedBlock) => draggedBlock.node._key),\n ),\n draggedFocusBlockPath: draggedFocusBlock?.path,\n draggingEntireBlocks: isSelectingEntireBlocks(dragSnapshot),\n }\n\n return dragOriginState\n }\n\n return [\n defineBehavior({\n on: 'drag.dragover',\n guard: ({snapshot, event}) => {\n const dropFocusBlock = getFocusBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n })\n\n if (!dropFocusBlock) {\n return false\n }\n\n const dragOrigin = event.dragOrigin\n\n if (!dragOrigin) {\n return false\n }\n\n const {draggedBlockKeys, draggedFocusBlockPath, draggingEntireBlocks} =\n getDragOriginState(snapshot, dragOrigin.selection)\n\n const selfDrop =\n draggedBlockKeys.has(dropFocusBlock.node._key) ||\n (draggedFocusBlockPath !== undefined &&\n isEqualPaths(draggedFocusBlockPath, dropFocusBlock.path))\n\n if (selfDrop) {\n // Core cancels the drop, so no position is valid here, including\n // one left over from the previous hover.\n return {dropFocusBlock, droppedInsideTextBlock: false, clear: true}\n }\n\n if (!draggingEntireBlocks) {\n return false\n }\n\n const dropSnapshot = {\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n }\n const focusTextBlock = getFocusTextBlock(dropSnapshot)\n // A collapsed drop strictly inside a non-empty text block already\n // shows the browser's own drop caret there.\n const droppedInsideTextBlock =\n isSelectionCollapsed(event.position.selection) &&\n focusTextBlock !== undefined &&\n !isEmptyTextBlock(snapshot.context, focusTextBlock.node) &&\n getFocusInlineObject(dropSnapshot) === undefined &&\n !isEqualSelectionPoints(\n event.position.selection.focus,\n getBlockStartPoint({\n context: snapshot.context,\n block: focusTextBlock,\n }),\n ) &&\n !isEqualSelectionPoints(\n event.position.selection.focus,\n getBlockEndPoint({\n context: snapshot.context,\n block: focusTextBlock,\n }),\n )\n\n return {dropFocusBlock, droppedInsideTextBlock, clear: false}\n },\n actions: [\n ({event}, {dropFocusBlock, droppedInsideTextBlock, clear}) => [\n effect(() => {\n setDropPosition(\n clear || droppedInsideTextBlock\n ? undefined\n : {\n path: dropFocusBlock.path,\n position: event.position.block,\n },\n )\n }),\n forward(event),\n ],\n ],\n }),\n defineBehavior({\n on: 'drag.*',\n guard: ({event}) =>\n // `drag.drag` (continuous on the drag source) and `drag.dragenter`\n // (preceding every `dragover` on a boundary crossing) are carrier\n // events, not end-of-interaction signals. Clearing on them would\n // toggle the drop position (and the caret-color write on the editor\n // root) per pointer move or per crossing, and each style write\n // invalidates layout for the whole page right before the next drag\n // event reads element rects: a forced reflow, scaling with page\n // size. `dragleave` still clears every genuine departure.\n event.type !== 'drag.dragover' &&\n event.type !== 'drag.drag' &&\n event.type !== 'drag.dragenter',\n actions: [\n ({event}) => [\n effect(() => {\n setDropPosition(undefined)\n }),\n forward(event),\n ],\n ],\n }),\n ]\n}\n\nconst DndContext = createContext<DropPositionStore | undefined>(undefined)\n\n/**\n * Tracks the drop position during drag and drop and serves it through\n * context. Mount inside `EditorProvider`, wrapping whatever reads the\n * position:\n *\n * ```tsx\n * <EditorProvider initialConfig={...}>\n * <DndProvider>\n * <PortableTextEditable />\n * </DndProvider>\n * </EditorProvider>\n * ```\n *\n * Reads via {@link useDropPosition} only re-render when the drop position\n * at their own path changes.\n *\n * @beta\n */\nexport function DndProvider(props: {children?: ReactNode}) {\n const editor = useEditor()\n const store = useMemo(\n () =>\n createDropPositionStore(() => {\n const editorElement = editor.dom.getEditorElement()\n return editorElement instanceof HTMLElement ? editorElement : undefined\n }),\n [editor],\n )\n const behaviors = useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )\n\n useEffect(() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }, [store])\n\n return (\n <DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>\n )\n}\n\n/**\n * Read where a drop would land relative to the block at `path`: `'start'`,\n * `'end'`, or `undefined` when no drag is hovering this block. Re-renders\n * only when the position at this path changes.\n *\n * `path` is the keyed block path render callbacks receive, e.g.\n * `[{_key: 'b0'}]`.\n *\n * @beta\n */\nexport function useDropPosition(\n path: Path,\n): DropPosition['position'] | undefined {\n const store = useContext(DndContext)\n\n if (store === undefined) {\n throw new Error('useDropPosition must be used below a <DndProvider>')\n }\n\n // Callers typically pass a fresh `path` array every render, so the\n // serialized string, not the array, is what keeps `subscribe` stable\n // below.\n const serializedPath = serializePath(path)\n\n const subscribe = useCallback(\n (callback: () => void) => store.subscribeKey(serializedPath, callback),\n [store, serializedPath],\n )\n\n const getSnapshot = () => store.get(serializedPath)\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/**\n * Serialize a keyed path to a string using Sanity's bracket notation.\n * Duplicated from the editor's internal `serializePath`; see\n * `drag-selection.ts` for the duplication rationale.\n */\nfunction serializePath(path: Path): string {\n return path.reduce<string>((result, segment, index) => {\n if (isKeyedSegment(segment)) {\n return `${result}[_key==\"${segment._key}\"]`\n }\n\n const separator = index === 0 ? '' : '.'\n return `${result}${separator}${segment}`\n }, '')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-dnd",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Track the drop position during drag and drop for custom drop indicators",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dnd",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"typescript": "^7.0.2",
|
|
44
44
|
"vite": "^8.2.0",
|
|
45
45
|
"vitest": "^4.1.11",
|
|
46
|
-
"@portabletext/editor": "^8.1.
|
|
46
|
+
"@portabletext/editor": "^8.1.3",
|
|
47
47
|
"@portabletext/schema": "^3.0.0",
|
|
48
48
|
"@portabletext/test": "^2.1.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^19.2",
|
|
52
|
-
"@portabletext/editor": "^8.1.
|
|
52
|
+
"@portabletext/editor": "^8.1.3"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=22.12"
|