forked from ionic-team/ionic-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathframework-delegate.ts
More file actions
169 lines (147 loc) · 5.19 KB
/
Copy pathframework-delegate.ts
File metadata and controls
169 lines (147 loc) · 5.19 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import type { ComponentRef, FrameworkDelegate } from '../interface';
import { componentOnReady } from './helpers';
// TODO(FW-2832): types
export const attachComponent = async (
delegate: FrameworkDelegate | undefined,
container: Element,
component?: ComponentRef,
cssClasses?: string[],
componentProps?: { [key: string]: any },
inline?: boolean
): Promise<HTMLElement> => {
if (delegate) {
return delegate.attachViewToDom(container, component, componentProps, cssClasses);
}
if (!inline && typeof component !== 'string' && !(component instanceof HTMLElement)) {
throw new Error('framework delegate is missing');
}
const el: any = typeof component === 'string' ? container.ownerDocument?.createElement(component) : component;
if (cssClasses) {
cssClasses.forEach((c) => el.classList.add(c));
}
if (componentProps) {
Object.assign(el, componentProps);
}
container.appendChild(el);
await new Promise((resolve) => componentOnReady(el, resolve));
return el;
};
export const detachComponent = (delegate: FrameworkDelegate | undefined, element: HTMLElement | undefined) => {
if (element) {
if (delegate) {
const container = element.parentElement;
return delegate.removeViewFromDom(container, element);
}
element.remove();
}
return Promise.resolve();
};
export const CoreDelegate = () => {
let BaseComponent: any;
let Reference: any;
const attachViewToDom = async (
parentElement: HTMLElement,
userComponent: any,
userComponentProps: any = {},
cssClasses: string[] = []
) => {
BaseComponent = parentElement;
let ChildComponent;
/**
* If passing in a component via the `component` props
* we need to append it inside of our overlay component.
*/
if (userComponent) {
/**
* If passing in the tag name, create
* the element otherwise just get a reference
* to the component.
*/
const el: any =
typeof userComponent === 'string' ? BaseComponent.ownerDocument?.createElement(userComponent) : userComponent;
/**
* Add any css classes passed in
* via the cssClasses prop on the overlay.
*/
cssClasses.forEach((c) => el.classList.add(c));
/**
* Add any props passed in
* via the componentProps prop on the overlay.
*/
Object.assign(el, userComponentProps);
/**
* Finally, append the component
* inside of the overlay component.
*/
BaseComponent.appendChild(el);
ChildComponent = el;
await new Promise((resolve) => componentOnReady(el, resolve));
} else if (
BaseComponent.children.length > 0 &&
(BaseComponent.tagName === 'ION-MODAL' || BaseComponent.tagName === 'ION-POPOVER')
) {
/**
* The delegate host wrapper el is only needed for modals and popovers
* because they allow the dev to provide custom content to the overlay.
*/
const root = (ChildComponent = BaseComponent.children[0] as HTMLElement);
if (!root.classList.contains('ion-delegate-host')) {
/**
* If the root element is not a delegate host, it means
* that the overlay has not been presented yet and we need
* to create the containing element with the specified classes.
*/
const el = BaseComponent.ownerDocument?.createElement('div');
// Add a class to track if the root element was created by the delegate.
el.classList.add('ion-delegate-host');
cssClasses.forEach((c) => el.classList.add(c));
// Move each child from the original template to the new parent element.
el.append(...BaseComponent.children);
// Append the new parent element to the original parent element.
BaseComponent.appendChild(el);
/**
* Update the ChildComponent to be the
* newly created div in the event that one
* does not already exist.
*/
ChildComponent = el;
}
}
/**
* Get the root of the app and
* add the overlay there.
*/
const app = document.querySelector('ion-app') || document.body;
/**
* Create a placeholder comment so that
* we can return this component to where
* it was previously.
*/
Reference = document.createComment('ionic teleport');
BaseComponent.parentNode.insertBefore(Reference, BaseComponent);
app.appendChild(BaseComponent);
/**
* We return the child component rather than the overlay
* reference itself since modal and popover will
* use this to wait for any Ionic components in the child view
* to be ready (i.e. componentOnReady) when using the
* lazy loaded component bundle.
*
* However, we fall back to returning BaseComponent
* in the event that a modal or popover is presented
* with no child content.
*/
return ChildComponent ?? BaseComponent;
};
const removeViewFromDom = () => {
/**
* Return component to where it was previously in the DOM.
*/
if (BaseComponent && Reference) {
Reference.parentNode.insertBefore(BaseComponent, Reference);
Reference.remove();
}
return Promise.resolve();
};
return { attachViewToDom, removeViewFromDom };
};