Skip to content
This repository was archived by the owner on Feb 6, 2023. It is now read-only.
Closed
2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.js.json

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
_onBlur: Function;
_onCharacterData: Function;
_onCompositionEnd: Function;
_onCompositionUpdate: Function;
_onCompositionStart: Function;
_onCopy: Function;
_onCut: Function;
Expand Down Expand Up @@ -209,6 +210,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
this._onBlur = this._buildHandler('onBlur');
this._onCharacterData = this._buildHandler('onCharacterData');
this._onCompositionEnd = this._buildHandler('onCompositionEnd');
this._onCompositionUpdate = this._buildHandler('onCompositionUpdate');
this._onCompositionStart = this._buildHandler('onCompositionStart');
this._onCopy = this._buildHandler('onCopy');
this._onCut = this._buildHandler('onCut');
Expand Down Expand Up @@ -251,6 +253,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {

// See `restoreEditorDOM()`.
this.state = {contentsKey: 0};
window.editor = this;
}

/**
Expand All @@ -272,7 +275,20 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
const method = this._handler && this._handler[eventName];
if (method) {
if (flushControlled) {
flushControlled(() => method(this, e));
flushControlled(() => {
e.persist();
console.groupCollapsed(
`${eventName} - "${e.data || ''}" - ${e.nativeEvent.key ||
null} - "${this._latestEditorState
.getCurrentContent()
.getPlainText()}"`,
);
console.log(this._latestEditorState.toJS());
console.log(e.nativeEvent);
console.log(e);
console.groupEnd();
return method(this, e);
});
} else {
method(this, e);
}
Expand Down Expand Up @@ -392,6 +408,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
onBeforeInput={this._onBeforeInput}
onBlur={this._onBlur}
onCompositionEnd={this._onCompositionEnd}
onCompositionUpdate={this._onCompositionUpdate}
onCompositionStart={this._onCompositionStart}
onCopy={this._onCopy}
onCut={this._onCut}
Expand Down Expand Up @@ -520,6 +537,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
* the active mode.
*/
setMode: DraftEditorModes => void = (mode: DraftEditorModes): void => {
console.log('CHANGED_MODE', mode);
this._handler = handlerMap[mode];
};

Expand Down Expand Up @@ -572,6 +590,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
* function.
*/
update: EditorState => void = (editorState: EditorState): void => {
console.error('UPDATE STATE');
this._latestEditorState = editorState;
this.props.onChange(editorState);
};
Expand Down
185 changes: 145 additions & 40 deletions src/component/handlers/composition/DraftEditorCompositionHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import type DraftEditor from 'DraftEditor.react';
const DraftModifier = require('DraftModifier');
const EditorState = require('EditorState');
const Keys = require('Keys');
const invariant = require('invariant');
const ReactDOM = require('ReactDOM');

const getEntityKeyForSelection = require('getEntityKeyForSelection');
const gkx = require('gkx');
const isEventHandled = require('isEventHandled');
const isSelectionAtLeafStart = require('isSelectionAtLeafStart');
const getDraftEditorSelectionWithNodes = require('getDraftEditorSelectionWithNodes');

/**
* Millisecond delay to allow `compositionstart` to fire again upon
Expand All @@ -42,21 +45,56 @@ const RESOLVE_DELAY = 20;
*/
let resolved = false;
let stillComposing = false;
let textInputData = '';
let beforeInputData = null;
let compositionEndData = null;
let compositionUpdateData = null;

const DraftEditorCompositionHandler = {
type AnchorSelectionData = {
anchorOffset: number,
anchorNode: Node | null,
};
let compositionSelectionStart: ?AnchorSelectionData = null;

var DraftEditorCompositionHandler = {
/**
* Some IMEs (Firefox Mobile, notably) fire multiple `beforeinput` events
* which include the text so far typed, in addition to (broken)
* `compositionend` events. In these cases, we construct beforeInputData from
* the `beforeinput` and consider that to be the definitive version of what
* was actually typed.
*
* Proper, compliant browsers will not do this and will instead include the
* entire resolved composition result in the `data` member of the
* `compositionend` events that they fire.
*/
onBeforeInput: function(editor: DraftEditor, e: SyntheticInputEvent<>): void {
textInputData = (textInputData || '') + e.data;
beforeInputData = (beforeInputData || '') + e.data;
},

/**
* A `compositionstart` event has fired while we're still in composition
* mode. Continue the current composition session to prevent a re-render.
*/
onCompositionStart: function(editor: DraftEditor): void {
const selection = global.getSelection();
compositionSelectionStart = {
anchorOffset: selection.anchorOffset,
anchorNode: selection.anchorNode,
};
stillComposing = true;
},

/**
* A `compositionupdate` event has fired. Update the current composition
* session.
*/
onCompositionUpdate: function(
editor: DraftEditor,
e: SyntheticInputEvent<>,
): void {
compositionUpdateData = e.data;
},

/**
* Attempt to end the current composition session.
*
Expand All @@ -71,9 +109,14 @@ const DraftEditorCompositionHandler = {
* twice could break the DOM, we only use the first event. Example: Arabic
* Google Input Tools on Windows 8.1 fires `compositionend` three times.
*/
onCompositionEnd: function(editor: DraftEditor, e: SyntheticEvent<>): void {
onCompositionEnd: function(
editor: DraftEditor,
e: SyntheticCompositionEvent<>,
): void {
resolved = false;
stillComposing = false;
// Use e.data from the first compositionend event seen
compositionEndData = compositionEndData || e.data;
setTimeout(() => {
if (!resolved) {
DraftEditorCompositionHandler.resolveComposition(editor, e);
Expand Down Expand Up @@ -113,6 +156,34 @@ const DraftEditorCompositionHandler = {
}
},

/**
* Normalizes platform inconsistencies with input event data.
*
* When beforeInputData is present, it is only preferred if its length
* is greater than that of the last compositionUpdate event data. This is
* meant to resolve IME incosistencies where compositionUpdate may contain
* only the last character or the entire composition depending on language
* (e.g. Korean vs. Japanese).
*
* When beforeInputData is not present, compositionUpdate data is preferred.
* This resolves issues with some platforms where beforeInput is never fired
* (e.g. Android with certain keyboard and browser combinations).
*
* Lastly, if neither beforeInput nor compositionUpdate events are fired, use
* the data in the compositionEnd event
*/
normalizeCompositionInput: function(): ?string {
const beforeInputDataLength = beforeInputData ? beforeInputData.length : 0;
const compositionUpdateDataLength = compositionUpdateData
? compositionUpdateData.length
: 0;
const updateData =
beforeInputDataLength > compositionUpdateDataLength
? beforeInputData
: compositionUpdateData;
return updateData || compositionEndData;
},

/**
* Attempt to insert composed characters into the document.
*
Expand All @@ -137,8 +208,11 @@ const DraftEditorCompositionHandler = {
}

resolved = true;
const composedChars = textInputData;
textInputData = '';

const composedChars = this.normalizeCompositionInput();
beforeInputData = null;
compositionUpdateData = null;
compositionEndData = null;

const editorState = EditorState.set(editor._latestEditorState, {
inCompositionMode: false,
Expand All @@ -151,54 +225,85 @@ const DraftEditorCompositionHandler = {
);

const mustReset =
!composedChars ||
isSelectionAtLeafStart(editorState) ||
currentStyle.size > 0 ||
entityKey !== null;

if (mustReset) {
console.log('RESET DOM');
editor.restoreEditorDOM();
}

editor.exitCurrentMode();

if (composedChars) {
if (
gkx('draft_handlebeforeinput_composed_text') &&
editor.props.handleBeforeInput &&
isEventHandled(
editor.props.handleBeforeInput(
composedChars,
editorState,
event.timeStamp,
),
)
) {
return;
}
// If characters have been composed, re-rendering with the update
// is sufficient to reset the editor.
const contentState = DraftModifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
composedChars,
currentStyle,
entityKey,
);
editor.update(
EditorState.push(editorState, contentState, 'insert-characters'),
);
if (composedChars == null) {
return;
}

if (mustReset) {
editor.update(
EditorState.set(editorState, {
nativelyRenderedContent: null,
forceSelection: true,
}),
);
if (
gkx('draft_handlebeforeinput_composed_text') &&
editor.props.handleBeforeInput &&
isEventHandled(
editor.props.handleBeforeInput(
composedChars,
editorState,
event.timeStamp,
),
)
) {
return;
}

const currentSelection = global.getSelection();

const editorNode = ReactDOM.findDOMNode(editor.editorContainer);
invariant(editorNode, 'Missing editorNode');
invariant(
editorNode.firstChild instanceof HTMLElement,
'editorNode.firstChild is not an HTMLElement',
);
invariant(compositionSelectionStart, 'compositionSelectionStart is null');
const startAnchorNode = compositionSelectionStart.anchorNode;
invariant(
startAnchorNode,
'compositionSelectionStart.anchorNode is not a Node',
);
// If compositionSelectionStart.anchorNode is detached from the document
// an exception ends up happening inside getDraftEditorSelectionWithNodes.
if (
startAnchorNode.nodeType === Node.TEXT_NODE &&
!startAnchorNode.ownerDocument.contains(startAnchorNode)
) {
return;
}
const {selectionState} = getDraftEditorSelectionWithNodes(
editorState,
editorNode.firstChild,
startAnchorNode,
compositionSelectionStart.anchorOffset,
currentSelection.focusNode,
currentSelection.focusOffset,
);

// TODO, add comment giving more context on this check
if (selectionState.isCollapsed() || selectionState.getIsBackward()) {
return;
}

console.log('COMPOSE UPDATE', composedChars);

// If characters have been composed, re-rendering with the update
// is sufficient to reset the editor.
const contentState = DraftModifier.replaceText(
editorState.getCurrentContent(),
editorState.getSelection(),
composedChars,
currentStyle,
entityKey,
);
editor.update(
EditorState.push(editorState, contentState, 'insert-characters'),
);
},
};

Expand Down
16 changes: 10 additions & 6 deletions src/component/handlers/edit/editOnBeforeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ function editOnBeforeInput(
editor: DraftEditor,
e: SyntheticInputEvent<>,
): void {
// if (e.data === 'Fabio') {
// eval('debugger');
// }
if (editor._pendingStateFromBeforeInput !== undefined) {
editor.update(editor._pendingStateFromBeforeInput);
editor._pendingStateFromBeforeInput = undefined;
Expand Down Expand Up @@ -239,12 +242,13 @@ function editOnBeforeInput(
}

if (mustPreventNative) {
e.preventDefault();
newEditorState = EditorState.set(newEditorState, {
forceSelection: true,
});
editor.update(newEditorState);
return;
console.log('WOULD PREVENT DEFAULT');
// e.preventDefault();
// newEditorState = EditorState.set(newEditorState, {
// forceSelection: true,
// });
// editor.update(newEditorState);
// return;
}

// We made it all the way! Let the browser do its thing and insert the char.
Expand Down
Loading