-
Notifications
You must be signed in to change notification settings - Fork 1
/
modal-hooks.ts
109 lines (92 loc) · 2.67 KB
/
modal-hooks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useToggle } from './hooks/use-toggle.js';
class ModalEmitter<T extends string> extends EventTarget {
public close(detail: T | 'dismiss') {
this.dispatchEvent(
new CustomEvent('close', {
detail,
}),
);
}
}
export function useDialog<T extends string>(
onClose?: (returnValue: T | 'dismiss') => void,
) {
const ref = useRef<HTMLDialogElement | null>(null);
const modalEmitterRef = useRef(new ModalEmitter<T>());
// is this a memory leak? it adds a listener on every call
const closeEvent = useCallback(
() =>
new Promise<T | 'dismiss'>((resolve) => {
if (!ref.current?.open) {
resolve(
ref.current?.returnValue
? (ref.current?.returnValue as T)
: 'dismiss',
);
}
modalEmitterRef.current.addEventListener(
'close',
(e: Event | CustomEvent<T>) =>
resolve('detail' in e ? e.detail : 'dismiss'),
{ once: true },
);
}),
[],
);
const show = () => {
ref.current?.showModal();
};
const close = useCallback((returnValue: T | 'dismiss') => {
ref.current?.close(returnValue);
}, []);
useEffect(() => {
const el = ref.current;
function handler(this: HTMLDialogElement) {
const returnValue = this.returnValue as T | 'dismiss';
modalEmitterRef.current.close(returnValue);
if (onClose) {
onClose(returnValue);
}
}
el?.addEventListener('close', handler);
return () => {
el?.removeEventListener('close', handler);
};
}, [onClose]);
const dialog = useMemo(() => ({ ref, show, close }), [close]);
return [dialog, closeEvent] as const;
}
export function useModal<T extends string>(
onClose?: (returnValue: T | 'dismiss') => void,
) {
const [open, toggleOpen] = useToggle();
const ref = useRef<HTMLDivElement | null>(null);
const modalEmitterRef = useRef(new ModalEmitter<T>());
const show = useCallback(() => {
toggleOpen(true);
return new Promise<T | 'dismiss'>((resolve) => {
modalEmitterRef.current.addEventListener(
'close',
(e: Event | CustomEvent<T>) =>
resolve('detail' in e ? e.detail : 'dismiss'),
{ once: true },
);
});
}, [toggleOpen]);
const close = useCallback(
(returnValue: T | 'dismiss'): void => {
toggleOpen(false);
modalEmitterRef.current.close(returnValue);
if (onClose) {
onClose(returnValue);
}
},
[onClose, toggleOpen],
);
const modal = useMemo(
() => ({ ref, open, show, close }),
[ref, open, show, close],
);
return modal;
}