mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
chore(): sync with main
This commit is contained in:
@ -1,18 +1,19 @@
|
||||
import { h, defineComponent, shallowRef, VNode } from 'vue';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-app.js';
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-app.js";
|
||||
import type { VNode } from "vue";
|
||||
import { h, defineComponent, shallowRef } from "vue";
|
||||
|
||||
const userComponents = shallowRef([]);
|
||||
export const IonApp = /*@__PURE__*/ defineComponent((_, { attrs, slots }) => {
|
||||
defineCustomElement();
|
||||
return () => {
|
||||
return h(
|
||||
'ion-app',
|
||||
"ion-app",
|
||||
{
|
||||
...attrs
|
||||
...attrs,
|
||||
},
|
||||
[slots.default && slots.default(), ...userComponents.value]
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
@ -26,12 +27,11 @@ export const IonApp = /*@__PURE__*/ defineComponent((_, { attrs, slots }) => {
|
||||
* of `ion-app` within the current application context.
|
||||
*/
|
||||
export const addTeleportedUserComponent = (component: VNode) => {
|
||||
userComponents.value = [
|
||||
...userComponents.value,
|
||||
component
|
||||
]
|
||||
}
|
||||
userComponents.value = [...userComponents.value, component];
|
||||
};
|
||||
|
||||
export const removeTeleportedUserComponent = (component: VNode) => {
|
||||
userComponents.value = userComponents.value.filter(cmp => cmp !== component);
|
||||
}
|
||||
userComponents.value = userComponents.value.filter(
|
||||
(cmp) => cmp !== component
|
||||
);
|
||||
};
|
||||
|
@ -1,32 +1,38 @@
|
||||
import { h, inject, defineComponent } from 'vue';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-back-button.js';
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-back-button.js";
|
||||
import { h, inject, defineComponent } from "vue";
|
||||
|
||||
export const IonBackButton = /*@__PURE__*/ defineComponent((_, { attrs, slots }) => {
|
||||
defineCustomElement();
|
||||
export const IonBackButton = /*@__PURE__*/ defineComponent(
|
||||
(_, { attrs, slots }) => {
|
||||
defineCustomElement();
|
||||
|
||||
const ionRouter: any = inject('navManager');
|
||||
// TODO(FW-2969): type
|
||||
const ionRouter: any = inject("navManager");
|
||||
|
||||
const onClick = () => {
|
||||
/**
|
||||
* When using ion-back-button outside of
|
||||
* a routing context, ionRouter is undefined.
|
||||
*/
|
||||
if (ionRouter === undefined) { return; }
|
||||
const onClick = () => {
|
||||
/**
|
||||
* When using ion-back-button outside of
|
||||
* a routing context, ionRouter is undefined.
|
||||
*/
|
||||
if (ionRouter === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultHref = attrs['default-href'] || attrs['defaultHref'];
|
||||
const routerAnimation = attrs['router-animation'] || attrs['routerAnimation'];
|
||||
const defaultHref = attrs["default-href"] || attrs["defaultHref"];
|
||||
const routerAnimation =
|
||||
attrs["router-animation"] || attrs["routerAnimation"];
|
||||
|
||||
ionRouter.handleNavigateBack(defaultHref, routerAnimation);
|
||||
ionRouter.handleNavigateBack(defaultHref, routerAnimation);
|
||||
};
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
"ion-back-button",
|
||||
{
|
||||
onClick,
|
||||
...attrs,
|
||||
},
|
||||
slots.default && slots.default()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
'ion-back-button',
|
||||
{
|
||||
onClick,
|
||||
...attrs
|
||||
},
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
});
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { h, defineComponent } from 'vue';
|
||||
import { isPlatform } from '@ionic/core/components';
|
||||
import { defineCustomElement } from 'ionicons/components/ion-icon.js';
|
||||
import { isPlatform } from "@ionic/core/components";
|
||||
import { defineCustomElement } from "ionicons/components/ion-icon.js";
|
||||
import { h, defineComponent } from "vue";
|
||||
|
||||
export const IonIcon = /*@__PURE__*/ defineComponent({
|
||||
name: 'IonIcon',
|
||||
name: "IonIcon",
|
||||
props: {
|
||||
color: String,
|
||||
flipRtl: Boolean,
|
||||
@ -14,7 +14,7 @@ export const IonIcon = /*@__PURE__*/ defineComponent({
|
||||
mode: String,
|
||||
name: String,
|
||||
size: String,
|
||||
src: String
|
||||
src: String,
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
defineCustomElement();
|
||||
@ -23,7 +23,7 @@ export const IonIcon = /*@__PURE__*/ defineComponent({
|
||||
|
||||
let iconToUse: typeof icon;
|
||||
if (ios || md) {
|
||||
if (isPlatform('ios')) {
|
||||
if (isPlatform("ios")) {
|
||||
iconToUse = ios ?? md ?? icon;
|
||||
} else {
|
||||
iconToUse = md ?? ios ?? icon;
|
||||
@ -33,13 +33,13 @@ export const IonIcon = /*@__PURE__*/ defineComponent({
|
||||
}
|
||||
|
||||
return h(
|
||||
'ion-icon',
|
||||
"ion-icon",
|
||||
{
|
||||
...props,
|
||||
icon: iconToUse
|
||||
icon: iconToUse,
|
||||
},
|
||||
slots
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { defineComponent, h, shallowRef, VNode } from 'vue';
|
||||
import { VueDelegate } from '../framework-delegate';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-nav.js';
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-nav.js";
|
||||
import type { VNode } from "vue";
|
||||
import { defineComponent, h, shallowRef } from "vue";
|
||||
|
||||
import { VueDelegate } from "../framework-delegate";
|
||||
|
||||
export const IonNav = /*@__PURE__*/ defineComponent(() => {
|
||||
defineCustomElement();
|
||||
@ -10,15 +12,13 @@ export const IonNav = /*@__PURE__*/ defineComponent(() => {
|
||||
* Allows us to create the component
|
||||
* within the Vue application context.
|
||||
*/
|
||||
const addView = (component: VNode) => views.value = [...views.value, component];
|
||||
const removeView = (component: VNode) => views.value = views.value.filter(cmp => cmp !== component);
|
||||
const addView = (component: VNode) =>
|
||||
(views.value = [...views.value, component]);
|
||||
const removeView = (component: VNode) =>
|
||||
(views.value = views.value.filter((cmp) => cmp !== component));
|
||||
|
||||
const delegate = VueDelegate(addView, removeView);
|
||||
return () => {
|
||||
return h(
|
||||
'ion-nav',
|
||||
{ delegate },
|
||||
views.value
|
||||
)
|
||||
}
|
||||
return h("ion-nav", { delegate }, views.value);
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { h, defineComponent } from 'vue';
|
||||
import { h, defineComponent } from "vue";
|
||||
|
||||
export const IonPage = /*@__PURE__*/ defineComponent({
|
||||
name: 'IonPage',
|
||||
name: "IonPage",
|
||||
props: {
|
||||
registerIonPage: { type: Function, default: () => {} }
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
registerIonPage: { type: Function, default: () => {} },
|
||||
},
|
||||
mounted() {
|
||||
this.$props.registerIonPage(this.$refs.ionPage);
|
||||
@ -11,14 +12,14 @@ export const IonPage = /*@__PURE__*/ defineComponent({
|
||||
setup(_, { attrs, slots }) {
|
||||
return () => {
|
||||
return h(
|
||||
'div',
|
||||
"div",
|
||||
{
|
||||
...attrs,
|
||||
['class']: 'ion-page',
|
||||
ref: 'ionPage'
|
||||
["class"]: "ion-page",
|
||||
ref: "ionPage",
|
||||
},
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -1,3 +1,11 @@
|
||||
import type { AnimationBuilder } from "@ionic/core/components";
|
||||
import {
|
||||
LIFECYCLE_DID_ENTER,
|
||||
LIFECYCLE_DID_LEAVE,
|
||||
LIFECYCLE_WILL_ENTER,
|
||||
LIFECYCLE_WILL_LEAVE,
|
||||
} from "@ionic/core/components";
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-router-outlet.js";
|
||||
import {
|
||||
h,
|
||||
defineComponent,
|
||||
@ -7,22 +15,25 @@ import {
|
||||
provide,
|
||||
watch,
|
||||
shallowRef,
|
||||
InjectionKey,
|
||||
onUnmounted,
|
||||
Ref
|
||||
} from 'vue';
|
||||
import { AnimationBuilder, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js';
|
||||
import { matchedRouteKey, routeLocationKey, useRoute } from 'vue-router';
|
||||
import { fireLifecycle, generateId, getConfig } from '../utils';
|
||||
} from "vue";
|
||||
import type { InjectionKey, Ref } from "vue";
|
||||
import { matchedRouteKey, routeLocationKey, useRoute } from "vue-router";
|
||||
|
||||
import { fireLifecycle, generateId, getConfig } from "../utils";
|
||||
|
||||
// TODO(FW-2969): types
|
||||
|
||||
const isViewVisible = (enteringEl: HTMLElement) => {
|
||||
return !enteringEl.classList.contains('ion-page-hidden') && !enteringEl.classList.contains('ion-page-invisible');
|
||||
}
|
||||
return (
|
||||
!enteringEl.classList.contains("ion-page-hidden") &&
|
||||
!enteringEl.classList.contains("ion-page-invisible")
|
||||
);
|
||||
};
|
||||
|
||||
let viewDepthKey: InjectionKey<0> = Symbol(0);
|
||||
const viewDepthKey: InjectionKey<0> = Symbol(0);
|
||||
export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
name: 'IonRouterOutlet',
|
||||
name: "IonRouterOutlet",
|
||||
setup() {
|
||||
defineCustomElement();
|
||||
|
||||
@ -33,14 +44,14 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
let previousMatchedRouteRef: Ref | undefined;
|
||||
let previousMatchedPath: string | undefined;
|
||||
|
||||
provide(viewDepthKey, depth + 1)
|
||||
provide(viewDepthKey, depth + 1);
|
||||
provide(matchedRouteKey, matchedRouteRef);
|
||||
|
||||
const ionRouterOutlet = ref();
|
||||
const id = generateId('ion-router-outlet');
|
||||
const id = generateId("ion-router-outlet");
|
||||
|
||||
const ionRouter: any = inject('navManager');
|
||||
const viewStacks: any = inject('viewStacks');
|
||||
const ionRouter: any = inject("navManager");
|
||||
const viewStacks: any = inject("viewStacks");
|
||||
|
||||
const components = shallowRef([]);
|
||||
|
||||
@ -55,57 +66,63 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
* So going from /page/1 to /page/2 would not fire this callback if we
|
||||
* only listened for changes to matchedRouteRef.
|
||||
*/
|
||||
watch(() => [route, matchedRouteRef.value], ([currentRoute, currentMatchedRouteRef]) => {
|
||||
/**
|
||||
* This callback checks whether or not a router outlet
|
||||
* needs to respond to a change in the matched route.
|
||||
* It handles a few cases:
|
||||
* 1. The matched route is undefined. This means that
|
||||
* the matched route is not applicable to this outlet.
|
||||
* For example, a /settings route is not applicable
|
||||
* to a /tabs/... route.
|
||||
*
|
||||
* Note: When going back to a tabs outlet from a non-tabs outlet,
|
||||
* the tabs outlet should NOT attempt a page transition from the
|
||||
* previous tab to the active tab. To do this we compare the current
|
||||
* route with the previous route. Unfortunately, we cannot rely on the
|
||||
* previous value provided by Vue in the watch callback. This is because
|
||||
* when coming back to the tabs context, the previous matched route will
|
||||
* be undefined (because nothing in the tabs context matches /settings)
|
||||
* but the current matched route will be defined and so a transition
|
||||
* will always occur.
|
||||
*
|
||||
* 2. The matched route is defined and is different than
|
||||
* the previously matched route. This is the most
|
||||
* common case such as when you go from /page1 to /page2.
|
||||
*
|
||||
* 3. The matched route is the same but the parameters are different.
|
||||
* This is a special case for parameterized routes (i.e. /page/:id).
|
||||
* When going from /page/1 to /page/2, the matched route object will
|
||||
* be the same, but we still need to perform a page transition. To do this
|
||||
* we check if the parameters are different (i.e. 1 vs 2). To avoid enumerating
|
||||
* all of the keys in the params object, we check the url path to
|
||||
* see if they are different after ensuring we are in a parameterized route.
|
||||
*/
|
||||
if (currentMatchedRouteRef !== undefined) {
|
||||
const matchedDifferentRoutes = currentMatchedRouteRef !== previousMatchedRouteRef;
|
||||
const matchedDifferentParameterRoutes = (
|
||||
currentRoute.matched[currentRoute.matched.length - 1] === currentMatchedRouteRef &&
|
||||
currentRoute.path !== previousMatchedPath
|
||||
);
|
||||
watch(
|
||||
() => [route, matchedRouteRef.value],
|
||||
([currentRoute, currentMatchedRouteRef]) => {
|
||||
/**
|
||||
* This callback checks whether or not a router outlet
|
||||
* needs to respond to a change in the matched route.
|
||||
* It handles a few cases:
|
||||
* 1. The matched route is undefined. This means that
|
||||
* the matched route is not applicable to this outlet.
|
||||
* For example, a /settings route is not applicable
|
||||
* to a /tabs/... route.
|
||||
*
|
||||
* Note: When going back to a tabs outlet from a non-tabs outlet,
|
||||
* the tabs outlet should NOT attempt a page transition from the
|
||||
* previous tab to the active tab. To do this we compare the current
|
||||
* route with the previous route. Unfortunately, we cannot rely on the
|
||||
* previous value provided by Vue in the watch callback. This is because
|
||||
* when coming back to the tabs context, the previous matched route will
|
||||
* be undefined (because nothing in the tabs context matches /settings)
|
||||
* but the current matched route will be defined and so a transition
|
||||
* will always occur.
|
||||
*
|
||||
* 2. The matched route is defined and is different than
|
||||
* the previously matched route. This is the most
|
||||
* common case such as when you go from /page1 to /page2.
|
||||
*
|
||||
* 3. The matched route is the same but the parameters are different.
|
||||
* This is a special case for parameterized routes (i.e. /page/:id).
|
||||
* When going from /page/1 to /page/2, the matched route object will
|
||||
* be the same, but we still need to perform a page transition. To do this
|
||||
* we check if the parameters are different (i.e. 1 vs 2). To avoid enumerating
|
||||
* all of the keys in the params object, we check the url path to
|
||||
* see if they are different after ensuring we are in a parameterized route.
|
||||
*/
|
||||
if (currentMatchedRouteRef !== undefined) {
|
||||
const matchedDifferentRoutes =
|
||||
currentMatchedRouteRef !== previousMatchedRouteRef;
|
||||
const matchedDifferentParameterRoutes =
|
||||
currentRoute.matched[currentRoute.matched.length - 1] ===
|
||||
currentMatchedRouteRef &&
|
||||
currentRoute.path !== previousMatchedPath;
|
||||
|
||||
if (matchedDifferentRoutes || matchedDifferentParameterRoutes) {
|
||||
setupViewItem(matchedRouteRef);
|
||||
if (matchedDifferentRoutes || matchedDifferentParameterRoutes) {
|
||||
setupViewItem(matchedRouteRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousMatchedRouteRef = currentMatchedRouteRef;
|
||||
previousMatchedPath = currentRoute.path;
|
||||
});
|
||||
previousMatchedRouteRef = currentMatchedRouteRef;
|
||||
previousMatchedPath = currentRoute.path;
|
||||
}
|
||||
);
|
||||
|
||||
const canStart = () => {
|
||||
const config = getConfig();
|
||||
const swipeEnabled = config && config.get('swipeBackEnabled', ionRouterOutlet.value.mode === 'ios');
|
||||
const swipeEnabled =
|
||||
config &&
|
||||
config.get("swipeBackEnabled", ionRouterOutlet.value.mode === "ios");
|
||||
if (!swipeEnabled) return false;
|
||||
|
||||
const stack = viewStacks.getViewStack(id);
|
||||
@ -117,14 +134,20 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
* to make sure the view is in the outlet we want.
|
||||
*/
|
||||
const routeInfo = ionRouter.getLeavingRouteInfo();
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(
|
||||
{ pathname: routeInfo.pushedByRoute || "" },
|
||||
id
|
||||
);
|
||||
|
||||
return !!enteringViewItem;
|
||||
}
|
||||
};
|
||||
const onStart = async () => {
|
||||
const routeInfo = ionRouter.getLeavingRouteInfo();
|
||||
const { routerAnimation } = routeInfo;
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(
|
||||
{ pathname: routeInfo.pushedByRoute || "" },
|
||||
id
|
||||
);
|
||||
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
|
||||
if (leavingViewItem) {
|
||||
@ -133,17 +156,14 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
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.
|
||||
*/
|
||||
* 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 &&
|
||||
customAnimation !== undefined
|
||||
) {
|
||||
if (animationBuilder === undefined && customAnimation !== undefined) {
|
||||
animationBuilder = customAnimation;
|
||||
}
|
||||
|
||||
@ -152,7 +172,7 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
await transition(
|
||||
enteringEl,
|
||||
leavingEl,
|
||||
'back',
|
||||
"back",
|
||||
ionRouter.canGoBack(2),
|
||||
true,
|
||||
animationBuilder
|
||||
@ -160,7 +180,7 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
const onEnd = (shouldContinue: boolean) => {
|
||||
if (shouldContinue) {
|
||||
@ -179,18 +199,21 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
* re-hide the page that was going to enter.
|
||||
*/
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
|
||||
enteringViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||
enteringViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(
|
||||
{ pathname: routeInfo.pushedByRoute || "" },
|
||||
id
|
||||
);
|
||||
enteringViewItem.ionPageElement.setAttribute("aria-hidden", "true");
|
||||
enteringViewItem.ionPageElement.classList.add("ion-page-hidden");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(ionRouterOutlet, () => {
|
||||
ionRouterOutlet.value.swipeHandler = {
|
||||
canStart,
|
||||
onStart,
|
||||
onEnd
|
||||
}
|
||||
onEnd,
|
||||
};
|
||||
});
|
||||
|
||||
const transition = async (
|
||||
@ -210,9 +233,10 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
enteringEl.classList.add('ion-page-invisible');
|
||||
enteringEl.classList.add("ion-page-invisible");
|
||||
|
||||
const hasRootDirection = direction === undefined || direction === 'root' || direction === 'none';
|
||||
const hasRootDirection =
|
||||
direction === undefined || direction === "root" || direction === "none";
|
||||
const result = await ionRouterOutlet.value.commit(enteringEl, leavingEl, {
|
||||
/**
|
||||
* replace operations result in a direction of none.
|
||||
@ -222,22 +246,35 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
|
||||
* they want an animation to be played even
|
||||
* though it is a replace operation.
|
||||
*/
|
||||
duration: hasRootDirection && animationBuilder === undefined ? 0 : undefined,
|
||||
duration:
|
||||
hasRootDirection && animationBuilder === undefined ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation,
|
||||
animationBuilder
|
||||
animationBuilder,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageTransition = async () => {
|
||||
const routeInfo = ionRouter.getCurrentRouteInfo();
|
||||
const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname, delta } = routeInfo;
|
||||
const {
|
||||
routerDirection,
|
||||
routerAction,
|
||||
routerAnimation,
|
||||
prevRouteLastPathname,
|
||||
delta,
|
||||
} = routeInfo;
|
||||
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(
|
||||
routeInfo,
|
||||
id
|
||||
);
|
||||
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(
|
||||
routeInfo,
|
||||
id
|
||||
);
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
|
||||
/**
|
||||
@ -253,7 +290,10 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
if (!leavingViewItem && prevRouteLastPathname) {
|
||||
leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id);
|
||||
leavingViewItem = viewStacks.findViewItemByPathname(
|
||||
prevRouteLastPathname,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,29 +315,44 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
* return early for swipe to go back when
|
||||
* going from a non-tabs page to a tabs page.
|
||||
*/
|
||||
if (isViewVisible(enteringEl) && leavingViewItem?.ionPageElement !== undefined && !isViewVisible(leavingViewItem.ionPageElement)) {
|
||||
if (
|
||||
isViewVisible(enteringEl) &&
|
||||
leavingViewItem?.ionPageElement !== undefined &&
|
||||
!isViewVisible(leavingViewItem.ionPageElement)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
|
||||
fireLifecycle(
|
||||
enteringViewItem.vueComponent,
|
||||
enteringViewItem.vueComponentRef,
|
||||
LIFECYCLE_WILL_ENTER
|
||||
);
|
||||
|
||||
if (leavingViewItem?.ionPageElement && enteringViewItem !== leavingViewItem) {
|
||||
if (
|
||||
leavingViewItem?.ionPageElement &&
|
||||
enteringViewItem !== leavingViewItem
|
||||
) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, leavingViewItem.vueComponentRef, LIFECYCLE_WILL_LEAVE);
|
||||
fireLifecycle(
|
||||
leavingViewItem.vueComponent,
|
||||
leavingViewItem.vueComponentRef,
|
||||
LIFECYCLE_WILL_LEAVE
|
||||
);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
* 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' &&
|
||||
routerDirection === "back" &&
|
||||
customAnimation !== undefined
|
||||
) {
|
||||
animationBuilder = customAnimation;
|
||||
@ -314,17 +369,22 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
animationBuilder
|
||||
);
|
||||
|
||||
leavingEl.classList.add('ion-page-hidden');
|
||||
leavingEl.setAttribute('aria-hidden', 'true');
|
||||
leavingEl.classList.add("ion-page-hidden");
|
||||
leavingEl.setAttribute("aria-hidden", "true");
|
||||
|
||||
const usingLinearNavigation = viewStacks.size() === 1;
|
||||
|
||||
if (routerAction === 'replace') {
|
||||
if (routerAction === "replace") {
|
||||
leavingViewItem.mount = false;
|
||||
leavingViewItem.ionPageElement = undefined;
|
||||
leavingViewItem.ionRoute = false;
|
||||
} else if (!(routerAction === 'push' && routerDirection === 'forward')) {
|
||||
const shouldLeavingViewBeRemoved = routerDirection !== 'none' && leavingViewItem && (enteringViewItem !== leavingViewItem);
|
||||
} else if (
|
||||
!(routerAction === "push" && routerDirection === "forward")
|
||||
) {
|
||||
const shouldLeavingViewBeRemoved =
|
||||
routerDirection !== "none" &&
|
||||
leavingViewItem &&
|
||||
enteringViewItem !== leavingViewItem;
|
||||
if (shouldLeavingViewBeRemoved) {
|
||||
leavingViewItem.mount = false;
|
||||
leavingViewItem.ionPageElement = undefined;
|
||||
@ -344,7 +404,11 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
viewStacks.mountIntermediaryViews(id, leavingViewItem, delta);
|
||||
}
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, leavingViewItem.vueComponentRef, LIFECYCLE_DID_LEAVE);
|
||||
fireLifecycle(
|
||||
leavingViewItem.vueComponent,
|
||||
leavingViewItem.vueComponentRef,
|
||||
LIFECYCLE_DID_LEAVE
|
||||
);
|
||||
} else {
|
||||
/**
|
||||
* If there is no leaving element, just show
|
||||
@ -352,13 +416,19 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
* in case ion-content's fullscreen callback
|
||||
* is running. Otherwise we'd have a flicker.
|
||||
*/
|
||||
requestAnimationFrame(() => enteringEl.classList.remove('ion-page-invisible'));
|
||||
requestAnimationFrame(() =>
|
||||
enteringEl.classList.remove("ion-page-invisible")
|
||||
);
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_DID_ENTER);
|
||||
fireLifecycle(
|
||||
enteringViewItem.vueComponent,
|
||||
enteringViewItem.vueComponentRef,
|
||||
LIFECYCLE_DID_ENTER
|
||||
);
|
||||
|
||||
components.value = viewStacks.getChildrenToRender(id);
|
||||
}
|
||||
};
|
||||
|
||||
const setupViewItem = (matchedRouteRef: any) => {
|
||||
const firstMatchedRoute = route.matched[0];
|
||||
@ -378,16 +448,25 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
*/
|
||||
if (
|
||||
!matchedRouteRef.value ||
|
||||
(matchedRouteRef.value !== firstMatchedRoute && firstMatchedRoute.path !== parentOutletPath)
|
||||
(matchedRouteRef.value !== firstMatchedRoute &&
|
||||
firstMatchedRoute.path !== parentOutletPath)
|
||||
) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRoute = ionRouter.getCurrentRouteInfo();
|
||||
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);
|
||||
let enteringViewItem = viewStacks.findViewItemByRouteInfo(
|
||||
currentRoute,
|
||||
id
|
||||
);
|
||||
|
||||
if (!enteringViewItem) {
|
||||
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
|
||||
enteringViewItem = viewStacks.createViewItem(
|
||||
id,
|
||||
matchedRouteRef.value.components.default,
|
||||
matchedRouteRef.value,
|
||||
currentRoute
|
||||
);
|
||||
viewStacks.add(enteringViewItem);
|
||||
}
|
||||
|
||||
@ -396,13 +475,13 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
enteringViewItem.registerCallback = () => {
|
||||
handlePageTransition();
|
||||
enteringViewItem.registerCallback = undefined;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
handlePageTransition();
|
||||
}
|
||||
|
||||
components.value = viewStacks.getChildrenToRender(id);
|
||||
}
|
||||
};
|
||||
|
||||
if (matchedRouteRef.value) {
|
||||
setupViewItem(matchedRouteRef);
|
||||
@ -430,88 +509,90 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
|
||||
* Page should be hidden initially
|
||||
* to avoid flickering.
|
||||
*/
|
||||
ionPageEl.classList.add('ion-page-invisible');
|
||||
ionPageEl.classList.add("ion-page-invisible");
|
||||
viewItem.registerCallback();
|
||||
|
||||
/**
|
||||
* If there is no registerCallback, then
|
||||
* this component is likely being re-registered
|
||||
* as a result of a hot module replacement.
|
||||
* We need to see if the oldIonPageEl has
|
||||
* .ion-page-invisible. If it does not then we
|
||||
* need to remove it from the new ionPageEl otherwise
|
||||
* the page will be hidden when it is replaced.
|
||||
*/
|
||||
} else if (oldIonPageEl && !oldIonPageEl.classList.contains('ion-page-invisible')) {
|
||||
ionPageEl.classList.remove('ion-page-invisible');
|
||||
/**
|
||||
* If there is no registerCallback, then
|
||||
* this component is likely being re-registered
|
||||
* as a result of a hot module replacement.
|
||||
* We need to see if the oldIonPageEl has
|
||||
* .ion-page-invisible. If it does not then we
|
||||
* need to remove it from the new ionPageEl otherwise
|
||||
* the page will be hidden when it is replaced.
|
||||
*/
|
||||
} else if (
|
||||
oldIonPageEl &&
|
||||
!oldIonPageEl.classList.contains("ion-page-invisible")
|
||||
) {
|
||||
ionPageEl.classList.remove("ion-page-invisible");
|
||||
}
|
||||
};
|
||||
};
|
||||
return {
|
||||
id,
|
||||
components,
|
||||
injectedRoute,
|
||||
ionRouterOutlet,
|
||||
registerIonPage
|
||||
}
|
||||
registerIonPage,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const { components, registerIonPage, injectedRoute } = this;
|
||||
|
||||
return h(
|
||||
'ion-router-outlet',
|
||||
{ ref: 'ionRouterOutlet' },
|
||||
components && components.map((c: any) => {
|
||||
let props = {
|
||||
ref: c.vueComponentRef,
|
||||
key: c.pathname,
|
||||
registerIonPage: (ionPageEl: HTMLElement) => registerIonPage(c, ionPageEl)
|
||||
}
|
||||
"ion-router-outlet",
|
||||
{ ref: "ionRouterOutlet" },
|
||||
components &&
|
||||
components.map((c: any) => {
|
||||
let props = {
|
||||
ref: c.vueComponentRef,
|
||||
key: c.pathname,
|
||||
registerIonPage: (ionPageEl: HTMLElement) =>
|
||||
registerIonPage(c, ionPageEl),
|
||||
};
|
||||
|
||||
/**
|
||||
* IonRouterOutlet does not support named outlets.
|
||||
*/
|
||||
const routePropsOption = c.matchedRoute?.props?.default;
|
||||
/**
|
||||
* IonRouterOutlet does not support named outlets.
|
||||
*/
|
||||
const routePropsOption = c.matchedRoute?.props?.default;
|
||||
|
||||
/**
|
||||
* Since IonRouterOutlet renders multiple components,
|
||||
* each render will cause all props functions to be
|
||||
* called again. As a result, we need to cache the function
|
||||
* result and provide it on each render so that the props
|
||||
* are not lost when navigating from and back to a page.
|
||||
* When a component is destroyed and re-created, the
|
||||
* function is called again.
|
||||
*/
|
||||
const getPropsFunctionResult = () => {
|
||||
const cachedPropsResult = c.vueComponentData?.propsFunctionResult;
|
||||
if (cachedPropsResult) {
|
||||
return cachedPropsResult;
|
||||
} else {
|
||||
const propsFunctionResult = routePropsOption(injectedRoute);
|
||||
c.vueComponentData = {
|
||||
...c.vueComponentData,
|
||||
propsFunctionResult
|
||||
};
|
||||
return propsFunctionResult;
|
||||
}
|
||||
}
|
||||
const routeProps = routePropsOption
|
||||
? routePropsOption === true
|
||||
? c.params
|
||||
: typeof routePropsOption === 'function'
|
||||
? getPropsFunctionResult()
|
||||
: routePropsOption
|
||||
: null
|
||||
/**
|
||||
* Since IonRouterOutlet renders multiple components,
|
||||
* each render will cause all props functions to be
|
||||
* called again. As a result, we need to cache the function
|
||||
* result and provide it on each render so that the props
|
||||
* are not lost when navigating from and back to a page.
|
||||
* When a component is destroyed and re-created, the
|
||||
* function is called again.
|
||||
*/
|
||||
const getPropsFunctionResult = () => {
|
||||
const cachedPropsResult = c.vueComponentData?.propsFunctionResult;
|
||||
if (cachedPropsResult) {
|
||||
return cachedPropsResult;
|
||||
} else {
|
||||
const propsFunctionResult = routePropsOption(injectedRoute);
|
||||
c.vueComponentData = {
|
||||
...c.vueComponentData,
|
||||
propsFunctionResult,
|
||||
};
|
||||
return propsFunctionResult;
|
||||
}
|
||||
};
|
||||
const routeProps = routePropsOption
|
||||
? routePropsOption === true
|
||||
? c.params
|
||||
: typeof routePropsOption === "function"
|
||||
? getPropsFunctionResult()
|
||||
: routePropsOption
|
||||
: null;
|
||||
|
||||
props = {
|
||||
...props,
|
||||
...routeProps
|
||||
}
|
||||
props = {
|
||||
...props,
|
||||
...routeProps,
|
||||
};
|
||||
|
||||
return h(
|
||||
c.vueComponent,
|
||||
props
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
return h(c.vueComponent, props);
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { h, defineComponent, getCurrentInstance, inject, VNode } from 'vue';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-tab-bar.js';
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-tab-bar.js";
|
||||
import type { VNode } from "vue";
|
||||
import { h, defineComponent, getCurrentInstance, inject } from "vue";
|
||||
|
||||
// TODO(FW-2969): types
|
||||
|
||||
interface TabState {
|
||||
activeTab?: string;
|
||||
@ -8,11 +11,11 @@ interface TabState {
|
||||
|
||||
interface Tab {
|
||||
originalHref: string;
|
||||
currentHref: string,
|
||||
ref: VNode
|
||||
currentHref: string;
|
||||
ref: VNode;
|
||||
}
|
||||
|
||||
const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
|
||||
const isTabButton = (child: any) => child.type?.name === "IonTabButton";
|
||||
|
||||
const getTabs = (nodes: VNode[]) => {
|
||||
let tabs: VNode[] = [];
|
||||
@ -26,25 +29,27 @@ const getTabs = (nodes: VNode[]) => {
|
||||
});
|
||||
|
||||
return tabs;
|
||||
}
|
||||
};
|
||||
|
||||
export const IonTabBar = defineComponent({
|
||||
name: 'IonTabBar',
|
||||
name: "IonTabBar",
|
||||
props: {
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
_tabsWillChange: { type: Function, default: () => {} },
|
||||
_tabsDidChange: { type: Function, default: () => {} }
|
||||
_tabsDidChange: { type: Function, default: () => {} },
|
||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabState: {
|
||||
activeTab: undefined,
|
||||
tabs: {}
|
||||
tabs: {},
|
||||
},
|
||||
tabVnodes: []
|
||||
}
|
||||
tabVnodes: [],
|
||||
};
|
||||
},
|
||||
updated() {
|
||||
this.setupTabState(inject('navManager'));
|
||||
this.setupTabState(inject("navManager"));
|
||||
},
|
||||
methods: {
|
||||
setupTabState(ionRouter: any) {
|
||||
@ -57,13 +62,15 @@ export const IonTabBar = defineComponent({
|
||||
*/
|
||||
const tabState: TabState = this.$data.tabState;
|
||||
const currentInstance = getCurrentInstance();
|
||||
const tabs = this.$data.tabVnodes = getTabs((currentInstance.subTree.children || []) as VNode[]);
|
||||
tabs.forEach(child => {
|
||||
const tabs = (this.$data.tabVnodes = getTabs(
|
||||
(currentInstance.subTree.children || []) as VNode[]
|
||||
));
|
||||
tabs.forEach((child) => {
|
||||
tabState.tabs[child.props.tab] = {
|
||||
originalHref: child.props.href,
|
||||
currentHref: child.props.href,
|
||||
ref: child
|
||||
}
|
||||
ref: child,
|
||||
};
|
||||
|
||||
/**
|
||||
* Passing this prop to each tab button
|
||||
@ -81,11 +88,10 @@ export const IonTabBar = defineComponent({
|
||||
const { tabs, activeTab: prevActiveTab } = this.$data.tabState;
|
||||
const tabState = this.$data.tabState;
|
||||
const tabKeys = Object.keys(tabs);
|
||||
const activeTab = tabKeys
|
||||
.find(key => {
|
||||
const href = tabs[key].originalHref;
|
||||
return currentRoute.pathname.startsWith(href);
|
||||
});
|
||||
const activeTab = tabKeys.find((key) => {
|
||||
const href = tabs[key].originalHref;
|
||||
return currentRoute.pathname.startsWith(href);
|
||||
});
|
||||
|
||||
/**
|
||||
* For each tab, check to see if the
|
||||
@ -94,13 +100,12 @@ export const IonTabBar = defineComponent({
|
||||
*/
|
||||
childNodes.forEach((child: VNode) => {
|
||||
const tab = tabs[child.props.tab];
|
||||
if (!tab || (tab.originalHref !== child.props.href)) {
|
||||
|
||||
if (!tab || tab.originalHref !== child.props.href) {
|
||||
tabs[child.props.tab] = {
|
||||
originalHref: child.props.href,
|
||||
currentHref: child.props.href,
|
||||
ref: child
|
||||
}
|
||||
ref: child,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@ -113,34 +118,38 @@ export const IonTabBar = defineComponent({
|
||||
* If we went to tab2 then back to tab1, we should
|
||||
* land on /tabs/tab1/child instead of /tabs/tab1.
|
||||
*/
|
||||
if (activeTab !== prevActiveTab || (prevHref !== currentRoute.pathname)) {
|
||||
|
||||
if (activeTab !== prevActiveTab || prevHref !== currentRoute.pathname) {
|
||||
/**
|
||||
* By default the search is `undefined` in Ionic Vue,
|
||||
* but Vue Router can set the search to the empty string.
|
||||
* We check for truthy here because empty string is falsy
|
||||
* and currentRoute.search cannot ever be a boolean.
|
||||
*/
|
||||
const search = (currentRoute.search) ? `?${currentRoute.search}` : '';
|
||||
const search = currentRoute.search ? `?${currentRoute.search}` : "";
|
||||
tabs[activeTab] = {
|
||||
...tabs[activeTab],
|
||||
currentHref: currentRoute.pathname + search
|
||||
}
|
||||
currentHref: currentRoute.pathname + search,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If navigating back and the tabs change,
|
||||
* set the previous tab back to its original href.
|
||||
*/
|
||||
if (currentRoute.routerAction === 'pop' && (activeTab !== prevActiveTab)) {
|
||||
if (
|
||||
currentRoute.routerAction === "pop" &&
|
||||
activeTab !== prevActiveTab
|
||||
) {
|
||||
tabs[prevActiveTab] = {
|
||||
...tabs[prevActiveTab],
|
||||
currentHref: tabs[prevActiveTab].originalHref
|
||||
}
|
||||
currentHref: tabs[prevActiveTab].originalHref,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const activeChild = childNodes.find((child: VNode) => isTabButton(child) && child.props?.tab === activeTab);
|
||||
const activeChild = childNodes.find(
|
||||
(child: VNode) => isTabButton(child) && child.props?.tab === activeTab
|
||||
);
|
||||
const tabBar = this.$refs.ionTabBar;
|
||||
const tabDidChange = activeTab !== prevActiveTab;
|
||||
if (tabBar) {
|
||||
@ -151,34 +160,36 @@ export const IonTabBar = defineComponent({
|
||||
tabBar.selectedTab = tabState.activeTab = activeTab;
|
||||
|
||||
tabDidChange && this.$props._tabsDidChange(activeTab);
|
||||
/**
|
||||
* When going to a tab that does
|
||||
* not have an associated ion-tab-button
|
||||
* we need to remove the selected state from
|
||||
* the old tab.
|
||||
*/
|
||||
/**
|
||||
* When going to a tab that does
|
||||
* not have an associated ion-tab-button
|
||||
* we need to remove the selected state from
|
||||
* the old tab.
|
||||
*/
|
||||
} else {
|
||||
tabBar.selectedTab = tabState.activeTab = '';
|
||||
tabBar.selectedTab = tabState.activeTab = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const ionRouter: any = inject('navManager');
|
||||
const ionRouter: any = inject("navManager");
|
||||
|
||||
this.setupTabState(ionRouter);
|
||||
|
||||
ionRouter.registerHistoryChangeListener(() => this.checkActiveTab(ionRouter));
|
||||
ionRouter.registerHistoryChangeListener(() =>
|
||||
this.checkActiveTab(ionRouter)
|
||||
);
|
||||
},
|
||||
setup(_, { slots }) {
|
||||
defineCustomElement();
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
'ion-tab-bar',
|
||||
{ ref: 'ionTabBar' },
|
||||
"ion-tab-bar",
|
||||
{ ref: "ionTabBar" },
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { h, defineComponent, inject } from 'vue';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-tab-button.js';
|
||||
import { defineCustomElement } from "@ionic/core/components/ion-tab-button.js";
|
||||
import { h, defineComponent, inject } from "vue";
|
||||
|
||||
export const IonTabButton = /*@__PURE__*/ defineComponent({
|
||||
name: 'IonTabButton',
|
||||
name: "IonTabButton",
|
||||
props: {
|
||||
_getTabState: { type: Function, default: () => { return {} } },
|
||||
_getTabState: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
disabled: Boolean,
|
||||
download: String,
|
||||
href: String,
|
||||
@ -12,12 +17,13 @@ export const IonTabButton = /*@__PURE__*/ defineComponent({
|
||||
layout: String,
|
||||
selected: Boolean,
|
||||
tab: String,
|
||||
target: String
|
||||
target: String,
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
defineCustomElement();
|
||||
|
||||
const ionRouter: any = inject('navManager');
|
||||
// TODO(FW-2969): type
|
||||
const ionRouter: any = inject("navManager");
|
||||
const onClick = (ev: Event) => {
|
||||
if (ev.cancelable) {
|
||||
ev.preventDefault();
|
||||
@ -49,18 +55,18 @@ export const IonTabButton = /*@__PURE__*/ defineComponent({
|
||||
ionRouter.resetTab(tab);
|
||||
}
|
||||
} else {
|
||||
ionRouter.changeTab(tab, currentHref)
|
||||
ionRouter.changeTab(tab, currentHref);
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
return h(
|
||||
'ion-tab-button',
|
||||
"ion-tab-button",
|
||||
{
|
||||
onClick,
|
||||
...props
|
||||
...props,
|
||||
},
|
||||
slots.default && slots.default()
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { h, defineComponent, VNode } from 'vue';
|
||||
import type { VNode } from "vue";
|
||||
import { h, defineComponent } from "vue";
|
||||
|
||||
const WILL_CHANGE = 'ionTabsWillChange';
|
||||
const DID_CHANGE = 'ionTabsDidChange';
|
||||
const WILL_CHANGE = "ionTabsWillChange";
|
||||
const DID_CHANGE = "ionTabsDidChange";
|
||||
|
||||
// TODO(FW-2969): types
|
||||
|
||||
export const IonTabs = /*@__PURE__*/ defineComponent({
|
||||
name: 'IonTabs',
|
||||
name: "IonTabs",
|
||||
emits: [WILL_CHANGE, DID_CHANGE],
|
||||
render() {
|
||||
const { $slots: slots, $emit } = this;
|
||||
@ -16,22 +19,31 @@ export const IonTabs = /*@__PURE__*/ defineComponent({
|
||||
* inside of ion-tabs.
|
||||
*/
|
||||
if (slottedContent && slottedContent.length > 0) {
|
||||
routerOutlet = slottedContent.find((child: VNode) => child.type && (child.type as any).name === 'IonRouterOutlet');
|
||||
routerOutlet = slottedContent.find(
|
||||
(child: VNode) =>
|
||||
child.type && (child.type as any).name === "IonRouterOutlet"
|
||||
);
|
||||
}
|
||||
|
||||
if (!routerOutlet) {
|
||||
throw new Error('IonTabs must contain an IonRouterOutlet. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information.');
|
||||
throw new Error(
|
||||
"IonTabs must contain an IonRouterOutlet. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information."
|
||||
);
|
||||
}
|
||||
|
||||
let childrenToRender = [
|
||||
h('div', {
|
||||
class: 'tabs-inner',
|
||||
style: {
|
||||
'position': 'relative',
|
||||
'flex': '1',
|
||||
'contain': 'layout size style'
|
||||
}
|
||||
}, routerOutlet)
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class: "tabs-inner",
|
||||
style: {
|
||||
position: "relative",
|
||||
flex: "1",
|
||||
contain: "layout size style",
|
||||
},
|
||||
},
|
||||
routerOutlet
|
||||
),
|
||||
];
|
||||
|
||||
/**
|
||||
@ -44,13 +56,17 @@ export const IonTabs = /*@__PURE__*/ defineComponent({
|
||||
* Render all content except for router outlet
|
||||
* since that needs to be inside of `.tabs-inner`.
|
||||
*/
|
||||
const filteredContent = slottedContent.filter((child: VNode) => (
|
||||
!child.type ||
|
||||
(child.type && (child.type as any).name !== 'IonRouterOutlet')
|
||||
));
|
||||
const filteredContent = slottedContent.filter(
|
||||
(child: VNode) =>
|
||||
!child.type ||
|
||||
(child.type && (child.type as any).name !== "IonRouterOutlet")
|
||||
);
|
||||
|
||||
const slottedTabBar = filteredContent.find((child: VNode) => child.type && (child.type as any).name === 'IonTabBar');
|
||||
const hasTopSlotTabBar = slottedTabBar && slottedTabBar.props?.slot === 'top';
|
||||
const slottedTabBar = filteredContent.find(
|
||||
(child: VNode) => child.type && (child.type as any).name === "IonTabBar"
|
||||
);
|
||||
const hasTopSlotTabBar =
|
||||
slottedTabBar && slottedTabBar.props?.slot === "top";
|
||||
|
||||
if (slottedTabBar) {
|
||||
if (!slottedTabBar.props) {
|
||||
@ -63,41 +79,37 @@ export const IonTabs = /*@__PURE__*/ defineComponent({
|
||||
* TODO: We may want to move logic from the tab bar into here
|
||||
* so we do not have code split across two components.
|
||||
*/
|
||||
slottedTabBar.props._tabsWillChange = (tab: string) => $emit(WILL_CHANGE, { tab });
|
||||
slottedTabBar.props._tabsDidChange = (tab: string) => $emit(DID_CHANGE, { tab });
|
||||
slottedTabBar.props._tabsWillChange = (tab: string) =>
|
||||
$emit(WILL_CHANGE, { tab });
|
||||
slottedTabBar.props._tabsDidChange = (tab: string) =>
|
||||
$emit(DID_CHANGE, { tab });
|
||||
}
|
||||
|
||||
if (hasTopSlotTabBar) {
|
||||
childrenToRender = [
|
||||
...filteredContent,
|
||||
...childrenToRender
|
||||
];
|
||||
childrenToRender = [...filteredContent, ...childrenToRender];
|
||||
} else {
|
||||
childrenToRender = [
|
||||
...childrenToRender,
|
||||
...filteredContent
|
||||
]
|
||||
childrenToRender = [...childrenToRender, ...filteredContent];
|
||||
}
|
||||
}
|
||||
|
||||
return h(
|
||||
'ion-tabs',
|
||||
"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'
|
||||
}
|
||||
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",
|
||||
},
|
||||
},
|
||||
childrenToRender
|
||||
)
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user