mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-20 06:01:24 +02:00
Fix/delete all closes keyboard (#5368)
* fix: clean up ref handling in leaf * fix: delay rendering of placeholder to allow selections to settle * fix: move placeholder height calculation into layout effect * fix: add change set * fix: handle placeholder resizing in a better way * fix: fix placeholder integration test
This commit is contained in:
5
.changeset/rare-baboons-camp.md
Normal file
5
.changeset/rare-baboons-camp.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'slate-react': patch
|
||||
---
|
||||
|
||||
Delay rendering of placeholder to avoid IME hiding
|
@@ -29,7 +29,6 @@ import { useSlate } from '../hooks/use-slate'
|
||||
import { TRIPLE_CLICK } from '../utils/constants'
|
||||
import {
|
||||
DOMElement,
|
||||
DOMNode,
|
||||
DOMRange,
|
||||
DOMText,
|
||||
getDefaultView,
|
||||
@@ -154,6 +153,9 @@ export const Editable = (props: EditableProps) => {
|
||||
const [isComposing, setIsComposing] = useState(false)
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
const deferredOperations = useRef<DeferredOperation[]>([])
|
||||
const [placeholderHeight, setPlaceholderHeight] = useState<
|
||||
number | undefined
|
||||
>()
|
||||
|
||||
const { onUserInput, receivedUserInput } = useTrackUserInput()
|
||||
|
||||
@@ -780,17 +782,30 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
const decorations = decorate([editor, []])
|
||||
|
||||
if (
|
||||
const showPlaceholder =
|
||||
placeholder &&
|
||||
editor.children.length === 1 &&
|
||||
Array.from(Node.texts(editor)).length === 1 &&
|
||||
Node.string(editor) === '' &&
|
||||
!isComposing
|
||||
) {
|
||||
|
||||
const placeHolderResizeHandler = useCallback(
|
||||
(placeholderEl: HTMLElement | null) => {
|
||||
if (placeholderEl && showPlaceholder) {
|
||||
setPlaceholderHeight(placeholderEl.getBoundingClientRect()?.height)
|
||||
} else {
|
||||
setPlaceholderHeight(undefined)
|
||||
}
|
||||
},
|
||||
[showPlaceholder]
|
||||
)
|
||||
|
||||
if (showPlaceholder) {
|
||||
const start = Editor.start(editor, [])
|
||||
decorations.push({
|
||||
[PLACEHOLDER_SYMBOL]: true,
|
||||
placeholder,
|
||||
onPlaceholderResize: placeHolderResizeHandler,
|
||||
anchor: start,
|
||||
focus: start,
|
||||
})
|
||||
@@ -845,10 +860,6 @@ export const Editable = (props: EditableProps) => {
|
||||
})
|
||||
})
|
||||
|
||||
const placeholderHeight = EDITOR_TO_PLACEHOLDER_ELEMENT.get(
|
||||
editor
|
||||
)?.getBoundingClientRect()?.height
|
||||
|
||||
return (
|
||||
<ReadOnlyContext.Provider value={readOnly}>
|
||||
<DecorateContext.Provider value={decorate}>
|
||||
@@ -1696,7 +1707,7 @@ export type RenderPlaceholderProps = {
|
||||
'data-slate-placeholder': boolean
|
||||
dir?: 'rtl'
|
||||
contentEditable: boolean
|
||||
ref: React.RefObject<any>
|
||||
ref: React.RefCallback<any>
|
||||
style: React.CSSProperties
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,10 @@
|
||||
import React, { useRef, useEffect } from 'react'
|
||||
import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
MutableRefObject,
|
||||
useState,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { Element, Text } from 'slate'
|
||||
import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observer'
|
||||
import String from './string'
|
||||
@@ -10,10 +16,30 @@ import {
|
||||
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
|
||||
import { useSlateStatic } from '../hooks/use-slate-static'
|
||||
|
||||
function disconnectPlaceholderResizeObserver(
|
||||
placeholderResizeObserver: MutableRefObject<ResizeObserver | null>,
|
||||
releaseObserver: boolean
|
||||
) {
|
||||
if (placeholderResizeObserver.current) {
|
||||
placeholderResizeObserver.current.disconnect()
|
||||
if (releaseObserver) {
|
||||
placeholderResizeObserver.current = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TimerId = ReturnType<typeof setTimeout> | null
|
||||
|
||||
function clearTimeoutRef(timeoutRef: MutableRefObject<TimerId>) {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual leaves in a text node with unique formatting.
|
||||
*/
|
||||
|
||||
const Leaf = (props: {
|
||||
isLast: boolean
|
||||
leaf: Text
|
||||
@@ -31,65 +57,61 @@ const Leaf = (props: {
|
||||
renderLeaf = (props: RenderLeafProps) => <DefaultLeaf {...props} />,
|
||||
} = props
|
||||
|
||||
const lastPlaceholderRef = useRef<HTMLSpanElement | null>(null)
|
||||
const placeholderRef = useRef<HTMLSpanElement | null>(null)
|
||||
const editor = useSlateStatic()
|
||||
|
||||
const placeholderResizeObserver = useRef<ResizeObserver | null>(null)
|
||||
const placeholderRef = useRef<HTMLElement | null>(null)
|
||||
const [showPlaceholder, setShowPlaceholder] = useState(false)
|
||||
const showPlaceholderTimeoutRef = useRef<TimerId>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (placeholderResizeObserver.current) {
|
||||
placeholderResizeObserver.current.disconnect()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
const callbackPlaceholderRef = useCallback(
|
||||
(placeholderEl: HTMLElement | null) => {
|
||||
disconnectPlaceholderResizeObserver(
|
||||
placeholderResizeObserver,
|
||||
placeholderEl == null
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const placeholderEl = placeholderRef?.current
|
||||
|
||||
if (placeholderEl) {
|
||||
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
|
||||
} else {
|
||||
if (placeholderEl == null) {
|
||||
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
|
||||
}
|
||||
leaf.onPlaceholderResize?.(null)
|
||||
} else {
|
||||
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
|
||||
|
||||
if (placeholderResizeObserver.current) {
|
||||
// Update existing observer.
|
||||
placeholderResizeObserver.current.disconnect()
|
||||
if (placeholderEl)
|
||||
placeholderResizeObserver.current.observe(placeholderEl)
|
||||
} else if (placeholderEl) {
|
||||
if (!placeholderResizeObserver.current) {
|
||||
// Create a new observer and observe the placeholder element.
|
||||
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill
|
||||
placeholderResizeObserver.current = new ResizeObserver(() => {
|
||||
// Force a re-render of the editor so its min-height can be updated
|
||||
// to the new height of the placeholder.
|
||||
const forceRender = EDITOR_TO_FORCE_RENDER.get(editor)
|
||||
forceRender?.()
|
||||
leaf.onPlaceholderResize?.(placeholderEl)
|
||||
})
|
||||
}
|
||||
placeholderResizeObserver.current.observe(placeholderEl)
|
||||
placeholderRef.current = placeholderEl
|
||||
}
|
||||
|
||||
if (!placeholderEl && lastPlaceholderRef.current) {
|
||||
// No placeholder element, so no need for a resize observer.
|
||||
// Force a re-render of the editor so its min-height can be reset.
|
||||
const forceRender = EDITOR_TO_FORCE_RENDER.get(editor)
|
||||
forceRender?.()
|
||||
}
|
||||
|
||||
lastPlaceholderRef.current = placeholderRef.current
|
||||
|
||||
return () => {
|
||||
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
|
||||
}
|
||||
}, [placeholderRef, leaf, editor])
|
||||
},
|
||||
[placeholderRef, leaf, editor]
|
||||
)
|
||||
|
||||
let children = (
|
||||
<String isLast={isLast} leaf={leaf} parent={parent} text={text} />
|
||||
)
|
||||
|
||||
if (leaf[PLACEHOLDER_SYMBOL]) {
|
||||
const leafIsPlaceholder = leaf[PLACEHOLDER_SYMBOL]
|
||||
useEffect(() => {
|
||||
if (leafIsPlaceholder) {
|
||||
if (!showPlaceholderTimeoutRef.current) {
|
||||
// Delay the placeholder so it will not render in a selection
|
||||
showPlaceholderTimeoutRef.current = setTimeout(() => {
|
||||
setShowPlaceholder(true)
|
||||
showPlaceholderTimeoutRef.current = null
|
||||
}, 300)
|
||||
}
|
||||
} else {
|
||||
clearTimeoutRef(showPlaceholderTimeoutRef)
|
||||
setShowPlaceholder(false)
|
||||
}
|
||||
return () => clearTimeoutRef(showPlaceholderTimeoutRef)
|
||||
}, [leafIsPlaceholder, setShowPlaceholder])
|
||||
|
||||
if (leafIsPlaceholder && showPlaceholder) {
|
||||
const placeholderProps: RenderPlaceholderProps = {
|
||||
children: leaf.placeholder,
|
||||
attributes: {
|
||||
@@ -105,7 +127,7 @@ const Leaf = (props: {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
contentEditable: false,
|
||||
ref: placeholderRef,
|
||||
ref: callbackPlaceholderRef,
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -6,9 +6,11 @@ declare module 'slate' {
|
||||
Editor: ReactEditor
|
||||
Text: BaseText & {
|
||||
placeholder?: string
|
||||
onPlaceholderResize?: (node: HTMLElement | null) => void
|
||||
}
|
||||
Range: BaseRange & {
|
||||
placeholder?: string
|
||||
onPlaceholderResize?: (node: HTMLElement | null) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,8 @@ test.describe('placeholder example', () => {
|
||||
const slateEditor = page.locator('[data-slate-editor=true]')
|
||||
const placeholderElement = page.locator('[data-slate-placeholder=true]')
|
||||
|
||||
await expect(placeholderElement).toBeVisible()
|
||||
|
||||
const editorBoundingBox = await slateEditor.boundingBox()
|
||||
const placeholderBoundingBox = await placeholderElement.boundingBox()
|
||||
|
||||
|
Reference in New Issue
Block a user