-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Expand file tree
/
Copy pathcreateControllerComponent.tsx
More file actions
127 lines (109 loc) · 4.12 KB
/
createControllerComponent.tsx
File metadata and controls
127 lines (109 loc) · 4.12 KB
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import type { OverlayEventDetail } from '@ionic/core/components';
import React from 'react';
import { attachProps, dashToPascalCase, setRef } from './react-component-lib/utils';
interface OverlayBase extends HTMLElement {
present: () => Promise<void>;
dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
}
export interface ReactControllerProps {
isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
}
export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>(
tagName: string,
controller: { create: (options: OptionsType) => Promise<OverlayType> },
defineCustomElement?: () => void
) => {
if (defineCustomElement) {
defineCustomElement();
}
const displayName = dashToPascalCase(tagName);
const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = OptionsType &
ReactControllerProps & {
forwardedRef?: React.ForwardedRef<OverlayType>;
};
class Overlay extends React.Component<Props> {
overlay?: OverlayType;
willUnmount = false;
constructor(props: Props) {
super(props);
this.handleDismiss = this.handleDismiss.bind(this);
}
static get displayName() {
return displayName;
}
async componentDidMount() {
/**
* Starting in React v18, strict mode will unmount and remount a component.
* See: https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
*
* We need to reset this flag when the component is re-mounted so that
* overlay.present() will be called and the overlay will display.
*/
this.willUnmount = false;
const { isOpen } = this.props;
if (isOpen as boolean) {
this.present();
}
}
componentWillUnmount() {
this.willUnmount = true;
if (this.overlay) {
this.overlay.dismiss();
}
}
async componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
if (this.overlay && prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
await this.overlay.dismiss();
}
}
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
setRef(this.props.forwardedRef, null);
}
async present(prevProps?: Props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
if (this.overlay) {
this.overlay.remove();
}
this.overlay = await controller.create({
...(cProps as any),
});
attachProps(
this.overlay,
{
[didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e),
},
prevProps
);
// Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.willUnmount === false) {
setRef(this.props.forwardedRef, this.overlay);
await this.overlay.present();
}
}
render(): null {
return null;
}
}
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...(props as Props)} forwardedRef={ref} />;
});
};