fix(editor): keep toolbar hidden through ime settle

This commit is contained in:
lucaronin
2026-05-04 01:02:07 +02:00
parent dfb9f98b7a
commit 1b218f5250
3 changed files with 146 additions and 27 deletions

View File

@@ -6,10 +6,31 @@ import type {
} from '@blocknote/core'
import { useEffect, useRef, useState } from 'react'
const COMPOSITION_SETTLE_MS = 250
function eventTargetsEditor(editorElement: Element, target: EventTarget | null) {
return target instanceof Node && editorElement.contains(target)
}
function focusTargetsEditor(editorElement: Element) {
const activeElement = editorElement.ownerDocument.activeElement
return activeElement instanceof Node && editorElement.contains(activeElement)
}
function selectionTargetsEditor(editorElement: Element) {
const anchorNode = editorElement.ownerDocument.getSelection()?.anchorNode
return anchorNode instanceof Node && editorElement.contains(anchorNode)
}
function compositionEventTargetsEditor(
editorElement: Element,
event: CompositionEvent,
) {
return eventTargetsEditor(editorElement, event.target)
|| focusTargetsEditor(editorElement)
|| selectionTargetsEditor(editorElement)
}
export function useEditorComposing<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
@@ -17,41 +38,72 @@ export function useEditorComposing<
>(editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {
const [isComposing, setIsComposing] = useState(false)
const composingRef = useRef(false)
const settleTimeoutRef = useRef<number | null>(null)
const editorElement = editor.domElement ?? null
useEffect(() => {
const clearSettleTimeout = () => {
if (settleTimeoutRef.current === null) return
window.clearTimeout(settleTimeoutRef.current)
settleTimeoutRef.current = null
}
const updateComposing = (nextIsComposing: boolean) => {
if (composingRef.current === nextIsComposing) return
composingRef.current = nextIsComposing
setIsComposing(nextIsComposing)
}
const startComposing = () => {
clearSettleTimeout()
updateComposing(true)
}
const finishComposing = () => {
clearSettleTimeout()
settleTimeoutRef.current = window.setTimeout(() => {
settleTimeoutRef.current = null
updateComposing(false)
}, COMPOSITION_SETTLE_MS)
}
clearSettleTimeout()
updateComposing(false)
if (!editorElement) return
const handleCompositionStart = (event: CompositionEvent) => {
if (!eventTargetsEditor(editorElement, event.target)) return
updateComposing(true)
if (!compositionEventTargetsEditor(editorElement, event)) return
startComposing()
}
const handleCompositionUpdate = (event: CompositionEvent) => {
if (!compositionEventTargetsEditor(editorElement, event)) return
startComposing()
}
const handleCompositionEnd = (event: CompositionEvent) => {
if (
!composingRef.current
&& !eventTargetsEditor(editorElement, event.target)
&& !compositionEventTargetsEditor(editorElement, event)
) {
return
}
updateComposing(false)
finishComposing()
}
document.addEventListener('compositionstart', handleCompositionStart, true)
document.addEventListener('compositionupdate', handleCompositionUpdate, true)
document.addEventListener('compositionend', handleCompositionEnd, true)
document.addEventListener('compositioncancel', handleCompositionEnd, true)
return () => {
clearSettleTimeout()
document.removeEventListener('compositionstart', handleCompositionStart, true)
document.removeEventListener('compositionupdate', handleCompositionUpdate, true)
document.removeEventListener('compositionend', handleCompositionEnd, true)
document.removeEventListener('compositioncancel', handleCompositionEnd, true)
}
}, [editorElement])