[lexical-table] Bug Fix: Click and drag table selection in Firefox (#7283)

This commit is contained in:
Bob Ippolito
2025-03-03 13:50:47 -08:00
committed by GitHub
parent b4d8797047
commit 4a032bd5d2
5 changed files with 446 additions and 22 deletions

View File

@ -606,7 +606,10 @@ test.describe('Images', () => {
page,
'span[data-lexical-text="true"]',
);
await dragMouse(page, textBoundingBox, textBoundingBox, 'start', 'middle');
await dragMouse(page, textBoundingBox, textBoundingBox, {
positionEnd: 'middle',
positionStart: 'start',
});
const lexicalSelection = await evaluate(page, (editor) => {
return window.lexicalEditor._editorState._selection;

View File

@ -25,6 +25,7 @@ import {
copyToClipboard,
deleteTableColumns,
deleteTableRows,
dragMouse,
expect,
focusEditor,
html,
@ -5691,4 +5692,396 @@ test.describe.parallel('Tables', () => {
{ignoreClasses: true},
);
});
test(`Click and drag to create selection in Firefox #7245`, async ({
page,
isPlainText,
isCollab,
}) => {
test.skip(isPlainText || isCollab);
await initialize({isCollab, page});
await focusEditor(page);
// Insert a table
await insertTable(page, 5, 5);
// Initial conditions
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
<table class="PlaygroundEditorTheme__table">
<colgroup>
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
</colgroup>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
</table>
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
`,
);
// Click and drag to select a 3x3 box (leaving the mouse down)
await dragMouse(
page,
await selectorBoundingBox(
page,
'table > tr:nth-of-type(2) > *:nth-child(2)',
),
await selectorBoundingBox(
page,
'table > tr:nth-of-type(4) > *:nth-child(4)',
),
{mouseDown: true, mouseUp: false, slow: true},
);
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
<table
class="PlaygroundEditorTheme__table PlaygroundEditorTheme__tableSelection">
<colgroup>
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
</colgroup>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
</table>
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
`,
);
// Drag to change the selection to a 4x2 box (releasing the mouse)
await dragMouse(
page,
await selectorBoundingBox(
page,
'table > tr:nth-of-type(4) > *:nth-child(4)',
),
await selectorBoundingBox(
page,
'table > tr:nth-of-type(3) > *:nth-child(5)',
),
{mouseDown: false, mouseUp: true, slow: true},
);
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
<table
class="PlaygroundEditorTheme__table PlaygroundEditorTheme__tableSelection">
<colgroup>
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
<col style="width: 92px;" />
</colgroup>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellSelected">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
<tr>
<th
class="PlaygroundEditorTheme__tableCell PlaygroundEditorTheme__tableCellHeader">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
</td>
</tr>
</table>
<p class="PlaygroundEditorTheme__paragraph"><br /></p>
`,
);
});
});

View File

@ -57,8 +57,7 @@ test.describe('Regression test #5251', () => {
page,
await selectorBoundingBox(page, 'li:has-text("one")'),
await selectorBoundingBox(page, 'li:has-text("three")'),
'middle',
'end',
{positionEnd: 'end', positionStart: 'middle'},
);
});
});

View File

@ -756,11 +756,15 @@ export async function dragMouse(
page,
fromBoundingBox,
toBoundingBox,
opts = {},
) {
const {
positionStart = 'middle',
positionEnd = 'middle',
mouseDown = true,
mouseUp = true,
slow = false,
) {
} = opts;
let fromX = fromBoundingBox.x;
let fromY = fromBoundingBox.y;
if (positionStart === 'middle') {
@ -781,7 +785,9 @@ export async function dragMouse(
}
await page.mouse.move(fromX, fromY);
if (mouseDown) {
await page.mouse.down();
}
await page.mouse.move(toX, toY, slow ? 10 : 1);
if (mouseUp) {
await page.mouse.up();
@ -798,8 +804,7 @@ export async function dragImage(
page,
await selectorBoundingBox(page, '.editor-image img'),
await selectorBoundingBox(page, toSelector),
positionStart,
positionEnd,
{positionEnd, positionStart},
);
}
@ -941,7 +946,7 @@ export async function selectCellsFromTableCords(
// const firstBox = await firstRowFirstColumnCell.boundingBox();
// const secondBox = await secondRowSecondCell.boundingBox();
// await dragMouse(page, firstBox, secondBox, 'middle', 'middle', true, true);
// await dragMouse(page, firstBox, secondBox, {slow: true});
}
export async function clickTableCellActiveButton(page) {
@ -1059,8 +1064,7 @@ export async function dragDraggableMenuTo(
page,
await selectorBoundingBox(page, '.draggable-block-menu'),
await selectorBoundingBox(page, toSelector),
positionStart,
positionEnd,
{positionEnd, positionStart},
);
}

View File

@ -198,27 +198,27 @@ export function applyTableHandlers(
};
const onMouseMove = (moveEvent: MouseEvent) => {
if (!isDOMNode(moveEvent.target)) {
return;
}
if (!isMouseDownOnEvent(moveEvent) && tableObserver.isSelecting) {
tableObserver.isSelecting = false;
editorWindow.removeEventListener('mouseup', onMouseUp);
editorWindow.removeEventListener('mousemove', onMouseMove);
return;
}
const override = !tableElement.contains(moveEvent.target);
if (!isDOMNode(moveEvent.target)) {
return;
}
let focusCell: null | TableDOMCell = null;
if (!override) {
focusCell = getDOMCellFromTarget(moveEvent.target);
// In firefox the moveEvent.target may be captured so we must always
// consult the coordinates #7245
const override = !(IS_FIREFOX || tableElement.contains(moveEvent.target));
if (override) {
focusCell = getDOMCellInTableFromTarget(tableElement, moveEvent.target);
} else {
for (const el of document.elementsFromPoint(
moveEvent.clientX,
moveEvent.clientY,
)) {
focusCell = tableElement.contains(el)
? getDOMCellFromTarget(el)
: null;
focusCell = getDOMCellInTableFromTarget(tableElement, el);
if (focusCell) {
break;
}
@ -1168,6 +1168,31 @@ export function getDOMCellFromTarget(node: null | Node): TableDOMCell | null {
return null;
}
export function getDOMCellInTableFromTarget(
table: HTMLTableElementWithWithTableSelectionState,
node: null | Node,
): TableDOMCell | null {
if (!table.contains(node)) {
return null;
}
let cell: null | TableDOMCell = null;
for (
let currentNode: ParentNode | Node | null = node;
currentNode != null;
currentNode = currentNode.parentNode
) {
if (currentNode === table) {
return cell;
}
const nodeName = currentNode.nodeName;
if (nodeName === 'TD' || nodeName === 'TH') {
// @ts-expect-error: internal field
cell = currentNode._cell || null;
}
}
return null;
}
export function doesTargetContainText(node: Node): boolean {
const currentNode: ParentNode | Node | null = node;