feat(transcription): dismiss + keyboard-operate the re-edit dropdown (#628 AC-4/AC-9)
Adds a visible × dismiss control to MentionDropdown (shared by the fresh-@ and re-edit paths) and, for the re-edit path which has no Tiptap suggestion plugin to forward keys, focuses the search input on open and handles its own keyboard: Escape dismisses (AC-4), Arrow/Enter reuse the exported selection logic so the dropdown is navigable on its own (AC-9 parity with the fresh-@ dropdown). Both close paths (Escape + ×) leave the mention node attrs + text byte-identical (AC-4) — close() never touches the document. Controller wires ondismiss=close (+refocus editor) and focusOnMount only for the re-edit open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -82,8 +82,10 @@ type LooseRenderProps = {
|
||||
// and (#628) the pencil re-edit affordance — so at most one dropdown is ever
|
||||
// mounted (the AC-6 single-dropdown invariant). open() closes any prior
|
||||
// dropdown first; render() is a thin adapter over open()/update()/close().
|
||||
type OpenOptions = { focusOnMount?: boolean };
|
||||
|
||||
type MentionController = {
|
||||
open: (clientRect: RectGetter, query: string, commit: CommitFn) => void;
|
||||
open: (clientRect: RectGetter, query: string, commit: CommitFn, opts?: OpenOptions) => void;
|
||||
update: (clientRect: RectGetter, query: string, commit: CommitFn) => void;
|
||||
close: () => void;
|
||||
onKeyDown: (event: KeyboardEvent) => boolean;
|
||||
@@ -155,7 +157,19 @@ function createMentionController(): MentionController {
|
||||
dropdownState.editorQuery = query.slice(0, MAX_QUERY_LENGTH);
|
||||
};
|
||||
|
||||
const open = (clientRect: RectGetter, query: string, commit: CommitFn) => {
|
||||
// Close the dropdown without touching the document and return focus to the
|
||||
// editor — wired to the dropdown's × control and the re-edit Escape path.
|
||||
const dismiss = () => {
|
||||
close();
|
||||
editor?.commands.focus();
|
||||
};
|
||||
|
||||
const open = (
|
||||
clientRect: RectGetter,
|
||||
query: string,
|
||||
commit: CommitFn,
|
||||
opts: OpenOptions = {}
|
||||
) => {
|
||||
// Single-dropdown invariant: tear down any open dropdown before mounting a
|
||||
// new one, and bump the request token so a previous open's in-flight fetch
|
||||
// cannot repopulate this dropdown.
|
||||
@@ -167,6 +181,8 @@ function createMentionController(): MentionController {
|
||||
target: document.body,
|
||||
props: {
|
||||
model: dropdownState,
|
||||
ondismiss: dismiss,
|
||||
focusOnMount: opts.focusOnMount ?? false,
|
||||
// MentionDropdown reads `editorQuery` off the shared state proxy via
|
||||
// this getter — Svelte 5's mount() does not expose settable prop
|
||||
// accessors, so we route through the proxy (same pattern as items).
|
||||
@@ -219,7 +235,7 @@ function commitRelink(pos: number): CommitFn {
|
||||
// the stored displayName.
|
||||
function requestRelink(getRect: () => DOMRect | null, displayName: string, pos: number) {
|
||||
if (!editor || !editor.isEditable) return;
|
||||
controller.open(getRect, displayName, commitRelink(pos));
|
||||
controller.open(getRect, displayName, commitRelink(pos), { focusOnMount: true });
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
Reference in New Issue
Block a user