diff --git a/packages/lexical-playground/__tests__/e2e/Images.spec.mjs b/packages/lexical-playground/__tests__/e2e/Images.spec.mjs
index 902d06d73..b99dc139f 100644
--- a/packages/lexical-playground/__tests__/e2e/Images.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/Images.spec.mjs
@@ -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;
diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
index 2cdedd5b0..7fd4e69c7 100644
--- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
@@ -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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+
+ // 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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+ // 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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ );
+ });
});
diff --git a/packages/lexical-playground/__tests__/regression/5583-select-list-followed-by-element-node.spec.mjs b/packages/lexical-playground/__tests__/regression/5583-select-list-followed-by-element-node.spec.mjs
index 3ccc3bfef..77113d3df 100644
--- a/packages/lexical-playground/__tests__/regression/5583-select-list-followed-by-element-node.spec.mjs
+++ b/packages/lexical-playground/__tests__/regression/5583-select-list-followed-by-element-node.spec.mjs
@@ -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'},
);
});
});
diff --git a/packages/lexical-playground/__tests__/utils/index.mjs b/packages/lexical-playground/__tests__/utils/index.mjs
index befc5bd2f..deb209bfd 100644
--- a/packages/lexical-playground/__tests__/utils/index.mjs
+++ b/packages/lexical-playground/__tests__/utils/index.mjs
@@ -756,11 +756,15 @@ export async function dragMouse(
page,
fromBoundingBox,
toBoundingBox,
- positionStart = 'middle',
- positionEnd = 'middle',
- mouseUp = true,
- slow = false,
+ 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);
- await page.mouse.down();
+ 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},
);
}
diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
index 92ff32aaf..41fc050d9 100644
--- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
+++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
@@ -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;