From aea274ead62fa3360863cbe4e7e35f2b56c0f2ec Mon Sep 17 00:00:00 2001 From: ocavue Date: Wed, 8 Jul 2026 03:34:24 +1000 Subject: [PATCH] restore `ProseMirror-selectednode` default --- .changeset/bright-needles-select.md | 11 +++++ e2e/src/shared/createEditorView.ts | 43 ++++++++++++++++++- e2e/tests/node-view.spec.ts | 19 ++++++++ packages/core/src/nodeView/CoreNodeView.ts | 20 ++++++++- .../lit/src/nodeView/useLitNodeViewCreator.ts | 18 ++++---- .../src/nodeView/usePreactNodeViewCreator.ts | 18 ++++---- .../src/nodeView/useReactNodeViewCreator.ts | 18 ++++---- .../src/nodeView/useSolidNodeViewCreator.ts | 18 ++++---- .../src/nodeView/useSvelteNodeViewCreator.ts | 18 ++++---- .../vue/src/nodeView/useVueNodeViewCreator.ts | 18 ++++---- 10 files changed, 149 insertions(+), 52 deletions(-) create mode 100644 .changeset/bright-needles-select.md diff --git a/.changeset/bright-needles-select.md b/.changeset/bright-needles-select.md new file mode 100644 index 00000000..5778b922 --- /dev/null +++ b/.changeset/bright-needles-select.md @@ -0,0 +1,11 @@ +--- +'@prosemirror-adapter/core': patch +'@prosemirror-adapter/lit': patch +'@prosemirror-adapter/preact': patch +'@prosemirror-adapter/react': patch +'@prosemirror-adapter/solid': patch +'@prosemirror-adapter/svelte': patch +'@prosemirror-adapter/vue': patch +--- + +Restore ProseMirror's default selected-node class and draggable behavior for node views without custom select handlers. diff --git a/e2e/src/shared/createEditorView.ts b/e2e/src/shared/createEditorView.ts index 438be77a..e8c2911c 100644 --- a/e2e/src/shared/createEditorView.ts +++ b/e2e/src/shared/createEditorView.ts @@ -6,7 +6,7 @@ import { exampleSetup } from 'prosemirror-example-setup' import { keymap } from 'prosemirror-keymap' import { schema } from 'prosemirror-schema-basic' import type { Plugin } from 'prosemirror-state' -import { EditorState } from 'prosemirror-state' +import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state' import type { MarkViewConstructor, NodeViewConstructor } from 'prosemirror-view' import { EditorView } from 'prosemirror-view' @@ -143,7 +143,7 @@ export function createEditorView( markViews: Record, plugins: Plugin[], ) { - return new EditorView(element, { + const view = new EditorView(element, { state: EditorState.create({ doc: schema.nodeFromJSON(defaultDoc), schema, @@ -174,4 +174,43 @@ export function createEditorView( nodeViews, markViews, }) + + installTestHelpers(view) + + return view +} + +type TestWindow = Window & { + __selectNodeByType?: (typeName: string, index?: number) => void + __setTextSelection?: (pos?: number) => void +} + +function installTestHelpers(view: EditorView) { + const testWindow = window as TestWindow + + testWindow.__selectNodeByType = (typeName, index = 0) => { + let foundPos: number | undefined + let remaining = index + + view.state.doc.descendants((node, pos) => { + if (foundPos != null) return false + if (node.type.name !== typeName) return true + if (remaining > 0) { + remaining -= 1 + return true + } + foundPos = pos + return false + }) + + if (foundPos == null) { + throw new Error(`Cannot find node of type "${typeName}" at index ${index}`) + } + + view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, foundPos))) + } + + testWindow.__setTextSelection = (pos = 1) => { + view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(pos), 1))) + } } diff --git a/e2e/tests/node-view.spec.ts b/e2e/tests/node-view.spec.ts index 403736d9..46929414 100644 --- a/e2e/tests/node-view.spec.ts +++ b/e2e/tests/node-view.spec.ts @@ -40,4 +40,23 @@ testAll(() => { await expect(h4).not.toBeVisible() await expect(h5).toBeVisible() }) + + test('node selection applies ProseMirror default selected node behavior', async ({ page }) => { + const headingNodeView = page.locator('.editor [data-node-view-root="true"]').first() + await expect(headingNodeView).toBeVisible() + + await page.evaluate(() => { + ;(window as unknown as { __selectNodeByType: (typeName: string) => void }).__selectNodeByType('heading') + }) + + await expect(headingNodeView).toHaveClass(/ProseMirror-selectednode/) + await expect(headingNodeView).toHaveAttribute('draggable', 'true') + + await page.evaluate(() => { + ;(window as unknown as { __setTextSelection: () => void }).__setTextSelection() + }) + + await expect(headingNodeView).not.toHaveClass(/ProseMirror-selectednode/) + await expect(headingNodeView).not.toHaveAttribute('draggable', 'true') + }) }) diff --git a/packages/core/src/nodeView/CoreNodeView.ts b/packages/core/src/nodeView/CoreNodeView.ts index 0d4454af..4cbd23a4 100644 --- a/packages/core/src/nodeView/CoreNodeView.ts +++ b/packages/core/src/nodeView/CoreNodeView.ts @@ -73,12 +73,28 @@ export class CoreNodeView implements NodeView { selectNode = () => { this.selected = true - this.options.selectNode?.() + if (this.options.selectNode) this.options.selectNode() + else this.selectNodeDefault() } deselectNode = () => { this.selected = false - this.options.deselectNode?.() + if (this.options.deselectNode) this.options.deselectNode() + else this.deselectNodeDefault() + } + + protected selectNodeDefault = () => { + this.dom.classList.add('ProseMirror-selectednode') + if (this.contentDOM || !this.node.type.spec.draggable) { + this.dom.draggable = true + } + } + + protected deselectNodeDefault = () => { + this.dom.classList.remove('ProseMirror-selectednode') + if (this.contentDOM || !this.node.type.spec.draggable) { + this.dom.removeAttribute('draggable') + } } // Return true if the current node view instance can handle the update. diff --git a/packages/lit/src/nodeView/useLitNodeViewCreator.ts b/packages/lit/src/nodeView/useLitNodeViewCreator.ts index 84d37d9e..c52fbfd1 100644 --- a/packages/lit/src/nodeView/useLitNodeViewCreator.ts +++ b/packages/lit/src/nodeView/useLitNodeViewCreator.ts @@ -20,20 +20,22 @@ export function useLitNodeViewCreator( options.onUpdate?.() nodeView.updateContext() }, - selectNode() { - options.selectNode?.() - nodeView.updateContext() - }, - deselectNode() { - options.deselectNode?.() - nodeView.updateContext() - }, destroy() { options.destroy?.() removeLitRenderer(nodeView) }, }, }) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + nodeView.updateContext() + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + nodeView.updateContext() + } renderLitRenderer(nodeView) return nodeView diff --git a/packages/preact/src/nodeView/usePreactNodeViewCreator.ts b/packages/preact/src/nodeView/usePreactNodeViewCreator.ts index 03106a9c..3df79ffb 100644 --- a/packages/preact/src/nodeView/usePreactNodeViewCreator.ts +++ b/packages/preact/src/nodeView/usePreactNodeViewCreator.ts @@ -23,14 +23,6 @@ export function buildPreactNodeViewCreator( userOptions.onUpdate?.() renderPreactRenderer(nodeView) }, - selectNode() { - userOptions.selectNode?.() - renderPreactRenderer(nodeView) - }, - deselectNode() { - userOptions.deselectNode?.() - renderPreactRenderer(nodeView) - }, destroy() { userOptions.destroy?.() removePreactRenderer(nodeView) @@ -45,6 +37,16 @@ export function buildPreactNodeViewCreator( options: patchedUserOptions, } const nodeView = new PreactNodeViewClass(spec) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + renderPreactRenderer(nodeView) + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + renderPreactRenderer(nodeView) + } renderPreactRenderer(nodeView, false) return nodeView } diff --git a/packages/react/src/nodeView/useReactNodeViewCreator.ts b/packages/react/src/nodeView/useReactNodeViewCreator.ts index 3ba7722f..92e4e180 100644 --- a/packages/react/src/nodeView/useReactNodeViewCreator.ts +++ b/packages/react/src/nodeView/useReactNodeViewCreator.ts @@ -23,14 +23,6 @@ export function buildReactNodeViewCreator( userOptions.onUpdate?.() renderReactRenderer(nodeView) }, - selectNode() { - userOptions.selectNode?.() - renderReactRenderer(nodeView) - }, - deselectNode() { - userOptions.deselectNode?.() - renderReactRenderer(nodeView) - }, destroy() { userOptions.destroy?.() removeReactRenderer(nodeView) @@ -45,6 +37,16 @@ export function buildReactNodeViewCreator( options: patchedUserOptions, } const nodeView = new ReactNodeViewClass(spec) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + renderReactRenderer(nodeView) + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + renderReactRenderer(nodeView) + } renderReactRenderer(nodeView, false) return nodeView } diff --git a/packages/solid/src/nodeView/useSolidNodeViewCreator.ts b/packages/solid/src/nodeView/useSolidNodeViewCreator.ts index e2048fd1..19b26bdc 100644 --- a/packages/solid/src/nodeView/useSolidNodeViewCreator.ts +++ b/packages/solid/src/nodeView/useSolidNodeViewCreator.ts @@ -22,14 +22,6 @@ export function buildSolidNodeViewCreator( userOptions.onUpdate?.() nodeView.updateContext() }, - selectNode() { - userOptions.selectNode?.() - nodeView.updateContext() - }, - deselectNode() { - userOptions.deselectNode?.() - nodeView.updateContext() - }, destroy() { userOptions.destroy?.() removeSolidRenderer(nodeView) @@ -44,6 +36,16 @@ export function buildSolidNodeViewCreator( options: patchedUserOptions, } const nodeView = new SolidNodeViewClass(spec) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + nodeView.updateContext() + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + nodeView.updateContext() + } renderSolidRenderer(nodeView, false) return nodeView } diff --git a/packages/svelte/src/nodeView/useSvelteNodeViewCreator.ts b/packages/svelte/src/nodeView/useSvelteNodeViewCreator.ts index 0bd30738..90b8deb7 100644 --- a/packages/svelte/src/nodeView/useSvelteNodeViewCreator.ts +++ b/packages/svelte/src/nodeView/useSvelteNodeViewCreator.ts @@ -24,14 +24,6 @@ export function buildSvelteNodeViewCreator( userOptions.onUpdate?.() nodeView.updateContext() }, - selectNode() { - userOptions.selectNode?.() - nodeView.updateContext() - }, - deselectNode() { - userOptions.deselectNode?.() - nodeView.updateContext() - }, destroy() { userOptions.destroy?.() removeSvelteRenderer(nodeView) @@ -46,6 +38,16 @@ export function buildSvelteNodeViewCreator( options: patchedUserOptions, } const nodeView = new SvelteNodeViewClass(spec) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + nodeView.updateContext() + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + nodeView.updateContext() + } renderSvelteRenderer(nodeView, { context }) return nodeView } diff --git a/packages/vue/src/nodeView/useVueNodeViewCreator.ts b/packages/vue/src/nodeView/useVueNodeViewCreator.ts index 18aa9abb..2be3118b 100644 --- a/packages/vue/src/nodeView/useVueNodeViewCreator.ts +++ b/packages/vue/src/nodeView/useVueNodeViewCreator.ts @@ -22,14 +22,6 @@ export function buildVueNodeViewCreator( userOptions.onUpdate?.() nodeView.updateContext() }, - selectNode() { - userOptions.selectNode?.() - nodeView.updateContext() - }, - deselectNode() { - userOptions.deselectNode?.() - nodeView.updateContext() - }, destroy() { userOptions.destroy?.() removeVueRenderer(nodeView) @@ -44,6 +36,16 @@ export function buildVueNodeViewCreator( options: patchedUserOptions, } const nodeView = new VueNodeViewClass(spec) + const selectNode = nodeView.selectNode + nodeView.selectNode = () => { + selectNode() + nodeView.updateContext() + } + const deselectNode = nodeView.deselectNode + nodeView.deselectNode = () => { + deselectNode() + nodeView.updateContext() + } renderVueRenderer(nodeView) return nodeView }