UNPKG

3.48 kBTypeScriptView Raw
1import * as React from 'react';
2
3import type { EventArg, EventConsumer, EventEmitter } from './types';
4
5export type NavigationEventEmitter<T extends Record<string, any>> =
6 EventEmitter<T> & {
7 create: (target: string) => EventConsumer<T>;
8 };
9
10type Listeners = ((e: any) => void)[];
11
12/**
13 * Hook to manage the event system used by the navigator to notify screens of various events.
14 */
15export default function useEventEmitter<T extends Record<string, any>>(
16 listen?: (e: any) => void
17): NavigationEventEmitter<T> {
18 const listenRef = React.useRef(listen);
19
20 React.useEffect(() => {
21 listenRef.current = listen;
22 });
23
24 const listeners = React.useRef<Record<string, Record<string, Listeners>>>(
25 Object.create(null)
26 );
27
28 const create = React.useCallback((target: string) => {
29 const removeListener = (type: string, callback: (data: any) => void) => {
30 const callbacks = listeners.current[type]
31 ? listeners.current[type][target]
32 : undefined;
33
34 if (!callbacks) {
35 return;
36 }
37
38 const index = callbacks.indexOf(callback);
39
40 if (index > -1) {
41 callbacks.splice(index, 1);
42 }
43 };
44
45 const addListener = (type: string, callback: (data: any) => void) => {
46 listeners.current[type] = listeners.current[type] || {};
47 listeners.current[type][target] = listeners.current[type][target] || [];
48 listeners.current[type][target].push(callback);
49
50 let removed = false;
51 return () => {
52 // Prevent removing other listeners when unsubscribing same listener multiple times
53 if (!removed) {
54 removed = true;
55 removeListener(type, callback);
56 }
57 };
58 };
59
60 return {
61 addListener,
62 removeListener,
63 };
64 }, []);
65
66 const emit = React.useCallback(
67 ({
68 type,
69 data,
70 target,
71 canPreventDefault,
72 }: {
73 type: string;
74 data?: any;
75 target?: string;
76 canPreventDefault?: boolean;
77 }) => {
78 const items = listeners.current[type] || {};
79
80 // Copy the current list of callbacks in case they are mutated during execution
81 const callbacks =
82 target !== undefined
83 ? items[target]?.slice()
84 : ([] as Listeners)
85 .concat(...Object.keys(items).map((t) => items[t]))
86 .filter((cb, i, self) => self.lastIndexOf(cb) === i);
87
88 const event: EventArg<any, any, any> = {
89 get type() {
90 return type;
91 },
92 };
93
94 if (target !== undefined) {
95 Object.defineProperty(event, 'target', {
96 enumerable: true,
97 get() {
98 return target;
99 },
100 });
101 }
102
103 if (data !== undefined) {
104 Object.defineProperty(event, 'data', {
105 enumerable: true,
106 get() {
107 return data;
108 },
109 });
110 }
111
112 if (canPreventDefault) {
113 let defaultPrevented = false;
114
115 Object.defineProperties(event, {
116 defaultPrevented: {
117 enumerable: true,
118 get() {
119 return defaultPrevented;
120 },
121 },
122 preventDefault: {
123 enumerable: true,
124 value() {
125 defaultPrevented = true;
126 },
127 },
128 });
129 }
130
131 listenRef.current?.(event);
132
133 callbacks?.forEach((cb) => cb(event));
134
135 return event as any;
136 },
137 []
138 );
139
140 return React.useMemo(() => ({ create, emit }), [create, emit]);
141}
142
\No newline at end of file