[lexical] Bug Fix: Ignore input event from inside decorators (#7354)

This commit is contained in:
Bob Ippolito
2025-03-23 19:47:15 -07:00
committed by GitHub
parent d6d9b3c59b
commit ce8663899b
3 changed files with 249 additions and 6 deletions

View File

@ -20,9 +20,7 @@ test.describe('Events', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
test('Autocapitalization (MacOS specific)', async ({page, isPlainText}) => {
if (LEGACY_EVENTS) {
return;
}
test.skip(LEGACY_EVENTS);
await focusEditor(page);
await page.keyboard.type('i');
await evaluate(page, () => {
@ -103,9 +101,7 @@ test.describe('Events', () => {
page,
isPlainText,
}) => {
if (LEGACY_EVENTS) {
return;
}
test.skip(LEGACY_EVENTS);
await focusEditor(page);
await page.keyboard.type(':)');
await assertHTML(

View File

@ -0,0 +1,240 @@
/**
* 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 {moveLeft} from '../keyboardShortcuts/index.mjs';
import {
assertHTML,
click,
focusEditor,
html,
initialize,
keyDownCtrlOrMeta,
keyUpCtrlOrMeta,
LEGACY_EVENTS,
selectFromInsertDropdown,
test,
withExclusiveClipboardAccess,
} from '../utils/index.mjs';
test.describe('HTML CopyAndPaste', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
test('Copy + paste multi line html with extra newlines', async ({
page,
isPlainText,
isCollab,
}) => {
test.skip(isPlainText || isCollab || LEGACY_EVENTS);
await focusEditor(page);
await selectFromInsertDropdown(page, '.poll');
await click(page, '.Modal__overlay[role=dialog] .Input__input');
await page.keyboard.type('Question');
await click(page, '.Modal__overlay[role=dialog] .DialogActions button');
await click(page, '.PollNode__optionInput');
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph">
<span
contenteditable="false"
style="display: inline-block"
data-lexical-decorator="true">
<div class="PollNode__container">
<div class="PollNode__inner">
<h2 class="PollNode__heading">Question</h2>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 1"
type="text"
value="" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 2"
type="text"
value="" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__footer">
<button class="Button__root Button__small">Add Option</button>
</div>
</div>
</div>
</span>
<br />
</p>
`,
);
await page.keyboard.type('hello');
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph">
<span
contenteditable="false"
style="display: inline-block"
data-lexical-decorator="true">
<div class="PollNode__container">
<div class="PollNode__inner">
<h2 class="PollNode__heading">Question</h2>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 1"
type="text"
value="hello" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 2"
type="text"
value="" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__footer">
<button class="Button__root Button__small">Add Option</button>
</div>
</div>
</div>
</span>
<br />
</p>
`,
);
await page.keyboard.down('Shift');
await moveLeft(page, 4);
await page.keyboard.up('Shift');
await keyDownCtrlOrMeta(page);
await withExclusiveClipboardAccess(async () => {
// copy 'ello' once
await page.keyboard.press('c');
await keyUpCtrlOrMeta(page);
await keyDownCtrlOrMeta(page);
// paste it twice into the decorator's input
await page.keyboard.press('v');
await page.keyboard.press('v');
});
await keyUpCtrlOrMeta(page);
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph">
<span
contenteditable="false"
style="display: inline-block"
data-lexical-decorator="true">
<div class="PollNode__container">
<div class="PollNode__inner">
<h2 class="PollNode__heading">Question</h2>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 1"
type="text"
value="helloello" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__optionContainer">
<div class="PollNode__optionCheckboxWrapper">
<input class="PollNode__optionCheckbox" type="checkbox" />
</div>
<div class="PollNode__optionInputWrapper">
<div
class="PollNode__optionInputVotes"
style="width: 0%"></div>
<span class="PollNode__optionInputVotesCount"></span>
<input
class="PollNode__optionInput"
placeholder="Option 2"
type="text"
value="" />
</div>
<button
class="PollNode__optionDelete PollNode__optionDeleteDisabled"
disabled=""
aria-label="Remove"></button>
</div>
<div class="PollNode__footer">
<button class="Button__root Button__small">Add Option</button>
</div>
</div>
</div>
</span>
<br />
</p>
`,
);
});
});

View File

@ -896,6 +896,13 @@ function onInput(event: InputEvent, editor: LexicalEditor): void {
updateEditorSync(
editor,
() => {
if (
isHTMLElement(event.target) &&
$isSelectionCapturedInDecorator(event.target)
) {
return;
}
const selection = $getSelection();
const data = event.data;
const targetRange = getTargetRange(event);