From 6faebcabd3489e0cb3168abb1f868909e7765b23 Mon Sep 17 00:00:00 2001 From: Talha Mujahid Date: Wed, 10 Jun 2026 13:13:04 +0500 Subject: [PATCH] fix(editor): prevent backspace in icon picker search from deleting editor content (#15089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When the callout block's icon picker is open and the user types in the search input, pressing backspace deletes content in the main editor instead of the search text. ## Root Cause The callout icon picker is mounted via `createPopup` inside `editor-host`. `PageKeyboardManager` registers a global `Backspace` handler on the editor host (`keyboard-manager.ts`) with `{ global: true }`, which fires on every backspace keydown regardless of what element is focused. Without `stopPropagation`, the backspace event from the search input bubbles up through the DOM and triggers block deletion. Other keys are unaffected because the editor handles character input through `contenteditable` focus, those handlers only act when a contenteditable node is active. ## Fix Add `onKeyDown` with `e.stopPropagation()` to the search inputs in both `EmojiPicker` and `AffineIconPicker`. This matches the existing pattern already used by `MenuComponent` (`menu-renderer.ts:107`) and all other interactive components (`date-picker`, `inline-edit`, `prompt-modal`). ## Why not affected elsewhere `DocIconPicker` uses the same pickers but wraps them in a Radix UI `Menu` with `modal: true`, which portals outside `editor-host` — so backspace events never reach the editor's global handler there. ## Summary by CodeRabbit * **Bug Fixes** * Improved keyboard event handling in search inputs for icon and emoji pickers --- .../picker/affine-icon/affine-icon-picker.tsx | 16 +++++++++++++++- .../ui/icon-picker/picker/emoji/emoji-picker.tsx | 10 +++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/frontend/component/src/ui/icon-picker/picker/affine-icon/affine-icon-picker.tsx b/packages/frontend/component/src/ui/icon-picker/picker/affine-icon/affine-icon-picker.tsx index 6a315e1947..600448de88 100644 --- a/packages/frontend/component/src/ui/icon-picker/picker/affine-icon/affine-icon-picker.tsx +++ b/packages/frontend/component/src/ui/icon-picker/picker/affine-icon/affine-icon-picker.tsx @@ -1,7 +1,13 @@ import keywords from '@blocksuite/icons/keywords/en.json'; import * as allIcons from '@blocksuite/icons/rc'; import { cssVarV2 } from '@toeverything/theme/v2'; -import { startTransition, useCallback, useEffect, useState } from 'react'; +import { + type KeyboardEvent, + startTransition, + useCallback, + useEffect, + useState, +} from 'react'; import { IconButton } from '../../../button'; import Input from '../../../input'; @@ -89,6 +95,13 @@ export const AffineIconPicker = ({ [addRecentIcon, onSelect, color] ); + const handleSearchKeyDown = useCallback( + (e: KeyboardEvent) => { + e.stopPropagation(); + }, + [] + ); + return (
{/* Search */} @@ -96,6 +109,7 @@ export const AffineIconPicker = ({ diff --git a/packages/frontend/component/src/ui/icon-picker/picker/emoji/emoji-picker.tsx b/packages/frontend/component/src/ui/icon-picker/picker/emoji/emoji-picker.tsx index 5bde85b4f1..d9026d59df 100644 --- a/packages/frontend/component/src/ui/icon-picker/picker/emoji/emoji-picker.tsx +++ b/packages/frontend/component/src/ui/icon-picker/picker/emoji/emoji-picker.tsx @@ -1,6 +1,6 @@ import { SearchIcon } from '@blocksuite/icons/rc'; import { cssVarV2 } from '@toeverything/theme/v2'; -import { useCallback, useState } from 'react'; +import { type KeyboardEvent, useCallback, useState } from 'react'; import { IconButton } from '../../../button'; import Input from '../../../input'; @@ -38,12 +38,20 @@ export const EmojiPicker = ({ [addRecent, onSelect] ); + const handleSearchKeyDown = useCallback( + (e: KeyboardEvent) => { + e.stopPropagation(); + }, + [] + ); + return (