tldraw
Version:
A tiny little drawing editor.
63 lines (55 loc) • 2.05 kB
text/typescript
import { ExtractShapeByProps, TLRichText, TLShapeId, isAccelKey, useEditor } from '@tldraw/editor'
import { useCallback, useEffect, useRef } from 'react'
import { isEmptyRichText } from '../../utils/text/richText'
import { useEditableTextCommon } from './useEditablePlainText'
/** @public */
export function useEditableRichText(
shapeId: TLShapeId,
type: ExtractShapeByProps<{ richText: TLRichText }>['type'],
richText?: TLRichText
) {
const commonUseEditableTextHandlers = useEditableTextCommon(shapeId)
const isEditing = commonUseEditableTextHandlers.isEditing
const editor = useEditor()
const rInput = useRef<HTMLDivElement>(null)
const isEmpty = richText && isEmptyRichText(richText)
useEffect(() => {
if (!isEditing) return
// N.B. In Development mode you need to ensure you're testing this without StrictMode on.
// Otherwise it's not gonna work as expected on iOS.
const contentEditable = rInput.current?.querySelector('[contenteditable]')
if (contentEditable && document.activeElement !== rInput.current) {
// This is a crucial difference with useEditablePlainText, that we need to select the
// child contentEditable <div> not rInput.current directly.
// Specifically, this is to ensure iOS works. Otherwise, we could just use rInput.current.
;(contentEditable as HTMLElement).focus()
}
}, [editor, isEditing])
// When the user presses ctrl / meta enter, complete the editing state.
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (editor.getEditingShapeId() !== shapeId) return
if (e.key === 'Enter' && isAccelKey(e)) editor.complete()
},
[editor, shapeId]
)
// When the text changes, update the text value.
const handleChange = useCallback(
({ richText }: { richText: TLRichText }) => {
if (editor.getEditingShapeId() !== shapeId) return
editor.updateShape({
id: shapeId,
type,
props: { richText },
})
},
[editor, shapeId, type]
)
return {
rInput,
handleKeyDown,
handleChange,
isEmpty,
...commonUseEditableTextHandlers,
}
}