mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 21:21:21 +02:00
move blur check to content component - closes #297
This commit is contained in:
@@ -4,6 +4,7 @@ import Debug from 'debug'
|
|||||||
import Node from './node'
|
import Node from './node'
|
||||||
import OffsetKey from '../utils/offset-key'
|
import OffsetKey from '../utils/offset-key'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
import Selection from '../models/selection'
|
import Selection from '../models/selection'
|
||||||
import Transfer from '../utils/transfer'
|
import Transfer from '../utils/transfer'
|
||||||
import TYPES from '../constants/types'
|
import TYPES from '../constants/types'
|
||||||
@@ -28,6 +29,37 @@ const debug = Debug('slate:content')
|
|||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the deepest descendant of a DOM `element`.
|
||||||
|
*
|
||||||
|
* @param {Element} node
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function findDeepestNode(element) {
|
||||||
|
return element.firstChild
|
||||||
|
? findDeepestNode(element.firstChild)
|
||||||
|
: element
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the child is a descendant of parent.
|
||||||
|
*
|
||||||
|
* @param parent
|
||||||
|
* @param child
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isDescendant(parent, child) {
|
||||||
|
if (!child || !parent) return false
|
||||||
|
let node = child.parentNode
|
||||||
|
while (node != null) {
|
||||||
|
if (node == parent) return true
|
||||||
|
node = node.parentNode
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content.
|
* Content.
|
||||||
*
|
*
|
||||||
@@ -122,16 +154,34 @@ class Content extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When finished rendering, move the `isRendering` flag on next tick.
|
* When finished rendering, move the `isRendering` flag on next tick and clean up the DOM's activeElement
|
||||||
|
* if neccessary.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} prevProps
|
||||||
* @param {Object} state
|
* @param {Object} prevState
|
||||||
*/
|
*/
|
||||||
|
|
||||||
componentDidUpdate = (props, state) => {
|
componentDidUpdate = (prevProps, prevState) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.tmp.isRendering = false
|
this.tmp.isRendering = false
|
||||||
}, 1)
|
}, 1)
|
||||||
|
|
||||||
|
// If this component was focused last render, but is not now we might need to clean up the activeElement.
|
||||||
|
if (this.props.state.isBlurred && prevProps.state.isFocused) {
|
||||||
|
// Get the current selection
|
||||||
|
const ref = ReactDOM.findDOMNode(this)
|
||||||
|
const el = findDeepestNode(ref)
|
||||||
|
const window = getWindow(el)
|
||||||
|
const native = window.getSelection()
|
||||||
|
|
||||||
|
// We need to make sure that the selection from our state is up-to-date with the native selection.
|
||||||
|
// We can do this by checking if native.anchorNode is a descendant of our this node.
|
||||||
|
if (isDescendant(ref, native.anchorNode)) {
|
||||||
|
// The selection was blurred, but the DOM selection state has not changed so we need to do this manually:
|
||||||
|
native.removeAllRanges()
|
||||||
|
if (ref) ref.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,24 +5,6 @@ import React from 'react'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the child is a descendant of parent.
|
|
||||||
*
|
|
||||||
* @param parent
|
|
||||||
* @param child
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function isDescendant(parent, child) {
|
|
||||||
if (!child || !parent) return false
|
|
||||||
let node = child.parentNode
|
|
||||||
while (node != null) {
|
|
||||||
if (node == parent) return true
|
|
||||||
node = node.parentNode
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debugger.
|
* Debugger.
|
||||||
*
|
*
|
||||||
@@ -154,6 +136,9 @@ class Leaf extends React.Component {
|
|||||||
const { state, ranges, isVoid } = this.props
|
const { state, ranges, isVoid } = this.props
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
|
|
||||||
|
// If the selection is blurred we have nothing to do.
|
||||||
|
if (selection.isBlurred) return
|
||||||
|
|
||||||
let { anchorOffset, focusOffset } = selection
|
let { anchorOffset, focusOffset } = selection
|
||||||
const { node, index } = this.props
|
const { node, index } = this.props
|
||||||
const { start, end } = OffsetKey.findBounds(index, ranges)
|
const { start, end } = OffsetKey.findBounds(index, ranges)
|
||||||
@@ -163,36 +148,6 @@ class Leaf extends React.Component {
|
|||||||
const hasFocus = selection.hasFocusBetween(node, start, end)
|
const hasFocus = selection.hasFocusBetween(node, start, end)
|
||||||
if (!hasAnchor && !hasFocus) return
|
if (!hasAnchor && !hasFocus) return
|
||||||
|
|
||||||
// We have a selection to render, so prepare a few things...
|
|
||||||
const ref = ReactDOM.findDOMNode(this)
|
|
||||||
const el = findDeepestNode(ref)
|
|
||||||
const window = getWindow(el)
|
|
||||||
|
|
||||||
// If no selection is defined we're probably in a node environment and we can skip the selection magic.
|
|
||||||
if (!window || !window.getSelection) return
|
|
||||||
|
|
||||||
const native = window.getSelection()
|
|
||||||
const parent = ref.closest('[contenteditable]')
|
|
||||||
|
|
||||||
// If the selection is blurred but the DOM selection still focuses this leaf,
|
|
||||||
// we need to clean up the ranges and blur the contenteditable.
|
|
||||||
if (selection.isBlurred && (hasAnchor || hasFocus)) {
|
|
||||||
// We need to make sure that the selection from our state is up-to-date with the native selection.
|
|
||||||
// We can do this by checking if native.anchorNode is a descendant of our contenteditable parent, which is this
|
|
||||||
// slate instance. If it's not, the selection is no longer under this instance's responsibility and not further
|
|
||||||
// action is required.
|
|
||||||
if (!isDescendant(parent, native.anchorNode)) return
|
|
||||||
|
|
||||||
// Apparently our selection is blurred, but the DOM still has ranges in the nodes we manage. To fix this,
|
|
||||||
// we blur the contenteditable and remove all ranges.
|
|
||||||
native.removeAllRanges()
|
|
||||||
if (parent) parent.blur()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the selection is blurred we have nothing to do.
|
|
||||||
if (selection.isBlurred) return
|
|
||||||
|
|
||||||
// If the leaf is a void leaf, ensure that it has no width. This is due to
|
// If the leaf is a void leaf, ensure that it has no width. This is due to
|
||||||
// void nodes always rendering an empty leaf, for browser compatibility.
|
// void nodes always rendering an empty leaf, for browser compatibility.
|
||||||
if (isVoid) {
|
if (isVoid) {
|
||||||
@@ -200,6 +155,13 @@ class Leaf extends React.Component {
|
|||||||
focusOffset = 0
|
focusOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have a selection to render, so prepare a few things...
|
||||||
|
const ref = ReactDOM.findDOMNode(this)
|
||||||
|
const el = findDeepestNode(ref)
|
||||||
|
const window = getWindow(el)
|
||||||
|
const native = window.getSelection()
|
||||||
|
const parent = ref.closest('[contenteditable]')
|
||||||
|
|
||||||
// In firefox it is not enough to create a range, you also need to focus the contenteditable element.
|
// In firefox it is not enough to create a range, you also need to focus the contenteditable element.
|
||||||
function focus() {
|
function focus() {
|
||||||
if (parent) setTimeout(() => parent.focus(), 0)
|
if (parent) setTimeout(() => parent.focus(), 0)
|
||||||
|
Reference in New Issue
Block a user