From 883fcdb4ce4e1a7f7d0f56096c80d9f03b2284b2 Mon Sep 17 00:00:00 2001 From: egoing Date: Wed, 28 May 2025 17:53:23 +0900 Subject: [PATCH] fix: resolve content disappearing issue when pressing Enter after title formatting in Android WebView - Add isMenuVisible parameter to SuggestionMenu keyboard handlers - Prevent keyboard event processing when menu is not visible - Strengthen validation for menu state and DOM existence during Enter key handling - Apply consistent logic to both GridSuggestionMenu and regular SuggestionMenu - Add selected index validity checks This change prevents the unintended activation of suggestion menu keyboard handlers when pressing Enter after applying title or other formatting in Android WebView environments, which was causing content to disappear. By ensuring keyboard events are only processed when the menu is actually displayed and exists in the DOM, this improves the stability of user input handling. Testing approach: 1. Open BlockNote editor in Android WebView 2. Input text and apply title formatting (H1, H2, etc.) 3. Press Enter while formatting is applied 4. Verify that existing content doesn't disappear and a new line is created 5. Confirm that Enter key selection works properly when suggestion menu is displayed 6. Perform regression testing across various browser environments --- .../GridSuggestionMenuWrapper.tsx | 1 + ...useGridSuggestionMenuKeyboardNavigation.ts | 25 ++++++++++++++++--- .../SuggestionMenu/SuggestionMenuWrapper.tsx | 1 + .../hooks/useSuggestionMenuKeyboardHandler.ts | 25 ++++++++++++++++--- .../useSuggestionMenuKeyboardNavigation.ts | 10 ++++---- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx index 173b36c4ea..69d1f42d80 100644 --- a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx +++ b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuWrapper.tsx @@ -57,6 +57,7 @@ export function GridSuggestionMenuWrapper(props: { items, columns, onItemClickCloseMenu, + items.length > 0 ); // set basic aria attributes when the menu is open diff --git a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts index 9f435158a1..a246edc2c4 100644 --- a/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts +++ b/packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts @@ -9,6 +9,7 @@ export function useGridSuggestionMenuKeyboardNavigation( items: Item[], columns: number, onItemClick?: (item: Item) => void, + isMenuVisible?: boolean, ) { const [selectedIndex, setSelectedIndex] = useState(0); @@ -16,6 +17,10 @@ export function useGridSuggestionMenuKeyboardNavigation( useEffect(() => { const handleMenuNavigationKeys = (event: KeyboardEvent) => { + if (isMenuVisible === false) { + return false; + } + if (event.key === "ArrowLeft") { event.preventDefault(); if (items.length) { @@ -53,12 +58,24 @@ export function useGridSuggestionMenuKeyboardNavigation( } if (event.key === "Enter" && !event.isComposing) { + + if (!items.length || selectedIndex < 0 || selectedIndex >= items.length) { + return false; + } + + const suggestionMenuElement = document.querySelector('#bn-grid-suggestion-menu, .bn-grid-suggestion-menu'); + const isMenuInDOM = !!suggestionMenuElement; + + if (!isMenuVisible || !isMenuInDOM) { + return false; + } + event.stopPropagation(); event.preventDefault(); - if (items.length) { - onItemClick?.(items[selectedIndex]); - } + if (onItemClick) { + onItemClick(items[selectedIndex]); + } return true; } @@ -79,7 +96,7 @@ export function useGridSuggestionMenuKeyboardNavigation( true, ); }; - }, [editor.domElement, items, selectedIndex, onItemClick, columns, isGrid]); + }, [editor.domElement, items, selectedIndex, onItemClick, columns, isGrid, isMenuVisible]); // Resets index when items change useEffect(() => { diff --git a/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx b/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx index 391bcb1b34..5b36fb2f95 100644 --- a/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx +++ b/packages/react/src/components/SuggestionMenu/SuggestionMenuWrapper.tsx @@ -54,6 +54,7 @@ export function SuggestionMenuWrapper(props: { query, items, onItemClickCloseMenu, + items.length > 0 ); // set basic aria attributes when the menu is open diff --git a/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardHandler.ts b/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardHandler.ts index 8d367ffdcf..76537d60b4 100644 --- a/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardHandler.ts +++ b/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardHandler.ts @@ -4,7 +4,8 @@ import React, { useState } from "react"; // & down arrow keys are used to select an item, enter is used to execute it. export function useSuggestionMenuKeyboardHandler( items: Item[], - onItemClick?: (item: Item) => void + onItemClick?: (item: Item) => void, + isMenuVisible?: boolean | undefined ) { const [selectedIndex, setSelectedIndex] = useState(0); @@ -12,6 +13,10 @@ export function useSuggestionMenuKeyboardHandler( selectedIndex, setSelectedIndex, handler: (event: KeyboardEvent | React.KeyboardEvent) => { + if (isMenuVisible === false) { + return false; + } + if (event.key === "ArrowUp") { event.preventDefault(); @@ -37,12 +42,24 @@ export function useSuggestionMenuKeyboardHandler( ? event.nativeEvent.isComposing : event.isComposing; if (event.key === "Enter" && !isComposing) { + + if (!items.length || selectedIndex < 0 || selectedIndex >= items.length) { + return false; + } + + const suggestionMenuElement = document.querySelector('#bn-suggestion-menu, .bn-suggestion-menu, #ai-suggestion-menu'); + const isMenuInDOM = !!suggestionMenuElement; + + if (!isMenuVisible || !isMenuInDOM) { + return false; + } + event.preventDefault(); event.stopPropagation(); - if (items.length) { - onItemClick?.(items[selectedIndex]); - } + if (onItemClick) { + onItemClick(items[selectedIndex]); + } return true; } diff --git a/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts b/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts index 99c72a8b6a..8c2b837db7 100644 --- a/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts +++ b/packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts @@ -9,22 +9,22 @@ export function useSuggestionMenuKeyboardNavigation( query: string, items: Item[], onItemClick?: (item: Item) => void, - element?: HTMLElement, + isMenuVisible?: boolean, ) { const { selectedIndex, setSelectedIndex, handler } = - useSuggestionMenuKeyboardHandler(items, onItemClick); + useSuggestionMenuKeyboardHandler(items, onItemClick, isMenuVisible); useEffect(() => { - (element || editor.domElement)?.addEventListener("keydown", handler, true); + editor.domElement?.addEventListener("keydown", handler, true); return () => { - (element || editor.domElement)?.removeEventListener( + editor.domElement?.removeEventListener( "keydown", handler, true, ); }; - }, [editor.domElement, items, selectedIndex, onItemClick, element, handler]); + }, [editor.domElement, items, selectedIndex, onItemClick, handler]); // Resets index when items change useEffect(() => {