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

Fix Copy/pasting void elements is not working (#5121)

* Create new function hasSelectableTarget and use it instead of hasEditableTarget. Fixes Copy/pasting void elements is not working https://github.com/ianstormtaylor/slate/issues/4808

* Add changeset

* Revert a change that made editable void not editable and add cypress test for editing editable void

* Extract methoods into easily overridable with help from @alex-vladut
This commit is contained in:
Laufey Rut Guðmundsdóttir
2022-11-17 16:17:10 +00:00
committed by GitHub
parent 6efe3d9a22
commit 06942c6d7e
4 changed files with 103 additions and 61 deletions

View File

@@ -206,12 +206,12 @@ export const Editable = (props: EditableProps) => {
const { anchorNode, focusNode } = domSelection
const anchorNodeSelectable =
hasEditableTarget(editor, anchorNode) ||
isTargetInsideNonReadonlyVoid(editor, anchorNode)
ReactEditor.hasEditableTarget(editor, anchorNode) ||
ReactEditor.isTargetInsideNonReadonlyVoid(editor, anchorNode)
const focusNodeSelectable =
hasEditableTarget(editor, focusNode) ||
isTargetInsideNonReadonlyVoid(editor, focusNode)
ReactEditor.hasEditableTarget(editor, focusNode) ||
ReactEditor.isTargetInsideNonReadonlyVoid(editor, focusNode)
if (anchorNodeSelectable && focusNodeSelectable) {
const range = ReactEditor.toSlateRange(editor, domSelection, {
@@ -434,7 +434,7 @@ export const Editable = (props: EditableProps) => {
if (
!readOnly &&
hasEditableTarget(editor, event.target) &&
ReactEditor.hasEditableTarget(editor, event.target) &&
!isDOMEventHandled(event, propsOnDOMBeforeInput)
) {
// COMPAT: BeforeInput events aren't cancelable on android, so we have to handle them differently using the android input manager.
@@ -861,7 +861,7 @@ export const Editable = (props: EditableProps) => {
!HAS_BEFORE_INPUT_SUPPORT &&
!readOnly &&
!isEventHandled(event, attributes.onBeforeInput) &&
hasEditableTarget(editor, event.target)
ReactEditor.hasSelectableTarget(editor, event.target)
) {
event.preventDefault()
if (!ReactEditor.isComposing(editor)) {
@@ -892,7 +892,7 @@ export const Editable = (props: EditableProps) => {
if (
readOnly ||
state.isUpdatingSelection ||
!hasEditableTarget(editor, event.target) ||
!ReactEditor.hasSelectableTarget(editor, event.target) ||
isEventHandled(event, attributes.onBlur)
) {
return
@@ -956,7 +956,7 @@ export const Editable = (props: EditableProps) => {
onClick={useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (
hasTarget(editor, event.target) &&
ReactEditor.hasTarget(editor, event.target) &&
!isEventHandled(event, attributes.onClick) &&
isDOMNode(event.target)
) {
@@ -1013,7 +1013,7 @@ export const Editable = (props: EditableProps) => {
)}
onCompositionEnd={useCallback(
(event: React.CompositionEvent<HTMLDivElement>) => {
if (hasEditableTarget(editor, event.target)) {
if (ReactEditor.hasSelectableTarget(editor, event.target)) {
if (ReactEditor.isComposing(editor)) {
setIsComposing(false)
IS_COMPOSING.set(editor, false)
@@ -1067,7 +1067,7 @@ export const Editable = (props: EditableProps) => {
onCompositionUpdate={useCallback(
(event: React.CompositionEvent<HTMLDivElement>) => {
if (
hasEditableTarget(editor, event.target) &&
ReactEditor.hasSelectableTarget(editor, event.target) &&
!isEventHandled(event, attributes.onCompositionUpdate)
) {
if (!ReactEditor.isComposing(editor)) {
@@ -1080,7 +1080,7 @@ export const Editable = (props: EditableProps) => {
)}
onCompositionStart={useCallback(
(event: React.CompositionEvent<HTMLDivElement>) => {
if (hasEditableTarget(editor, event.target)) {
if (ReactEditor.hasSelectableTarget(editor, event.target)) {
androidInputManager?.handleCompositionStart(event)
if (
@@ -1120,7 +1120,7 @@ export const Editable = (props: EditableProps) => {
onCopy={useCallback(
(event: React.ClipboardEvent<HTMLDivElement>) => {
if (
hasEditableTarget(editor, event.target) &&
ReactEditor.hasSelectableTarget(editor, event.target) &&
!isEventHandled(event, attributes.onCopy)
) {
event.preventDefault()
@@ -1137,7 +1137,7 @@ export const Editable = (props: EditableProps) => {
(event: React.ClipboardEvent<HTMLDivElement>) => {
if (
!readOnly &&
hasEditableTarget(editor, event.target) &&
ReactEditor.hasSelectableTarget(editor, event.target) &&
!isEventHandled(event, attributes.onCut)
) {
event.preventDefault()
@@ -1165,7 +1165,7 @@ export const Editable = (props: EditableProps) => {
onDragOver={useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
if (
hasTarget(editor, event.target) &&
ReactEditor.hasTarget(editor, event.target) &&
!isEventHandled(event, attributes.onDragOver)
) {
// Only when the target is void, call `preventDefault` to signal
@@ -1184,7 +1184,7 @@ export const Editable = (props: EditableProps) => {
(event: React.DragEvent<HTMLDivElement>) => {
if (
!readOnly &&
hasTarget(editor, event.target) &&
ReactEditor.hasTarget(editor, event.target) &&
!isEventHandled(event, attributes.onDragStart)
) {
const node = ReactEditor.toSlateNode(editor, event.target)
@@ -1215,7 +1215,7 @@ export const Editable = (props: EditableProps) => {
(event: React.DragEvent<HTMLDivElement>) => {
if (
!readOnly &&
hasTarget(editor, event.target) &&
ReactEditor.hasTarget(editor, event.target) &&
!isEventHandled(event, attributes.onDrop)
) {
event.preventDefault()
@@ -1260,7 +1260,7 @@ export const Editable = (props: EditableProps) => {
!readOnly &&
state.isDraggingInternally &&
attributes.onDragEnd &&
hasTarget(editor, event.target)
ReactEditor.hasTarget(editor, event.target)
) {
attributes.onDragEnd(event)
}
@@ -1277,7 +1277,7 @@ export const Editable = (props: EditableProps) => {
if (
!readOnly &&
!state.isUpdatingSelection &&
hasEditableTarget(editor, event.target) &&
ReactEditor.hasSelectableTarget(editor, event.target) &&
!isEventHandled(event, attributes.onFocus)
) {
const el = ReactEditor.toDOMNode(editor, editor)
@@ -1299,7 +1299,10 @@ export const Editable = (props: EditableProps) => {
)}
onKeyDown={useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!readOnly && hasEditableTarget(editor, event.target)) {
if (
!readOnly &&
ReactEditor.hasEditableTarget(editor, event.target)
) {
androidInputManager?.handleKeyDown(event)
const { nativeEvent } = event
@@ -1573,7 +1576,7 @@ export const Editable = (props: EditableProps) => {
(event: React.ClipboardEvent<HTMLDivElement>) => {
if (
!readOnly &&
hasEditableTarget(editor, event.target) &&
ReactEditor.hasSelectableTarget(editor, event.target) &&
!isEventHandled(event, attributes.onPaste)
) {
// COMPAT: Certain browsers don't support the `beforeinput` event, so we
@@ -1668,46 +1671,6 @@ const defaultScrollSelectionIntoView = (
}
}
/**
* Check if the target is in the editor.
*/
export const hasTarget = (
editor: ReactEditor,
target: EventTarget | null
): target is DOMNode => {
return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target)
}
/**
* Check if the target is editable and in the editor.
*/
export const hasEditableTarget = (
editor: ReactEditor,
target: EventTarget | null
): target is DOMNode => {
return (
isDOMNode(target) &&
ReactEditor.hasDOMNode(editor, target, { editable: true })
)
}
/**
* Check if the target is inside void and in an non-readonly editor.
*/
export const isTargetInsideNonReadonlyVoid = (
editor: ReactEditor,
target: EventTarget | null
): boolean => {
if (IS_READ_ONLY.get(editor)) return false
const slateNode =
hasTarget(editor, target) && ReactEditor.toSlateNode(editor, target)
return Editor.isVoid(editor, slateNode)
}
/**
* Check if an event is overrided by a handler.
*/

View File

@@ -33,6 +33,7 @@ import {
DOMStaticRange,
isDOMElement,
isDOMSelection,
isDOMNode,
normalizeDOMPoint,
hasShadowRoot,
DOMText,
@@ -52,6 +53,22 @@ export interface ReactEditor extends BaseEditor {
originEvent?: 'drag' | 'copy' | 'cut'
) => void
hasRange: (editor: ReactEditor, range: Range) => boolean
hasTarget: (
editor: ReactEditor,
target: EventTarget | null
) => target is DOMNode
hasEditableTarget: (
editor: ReactEditor,
target: EventTarget | null
) => target is DOMNode
hasSelectableTarget: (
editor: ReactEditor,
target: EventTarget | null
) => boolean
isTargetInsideNonReadonlyVoid: (
editor: ReactEditor,
target: EventTarget | null
) => boolean
}
// eslint-disable-next-line no-redeclare
@@ -779,6 +796,57 @@ export const ReactEditor = {
)
},
/**
* Check if the target is in the editor.
*/
hasTarget(
editor: ReactEditor,
target: EventTarget | null
): target is DOMNode {
return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target)
},
/**
* Check if the target is editable and in the editor.
*/
hasEditableTarget(
editor: ReactEditor,
target: EventTarget | null
): target is DOMNode {
return (
isDOMNode(target) &&
ReactEditor.hasDOMNode(editor, target, { editable: true })
)
},
/**
* Check if the target can be selectable
*/
hasSelectableTarget(
editor: ReactEditor,
target: EventTarget | null
): boolean {
return (
ReactEditor.hasEditableTarget(editor, target) ||
ReactEditor.isTargetInsideNonReadonlyVoid(editor, target)
)
},
/**
* Check if the target is inside void and in an non-readonly editor.
*/
isTargetInsideNonReadonlyVoid(
editor: ReactEditor,
target: EventTarget | null
): boolean {
if (IS_READ_ONLY.get(editor)) return false
const slateNode =
ReactEditor.hasTarget(editor, target) &&
ReactEditor.toSlateNode(editor, target)
return Editor.isVoid(editor, slateNode)
},
/**
* Experimental and android specific: Flush all pending diffs and cancel composition at the next possible time.
*/