1
0
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:
Aeneas Rekkas (arekkas)
2016-11-16 13:02:58 +01:00
parent ff25061ec0
commit d9bb0dd72c
2 changed files with 64 additions and 52 deletions

View File

@@ -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()
}
}
} }
/** /**

View File

@@ -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)