(frontend) preserve @ character when esc is pressed after typing it

improves user experience by keeping @ symbol after cancelling mention trigger

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-10-22 15:46:53 +02:00
parent 23a0f2761f
commit cdb26b480a
3 changed files with 60 additions and 44 deletions

View File

@@ -11,6 +11,7 @@ and this project adheres to
- ♿(frontend) improve accessibility:
- ♿(frontend) improve ARIA in doc grid and editor for a11y #1519
- 🐛(docx) fix image overflow by limiting width to 600px during export #1525
- 🐛(frontend) preserve @ character when esc is pressed after typing it #1512
## [3.9.0] - 2025-11-10

View File

@@ -806,6 +806,12 @@ test.describe('Doc Editor', () => {
});
await expect(interlinkChild1).toBeVisible({ timeout: 10000 });
await expect(interlinkChild1.locator('svg').first()).toBeVisible();
await page.keyboard.press('@');
await page.keyboard.press('Escape');
await expect(editor.getByText('@')).toBeVisible();
});
test('it checks multiple big doc scroll to the top', async ({

View File

@@ -3,6 +3,7 @@ import {
StyleSchema,
} from '@blocknote/core';
import { useBlockNoteEditor } from '@blocknote/react';
import type { KeyboardEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
@@ -99,6 +100,55 @@ export const SearchPage = ({
}, 100);
}, [inputRef]);
const closeSearch = (insertContent: string) => {
updateInlineContent({
type: 'interlinkingSearchInline',
props: {
disabled: true,
trigger,
},
});
contentRef(null);
editor.focus();
editor.insertInlineContent([insertContent]);
};
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape') {
e.preventDefault();
// Keep the trigger character ('@' or '/') in the editor when closing with Escape
closeSearch(trigger);
} else if (e.key === 'Backspace' && search.length === 0) {
e.preventDefault();
closeSearch('');
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
// Allow arrow keys to be handled by the command menu for navigation
const commandList = e.currentTarget
.closest('.inline-content')
?.nextElementSibling?.querySelector('[cmdk-list]');
// Create a synthetic keyboard event for the command menu
const syntheticEvent = new KeyboardEvent('keydown', {
key: e.key,
bubbles: true,
cancelable: true,
});
commandList?.dispatchEvent(syntheticEvent);
e.preventDefault();
} else if (e.key === 'Enter') {
// Handle Enter key to select the currently highlighted item
const selectedItem = e.currentTarget
.closest('.inline-content')
?.nextElementSibling?.querySelector(
'[cmdk-item][data-selected="true"]',
) as HTMLElement;
selectedItem?.click();
e.preventDefault();
}
};
return (
<Box as="span" $position="relative">
<Box
@@ -124,50 +174,7 @@ export const SearchPage = ({
const value = (e.target as HTMLInputElement).value;
setSearch(value);
}}
onKeyDown={(e) => {
if (
(e.key === 'Backspace' && search.length === 0) ||
e.key === 'Escape'
) {
e.preventDefault();
updateInlineContent({
type: 'interlinkingSearchInline',
props: {
disabled: true,
trigger,
},
});
contentRef(null);
editor.focus();
editor.insertInlineContent(['']);
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
// Allow arrow keys to be handled by the command menu for navigation
const commandList = e.currentTarget
.closest('.inline-content')
?.nextElementSibling?.querySelector('[cmdk-list]');
// Create a synthetic keyboard event for the command menu
const syntheticEvent = new KeyboardEvent('keydown', {
key: e.key,
bubbles: true,
cancelable: true,
});
commandList?.dispatchEvent(syntheticEvent);
e.preventDefault();
} else if (e.key === 'Enter') {
// Handle Enter key to select the currently highlighted item
const selectedItem = e.currentTarget
.closest('.inline-content')
?.nextElementSibling?.querySelector(
'[cmdk-item][data-selected="true"]',
) as HTMLElement;
selectedItem?.click();
e.preventDefault();
}
}}
onKeyDown={handleKeyDown}
/>
</Box>
<Box
@@ -224,6 +231,8 @@ export const SearchPage = ({
},
});
contentRef(null);
editor.insertInlineContent([
{
type: 'interlinkingLinkInline',