From 780c35eabebafcc1c4cdd939bb90b532880a51bd Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Tue, 8 Apr 2025 10:03:06 +0000 Subject: [PATCH] fix(editor): prevent Tab key propagation outside editor (#11531) Closes: BS-2964 --- .../blocks/root/src/common-specs/index.ts | 2 + .../affine/blocks/root/src/keyboard/keymap.ts | 16 ++++++++ .../affine-local/e2e/blocksuite/list.spec.ts | 39 +++++++++++++++++++ tests/affine-local/e2e/links.spec.ts | 17 +------- tests/kit/src/bs/linked-toolbar.ts | 17 ++++++++ 5 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 blocksuite/affine/blocks/root/src/keyboard/keymap.ts create mode 100644 tests/kit/src/bs/linked-toolbar.ts diff --git a/blocksuite/affine/blocks/root/src/common-specs/index.ts b/blocksuite/affine/blocks/root/src/common-specs/index.ts index 6aaa3dca1d..5b807ca122 100644 --- a/blocksuite/affine/blocks/root/src/common-specs/index.ts +++ b/blocksuite/affine/blocks/root/src/common-specs/index.ts @@ -42,6 +42,7 @@ import type { ExtensionType } from '@blocksuite/store'; import { RootBlockAdapterExtensions } from '../adapters/extension'; import { clipboardConfigs } from '../clipboard'; import { builtinToolbarConfig } from '../configs/toolbar'; +import { fallbackKeymap } from '../keyboard/keymap'; import { innerModalWidget, linkedDocWidget, @@ -94,6 +95,7 @@ export const CommonSpecs: ExtensionType[] = [ viewportOverlayWidget, scrollAnchoringWidget, toolbarWidget, + fallbackKeymap, ToolbarModuleExtension({ id: BlockFlavourIdentifier(NoteBlockSchema.model.flavour), diff --git a/blocksuite/affine/blocks/root/src/keyboard/keymap.ts b/blocksuite/affine/blocks/root/src/keyboard/keymap.ts new file mode 100644 index 0000000000..6acb3d27db --- /dev/null +++ b/blocksuite/affine/blocks/root/src/keyboard/keymap.ts @@ -0,0 +1,16 @@ +import { KeymapExtension } from '@blocksuite/std'; + +export const fallbackKeymap = KeymapExtension(() => { + return { + Tab: ctx => { + const event = ctx.get('defaultState').event; + event.stopPropagation(); + event.preventDefault(); + }, + 'Shift-Tab': ctx => { + const event = ctx.get('defaultState').event; + event.stopPropagation(); + event.preventDefault(); + }, + }; +}); diff --git a/tests/affine-local/e2e/blocksuite/list.spec.ts b/tests/affine-local/e2e/blocksuite/list.spec.ts index d639a6dc2f..5ba70a6646 100644 --- a/tests/affine-local/e2e/blocksuite/list.spec.ts +++ b/tests/affine-local/e2e/blocksuite/list.spec.ts @@ -1,3 +1,4 @@ +import { toolbarButtons } from '@affine-test/kit/bs/linked-toolbar'; import { test } from '@affine-test/kit/playwright'; import { pressArrowUp, @@ -7,6 +8,7 @@ import { import { openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, + createLinkedPage, type, waitForEditorLoad, } from '@affine-test/kit/utils/page-logic'; @@ -77,4 +79,41 @@ test.describe('split list', () => { listLocator.nth(4).locator('.affine-list-block__numbered') ).toHaveText('4.'); }); + + test('tab in list should not propagate out of editor', async ({ page }) => { + await pressEnter(page); + await type(page, '1. aaa'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Tab'); + await createLinkedPage(page, 'Test Page'); + const inlineLink = page.locator('affine-reference'); + const { switchViewBtn, cardViewBtn } = toolbarButtons(page); + const list = page.locator('affine-list'); + const card = page.locator('affine-embed-linked-doc-block'); + + const pressWithCount = async (key: string, count: number) => { + for (let i = 0; i < count; i++) { + await new Promise(resolve => setTimeout(resolve, 5)); + await page.keyboard.press(key); + } + }; + + await inlineLink.hover(); + await switchViewBtn.click(); + await cardViewBtn.click(); + + await expect(list.filter({ has: card })).toHaveCount(1); + + await page.keyboard.press('Shift+Tab'); + + await expect(list.filter({ hasNot: card })).toHaveCount(1); + + await pressWithCount('Tab', 5); + + await expect(list.filter({ has: card })).toHaveCount(1); + + await pressWithCount('Shift+Tab', 5); + + await expect(list.filter({ hasNot: card })).toHaveCount(1); + }); }); diff --git a/tests/affine-local/e2e/links.spec.ts b/tests/affine-local/e2e/links.spec.ts index 6483dc7101..ad297ffa62 100644 --- a/tests/affine-local/e2e/links.spec.ts +++ b/tests/affine-local/e2e/links.spec.ts @@ -1,3 +1,4 @@ +import { toolbarButtons } from '@affine-test/kit/bs/linked-toolbar'; import { waitNextFrame } from '@affine-test/kit/bs/misc'; import { test } from '@affine-test/kit/playwright'; import { clickEdgelessModeButton } from '@affine-test/kit/utils/editor'; @@ -27,22 +28,6 @@ test.beforeEach(async ({ page }) => { await waitForEmptyEditor(page); }); -function toolbarButtons(page: Page) { - const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); - const switchViewBtn = toolbar.getByLabel('Switch view'); - const inlineViewBtn = toolbar.getByLabel('Inline view'); - const cardViewBtn = toolbar.getByLabel('Card view'); - const embedViewBtn = toolbar.getByLabel('Embed view'); - - return { - toolbar, - switchViewBtn, - inlineViewBtn, - cardViewBtn, - embedViewBtn, - }; -} - async function enableEmojiDocIcon(page: Page) { // Opens settings panel await openEditorSetting(page); diff --git a/tests/kit/src/bs/linked-toolbar.ts b/tests/kit/src/bs/linked-toolbar.ts new file mode 100644 index 0000000000..7a057df6a8 --- /dev/null +++ b/tests/kit/src/bs/linked-toolbar.ts @@ -0,0 +1,17 @@ +import type { Page } from '@playwright/test'; + +export function toolbarButtons(page: Page) { + const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); + const switchViewBtn = toolbar.getByLabel('Switch view'); + const inlineViewBtn = toolbar.getByLabel('Inline view'); + const cardViewBtn = toolbar.getByLabel('Card view'); + const embedViewBtn = toolbar.getByLabel('Embed view'); + + return { + toolbar, + switchViewBtn, + inlineViewBtn, + cardViewBtn, + embedViewBtn, + }; +}