Skip to content

fix: Slash menu item selection behaviour (BLO-1222)#2838

Open
matthewlipski wants to merge 1 commit into
mainfrom
slash-menu-item-cursor-fix
Open

fix: Slash menu item selection behaviour (BLO-1222)#2838
matthewlipski wants to merge 1 commit into
mainfrom
slash-menu-item-cursor-fix

Conversation

@matthewlipski
Copy link
Copy Markdown
Collaborator

@matthewlipski matthewlipski commented Jun 5, 2026

Summary

setSelectionToNextContentEditableBlock finds the next block with rich text content, from the block currently containing the text cursor, and moves the cursor to it. The issue is that a block with rich text content doesn't always exist later in the document, leading to situations where e.g. a divider is inserted via the slash menu and selected.

This PR makes it so that when this happens, an empty paragraph is added to the end of the document and the text cursor is move to it.

Closes #2817

Rationale

While minor, we generally want to avoid moving the selection to a block without rich text content for better UX.

Changes

  • Updated setSelectionToNextContentEditableBlock.

Impact

setSelectionToNextContentEditableBlock may also be used by consumers (through insertOrUpdateBlockForSlashMenu), so they will be forced to adopt this new behaviour. Should be fine as it is overall an improvement.

Testing

Updated e2e tests/snapshots.

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

N/A

Summary by CodeRabbit

  • Bug Fixes

    • Updated cursor behavior: editor now appends an empty paragraph when reaching the document end without a subsequent content-editable block.
  • Tests

    • Updated end-to-end tests for image drag-and-drop and keyboard handling scenarios to align with cursor positioning changes.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Error Error Jun 5, 2026 9:36am
blocknote-website Error Error Jun 5, 2026 9:36am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fixes cursor navigation behavior at document end by modifying setSelectionToNextContentEditableBlock to append an empty paragraph when no subsequent content-editable block exists. Test utilities and E2E tests are updated to interact with the newly appended paragraph instead of trailing block elements.

Changes

End-of-document Cursor Navigation

Layer / File(s) Summary
End-of-document paragraph insertion
packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
setSelectionToNextContentEditableBlock inserts a new empty paragraph block and moves the cursor to it when no next block exists, enabling forward navigation at document end.
Test utility and E2E test adjustments
tests/src/utils/copypaste.ts, tests/src/end-to-end/dragdrop/dragdrop.test.tsx, tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.tsx
Test utilities now query for trailing block directly with paragraph fallback. Drag-and-drop tests click the last paragraph element instead of trailing block after slash command. Keyboard handler test adds cursor positioning step before deletion.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • TypeCellOS/BlockNote#2704: Both PRs modify slash-menu insertion behavior in getDefaultSlashMenuItems.ts with matching end-to-end test adjustments around post-insert selection targets.

Suggested reviewers

  • nperez0111

Poem

🐰 Hoppy times ahead, no more cursor blues!
When dividers divide and paragraphs ensue,
The arrow keys now know just what to do—
They leap to the new block, crisp and true!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 'fix: Slash menu item selection behaviour (BLO-1222)' accurately describes the main change to improve slash menu cursor behavior when no subsequent editable block exists.
Description check ✅ Passed The PR description is mostly complete with Summary, Rationale, Changes, Impact, and Testing sections filled. However, Unit tests coverage checkbox remains unchecked despite code changes.
Linked Issues check ✅ Passed The PR successfully addresses issue #2817 by implementing the required behavior: appending an empty paragraph when no subsequent editable block exists and moving cursor to it, resolving cursor navigation issues after inserting non-editable blocks.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing slash menu cursor behavior: updating getDefaultSlashMenuItems.ts, adjusting e2e tests to reflect new behavior, and updating copy-paste utility tests for consistency.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slash-menu-item-cursor-fix

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 and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 5, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2838

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2838

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2838

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2838

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2838

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2838

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2838

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2838

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2838

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2838

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2838

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2838

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2838

commit: 7317c64

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-2838/

Built to branch gh-pages at 2026-06-05 09:06 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts`:
- Around line 35-39: The code currently hard-codes inserting { type: "paragraph"
} in getDefaultSlashMenuItems (used by insertOrUpdateBlockForSlashMenu), which
will break for custom schemas without a paragraph type; update the fallback to
select a valid inline-content block type from editor.schema.blockSchema (e.g.,
find the first block whose content model allows inline content) and use that
type in the call to editor.insertBlocks, and if none exists, throw a clear
schema error mentioning insertOrUpdateBlockForSlashMenu and
editor.schema.blockSchema so failure is explicit.

In `@tests/src/utils/copypaste.ts`:
- Around line 18-21: Guard the paragraph fallback by checking that the NodeList
referenced by paragraphs has at least one element before calling
userEvent.click; if paragraphs.length === 0 (and .bn-trailing-block is absent)
throw a clear error or fail the test instead of calling
userEvent.click(undefined). Update the code around the paragraphs variable and
the userEvent.click(paragraphs[paragraphs.length - 1]) call to perform this
explicit empty-check and error message.
🪄 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

Run ID: 1e545d6a-b606-498d-9a64-d547b7b47d30

📥 Commits

Reviewing files that changed from the base of the PR and between ec9c151 and 7317c64.

⛔ Files ignored due to path filters (17)
  • tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/ariakit/__screenshots__/ariakit.test.tsx/ariakit-image-toolbar-webkit-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/images/__screenshots__/images.test.tsx/create-image-webkit-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/images/__snapshots__/createImage.json is excluded by !**/__snapshots__/**
  • tests/src/end-to-end/images/__snapshots__/embedImage.json is excluded by !**/__snapshots__/**
  • tests/src/end-to-end/images/__snapshots__/resizeImage.json is excluded by !**/__snapshots__/**
  • tests/src/end-to-end/keyboardhandlers/__snapshots__/deleteImage.json is excluded by !**/__snapshots__/**
  • tests/src/end-to-end/keyboardhandlers/__snapshots__/deleteImageChild.json is excluded by !**/__snapshots__/**
  • tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/shadcn/__screenshots__/shadcn.test.tsx/shadcn-image-toolbar-webkit-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/theming/__screenshots__/theming.test.tsx/dark-image-toolbar-webkit-linux.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
  • tests/src/end-to-end/dragdrop/dragdrop.test.tsx
  • tests/src/end-to-end/keyboardhandlers/keyboardhandlers.test.tsx
  • tests/src/utils/copypaste.ts

Comment on lines +35 to +39
const lastBlock = editor.document[editor.document.length - 1];
const newBlock = editor.insertBlocks(
[{ type: "paragraph" }],
lastBlock,
"after",
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid hard-coding "paragraph" in a schema-generic cursor fallback.

Line 37 always inserts { type: "paragraph" }, but this helper is reached through exported insertOrUpdateBlockForSlashMenu. In custom schemas without paragraph, this path will fail at runtime when insertion reaches document end. Pick an available inline-content type from editor.schema.blockSchema (or fail with a clear schema error) instead of assuming paragraph.

Proposed direction
-      const newBlock = editor.insertBlocks(
-        [{ type: "paragraph" }],
+      const fallbackInlineType = Object.entries(editor.schema.blockSchema).find(
+        ([, spec]) => spec.content === "inline",
+      )?.[0];
+      if (!fallbackInlineType) {
+        throw new Error(
+          "No inline-content block type available to place text cursor.",
+        );
+      }
+      const newBlock = editor.insertBlocks(
+        [{ type: fallbackInlineType as keyof BSchema }],
         lastBlock,
         "after",
       )[0];
🤖 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 `@packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts`
around lines 35 - 39, The code currently hard-codes inserting { type:
"paragraph" } in getDefaultSlashMenuItems (used by
insertOrUpdateBlockForSlashMenu), which will break for custom schemas without a
paragraph type; update the fallback to select a valid inline-content block type
from editor.schema.blockSchema (e.g., find the first block whose content model
allows inline content) and use that type in the call to editor.insertBlocks, and
if none exists, throw a clear schema error mentioning
insertOrUpdateBlockForSlashMenu and editor.schema.blockSchema so failure is
explicit.

Comment on lines +18 to +21
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
await userEvent.click(paragraphs[paragraphs.length - 1]);
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard the paragraph fallback before clicking.

If .bn-trailing-block is absent and no paragraph exists, Line 21 clicks undefined and fails non-deterministically. Add an explicit empty-check with a clear error.

Suggested fix
   } else {
     const paragraphs = document.querySelectorAll<HTMLElement>(
       '[data-content-type="paragraph"]',
     );
+    if (paragraphs.length === 0) {
+      throw new Error(
+        "copyPaste: no trailing block or paragraph available to focus before paste.",
+      );
+    }
     await userEvent.click(paragraphs[paragraphs.length - 1]);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
await userEvent.click(paragraphs[paragraphs.length - 1]);
const paragraphs = document.querySelectorAll<HTMLElement>(
'[data-content-type="paragraph"]',
);
if (paragraphs.length === 0) {
throw new Error(
"copyPaste: no trailing block or paragraph available to focus before paste.",
);
}
await userEvent.click(paragraphs[paragraphs.length - 1]);
🤖 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/utils/copypaste.ts` around lines 18 - 21, Guard the paragraph
fallback by checking that the NodeList referenced by paragraphs has at least one
element before calling userEvent.click; if paragraphs.length === 0 (and
.bn-trailing-block is absent) throw a clear error or fail the test instead of
calling userEvent.click(undefined). Update the code around the paragraphs
variable and the userEvent.click(paragraphs[paragraphs.length - 1]) call to
perform this explicit empty-check and error message.

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.

Cursor moves back up when hitting ➡️ or ⬇️ after creating a divider

1 participant