mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-07 06:57:02 +08:00
fix(vue): overlays function properly when used via controller or template (#22155)
resolves #22090
This commit is contained in:
29
packages/vue/src/components/Overlays.ts
Normal file
29
packages/vue/src/components/Overlays.ts
Normal 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);
|
||||
|
||||
26
packages/vue/src/controllers.ts
Normal file
26
packages/vue/src/controllers.ts
Normal 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
|
||||
}
|
||||
21
packages/vue/src/framework-delegate.ts
Normal file
21
packages/vue/src/framework-delegate.ts
Normal 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 }
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
75
packages/vue/src/vue-component-lib/overlays.ts
Normal file
75
packages/vue/src/vue-component-lib/overlays.ts
Normal 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;
|
||||
}
|
||||
@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user