I'm struggling with making Quill 2.0 support soft line breaks in React 19. The library I'm using is react-quill-new
Basically I want editor to add a <br> tag without the surrounding <p> tags (which is default behavior, same as with Enter key).I found this solution Extending Quill to support soft line breaks and implemented it in my code, but the code won't run saying:
ParchmentError: [Parchment] Unable to create softbreak blot
Here's the complete component
import Embed from 'quill/blots/embed'
import { PropsWithChildren, ReactElement, useState } from 'react'
import ReactQuill, { Quill } from 'react-quill-new'
import 'react-quill-new/dist/quill.snow.css'
const Delta = Quill.import('delta')
export class SoftLineBreakBlot extends Embed {
static blotName = 'softbreak'
static tagName = 'br'
static className = 'softbreak'
}
Quill.register(SoftLineBreakBlot)
export function shiftEnterHandler(this: any, range) {
const currentLeaf = this.quill.getLeaf(range.index)[0]
const nextLeaf = this.quill.getLeaf(range.index + 1)[0]
this.quill.insertEmbed(range.index, 'softbreak', true, Quill.sources.USER)
// Insert a second break if:
// At the end of the editor, OR next leaf has a different parent (<p>)
if (nextLeaf === null || currentLeaf.parent !== nextLeaf.parent) {
this.quill.insertEmbed(range.index, 'softbreak', true, Quill.sources.USER)
}
// Now that we've inserted a line break, move the cursor forward
this.quill.setSelection(range.index + 1, Quill.sources.SILENT)
}
export function brMatcher(node, delta) {
let newDelta = new Delta()
newDelta.insert({ softbreak: true })
return newDelta
}
interface EditorProps extends PropsWithChildren {
defaultValue?: string
onChange?: (value: string) => void
children?: ReactElement
}
const Editor = ({ defaultValue = '', onChange, children }: EditorProps) => {
const [value, setValue] = useState(defaultValue)
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
['clean'],
],
keyboard: {
bindings: {
'shift enter': {
key: 13,
shiftKey: true,
handler: shiftEnterHandler,
},
},
},
clipboard: {
matchers: [['BR', brMatcher]],
},
}
const formats = ['bold', 'italic', 'underline', 'strike', 'list', 'image', 'link']
const handleChange = (newValue: string) => {
setValue(newValue)
if (onChange) {
onChange(newValue)
}
}
return (
<ReactQuill
theme="snow"
value={value}
onChange={handleChange}
modules={modules}
formats={formats}
>
{children}
</ReactQuill>
)
}
export default Editor
Following the previous answer, with the import of Embed like this `import Embed from 'quill/blots/embed'`, I want to provide further steps who seems to also be importants.
We should overwrite the registration with the second flag `true` in `Quill.register(SoftLineBreakBlot, true)`
Insert a Zero-Width space \u200B right after the "softbreak" to stabilize the cursor position after the `<br>` could also improve the unexpected behaviours before and after the line return
Use "Enter" instead of key 13 seems to give better results in the keyboard bindings declaration
The declaration of `softbreak` in the formats is crucial to tell Quill which embed we need to insert in the editor
const Delta = Quill.import('delta')
const Embed = Quill.import('blots/embed')
export class SoftLineBreakBlot extends Embed {
static blotName = 'softbreak'
static tagName = 'br'
static className = 'softbreak'
}
Quill.register(SoftLineBreakBlot, true) // overwrite the registration
function shiftEnterHandler(this: any, range) {
this.quill.insertEmbed(range.index, 'softbreak', true, Quill.sources.USER)
this.quill.insertText(range.index + 1, '\u200B', Quill.sources.USER)
this.quill.setSelection(range.index + 1, Quill.sources.SILENT)
}
const brMatcher = () => new Delta().insert({ softbreak: true })
Then inside the component for modules and formats declarations :
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'clean'],
],
keyboard: {
bindings: {
shiftEnter: {
key: 'Enter', // works better than 13
shiftKey: true,
handler: shiftEnterHandler,
},
},
},
clipboard: {
matchers: [['BR', brMatcher]],
matchVisual: false,
},
}
const formats = [
'bold', 'italic', 'underline', 'strike',
'list',
'image', 'link',
'softbreak', // quill need to know which format "embed"
]