Skip to content

fix: prevent duplication of work package chips during drag and drop#129

Open
ihordubas99 wants to merge 5 commits intodevfrom
bug/74540-dragging-text-containing-chip-creates-copy-instead-of-moving-it
Open

fix: prevent duplication of work package chips during drag and drop#129
ihordubas99 wants to merge 5 commits intodevfrom
bug/74540-dragging-text-containing-chip-creates-copy-instead-of-moving-it

Conversation

@ihordubas99
Copy link
Copy Markdown
Collaborator

@ihordubas99 ihordubas99 commented Apr 29, 2026

Ticket

https://community.openproject.org/projects/communicator-stream/work_packages/74540

What are you trying to accomplish?

Fix a duplication bug that occurs when dragging and dropping Work Package chips (both Inline and Block) inside the BlockNote editor.

Previously, if a user selected text along with a chip and initiated the drag specifically by grabbing the chip (contentEditable={false}), ProseMirror would treat the drop as a new HTML insertion (copy) rather than a structural move, leaving the original content behind.

Screenshots

Screencast.from.2026-05-07.14-05-22.webm

What approach did you choose and why?

The root cause is in how the browser handles native dragstart events on non-editable elements. When a user drags a chip, the browser takes over the drag operation, causing ProseMirror to lose the context of the internal text selection. Because of this, ProseMirror defaults to a copy operation upon drop, inserting the HTML without removing the original source.

The fix uses public BlockNote APIs for both inline and block chips, avoiding any direct access to the underlying TipTap/ProseMirror internals.

Inline chip

  • Added meta.draggable: true to openProjectWorkPackageInlineSpec and data-drag-handle to the rendered chip element. This is the official BlockNote way to mark inline content as draggable.
  • Added a small dragstart handler that clears any active selection before the drag. Without it, dragging a chip while a wider selection is active produces inconsistent results (text-only drags, duplicates, half-moved selections). Clearing the selection ensures a clean chip-only drag every time.
  • Extracted the BlockNote-specific wiring into a thin InlineWorkPackageChipInEditor wrapper, keeping InlineWorkPackageChip free of any BlockNote context dependency so it stays independently testable and renderable outside of the editor. The drag handler is passed down as an optional onDragStart prop.

Block chip

  • Direct drag on the block delegates to SideMenuExtension.blockDragStart, the same mechanism the ⠿ side menu uses internally. Both entry points (the side menu and direct drag on the block) now behave identically.
  • The SideMenu extension is looked up by string key on editor.extensions (which is a Map at runtime) instead of via useExtension(SideMenuExtension, ...). The class-identity check in useExtension throws "Extension not found" when @blocknote/core is duplicated in the host app's node_modules — looking up by registered key sidesteps this entirely.

Drag behaviour summary

Scenario Behaviour
Drag the chip alone Chip moves
Select text + chip + text, drag by the text Whole selection moves
Select text + chip + text, drag by the chip Only the chip moves

The third case is a known HTML5 drag-and-drop quirk: when a drag starts on an element with draggable="true", the browser performs a native element drag of that element and ignores the active text selection. There's no public BlockNote API to override this for inline content, so we accept the limitation in favour of keeping the codebase free of TipTap internals.

Alternatives considered

  • Custom useDragSelection hook reaching into editor._tiptapEditor.view to override ProseMirror's view.dragging with a slice of the full selection plus move: true. This was the original fix and it did handle the third drag scenario above correctly, but it relies on private TipTap APIs that the project is moving away from.
  • Overriding handleDrop or transformPastedHTML on the ProseMirror view directly. Discarded because it creates a leaky abstraction and risks breaking standard drag-and-drop behaviour across the entire editor.

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

@ihordubas99 ihordubas99 self-assigned this Apr 29, 2026
@ihordubas99 ihordubas99 marked this pull request as ready for review April 30, 2026 10:42
@ihordubas99 ihordubas99 requested a review from judithroth April 30, 2026 10:42
Copy link
Copy Markdown
Contributor

@judithroth judithroth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok... maybe I need to stop working for today. Testing this was though somehow.
So, first I tried your implementation.
Then I saw that you're using TipTap again to solve this and my thoughts were like "Did the BlockNote devs really not solve this probem?!" So I went to their docs and they did.
Then I went ahead and tried that and it works. Then I wanted to verify that by switching back. Switching back to the dev branch of op-blocknote-extensions did not change it though (dragging still worked without creating duplicates or links). Switching back to the implementation of version 0.0.25 did though and the wrong behaviour was back.

So, long story short: I think I maybe need to test this again tomorrow.
Can you maybe also test if drag and drop is still broken on the dev branch without these changes here (to confirm)?

Comment thread lib/hooks/useDragSelection.ts Outdated
return useCallback(() => {
if (!editor) return;

const view = editor._tiptapEditor?.view;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, TipTap again 😅

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer, that was exactly the API I needed. Switched the inline chip to meta.draggable: true and dropped the TipTap usage from the drag path entirely.

One behavioural caveat worth flagging though. There are three drag scenarios for an inline chip:

  1. Drag the chip alone → chip moves.
  2. Select text + chip + text, drag by the text → whole selection moves.
  3. Select text + chip + text, drag by the chip → only the chip moves; the surrounding text stays in place.

Case 3 is a regression vs. 0.0.25 - in the old TipTap-based fix it would move the whole selection. The cause is in HTML5 drag-and-drop itself: when a drag starts on an element with draggable="true", the browser performs a native element drag of just that element and ignores the active text selection. The old workaround handled it by overriding ProseMirror's view.dragging with a slice of the selection, and there's no public BlockNote API for that as far as I can tell.

I added a small dragstart handler on the chip that clears any active selection before the drag, so the result is at least clean - no duplicates, no half-moved selections.

For the block chip, drag now works both through the ⠿ side menu (as before) and directly on the block itself. Direct drag is wired up via SideMenuExtension.blockDragStart, which is the same mechanism the ⠿ uses internally, so both entry points behave identically.

This PR is scoped to drag-and-drop only, so I left the remaining _tiptapEditor usage in BlockWorkPackageComponent (the selectionUpdate listener) untouched. I found a public BlockNote API for that too - will tackle it in a separate PR to keep this one focused.

Let me know if case 3 is a blocker - if so, the cleanest fallback would be keeping the old TipTap fix just for that path with a comment explaining why. Otherwise I'd lean toward shipping this as-is.

@ihordubas99 ihordubas99 force-pushed the bug/74540-dragging-text-containing-chip-creates-copy-instead-of-moving-it branch from 2419c10 to c86a21d Compare May 6, 2026 14:56
@ihordubas99 ihordubas99 marked this pull request as draft May 7, 2026 10:43
@ihordubas99 ihordubas99 marked this pull request as ready for review May 7, 2026 10:55
@ihordubas99 ihordubas99 requested a review from judithroth May 7, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants