Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/little-shrimps-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tiptap/extensions': patch
'tiptap-demos': patch
---

Fix the `Selection` extension highlighting beyond the selected text on multi-line selections: the native browser selection is now hidden while the editor is blurred, so only the styled `.selection` decoration is shown.
4 changes: 2 additions & 2 deletions demos/src/Extensions/Selection/React/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export default () => {
],
content: `
<p>
The selection extension adds a class to the selection when the editor is blurred. That enables you to visually preserve the selection even though the editor is blurred. By default, it’ll add <code>.selection</code> classname.
The Selection extension adds a class to the current text selection when the editor is blurred, which lets you keep the selected text highlighted even after the editor loses focus. This is especially useful when a selection spans multiple wrapped lines, where only the selected text should be highlighted rather than the empty space at the end of each line. By default it adds a <code>.selection</code> classname that you can style.
</p>
`,

onCreate: ctx => {
ctx.editor.commands.setTextSelection({ from: 5, to: 30 })
ctx.editor.commands.setTextSelection({ from: 5, to: 280 })
},
})

Expand Down
4 changes: 2 additions & 2 deletions demos/src/Extensions/Selection/Vue/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export default {
],
content: `
<p>
The selection extension adds a class to the selection when the editor is blurred. That enables you to visually preserve the selection even though the editor is blurred. By default, it’ll add <code>.selection</code> classname.
The Selection extension adds a class to the current text selection when the editor is blurred, which lets you keep the selected text highlighted even after the editor loses focus. This is especially useful when a selection spans multiple wrapped lines, where only the selected text should be highlighted rather than the empty space at the end of each line. By default it adds a <code>.selection</code> classname that you can style.
</p>
`,
onCreate: ({ editor }) => {
editor.commands.setTextSelection({ from: 5, to: 30 })
editor.commands.setTextSelection({ from: 5, to: 280 })
},
})
},
Expand Down
45 changes: 45 additions & 0 deletions packages/extensions/__tests__/selection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { Selection } from '@tiptap/extensions'
import { afterEach, describe, expect, it } from 'vitest'

const styleSelector = 'style[data-tiptap-style-selection]'

describe('extension-selection', () => {
let editor: Editor | null = null

afterEach(() => {
if (editor) {
editor.destroy()
editor = null
}

document.querySelectorAll(styleSelector).forEach(node => node.remove())
})

it('injects a stylesheet that hides the native selection while the editor is blurred', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, Selection],
content: '<p>Hello world</p>',
})

const styleTag = document.querySelector(styleSelector)

expect(styleTag).not.toBeNull()
expect(styleTag?.textContent).toContain('.ProseMirror:not(.ProseMirror-focused) *::selection')
expect(styleTag?.textContent).toContain('*::-moz-selection')
expect(styleTag?.textContent).toContain('background: transparent')
})

it('does not inject the stylesheet when injectCSS is disabled', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, Selection],
content: '<p>Hello world</p>',
injectCSS: false,
})

expect(document.querySelector(styleSelector)).toBeNull()
})
})
14 changes: 13 additions & 1 deletion packages/extensions/src/selection/selection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Extension, isNodeSelection } from '@tiptap/core'
import { createStyleTag, Extension, isNodeSelection } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from '@tiptap/pm/view'

const selectionStyle = `.ProseMirror:not(.ProseMirror-focused) *::selection {
background: transparent;
}

.ProseMirror:not(.ProseMirror-focused) *::-moz-selection {
background: transparent;
}`

export type SelectionOptions = {
/**
* The class name that should be added to the selected text.
Expand All @@ -27,6 +35,10 @@ export const Selection = Extension.create<SelectionOptions>({
addProseMirrorPlugins() {
const { editor, options } = this

if (editor.options.injectCSS && typeof document !== 'undefined') {
createStyleTag(selectionStyle, editor.options.injectNonce, 'selection')
}

return [
new Plugin({
key: new PluginKey('selection'),
Expand Down
Loading