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:
@ -115,9 +115,10 @@ export const IonTabBar = defineComponent({
|
||||
* land on /tabs/tab1/child instead of /tabs/tab1.
|
||||
*/
|
||||
if (activeTab !== prevActiveTab || (prevHref !== currentRoute.pathname)) {
|
||||
const search = (currentRoute.search !== undefined) ? `?${currentRoute.search}` : '';
|
||||
tabs[activeTab] = {
|
||||
...tabs[activeTab],
|
||||
currentHref: currentRoute.pathname + (currentRoute.search || '')
|
||||
currentHref: currentRoute.pathname + search
|
||||
}
|
||||
}
|
||||
|
||||
|
112
packages/vue/src/hooks.ts
Normal file
112
packages/vue/src/hooks.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { BackButtonEvent } from '@ionic/core';
|
||||
import {
|
||||
inject,
|
||||
ref,
|
||||
Ref,
|
||||
ComponentInternalInstance,
|
||||
getCurrentInstance
|
||||
} from 'vue';
|
||||
import { LifecycleHooks } from './utils';
|
||||
|
||||
type Handler = (processNextHandler: () => void) => Promise<any> | void | null;
|
||||
|
||||
export interface IonRouter {
|
||||
canGoBack: (deep?: number) => boolean;
|
||||
}
|
||||
|
||||
export interface IonKeyboardRef {
|
||||
isOpen: Ref<boolean>;
|
||||
keyboardHeight: Ref<number>;
|
||||
unregister: () => void
|
||||
}
|
||||
|
||||
export const useBackButton = (priority: number, handler: Handler) => {
|
||||
const callback = (ev: BackButtonEvent) => ev.detail.register(priority, handler);
|
||||
const unregister = () => document.removeEventListener('ionBackButton', callback);
|
||||
|
||||
document.addEventListener('ionBackButton', callback);
|
||||
|
||||
return { unregister };
|
||||
}
|
||||
|
||||
export const useIonRouter = (): IonRouter => {
|
||||
const { canGoBack } = inject('navManager') as any;
|
||||
|
||||
return {
|
||||
canGoBack
|
||||
} as IonRouter
|
||||
}
|
||||
|
||||
export const useKeyboard = (): IonKeyboardRef => {
|
||||
let isOpen = ref(false);
|
||||
let 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.addEventListener('ionKeyboardDidShow', showCallback);
|
||||
window.addEventListener('ionKeyboardDidHide', hideCallback);
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
keyboardHeight,
|
||||
unregister
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 => {
|
||||
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] = []);
|
||||
/**
|
||||
* Define property on public instances using `setup` syntax in Vue 3.x
|
||||
*/
|
||||
if (target.exposed) {
|
||||
target.exposed[lifecycleType] = hooks;
|
||||
}
|
||||
const wrappedHook = (...args: unknown[]) => {
|
||||
if (target.isUnmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
return args ? hook(...args) : hook();
|
||||
};
|
||||
|
||||
hooks.push(wrappedHook);
|
||||
|
||||
return wrappedHook;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
export const onIonViewWillEnter = createHook(LifecycleHooks.WillEnter);
|
||||
export const onIonViewDidEnter = createHook(LifecycleHooks.DidEnter);
|
||||
export const onIonViewWillLeave = createHook(LifecycleHooks.WillLeave);
|
||||
export const onIonViewDidLeave = createHook(LifecycleHooks.DidLeave);
|
3928
packages/vue/test-app/package-lock.json
generated
3928
packages/vue/test-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,41 +2,41 @@
|
||||
"name": "test-app",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "An Ionic project",
|
||||
"scripts": {
|
||||
"start": "npm run sync && vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"test:e2e": "concurrently \"npm run start\" \"wait-on http-get://localhost:8080 && npm run cypress\" --kill-others --success first",
|
||||
"lint": "vue-cli-service lint",
|
||||
"cypress": "node_modules/.bin/cypress run --headless --browser chrome",
|
||||
"start": "npm run sync && vue-cli-service serve",
|
||||
"sync": "sh ./scripts/sync.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/vue": "5.6.3",
|
||||
"@ionic/vue-router": "5.6.3",
|
||||
"vue": "^3.1.5",
|
||||
"vue-router": "^4.0.0-rc.4"
|
||||
"vue": "^3.2.22",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.19",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.5.12",
|
||||
"@vue/cli-plugin-babel": "~4.5.15",
|
||||
"@vue/cli-plugin-e2e-cypress": "^5.0.0-alpha.7",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||
"@vue/cli-plugin-router": "~4.5.15",
|
||||
"@vue/cli-plugin-typescript": "~4.5.15",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.15",
|
||||
"@vue/cli-service": "~4.5.15",
|
||||
"@vue/compiler-sfc": "^3.0.0-0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"concurrently": "^6.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0-0",
|
||||
"typescript": "~3.9.3",
|
||||
"typescript": "~4.1.5",
|
||||
"vue-jest": "^5.0.0-0",
|
||||
"wait-on": "^5.3.0"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/lifecycle',
|
||||
component: () => import('@/views/Lifecycle.vue')
|
||||
},
|
||||
{
|
||||
path: '/lifecycle-setup',
|
||||
component: () => import('@/views/LifecycleSetup.vue')
|
||||
},
|
||||
{
|
||||
path: '/overlays',
|
||||
name: 'Overlays',
|
||||
|
@ -47,6 +47,9 @@
|
||||
<ion-item button router-link="/lifecycle" id="lifecycle">
|
||||
<ion-label>Lifecycle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button router-link="/lifecycle-setup" id="lifecycle-setup">
|
||||
<ion-label>Lifecycle (Setup)</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button router-link="/delayed-redirect" id="delayed-redirect">
|
||||
<ion-label>Delayed Redirect</ion-label>
|
||||
</ion-item>
|
||||
|
56
packages/vue/test-app/src/views/LifecycleSetup.vue
Normal file
56
packages/vue/test-app/src/views/LifecycleSetup.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<ion-page data-pageid="lifecycle-setup">
|
||||
<ion-header :translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-buttons>
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Lifecycle (Setup)</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Lifecycle (Setup)</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div class="ion-padding">
|
||||
onIonViewWillEnter: <div id="onWillEnter">{{ onWillEnter }}</div><br />
|
||||
onIonViewDidEnter: <div id="onDidEnter">{{ onDidEnter }}</div><br />
|
||||
onIonViewWillLeave: <div id="onWillLeave">{{ onWillLeave }}</div><br />
|
||||
onIonViewDidLeave: <div id="onDidLeave">{{ onDidLeave }}</div><br />
|
||||
|
||||
<ion-button router-link="/navigation" id="lifecycle-navigation">Go to another page</ion-button>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
IonButton,
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
onIonViewWillEnter,
|
||||
onIonViewDidEnter,
|
||||
onIonViewWillLeave,
|
||||
onIonViewDidLeave
|
||||
} from '@ionic/vue';
|
||||
import { ref } from 'vue';
|
||||
const onWillEnter = ref(0);
|
||||
const onDidEnter = ref(0);
|
||||
const onWillLeave = ref(0);
|
||||
const onDidLeave = ref(0);
|
||||
|
||||
onIonViewWillEnter(() => onWillEnter.value += 1);
|
||||
onIonViewDidEnter(() => onDidEnter.value += 1);
|
||||
onIonViewWillLeave(() => onWillLeave.value += 1);
|
||||
onIonViewDidLeave(() => onDidLeave.value += 1);
|
||||
</script>
|
@ -31,6 +31,10 @@
|
||||
<ion-item button router-link="/tabs" id="tabs-primary">
|
||||
<ion-label>Go to Primary Tabs</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item router-link="/tabs/tab1/child-one?key=value" id="child-one-query-string">
|
||||
<ion-label>Go to Tab 1 Child 1 with Query Params</ion-label>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
@ -55,14 +55,62 @@ describe('Lifecycle', () => {
|
||||
onIonViewDidLeave: 0
|
||||
});
|
||||
});
|
||||
|
||||
it('should fire lifecycle events when navigating to and from a page - setup', () => {
|
||||
cy.visit('http://localhost:8080');
|
||||
cy.get('#lifecycle-setup').click();
|
||||
|
||||
testLifecycle('lifecycle-setup', {
|
||||
onIonViewWillEnter: 1,
|
||||
onIonViewDidEnter: 1,
|
||||
onIonViewWillLeave: 0,
|
||||
onIonViewDidLeave: 0
|
||||
});
|
||||
|
||||
cy.get('#lifecycle-navigation').click();
|
||||
|
||||
testLifecycle('lifecycle-setup', {
|
||||
onIonViewWillEnter: 1,
|
||||
onIonViewDidEnter: 1,
|
||||
onIonViewWillLeave: 1,
|
||||
onIonViewDidLeave: 1
|
||||
});
|
||||
|
||||
cy.ionBackClick('navigation');
|
||||
|
||||
testLifecycle('lifecycle-setup', {
|
||||
onIonViewWillEnter: 2,
|
||||
onIonViewDidEnter: 2,
|
||||
onIonViewWillLeave: 1,
|
||||
onIonViewDidLeave: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('should fire lifecycle events when landed on directly - setup', () => {
|
||||
cy.visit('http://localhost:8080/lifecycle-setup');
|
||||
|
||||
testLifecycle('lifecycle-setup', {
|
||||
onIonViewWillEnter: 1,
|
||||
onIonViewDidEnter: 1,
|
||||
onIonViewWillLeave: 0,
|
||||
onIonViewDidLeave: 0
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
const testLifecycle = (selector, expected = {}) => {
|
||||
cy.get(`[data-pageid=${selector}] #willEnter`).should('have.text', expected.ionViewWillEnter);
|
||||
cy.get(`[data-pageid=${selector}] #didEnter`).should('have.text', expected.ionViewDidEnter);
|
||||
cy.get(`[data-pageid=${selector}] #willLeave`).should('have.text', expected.ionViewWillLeave);
|
||||
cy.get(`[data-pageid=${selector}] #didLeave`).should('have.text', expected.ionViewDidLeave);
|
||||
|
||||
if (expected.ionViewWillEnter) {
|
||||
cy.get(`[data-pageid=${selector}] #willEnter`).should('have.text', expected.ionViewWillEnter);
|
||||
}
|
||||
if (expected.ionViewDidEnter) {
|
||||
cy.get(`[data-pageid=${selector}] #didEnter`).should('have.text', expected.ionViewDidEnter);
|
||||
}
|
||||
if (expected.ionViewWillLeave) {
|
||||
cy.get(`[data-pageid=${selector}] #willLeave`).should('have.text', expected.ionViewWillLeave);
|
||||
}
|
||||
if (expected.ionViewDidLeave) {
|
||||
cy.get(`[data-pageid=${selector}] #didLeave`).should('have.text', expected.ionViewDidLeave);
|
||||
}
|
||||
cy.get(`[data-pageid=${selector}] #onWillEnter`).should('have.text', expected.onIonViewWillEnter);
|
||||
cy.get(`[data-pageid=${selector}] #onDidEnter`).should('have.text', expected.onIonViewDidEnter);
|
||||
cy.get(`[data-pageid=${selector}] #onWillLeave`).should('have.text', expected.onIonViewWillLeave);
|
||||
|
@ -268,6 +268,27 @@ describe('Tabs', () => {
|
||||
cy.get('ion-tab-button#tab-button-tab1').should('not.have.class', 'tab-selected');
|
||||
cy.get('ion-tab-button#tab-button-tab4').should('have.class', 'tab-selected');
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23699
|
||||
it('should preserve query string when switching tabs', () => {
|
||||
cy.visit('http://localhost:8080/tabs/tab1');
|
||||
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
cy.get('#child-one-query-string').click();
|
||||
cy.ionPageVisible('tab1childone');
|
||||
cy.ionPageHidden('tab1');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||
cy.ionPageVisible('tab2');
|
||||
cy.ionPageHidden('tab1childone');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab1').click();
|
||||
cy.ionPageVisible('tab1childone');
|
||||
cy.ionPageHidden('tab2');
|
||||
|
||||
cy.url().should('include', '/tabs/tab1/child-one?key=value');
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tabs - Swipe to Go Back', () => {
|
||||
|
Reference in New Issue
Block a user