forked from ionic-team/ionic-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreateControllerComponent.tsx
More file actions
128 lines (110 loc) · 4.14 KB
/
createControllerComponent.tsx
File metadata and controls
128 lines (110 loc) · 4.14 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
128
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;
children?: React.ReactNode;
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} forwardedRef={ref} />;
});
};