1 | import React from 'react';
|
2 | import ReactDOM from 'react-dom';
|
3 |
|
4 | import { OverlayEventDetail } from './interfaces';
|
5 | import {
|
6 | StencilReactForwardedRef,
|
7 | attachProps,
|
8 | dashToPascalCase,
|
9 | defineCustomElement,
|
10 | setRef,
|
11 | } from './utils';
|
12 |
|
13 | interface OverlayElement extends HTMLElement {
|
14 | present: () => Promise<void>;
|
15 | dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
|
16 | }
|
17 |
|
18 | export 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 |
|
27 | export 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 |
|
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 |
|
108 |
|
109 |
|
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 |
|
152 |
|
153 |
|
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 |