1import { useCallback, useEffect, useState } from "react";
2import {
3 COMPOSER_TYPING_IDLE_MS,
4 createComposerTypingGuardState,
5 updateComposerTypingGuard,
6} from "./composerTypingGuard";
7
8export function useComposerTypingGuard(scope: string, requestIds: readonly string[]) {
9 const requestsKey = JSON.stringify(requestIds);
10 const [snapshot, setSnapshot] = useState(() => ({
11 scope,
12 requestsKey,
13 state: updateComposerTypingGuard(createComposerTypingGuardState(), {
14 type: "requests",
15 requestIds,
16 now: Date.now(),
17 }),
18 }));
19 const state = snapshot.state;
20 if (snapshot.scope !== scope || snapshot.requestsKey !== requestsKey) {
21 setSnapshot((current) => ({
22 scope,
23 requestsKey,
24 state: updateComposerTypingGuard(
25 current.scope === scope
26 ? current.state
27 : { ...createComposerTypingGuardState(), focused: current.state.focused },
28 { type: "requests", requestIds, now: Date.now() },
29 ),
30 }));
31 }
32 const dispatch = useCallback((event: Parameters<typeof updateComposerTypingGuard>[1]) => {
33 setSnapshot((current) => ({
34 ...current,
35 state: updateComposerTypingGuard(current.state, event),
36 }));
37 }, []);
38 const onDraftChange = useCallback(() => dispatch({ type: "type", now: Date.now() }), [dispatch]);
39 const onFocus = useCallback(() => dispatch({ type: "focus" }), [dispatch]);
40 const onBlur = useCallback(() => dispatch({ type: "blur" }), [dispatch]);
41 const onSend = useCallback(() => dispatch({ type: "send" }), [dispatch]);
42 useEffect(() => {
43 if (state.held.size === 0) return;
44 const timeout = window.setTimeout(
45 () => dispatch({ type: "idle" }),
46 Math.max(0, state.lastTypedAt + COMPOSER_TYPING_IDLE_MS - Date.now()),
47 );
48 return () => window.clearTimeout(timeout);
49 }, [state.held, state.lastTypedAt, dispatch]);
50 return { heldRequestIds: state.held, onDraftChange, onFocus, onBlur, onSend };
51}