mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 14:41:23 +02:00
fix selection handling for changing tabs, and inside embedded inputs, closes #749
This commit is contained in:
@@ -3,7 +3,6 @@ import Debug from 'debug'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Types from 'prop-types'
|
import Types from 'prop-types'
|
||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
import isWindow from 'is-window'
|
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
|
|
||||||
import TYPES from '../constants/types'
|
import TYPES from '../constants/types'
|
||||||
@@ -117,18 +116,11 @@ class Content extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* When the editor first mounts in the DOM we need to:
|
* When the editor first mounts in the DOM we need to:
|
||||||
*
|
*
|
||||||
* - Setup the original state for `isWindowFocused`.
|
|
||||||
* - Attach window focus and blur listeners for tab switching.
|
|
||||||
* - Update the selection, in case it starts focused.
|
* - Update the selection, in case it starts focused.
|
||||||
* - Focus the editor if `autoFocus` is set.
|
* - Focus the editor if `autoFocus` is set.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
const window = getWindow(this.element)
|
|
||||||
window.addEventListener('focus', this.onWindowFocus, true)
|
|
||||||
window.addEventListener('blur', this.onWindowBlur, true)
|
|
||||||
this.tmp.isWindowFocused = !window.document.hidden
|
|
||||||
|
|
||||||
this.updateSelection()
|
this.updateSelection()
|
||||||
|
|
||||||
if (this.props.autoFocus) {
|
if (this.props.autoFocus) {
|
||||||
@@ -144,16 +136,6 @@ class Content extends React.Component {
|
|||||||
this.updateSelection()
|
this.updateSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Before unmounting, detach our window focus and blur listeners.
|
|
||||||
*/
|
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
|
||||||
const window = getWindow(this.element)
|
|
||||||
window.removeEventListener('focus', this.onWindowFocus, true)
|
|
||||||
window.removeEventListener('blur', this.onWindowBlur, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the native DOM selection to reflect the internal model.
|
* Update the native DOM selection to reflect the internal model.
|
||||||
*/
|
*/
|
||||||
@@ -167,10 +149,10 @@ class Content extends React.Component {
|
|||||||
// If both selections are blurred, do nothing.
|
// If both selections are blurred, do nothing.
|
||||||
if (!native.rangeCount && selection.isBlurred) return
|
if (!native.rangeCount && selection.isBlurred) return
|
||||||
|
|
||||||
// If the selection has been blurred, but hasn't been updated in the DOM,
|
// If the selection has been blurred, but is still inside the editor in the
|
||||||
// blur the DOM selection.
|
// DOM, blur it manually.
|
||||||
if (selection.isBlurred) {
|
if (selection.isBlurred) {
|
||||||
if (!this.element.contains(native.anchorNode)) return
|
if (!this.isInEditor(native.anchorNode)) return
|
||||||
native.removeAllRanges()
|
native.removeAllRanges()
|
||||||
this.element.blur()
|
this.element.blur()
|
||||||
debug('updateSelection', { selection, native })
|
debug('updateSelection', { selection, native })
|
||||||
@@ -255,17 +237,16 @@ class Content extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an `event` is being fired from within the contenteditable element.
|
* Check if an event `target` is fired from within the contenteditable
|
||||||
* This will return false for edits happening in non-contenteditable children,
|
* element. This should be false for edits happening in non-contenteditable
|
||||||
* such as void nodes and other nested Slate editors.
|
* children, such as void nodes and other nested Slate editors.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Element} target
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
isInEditor = (event) => {
|
isInEditor = (target) => {
|
||||||
const { element } = this
|
const { element } = this
|
||||||
const { target } = event
|
|
||||||
return (
|
return (
|
||||||
(target.isContentEditable) &&
|
(target.isContentEditable) &&
|
||||||
(target == element || findClosestNode(target, '[data-slate-editor]') == element)
|
(target == element || findClosestNode(target, '[data-slate-editor]') == element)
|
||||||
@@ -280,7 +261,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onBeforeInput = (event) => {
|
onBeforeInput = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
const data = {}
|
const data = {}
|
||||||
|
|
||||||
@@ -297,13 +278,13 @@ class Content extends React.Component {
|
|||||||
onBlur = (event) => {
|
onBlur = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (this.tmp.isCopying) return
|
if (this.tmp.isCopying) return
|
||||||
if (!this.tmp.isWindowFocused) return
|
if (!this.isInEditor(event.target)) return
|
||||||
if (!this.isInEditor(event)) return
|
|
||||||
|
|
||||||
// If the element that is now focused is actually inside the editor, we
|
// If the active element is still the editor, the blur event is due to the
|
||||||
// need to ignore it. This can happen in situations where there is a nested
|
// window itself being blurred (eg. when changing tabs) so we should ignore
|
||||||
// `contenteditable="true"` node that isn't another Slate editor.
|
// the event, since we want to maintain focus when returning.
|
||||||
if (this.element.contains(event.relatedTarget)) return
|
const window = getWindow(this.element)
|
||||||
|
if (window.document.activeElement == this.element) return
|
||||||
|
|
||||||
const data = {}
|
const data = {}
|
||||||
|
|
||||||
@@ -320,8 +301,7 @@ class Content extends React.Component {
|
|||||||
onFocus = (event) => {
|
onFocus = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (this.tmp.isCopying) return
|
if (this.tmp.isCopying) return
|
||||||
if (!this.tmp.isWindowFocused) return
|
if (!this.isInEditor(event.target)) return
|
||||||
if (!this.isInEditor(event)) return
|
|
||||||
|
|
||||||
// COMPAT: If the editor has nested editable elements, the focus can go to
|
// COMPAT: If the editor has nested editable elements, the focus can go to
|
||||||
// those elements. In Firefox, this must be prevented because it results in
|
// those elements. In Firefox, this must be prevented because it results in
|
||||||
@@ -355,7 +335,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onCompositionStart = (event) => {
|
onCompositionStart = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
this.tmp.isComposing = true
|
this.tmp.isComposing = true
|
||||||
this.tmp.compositions++
|
this.tmp.compositions++
|
||||||
@@ -372,7 +352,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onCompositionEnd = (event) => {
|
onCompositionEnd = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
this.tmp.forces++
|
this.tmp.forces++
|
||||||
const count = this.tmp.compositions
|
const count = this.tmp.compositions
|
||||||
@@ -395,7 +375,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onCopy = (event) => {
|
onCopy = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
|
|
||||||
this.tmp.isCopying = true
|
this.tmp.isCopying = true
|
||||||
@@ -420,7 +400,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onCut = (event) => {
|
onCut = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
|
|
||||||
this.tmp.isCopying = true
|
this.tmp.isCopying = true
|
||||||
@@ -444,7 +424,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onDragEnd = (event) => {
|
onDragEnd = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
this.tmp.isDragging = false
|
this.tmp.isDragging = false
|
||||||
this.tmp.isInternalDrag = null
|
this.tmp.isInternalDrag = null
|
||||||
@@ -459,7 +439,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onDragOver = (event) => {
|
onDragOver = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
const { dataTransfer } = event.nativeEvent
|
const { dataTransfer } = event.nativeEvent
|
||||||
const data = getTransferData(dataTransfer)
|
const data = getTransferData(dataTransfer)
|
||||||
@@ -483,7 +463,7 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onDragStart = (event) => {
|
onDragStart = (event) => {
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
this.tmp.isDragging = true
|
this.tmp.isDragging = true
|
||||||
this.tmp.isInternalDrag = true
|
this.tmp.isInternalDrag = true
|
||||||
@@ -509,7 +489,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onDrop = (event) => {
|
onDrop = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
@@ -567,7 +547,7 @@ class Content extends React.Component {
|
|||||||
onInput = (event) => {
|
onInput = (event) => {
|
||||||
if (this.tmp.isComposing) return
|
if (this.tmp.isComposing) return
|
||||||
if (this.props.state.isBlurred) return
|
if (this.props.state.isBlurred) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
debug('onInput', { event })
|
debug('onInput', { event })
|
||||||
|
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
@@ -638,7 +618,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onKeyDown = (event) => {
|
onKeyDown = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||||
const key = keycode(which)
|
const key = keycode(which)
|
||||||
@@ -716,7 +696,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onPaste = (event) => {
|
onPaste = (event) => {
|
||||||
if (this.props.readOnly) return
|
if (this.props.readOnly) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const data = getTransferData(event.clipboardData)
|
const data = getTransferData(event.clipboardData)
|
||||||
@@ -740,7 +720,7 @@ class Content extends React.Component {
|
|||||||
if (this.tmp.isCopying) return
|
if (this.tmp.isCopying) return
|
||||||
if (this.tmp.isComposing) return
|
if (this.tmp.isComposing) return
|
||||||
if (this.tmp.isSelecting) return
|
if (this.tmp.isSelecting) return
|
||||||
if (!this.isInEditor(event)) return
|
if (!this.isInEditor(event.target)) return
|
||||||
|
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
const { state, editor } = this.props
|
const { state, editor } = this.props
|
||||||
@@ -820,30 +800,6 @@ class Content extends React.Component {
|
|||||||
this.props.onSelect(event, data)
|
this.props.onSelect(event, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* On window blur, unset the `isWindowFocused` flag.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
*/
|
|
||||||
|
|
||||||
onWindowBlur = (event) => {
|
|
||||||
if (!isWindow(event.target)) return
|
|
||||||
debug('onWindowBlur')
|
|
||||||
this.tmp.isWindowFocused = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On window focus, update the `isWindowFocused` flag.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
*/
|
|
||||||
|
|
||||||
onWindowFocus = (event) => {
|
|
||||||
if (!isWindow(event.target)) return
|
|
||||||
debug('onWindowFocus')
|
|
||||||
this.tmp.isWindowFocused = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the editor content.
|
* Render the editor content.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user