Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/delete all closes keyboard #5368

Merged
merged 6 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rare-baboons-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'slate-react': patch
---

Delay rendering of placeholder to avoid IME hiding
27 changes: 19 additions & 8 deletions packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { useSlate } from '../hooks/use-slate'
import { TRIPLE_CLICK } from '../utils/constants'
import {
DOMElement,
DOMNode,
DOMRange,
DOMText,
getDefaultView,
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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,
})
Expand Down Expand Up @@ -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}>
Expand Down Expand Up @@ -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
}
}
Expand Down
130 changes: 76 additions & 54 deletions packages/slate-react/src/components/leaf.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -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)

useEffect(() => {
return () => {
if (placeholderResizeObserver.current) {
placeholderResizeObserver.current.disconnect()
}
}
}, [])

useEffect(() => {
const placeholderEl = placeholderRef?.current

if (placeholderEl) {
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
} else {
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
}

if (placeholderResizeObserver.current) {
// Update existing observer.
placeholderResizeObserver.current.disconnect()
if (placeholderEl)
const placeholderRef = useRef<HTMLElement | null>(null)
const [showPlaceholder, setShowPlaceholder] = useState(false)
const showPlaceholderTimeoutRef = useRef<TimerId>(null)

const callbackPlaceholderRef = useCallback(
(placeholderEl: HTMLElement | null) => {
disconnectPlaceholderResizeObserver(
placeholderResizeObserver,
placeholderEl == null
)

if (placeholderEl == null) {
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
leaf.onPlaceholderResize?.(null)
} else {
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)

if (!placeholderResizeObserver.current) {
// Create a new observer and observe the placeholder element.
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill
placeholderResizeObserver.current = new ResizeObserver(() => {
leaf.onPlaceholderResize?.(placeholderEl)
})
}
placeholderResizeObserver.current.observe(placeholderEl)
} else if (placeholderEl) {
// 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?.()
})
placeholderResizeObserver.current.observe(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.current = placeholderEl
}
},
[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: {
Expand All @@ -105,7 +127,7 @@ const Leaf = (props: {
textDecoration: 'none',
},
contentEditable: false,
ref: placeholderRef,
ref: callbackPlaceholderRef,
},
}

Expand Down
2 changes: 2 additions & 0 deletions packages/slate-react/src/custom-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
2 changes: 2 additions & 0 deletions playwright/integration/examples/placeholder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down