1// @effect-diagnostics globalTimers:off - Display timing for React hooks, outside an Effect runtime.
2
3/** How long a status must last before a client shows it. */
4export const STATUS_SHOW_DELAY_MS = 400;
5/** How long a shown status stays up, so it cannot flash at the show delay. */
6export const STATUS_MIN_VISIBLE_MS = 400;
7
8/** The status a client should show, and the key it belongs to. */
9export interface ShownStatus<A> {
10 readonly key: string;
11 readonly value: A;
12}
13
14export interface DelayedStatus<A> {
15 /** Reports the real status for `key`. A new key drops the shown status at once. */
16 readonly update: (key: string, value: A | null) => void;
17 /** Cancels pending timers. A later `update` starts again from the real status. */
18 readonly dispose: () => void;
19}
20
21/**
22 * Turns a real status (for example the thread sync phase) into the status a
23 * client shows. A status that clears within `STATUS_SHOW_DELAY_MS` is never
24 * shown. A shown status stays for at least `STATUS_MIN_VISIBLE_MS`. Values
25 * compare by identity, so use strings or other stable values.
26 *
27 * Web and mobile wrap this in a small `useDelayedStatus` hook.
28 */
29export function createDelayedStatus<A>(
30 onChange: (shown: ShownStatus<A> | null) => void,
31): DelayedStatus<A> {
32 let key = "";
33 let latest: A | null = null;
34 let shown: A | null = null;
35 // While hidden, this is the show delay. While shown, the minimum visible time.
36 let timer: ReturnType<typeof setTimeout> | undefined;
37
38 const clearTimer = () => {
39 clearTimeout(timer);
40 timer = undefined;
41 };
42 const hide = () => {
43 shown = null;
44 onChange(null);
45 };
46 // Every shown value, including a new label, gets the full minimum visible time.
47 const show = (value: A) => {
48 shown = value;
49 onChange({ key, value });
50 clearTimer();
51 timer = setTimeout(() => {
52 timer = undefined;
53 if (latest === null) hide();
54 }, STATUS_MIN_VISIBLE_MS);
55 };
56
57 return {
58 update: (nextKey, value) => {
59 if (nextKey !== key) {
60 key = nextKey;
61 clearTimer();
62 if (shown !== null) hide();
63 }
64 latest = value;
65
66 if (shown === null) {
67 if (value === null) {
68 clearTimer();
69 } else if (timer === undefined) {
70 timer = setTimeout(() => {
71 timer = undefined;
72 if (latest !== null) show(latest);
73 }, STATUS_SHOW_DELAY_MS);
74 }
75 return;
76 }
77
78 if (value !== null) {
79 if (value !== shown) show(value);
80 } else if (timer === undefined) {
81 hide();
82 }
83 },
84 dispose: clearTimer,
85 };
86}