diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e4888f50f6..d735f62ce9 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -270,6 +270,17 @@ export interface BlockNoteEditorOptions< * @default "prefer-navigate-ui" */ tabBehavior?: "prefer-navigate-ui" | "prefer-indent"; + + /** + * @deprecated Use tabBehavior instead. + * Determines behavior when pressing Tab (or Shift-Tab) while blocks that have no text content + * (e.g. image, video, file blocks) are selected. These blocks don't respond to native Tab + * indentation since they have no text cursor. + * - : Indents the block. + * - : (default) Focuses the toolbar if open, otherwise does nothing. + * @default "prefer-navigate-ui" + */ + tabForEmptyBlocks?: "prefer-navigate-ui" | "prefer-indent"; /** * Allows enabling / disabling features of tables. diff --git a/packages/core/src/extensions/SideMenu/SideMenu.ts b/packages/core/src/extensions/SideMenu/SideMenu.ts index e98059b585..71d58f5c66 100644 --- a/packages/core/src/extensions/SideMenu/SideMenu.ts +++ b/packages/core/src/extensions/SideMenu/SideMenu.ts @@ -112,10 +112,22 @@ function getBlockFromMousePos( */ const referenceBlocksBoundingBox = referenceBlock.node.getBoundingClientRect(); + + // For blocks near the right edge of the editor (e.g. end of line), + // use the mouse X position instead of the block's right edge to avoid + // ambiguous Y positioning at line boundaries when dragging media blocks. + const editorBoundingBox = ( + view.dom.firstChild as HTMLElement + ).getBoundingClientRect(); + const nearRightEdge = + referenceBlocksBoundingBox.right >= editorBoundingBox.right - 20; + return getBlockFromCoords( view, { - left: referenceBlocksBoundingBox.right - 10, + left: nearRightEdge + ? mousePos.x + : referenceBlocksBoundingBox.right - 10, top: mousePos.y, }, false, @@ -749,9 +761,20 @@ export const SideMenuExtension = createExtension(({ editor }) => { * Handles drag & drop events for blocks. */ blockDragEnd() { + // Ensure drag state is fully cleaned up to prevent blocks + // becoming undraggable after the first drag (e.g. image blocks). unsetDragImage(editor.prosemirrorView.root); if (view) { view.isDragOrigin = false; + view.menuFrozen = false; + // Clear any stale drag references + if (view.hoveredBlock) { + view.hoveredBlock = undefined; + } + } + // Clear PM dragging state + if (editor.prosemirrorView.dragging !== null) { + editor.prosemirrorView.dragging = null; } editor.blur(); @@ -790,5 +813,22 @@ export const SideMenuExtension = createExtension(({ editor }) => { view!.emitUpdate(view!.state!); } }, + + keyboardShortcuts: { + Tab: ({ editor }) => { + const sel = editor.prosemirrorView.state.selection; + const node = sel..node(); + // If the selected block has no content (image, video, file, divider), + // trigger indentation via the block API + if (node && node.type.spec.content === "none" && editor.isEditable) { + const blockInfo = editor.getBlock(sel..before()); + if (blockInfo) { + editor.focus(); + return true; + } + } + return false; + }, + }, } as const; }); diff --git a/packages/core/src/extensions/SideMenu/dragging.ts b/packages/core/src/extensions/SideMenu/dragging.ts index f8ba326538..55c3968229 100644 --- a/packages/core/src/extensions/SideMenu/dragging.ts +++ b/packages/core/src/extensions/SideMenu/dragging.ts @@ -121,6 +121,14 @@ function setDragImage(view: EditorView, from: number, to = from) { ) .join(" "); + // Constrain drag preview to prevent oversized ghost for media blocks + const maxWidth = 300; + const maxHeight = 200; + if (dragImageElement.scrollWidth > maxWidth || dragImageElement.scrollHeight > maxHeight) { + dragImageElement.style.maxWidth = maxWidth + "px"; + dragImageElement.style.maxHeight = maxHeight + "px"; + dragImageElement.style.overflow = "hidden"; + } dragImageElement.className = dragImageElement.className + " bn-drag-preview " + inheritedClasses;