1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-21 06:31:28 +02:00

Remove is native (#1088)

* start removing isNative

* more

* remove more isNative logic

* fix linter
This commit is contained in:
Ian Storm Taylor
2017-09-10 14:56:40 -07:00
committed by GitHub
parent 9fca2d3b9b
commit 10e4216d00
6 changed files with 27 additions and 173 deletions

View File

@@ -7,19 +7,6 @@
const Changes = {} const Changes = {}
/**
* Set the `isNative` flag on the underlying state to prevent re-renders.
*
* @param {Change} change
* @param {Boolean} value
*/
Changes.setIsNative = (change, value) => {
let { state } = change
state = state.set('isNative', value)
change.state = state
}
/** /**
* Set `properties` on the top-level state's data. * Set `properties` on the top-level state's data.
* *

View File

@@ -91,33 +91,6 @@ class Content extends React.Component {
this.tmp.forces = 0 this.tmp.forces = 0
} }
/**
* Should the component update?
*
* @param {Object} props
* @param {Object} state
* @return {Boolean}
*/
shouldComponentUpdate = (props, state) => {
// If the readOnly state has changed, we need to re-render so that
// the cursor will be added or removed again.
if (props.readOnly != this.props.readOnly) return true
// If the state has been changed natively, never re-render, or else we'll
// end up duplicating content.
if (props.state.isNative) return false
return (
props.className != this.props.className ||
props.schema != this.props.schema ||
props.autoCorrect != this.props.autoCorrect ||
props.spellCheck != this.props.spellCheck ||
props.state != this.props.state ||
props.style != this.props.style
)
}
/** /**
* When the editor first mounts in the DOM we need to: * When the editor first mounts in the DOM we need to:
* *
@@ -722,7 +695,6 @@ class Content extends React.Component {
// If there are no ranges, the editor was blurred natively. // If there are no ranges, the editor was blurred natively.
if (!native.rangeCount) { if (!native.rangeCount) {
data.selection = selection.set('isFocused', false) data.selection = selection.set('isFocused', false)
data.isNative = true
} }
// Otherwise, determine the Slate selection from the native one. // Otherwise, determine the Slate selection from the native one.

View File

@@ -1,12 +1,10 @@
import Debug from 'debug' import Debug from 'debug'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom'
import Types from 'prop-types' import Types from 'prop-types'
import OffsetKey from '../utils/offset-key' import OffsetKey from '../utils/offset-key'
import SlateTypes from '../utils/prop-types' import SlateTypes from '../utils/prop-types'
import findDeepestNode from '../utils/find-deepest-node'
import { IS_FIREFOX } from '../constants/environment' import { IS_FIREFOX } from '../constants/environment'
/** /**
@@ -45,18 +43,6 @@ class Leaf extends React.Component {
text: Types.string.isRequired, text: Types.string.isRequired,
} }
/**
* Constructor.
*
* @param {Object} props
*/
constructor(props) {
super(props)
this.tmp = {}
this.tmp.renders = 0
}
/** /**
* Debug. * Debug.
* *
@@ -86,12 +72,6 @@ class Leaf extends React.Component {
return true return true
} }
// If the DOM text does not equal the `text` property, re-render, this can
// happen because React gets out of sync when previously natively rendered.
const el = findDeepestNode(ReactDOM.findDOMNode(this))
const text = this.renderText(props)
if (el.textContent != text) return true
// Otherwise, don't update. // Otherwise, don't update.
return false return false
} }
@@ -110,16 +90,10 @@ class Leaf extends React.Component {
index index
}) })
// Increment the renders key, which forces a re-render whenever this
// component is told it should update. This is required because "native"
// renders where we don't update the leaves cause React's internal state to
// get out of sync, causing it to not realize the DOM needs updating.
this.tmp.renders++
this.debug('render', { props }) this.debug('render', { props })
return ( return (
<span key={this.tmp.renders} data-offset-key={offsetKey}> <span data-offset-key={offsetKey}>
{this.renderMarks(props)} {this.renderMarks(props)}
</span> </span>
) )

View File

@@ -45,7 +45,6 @@ class Change {
this.state = state this.state = state
this.operations = [] this.operations = []
this.flags = pick(attrs, ['merge', 'save']) this.flags = pick(attrs, ['merge', 'save'])
this.setIsNative(attrs.isNative === undefined ? false : attrs.isNative)
} }
/** /**

View File

@@ -20,7 +20,6 @@ const DEFAULTS = {
selection: Selection.create(), selection: Selection.create(),
history: History.create(), history: History.create(),
data: new Map(), data: new Map(),
isNative: false,
} }
/** /**

View File

@@ -1,7 +1,6 @@
import Base64 from '../serializers/base-64' import Base64 from '../serializers/base-64'
import Block from '../models/block' import Block from '../models/block'
import Character from '../models/character'
import Content from '../components/content' import Content from '../components/content'
import Debug from 'debug' import Debug from 'debug'
import Inline from '../models/inline' import Inline from '../models/inline'
@@ -50,10 +49,6 @@ function Plugin(options = {}) {
const schema = editor.getSchema() const schema = editor.getSchema()
const prevState = editor.getState() const prevState = editor.getState()
// PERF: Skip normalizing if the change is native, since we know that it
// can't have changed anything that requires a core schema fix.
if (state.isNative) return
// PERF: Skip normalizing if the document hasn't changed, since the core // PERF: Skip normalizing if the document hasn't changed, since the core
// schema only normalizes changes to the document, not selection. // schema only normalizes changes to the document, not selection.
if (prevState && state.document == prevState.document) return if (prevState && state.document == prevState.document) return
@@ -63,8 +58,7 @@ function Plugin(options = {}) {
} }
/** /**
* On before input, see if we can let the browser continue with it's native * On before input, correct any browser inconsistencies.
* input behavior, to avoid a re-render for performance.
* *
* @param {Event} e * @param {Event} e
* @param {Object} data * @param {Object} data
@@ -73,110 +67,39 @@ function Plugin(options = {}) {
*/ */
function onBeforeInput(e, data, change, editor) { function onBeforeInput(e, data, change, editor) {
debug('onBeforeInput', { data })
e.preventDefault()
const { state } = change const { state } = change
const { document, startKey, startBlock, startOffset, startInline, startText } = state const { selection } = state
const pText = startBlock.getPreviousText(startKey) const { anchorKey, anchorOffset, focusKey, focusOffset } = selection
const pInline = pText && startBlock.getClosestInline(pText.key)
const nText = startBlock.getNextText(startKey)
const nInline = nText && startBlock.getClosestInline(nText.key)
// Determine what the characters would be if natively inserted. // COMPAT: In iOS, when using predictive text suggestions, the native
const schema = editor.getSchema() // selection will be changed to span the existing word, so that the word is
const decorators = document.getDescendantDecorators(startKey, schema) // replaced. But the `select` fires after the `beforeInput` event, even
const initialChars = startText.getDecorations(decorators) // though the native selection is updated. So we need to manually check if
const prevChar = startOffset === 0 ? null : initialChars.get(startOffset - 1) // the selection has gotten out of sync, and adjust it if so. (03/18/2017)
const nextChar = startOffset === initialChars.size ? null : initialChars.get(startOffset)
const char = Character.create({
text: e.data,
// When cursor is at start of a range of marks, without preceding text,
// the native behavior is to insert inside the range of marks.
marks: (
(prevChar && prevChar.marks) ||
(nextChar && nextChar.marks) ||
[]
)
})
const chars = initialChars.insert(startOffset, char)
// COMPAT: In iOS, when choosing from the predictive text suggestions, the
// native selection will be changed to span the existing word, so that the word
// is replaced. But the `select` event for this change doesn't fire until after
// the `beforeInput` event, even though the native selection is updated. So we
// need to manually adjust the selection to be in sync. (03/18/2017)
const window = getWindow(e.target) const window = getWindow(e.target)
const native = window.getSelection() const native = window.getSelection()
const { anchorNode, anchorOffset, focusNode, focusOffset } = native const a = getPoint(native.anchorNode, native.anchorOffset, state, editor)
const anchorPoint = getPoint(anchorNode, anchorOffset, state, editor) const f = getPoint(native.focusNode, native.focusOffset, state, editor)
const focusPoint = getPoint(focusNode, focusOffset, state, editor) const hasMismatch = a && f && (
if (anchorPoint && focusPoint) { anchorKey != a.key ||
const { selection } = state anchorOffset != a.offset ||
if ( focusKey != f.key ||
selection.anchorKey !== anchorPoint.key || focusOffset != f.offset
selection.anchorOffset !== anchorPoint.offset ||
selection.focusKey !== focusPoint.key ||
selection.focusOffset !== focusPoint.offset
) {
change = change
.select({
anchorKey: anchorPoint.key,
anchorOffset: anchorPoint.offset,
focusKey: focusPoint.key,
focusOffset: focusPoint.offset
})
}
}
// Determine what the characters should be, if not natively inserted.
change.insertText(e.data)
const next = change.state
const nextText = next.startText
const nextChars = nextText.getDecorations(decorators)
// We do not have to re-render if the current selection is collapsed, the
// current node is not empty, there are no marks on the cursor, the cursor
// is not at the edge of an inline node, the cursor isn't at the starting
// edge of a text node after an inline node, and the natively inserted
// characters would be the same as the non-native.
const isNative = (
// If the selection is expanded, we don't know what the edit will look
// like so we can't let it happen natively.
(state.isCollapsed) &&
// If the selection has marks, then we need to render it non-natively
// because we need to create the new marks as well.
(state.selection.marks == null) &&
// If the text node in question has no content, browsers might do weird
// things so we need to insert it normally instead.
(state.startText.text != '') &&
// COMPAT: Browsers do weird things when typing at the edges of inline
// nodes, so we can't let them render natively. (?)
(!startInline || !state.selection.isAtStartOf(startInline)) &&
(!startInline || !state.selection.isAtEndOf(startInline)) &&
// COMPAT: In Chrome & Safari, it isn't possible to have a selection at
// the starting edge of a text node after another inline node. It will
// have been automatically changed. So we can't render natively because
// the cursor isn't technically in the right spot. (2016/12/01)
(!(pInline && !pInline.isVoid && startOffset == 0)) &&
(!(nInline && !nInline.isVoid && startOffset == startText.text.length)) &&
// COMPAT: When inserting a Space character, Chrome will sometimes
// split the text node into two adjacent text nodes. See:
// https://github.com/ianstormtaylor/slate/issues/938
(!(e.data === ' ' && IS_CHROME)) &&
// If the
(chars.equals(nextChars))
) )
// If `isNative`, set the flag on the change. if (hasMismatch) {
if (isNative) { change.select({
change.setIsNative(true) anchorKey: a.key,
anchorOffset: a.offset,
focusKey: f.key,
focusOffset: f.offset
})
} }
// Otherwise, prevent default so that the DOM remains untouched. change.insertText(e.data)
else {
e.preventDefault()
}
debug('onBeforeInput', { data, isNative })
} }
/** /**