Skip to content

Commit

Permalink
Merge pull request #84 from textioHQ/topic-native-before-input
Browse files Browse the repository at this point in the history
Optionally use native 'beforeinput' events instead of React's synthetic event
  • Loading branch information
ShannonLCapper authored Feb 21, 2019
2 parents bf79731 + 4dd7fe8 commit df3ea0c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 2 deletions.
19 changes: 18 additions & 1 deletion src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const editOnSelect = require('editOnSelect');
const getScrollPosition = require('getScrollPosition');
const invariant = require('invariant');
const nullthrows = require('nullthrows');
const areLevel2InputEventsSupported = require('areLevel2InputEventsSupported');

const isIE = UserAgent.isBrowser('IE');

Expand Down Expand Up @@ -96,6 +97,7 @@ class DraftEditor extends React.Component {
_renderNativeContent: boolean;
_updatedNativeInsertionBlock: boolean;
_latestCommittedEditorState: EditorState;
_useNativeBeforeInput: boolean;

/**
* Define proxies that can route events to the current handler.
Expand Down Expand Up @@ -136,6 +138,8 @@ class DraftEditor extends React.Component {
constructor(props: DraftEditorProps) {
super(props);

this._useNativeBeforeInput = props.useNativeBeforeInputIfAble && areLevel2InputEventsSupported();

this._blockSelectEvents = false;
this._clipboard = null;
this._handler = null;
Expand Down Expand Up @@ -235,15 +239,28 @@ class DraftEditor extends React.Component {
// it is not possible to set up an onPaste handler through react.
// Manually use addEventListener and removeEventListener below.
// See the comments in editOnPaste for why this is needed.
//
// We also provide an option to manually manage our own onBeforeInput handler
// without going through React. React polyfills this event using `textInput`/`keypress`,
// but doesn't use the natively-available event when it can (see https://github.com/facebook/react/issues/11211)
// In rare circumstances, we want to provide the option to force the use of the native
// `beforeinput`, event. Slate does something similar https://github.com/ianstormtaylor/slate/commit/f812816b7dcb2d4b2efa0d4ba12d4feac31850c9
if (this._editor) {
const editorNode = ReactDOM.findDOMNode(this._editor);
editorNode.removeEventListener('paste', this._onPaste);
if (this._useNativeBeforeInput) {
editorNode.removeEventListener('beforeinput', this._onBeforeInput);
}
}

this._editor = ref;
if (this._editor) {
const editorNode = ReactDOM.findDOMNode(this._editor);

editorNode.addEventListener('paste', this._onPaste);
if (this._useNativeBeforeInput) {
editorNode.addEventListener('beforeinput', this._onBeforeInput);
}

// Add ignore attribute for an IESpell, an obscure plugin that doesn't respect spellcheck="false" on a
// contenteditable div
Expand Down Expand Up @@ -307,7 +324,7 @@ class DraftEditor extends React.Component {
})}
contentEditable={!readOnly}
data-testid={this.props.webDriverTestID}
onBeforeInput={this._onBeforeInput}
onBeforeInput={this._useNativeBeforeInput ? undefined : this._onBeforeInput}
onBlur={this._onBlur}
onCompositionEnd={this._onCompositionEnd}
onCompositionStart={this._onCompositionStart}
Expand Down
3 changes: 3 additions & 0 deletions src/component/base/DraftEditorProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ export type DraftEditorProps = {
// an element tag and an optional react element wrapper. This configuration
// is used for both rendering and paste processing.
blockRenderMap: DraftBlockRenderMap,

useNativeBeforeInputIfAble?: boolean,
};

export type DraftEditorDefaultProps = {
Expand All @@ -185,4 +187,5 @@ export type DraftEditorDefaultProps = {
readOnly: boolean,
spellCheck: boolean,
stripPastedStyles: boolean,
useNativeBeforeInputIfAble: false,
};
2 changes: 1 addition & 1 deletion src/component/handlers/edit/editOnBeforeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function replaceText(
* preserve spellcheck highlighting, which disappears or flashes if re-render
* occurs on the relevant text nodes.
*/
function editOnBeforeInput(editor: DraftEditor, e: SyntheticInputEvent): void {
function editOnBeforeInput(editor: DraftEditor, e: InputEvent | SyntheticInputEvent): void {

// React doesn't fire a selection event until mouseUp, so it's possible to
// click to change selection, hold the mouse down, and type a character
Expand Down
21 changes: 21 additions & 0 deletions src/component/utils/areLevel2InputEventsSupported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @providesModule areLevel2InputEventsSupported
* @typechecks
* @flow
*
* This method determines if we're in a browser which fires native 'beforeinput'
* events (Level 1 support) and allows that event to be cancellable (Level 2 support)
*
* It's borrowed from Slate https://github.com/ianstormtaylor/slate/blob/9694b228464d8b4d874074496a4c0a50f6ec4614/packages/slate-dev-environment/src/index.js#L74-L79
*/

'use strict';

function areLevel2InputEventsSupported(): boolean {
const element = window.document.createElement('div');
element.contentEditable = true;
const support = 'onbeforeinput' in element;
return support;
}

module.exports = areLevel2InputEventsSupported;

0 comments on commit df3ea0c

Please sign in to comment.