fix(vue): overlays function properly when used via controller or template (#22155)

resolves #22090
This commit is contained in:
Liam DeBeasi
2020-09-24 11:18:28 -04:00
committed by GitHub
parent b76bfa36c2
commit fe5fadf19c
14 changed files with 343 additions and 1065 deletions

View File

@ -0,0 +1,29 @@
/* auto-generated vue overlay proxies */
import {
JSX,
actionSheetController,
alertController,
loadingController,
modalController,
pickerController,
popoverController,
toastController
} from '@ionic/core';
import { defineOverlayContainer } from '../vue-component-lib/overlays';
export const IonActionSheet = /*@__PURE__*/defineOverlayContainer<JSX.IonActionSheet>('ion-action-sheet', ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'translucent'], actionSheetController);
export const IonAlert = /*@__PURE__*/defineOverlayContainer<JSX.IonAlert>('ion-alert', ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'inputs', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent'], alertController);
export const IonLoading = /*@__PURE__*/defineOverlayContainer<JSX.IonLoading>('ion-loading', ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'translucent'], loadingController);
export const IonModal = /*@__PURE__*/defineOverlayContainer<JSX.IonModal>('ion-modal', ['animated', 'backdropDismiss', 'component', 'componentProps', 'cssClass', 'enterAnimation', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose'], modalController);
export const IonPicker = /*@__PURE__*/defineOverlayContainer<JSX.IonPicker>('ion-picker', ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop'], pickerController);
export const IonPopover = /*@__PURE__*/defineOverlayContainer<JSX.IonPopover>('ion-popover', ['animated', 'backdropDismiss', 'component', 'componentProps', 'cssClass', 'enterAnimation', 'event', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'translucent'], popoverController);
export const IonToast = /*@__PURE__*/defineOverlayContainer<JSX.IonToast>('ion-toast', ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);

View File

@ -0,0 +1,26 @@
import {
modalController,
popoverController
} from '@ionic/core';
import { VueDelegate } from './framework-delegate';
const oldModalCreate = modalController.create.bind(modalController);
modalController.create = (options) => {
return oldModalCreate({
...options,
delegate: VueDelegate()
});
}
const oldPopoverCreate = popoverController.create.bind(popoverController);
popoverController.create = (options) => {
return oldPopoverCreate({
...options,
delegate: VueDelegate()
});
}
export {
modalController,
popoverController
}

View File

@ -0,0 +1,21 @@
import { createVNode, render } from 'vue';
export const VueDelegate = () => {
const attachViewToDom = (parentElement: HTMLElement, component: any, _: any, classes?: string[]) => {
const vueInstance = createVNode(component);
const div = document.createElement('div');
classes && div.classList.add(...classes);
parentElement.appendChild(div);
render(vueInstance, div);
}
const removeViewFromDom = (_: HTMLElement, childElement: any) => {
render(null, childElement);
return Promise.resolve();
}
return { attachViewToDom, removeViewFromDom }
}

View File

@ -10,18 +10,22 @@ export { IonRouterOutlet } from './components/IonRouterOutlet';
export { IonTabButton } from './components/IonTabButton';
export { IonTabs } from './components/IonTabs';
export { IonTabBar } from './components/IonTabBar';
export * from './components/Overlays';
export { IonKeyboardRef, IonRouter, useBackButton, useIonRouter, useKeyboard } from './hooks';
export {
modalController,
popoverController
} from './controllers';
export {
// Overlay Controllers
alertController,
actionSheetController,
menuController,
modalController,
loadingController,
pickerController,
popoverController,
toastController,
// Security

View File

@ -8,46 +8,6 @@ import { JSX } from '@ionic/core';
export const IonActionSheet = /*@__PURE__*/ defineContainer<JSX.IonActionSheet>('ion-action-sheet', [
'overlayIndex',
'keyboardClose',
'enterAnimation',
'leaveAnimation',
'buttons',
'cssClass',
'backdropDismiss',
'header',
'subHeader',
'translucent',
'animated',
'ionActionSheetDidPresent',
'ionActionSheetWillPresent',
'ionActionSheetWillDismiss',
'ionActionSheetDidDismiss'
]);
export const IonAlert = /*@__PURE__*/ defineContainer<JSX.IonAlert>('ion-alert', [
'overlayIndex',
'keyboardClose',
'enterAnimation',
'leaveAnimation',
'cssClass',
'header',
'subHeader',
'message',
'buttons',
'inputs',
'backdropDismiss',
'translucent',
'animated',
'ionAlertDidPresent',
'ionAlertWillPresent',
'ionAlertWillDismiss',
'ionAlertDidDismiss'
]);
export const IonApp = /*@__PURE__*/ defineContainer<JSX.IonApp>('ion-app');
@ -439,26 +399,6 @@ export const IonListHeader = /*@__PURE__*/ defineContainer<JSX.IonListHeader>('i
]);
export const IonLoading = /*@__PURE__*/ defineContainer<JSX.IonLoading>('ion-loading', [
'overlayIndex',
'keyboardClose',
'enterAnimation',
'leaveAnimation',
'message',
'cssClass',
'duration',
'backdropDismiss',
'showBackdrop',
'spinner',
'translucent',
'animated',
'ionLoadingDidPresent',
'ionLoadingWillPresent',
'ionLoadingWillDismiss',
'ionLoadingDidDismiss'
]);
export const IonMenu = /*@__PURE__*/ defineContainer<JSX.IonMenu>('ion-menu', [
'contentId',
'menuId',
@ -490,27 +430,6 @@ export const IonMenuToggle = /*@__PURE__*/ defineContainer<JSX.IonMenuToggle>('i
]);
export const IonModal = /*@__PURE__*/ defineContainer<JSX.IonModal>('ion-modal', [
'overlayIndex',
'delegate',
'keyboardClose',
'enterAnimation',
'leaveAnimation',
'component',
'componentProps',
'cssClass',
'backdropDismiss',
'showBackdrop',
'animated',
'swipeToClose',
'presentingElement',
'ionModalDidPresent',
'ionModalWillPresent',
'ionModalWillDismiss',
'ionModalDidDismiss'
]);
export const IonNav = /*@__PURE__*/ defineContainer<JSX.IonNav>('ion-nav', [
'delegate',
'swipeGesture',
@ -537,46 +456,6 @@ export const IonNote = /*@__PURE__*/ defineContainer<JSX.IonNote>('ion-note', [
]);
export const IonPicker = /*@__PURE__*/ defineContainer<JSX.IonPicker>('ion-picker', [
'overlayIndex',
'keyboardClose',
'enterAnimation',
'leaveAnimation',
'buttons',
'columns',
'cssClass',
'duration',
'showBackdrop',
'backdropDismiss',
'animated',
'ionPickerDidPresent',
'ionPickerWillPresent',
'ionPickerWillDismiss',
'ionPickerDidDismiss'
]);
export const IonPopover = /*@__PURE__*/ defineContainer<JSX.IonPopover>('ion-popover', [
'delegate',
'overlayIndex',
'enterAnimation',
'leaveAnimation',
'component',
'componentProps',
'keyboardClose',
'cssClass',
'backdropDismiss',
'event',
'showBackdrop',
'translucent',
'animated',
'ionPopoverDidPresent',
'ionPopoverWillPresent',
'ionPopoverWillDismiss',
'ionPopoverDidDismiss'
]);
export const IonProgressBar = /*@__PURE__*/ defineContainer<JSX.IonProgressBar>('ion-progress-bar', [
'type',
'reversed',
@ -860,27 +739,6 @@ export const IonTitle = /*@__PURE__*/ defineContainer<JSX.IonTitle>('ion-title',
]);
export const IonToast = /*@__PURE__*/ defineContainer<JSX.IonToast>('ion-toast', [
'overlayIndex',
'color',
'enterAnimation',
'leaveAnimation',
'cssClass',
'duration',
'header',
'message',
'keyboardClose',
'position',
'buttons',
'translucent',
'animated',
'ionToastDidPresent',
'ionToastWillPresent',
'ionToastWillDismiss',
'ionToastDidDismiss'
]);
export const IonToggle = /*@__PURE__*/ defineContainer<JSX.IonToggle>('ion-toggle', [
'color',
'name',

View File

@ -0,0 +1,75 @@
import { defineComponent, h, ref } from 'vue';
export interface OverlayProps {
isOpen?: boolean;
}
export const defineOverlayContainer = <Props extends object>(name: string, componentProps: string[] = [], controller: any) => {
// TODO
const eventPrefix = name.toLowerCase().split('-').join('');
const eventListeners = [
{ componentEv: `${eventPrefix}willpresent`, frameworkEv: 'onWillPresent' },
{ componentEv: `${eventPrefix}didpresent`, frameworkEv: 'onDidPresent' },
{ componentEv: `${eventPrefix}willdismiss`, frameworkEv: 'onWillDismiss' },
{ componentEv: `${eventPrefix}diddismiss`, frameworkEv: 'onDidDismiss' },
];
const Container = defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
const overlay = ref();
const content = ref();
const onVnodeMounted = async () => {
const isOpen = props.isOpen;
isOpen && (await present(props))
}
const onVnodeUpdated = async () => {
const isOpen = props.isOpen;
if (isOpen) {
await overlay.value?.present() || present(props);
} else {
await overlay.value?.dismiss();
overlay.value = undefined;
}
}
const onVnodeBeforeUnmount = async () => {
await overlay.value?.dismiss();
overlay.value = undefined;
}
const present = async (props: Readonly<Props>) => {
const component = (slots) ? h('div', { ref: content }, slots) : undefined;
overlay.value = await controller.create({
...props,
component
});
eventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv);
});
})
await overlay.value.present();
}
return () => {
return h(
'div',
{
style: { display: 'none' },
onVnodeMounted,
onVnodeUpdated,
onVnodeBeforeUnmount
}
);
}
});
Container.displayName = name;
Container.props = [...componentProps, 'isOpen'];
Container.emits = eventListeners.map(ev => ev.frameworkEv);
return Container;
}

View File

@ -1,4 +1,4 @@
import { FunctionalComponent, VNode, defineComponent, h, inject, ref, Ref } from 'vue';
import { VNode, defineComponent, h, inject, ref, Ref } from 'vue';
export interface InputProps extends Object {
modelValue: string | boolean;
@ -40,7 +40,7 @@ const getElementClasses = (ref: Ref<HTMLElement | undefined>, componentClasses:
* options for the component such as router or v-model
* integrations.
*/
export const defineContainer = <Props extends object>(name: string, componentProps: string[] = [], componentOptions: ComponentOptions = {}) => {
export const defineContainer = <Props>(name: string, componentProps: string[] = [], componentOptions: ComponentOptions = {}) => {
const { modelProp, modelUpdateEvent, routerLinkComponent } = componentOptions;
/**
@ -48,7 +48,7 @@ export const defineContainer = <Props extends object>(name: string, componentPro
* Note: The `props` here are not all properties on a component.
* They refer to whatever properties are set on an instance of a component.
*/
const Container: FunctionalComponent<Props & InputProps> = defineComponent((props, { attrs, slots, emit }) => {
const Container = defineComponent<Props & InputProps>((props, { attrs, slots, emit }) => {
const containerRef = ref<HTMLElement>();
const classes = new Set(getComponentClasses(attrs.class));
const onVnodeBeforeMount = (vnode: VNode) => {