feat(vue): add ionic vue beta (#22062)

This commit is contained in:
Liam DeBeasi
2020-09-10 15:20:49 -04:00
committed by GitHub
parent 74af3cb50b
commit 5ffa65f84a
48 changed files with 3949 additions and 26 deletions

View 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()
)
}
}
});

View 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()
)
}
}
});

View 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 }))
)
}
});

View 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()
)
}
}
});

View 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
)
}
}
});

View 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()
]
)
}
});