BangSo/packages/core/src/composer-mention-picker.ts

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" };
}