From 5404e4b2302437a0d8d7de43c9d0475572c2c237 Mon Sep 17 00:00:00 2001 From: ocavue Date: Sat, 25 Jul 2026 04:12:17 +1000 Subject: [PATCH] fix(core): report `contentDOM` replacement mutations --- .changeset/tender-spoons-report.md | 5 +++++ e2e/tests/node-view.spec.ts | 7 ++++--- packages/core/src/nodeView/CoreNodeView.ts | 11 ++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .changeset/tender-spoons-report.md diff --git a/.changeset/tender-spoons-report.md b/.changeset/tender-spoons-report.md new file mode 100644 index 00000000..73faec10 --- /dev/null +++ b/.changeset/tender-spoons-report.md @@ -0,0 +1,5 @@ +--- +'@prosemirror-adapter/core': patch +--- + +Report replacement mutations after the browser removes a node view's `contentDOM`, allowing ProseMirror to preserve the first character typed over a full selection. diff --git a/e2e/tests/node-view.spec.ts b/e2e/tests/node-view.spec.ts index 18925f79..1a9516e9 100644 --- a/e2e/tests/node-view.spec.ts +++ b/e2e/tests/node-view.spec.ts @@ -42,14 +42,15 @@ testAll(() => { }) }) -testAll(() => { +testAll(({ framework }) => { test('code block node view preserves the first character typed over a full selection', async ({ page, browserName, }) => { test.fail( - browserName === 'chromium' || browserName === 'webkit', - 'prosemirror-view currently drops the first character in these browsers', + // https://code.haverbeke.berlin/prosemirror/prosemirror/issues/1581 + framework === 'lit' && (browserName === 'chromium' || browserName === 'webkit'), + 'prosemirror-view cannot recover Lit node view content in these browsers', ) const content = page.locator('.editor [data-node-view-root="true"] pre code[data-node-view-content="true"]') diff --git a/packages/core/src/nodeView/CoreNodeView.ts b/packages/core/src/nodeView/CoreNodeView.ts index d1e651f7..60414cdc 100644 --- a/packages/core/src/nodeView/CoreNodeView.ts +++ b/packages/core/src/nodeView/CoreNodeView.ts @@ -8,6 +8,8 @@ import { isContentDOMRemoval } from '../utils/is-content-dom-removal' import type { CoreNodeViewSpec, CoreNodeViewUserOptions, NodeViewDOMSpec } from './CoreNodeViewOptions' export class CoreNodeView implements NodeView { + #contentDOMWasRemoved = false + key: string dom: HTMLElement contentDOM: HTMLElement | null @@ -107,6 +109,8 @@ export class CoreNodeView implements NodeView { shouldIgnoreMutation: (mutation: ViewMutationRecord) => boolean = (mutation) => { if (!this.dom || !this.contentDOM) return true + if (this.dom.contains(this.contentDOM)) this.#contentDOMWasRemoved = false + if (this.node.isLeaf || this.node.isAtom) return true if (mutation.type === 'selection') return false @@ -115,7 +119,12 @@ export class CoreNodeView implements NodeView { if (this.contentDOM.contains(mutation.target)) return false - if (isContentDOMRemoval(mutation, this.contentDOM)) return false + if (isContentDOMRemoval(mutation, this.contentDOM)) { + this.#contentDOMWasRemoved = true + return false + } + + if (this.#contentDOMWasRemoved && this.dom.contains(mutation.target)) return false return true }