Skip to content

Add table reordering visualization example (drag source highlight, drop-cursor color, floating drag image)#2920

Open
mustafa-yilmaz wants to merge 4 commits into
TypeCellOS:mainfrom
mustafa-yilmaz:table-reordering-visualization
Open

Add table reordering visualization example (drag source highlight, drop-cursor color, floating drag image)#2920
mustafa-yilmaz wants to merge 4 commits into
TypeCellOS:mainfrom
mustafa-yilmaz:table-reordering-visualization

Conversation

@mustafa-yilmaz

@mustafa-yilmaz mustafa-yilmaz commented Jul 26, 2026

Copy link
Copy Markdown
Table_Reordering.mov

Aim

To make the table-reordering interaction and visual feedback in BlockNote more closely resemble those of Microsoft Loop.

Summary

Adds a new example (examples/03-ui-components/21-table-reordering-visualization) that gives table row/column drag-and-drop much clearer visual feedback, plus a small /table slash-menu tweak:

  • Restyled table (rounded card, muted header, hairline borders, row hover)
  • The row/column being dragged is tinted/outlined so it's clear what's moving
  • The drop-position indicator uses a solid brand color instead of pale blue
  • A real floating drag-image snapshot follows the cursor, replacing BlockNote's default invisible native drag image
  • New tables via /table now default to headerRows: 1

Interaction model

This doesn't change what a drag does — TableHandlesExtension still owns the drag lifecycle and the actual moveRow/moveColumn + updateBlock reorder. It only adds a ProseMirror decoration for the drag source (keyed off the same transaction metadata BlockNote's own drop-cursor decoration already uses) and a custom native drag image. Full write-up in the example's README.

Test plan

tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx, run across chromium/firefox/webkit:

  • Row drag shows source tint + colored drop cursor, both clear on drop
  • Column drag tints every cell in that column
  • Cancelling a drag (Escape) still cleans up the tint
  • Dragging a row with rich (bold) inline content doesn't throw
  • Dragging a column across a merged (rowspan) cell doesn't throw or corrupt the table
  • /table produces a header row immediately

Known limitations (not introduced by this PR — pre-existing BlockNote core behavior)

  • No keyboard-operable reorder path and no touch support — BlockNote's table drag is native-HTML5-DnD only today; building either is a separate, larger feature.
  • No focus-restoration test, for the same reason — the interaction isn't keyboard-reachable yet.
  • Merged cells: the source highlight is index-based and doesn't fully account for colspan/rowspan; BlockNote's own drag-validity guards already block most such drags, so this affects highlight fidelity, not document correctness.
  • Concurrent edits mid-drag: dropHandler snapshots the table once at drag-start and doesn't refresh mid-drag, so a concurrent collaborator edit during someone else's drag can be overwritten on drop. Existing core behavior, untouched by this PR — flagging it since it came up in the pre-PR discussion.

Raised in discussion #2919 first per maintainer guidance.

🤖 Generated with Claude Code

Summary by CodeRabbit

Summary

  • New Features

    • Added a new “Table Reordering Visualization” example with improved row/column drag visuals.
    • Added a floating native drag preview for smoother table drag-and-drop.
    • Tables inserted via the /table command now default to include a header row.
  • Documentation

    • Added a dedicated README covering the interaction flow and known limitations.
  • Tests

    • Added end-to-end coverage for drag behavior, cancelation (including Escape), rich content, merged-cell edge cases, and /table insertion.

mustafa-yilmaz and others added 2 commits July 26, 2026 08:37
Ports the enhanced table drag-and-drop feedback originally built as a
customization on top of La Suite Docs into a standalone BlockNote.js
example, using only public BlockNote/ProseMirror APIs and plain colors
(no external design-token dependency):

- Restyled tables: rounded card look, muted header row, hairline
  borders, row-hover highlight.
- Drag source highlight: the row/column being dragged is tinted and
  outlined via a ProseMirror decoration (survives redraws, unlike a
  direct DOM class mutation).
- Colored drop-position indicator.
- Floating drag image: a real snapshot of the row/column follows the
  cursor, replacing BlockNote's default hidden native drag image.
- New tables via "/table" now default to a header row, so the header
  styling is visible immediately instead of requiring a manual toggle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds e2e coverage for the parts the new example actually changes:
- source-highlight + colored drop-cursor appearance during row/column drags
- per-cell tinting for column drags
- cleanup after a cancelled (Escape) drag
- dragging a row with rich inline content
- dragging a column across a merged (rowspan) cell
- the /table slash command defaulting new tables to a header row

Also documents the interaction model and known limitations (no
keyboard/touch reordering, no focus-restoration path, merged-cell
index fidelity, and stale-snapshot behavior on concurrent edits
mid-drag) in the example's README, since those are pre-existing
characteristics of BlockNote's own table-drag implementation that this
example doesn't introduce or change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@mustafa-yilmaz is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7dcf9cad-820a-4576-bf76-49cb2ed294a5

📥 Commits

Reviewing files that changed from the base of the PR and between 7af99b6 and a283737.

📒 Files selected for processing (3)
  • examples/03-ui-components/21-table-reordering-visualization/README.md
  • examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts
  • tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/03-ui-components/21-table-reordering-visualization/README.md
  • examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts
  • tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx

📝 Walkthrough

Walkthrough

Adds a standalone table reordering visualization example with custom table insertion, drag-source and drop indicators, native row/column drag images, styling, documentation, and end-to-end tests.

Changes

Table reordering visualization

Layer / File(s) Summary
Example wiring
examples/03-ui-components/21-table-reordering-visualization/{index.html,main.tsx,package.json,tsconfig.json,vite.config.ts,vite-env.d.ts}, examples/03-ui-components/21-table-reordering-visualization/src/App.tsx, .bnexample.json
Adds the React example, local build configuration, editor setup, initial table content, and a /table command that creates tables with one header row.
Source and drop visuals
examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts, tableStyles.css, README.md
Highlights dragged rows or columns, styles table controls and drop cursors, and documents the interaction model and implementation.
Native drag image
examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts
Creates native drag images from cloned table cells and installs the document-level drag handler.
End-to-end validation
tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx
Tests row and column dragging, cancellation, rich content, merged cells, decoration cleanup, and header-row insertion.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TableHandlesExtension
  participant useTableDragImage
  participant DataTransfer
  User->>TableHandlesExtension: start row or column drag
  TableHandlesExtension-->>useTableDragImage: expose dragging state
  useTableDragImage->>DataTransfer: set cloned table drag image
  User->>TableHandlesExtension: move or cancel drag
Loading

Suggested reviewers: nperez0111

Poem

I hop through rows with ears held high,
A painted source goes drifting by.
Cells lift softly, indicators gleam,
Headers bloom in every table dream.
“Hop!” says the rabbit—dragging’s a beam!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the new table reordering visualization example and its main visual features.
Description check ✅ Passed It covers the main sections well, including summary, rationale, changes, testing, and demo link, but checklist/additional notes aren't explicit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🧹 Nitpick comments (5)
examples/03-ui-components/21-table-reordering-visualization/index.html (1)

6-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

HTML comment wrapped in a <script> tag.

The generated marker is inside <script>, so it's parsed as JavaScript (it survives only via legacy HTML-like comment handling). A plain HTML comment outside the script is clearer. Again, this is generator-side.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/03-ui-components/21-table-reordering-visualization/index.html`
around lines 6 - 8, Move the “AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY” marker
outside the <script> element in the generator that produces this HTML, emitting
it as a plain HTML comment before the script block. Update the generator
template or output logic rather than editing the generated file directly.
tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx (1)

22-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Handle discovery via the rotate transform is a presentation-coupled heuristic.

Row vs. column handles are distinguished by inspecting inline style.transform. Any styling change in TableHandlesExtension silently flips these tests to selecting the wrong handle. A data attribute or ordering-based lookup would be more durable, if one is available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx` around
lines 22 - 46, The getRowHandle and getColumnHandle helpers rely on the
presentation-specific rotate transform to distinguish handles; replace this
heuristic with a durable semantic selector exposed by TableHandlesExtension,
such as a data attribute or stable ordering contract. Update both helpers to use
that selector while preserving their existing hover and wait-for-visibility
behavior.
examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts (1)

105-112: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Hit-testing for the table via elementFromPoint is fragile.

The point referencePosTable.x + 1, y + 1 can be covered by the drag handle, a floating toolbar, or any overlay, in which case closest("table") returns null and the drag image silently never appears. Resolving the table from the editor DOM (e.g. editor.domElement/the handles extension's stored table element) would be deterministic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts`
around lines 105 - 112, Replace the fragile elementFromPoint-based table lookup
in the drag-image logic with deterministic resolution from the editor DOM or the
handles extension’s stored table element. Update the code around
referencePosTable and closest("table") to use that known table element, while
preserving the existing early return when no table can be resolved.
examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts (1)

50-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider guarding the resolve against out-of-range positions.

Even with the mapping fix above, state.doc.resolve(tablePos + 1) and tableResolvedPos.node() assume the position still lands inside a table node. A try/catch (or a tablePos + 1 <= state.doc.content.size check plus a tableNode.type.name check) returning null keeps a stale drag state from tearing down the whole editor view.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts`
around lines 50 - 91, Guard the position resolution in the decorations method
before using tableResolvedPos.node(): validate that tablePos + 1 remains within
state.doc bounds and that the resolved node is a table, or catch resolution
failures. Return null for stale or invalid drag state, while preserving the
existing row and column decoration behavior for valid tables.
examples/03-ui-components/21-table-reordering-visualization/src/App.tsx (1)

26-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Don’t rely on the untyped key field for the slash-menu override.

key is an implementation detail here, so if the item shape changes this map returns the stock stock table item and /table won’t insert with headerRows: 1. Match on a stable public field such as title instead, or add a dev-time guard that fails when no table item was found. The content as any cast is also unnecessary because Table content.headerRows is already optional in the public table content type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/03-ui-components/21-table-reordering-visualization/src/App.tsx`
around lines 26 - 45, The slash-menu override currently identifies the table
item through the untyped implementation-detail `key`; update the mapping around
`getDefaultReactSlashMenuItems` to match the stable public `title` field
instead, and remove the unnecessary `content as any` cast because `headerRows`
is supported by the public table content type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/03-ui-components/21-table-reordering-visualization/index.html`:
- Line 1: Update the template or generator that produces the table reordering
visualization HTML so its output begins with the HTML5 <!doctype html>
declaration before the html element. Regenerate the example to include the
declaration, preserving the existing document content and structure.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts`:
- Around line 38-47: Update the `apply` method to validate that object metadata
contains a valid numeric `tablePos` before treating it as `DragSourceMeta`;
otherwise return `prev` (or the existing empty state) so malformed metadata
cannot reach `decorations`. Map the accepted `tablePos` through `tr.mapping`
before storing and returning it, preserving the drag highlight at the
corresponding document position after document changes.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts`:
- Around line 126-136: Update the drag-image setup in useTableDragImage so the
appended element remains rendered and visible to the layout engine by replacing
the far off-screen top/left placement with an on-screen invisible placement,
such as a transform or low-opacity style. Wrap the appendChild, setDragImage,
and cleanup scheduling flow in try/finally so dragImage.remove() is guaranteed
if any operation after appendChild throws.

In `@examples/03-ui-components/21-table-reordering-visualization/vite.config.ts`:
- Around line 15-28: Update the local package alias paths in the Vite
configuration to resolve through ../../../packages instead of ../../packages,
including both `@blocknote/core` and `@blocknote/react` and the surrounding
source-existence check, so they match tsconfig.json and enable local source
loading with live reload.

In `@tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx`:
- Around line 94-103: Update both post-drop cleanup assertion sites in
tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx: lines 94-103
should await vi.waitFor for the drag-source-row and drop-cursor absence checks,
and lines 140-143 should await vi.waitFor for the drag-source-col absence check.
Preserve the existing selectors and zero-length expectations.

---

Nitpick comments:
In `@examples/03-ui-components/21-table-reordering-visualization/index.html`:
- Around line 6-8: Move the “AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY” marker
outside the <script> element in the generator that produces this HTML, emitting
it as a plain HTML comment before the script block. Update the generator
template or output logic rather than editing the generated file directly.

In `@examples/03-ui-components/21-table-reordering-visualization/src/App.tsx`:
- Around line 26-45: The slash-menu override currently identifies the table item
through the untyped implementation-detail `key`; update the mapping around
`getDefaultReactSlashMenuItems` to match the stable public `title` field
instead, and remove the unnecessary `content as any` cast because `headerRows`
is supported by the public table content type.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts`:
- Around line 50-91: Guard the position resolution in the decorations method
before using tableResolvedPos.node(): validate that tablePos + 1 remains within
state.doc bounds and that the resolved node is a table, or catch resolution
failures. Return null for stale or invalid drag state, while preserving the
existing row and column decoration behavior for valid tables.

In
`@examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts`:
- Around line 105-112: Replace the fragile elementFromPoint-based table lookup
in the drag-image logic with deterministic resolution from the editor DOM or the
handles extension’s stored table element. Update the code around
referencePosTable and closest("table") to use that known table element, while
preserving the existing early return when no table can be resolved.

In `@tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx`:
- Around line 22-46: The getRowHandle and getColumnHandle helpers rely on the
presentation-specific rotate transform to distinguish handles; replace this
heuristic with a durable semantic selector exposed by TableHandlesExtension,
such as a data attribute or stable ordering contract. Update both helpers to use
that selector while preserving their existing hover and wait-for-visibility
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9eeb92c5-631f-406a-b81f-575bc1e4e81c

📥 Commits

Reviewing files that changed from the base of the PR and between dee7880 and ceedb6b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • examples/03-ui-components/21-table-reordering-visualization/.bnexample.json
  • examples/03-ui-components/21-table-reordering-visualization/README.md
  • examples/03-ui-components/21-table-reordering-visualization/index.html
  • examples/03-ui-components/21-table-reordering-visualization/main.tsx
  • examples/03-ui-components/21-table-reordering-visualization/package.json
  • examples/03-ui-components/21-table-reordering-visualization/src/App.tsx
  • examples/03-ui-components/21-table-reordering-visualization/src/tableDragSourceExtension.ts
  • examples/03-ui-components/21-table-reordering-visualization/src/tableStyles.css
  • examples/03-ui-components/21-table-reordering-visualization/src/useTableDragImage.ts
  • examples/03-ui-components/21-table-reordering-visualization/src/vite-env.d.ts
  • examples/03-ui-components/21-table-reordering-visualization/tsconfig.json
  • examples/03-ui-components/21-table-reordering-visualization/vite-env.d.ts
  • examples/03-ui-components/21-table-reordering-visualization/vite.config.ts
  • tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx

Comment thread tests/src/end-to-end/tables/tableReorderingVisualization.test.tsx Outdated
mustafa-yilmaz and others added 2 commits July 26, 2026 10:08
- vite.config.ts: fix the local-source alias path (was 2 levels up,
  needed 3 to actually reach packages/core|react/src - tsconfig.json
  already had the correct depth, so this was a silent no-op before,
  always falling back to node_modules resolution)
- index.html: add missing <!doctype html>, move the generator marker
  comment out of the <script> tag
- tableDragSourceExtension.ts: guard the decoration's position
  resolution against a stale/out-of-range tablePos instead of letting
  it throw, since nothing remaps tablePos across later transactions
- useTableDragImage.ts: resolve the table's DOM node deterministically
  via its stable block ID instead of elementFromPoint hit-testing
  (which silently fails if any overlay covers that pixel); position
  the drag-image clone on-screen-but-invisible instead of far
  off-screen, since some browsers skip rasterizing elements placed
  well outside the viewport; wrap the append/setDragImage/cleanup in
  try/finally so cleanup always runs
- tableReorderingVisualization.test.tsx: wrap the post-drop decoration
  cleanup assertions in vi.waitFor instead of asserting immediately
  after mouseup, since cleanup isn't necessarily synchronous with it

Verified all fixes against the actual dev server (not just the test
suite, since the vite.config.ts alias fix specifically changes that
path) and re-ran the full test file across chromium/firefox/webkit
after each change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review comment on TypeCellOS#2920 (r3651925104): apply() cast any object
transaction meta straight to DragSourceMeta without checking tablePos/
originalIndex were actually numbers, and never remapped a stored tablePos
across later transactions.

- Validate the meta shape before accepting it, matching the suggested fix.
- When a transaction changes the document without setting our meta (a
  concurrent local or collaborative edit while a drag is in progress),
  remap the stored tablePos through tr.mapping instead of leaving it
  stale.

While writing a regression test for this, dispatching an unrelated
transaction mid-drag surfaced a pre-existing bug in BlockNote's own
TableHandlesExtension: view.tablePos (used for its drop-cursor decoration)
has the same never-remapped issue, but throws a RangeError instead of
failing safely, since it's a plain instance property rather than plugin
state going through tr.mapping. That's out of scope for this example to
fix, so the test was dropped (it can't pass while core's own decorations()
throws first in the same view update) and the README's "Concurrent edits
mid-drag" section was corrected - it previously understated this as
"drops can overwrite a concurrent edit" when it can actually throw and
break the editor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant