diff --git a/src/changes/on-state.js b/src/changes/on-state.js index c3cc176e1..3bd8a06cb 100644 --- a/src/changes/on-state.js +++ b/src/changes/on-state.js @@ -7,19 +7,6 @@ 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. * diff --git a/src/components/content.js b/src/components/content.js index 689718678..ee2f2da66 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -91,33 +91,6 @@ class Content extends React.Component { 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: * @@ -722,7 +695,6 @@ class Content extends React.Component { // If there are no ranges, the editor was blurred natively. if (!native.rangeCount) { data.selection = selection.set('isFocused', false) - data.isNative = true } // Otherwise, determine the Slate selection from the native one. diff --git a/src/components/leaf.js b/src/components/leaf.js index d928845d4..28d30c4ba 100644 --- a/src/components/leaf.js +++ b/src/components/leaf.js @@ -1,12 +1,10 @@ import Debug from 'debug' import React from 'react' -import ReactDOM from 'react-dom' import Types from 'prop-types' import OffsetKey from '../utils/offset-key' import SlateTypes from '../utils/prop-types' -import findDeepestNode from '../utils/find-deepest-node' import { IS_FIREFOX } from '../constants/environment' /** @@ -45,18 +43,6 @@ class Leaf extends React.Component { text: Types.string.isRequired, } - /** - * Constructor. - * - * @param {Object} props - */ - - constructor(props) { - super(props) - this.tmp = {} - this.tmp.renders = 0 - } - /** * Debug. * @@ -86,12 +72,6 @@ class Leaf extends React.Component { 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. return false } @@ -110,16 +90,10 @@ class Leaf extends React.Component { 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 }) return ( - + {this.renderMarks(props)} ) diff --git a/src/models/change.js b/src/models/change.js index 6c812df87..3610657b9 100644 --- a/src/models/change.js +++ b/src/models/change.js @@ -45,7 +45,6 @@ class Change { this.state = state this.operations = [] this.flags = pick(attrs, ['merge', 'save']) - this.setIsNative(attrs.isNative === undefined ? false : attrs.isNative) } /** diff --git a/src/models/state.js b/src/models/state.js index 39ff287e9..3c103a1af 100644 --- a/src/models/state.js +++ b/src/models/state.js @@ -20,7 +20,6 @@ const DEFAULTS = { selection: Selection.create(), history: History.create(), data: new Map(), - isNative: false, } /** diff --git a/src/plugins/core.js b/src/plugins/core.js index ae1f77440..11e17efcb 100644 --- a/src/plugins/core.js +++ b/src/plugins/core.js @@ -1,7 +1,6 @@ import Base64 from '../serializers/base-64' import Block from '../models/block' -import Character from '../models/character' import Content from '../components/content' import Debug from 'debug' import Inline from '../models/inline' @@ -50,10 +49,6 @@ function Plugin(options = {}) { const schema = editor.getSchema() 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 // schema only normalizes changes to the document, not selection. 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 - * input behavior, to avoid a re-render for performance. + * On before input, correct any browser inconsistencies. * * @param {Event} e * @param {Object} data @@ -73,110 +67,39 @@ function Plugin(options = {}) { */ function onBeforeInput(e, data, change, editor) { + debug('onBeforeInput', { data }) + e.preventDefault() + const { state } = change - const { document, startKey, startBlock, startOffset, startInline, startText } = state - const pText = startBlock.getPreviousText(startKey) - const pInline = pText && startBlock.getClosestInline(pText.key) - const nText = startBlock.getNextText(startKey) - const nInline = nText && startBlock.getClosestInline(nText.key) + const { selection } = state + const { anchorKey, anchorOffset, focusKey, focusOffset } = selection - // Determine what the characters would be if natively inserted. - const schema = editor.getSchema() - const decorators = document.getDescendantDecorators(startKey, schema) - const initialChars = startText.getDecorations(decorators) - const prevChar = startOffset === 0 ? null : initialChars.get(startOffset - 1) - 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) + // COMPAT: In iOS, when using predictive text suggestions, the native + // selection will be changed to span the existing word, so that the word is + // replaced. But the `select` fires after the `beforeInput` event, even + // though the native selection is updated. So we need to manually check if + // the selection has gotten out of sync, and adjust it if so. (03/18/2017) const window = getWindow(e.target) const native = window.getSelection() - const { anchorNode, anchorOffset, focusNode, focusOffset } = native - const anchorPoint = getPoint(anchorNode, anchorOffset, state, editor) - const focusPoint = getPoint(focusNode, focusOffset, state, editor) - if (anchorPoint && focusPoint) { - const { selection } = state - if ( - selection.anchorKey !== anchorPoint.key || - 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)) + const a = getPoint(native.anchorNode, native.anchorOffset, state, editor) + const f = getPoint(native.focusNode, native.focusOffset, state, editor) + const hasMismatch = a && f && ( + anchorKey != a.key || + anchorOffset != a.offset || + focusKey != f.key || + focusOffset != f.offset ) - // If `isNative`, set the flag on the change. - if (isNative) { - change.setIsNative(true) + if (hasMismatch) { + change.select({ + anchorKey: a.key, + anchorOffset: a.offset, + focusKey: f.key, + focusOffset: f.offset + }) } - // Otherwise, prevent default so that the DOM remains untouched. - else { - e.preventDefault() - } - - debug('onBeforeInput', { data, isNative }) + change.insertText(e.data) } /**