Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally use native 'beforeinput' events instead of React's synthetic event #84

Merged
merged 2 commits into from
Feb 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;