65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
/** Keyboard actions for the composer `@` mention picker. */
|
|
|
|
export type MentionPickerKeyAction =
|
|
| { type: "complete"; index: number }
|
|
| { type: "move"; index: number }
|
|
| { type: "dismiss" }
|
|
| { type: "send" }
|
|
| { type: "none" };
|
|
|
|
/** Wrap highlight within `[0, count)`. Returns `0` when the list is empty. */
|
|
export function wrapMentionHighlightIndex(index: number, count: number): number {
|
|
if (count <= 0) return 0;
|
|
return ((index % count) + count) % count;
|
|
}
|
|
|
|
/** Keep a stored highlight valid after the option list shrinks or grows. */
|
|
export function clampMentionHighlightIndex(index: number, count: number): number {
|
|
if (count <= 0) return 0;
|
|
if (index < 0) return 0;
|
|
if (index >= count) return count - 1;
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Resolve composer key handling while (or after) the mention picker is open.
|
|
* When `optionCount` is 0 the picker is closed or empty: Enter sends (unless Shift).
|
|
* While IME composition is active, returns `none` so Enter confirms text instead.
|
|
*/
|
|
export function resolveMentionPickerKey(input: {
|
|
key: string;
|
|
shiftKey?: boolean;
|
|
isComposing?: boolean;
|
|
optionCount: number;
|
|
highlightedIndex: number;
|
|
}): MentionPickerKeyAction {
|
|
if (input.isComposing) return { type: "none" };
|
|
|
|
const { key, shiftKey = false, optionCount } = input;
|
|
const highlightedIndex = clampMentionHighlightIndex(input.highlightedIndex, optionCount);
|
|
|
|
if (optionCount > 0) {
|
|
if (key === "ArrowDown") {
|
|
return { type: "move", index: wrapMentionHighlightIndex(highlightedIndex + 1, optionCount) };
|
|
}
|
|
if (key === "ArrowUp") {
|
|
return { type: "move", index: wrapMentionHighlightIndex(highlightedIndex - 1, optionCount) };
|
|
}
|
|
if (key === "Enter" && !shiftKey) {
|
|
return { type: "complete", index: highlightedIndex };
|
|
}
|
|
if (key === "Tab") {
|
|
return { type: "complete", index: highlightedIndex };
|
|
}
|
|
if (key === "Escape") {
|
|
return { type: "dismiss" };
|
|
}
|
|
return { type: "none" };
|
|
}
|
|
|
|
if (key === "Enter" && !shiftKey) {
|
|
return { type: "send" };
|
|
}
|
|
return { type: "none" };
|
|
}
|