Skip to content

Commit

Permalink
Fix autocapitalization (#3881)
Browse files Browse the repository at this point in the history
  • Loading branch information
zurfyx authored and Lexical GitHub Actions Bot committed Feb 17, 2023
1 parent de0361d commit fd8390b
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 1 deletion.
102 changes: 102 additions & 0 deletions packages/lexical-playground/__tests__/e2e/Events.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {
assertHTML,
evaluate,
focusEditor,
html,
initialize,
LEGACY_EVENTS,
test,
} from '../utils/index.mjs';

test.describe('Events', () => {
test.beforeEach(({isCollab, page}) =>
initialize({isAutocomplete: true, isCollab, page}),
);
test('Autocapitalization (MacOS specific)', async ({page, isPlainText}) => {
if (LEGACY_EVENTS) {
return;
}
await focusEditor(page);
await page.keyboard.type('i');
await evaluate(page, () => {
const editable = document.querySelector('[contenteditable="true"]');
const span = editable.querySelector('span');
const textNode = span.firstChild;
function singleRangeFn(
startContainer,
startOffset,
endContainer,
endOffset,
) {
return () => [
new StaticRange({
endContainer,
endOffset,
startContainer,
startOffset,
}),
];
}
const character = 'S'; // S for space because the space itself gets trimmed in the assertHTML
const replacementCharacter = 'I';
const dataTransfer = new DataTransfer();
dataTransfer.setData('text/plain', replacementCharacter);
dataTransfer.setData('text/html', replacementCharacter);
const characterBeforeInputEvent = new InputEvent('beforeinput', {
bubbles: true,
cancelable: true,
data: character,
inputType: 'insertText',
});
characterBeforeInputEvent.getTargetRanges = singleRangeFn(
textNode,
1,
textNode,
1,
);
const replacementBeforeInputEvent = new InputEvent('beforeinput', {
bubbles: true,
cancelable: true,
clipboardData: dataTransfer,
data: replacementCharacter,
dataTransfer,
inputType: 'insertReplacementText',
});
replacementBeforeInputEvent.getTargetRanges = singleRangeFn(
textNode,
0,
textNode,
1,
);
const characterInputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
data: character,
inputType: 'insertText',
});
editable.dispatchEvent(characterBeforeInputEvent);
textNode.textContent += character;
editable.dispatchEvent(replacementBeforeInputEvent);
editable.dispatchEvent(characterInputEvent);
});

await assertHTML(
page,
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">IS</span>
</p>
`,
);
});
});
19 changes: 18 additions & 1 deletion packages/lexical/src/LexicalEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ if (CAN_USE_BEFORE_INPUT) {
let lastKeyDownTimeStamp = 0;
let lastKeyCode = 0;
let lastBeforeInputInsertTextTimeStamp = 0;
let unprocessedBeforeInputData: null | string = null;
let rootElementsRegistered = 0;
let isSelectionChangeFromDOMUpdate = false;
let isSelectionChangeFromMouseDown = false;
Expand Down Expand Up @@ -500,14 +501,27 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {

const data = event.data;

// This represents the case when two beforeinput events are triggered at the same time (without a
// full event loop ending at input). This happens with MacOS with the default keyboard settings,
// a combination of autocorrection + autocapitalization.
// Having Lexical run everything in controlled mode would fix the issue without additional code
// but this would kill the massive performance win from the most common typing event.
// Alternatively, when this happens we can prematurely update our EditorState based on the DOM
// content, a job that would usually be the input event's responsibility.
if (unprocessedBeforeInputData !== null) {
$updateSelectedTextFromDOM(false, editor, unprocessedBeforeInputData);
}

if (
!selection.dirty &&
(!selection.dirty || unprocessedBeforeInputData !== null) &&
selection.isCollapsed() &&
!$isRootNode(selection.anchor.getNode())
) {
$applyTargetRange(selection, event);
}

unprocessedBeforeInputData = null;

const anchor = selection.anchor;
const focus = selection.focus;
const anchorNode = anchor.getNode();
Expand Down Expand Up @@ -536,6 +550,8 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {
) {
event.preventDefault();
dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
} else {
unprocessedBeforeInputData = data;
}
lastBeforeInputInsertTextTimeStamp = event.timeStamp;
return;
Expand Down Expand Up @@ -748,6 +764,7 @@ function onInput(event: InputEvent, editor: LexicalEditor): void {
// since the change.
$flushMutations();
});
unprocessedBeforeInputData = null;
}

function onCompositionStart(
Expand Down

0 comments on commit fd8390b

Please sign in to comment.