mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-29 18:09:49 +02:00
fix focusing across multiple editors (#2396)
This commit is contained in:
@@ -146,103 +146,120 @@ class Content extends React.Component {
|
|||||||
const { isBackward } = selection
|
const { isBackward } = selection
|
||||||
const window = getWindow(this.element)
|
const window = getWindow(this.element)
|
||||||
const native = window.getSelection()
|
const native = window.getSelection()
|
||||||
|
const { activeElement } = window.document
|
||||||
|
|
||||||
// .getSelection() can return null in some cases
|
// COMPAT: In Firefox, there's a but where `getSelection` can return `null`.
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=827585
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=827585 (2018/11/07)
|
||||||
if (!native) return
|
if (!native) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { rangeCount, anchorNode } = native
|
const { rangeCount, anchorNode } = native
|
||||||
|
let updated = false
|
||||||
|
|
||||||
// If both selections are blurred, do nothing.
|
// If the Slate selection is blurred, but the DOM's active element is still
|
||||||
if (!rangeCount && selection.isBlurred) return
|
// the editor, we need to blur it.
|
||||||
|
if (selection.isBlurred && activeElement === this.element) {
|
||||||
// If the selection has been blurred, but is still inside the editor in the
|
|
||||||
// DOM, blur it manually.
|
|
||||||
if (selection.isBlurred) {
|
|
||||||
if (!this.isInEditor(anchorNode)) return
|
|
||||||
removeAllRanges(native)
|
|
||||||
this.element.blur()
|
this.element.blur()
|
||||||
debug('updateSelection', { selection, native })
|
updated = true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the selection isn't set, do nothing.
|
// If the Slate selection is unset, but the DOM selection has a range
|
||||||
if (selection.isUnset) return
|
// selected in the editor, we need to remove the range.
|
||||||
|
if (selection.isUnset && rangeCount && this.isInEditor(anchorNode)) {
|
||||||
|
removeAllRanges(native)
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Slate selection is focused, but the DOM's active element is not
|
||||||
|
// the editor, we need to focus it.
|
||||||
|
if (selection.isFocused && activeElement !== this.element) {
|
||||||
|
this.element.focus()
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, figure out which DOM nodes should be selected...
|
// Otherwise, figure out which DOM nodes should be selected...
|
||||||
const current = !!rangeCount && native.getRangeAt(0)
|
if (selection.isFocused && selection.isSet) {
|
||||||
const range = findDOMRange(selection, window)
|
const current = !!rangeCount && native.getRangeAt(0)
|
||||||
|
const range = findDOMRange(selection, window)
|
||||||
|
|
||||||
if (!range) {
|
if (!range) {
|
||||||
warning(
|
warning(
|
||||||
false,
|
false,
|
||||||
'Unable to find a native DOM range from the current selection.'
|
'Unable to find a native DOM range from the current selection.'
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { startContainer, startOffset, endContainer, endOffset } = range
|
|
||||||
|
|
||||||
// If the new range matches the current selection, there is nothing to fix.
|
|
||||||
// COMPAT: The native `Range` object always has it's "start" first and "end"
|
|
||||||
// last in the DOM. It has no concept of "backwards/forwards", so we have
|
|
||||||
// to check both orientations here. (2017/10/31)
|
|
||||||
if (current) {
|
|
||||||
if (
|
|
||||||
(startContainer == current.startContainer &&
|
|
||||||
startOffset == current.startOffset &&
|
|
||||||
endContainer == current.endContainer &&
|
|
||||||
endOffset == current.endOffset) ||
|
|
||||||
(startContainer == current.endContainer &&
|
|
||||||
startOffset == current.endOffset &&
|
|
||||||
endContainer == current.startContainer &&
|
|
||||||
endOffset == current.startOffset)
|
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, set the `isUpdatingSelection` flag and update the selection.
|
const { startContainer, startOffset, endContainer, endOffset } = range
|
||||||
this.tmp.isUpdatingSelection = true
|
|
||||||
removeAllRanges(native)
|
|
||||||
|
|
||||||
// COMPAT: IE 11 does not support Selection.setBaseAndExtent
|
// If the new range matches the current selection, there is nothing to fix.
|
||||||
if (native.setBaseAndExtent) {
|
// COMPAT: The native `Range` object always has it's "start" first and "end"
|
||||||
// COMPAT: Since the DOM range has no concept of backwards/forwards
|
// last in the DOM. It has no concept of "backwards/forwards", so we have
|
||||||
// we need to check and do the right thing here.
|
// to check both orientations here. (2017/10/31)
|
||||||
if (isBackward) {
|
if (current) {
|
||||||
native.setBaseAndExtent(
|
if (
|
||||||
range.endContainer,
|
(startContainer == current.startContainer &&
|
||||||
range.endOffset,
|
startOffset == current.startOffset &&
|
||||||
range.startContainer,
|
endContainer == current.endContainer &&
|
||||||
range.startOffset
|
endOffset == current.endOffset) ||
|
||||||
)
|
(startContainer == current.endContainer &&
|
||||||
} else {
|
startOffset == current.endOffset &&
|
||||||
native.setBaseAndExtent(
|
endContainer == current.startContainer &&
|
||||||
range.startContainer,
|
endOffset == current.startOffset)
|
||||||
range.startOffset,
|
) {
|
||||||
range.endContainer,
|
return
|
||||||
range.endOffset
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// COMPAT: IE 11 does not support Selection.extend, fallback to addRange
|
// Otherwise, set the `isUpdatingSelection` flag and update the selection.
|
||||||
native.addRange(range)
|
updated = true
|
||||||
|
this.tmp.isUpdatingSelection = true
|
||||||
|
removeAllRanges(native)
|
||||||
|
|
||||||
|
// COMPAT: IE 11 does not support `setBaseAndExtent`. (2018/11/07)
|
||||||
|
if (native.setBaseAndExtent) {
|
||||||
|
// COMPAT: Since the DOM range has no concept of backwards/forwards
|
||||||
|
// we need to check and do the right thing here.
|
||||||
|
if (isBackward) {
|
||||||
|
native.setBaseAndExtent(
|
||||||
|
range.endContainer,
|
||||||
|
range.endOffset,
|
||||||
|
range.startContainer,
|
||||||
|
range.startOffset
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
native.setBaseAndExtent(
|
||||||
|
range.startContainer,
|
||||||
|
range.startOffset,
|
||||||
|
range.endContainer,
|
||||||
|
range.endOffset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
native.addRange(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to the selection, in case it's out of view.
|
||||||
|
scrollToSelection(native)
|
||||||
|
|
||||||
|
// Then unset the `isUpdatingSelection` flag after a delay, to ensure that
|
||||||
|
// it is still set when selection-related events from updating it fire.
|
||||||
|
setTimeout(() => {
|
||||||
|
// COMPAT: In Firefox, it's not enough to create a range, you also need
|
||||||
|
// to focus the contenteditable element too. (2016/11/16)
|
||||||
|
if (IS_FIREFOX && this.element) {
|
||||||
|
this.element.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tmp.isUpdatingSelection = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to the selection, in case it's out of view.
|
if (updated) {
|
||||||
scrollToSelection(native)
|
debug('updateSelection', { selection, native, activeElement })
|
||||||
|
}
|
||||||
// Then unset the `isUpdatingSelection` flag after a delay.
|
|
||||||
setTimeout(() => {
|
|
||||||
// COMPAT: In Firefox, it's not enough to create a range, you also need to
|
|
||||||
// focus the contenteditable element too. (2016/11/16)
|
|
||||||
if (IS_FIREFOX && this.element) this.element.focus()
|
|
||||||
this.tmp.isUpdatingSelection = false
|
|
||||||
})
|
|
||||||
|
|
||||||
debug('updateSelection', { selection, native })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -345,9 +362,11 @@ class Content extends React.Component {
|
|||||||
handler == 'onDragStart' ||
|
handler == 'onDragStart' ||
|
||||||
handler == 'onDrop'
|
handler == 'onDrop'
|
||||||
) {
|
) {
|
||||||
const { target } = event
|
const closest = event.target.closest('[data-slate-editor]')
|
||||||
const targetEditorNode = target.closest('[data-slate-editor]')
|
|
||||||
if (targetEditorNode !== this.element) return
|
if (closest !== this.element) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some events require being in editable in the editor, so if the event
|
// Some events require being in editable in the editor, so if the event
|
||||||
@@ -366,7 +385,9 @@ class Content extends React.Component {
|
|||||||
handler == 'onPaste' ||
|
handler == 'onPaste' ||
|
||||||
handler == 'onSelect'
|
handler == 'onSelect'
|
||||||
) {
|
) {
|
||||||
if (!this.isInEditor(event.target)) return
|
if (!this.isInEditor(event.target)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onEvent(handler, event)
|
this.props.onEvent(handler, event)
|
||||||
|
@@ -24,6 +24,8 @@ const EVENT_HANDLERS = [
|
|||||||
'onFocus',
|
'onFocus',
|
||||||
'onKeyDown',
|
'onKeyDown',
|
||||||
'onKeyUp',
|
'onKeyUp',
|
||||||
|
'onMouseDown',
|
||||||
|
'onMouseUp',
|
||||||
'onPaste',
|
'onPaste',
|
||||||
'onSelect',
|
'onSelect',
|
||||||
]
|
]
|
||||||
|
@@ -31,6 +31,7 @@ const debug = Debug('slate:after')
|
|||||||
|
|
||||||
function AfterPlugin(options = {}) {
|
function AfterPlugin(options = {}) {
|
||||||
let isDraggingInternally = null
|
let isDraggingInternally = null
|
||||||
|
let isMouseDown = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On before input.
|
* On before input.
|
||||||
@@ -375,6 +376,30 @@ function AfterPlugin(options = {}) {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On focus.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onFocus(event, editor, next) {
|
||||||
|
debug('onFocus', { event })
|
||||||
|
|
||||||
|
// COMPAT: If the focus event is a mouse-based one, it will be shortly
|
||||||
|
// followed by a `selectionchange`, so we need to deselect here to prevent
|
||||||
|
// the old selection from being set by the `updateSelection` of `<Content>`,
|
||||||
|
// preventing the `selectionchange` from firing. (2018/11/07)
|
||||||
|
if (isMouseDown) {
|
||||||
|
editor.deselect().focus()
|
||||||
|
} else {
|
||||||
|
editor.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On input.
|
* On input.
|
||||||
*
|
*
|
||||||
@@ -580,6 +605,34 @@ function AfterPlugin(options = {}) {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mouse down.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onMouseDown(event, editor, next) {
|
||||||
|
debug('onMouseDown', { event })
|
||||||
|
isMouseDown = true
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On mouse up.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onMouseUp(event, editor, next) {
|
||||||
|
debug('onMouseUp', { event })
|
||||||
|
isMouseDown = false
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On paste.
|
* On paste.
|
||||||
*
|
*
|
||||||
@@ -638,7 +691,10 @@ function AfterPlugin(options = {}) {
|
|||||||
|
|
||||||
// Otherwise, determine the Slate selection from the native one.
|
// Otherwise, determine the Slate selection from the native one.
|
||||||
let range = findRange(native, editor)
|
let range = findRange(native, editor)
|
||||||
if (!range) return
|
|
||||||
|
if (!range) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { anchor, focus } = range
|
const { anchor, focus } = range
|
||||||
const anchorText = document.getNode(anchor.key)
|
const anchorText = document.getNode(anchor.key)
|
||||||
@@ -715,8 +771,11 @@ function AfterPlugin(options = {}) {
|
|||||||
onDragEnd,
|
onDragEnd,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDrop,
|
onDrop,
|
||||||
|
onFocus,
|
||||||
onInput,
|
onInput,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
|
onMouseDown,
|
||||||
|
onMouseUp,
|
||||||
onPaste,
|
onPaste,
|
||||||
onSelect,
|
onSelect,
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user