UNPKG

4.62 kBTypeScriptView Raw
1import React from 'react';
2import ReactDOM from 'react-dom';
3
4import { OverlayEventDetail } from './interfaces';
5import {
6 StencilReactForwardedRef,
7 attachProps,
8 dashToPascalCase,
9 defineCustomElement,
10 setRef,
11} from './utils';
12
13interface OverlayElement extends HTMLElement {
14 present: () => Promise<void>;
15 dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
16}
17
18export interface ReactOverlayProps {
19 children?: React.ReactNode;
20 isOpen: boolean;
21 onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
22 onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
23 onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
24 onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
25}
26
27export const createOverlayComponent = <
28 OverlayComponent extends object,
29 OverlayType extends OverlayElement
30>(
31 tagName: string,
32 controller: { create: (options: any) => Promise<OverlayType> },
33 customElement?: any
34) => {
35 defineCustomElement(tagName, customElement);
36
37 const displayName = dashToPascalCase(tagName);
38 const didDismissEventName = `on${displayName}DidDismiss`;
39 const didPresentEventName = `on${displayName}DidPresent`;
40 const willDismissEventName = `on${displayName}WillDismiss`;
41 const willPresentEventName = `on${displayName}WillPresent`;
42
43 type Props = OverlayComponent &
44 ReactOverlayProps & {
45 forwardedRef?: StencilReactForwardedRef<OverlayType>;
46 };
47
48 let isDismissing = false;
49
50 class Overlay extends React.Component<Props> {
51 overlay?: OverlayType;
52 el!: HTMLDivElement;
53
54 constructor(props: Props) {
55 super(props);
56 if (typeof document !== 'undefined') {
57 this.el = document.createElement('div');
58 }
59 this.handleDismiss = this.handleDismiss.bind(this);
60 }
61
62 static get displayName() {
63 return displayName;
64 }
65
66 componentDidMount() {
67 if (this.props.isOpen) {
68 this.present();
69 }
70 }
71
72 componentWillUnmount() {
73 if (this.overlay) {
74 this.overlay.dismiss();
75 }
76 }
77
78 handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
79 if (this.props.onDidDismiss) {
80 this.props.onDidDismiss(event);
81 }
82 setRef(this.props.forwardedRef, null)
83 }
84
85 shouldComponentUpdate(nextProps: Props) {
86 // Check if the overlay component is about to dismiss
87 if (this.overlay && nextProps.isOpen !== this.props.isOpen && nextProps.isOpen === false) {
88 isDismissing = true;
89 }
90
91 return true;
92 }
93
94 async componentDidUpdate(prevProps: Props) {
95 if (this.overlay) {
96 attachProps(this.overlay, this.props, prevProps);
97 }
98
99 if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
100 this.present(prevProps);
101 }
102 if (this.overlay && prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
103 await this.overlay.dismiss();
104 isDismissing = false;
105
106 /**
107 * Now that the overlay is dismissed
108 * we need to render again so that any
109 * inner components will be unmounted
110 */
111 this.forceUpdate();
112 }
113 }
114
115 async present(prevProps?: Props) {
116 const {
117 children,
118 isOpen,
119 onDidDismiss,
120 onDidPresent,
121 onWillDismiss,
122 onWillPresent,
123 ...cProps
124 } = this.props;
125 const elementProps = {
126 ...cProps,
127 ref: this.props.forwardedRef,
128 [didDismissEventName]: this.handleDismiss,
129 [didPresentEventName]: (e: CustomEvent) =>
130 this.props.onDidPresent && this.props.onDidPresent(e),
131 [willDismissEventName]: (e: CustomEvent) =>
132 this.props.onWillDismiss && this.props.onWillDismiss(e),
133 [willPresentEventName]: (e: CustomEvent) =>
134 this.props.onWillPresent && this.props.onWillPresent(e),
135 };
136
137 this.overlay = await controller.create({
138 ...elementProps,
139 component: this.el,
140 componentProps: {},
141 });
142
143 setRef(this.props.forwardedRef, this.overlay);
144 attachProps(this.overlay, elementProps, prevProps);
145
146 await this.overlay.present();
147 }
148
149 render() {
150 /**
151 * Continue to render the component even when
152 * overlay is dismissing otherwise component
153 * will be hidden before animation is done.
154 */
155 return ReactDOM.createPortal(this.props.isOpen || isDismissing ? this.props.children : null, this.el);
156 }
157 }
158
159 return React.forwardRef<OverlayType, Props>((props, ref) => {
160 return <Overlay {...props} forwardedRef={ref} />;
161 });
162};
163
\No newline at end of file