mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
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:
+15
-1
@@ -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 }}>
|
||||
|
||||
Reference in New Issue
Block a user