1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-22 23:12:52 +02:00

Optimize TextString rendering to support browser/OS text features, eg fix native spellcheck (#4733)

* slate-react: use a layout effect to render leaf text nodes instead of via virtual DOM, which implements diffing with real DOM avoiding interference with native TextNode behaviors for example spellcheck

* lint

* clarify and simplify extreme case of null text in TextString rendering

* code style: use string interpolation in TextString

Co-authored-by: Nemanja Tosic <netosic90@gmail.com>

Co-authored-by: Peter Sipos <schipy@craft.do>
Co-authored-by: Nemanja Tosic <netosic90@gmail.com>
This commit is contained in:
Peter Sipos
2021-12-18 16:38:51 +01:00
committed by GitHub
parent 228cee56a1
commit ccafb6982f

View File

@@ -1,4 +1,4 @@
import React, { useRef } from 'react' import React, { useRef, useLayoutEffect } from 'react'
import { Editor, Text, Path, Element, Node } from 'slate' import { Editor, Text, Path, Element, Node } from 'slate'
import { ReactEditor, useSlateStatic } from '..' import { ReactEditor, useSlateStatic } from '..'
@@ -59,21 +59,30 @@ const TextString = (props: { text: string; isTrailing?: boolean }) => {
const { text, isTrailing = false } = props const { text, isTrailing = false } = props
const ref = useRef<HTMLSpanElement>(null) const ref = useRef<HTMLSpanElement>(null)
const forceUpdateCount = useRef(0)
if (ref.current && ref.current.textContent !== text) { // This is the actual text rendering boundary where we interface with the DOM
forceUpdateCount.current += 1 // The text is not rendered as part of the virtual DOM, as since we handle basic character insertions natively,
} // updating the DOM is not a one way dataflow anymore. What we need here is not reconciliation and diffing
// with previous version of the virtual DOM, but rather diffing with the actual DOM element, and replace the DOM <span> content
// exactly if and only if its current content does not match our current virtual DOM.
// Otherwise the DOM TextNode would always be replaced by React as the user types, which interferes with native text features,
// eg makes native spellcheck opt out from checking the text node.
// This component may have skipped rendering due to native operations being // useLayoutEffect: updating our span before browser paint
// applied. If an undo is performed React will see the old and new shadow DOM useLayoutEffect(() => {
// match and not apply an update. Forces each render to actually reconcile. // null coalescing text to make sure we're not outputing "null" as a string in the extreme case it is nullish at runtime
return ( const textWithTrailing = `${text ?? ''}${isTrailing ? '\n' : ''}`
<span data-slate-string ref={ref} key={forceUpdateCount.current}>
{text} if (ref.current && ref.current.textContent !== textWithTrailing) {
{isTrailing ? '\n' : null} ref.current.textContent = textWithTrailing
</span> }
)
// intentionally not specifying dependencies, so that this effect runs on every render
// as this effectively replaces "specifying the text in the virtual DOM under the <span> below" on each render
})
// the span is intentionally same on every render in virtual DOM, actual rendering happens in the layout effect above
return <span data-slate-string ref={ref} />
} }
/** /**