/** * Universal, prefix-based `@`-mention autocomplete. * * A single mechanism that turns typed tokens like `@`, `@T`, `@S` into inserted * references such as `@T123456`, `@S98765`, or `@username`. Which list is shown * (and what gets inserted) is decided by a configurable set of {@link MentionSource}s, * each keyed by the characters that follow the `@`: * * { prefix: "T", search: (q) => api.findTests(q) } // @T -> fetch tests -> "@T123456" * { prefix: "S", search: (q) => api.findSuites(q) } // @S -> fetch suites -> "@S98765" * { prefix: "", items: users, insert: (u) => `@${u.label}` } // @ -> filter users -> "@john" * * This file is intentionally DOM-free: it holds only the pure logic (matching, * source resolution, filtering, token building) plus the global source registry. * The React wiring lives in `MentionAutocomplete.tsx`. */ export type MentionItem = { /** Stable identifier. For test/suite sources this is what gets inserted (`@T{id}`). */ id: string; /** Primary text shown in the popup and used for client-side filtering. */ label: string; /** Optional secondary text (e.g. `#123`, an email, a description). */ detail?: string | null; /** * Optional explicit text to insert for this specific item. When omitted the * source's `insert` builder (or the default `@{prefix}{id}`) is used. */ insertText?: string; /** Sources may carry arbitrary extra data through to their renderers. */ [key: string]: unknown; }; /** Static item list, or a (possibly async) provider of one. */ export type MentionItemsInput = MentionItem[] | (() => MentionItem[] | Promise); /** Result of an async `search`. May be raw items or a JSON:API document. */ export type MentionSearchResult = MentionItem[] | MentionJsonApiDocument | MentionJsonApiResource[] | null | undefined; export type MentionSource = { /** * Characters that must appear immediately after `@` to select this source. * Use `""` for the default/fallback source (e.g. usernames on a bare `@`). * Matching is case-sensitive and longest-prefix wins, so `@T` routes to the * `"T"` source rather than the `""` source. */ prefix: string; /** Human label for the popup header / grouping (e.g. "Tests", "Users"). */ label?: string; /** * Static list (or provider). Filtered client-side by the typed query. Use * this for data already in memory, like the list of project members. */ items?: MentionItemsInput; /** * Async fetcher invoked with the typed query (the text after the prefix). * Use this for server-backed lookups like tests (`@T`) and suites (`@S`). */ search?: (query: string) => Promise | MentionSearchResult; /** * Builds the text inserted when an item is chosen. Defaults to * `@{prefix}{id}` (so a test with id `123456` becomes `@T123456`). */ insert?: (item: MentionItem, source: MentionSource) => string; /** Minimum query length before `search` runs. Default 0 (fires on empty query). */ minChars?: number; /** Max items shown. Default 8. */ limit?: number; }; /** A currently-active mention detected at the caret. */ export type ActiveMention = { source: MentionSource; /** Matched prefix (e.g. "T", "S", or ""). */ prefix: string; /** Text typed after the prefix (what `search`/filter receives). */ query: string; /** Index of the `@` in the source text. */ start: number; /** Caret index (exclusive end of the token being edited). */ end: number; /** Full token text between `@` (exclusive) and the caret, i.e. `prefix + query`. */ token: string; }; export type MentionJsonApiAttributes = { title?: string | null; name?: string | null; label?: string | null; username?: string | null; email?: string | null; description?: string | null; detail?: string | null; [key: string]: unknown; }; export type MentionJsonApiResource = { id?: string | number | null; type?: string | null; attributes?: MentionJsonApiAttributes | null; }; export type MentionJsonApiDocument = { data?: MentionJsonApiResource[] | null; }; /** Register the mention sources used when a consumer doesn't pass `sources` explicitly. */ export declare function setMentionSources(sources: MentionSource[] | null): void; /** Read the globally-registered mention sources. */ export declare function getMentionSources(): MentionSource[]; /** * Pick the source whose `prefix` matches the start of `token`. Longest prefix * wins; the empty-prefix source is the ultimate fallback. Returns `null` when * nothing matches (e.g. `@T` typed but only a `""` source is registered and it * is absent). */ export declare function resolveMentionSource(token: string, sources: MentionSource[]): MentionSource | null; /** * Detect the mention being edited at `caret` within `text`. * * A mention is the run of non-whitespace characters after an `@` that sits at * the start of the text or immediately after whitespace (so emails like * `a@b.com` never trigger). Returns `null` when the caret is not inside such a * token or no source matches. */ export declare function parseActiveMention(text: string, caret: number, sources: MentionSource[]): ActiveMention | null; /** The text a chosen item inserts, honoring item/source overrides. */ export declare function buildMentionInsertText(source: MentionSource, item: MentionItem): string; export type MentionApplication = { /** The full text after replacing the active token with the inserted mention. */ text: string; /** Caret position after insertion (after the token and its trailing space). */ caret: number; }; /** * Replace the active mention token in `text` with `insertText`, adding a single * trailing space so the caret lands ready for the next word and the freshly * inserted `@…` token does not immediately re-trigger the popup. */ export declare function applyMention(text: string, active: Pick, insertText: string): MentionApplication; /** * Rank static items against a query: label starts-with beats label contains, * which beats id matches. Case-insensitive. An empty query returns the head of * the list unchanged. */ export declare function filterMentionItems(items: MentionItem[], query: string, limit?: number): MentionItem[]; /** * Route a typed query (everything after the trigger char, e.g. `T123` or `bob`) * to its source and resolve the items to show. Handles prefix stripping, * `minChars`, async `search`, static `items` (incl. provider functions), * normalization, and the `limit`. Returns `null` when no source matches. * * Shared by both mention frontends (the textarea hook and the BlockNote menu). */ export declare function resolveMentionQuery(query: string, sources: MentionSource[]): Promise<{ source: MentionSource; items: MentionItem[]; } | null>; export declare function parseMentionsFromJsonApi(document: MentionJsonApiDocument | MentionJsonApiResource[] | null | undefined): MentionItem[]; /** Coerce any supported `search`/`items` result into a clean `MentionItem[]`. */ export declare function normalizeMentionItems(result: MentionSearchResult): MentionItem[];