Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/react/src/schema/ReactBlockSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,39 @@ export function createReactBlockSpec<
},
{
className: "bn-react-node-view-renderer",
// Any change to a block's content causes ProseMirror to
// re-render the block. On desktop, this change is detected
// using key presses. On mobile, it's instead detected using
// DOM mutations as touchscreen keyboards don't use typical key
// events. However, React's own rendering causes DOM mutations,
// triggering ProseMirror to re-render the block even if the
// mutation is outside the block's editable content. Therefore,
// we need to explicitly ignore mutations outside the block's
// editable content.
ignoreMutation: ({ mutation }) => {
if (mutation.type === "selection") {
return false;
}

const target =
mutation.target.nodeType === Node.ELEMENT_NODE
? (mutation.target as HTMLElement)
: mutation.target.parentElement;
const content = target?.closest("[data-node-view-content]");

// Ignore mutations outside a block's editable content.
if (!content) {
return true;
}

// Also ignore mutations for the editable content wrapper
// element.
if (mutation.target === content) {
return true;
}
Comment on lines +343 to +347
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

🧩 Analysis chain

🌐 Web query:

In ProseMirror NodeView ignoreMutation, should childListmutations targetingcontentDOM be ignored, or treated as content changes that must be handled?

💡 Result:

In ProseMirror NodeViews, childList mutations targeting contentDOM should generally not be ignored [1]. If you ignore mutations within contentDOM, you prevent ProseMirror from detecting changes to the document content, which effectively breaks the editor's ability to sync the DOM with the document state [1][2][3]. The ignoreMutation method is intended to give you control over which DOM changes ProseMirror should observe [2][3]. By returning false, you instruct ProseMirror to re-read the selection or re-parse the range around the mutation [2][3]. Since contentDOM is the container ProseMirror manages to hold the node's children, it is crucial that ProseMirror remains aware of modifications made to it so it can reconcile them with the underlying document model [4][1]. Common practices for ignoreMutation include: 1. Always returning false for mutations that occur within contentDOM, as these are legitimate document content changes [1]. 2. Returning true for mutations that occur in parts of the NodeView's DOM that ProseMirror does not manage (such as custom UI wrappers or non-editable nodes) [1]. 3. Being careful with mutations that occur on the container itself; in some cases, it is necessary to allow ProseMirror to handle these to prevent state desynchronization or issues with cursor positioning [1]. If you find that your NodeView is causing performance issues or unwanted re-renders, it is usually better to ensure that your update method is correctly implemented or that your contentDOM is properly configured, rather than broadly ignoring mutations within it [5][6]. Ignoring mutations within contentDOM is generally counterproductive to the editor's core functionality [1][2].

Citations:


Don’t ignore all wrapper-targeted mutations.
In packages/react/src/schema/ReactBlockSpec.tsx lines ~343-347, ignoreMutation returns true for all mutation types whenever mutation.target === content (the closest [data-node-view-content] wrapper), which also suppresses childList mutations that can represent real editable-content updates (e.g., mobile/IME). Narrow the ignore to wrapper attributes mutations only.

Proposed fix
-                  if (mutation.target === content) {
+                  if (
+                    mutation.target === content &&
+                    mutation.type === "attributes"
+                  ) {
                     return true;
                   }
📝 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
// Also ignore mutations for the editable content wrapper
// element.
if (mutation.target === content) {
return true;
}
// Also ignore mutations for the editable content wrapper
// element.
if (
mutation.target === content &&
mutation.type === "attributes"
) {
return true;
}
🤖 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/react/src/schema/ReactBlockSpec.tsx` around lines 343 - 347, In
ReactBlockSpec.tsx inside the ignoreMutation implementation (the ignoreMutation
function used by the ReactNodeView), stop returning true for all mutation types
when mutation.target === content; instead only ignore wrapper attribute changes
by checking that mutation.type === "attributes" && mutation.target === content
before returning true so childList/textInput mutations on the
[data-node-view-content] element are not suppressed.


return false;
},
},
)(this.props!) as ReturnType<BlockImplementation["render"]>;
} else {
Expand Down
Loading