mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
feat(vue): add ionic vue beta (#22062)
This commit is contained in:
26
packages/vue/src/components/IonBackButton.ts
Normal file
26
packages/vue/src/components/IonBackButton.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { h, inject, defineComponent } from 'vue';
|
||||
|
||||
export const IonBackButton = defineComponent({
|
||||
name: 'IonBackButton',
|
||||
setup(_, { attrs, slots }) {
|
||||
const ionRouter: any = inject('navManager');
|
||||
|
||||
const onClick = () => {
|
||||
const defaultHref = attrs['default-href'] || attrs['defaultHref'];
|
||||
const routerAnimation = attrs['router-animation'] || attrs['routerAnimation'];
|
||||
|
||||
ionRouter.handleNavigateBack(defaultHref, routerAnimation);
|
||||
}
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
'ion-back-button',
|
||||
{
|
||||
onClick,
|
||||
...attrs
|
||||
},
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
18
packages/vue/src/components/IonPage.ts
Normal file
18
packages/vue/src/components/IonPage.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { h, defineComponent } from 'vue';
|
||||
|
||||
export const IonPage = defineComponent({
|
||||
name: 'IonPage',
|
||||
setup(_, { attrs, slots }) {
|
||||
return () => {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
['class']: 'ion-page',
|
||||
...attrs,
|
||||
ref: 'ionPage'
|
||||
},
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
253
packages/vue/src/components/IonRouterOutlet.ts
Normal file
253
packages/vue/src/components/IonRouterOutlet.ts
Normal file
@ -0,0 +1,253 @@
|
||||
import {
|
||||
h,
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
inject,
|
||||
watch,
|
||||
shallowRef,
|
||||
InjectionKey
|
||||
} from 'vue';
|
||||
import { AnimationBuilder } from '@ionic/core';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { fireLifecycle, generateId, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '../utils';
|
||||
|
||||
let viewDepthKey: InjectionKey<0> = Symbol(0);
|
||||
export const IonRouterOutlet = defineComponent({
|
||||
name: 'IonRouterOutlet',
|
||||
setup(_, { attrs }) {
|
||||
const vueRouter = useRouter();
|
||||
const route = useRoute();
|
||||
const depth = inject(viewDepthKey, 0);
|
||||
|
||||
// TODO types
|
||||
const matchedRouteRef: any = computed(() => {
|
||||
if (attrs.tabs) {
|
||||
return route.matched[route.matched.length - 1];
|
||||
}
|
||||
|
||||
return route.matched[depth];
|
||||
});
|
||||
const ionRouterOutlet = ref();
|
||||
const id = generateId('ion-router-outlet');
|
||||
|
||||
// TODO types
|
||||
const ionRouter: any = inject('navManager');
|
||||
const viewStacks: any = inject('viewStacks');
|
||||
|
||||
const components = shallowRef([]);
|
||||
|
||||
let skipTransition = false;
|
||||
|
||||
watch(matchedRouteRef, () => {
|
||||
setupViewItem(matchedRouteRef);
|
||||
});
|
||||
|
||||
const canStart = () => {
|
||||
const stack = viewStacks.getViewStack(id);
|
||||
return stack && stack.length > 1;
|
||||
}
|
||||
const onStart = async () => {
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const { routerAnimation } = routeInfo;
|
||||
const items = viewStacks.getViewStack(id);
|
||||
const enteringViewItem = items[items.length - 2];
|
||||
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
|
||||
if (leavingViewItem) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
/**
|
||||
* If we are going back from a page that
|
||||
* was presented using a custom animation
|
||||
* we should default to using that
|
||||
* unless the developer explicitly
|
||||
* provided another animation.
|
||||
*/
|
||||
const customAnimation = enteringViewItem.routerAnimation;
|
||||
if (
|
||||
animationBuilder === undefined &&
|
||||
// todo check for tab switch
|
||||
customAnimation !== undefined
|
||||
) {
|
||||
animationBuilder = customAnimation;
|
||||
}
|
||||
|
||||
leavingViewItem.routerAnimation = animationBuilder;
|
||||
|
||||
await transition(
|
||||
enteringEl,
|
||||
leavingEl,
|
||||
'back',
|
||||
ionRouter.canGoBack(2),
|
||||
true,
|
||||
animationBuilder
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const onEnd = (shouldContinue: boolean) => {
|
||||
if (shouldContinue) {
|
||||
skipTransition = true;
|
||||
vueRouter.back();
|
||||
}
|
||||
}
|
||||
|
||||
watch(ionRouterOutlet, () => {
|
||||
ionRouterOutlet.value.swipeHandler = {
|
||||
canStart,
|
||||
onStart,
|
||||
onEnd
|
||||
}
|
||||
});
|
||||
|
||||
const transition = (
|
||||
enteringEl: HTMLElement,
|
||||
leavingEl: HTMLElement,
|
||||
direction: any, // TODO types
|
||||
showGoBack: boolean,
|
||||
progressAnimation: boolean,
|
||||
animationBuilder?: AnimationBuilder
|
||||
) => {
|
||||
if (skipTransition) {
|
||||
skipTransition = false;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (enteringEl === leavingEl) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
enteringEl.classList.add('ion-page-invisible');
|
||||
|
||||
return ionRouterOutlet.value.commit(enteringEl, leavingEl, {
|
||||
deepWait: true,
|
||||
duration: direction === undefined || direction === 'root' || direction === 'none' ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation,
|
||||
animationBuilder
|
||||
});
|
||||
}
|
||||
|
||||
const handlePageTransition = async () => {
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const { routerDirection, routerAction, routerAnimation } = routeInfo;
|
||||
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
const leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, LIFECYCLE_WILL_ENTER);
|
||||
|
||||
if (leavingViewItem) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_WILL_LEAVE);
|
||||
|
||||
enteringEl.classList.remove('ion-page-hidden');
|
||||
|
||||
/**
|
||||
* If we are going back from a page that
|
||||
* was presented using a custom animation
|
||||
* we should default to using that
|
||||
* unless the developer explicitly
|
||||
* provided another animation.
|
||||
*/
|
||||
const customAnimation = enteringViewItem.routerAnimation;
|
||||
if (
|
||||
animationBuilder === undefined &&
|
||||
routerDirection === 'back' &&
|
||||
// todo check for tab switch
|
||||
customAnimation !== undefined
|
||||
) {
|
||||
animationBuilder = customAnimation;
|
||||
}
|
||||
|
||||
leavingViewItem.routerAnimation = animationBuilder;
|
||||
|
||||
await transition(
|
||||
enteringEl,
|
||||
leavingEl,
|
||||
routerDirection,
|
||||
routerDirection === 'forward',
|
||||
false,
|
||||
animationBuilder
|
||||
);
|
||||
|
||||
leavingEl.classList.add('ion-page-hidden');
|
||||
leavingEl.setAttribute('aria-hidden', 'true');
|
||||
|
||||
if (!(routerAction === 'push' && routerDirection === 'forward')) {
|
||||
const shouldLeavingViewBeRemoved = routerDirection !== 'none' && leavingViewItem && (enteringViewItem !== leavingViewItem);
|
||||
if (shouldLeavingViewBeRemoved) {
|
||||
leavingViewItem.mount = false;
|
||||
leavingViewItem.ionPageElement = undefined;
|
||||
leavingViewItem.ionRoute = false;
|
||||
}
|
||||
}
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_DID_LEAVE);
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, LIFECYCLE_DID_ENTER);
|
||||
|
||||
components.value = viewStacks.getChildrenToRender(id);
|
||||
}
|
||||
|
||||
// TODO types
|
||||
const setupViewItem = (matchedRouteRef: any) => {
|
||||
if (!matchedRouteRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRoute = ionRouter.getCurrentRouteInfo();
|
||||
if (currentRoute.tab !== undefined && !attrs.tabs) return
|
||||
|
||||
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);
|
||||
|
||||
if (!enteringViewItem) {
|
||||
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
|
||||
viewStacks.add(enteringViewItem);
|
||||
}
|
||||
|
||||
if (!enteringViewItem.mount) {
|
||||
enteringViewItem.mount = true;
|
||||
enteringViewItem.vueComponent.components.IonPage.mounted = function () {
|
||||
viewStacks.registerIonPage(enteringViewItem, this.$refs.ionPage);
|
||||
handlePageTransition();
|
||||
enteringViewItem.vueComponent.components.IonPage.mounted = undefined;
|
||||
};
|
||||
} else {
|
||||
handlePageTransition();
|
||||
}
|
||||
|
||||
components.value = viewStacks.getChildrenToRender(id);
|
||||
}
|
||||
|
||||
if (matchedRouteRef.value) {
|
||||
setupViewItem(matchedRouteRef);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
components,
|
||||
ionRouterOutlet
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { components } = this;
|
||||
|
||||
return h(
|
||||
'ion-router-outlet',
|
||||
{ ref: 'ionRouterOutlet' },
|
||||
// TODO types
|
||||
components && components.map((c: any) => h(c.vueComponent, { key: c.pathname }))
|
||||
)
|
||||
}
|
||||
});
|
33
packages/vue/src/components/IonTabBar.ts
Normal file
33
packages/vue/src/components/IonTabBar.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { h, defineComponent, inject } from 'vue';
|
||||
|
||||
export const IonTabBar = defineComponent({
|
||||
name: 'IonTabBar',
|
||||
mounted() {
|
||||
const ionRouter: any = inject('navManager');
|
||||
|
||||
const checkActiveTab = (currentRoute: any) => {
|
||||
// TODO types
|
||||
const tabs = Array.from(this.$el.querySelectorAll('ion-tab-button')) as any[];
|
||||
const activeTab = tabs.find(tab => currentRoute.pathname.startsWith(tab.href));
|
||||
|
||||
if (activeTab) {
|
||||
ionRouter.handleSetCurrentTab(activeTab.tab);
|
||||
const tabBar = this.$refs.ionTabBar;
|
||||
tabBar.selectedTab = activeTab.tab;
|
||||
}
|
||||
}
|
||||
|
||||
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
|
||||
checkActiveTab(ionRouter.getCurrentRouteInfo());
|
||||
|
||||
},
|
||||
setup(_, { slots }) {
|
||||
return () => {
|
||||
return h(
|
||||
'ion-tab-bar',
|
||||
{ ref: 'ionTabBar' },
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
37
packages/vue/src/components/IonTabButton.ts
Normal file
37
packages/vue/src/components/IonTabButton.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { h, defineComponent, inject } from 'vue';
|
||||
|
||||
export const IonTabButton = defineComponent({
|
||||
name: 'IonTabButton',
|
||||
setup(_, { attrs, slots }) {
|
||||
const ionRouter: any = inject('navManager');
|
||||
const onClick = (ev: Event) => {
|
||||
|
||||
if (ev.cancelable) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
const { tab, href } = attrs;
|
||||
const currentRoute = ionRouter.getCurrentRouteInfo();
|
||||
|
||||
if (currentRoute.tab === tab) {
|
||||
if (href !== currentRoute.pathname) {
|
||||
ionRouter.resetTab(tab, href);
|
||||
}
|
||||
} else {
|
||||
// TODO tabs will change/did change
|
||||
ionRouter.changeTab(tab, href)
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
const children = slots.default && slots.default()
|
||||
return h(
|
||||
'ion-tab-button',
|
||||
{
|
||||
onClick,
|
||||
...attrs
|
||||
},
|
||||
children
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
45
packages/vue/src/components/IonTabs.ts
Normal file
45
packages/vue/src/components/IonTabs.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { h, defineComponent } from 'vue';
|
||||
import { IonRouterOutlet } from './IonRouterOutlet';
|
||||
|
||||
export const IonTabs = defineComponent({
|
||||
name: 'IonTabs',
|
||||
render() {
|
||||
const { $slots: slots } = this;
|
||||
return h(
|
||||
'ion-tabs',
|
||||
{
|
||||
style: {
|
||||
'display': 'flex',
|
||||
'position': 'absolute',
|
||||
'top': '0',
|
||||
'left': '0',
|
||||
'right': '0',
|
||||
'bottom': '0',
|
||||
'flex-direction': 'column',
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'contain': 'layout size style',
|
||||
'z-index': '0'
|
||||
},
|
||||
ref: 'ionTabsRef'
|
||||
},
|
||||
[
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
class: 'tabs-inner',
|
||||
style: {
|
||||
'position': 'relative',
|
||||
'flex': '1',
|
||||
'contain': 'layout size style'
|
||||
}
|
||||
},
|
||||
[
|
||||
h(IonRouterOutlet, { tabs: true })
|
||||
]
|
||||
),
|
||||
...slots.default && slots.default()
|
||||
]
|
||||
)
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user