fix(editor): prevent backspace in icon picker search from deleting editor content (#15089)

## 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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved keyboard event handling in search inputs for icon and emoji
pickers

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Talha Mujahid
2026-06-10 13:13:04 +05:00
committed by GitHub
parent d10dd12663
commit 6faebcabd3
2 changed files with 24 additions and 2 deletions
@@ -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<HTMLInputElement>) => {
e.stopPropagation();
},
[]
);
return (
<div className={pickerStyles.root}>
{/* Search */}
@@ -96,6 +109,7 @@ export const AffineIconPicker = ({
<Input
value={keyword}
onChange={setKeyword}
onKeyDown={handleSearchKeyDown}
className={pickerStyles.searchInput}
preFix={
<div style={{ marginLeft: 10, lineHeight: 0 }}>
@@ -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<HTMLInputElement>) => {
e.stopPropagation();
},
[]
);
return (
<div className={pickerStyles.root}>
<header className={pickerStyles.searchContainer}>
<Input
value={keyword}
onChange={setKeyword}
onKeyDown={handleSearchKeyDown}
className={pickerStyles.searchInput}
preFix={
<div style={{ marginLeft: 10, lineHeight: 0 }}>