1// Shared across hook instances so sidebar, header and menu actions invalidate each other.
2const currentActions = new Map<string, symbol>();
3const listeners = new Set<() => void>();
4
5export function subscribe(listener: () => void) {
6 listeners.add(listener);
7 return () => {
8 listeners.delete(listener);
9 };
10}
11
12function notify() {
13 for (const listener of listeners) listener();
14}
15
16/** Claims one kind of thread action; a later claim of that kind expires its Undo. */
17export function begin(kind: string, threadKey: string) {
18 const key = JSON.stringify([kind, threadKey]);
19 const token = Symbol();
20 currentActions.set(key, token);
21 notify();
22 const isCurrent = () => currentActions.get(key) === token;
23 return {
24 isCurrent,
25 finish: () => {
26 if (isCurrent()) {
27 currentActions.delete(key);
28 notify();
29 }
30 },
31 };
32}
33
34/** Expires only this action kind, leaving unrelated thread actions intact. */
35export function invalidate(kind: string, threadKey: string) {
36 currentActions.delete(JSON.stringify([kind, threadKey]));
37 notify();
38}