Skip to content
Source
Sign inSign up
Open History to see the last commit

composerTypingGuard.ts

58 lines1.5 KB

1export const COMPOSER_TYPING_IDLE_MS = 1500;
2​
3export function createComposerTypingGuardState() {
4 return {
5 seen: new Set<string>(),
6 held: new Set<string>(),
7 focused: false,
8 lastTypedAt: -Infinity,
9 };
11​
12type TypingGuardEvent =
13 | { type: "requests"; requestIds: readonly string[]; now: number }
14 | { type: "type"; now: number }
15 | { type: "focus" | "blur" | "send" | "idle" };
16​
17export function updateComposerTypingGuard(
18 state: ReturnType<typeof createComposerTypingGuardState>,
19 event: TypingGuardEvent,
20) {
21 switch (event.type) {
22 case "requests": {
23 const seen = new Set(state.seen);
24 const held = new Set([...state.held].filter((id) => event.requestIds.includes(id)));
25 for (const id of event.requestIds) {
26 if (
27 !seen.has(id) &&
28 state.focused &&
29 event.now - state.lastTypedAt < COMPOSER_TYPING_IDLE_MS
30 ) {
31 held.add(id);
32 }
33 seen.add(id);
34 }
35 return {
36 ...state,
37 seen,
38 held:
39 held.size === state.held.size && [...held].every((id) => state.held.has(id))
40 ? state.held
41 : held,
42 };
43 }
44 case "type":
45 return { ...state, lastTypedAt: event.now };
46 case "focus":
47 return { ...state, focused: true };
48 case "blur":
49 case "send":
50 case "idle":
51 return {
52 ...state,
53 held: state.held.size === 0 ? state.held : new Set<string>(),
54 lastTypedAt: -Infinity,
55 focused: event.type === "blur" ? false : state.focused,
56 };
57 }