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;