chore(vue): add eslint and prettier (#26635)

This commit is contained in:
Liam DeBeasi
2023-01-18 18:29:25 -05:00
committed by GitHub
parent 6d4c52aa5b
commit dc27736bd5
25 changed files with 5693 additions and 574 deletions

View File

@ -12,7 +12,7 @@ runs:
path: ./core
filename: CoreBuild.zip
- name: Install Vue Dependencies
run: npm install --legacy-peer-deps
run: npm ci
shell: bash
working-directory: ./packages/vue
- name: Sync

View File

@ -0,0 +1,2 @@
vue-component-lib/
src/components/Overlays.ts

26
packages/vue/.eslintrc.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"@ionic/eslint-config/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/prefer-optional-chain": "off",
"@typescript-eslint/ban-types": "off"
}
};

View File

@ -0,0 +1,4 @@
vue-component-lib
proxies.ts
components.d.ts
src/components/Overlays.ts

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,10 @@
"version": "6.5.0",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"lint": "echo add linter",
"lint.fix": "npm run lint -- --fix",
"eslint": "eslint src",
"prettier": "prettier \"./src/**/*.{html,ts,tsx,js,jsx}\"",
"lint": "npm run eslint && npm run prettier -- --write --cache",
"lint.fix": "npm run eslint -- --fix && npm run prettier -- --write --cache",
"test": "jest",
"build": "npm run clean && npm run copy && npm run copy.overlays && npm run compile && npm run bundle && npm run build.vetur && npm run build.web-types",
"bundle": "rollup --config rollup.config.js",
@ -50,9 +52,15 @@
"homepage": "https://github.com/ionic-team/ionic#readme",
"devDependencies": {
"@babel/types": "^7.18.4",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@stencil/core": "^1.17.0",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"change-case": "^4.1.1",
"eslint": "^7.32.0",
"fs-extra": "^9.1.0",
"prettier": "^2.8.3",
"rimraf": "^3.0.2",
"rollup": "^2.32.1",
"typescript": "^4.7.3",

View File

@ -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,11 +27,8 @@ 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) => {
/**
@ -39,8 +37,8 @@ export const removeTeleportedUserComponent = (component: VNode) => {
* but this was causing a bug where dismissing an overlay and then presenting
* a new overlay, would cause the new overlay to be removed.
*/
const index = userComponents.value.findIndex(cmp => cmp === component);
const index = userComponents.value.findIndex((cmp) => cmp === component);
if (index !== -1) {
userComponents.value.splice(index, 1);
}
}
};

View File

@ -1,33 +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();
// TODO(FW-2969): type
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()
)
}
});
);

View File

@ -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
)
}
}
);
};
},
});

View File

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

View File

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

View File

@ -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,24 +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();
@ -35,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([]);
@ -57,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);
@ -119,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) {
@ -135,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;
}
@ -154,7 +172,7 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
await transition(
enteringEl,
leavingEl,
'back',
"back",
ionRouter.canGoBack(2),
true,
animationBuilder
@ -162,7 +180,7 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
}
return Promise.resolve();
}
};
const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) {
@ -181,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 = (
@ -203,7 +224,7 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
progressAnimation: boolean,
animationBuilder?: AnimationBuilder
) => {
return new Promise(resolve => {
return new Promise((resolve) => {
if (skipTransition) {
skipTransition = false;
return resolve(false);
@ -215,38 +236,60 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
requestAnimationFrame(() => {
requestAnimationFrame(async () => {
enteringEl.classList.add('ion-page-invisible');
enteringEl.classList.add("ion-page-invisible");
const hasRootDirection = direction === undefined || direction === 'root' || direction === 'none';
const result = await ionRouterOutlet.value.commit(enteringEl, leavingEl, {
deepWait: true,
/**
* replace operations result in a direction of none.
* These typically do not have need animations, so we set
* the duration to 0. However, if a developer explicitly
* passes an animationBuilder, we should assume that
* they want an animation to be played even
* though it is a replace operation.
*/
duration: hasRootDirection && animationBuilder === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation,
animationBuilder
});
const hasRootDirection =
direction === undefined ||
direction === "root" ||
direction === "none";
const result = await ionRouterOutlet.value.commit(
enteringEl,
leavingEl,
{
deepWait: true,
/**
* replace operations result in a direction of none.
* These typically do not have need animations, so we set
* the duration to 0. However, if a developer explicitly
* passes an animationBuilder, we should assume that
* they want an animation to be played even
* though it is a replace operation.
*/
duration:
hasRootDirection && animationBuilder === undefined
? 0
: undefined,
direction,
showGoBack,
progressAnimation,
animationBuilder,
}
);
return resolve(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;
/**
@ -262,7 +305,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
);
}
/**
@ -284,29 +330,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;
@ -323,17 +384,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;
@ -353,7 +419,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
@ -361,13 +431,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];
@ -387,16 +463,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);
}
@ -405,13 +490,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);
@ -439,88 +524,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);
})
);
},
});

View File

@ -1,5 +1,6 @@
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
@ -10,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[] = [];
@ -28,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) {
@ -59,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
@ -83,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
@ -96,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,
};
}
});
@ -115,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) {
@ -153,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()
)
}
}
);
};
},
});

View File

@ -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,13 +17,13 @@ export const IonTabButton = /*@__PURE__*/ defineComponent({
layout: String,
selected: Boolean,
tab: String,
target: String
target: String,
},
setup(props, { slots }) {
defineCustomElement();
// TODO(FW-2969): type
const ionRouter: any = inject('navManager');
const ionRouter: any = inject("navManager");
const onClick = (ev: Event) => {
if (ev.cancelable) {
ev.preventDefault();
@ -50,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()
)
}
}
);
};
},
});

View File

@ -1,12 +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;
@ -18,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
),
];
/**
@ -46,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) {
@ -65,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
)
}
);
},
});

View File

@ -6,17 +6,16 @@ import {
loadingController as loadingCtrl,
pickerController as pickerCtrl,
toastController as toastCtrl,
} from '@ionic/core/components';
} from "@ionic/core/components";
import { defineCustomElement as defineIonActionSheetCustomElement } from "@ionic/core/components/ion-action-sheet.js";
import { defineCustomElement as defineIonAlertCustomElement } from "@ionic/core/components/ion-alert.js";
import { defineCustomElement as defineIonLoadingCustomElement } from "@ionic/core/components/ion-loading.js";
import { defineCustomElement as defineIonModalCustomElement } from "@ionic/core/components/ion-modal.js";
import { defineCustomElement as defineIonPickerCustomElement } from "@ionic/core/components/ion-picker.js";
import { defineCustomElement as defineIonPopoverCustomElement } from "@ionic/core/components/ion-popover.js";
import { defineCustomElement as defineIonToastCustomElement } from "@ionic/core/components/ion-toast.js";
import { VueDelegate } from './framework-delegate';
import { defineCustomElement as defineIonActionSheetCustomElement } from '@ionic/core/components/ion-action-sheet.js'
import { defineCustomElement as defineIonAlertCustomElement } from '@ionic/core/components/ion-alert.js'
import { defineCustomElement as defineIonLoadingCustomElement } from '@ionic/core/components/ion-loading.js'
import { defineCustomElement as defineIonPickerCustomElement } from '@ionic/core/components/ion-picker.js'
import { defineCustomElement as defineIonToastCustomElement } from '@ionic/core/components/ion-toast.js'
import { defineCustomElement as defineIonModalCustomElement } from '@ionic/core/components/ion-modal.js'
import { defineCustomElement as defineIonPopoverCustomElement } from '@ionic/core/components/ion-popover.js'
import { VueDelegate } from "./framework-delegate";
// TODO(FW-2969): types
@ -26,8 +25,16 @@ import { defineCustomElement as defineIonPopoverCustomElement } from '@ionic/cor
* (optionally) provide a framework delegate.
*/
const createController: {
<T>(defineCustomElement: () => void, oldController: T, useDelegate?: boolean): T
} = (defineCustomElement: () => void, oldController: any, useDelegate = false) => {
<T>(
defineCustomElement: () => void,
oldController: T,
useDelegate?: boolean
): T;
} = (
defineCustomElement: () => void,
oldController: any,
useDelegate = false
) => {
const delegate = useDelegate ? VueDelegate() : undefined;
const oldCreate = oldController.create.bind(oldController);
oldController.create = (options: any) => {
@ -35,20 +42,43 @@ const createController: {
return oldCreate({
...options,
delegate
})
}
delegate,
});
};
return oldController;
}
};
const modalController = /*@__PURE__*/ createController(defineIonModalCustomElement, modalCtrl, true);
const popoverController = /*@__PURE__*/ createController(defineIonPopoverCustomElement, popoverCtrl, true);
const alertController = /*@__PURE__*/ createController(defineIonAlertCustomElement, alertCtrl);
const actionSheetController = /*@__PURE__*/ createController(defineIonActionSheetCustomElement, actionSheetCtrl);
const loadingController = /*@__PURE__*/ createController(defineIonLoadingCustomElement, loadingCtrl);
const pickerController = /*@__PURE__*/ createController(defineIonPickerCustomElement, pickerCtrl);
const toastController = /*@__PURE__*/ createController(defineIonToastCustomElement, toastCtrl);
const modalController = /*@__PURE__*/ createController(
defineIonModalCustomElement,
modalCtrl,
true
);
const popoverController = /*@__PURE__*/ createController(
defineIonPopoverCustomElement,
popoverCtrl,
true
);
const alertController = /*@__PURE__*/ createController(
defineIonAlertCustomElement,
alertCtrl
);
const actionSheetController = /*@__PURE__*/ createController(
defineIonActionSheetCustomElement,
actionSheetCtrl
);
const loadingController = /*@__PURE__*/ createController(
defineIonLoadingCustomElement,
loadingCtrl
);
const pickerController = /*@__PURE__*/ createController(
defineIonPickerCustomElement,
pickerCtrl
);
const toastController = /*@__PURE__*/ createController(
defineIonToastCustomElement,
toastCtrl
);
export {
modalController,
@ -57,5 +87,5 @@ export {
actionSheetController,
loadingController,
pickerController,
toastController
}
toastController,
};

View File

@ -1,40 +1,48 @@
import { h, Teleport, VNode } from 'vue';
import type { FrameworkDelegate } from '@ionic/core/components';
import type { FrameworkDelegate } from "@ionic/core/components";
import type { VNode } from "vue";
import { h, Teleport } from "vue";
import { addTeleportedUserComponent, removeTeleportedUserComponent } from './components/IonApp';
import {
addTeleportedUserComponent,
removeTeleportedUserComponent,
} from "./components/IonApp";
export const VueDelegate = (addFn = addTeleportedUserComponent, removeFn = removeTeleportedUserComponent): FrameworkDelegate => {
export const VueDelegate = (
addFn = addTeleportedUserComponent,
removeFn = removeTeleportedUserComponent
): FrameworkDelegate => {
let Component: VNode | undefined;
// TODO(FW-2969): types
const attachViewToDom = (parentElement: HTMLElement, component: any, componentProps: any = {}, classes?: string[]) => {
const attachViewToDom = (
parentElement: HTMLElement,
component: any,
componentProps: any = {},
classes?: string[]
) => {
/**
* Ionic Framework passes in modal and popover element
* refs as props, but if these are not defined
* on the Vue component instance as props, Vue will
* warn the user.
*/
delete componentProps['modal'];
delete componentProps['popover'];
delete componentProps["modal"];
delete componentProps["popover"];
const div = document.createElement('div');
const div = document.createElement("div");
classes && div.classList.add(...classes);
parentElement.appendChild(div);
Component = h(
Teleport,
{ to: div },
h(component, { ...componentProps })
);
Component = h(Teleport, { to: div }, h(component, { ...componentProps }));
addFn(Component);
return Promise.resolve(div);
}
};
const removeViewFromDom = () => {
Component && removeFn(Component);
return Promise.resolve();
}
};
return { attachViewToDom, removeViewFromDom }
}
return { attachViewToDom, removeViewFromDom };
};

View File

@ -1,6 +1,11 @@
import { LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core/components';
import type {
LIFECYCLE_DID_ENTER,
LIFECYCLE_DID_LEAVE,
LIFECYCLE_WILL_ENTER,
LIFECYCLE_WILL_LEAVE,
} from "@ionic/core/components";
declare module '@vue/runtime-core' {
declare module "@vue/runtime-core" {
export interface ComponentCustomOptions {
[LIFECYCLE_DID_ENTER]?: () => void;
[LIFECYCLE_DID_LEAVE]?: () => void;

View File

@ -1,15 +1,20 @@
import { BackButtonEvent } from '@ionic/core/components';
import type { BackButtonEvent } from "@ionic/core/components";
type Handler = (processNextHandler: () => void) => Promise<any> | void | null;
export interface UseBackButtonResult {
unregister: () => void;
}
export const useBackButton = (priority: number, handler: Handler): UseBackButtonResult => {
const callback = (ev: BackButtonEvent) => ev.detail.register(priority, handler);
const unregister = () => document.removeEventListener('ionBackButton', callback);
export const useBackButton = (
priority: number,
handler: Handler
): UseBackButtonResult => {
const callback = (ev: BackButtonEvent) =>
ev.detail.register(priority, handler);
const unregister = () =>
document.removeEventListener("ionBackButton", callback);
document.addEventListener('ionBackButton', callback);
document.addEventListener("ionBackButton", callback);
return { unregister };
}
};

View File

@ -1,40 +1,41 @@
import { ref, Ref } from 'vue';
import type { Ref } from "vue";
import { ref } from "vue";
export interface UseKeyboardResult {
isOpen: Ref<boolean>;
keyboardHeight: Ref<number>;
unregister: () => void
unregister: () => void;
}
export const useKeyboard = (): UseKeyboardResult => {
let isOpen = ref(false);
let keyboardHeight = ref(0);
const isOpen = ref(false);
const keyboardHeight = ref(0);
const showCallback = (ev: CustomEvent) => {
isOpen.value = true;
keyboardHeight.value = ev.detail.keyboardHeight;
}
};
const hideCallback = () => {
isOpen.value = false;
keyboardHeight.value = 0;
}
};
const unregister = () => {
if (typeof (window as any) !== 'undefined') {
window.removeEventListener('ionKeyboardDidShow', showCallback);
window.removeEventListener('ionKeyboardDidHide', hideCallback);
if (typeof (window as any) !== "undefined") {
window.removeEventListener("ionKeyboardDidShow", showCallback);
window.removeEventListener("ionKeyboardDidHide", hideCallback);
}
}
};
if (typeof (window as any) !== 'undefined') {
window.addEventListener('ionKeyboardDidShow', showCallback);
window.addEventListener('ionKeyboardDidHide', hideCallback);
if (typeof (window as any) !== "undefined") {
window.addEventListener("ionKeyboardDidShow", showCallback);
window.addEventListener("ionKeyboardDidHide", hideCallback);
}
return {
isOpen,
keyboardHeight,
unregister
}
}
unregister,
};
};

View File

@ -1,16 +1,22 @@
import { LifecycleHooks } from '../utils';
import { ComponentInternalInstance, getCurrentInstance } from 'vue';
import type { ComponentInternalInstance } from "vue";
import { getCurrentInstance } from "vue";
import { LifecycleHooks } from "../utils";
/**
* Creates an returns a function that
* can be used to provide a lifecycle hook.
*/
const injectHook = (lifecycleType: LifecycleHooks, hook: Function, component: ComponentInternalInstance | null): Function | undefined => {
const injectHook = (
lifecycleType: LifecycleHooks,
hook: Function,
component: ComponentInternalInstance | null
): Function | undefined => {
if (component) {
// Add to public instance so it is accessible to IonRouterOutlet
const target = component as any;
const hooks = target.proxy[lifecycleType] || (target.proxy[lifecycleType] = []);
const hooks =
target.proxy[lifecycleType] || (target.proxy[lifecycleType] = []);
/**
* Define property on public instances using `setup` syntax in Vue 3.x
*/
@ -29,13 +35,20 @@ const injectHook = (lifecycleType: LifecycleHooks, hook: Function, component: Co
return wrappedHook;
} else {
console.warn('[@ionic/vue]: Ionic Lifecycle Hooks can only be used during execution of setup().');
console.warn(
"[@ionic/vue]: Ionic Lifecycle Hooks can only be used during execution of setup()."
);
}
}
};
const createHook = <T extends Function = () => any>(lifecycle: LifecycleHooks) => {
return (hook: T, target: ComponentInternalInstance | null = getCurrentInstance()) => injectHook(lifecycle, hook, target);
}
const createHook = <T extends Function = () => any>(
lifecycle: LifecycleHooks
) => {
return (
hook: T,
target: ComponentInternalInstance | null = getCurrentInstance()
) => injectHook(lifecycle, hook, target);
};
export const onIonViewWillEnter = createHook(LifecycleHooks.WillEnter);
export const onIonViewDidEnter = createHook(LifecycleHooks.DidEnter);

View File

@ -1,11 +1,11 @@
import { inject } from 'vue';
import { AnimationBuilder } from '../';
import { inject } from "vue";
export type RouteAction = 'push' | 'pop' | 'replace';
export type RouteDirection = 'forward' | 'back' | 'root' | 'none';
import type { AnimationBuilder } from "../";
export type RouteAction = "push" | "pop" | "replace";
export type RouteDirection = "forward" | "back" | "root" | "none";
export interface UseIonRouterResult {
/**
* The location parameter is really of type 'RouteLocationRaw'
* imported from vue-router, but the @ionic/vue package should
@ -29,7 +29,9 @@ export interface UseIonRouterResult {
* while controlling the animation.
*/
export const useIonRouter = (): UseIonRouterResult => {
const { canGoBack, goBack, goForward, handleNavigate } = inject('navManager') as any;
const { canGoBack, goBack, goForward, handleNavigate } = inject(
"navManager"
) as any;
const navigate = (
location: any,
@ -38,23 +40,16 @@ export const useIonRouter = (): UseIonRouterResult => {
routerAnimation?: AnimationBuilder
) => handleNavigate(location, routerAction, routerDirection, routerAnimation);
const push = (
location: any,
routerAnimation?: AnimationBuilder
) => navigate(location, 'forward', 'push', routerAnimation);
const push = (location: any, routerAnimation?: AnimationBuilder) =>
navigate(location, "forward", "push", routerAnimation);
const replace = (
location: any,
routerAnimation?: AnimationBuilder
) => navigate(location, 'root', 'replace', routerAnimation);
const replace = (location: any, routerAnimation?: AnimationBuilder) =>
navigate(location, "root", "replace", routerAnimation);
const back = (
routerAnimation?: AnimationBuilder
) => goBack(routerAnimation);
const back = (routerAnimation?: AnimationBuilder) => goBack(routerAnimation);
const forward = (
routerAnimation?: AnimationBuilder
) => goForward(routerAnimation);
const forward = (routerAnimation?: AnimationBuilder) =>
goForward(routerAnimation);
return {
canGoBack,
@ -62,8 +57,6 @@ export const useIonRouter = (): UseIonRouterResult => {
replace,
back,
forward,
navigate
} as UseIonRouterResult
}
navigate,
} as UseIonRouterResult;
};

View File

@ -1,23 +1,28 @@
export * from './proxies';
export * from "./proxies";
export { UseBackButtonResult, useBackButton } from './hooks/back-button';
export { UseKeyboardResult, useKeyboard } from './hooks/keyboard';
export { onIonViewWillEnter, onIonViewDidEnter, onIonViewWillLeave, onIonViewDidLeave } from './hooks/lifecycle';
export { UseIonRouterResult, useIonRouter } from './hooks/router';
export { UseBackButtonResult, useBackButton } from "./hooks/back-button";
export { UseKeyboardResult, useKeyboard } from "./hooks/keyboard";
export {
onIonViewWillEnter,
onIonViewDidEnter,
onIonViewWillLeave,
onIonViewDidLeave,
} from "./hooks/lifecycle";
export { UseIonRouterResult, useIonRouter } from "./hooks/router";
export { IonicVue } from './ionic-vue';
export { IonicVue } from "./ionic-vue";
export { IonBackButton } from './components/IonBackButton';
export { IonPage } from './components/IonPage';
export { IonRouterOutlet } from './components/IonRouterOutlet';
export { IonTabButton } from './components/IonTabButton';
export { IonTabs } from './components/IonTabs';
export { IonTabBar } from './components/IonTabBar';
export { IonNav } from './components/IonNav';
export { IonIcon } from './components/IonIcon';
export { IonApp } from './components/IonApp';
export { IonBackButton } from "./components/IonBackButton";
export { IonPage } from "./components/IonPage";
export { IonRouterOutlet } from "./components/IonRouterOutlet";
export { IonTabButton } from "./components/IonTabButton";
export { IonTabs } from "./components/IonTabs";
export { IonTabBar } from "./components/IonTabBar";
export { IonNav } from "./components/IonNav";
export { IonIcon } from "./components/IonIcon";
export { IonApp } from "./components/IonApp";
export * from './components/Overlays';
export * from "./components/Overlays";
export {
modalController,
@ -26,10 +31,10 @@ export {
actionSheetController,
loadingController,
pickerController,
toastController
} from './controllers';
toastController,
} from "./controllers";
export * from './globalExtensions';
export * from "./globalExtensions";
export {
// UTILS
@ -56,98 +61,66 @@ export {
GestureConfig,
GestureDetail,
NavComponentWithProps,
SpinnerTypes,
AccordionGroupCustomEvent,
AccordionGroupChangeEventDetail,
BreadcrumbCustomEvent,
BreadcrumbCollapsedClickEventDetail,
ActionSheetOptions,
ActionSheetButton,
AlertOptions,
AlertInput,
AlertTextareaAttributes,
AlertInputAttributes,
AlertButton,
BackButtonEvent,
CheckboxCustomEvent,
CheckboxChangeEventDetail,
DatetimeCustomEvent,
DatetimeChangeEventDetail,
InfiniteScrollCustomEvent,
InputCustomEvent,
InputChangeEventDetail,
ItemReorderEventDetail,
ItemReorderCustomEvent,
ItemSlidingCustomEvent,
IonicSafeString,
LoadingOptions,
MenuCustomEvent,
ModalOptions,
NavCustomEvent,
PickerOptions,
PickerButton,
PickerColumn,
PickerColumnOption,
Platforms,
PlatformConfig,
PopoverOptions,
RadioGroupCustomEvent,
RadioGroupChangeEventDetail,
RangeCustomEvent,
RangeChangeEventDetail,
RangeKnobMoveStartEventDetail,
RangeKnobMoveEndEventDetail,
RefresherCustomEvent,
RefresherEventDetail,
RouterEventDetail,
RouterCustomEvent,
ScrollBaseCustomEvent,
ScrollBaseDetail,
ScrollDetail,
ScrollCustomEvent,
SearchbarCustomEvent,
SearchbarChangeEventDetail,
SegmentChangeEventDetail,
SegmentCustomEvent,
SelectChangeEventDetail,
SelectCustomEvent,
TabsCustomEvent,
TextareaChangeEventDetail,
TextareaCustomEvent,
ToastOptions,
ToastButton,
ToggleChangeEventDetail,
ToggleCustomEvent,
} from '@ionic/core/components';
} from "@ionic/core/components";

View File

@ -1,27 +1,32 @@
import { App, Plugin } from 'vue';
import { IonicConfig, initialize } from '@ionic/core/components';
import type { IonicConfig } from "@ionic/core/components";
import { initialize } from "@ionic/core/components";
import type { App, Plugin } from "vue";
// TODO(FW-2969): types
/**
* We need to make sure that the web component fires an event
* that will not conflict with the user's @ionChange binding,
* otherwise the binding's callback will fire before any
* v-model values have been updated.
*/
const toKebabCase = (eventName: string) => eventName === 'ionChange' ? 'v-ion-change' : eventName.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
* We need to make sure that the web component fires an event
* that will not conflict with the user's @ionChange binding,
* otherwise the binding's callback will fire before any
* v-model values have been updated.
*/
const toKebabCase = (eventName: string) =>
eventName === "ionChange"
? "v-ion-change"
: eventName.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
const getHelperFunctions = () => {
return {
ael: (el: any, eventName: string, cb: any, opts: any) => el.addEventListener(toKebabCase(eventName), cb, opts),
rel: (el: any, eventName: string, cb: any, opts: any) => el.removeEventListener(toKebabCase(eventName), cb, opts),
ce: (eventName: string, opts: any) => new CustomEvent(toKebabCase(eventName), opts)
ael: (el: any, eventName: string, cb: any, opts: any) =>
el.addEventListener(toKebabCase(eventName), cb, opts),
rel: (el: any, eventName: string, cb: any, opts: any) =>
el.removeEventListener(toKebabCase(eventName), cb, opts),
ce: (eventName: string, opts: any) =>
new CustomEvent(toKebabCase(eventName), opts),
};
};
export const IonicVue: Plugin = {
async install(_: App, config: IonicConfig = {}) {
/**
* By default Ionic Framework hides elements that
@ -30,8 +35,8 @@ export const IonicVue: Plugin = {
* TODO FW-2797: Remove when all integrations have been
* migrated to CE build.
*/
if (typeof (document as any) !== 'undefined') {
document.documentElement.classList.add('ion-ce');
if (typeof (document as any) !== "undefined") {
document.documentElement.classList.add("ion-ce");
}
const { ael, rel, ce } = getHelperFunctions();
@ -39,7 +44,7 @@ export const IonicVue: Plugin = {
...config,
_ael: ael,
_rel: rel,
_ce: ce
_ce: ce,
});
}
},
};

View File

@ -1,32 +1,46 @@
import { Ref, ComponentPublicInstance } from 'vue';
import { Config as CoreConfig, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core/components';
import type { Config as CoreConfig } from "@ionic/core/components";
import {
LIFECYCLE_DID_ENTER,
LIFECYCLE_DID_LEAVE,
LIFECYCLE_WILL_ENTER,
LIFECYCLE_WILL_LEAVE,
} from "@ionic/core/components";
import type { Ref, ComponentPublicInstance } from "vue";
type LIFECYCLE_EVENTS = typeof LIFECYCLE_WILL_ENTER | typeof LIFECYCLE_DID_ENTER | typeof LIFECYCLE_WILL_LEAVE | typeof LIFECYCLE_DID_LEAVE;
type LIFECYCLE_EVENTS =
| typeof LIFECYCLE_WILL_ENTER
| typeof LIFECYCLE_DID_ENTER
| typeof LIFECYCLE_WILL_LEAVE
| typeof LIFECYCLE_DID_LEAVE;
// TODO(FW-2969): types
export enum LifecycleHooks {
WillEnter = 'onIonViewWillEnter',
DidEnter = 'onIonViewDidEnter',
WillLeave = 'onIonViewWillLeave',
DidLeave = 'onIonViewDidLeave'
WillEnter = "onIonViewWillEnter",
DidEnter = "onIonViewDidEnter",
WillLeave = "onIonViewWillLeave",
DidLeave = "onIonViewDidLeave",
}
const hookNames = {
[LIFECYCLE_WILL_ENTER]: LifecycleHooks.WillEnter,
[LIFECYCLE_DID_ENTER]: LifecycleHooks.DidEnter,
[LIFECYCLE_WILL_LEAVE]: LifecycleHooks.WillLeave,
[LIFECYCLE_DID_LEAVE]: LifecycleHooks.DidLeave
}
[LIFECYCLE_DID_LEAVE]: LifecycleHooks.DidLeave,
};
const ids: { [k: string]: number } = { main: 0 };
export const generateId = (type = 'main') => {
export const generateId = (type = "main") => {
const id = (ids[type] ?? 0) + 1;
ids[type] = id;
return (id).toString();
return id.toString();
};
export const fireLifecycle = (vueComponent: any, vueInstance: Ref<ComponentPublicInstance>, lifecycle: LIFECYCLE_EVENTS) => {
export const fireLifecycle = (
vueComponent: any,
vueInstance: Ref<ComponentPublicInstance>,
lifecycle: LIFECYCLE_EVENTS
) => {
if (vueComponent?.[lifecycle]) {
vueComponent[lifecycle].bind(vueInstance?.value)();
}
@ -47,10 +61,10 @@ export const fireLifecycle = (vueComponent: any, vueInstance: Ref<ComponentPubli
hooks.forEach((hook: Function) => hook());
}
}
}
};
export const getConfig = (): CoreConfig | null => {
if (typeof (window as any) !== 'undefined') {
if (typeof (window as any) !== "undefined") {
const Ionic = (window as any).Ionic;
if (Ionic && Ionic.config) {
return Ionic.config;