mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 13:11:17 +02:00
Fix missing callback dependencies in slate-react <Editable /> (#3192)
* Fix missing callback dependencies * Fix editable formatting
This commit is contained in:
committed by
Ian Storm Taylor
parent
0dd3d17cdb
commit
52e20da69a
@@ -445,77 +445,83 @@ export const Editable = (
|
|||||||
editor.exec({ type: 'insert_text', text })
|
editor.exec({ type: 'insert_text', text })
|
||||||
}
|
}
|
||||||
}, [])}
|
}, [])}
|
||||||
onBlur={useCallback((event: React.FocusEvent<HTMLDivElement>) => {
|
onBlur={useCallback(
|
||||||
if (
|
(event: React.FocusEvent<HTMLDivElement>) => {
|
||||||
readOnly ||
|
if (
|
||||||
state.isUpdatingSelection ||
|
readOnly ||
|
||||||
!hasEditableTarget(editor, event.target) ||
|
state.isUpdatingSelection ||
|
||||||
isEventHandled(event, attributes.onBlur)
|
!hasEditableTarget(editor, event.target) ||
|
||||||
) {
|
isEventHandled(event, attributes.onBlur)
|
||||||
return
|
) {
|
||||||
}
|
|
||||||
|
|
||||||
// COMPAT: If the current `activeElement` is still the previous
|
|
||||||
// one, this is due to the window being blurred when the tab
|
|
||||||
// itself becomes unfocused, so we want to abort early to allow to
|
|
||||||
// editor to stay focused when the tab becomes focused again.
|
|
||||||
if (state.latestElement === window.document.activeElement) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { relatedTarget } = event
|
|
||||||
const el = ReactEditor.toDOMNode(editor, editor)
|
|
||||||
|
|
||||||
// COMPAT: The event should be ignored if the focus is returning
|
|
||||||
// to the editor from an embedded editable element (eg. an <input>
|
|
||||||
// element inside a void node).
|
|
||||||
if (relatedTarget === el) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMPAT: The event should be ignored if the focus is moving from
|
|
||||||
// the editor to inside a void node's spacer element.
|
|
||||||
if (
|
|
||||||
isDOMElement(relatedTarget) &&
|
|
||||||
relatedTarget.hasAttribute('data-slate-spacer')
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMPAT: The event should be ignored if the focus is moving to a
|
|
||||||
// non- editable section of an element that isn't a void node (eg.
|
|
||||||
// a list item of the check list example).
|
|
||||||
if (
|
|
||||||
relatedTarget != null &&
|
|
||||||
isDOMNode(relatedTarget) &&
|
|
||||||
ReactEditor.hasDOMNode(editor, relatedTarget)
|
|
||||||
) {
|
|
||||||
const node = ReactEditor.toSlateNode(editor, relatedTarget)
|
|
||||||
|
|
||||||
if (Element.isElement(node) && !editor.isVoid(node)) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
IS_FOCUSED.delete(editor)
|
// COMPAT: If the current `activeElement` is still the previous
|
||||||
}, [])}
|
// one, this is due to the window being blurred when the tab
|
||||||
onClick={useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
// itself becomes unfocused, so we want to abort early to allow to
|
||||||
if (
|
// editor to stay focused when the tab becomes focused again.
|
||||||
!readOnly &&
|
if (state.latestElement === window.document.activeElement) {
|
||||||
hasTarget(editor, event.target) &&
|
return
|
||||||
!isEventHandled(event, attributes.onClick) &&
|
|
||||||
isDOMNode(event.target)
|
|
||||||
) {
|
|
||||||
const node = ReactEditor.toSlateNode(editor, event.target)
|
|
||||||
const path = ReactEditor.findPath(editor, node)
|
|
||||||
const start = Editor.start(editor, path)
|
|
||||||
|
|
||||||
if (Editor.match(editor, start, 'void')) {
|
|
||||||
const range = Editor.range(editor, start)
|
|
||||||
Editor.select(editor, range)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, [])}
|
const { relatedTarget } = event
|
||||||
|
const el = ReactEditor.toDOMNode(editor, editor)
|
||||||
|
|
||||||
|
// COMPAT: The event should be ignored if the focus is returning
|
||||||
|
// to the editor from an embedded editable element (eg. an <input>
|
||||||
|
// element inside a void node).
|
||||||
|
if (relatedTarget === el) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMPAT: The event should be ignored if the focus is moving from
|
||||||
|
// the editor to inside a void node's spacer element.
|
||||||
|
if (
|
||||||
|
isDOMElement(relatedTarget) &&
|
||||||
|
relatedTarget.hasAttribute('data-slate-spacer')
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMPAT: The event should be ignored if the focus is moving to a
|
||||||
|
// non- editable section of an element that isn't a void node (eg.
|
||||||
|
// a list item of the check list example).
|
||||||
|
if (
|
||||||
|
relatedTarget != null &&
|
||||||
|
isDOMNode(relatedTarget) &&
|
||||||
|
ReactEditor.hasDOMNode(editor, relatedTarget)
|
||||||
|
) {
|
||||||
|
const node = ReactEditor.toSlateNode(editor, relatedTarget)
|
||||||
|
|
||||||
|
if (Element.isElement(node) && !editor.isVoid(node)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IS_FOCUSED.delete(editor)
|
||||||
|
},
|
||||||
|
[attributes.onBlur]
|
||||||
|
)}
|
||||||
|
onClick={useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (
|
||||||
|
!readOnly &&
|
||||||
|
hasTarget(editor, event.target) &&
|
||||||
|
!isEventHandled(event, attributes.onClick) &&
|
||||||
|
isDOMNode(event.target)
|
||||||
|
) {
|
||||||
|
const node = ReactEditor.toSlateNode(editor, event.target)
|
||||||
|
const path = ReactEditor.findPath(editor, node)
|
||||||
|
const start = Editor.start(editor, path)
|
||||||
|
|
||||||
|
if (Editor.match(editor, start, 'void')) {
|
||||||
|
const range = Editor.range(editor, start)
|
||||||
|
Editor.select(editor, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[attributes.onClick]
|
||||||
|
)}
|
||||||
onCompositionEnd={useCallback(
|
onCompositionEnd={useCallback(
|
||||||
(event: React.CompositionEvent<HTMLDivElement>) => {
|
(event: React.CompositionEvent<HTMLDivElement>) => {
|
||||||
if (
|
if (
|
||||||
@@ -533,7 +539,7 @@ export const Editable = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[attributes.onCompositionEnd]
|
||||||
)}
|
)}
|
||||||
onCompositionStart={useCallback(
|
onCompositionStart={useCallback(
|
||||||
(event: React.CompositionEvent<HTMLDivElement>) => {
|
(event: React.CompositionEvent<HTMLDivElement>) => {
|
||||||
@@ -544,318 +550,342 @@ export const Editable = (
|
|||||||
state.isComposing = true
|
state.isComposing = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[attributes.onCompositionStart]
|
||||||
)}
|
)}
|
||||||
onCopy={useCallback((event: React.ClipboardEvent<HTMLDivElement>) => {
|
onCopy={useCallback(
|
||||||
if (
|
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
||||||
hasEditableTarget(editor, event.target) &&
|
|
||||||
!isEventHandled(event, attributes.onCopy)
|
|
||||||
) {
|
|
||||||
event.preventDefault()
|
|
||||||
setFragmentData(event.clipboardData, editor)
|
|
||||||
}
|
|
||||||
}, [])}
|
|
||||||
onCut={useCallback((event: React.ClipboardEvent<HTMLDivElement>) => {
|
|
||||||
if (
|
|
||||||
!readOnly &&
|
|
||||||
hasEditableTarget(editor, event.target) &&
|
|
||||||
!isEventHandled(event, attributes.onCut)
|
|
||||||
) {
|
|
||||||
event.preventDefault()
|
|
||||||
setFragmentData(event.clipboardData, editor)
|
|
||||||
const { selection } = editor
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])}
|
|
||||||
onDragOver={useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
if (
|
|
||||||
hasTarget(editor, event.target) &&
|
|
||||||
!isEventHandled(event, attributes.onDragOver)
|
|
||||||
) {
|
|
||||||
// Only when the target is void, call `preventDefault` to signal
|
|
||||||
// that drops are allowed. Editable content is droppable by
|
|
||||||
// default, and calling `preventDefault` hides the cursor.
|
|
||||||
const node = ReactEditor.toSlateNode(editor, event.target)
|
|
||||||
|
|
||||||
if (Element.isElement(node) && editor.isVoid(node)) {
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])}
|
|
||||||
onDragStart={useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
if (
|
|
||||||
hasTarget(editor, event.target) &&
|
|
||||||
!isEventHandled(event, attributes.onDragStart)
|
|
||||||
) {
|
|
||||||
const node = ReactEditor.toSlateNode(editor, event.target)
|
|
||||||
const path = ReactEditor.findPath(editor, node)
|
|
||||||
const voidMatch = Editor.match(editor, path, 'void')
|
|
||||||
|
|
||||||
// If starting a drag on a void node, make sure it is selected
|
|
||||||
// so that it shows up in the selection's fragment.
|
|
||||||
if (voidMatch) {
|
|
||||||
const range = Editor.range(editor, path)
|
|
||||||
Editor.select(editor, range)
|
|
||||||
}
|
|
||||||
|
|
||||||
setFragmentData(event.dataTransfer, editor)
|
|
||||||
}
|
|
||||||
}, [])}
|
|
||||||
onDrop={useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
if (
|
|
||||||
hasTarget(editor, event.target) &&
|
|
||||||
!readOnly &&
|
|
||||||
!isEventHandled(event, attributes.onDrop)
|
|
||||||
) {
|
|
||||||
// COMPAT: Firefox doesn't fire `beforeinput` events at all, and
|
|
||||||
// Chromium browsers don't properly fire them for files being
|
|
||||||
// dropped into a `contenteditable`. (2019/11/26)
|
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
|
|
||||||
if (
|
if (
|
||||||
IS_FIREFOX ||
|
hasEditableTarget(editor, event.target) &&
|
||||||
(!IS_SAFARI && event.dataTransfer.files.length > 0)
|
!isEventHandled(event, attributes.onCopy)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const range = ReactEditor.findEventRange(editor, event)
|
setFragmentData(event.clipboardData, editor)
|
||||||
const data = event.dataTransfer
|
|
||||||
Editor.select(editor, range)
|
|
||||||
editor.exec({ type: 'insert_data', data })
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [])}
|
[attributes.onCopy]
|
||||||
onFocus={useCallback((event: React.FocusEvent<HTMLDivElement>) => {
|
)}
|
||||||
if (
|
onCut={useCallback(
|
||||||
!readOnly &&
|
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
||||||
!state.isUpdatingSelection &&
|
if (
|
||||||
hasEditableTarget(editor, event.target) &&
|
!readOnly &&
|
||||||
!isEventHandled(event, attributes.onFocus)
|
hasEditableTarget(editor, event.target) &&
|
||||||
) {
|
!isEventHandled(event, attributes.onCut)
|
||||||
const el = ReactEditor.toDOMNode(editor, editor)
|
) {
|
||||||
state.latestElement = window.document.activeElement
|
|
||||||
|
|
||||||
// COMPAT: If the editor has nested editable elements, the focus
|
|
||||||
// can go to them. In Firefox, this must be prevented because it
|
|
||||||
// results in issues with keyboard navigation. (2017/03/30)
|
|
||||||
if (IS_FIREFOX && event.target !== el) {
|
|
||||||
el.focus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
IS_FOCUSED.set(editor, true)
|
|
||||||
}
|
|
||||||
}, [])}
|
|
||||||
onKeyDown={event => {
|
|
||||||
if (
|
|
||||||
!readOnly &&
|
|
||||||
hasEditableTarget(editor, event.target) &&
|
|
||||||
!isEventHandled(event, attributes.onKeyDown)
|
|
||||||
) {
|
|
||||||
const { nativeEvent } = event
|
|
||||||
const { selection } = editor
|
|
||||||
|
|
||||||
// COMPAT: Since we prevent the default behavior on
|
|
||||||
// `beforeinput` events, the browser doesn't think there's ever
|
|
||||||
// any history stack to undo or redo, so we have to manage these
|
|
||||||
// hotkeys ourselves. (2019/11/06)
|
|
||||||
if (Hotkeys.isRedo(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'redo' })
|
setFragmentData(event.clipboardData, editor)
|
||||||
return
|
const { selection } = editor
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[attributes.onCut]
|
||||||
|
)}
|
||||||
|
onDragOver={useCallback(
|
||||||
|
(event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
if (
|
||||||
|
hasTarget(editor, event.target) &&
|
||||||
|
!isEventHandled(event, attributes.onDragOver)
|
||||||
|
) {
|
||||||
|
// Only when the target is void, call `preventDefault` to signal
|
||||||
|
// that drops are allowed. Editable content is droppable by
|
||||||
|
// default, and calling `preventDefault` hides the cursor.
|
||||||
|
const node = ReactEditor.toSlateNode(editor, event.target)
|
||||||
|
|
||||||
if (Hotkeys.isUndo(nativeEvent)) {
|
if (Element.isElement(node) && editor.isVoid(node)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'undo' })
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[attributes.onDragOver]
|
||||||
|
)}
|
||||||
|
onDragStart={useCallback(
|
||||||
|
(event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
if (
|
||||||
|
hasTarget(editor, event.target) &&
|
||||||
|
!isEventHandled(event, attributes.onDragStart)
|
||||||
|
) {
|
||||||
|
const node = ReactEditor.toSlateNode(editor, event.target)
|
||||||
|
const path = ReactEditor.findPath(editor, node)
|
||||||
|
const voidMatch = Editor.match(editor, path, 'void')
|
||||||
|
|
||||||
// COMPAT: Certain browsers don't handle the selection updates
|
// If starting a drag on a void node, make sure it is selected
|
||||||
// properly. In Chrome, the selection isn't properly extended.
|
// so that it shows up in the selection's fragment.
|
||||||
// And in Firefox, the selection isn't properly collapsed.
|
if (voidMatch) {
|
||||||
// (2017/10/17)
|
const range = Editor.range(editor, path)
|
||||||
if (Hotkeys.isMoveLineBackward(nativeEvent)) {
|
Editor.select(editor, range)
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, { unit: 'line', reverse: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isMoveLineForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, { unit: 'line' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isExtendLineBackward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, {
|
|
||||||
unit: 'line',
|
|
||||||
edge: 'focus',
|
|
||||||
reverse: true,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isExtendLineForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, { unit: 'line', edge: 'focus' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMPAT: If a void node is selected, or a zero-width text node
|
|
||||||
// adjacent to an inline is selected, we need to handle these
|
|
||||||
// hotkeys manually because browsers won't be able to skip over
|
|
||||||
// the void node with the zero-width space not being an empty
|
|
||||||
// string.
|
|
||||||
if (Hotkeys.isMoveBackward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isCollapsed(selection)) {
|
|
||||||
Editor.move(editor, { reverse: true })
|
|
||||||
} else {
|
|
||||||
Editor.collapse(editor, { edge: 'start' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
setFragmentData(event.dataTransfer, editor)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if (Hotkeys.isMoveForward(nativeEvent)) {
|
[attributes.onDragStart]
|
||||||
event.preventDefault()
|
)}
|
||||||
|
onDrop={useCallback(
|
||||||
if (selection && Range.isCollapsed(selection)) {
|
(event: React.DragEvent<HTMLDivElement>) => {
|
||||||
Editor.move(editor)
|
if (
|
||||||
} else {
|
hasTarget(editor, event.target) &&
|
||||||
Editor.collapse(editor, { edge: 'end' })
|
!readOnly &&
|
||||||
}
|
!isEventHandled(event, attributes.onDrop)
|
||||||
|
) {
|
||||||
return
|
// COMPAT: Firefox doesn't fire `beforeinput` events at all, and
|
||||||
}
|
// Chromium browsers don't properly fire them for files being
|
||||||
|
// dropped into a `contenteditable`. (2019/11/26)
|
||||||
if (Hotkeys.isMoveWordBackward(nativeEvent)) {
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, { unit: 'word', reverse: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isMoveWordForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
Editor.move(editor, { unit: 'word' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMPAT: Firefox doesn't support the `beforeinput` event, so we
|
|
||||||
// fall back to guessing at the input intention for hotkeys.
|
|
||||||
// COMPAT: In iOS, some of these hotkeys are handled in the
|
|
||||||
if (IS_FIREFOX) {
|
|
||||||
// We don't have a core behavior for these, but they change the
|
|
||||||
// DOM if we don't prevent them, so we have to.
|
|
||||||
if (
|
if (
|
||||||
Hotkeys.isBold(nativeEvent) ||
|
IS_FIREFOX ||
|
||||||
Hotkeys.isItalic(nativeEvent) ||
|
(!IS_SAFARI && event.dataTransfer.files.length > 0)
|
||||||
Hotkeys.isTransposeCharacter(nativeEvent)
|
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
const range = ReactEditor.findEventRange(editor, event)
|
||||||
}
|
const data = event.dataTransfer
|
||||||
|
Editor.select(editor, range)
|
||||||
if (Hotkeys.isSplitBlock(nativeEvent)) {
|
editor.exec({ type: 'insert_data', data })
|
||||||
event.preventDefault()
|
|
||||||
editor.exec({ type: 'insert_break' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteBackward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_backward', unit: 'character' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_forward', unit: 'character' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_backward', unit: 'line' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteLineForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_forward', unit: 'line' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_backward', unit: 'word' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hotkeys.isDeleteWordForward(nativeEvent)) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (selection && Range.isExpanded(selection)) {
|
|
||||||
editor.exec({ type: 'delete_fragment' })
|
|
||||||
} else {
|
|
||||||
editor.exec({ type: 'delete_forward', unit: 'word' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}}
|
[attributes.onDrop]
|
||||||
onPaste={useCallback((event: React.ClipboardEvent<HTMLDivElement>) => {
|
)}
|
||||||
// COMPAT: Firefox doesn't support the `beforeinput` event, so we
|
onFocus={useCallback(
|
||||||
// fall back to React's `onPaste` here instead.
|
(event: React.FocusEvent<HTMLDivElement>) => {
|
||||||
if (
|
if (
|
||||||
IS_FIREFOX &&
|
!readOnly &&
|
||||||
!readOnly &&
|
!state.isUpdatingSelection &&
|
||||||
hasEditableTarget(editor, event.target) &&
|
hasEditableTarget(editor, event.target) &&
|
||||||
!isEventHandled(event, attributes.onPaste)
|
!isEventHandled(event, attributes.onFocus)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
const el = ReactEditor.toDOMNode(editor, editor)
|
||||||
editor.exec({
|
state.latestElement = window.document.activeElement
|
||||||
type: 'insert_data',
|
|
||||||
data: event.clipboardData,
|
// COMPAT: If the editor has nested editable elements, the focus
|
||||||
})
|
// can go to them. In Firefox, this must be prevented because it
|
||||||
}
|
// results in issues with keyboard navigation. (2017/03/30)
|
||||||
}, [])}
|
if (IS_FIREFOX && event.target !== el) {
|
||||||
|
el.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
IS_FOCUSED.set(editor, true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[attributes.onFocus]
|
||||||
|
)}
|
||||||
|
onKeyDown={useCallback(
|
||||||
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (
|
||||||
|
!readOnly &&
|
||||||
|
hasEditableTarget(editor, event.target) &&
|
||||||
|
!isEventHandled(event, attributes.onKeyDown)
|
||||||
|
) {
|
||||||
|
const { nativeEvent } = event
|
||||||
|
const { selection } = editor
|
||||||
|
|
||||||
|
// COMPAT: Since we prevent the default behavior on
|
||||||
|
// `beforeinput` events, the browser doesn't think there's ever
|
||||||
|
// any history stack to undo or redo, so we have to manage these
|
||||||
|
// hotkeys ourselves. (2019/11/06)
|
||||||
|
if (Hotkeys.isRedo(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
editor.exec({ type: 'redo' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isUndo(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
editor.exec({ type: 'undo' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMPAT: Certain browsers don't handle the selection updates
|
||||||
|
// properly. In Chrome, the selection isn't properly extended.
|
||||||
|
// And in Firefox, the selection isn't properly collapsed.
|
||||||
|
// (2017/10/17)
|
||||||
|
if (Hotkeys.isMoveLineBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, { unit: 'line', reverse: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isMoveLineForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, { unit: 'line' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isExtendLineBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, {
|
||||||
|
unit: 'line',
|
||||||
|
edge: 'focus',
|
||||||
|
reverse: true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isExtendLineForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, { unit: 'line', edge: 'focus' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMPAT: If a void node is selected, or a zero-width text node
|
||||||
|
// adjacent to an inline is selected, we need to handle these
|
||||||
|
// hotkeys manually because browsers won't be able to skip over
|
||||||
|
// the void node with the zero-width space not being an empty
|
||||||
|
// string.
|
||||||
|
if (Hotkeys.isMoveBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isCollapsed(selection)) {
|
||||||
|
Editor.move(editor, { reverse: true })
|
||||||
|
} else {
|
||||||
|
Editor.collapse(editor, { edge: 'start' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isMoveForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isCollapsed(selection)) {
|
||||||
|
Editor.move(editor)
|
||||||
|
} else {
|
||||||
|
Editor.collapse(editor, { edge: 'end' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isMoveWordBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, { unit: 'word', reverse: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isMoveWordForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
Editor.move(editor, { unit: 'word' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMPAT: Firefox doesn't support the `beforeinput` event, so we
|
||||||
|
// fall back to guessing at the input intention for hotkeys.
|
||||||
|
// COMPAT: In iOS, some of these hotkeys are handled in the
|
||||||
|
if (IS_FIREFOX) {
|
||||||
|
// We don't have a core behavior for these, but they change the
|
||||||
|
// DOM if we don't prevent them, so we have to.
|
||||||
|
if (
|
||||||
|
Hotkeys.isBold(nativeEvent) ||
|
||||||
|
Hotkeys.isItalic(nativeEvent) ||
|
||||||
|
Hotkeys.isTransposeCharacter(nativeEvent)
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isSplitBlock(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
editor.exec({ type: 'insert_break' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_backward', unit: 'character' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_forward', unit: 'character' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_backward', unit: 'line' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteLineForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_forward', unit: 'line' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_backward', unit: 'word' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hotkeys.isDeleteWordForward(nativeEvent)) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (selection && Range.isExpanded(selection)) {
|
||||||
|
editor.exec({ type: 'delete_fragment' })
|
||||||
|
} else {
|
||||||
|
editor.exec({ type: 'delete_forward', unit: 'word' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[attributes.onKeyDown]
|
||||||
|
)}
|
||||||
|
onPaste={useCallback(
|
||||||
|
(event: React.ClipboardEvent<HTMLDivElement>) => {
|
||||||
|
// COMPAT: Firefox doesn't support the `beforeinput` event, so we
|
||||||
|
// fall back to React's `onPaste` here instead.
|
||||||
|
if (
|
||||||
|
IS_FIREFOX &&
|
||||||
|
!readOnly &&
|
||||||
|
hasEditableTarget(editor, event.target) &&
|
||||||
|
!isEventHandled(event, attributes.onPaste)
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
editor.exec({
|
||||||
|
type: 'insert_data',
|
||||||
|
data: event.clipboardData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[attributes.onPaste]
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Children
|
<Children
|
||||||
decorate={decorate}
|
decorate={decorate}
|
||||||
|
Reference in New Issue
Block a user