(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 accessibility:
- ♿(frontend) improve ARIA in doc grid and editor for a11y #1519 - ♿(frontend) improve ARIA in doc grid and editor for a11y #1519
- 🐛(docx) fix image overflow by limiting width to 600px during export #1525 - 🐛(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 ## [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).toBeVisible({ timeout: 10000 });
await expect(interlinkChild1.locator('svg').first()).toBeVisible(); 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 ({ test('it checks multiple big doc scroll to the top', async ({

View File

@@ -3,6 +3,7 @@ import {
StyleSchema, StyleSchema,
} from '@blocknote/core'; } from '@blocknote/core';
import { useBlockNoteEditor } from '@blocknote/react'; import { useBlockNoteEditor } from '@blocknote/react';
import type { KeyboardEvent } from 'react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { css } from 'styled-components'; import { css } from 'styled-components';
@@ -99,38 +100,7 @@ export const SearchPage = ({
}, 100); }, 100);
}, [inputRef]); }, [inputRef]);
return ( const closeSearch = (insertContent: string) => {
<Box as="span" $position="relative">
<Box
as="span"
className="inline-content"
$background={colorsTokens['greyscale-100']}
$color="var(--c--theme--colors--greyscale-700)"
$direction="row"
$radius="3px"
$padding="1px"
$display="inline-flex"
tabIndex={-1} // Ensure the span is focusable
>
{' '}
{trigger}
<Box
as="input"
$padding={{ left: '3px' }}
$css={inputStyle}
ref={inputRef}
$display="inline-flex"
onInput={(e) => {
const value = (e.target as HTMLInputElement).value;
setSearch(value);
}}
onKeyDown={(e) => {
if (
(e.key === 'Backspace' && search.length === 0) ||
e.key === 'Escape'
) {
e.preventDefault();
updateInlineContent({ updateInlineContent({
type: 'interlinkingSearchInline', type: 'interlinkingSearchInline',
props: { props: {
@@ -141,7 +111,17 @@ export const SearchPage = ({
contentRef(null); contentRef(null);
editor.focus(); editor.focus();
editor.insertInlineContent(['']); 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') { } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
// Allow arrow keys to be handled by the command menu for navigation // Allow arrow keys to be handled by the command menu for navigation
const commandList = e.currentTarget const commandList = e.currentTarget
@@ -167,7 +147,34 @@ export const SearchPage = ({
selectedItem?.click(); selectedItem?.click();
e.preventDefault(); e.preventDefault();
} }
};
return (
<Box as="span" $position="relative">
<Box
as="span"
className="inline-content"
$background={colorsTokens['greyscale-100']}
$color="var(--c--theme--colors--greyscale-700)"
$direction="row"
$radius="3px"
$padding="1px"
$display="inline-flex"
tabIndex={-1} // Ensure the span is focusable
>
{' '}
{trigger}
<Box
as="input"
$padding={{ left: '3px' }}
$css={inputStyle}
ref={inputRef}
$display="inline-flex"
onInput={(e) => {
const value = (e.target as HTMLInputElement).value;
setSearch(value);
}} }}
onKeyDown={handleKeyDown}
/> />
</Box> </Box>
<Box <Box
@@ -224,6 +231,8 @@ export const SearchPage = ({
}, },
}); });
contentRef(null);
editor.insertInlineContent([ editor.insertInlineContent([
{ {
type: 'interlinkingLinkInline', type: 'interlinkingLinkInline',