diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
index 419b90fc364..55087f0a50d 100644
--- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
@@ -12,6 +12,7 @@ import {
moveLeft,
moveRight,
moveToEditorBeginning,
+ moveUp,
pressBackspace,
selectAll,
} from '../keyboardShortcuts/index.mjs';
@@ -44,6 +45,7 @@ import {
test,
toggleColumnHeader,
unmergeTableCell,
+ waitForSelector,
} from '../utils/index.mjs';
async function fillTablePartiallyWithText(page) {
@@ -486,52 +488,171 @@ test.describe('Tables', () => {
});
});
- test(`Can navigate table with keyboard`, async ({
- page,
- isPlainText,
- isCollab,
- }) => {
- await initialize({isCollab, page});
- test.skip(isPlainText);
+ test.describe(`Can navigate table with keyboard`, () => {
+ test(`Can navigate cells horizontally`, async ({
+ page,
+ isPlainText,
+ isCollab,
+ }) => {
+ await initialize({isCollab, page});
+ test.skip(isPlainText);
- await focusEditor(page);
- await insertTable(page, 2, 3);
+ await focusEditor(page);
+ await insertTable(page, 2, 2);
- await fillTablePartiallyWithText(page);
+ await assertHTML(
+ page,
+ html`
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+
+ `,
+ undefined,
+ {ignoreClasses: true},
+ );
- await assertHTML(
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 0, 0],
+ });
+
+ await moveRight(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 1, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 1, 0],
+ });
+
+ await moveRight(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 1, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 1, 0, 0],
+ });
+
+ await moveRight(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 1, 1, 0],
+ focusOffset: 0,
+ focusPath: [1, 1, 1, 0],
+ });
+
+ await moveLeft(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 1, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 1, 0, 0],
+ });
+
+ await moveLeft(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 1, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 1, 0],
+ });
+
+ await moveLeft(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 0, 0],
+ });
+ });
+
+ test(`Can navigate cells vertically`, async ({
page,
- html`
-
-
-
-
- a
- |
-
- bb
- |
-
- cc
- |
-
-
-
- d
- |
-
- e
- |
-
- f
- |
-
-
-
- `,
- undefined,
- {ignoreClasses: true},
- );
+ isPlainText,
+ isCollab,
+ }) => {
+ await initialize({isCollab, page});
+ test.skip(isPlainText);
+
+ await focusEditor(page);
+ await insertTable(page, 2, 2);
+
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 0, 0],
+ });
+
+ await moveDown(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 1, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 1, 0, 0],
+ });
+
+ await moveUp(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [1, 0, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 0, 0],
+ });
+ });
+
+ test('Should not navigate cells when typeahead menu is open and focused', async ({
+ page,
+ isCollab,
+ isPlainText,
+ }) => {
+ await initialize({isCollab, page});
+ test.skip(isPlainText);
+
+ await focusEditor(page);
+ await insertTable(page, 2, 2);
+
+ await page.keyboard.type('@A');
+ await assertSelection(page, {
+ anchorOffset: 2,
+ anchorPath: [1, 0, 0, 0, 0, 0],
+ focusOffset: 2,
+ focusPath: [1, 0, 0, 0, 0, 0],
+ });
+
+ await waitForSelector(page, `#typeahead-menu ul li:first-child.selected`);
+
+ await moveDown(page, 1);
+ await assertSelection(page, {
+ anchorOffset: 2,
+ anchorPath: [1, 0, 0, 0, 0, 0],
+ focusOffset: 2,
+ focusPath: [1, 0, 0, 0, 0, 0],
+ });
+
+ await waitForSelector(
+ page,
+ '#typeahead-menu ul li:nth-child(2).selected',
+ );
+ });
});
test(`Can select cells using Table selection`, async ({
diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
index ed443fc3f17..e45c366faa3 100644
--- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
+++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
@@ -707,9 +707,7 @@ export function applyTableHandlers(
if (isFocusInside) {
newSelection.focus.set(
tableNode.getParentOrThrow().getKey(),
- isBackward
- ? tableNode.getIndexWithinParent()
- : tableNode.getIndexWithinParent() + 1,
+ tableNode.getIndexWithinParent(),
'element',
);
} else {
@@ -1265,6 +1263,13 @@ function $handleArrowKey(
tableNode: TableNode,
tableObserver: TableObserver,
): boolean {
+ if (
+ (direction === 'up' || direction === 'down') &&
+ isTypeaheadMenuInView(editor)
+ ) {
+ return false;
+ }
+
const selection = $getSelection();
if (!$isSelectionInTable(selection, tableNode)) {
@@ -1481,6 +1486,19 @@ function stopEvent(event: Event) {
event.stopPropagation();
}
+function isTypeaheadMenuInView(editor: LexicalEditor) {
+ // There is no inbuilt way to check if the component picker is in view
+ // but we can check if the root DOM element has the aria-controls attribute "typeahead-menu".
+ const root = editor.getRootElement();
+ if (!root) {
+ return false;
+ }
+ return (
+ root.hasAttribute('aria-controls') &&
+ root.getAttribute('aria-controls') === 'typeahead-menu'
+ );
+}
+
function isExitingTableAnchor(
type: string,
offset: number,
diff --git a/packages/lexical/src/LexicalSelection.ts b/packages/lexical/src/LexicalSelection.ts
index 520541f8e9b..959c9d4ac4a 100644
--- a/packages/lexical/src/LexicalSelection.ts
+++ b/packages/lexical/src/LexicalSelection.ts
@@ -1940,13 +1940,13 @@ function internalResolveSelectionPoint(
: child.getFirstDescendant();
if (descendant === null) {
resolvedElement = child;
- resolvedOffset = 0;
} else {
child = descendant;
resolvedElement = $isElementNode(child)
? child
: child.getParentOrThrow();
}
+ resolvedOffset = 0;
}
if ($isTextNode(child)) {
resolvedNode = child;