Skip to content
Draft
Show file tree
Hide file tree
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
11 changes: 11 additions & 0 deletions .changeset/bright-needles-select.md
Original file line number Diff line number Diff line change
@@ -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.
43 changes: 41 additions & 2 deletions e2e/src/shared/createEditorView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -143,7 +143,7 @@ export function createEditorView(
markViews: Record<string, MarkViewConstructor>,
plugins: Plugin[],
) {
return new EditorView(element, {
const view = new EditorView(element, {
state: EditorState.create({
doc: schema.nodeFromJSON(defaultDoc),
schema,
Expand Down Expand Up @@ -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)))
}
}
19 changes: 19 additions & 0 deletions e2e/tests/node-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
20 changes: 18 additions & 2 deletions packages/core/src/nodeView/CoreNodeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,28 @@ export class CoreNodeView<ComponentType> 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.
Expand Down
18 changes: 10 additions & 8 deletions packages/lit/src/nodeView/useLitNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 10 additions & 8 deletions packages/preact/src/nodeView/usePreactNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ export function buildPreactNodeViewCreator<ComponentType>(
userOptions.onUpdate?.()
renderPreactRenderer(nodeView)
},
selectNode() {
userOptions.selectNode?.()
renderPreactRenderer(nodeView)
},
deselectNode() {
userOptions.deselectNode?.()
renderPreactRenderer(nodeView)
},
destroy() {
userOptions.destroy?.()
removePreactRenderer(nodeView)
Expand All @@ -45,6 +37,16 @@ export function buildPreactNodeViewCreator<ComponentType>(
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
}
Expand Down
18 changes: 10 additions & 8 deletions packages/react/src/nodeView/useReactNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ export function buildReactNodeViewCreator<ComponentType>(
userOptions.onUpdate?.()
renderReactRenderer(nodeView)
},
selectNode() {
userOptions.selectNode?.()
renderReactRenderer(nodeView)
},
deselectNode() {
userOptions.deselectNode?.()
renderReactRenderer(nodeView)
},
destroy() {
userOptions.destroy?.()
removeReactRenderer(nodeView)
Expand All @@ -45,6 +37,16 @@ export function buildReactNodeViewCreator<ComponentType>(
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
}
Expand Down
18 changes: 10 additions & 8 deletions packages/solid/src/nodeView/useSolidNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ export function buildSolidNodeViewCreator<ComponentType>(
userOptions.onUpdate?.()
nodeView.updateContext()
},
selectNode() {
userOptions.selectNode?.()
nodeView.updateContext()
},
deselectNode() {
userOptions.deselectNode?.()
nodeView.updateContext()
},
destroy() {
userOptions.destroy?.()
removeSolidRenderer(nodeView)
Expand All @@ -44,6 +36,16 @@ export function buildSolidNodeViewCreator<ComponentType>(
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
}
Expand Down
18 changes: 10 additions & 8 deletions packages/svelte/src/nodeView/useSvelteNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ export function buildSvelteNodeViewCreator<ComponentType>(
userOptions.onUpdate?.()
nodeView.updateContext()
},
selectNode() {
userOptions.selectNode?.()
nodeView.updateContext()
},
deselectNode() {
userOptions.deselectNode?.()
nodeView.updateContext()
},
destroy() {
userOptions.destroy?.()
removeSvelteRenderer(nodeView)
Expand All @@ -46,6 +38,16 @@ export function buildSvelteNodeViewCreator<ComponentType>(
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
}
Expand Down
18 changes: 10 additions & 8 deletions packages/vue/src/nodeView/useVueNodeViewCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ export function buildVueNodeViewCreator<ComponentType>(
userOptions.onUpdate?.()
nodeView.updateContext()
},
selectNode() {
userOptions.selectNode?.()
nodeView.updateContext()
},
deselectNode() {
userOptions.deselectNode?.()
nodeView.updateContext()
},
destroy() {
userOptions.destroy?.()
removeVueRenderer(nodeView)
Expand All @@ -44,6 +36,16 @@ export function buildVueNodeViewCreator<ComponentType>(
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
}
Expand Down
Loading