mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 02:31:34 +08:00
chore(): sync with main
This commit is contained in:
@ -41,7 +41,7 @@ runs:
|
|||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ./packages/vue/test-app
|
working-directory: ./packages/vue/test-app
|
||||||
- name: Run E2E ests
|
- name: Run E2E Tests
|
||||||
run: npm run test:e2e
|
run: npm run test:e2e
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ./packages/vue/test-app
|
working-directory: ./packages/vue/test-app
|
||||||
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,3 +1,20 @@
|
|||||||
|
## [5.9.2](https://github.com/ionic-team/ionic/compare/v5.9.1...v5.9.2) (2021-12-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **angular:** improve typing when compiling with legacy View Engine ([#24221](https://github.com/ionic-team/ionic/issues/24221)) ([816096f](https://github.com/ionic-team/ionic/commit/816096f89747e943a4a273175d384189f25e4628))
|
||||||
|
* **content:** ensure fixed slot renders on top of content in iOS ([#24300](https://github.com/ionic-team/ionic/issues/24300)) ([e41b0e0](https://github.com/ionic-team/ionic/commit/e41b0e0cf2a794972d7f4d8943a0bec3d1e08016)), closes [#24286](https://github.com/ionic-team/ionic-framework/issues/24286)
|
||||||
|
* **popover:** improve scrolling in popover when using header and footer ([#24294](https://github.com/ionic-team/ionic/issues/24294)) ([f6a00ea](https://github.com/ionic-team/ionic/commit/f6a00ea9544aa70620b5f8f65a7702fa3bedd974))
|
||||||
|
* **react:** present and dismiss hooks return promises ([#24299](https://github.com/ionic-team/ionic/issues/24299)) ([4b26fea](https://github.com/ionic-team/ionic/commit/4b26feaa47efed4806aba565a52554db232b99e2)), closes [#24293](https://github.com/ionic-team/ionic-framework/issues/24293)
|
||||||
|
* **react:** properly check for custom elements to avoid errors in unit tests ([#24156](https://github.com/ionic-team/ionic/issues/24156)) ([8f188ea](https://github.com/ionic-team/ionic/commit/8f188eaae7422c9e81053868b9dd93b4ac738e98)), closes [#24149](https://github.com/ionic-team/ionic/issues/24149)
|
||||||
|
* **router:** popping route now accounts for route params ([#24315](https://github.com/ionic-team/ionic/issues/24315)) ([5e5054d](https://github.com/ionic-team/ionic/commit/5e5054d369ad68c9ac43e12439d71fb42d6ca26b)), closes [#24223](https://github.com/ionic-team/ionic-framework/issues/24223)
|
||||||
|
* **slides:** update swiper instance after initialization ([#24257](https://github.com/ionic-team/ionic/issues/24257)) ([89e4bc5](https://github.com/ionic-team/ionic/commit/89e4bc56a1c3cd4fb26fc5514f38c6a01f047297)), closes [#19638](https://github.com/ionic-team/ionic-framework/issues/19638)
|
||||||
|
* **vue:** ionic lifecycle hooks now run when using vue 3.2 setup syntax ([#24253](https://github.com/ionic-team/ionic/issues/24253)) ([fb96ab5](https://github.com/ionic-team/ionic/commit/fb96ab5a26d87818a8b64ee82df0020355054183)), closes [#23824](https://github.com/ionic-team/ionic/issues/23824)
|
||||||
|
* **vue:** switching between tabs preserves query string ([#24297](https://github.com/ionic-team/ionic/issues/24297)) ([047d3c7](https://github.com/ionic-team/ionic/commit/047d3c77729db08e4fd84f426f6c5c2af0eacc52)), closes [#23699](https://github.com/ionic-team/ionic/issues/23699)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [6.0.0-rc.3](https://github.com/ionic-team/ionic/compare/v6.0.0-rc.2...v6.0.0-rc.3) (2021-11-17)
|
# [6.0.0-rc.3](https://github.com/ionic-team/ionic/compare/v6.0.0-rc.2...v6.0.0-rc.3) (2021-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
|
|||||||
*/
|
*/
|
||||||
const formControl = ngControl.control as any;
|
const formControl = ngControl.control as any;
|
||||||
if (formControl) {
|
if (formControl) {
|
||||||
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
|
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'] as const;
|
||||||
methodsToPatch.forEach((method) => {
|
methodsToPatch.forEach((method) => {
|
||||||
if (formControl.get(method)) {
|
if (formControl.get(method)) {
|
||||||
const oldFn = formControl[method].bind(formControl);
|
const oldFn = formControl[method].bind(formControl);
|
||||||
|
4
core/src/components.d.ts
vendored
4
core/src/components.d.ts
vendored
@ -238,7 +238,7 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"disabled": boolean;
|
"disabled": boolean;
|
||||||
/**
|
/**
|
||||||
* The icon name to use for the back button.
|
* The built-in named SVG icon name or the exact `src` of an SVG file to use for the back button.
|
||||||
*/
|
*/
|
||||||
"icon"?: string | null;
|
"icon"?: string | null;
|
||||||
/**
|
/**
|
||||||
@ -3891,7 +3891,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"disabled"?: boolean;
|
"disabled"?: boolean;
|
||||||
/**
|
/**
|
||||||
* The icon name to use for the back button.
|
* The built-in named SVG icon name or the exact `src` of an SVG file to use for the back button.
|
||||||
*/
|
*/
|
||||||
"icon"?: string | null;
|
"icon"?: string | null;
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,8 @@ export class BackButton implements ComponentInterface, ButtonInterface {
|
|||||||
@Prop({ reflect: true }) disabled = false;
|
@Prop({ reflect: true }) disabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The icon name to use for the back button.
|
* The built-in named SVG icon name or the exact `src` of an SVG file
|
||||||
|
* to use for the back button.
|
||||||
*/
|
*/
|
||||||
@Prop() icon?: string | null;
|
@Prop() icon?: string | null;
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ export default defineComponent({
|
|||||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||||
| `defaultHref` | `default-href` | The url to navigate back to by default when there is no history. | `string \| undefined` | `undefined` |
|
| `defaultHref` | `default-href` | The url to navigate back to by default when there is no history. | `string \| undefined` | `undefined` |
|
||||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the button. | `boolean` | `false` |
|
| `disabled` | `disabled` | If `true`, the user cannot interact with the button. | `boolean` | `false` |
|
||||||
| `icon` | `icon` | The icon name to use for the back button. | `null \| string \| undefined` | `undefined` |
|
| `icon` | `icon` | The built-in named SVG icon name or the exact `src` of an SVG file to use for the back button. | `null \| string \| undefined` | `undefined` |
|
||||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||||
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
|
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
|
||||||
| `text` | `text` | The text to display in the back button. | `null \| string \| undefined` | `undefined` |
|
| `text` | `text` | The text to display in the back button. | `null \| string \| undefined` | `undefined` |
|
||||||
|
@ -136,10 +136,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host(.content-sizing) {
|
:host(.content-sizing) {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This resolves a sizing issue in popovers where extra long content
|
||||||
|
* would overflow the popover's height, preventing scrolling. It's a
|
||||||
|
* quirk of flexbox that forces the content to shrink to fit.
|
||||||
|
*
|
||||||
|
* overflow: hidden can't be used here because it prevents the visual
|
||||||
|
* effect from showing on translucent headers.
|
||||||
|
*/
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
contain: none;
|
contain: none;
|
||||||
}
|
}
|
||||||
:host(.content-sizing) .inner-scroll {
|
:host(.content-sizing) .inner-scroll {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because the outer content has display: flex here (to help enable
|
||||||
|
* scrolling in a popover), offsetting via `top` (such as when using
|
||||||
|
* a translucent header) creates white space under the content. Use
|
||||||
|
* a negative margin instead to keep the bottom in place. (A similar
|
||||||
|
* thing happens with `bottom` and footers.)
|
||||||
|
*/
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
margin-top: calc(var(--offset-top) * -1);
|
||||||
|
margin-bottom: calc(var(--offset-bottom) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transition-effect {
|
.transition-effect {
|
||||||
@ -195,4 +222,15 @@
|
|||||||
|
|
||||||
::slotted([slot="fixed"]) {
|
::slotted([slot="fixed"]) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When presenting ion-content inside of an ion-modal, the .inner-scroll
|
||||||
|
* element is composited. In WebKit, the fixed content is not composited
|
||||||
|
* causing it to appear under the main scrollable content as a result.
|
||||||
|
* The fixed content is correctly composited in other browsers. Adding
|
||||||
|
* the translateZ forces the fixed content to be composited so it correctly
|
||||||
|
* shows on top of the scrollable content. Setting a negative z-index will
|
||||||
|
* still allow the fixed content to appear under the scroll content if specified.
|
||||||
|
*/
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
@ -384,10 +384,17 @@ const getPageElement = (el: HTMLElement) => {
|
|||||||
if (tabs) {
|
if (tabs) {
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
const page = el.closest('ion-app,ion-page,.ion-page,page-inner');
|
|
||||||
|
/**
|
||||||
|
* If we're in a popover, we need to use its wrapper so we can account for space
|
||||||
|
* between the popover and the edges of the screen. But if the popover contains
|
||||||
|
* its own page element, we should use that instead.
|
||||||
|
*/
|
||||||
|
const page = el.closest('ion-app, ion-page, .ion-page, page-inner, .popover-content');
|
||||||
if (page) {
|
if (page) {
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getParentElement(el);
|
return getParentElement(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,6 +87,11 @@
|
|||||||
--ion-safe-area-right: 0px;
|
--ion-safe-area-right: 0px;
|
||||||
--ion-safe-area-bottom: 0px;
|
--ion-safe-area-bottom: 0px;
|
||||||
--ion-safe-area-left: 0px;
|
--ion-safe-area-left: 0px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested Popovers
|
// Nested Popovers
|
||||||
@ -114,4 +119,3 @@
|
|||||||
--offset-x: 5px;
|
--offset-x: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,14 @@ test('popover: custom class', async () => {
|
|||||||
await testPopover(DIRECTORY, '#custom-class-popover');
|
await testPopover(DIRECTORY, '#custom-class-popover');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('popover: header', async () => {
|
||||||
|
await testPopover(DIRECTORY, '#header-popover');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popover: translucent header', async () => {
|
||||||
|
await testPopover(DIRECTORY, '#translucent-header-popover');
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RTL Tests
|
* RTL Tests
|
||||||
*/
|
*/
|
||||||
@ -97,6 +105,14 @@ test('popover:rtl: custom class', async () => {
|
|||||||
await testPopover(DIRECTORY, '#custom-class-popover', true, true);
|
await testPopover(DIRECTORY, '#custom-class-popover', true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('popover:rtl: header', async () => {
|
||||||
|
await testPopover(DIRECTORY, '#header-popover', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popover:rtl: translucent header', async () => {
|
||||||
|
await testPopover(DIRECTORY, '#translucent-header-popover', true);
|
||||||
|
});
|
||||||
|
|
||||||
test('popover: htmlAttributes', async () => {
|
test('popover: htmlAttributes', async () => {
|
||||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@
|
|||||||
<ion-button id="long-list-popover" expand="block" color="secondary" onclick="presentPopover({ component: 'list-page', event: event })">Show Long List Popover</ion-button>
|
<ion-button id="long-list-popover" expand="block" color="secondary" onclick="presentPopover({ component: 'list-page', event: event })">Show Long List Popover</ion-button>
|
||||||
<ion-button id="no-event-popover" expand="block" color="danger" onclick="presentPopover({ component: 'profile-page' })">No Event Popover</ion-button>
|
<ion-button id="no-event-popover" expand="block" color="danger" onclick="presentPopover({ component: 'profile-page' })">No Event Popover</ion-button>
|
||||||
<ion-button id="custom-class-popover" expand="block" color="tertiary" onclick="presentPopover({ component: 'translucent-page', event: event, cssClass: 'my-custom-class' })">Custom Class Popover</ion-button>
|
<ion-button id="custom-class-popover" expand="block" color="tertiary" onclick="presentPopover({ component: 'translucent-page', event: event, cssClass: 'my-custom-class' })">Custom Class Popover</ion-button>
|
||||||
|
<ion-button id="header-popover" expand="block" onclick="presentPopover({ component: 'header-page' })">Popover With Header</ion-button>
|
||||||
|
<ion-button id="translucent-header-popover" expand="block" onclick="presentPopover({ component: 'translucent-header-page' })">Popover With Translucent Header</ion-button>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<ion-footer>
|
<ion-footer>
|
||||||
@ -126,6 +128,56 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('translucent-page', TranslucentPage);
|
customElements.define('translucent-page', TranslucentPage);
|
||||||
|
|
||||||
|
class HeaderPage extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding" color="primary">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.In rutrum tortor lacus, ac interdum ipsum bibendum vel.Aenean non nibh gravida, ullamcorper mi at, tempor nulla.Proin malesuada tellus ut ullamcorper accumsan.Donec semper justo vulputate neque tempus ultricies.Proin non aliquet ipsum.Praesent mauris sem, facilisis eu justo nec, euismod imperdiet tellus.Duis eget justo congue, lacinia orci sed, fermentum urna.Quisque sed massa faucibus, interdum dolor rhoncus, molestie erat.Proin suscipit ante non mauris volutpat egestas.Donec a ultrices ligula.Mauris in felis vel dui consectetur viverra.Nam vitae quam in arcu aliquam aliquam.Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Cras non velit nisl.Donec viverra, magna quis vestibulum volutpat, metus ante tincidunt augue, non porta nisi mi sit amet neque.Proin dapibus eros vitae nibh tincidunt, blandit rhoncus est porttitor.
|
||||||
|
</ion-content>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('header-page', HeaderPage);
|
||||||
|
|
||||||
|
class TranslucentHeaderPage extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<ion-header translucent>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Header</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content class="ion-padding" fullscreen color="primary">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.In rutrum tortor lacus, ac interdum ipsum bibendum vel.Aenean non nibh gravida, ullamcorper mi at, tempor nulla.Proin malesuada tellus ut ullamcorper accumsan.Donec semper justo vulputate neque tempus ultricies.Proin non aliquet ipsum.Praesent mauris sem, facilisis eu justo nec, euismod imperdiet tellus.Duis eget justo congue, lacinia orci sed, fermentum urna.Quisque sed massa faucibus, interdum dolor rhoncus, molestie erat.Proin suscipit ante non mauris volutpat egestas.Donec a ultrices ligula.Mauris in felis vel dui consectetur viverra.Nam vitae quam in arcu aliquam aliquam.Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Cras non velit nisl.Donec viverra, magna quis vestibulum volutpat, metus ante tincidunt augue, non porta nisi mi sit amet neque.Proin dapibus eros vitae nibh tincidunt, blandit rhoncus est porttitor.
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer translucent>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('translucent-header-page', TranslucentHeaderPage);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -30,15 +30,24 @@ const CHAIN_3: RouteChain = [
|
|||||||
describe('matchesIDs', () => {
|
describe('matchesIDs', () => {
|
||||||
it('should match simple set of ids', () => {
|
it('should match simple set of ids', () => {
|
||||||
const chain: RouteChain = CHAIN_1;
|
const chain: RouteChain = CHAIN_1;
|
||||||
expect(matchesIDs(['2'], chain)).toBe(1);
|
expect(matchesIDs([{ id: '2' }], chain)).toBe(1);
|
||||||
expect(matchesIDs(['2', '1'], chain)).toBe(2);
|
expect(matchesIDs([{ id: '2' }, { id: '1' }], chain)).toBe(2);
|
||||||
expect(matchesIDs(['2', '1', '3'], chain)).toBe(3);
|
expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }], chain)).toBe(3);
|
||||||
expect(matchesIDs(['2', '1', '3', '4'], chain)).toBe(4);
|
expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }, { id: '4' }], chain)).toBe(4);
|
||||||
expect(matchesIDs(['2', '1', '3', '4', '5'], chain)).toBe(4);
|
expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }, { id: '4' }, { id: '5' }], chain)).toBe(4);
|
||||||
|
|
||||||
expect(matchesIDs([], chain)).toBe(0);
|
expect(matchesIDs([], chain)).toBe(0);
|
||||||
expect(matchesIDs(['1'], chain)).toBe(0);
|
expect(matchesIDs([{ id: '1' }], chain)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should match path with params', () => {
|
||||||
|
const ids = [{ id: 'my-page', params: { s1: 'a', s2: 'b' } }];
|
||||||
|
|
||||||
|
expect(matchesIDs(ids, [{ id: 'my-page', path: [''], params: {} }])).toBe(1);
|
||||||
|
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1'], params: {} }])).toBe(1);
|
||||||
|
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1', ':s2'], params: {} }])).toBe(3);
|
||||||
|
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1', ':s2', ':s3'], params: {} }])).toBe(1);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matchesPath', () => {
|
describe('matchesPath', () => {
|
||||||
@ -227,7 +236,7 @@ describe('mergeParams', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('RouterSegments', () => {
|
describe('RouterSegments', () => {
|
||||||
it ('should initialize with empty array', () => {
|
it('should initialize with empty array', () => {
|
||||||
const s = new RouterSegments([]);
|
const s = new RouterSegments([]);
|
||||||
expect(s.next()).toEqual('');
|
expect(s.next()).toEqual('');
|
||||||
expect(s.next()).toEqual('');
|
expect(s.next()).toEqual('');
|
||||||
@ -236,7 +245,7 @@ describe('RouterSegments', () => {
|
|||||||
expect(s.next()).toEqual('');
|
expect(s.next()).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it ('should initialize with array', () => {
|
it('should initialize with array', () => {
|
||||||
const s = new RouterSegments(['', 'path', 'to', 'destination']);
|
const s = new RouterSegments(['', 'path', 'to', 'destination']);
|
||||||
expect(s.next()).toEqual('');
|
expect(s.next()).toEqual('');
|
||||||
expect(s.next()).toEqual('path');
|
expect(s.next()).toEqual('path');
|
||||||
|
@ -32,16 +32,60 @@ export const findRouteRedirect = (path: string[], redirects: RouteRedirect[]) =>
|
|||||||
return redirects.find(redirect => matchesRedirect(path, redirect));
|
return redirects.find(redirect => matchesRedirect(path, redirect));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const matchesIDs = (ids: string[], chain: RouteChain): number => {
|
export const matchesIDs = (ids: Pick<RouteID, 'id' | 'params'>[], chain: RouteChain): number => {
|
||||||
const len = Math.min(ids.length, chain.length);
|
const len = Math.min(ids.length, chain.length);
|
||||||
let i = 0;
|
|
||||||
for (; i < len; i++) {
|
let score = 0;
|
||||||
if (ids[i].toLowerCase() !== chain[i].id) {
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const routeId = ids[i];
|
||||||
|
const routeChain = chain[i];
|
||||||
|
// Skip results where the route id does not match the chain at the same index
|
||||||
|
if (routeId.id.toLowerCase() !== routeChain.id) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (routeId.params) {
|
||||||
|
const routeIdParams = Object.keys(routeId.params);
|
||||||
|
/**
|
||||||
|
* Only compare routes with the chain that have the same number of parameters.
|
||||||
|
*/
|
||||||
|
if (routeIdParams.length === routeChain.path.length) {
|
||||||
|
/**
|
||||||
|
* Maps the route's params into a path based on the path variable names,
|
||||||
|
* to compare against the route chain format.
|
||||||
|
*
|
||||||
|
* Before:
|
||||||
|
* ```ts
|
||||||
|
* {
|
||||||
|
* params: {
|
||||||
|
* s1: 'a',
|
||||||
|
* s2: 'b'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* After:
|
||||||
|
* ```ts
|
||||||
|
* [':s1',':s2']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const pathWithParams = routeIdParams.map(key => `:${key}`);
|
||||||
|
for (let j = 0; j < pathWithParams.length; j++) {
|
||||||
|
// Skip results where the path variable is not a match
|
||||||
|
if (pathWithParams[j].toLowerCase() !== routeChain.path[j]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Weight path matches for the same index higher.
|
||||||
|
score++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Weight id matches
|
||||||
|
score++;
|
||||||
}
|
}
|
||||||
return i;
|
return score;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain | null => {
|
export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain | null => {
|
||||||
const segments = new RouterSegments(inputPath);
|
const segments = new RouterSegments(inputPath);
|
||||||
@ -90,16 +134,16 @@ export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain
|
|||||||
|
|
||||||
// Merges the route parameter objects.
|
// Merges the route parameter objects.
|
||||||
// Returns undefined when both parameters are undefined.
|
// Returns undefined when both parameters are undefined.
|
||||||
export const mergeParams = (a: {[key: string]: any} | undefined, b: {[key: string]: any} | undefined): {[key: string]: any} | undefined => {
|
export const mergeParams = (a: { [key: string]: any } | undefined, b: { [key: string]: any } | undefined): { [key: string]: any } | undefined => {
|
||||||
return a || b ? { ...a, ...b } : undefined;
|
return a || b ? { ...a, ...b } : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerIDsToChain = (ids: RouteID[], chains: RouteChain[]): RouteChain | null => {
|
export const routerIDsToChain = (ids: RouteID[], chains: RouteChain[]): RouteChain | null => {
|
||||||
let match: RouteChain | null = null;
|
let match: RouteChain | null = null;
|
||||||
let maxMatches = 0;
|
let maxMatches = 0;
|
||||||
const plainIDs = ids.map(i => i.id);
|
|
||||||
for (const chain of chains) {
|
for (const chain of chains) {
|
||||||
const score = matchesIDs(plainIDs, chain);
|
const score = matchesIDs(ids, chain);
|
||||||
if (score > maxMatches) {
|
if (score > maxMatches) {
|
||||||
match = chain;
|
match = chain;
|
||||||
maxMatches = score;
|
maxMatches = score;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core';
|
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { componentOnReady } from '../../utils/helpers'
|
import { componentOnReady } from '../../utils/helpers'
|
||||||
@ -24,8 +24,6 @@ export class Slides implements ComponentInterface {
|
|||||||
private mutationO?: MutationObserver;
|
private mutationO?: MutationObserver;
|
||||||
private readySwiper!: (swiper: SwiperInterface) => void;
|
private readySwiper!: (swiper: SwiperInterface) => void;
|
||||||
private swiper: Promise<SwiperInterface> = new Promise(resolve => { this.readySwiper = resolve; });
|
private swiper: Promise<SwiperInterface> = new Promise(resolve => { this.readySwiper = resolve; });
|
||||||
private syncSwiper?: SwiperInterface;
|
|
||||||
private didInit = false;
|
|
||||||
|
|
||||||
@Element() el!: HTMLIonSlidesElement;
|
@Element() el!: HTMLIonSlidesElement;
|
||||||
|
|
||||||
@ -141,8 +139,7 @@ export class Slides implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
// tslint:disable-next-line: strict-type-predicates
|
if (Build.isBrowser) {
|
||||||
if (typeof MutationObserver !== 'undefined') {
|
|
||||||
const mut = this.mutationO = new MutationObserver(() => {
|
const mut = this.mutationO = new MutationObserver(() => {
|
||||||
if (this.swiperReady) {
|
if (this.swiperReady) {
|
||||||
this.update();
|
this.update();
|
||||||
@ -154,10 +151,7 @@ export class Slides implements ComponentInterface {
|
|||||||
});
|
});
|
||||||
|
|
||||||
componentOnReady(this.el, () => {
|
componentOnReady(this.el, () => {
|
||||||
if (!this.didInit) {
|
|
||||||
this.didInit = true;
|
|
||||||
this.initSwiper();
|
this.initSwiper();
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,23 +161,6 @@ export class Slides implements ComponentInterface {
|
|||||||
this.mutationO.disconnect();
|
this.mutationO.disconnect();
|
||||||
this.mutationO = undefined;
|
this.mutationO = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* We need to synchronously destroy
|
|
||||||
* swiper otherwise it is possible
|
|
||||||
* that it will be left in a
|
|
||||||
* destroyed state if connectedCallback
|
|
||||||
* is called multiple times
|
|
||||||
*/
|
|
||||||
const swiper = this.syncSwiper;
|
|
||||||
if (swiper !== undefined) {
|
|
||||||
swiper.destroy(true, true);
|
|
||||||
this.swiper = new Promise(resolve => { this.readySwiper = resolve; });
|
|
||||||
this.swiperReady = false;
|
|
||||||
this.syncSwiper = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.didInit = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -369,7 +346,6 @@ export class Slides implements ComponentInterface {
|
|||||||
await waitForSlides(this.el);
|
await waitForSlides(this.el);
|
||||||
const swiper = new Swiper(this.el, finalOptions);
|
const swiper = new Swiper(this.el, finalOptions);
|
||||||
this.swiperReady = true;
|
this.swiperReady = true;
|
||||||
this.syncSwiper = swiper;
|
|
||||||
this.readySwiper(swiper);
|
this.readySwiper(swiper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +459,8 @@ export class Slides implements ComponentInterface {
|
|||||||
init: () => {
|
init: () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.ionSlidesDidLoad.emit();
|
this.ionSlidesDidLoad.emit();
|
||||||
|
// Forces the swiper instance to update after initializing.
|
||||||
|
this.update();
|
||||||
}, 20);
|
}, 20);
|
||||||
},
|
},
|
||||||
slideChangeTransitionStart: this.ionSlideWillChange.emit,
|
slideChangeTransitionStart: this.ionSlideWillChange.emit,
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"release.dev": "node .scripts/release-dev.js",
|
"release.dev": "node .scripts/release-dev.js",
|
||||||
"release.prepare": "node .scripts/prepare.js",
|
"release.prepare": "node .scripts/prepare.js",
|
||||||
"release": "node .scripts/release.js",
|
"release": "node .scripts/release.js",
|
||||||
"changelog": "conventional-changelog -p angular -i ./CHANGELOG.md -k core -s"
|
"changelog": "conventional-changelog -p angular -i ./CHANGELOG.md -k core -s",
|
||||||
|
"commitizenBranches": "git-branch-is -q --not -r \"^(main|next|release-)\""
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^13.1.0",
|
"@commitlint/cli": "^13.1.0",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"execa": "^0.10.0",
|
"execa": "^0.10.0",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
|
"git-branch-is": "^4.0.0",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
"inquirer": "^6.0.0",
|
"inquirer": "^6.0.0",
|
||||||
"listr": "^0.14.0",
|
"listr": "^0.14.0",
|
||||||
@ -34,8 +36,8 @@
|
|||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
|
"commit-msg": "npm run commitizenBranches --silent && commitlint -E HUSKY_GIT_PARAMS || true",
|
||||||
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true"
|
"prepare-commit-msg": "npm run commitizenBranches --silent && exec < /dev/tty && git cz --hook || true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ class IonTabsElement extends HTMLElementSSR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (window as any) !== 'undefined' && window.customElements) {
|
if (typeof (window as any) !== 'undefined' && window.customElements) {
|
||||||
const element = customElements.get('ion-tabs');
|
const element = window.customElements.get('ion-tabs');
|
||||||
if (!element) {
|
if (!element) {
|
||||||
customElements.define('ion-tabs', IonTabsElement);
|
window.customElements.define('ion-tabs', IonTabsElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,12 +20,12 @@ export function useIonActionSheet(): UseIonActionSheetResult {
|
|||||||
header?: string
|
header?: string
|
||||||
) => {
|
) => {
|
||||||
if (Array.isArray(buttonsOrOptions)) {
|
if (Array.isArray(buttonsOrOptions)) {
|
||||||
controller.present({
|
return controller.present({
|
||||||
buttons: buttonsOrOptions,
|
buttons: buttonsOrOptions,
|
||||||
header,
|
header,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
controller.present(buttonsOrOptions);
|
return controller.present(buttonsOrOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[controller.present]
|
[controller.present]
|
||||||
@ -41,15 +41,15 @@ export type UseIonActionSheetResult = [
|
|||||||
* @param buttons An array of buttons for the action sheet
|
* @param buttons An array of buttons for the action sheet
|
||||||
* @param header Optional - Title for the action sheet
|
* @param header Optional - Title for the action sheet
|
||||||
*/
|
*/
|
||||||
(buttons: ActionSheetButton[], header?: string | undefined): void;
|
(buttons: ActionSheetButton[], header?: string | undefined): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Presents the action sheet
|
* Presents the action sheet
|
||||||
* @param options The options to pass to the IonActionSheet
|
* @param options The options to pass to the IonActionSheet
|
||||||
*/
|
*/
|
||||||
(options: ActionSheetOptions & HookOverlayOptions): void;
|
(options: ActionSheetOptions & HookOverlayOptions): Promise<void>;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Dismisses the action sheet
|
* Dismisses the action sheet
|
||||||
*/
|
*/
|
||||||
() => void
|
() => Promise<void>
|
||||||
];
|
];
|
||||||
|
@ -14,12 +14,12 @@ export function useIonAlert(): UseIonAlertResult {
|
|||||||
const present = useCallback(
|
const present = useCallback(
|
||||||
(messageOrOptions: string | (AlertOptions & HookOverlayOptions), buttons?: AlertButton[]) => {
|
(messageOrOptions: string | (AlertOptions & HookOverlayOptions), buttons?: AlertButton[]) => {
|
||||||
if (typeof messageOrOptions === 'string') {
|
if (typeof messageOrOptions === 'string') {
|
||||||
controller.present({
|
return controller.present({
|
||||||
message: messageOrOptions,
|
message: messageOrOptions,
|
||||||
buttons: buttons ?? [{ text: 'Ok' }],
|
buttons: buttons ?? [{ text: 'Ok' }],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
controller.present(messageOrOptions);
|
return controller.present(messageOrOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[controller.present]
|
[controller.present]
|
||||||
@ -35,15 +35,15 @@ export type UseIonAlertResult = [
|
|||||||
* @param message The main message to be displayed in the alert
|
* @param message The main message to be displayed in the alert
|
||||||
* @param buttons Optional - Array of buttons to be added to the alert
|
* @param buttons Optional - Array of buttons to be added to the alert
|
||||||
*/
|
*/
|
||||||
(message: string, buttons?: AlertButton[]): void;
|
(message: string, buttons?: AlertButton[]): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Presents the alert
|
* Presents the alert
|
||||||
* @param options The options to pass to the IonAlert
|
* @param options The options to pass to the IonAlert
|
||||||
*/
|
*/
|
||||||
(options: AlertOptions & HookOverlayOptions): void;
|
(options: AlertOptions & HookOverlayOptions): Promise<void>;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Dismisses the alert
|
* Dismisses the alert
|
||||||
*/
|
*/
|
||||||
() => void
|
() => Promise<void>
|
||||||
];
|
];
|
||||||
|
@ -21,13 +21,13 @@ export function useIonLoading(): UseIonLoadingResult {
|
|||||||
spinner?: SpinnerTypes
|
spinner?: SpinnerTypes
|
||||||
) => {
|
) => {
|
||||||
if (typeof messageOrOptions === 'string') {
|
if (typeof messageOrOptions === 'string') {
|
||||||
controller.present({
|
return controller.present({
|
||||||
message: messageOrOptions,
|
message: messageOrOptions,
|
||||||
duration,
|
duration,
|
||||||
spinner: spinner ?? 'lines',
|
spinner: spinner ?? 'lines',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
controller.present(messageOrOptions);
|
return controller.present(messageOrOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[controller.present]
|
[controller.present]
|
||||||
@ -44,15 +44,15 @@ export type UseIonLoadingResult = [
|
|||||||
* @param duration Optional - Number of milliseconds to wait before dismissing the loading indicator
|
* @param duration Optional - Number of milliseconds to wait before dismissing the loading indicator
|
||||||
* @param spinner Optional - The name of the spinner to display, defaults to "lines"
|
* @param spinner Optional - The name of the spinner to display, defaults to "lines"
|
||||||
*/
|
*/
|
||||||
(message?: string, duration?: number, spinner?: SpinnerTypes): void;
|
(message?: string, duration?: number, spinner?: SpinnerTypes): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Presents the loading indicator
|
* Presents the loading indicator
|
||||||
* @param options The options to pass to the IonLoading
|
* @param options The options to pass to the IonLoading
|
||||||
*/
|
*/
|
||||||
(options: LoadingOptions & HookOverlayOptions): void;
|
(options: LoadingOptions & HookOverlayOptions): Promise<void>;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Dismisses the loading indicator
|
* Dismisses the loading indicator
|
||||||
*/
|
*/
|
||||||
() => void
|
() => Promise<void>
|
||||||
];
|
];
|
||||||
|
@ -24,12 +24,12 @@ export function useIonPicker(): UseIonPickerResult {
|
|||||||
buttons?: PickerButton[]
|
buttons?: PickerButton[]
|
||||||
) => {
|
) => {
|
||||||
if (Array.isArray(columnsOrOptions)) {
|
if (Array.isArray(columnsOrOptions)) {
|
||||||
controller.present({
|
return controller.present({
|
||||||
columns: columnsOrOptions,
|
columns: columnsOrOptions,
|
||||||
buttons: buttons ?? [{ text: 'Ok' }],
|
buttons: buttons ?? [{ text: 'Ok' }],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
controller.present(columnsOrOptions);
|
return controller.present(columnsOrOptions);
|
||||||
}
|
}
|
||||||
}, [controller.present]);
|
}, [controller.present]);
|
||||||
|
|
||||||
@ -43,15 +43,15 @@ export type UseIonPickerResult = [
|
|||||||
* @param columns Array of columns to be displayed in the picker.
|
* @param columns Array of columns to be displayed in the picker.
|
||||||
* @param buttons Optional - Array of buttons to be displayed at the top of the picker.
|
* @param buttons Optional - Array of buttons to be displayed at the top of the picker.
|
||||||
*/
|
*/
|
||||||
(columns: PickerColumn[], buttons?: PickerButton[]): void;
|
(columns: PickerColumn[], buttons?: PickerButton[]): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Presents the picker
|
* Presents the picker
|
||||||
* @param options The options to pass to the IonPicker
|
* @param options The options to pass to the IonPicker
|
||||||
*/
|
*/
|
||||||
(options: PickerOptions & HookOverlayOptions): void;
|
(options: PickerOptions & HookOverlayOptions): Promise<void>;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Dismisses the picker
|
* Dismisses the picker
|
||||||
*/
|
*/
|
||||||
() => void
|
() => Promise<void>
|
||||||
];
|
];
|
||||||
|
@ -16,12 +16,12 @@ export function useIonToast(): UseIonToastResult {
|
|||||||
|
|
||||||
const present = useCallback((messageOrOptions: string | ToastOptions & HookOverlayOptions, duration?: number) => {
|
const present = useCallback((messageOrOptions: string | ToastOptions & HookOverlayOptions, duration?: number) => {
|
||||||
if (typeof messageOrOptions === 'string') {
|
if (typeof messageOrOptions === 'string') {
|
||||||
controller.present({
|
return controller.present({
|
||||||
message: messageOrOptions,
|
message: messageOrOptions,
|
||||||
duration
|
duration
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
controller.present(messageOrOptions);
|
return controller.present(messageOrOptions);
|
||||||
}
|
}
|
||||||
}, [controller.present]);
|
}, [controller.present]);
|
||||||
|
|
||||||
@ -38,15 +38,15 @@ export type UseIonToastResult = [
|
|||||||
* @param message Message to be shown in the toast.
|
* @param message Message to be shown in the toast.
|
||||||
* @param duration Optional - How many milliseconds to wait before hiding the toast. By default, it will show until dismissToast() is called.
|
* @param duration Optional - How many milliseconds to wait before hiding the toast. By default, it will show until dismissToast() is called.
|
||||||
*/
|
*/
|
||||||
(message: string, duration?: number): void;
|
(message: string, duration?: number): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Presents the Toast
|
* Presents the Toast
|
||||||
* @param options The options to pass to the IonToast.
|
* @param options The options to pass to the IonToast.
|
||||||
*/
|
*/
|
||||||
(options: ToastOptions & HookOverlayOptions): void;
|
(options: ToastOptions & HookOverlayOptions): Promise<void>;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Dismisses the toast
|
* Dismisses the toast
|
||||||
*/
|
*/
|
||||||
() => void
|
() => Promise<void>
|
||||||
];
|
];
|
||||||
|
@ -115,9 +115,10 @@ export const IonTabBar = defineComponent({
|
|||||||
* land on /tabs/tab1/child instead of /tabs/tab1.
|
* land on /tabs/tab1/child instead of /tabs/tab1.
|
||||||
*/
|
*/
|
||||||
if (activeTab !== prevActiveTab || (prevHref !== currentRoute.pathname)) {
|
if (activeTab !== prevActiveTab || (prevHref !== currentRoute.pathname)) {
|
||||||
|
const search = (currentRoute.search !== undefined) ? `?${currentRoute.search}` : '';
|
||||||
tabs[activeTab] = {
|
tabs[activeTab] = {
|
||||||
...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",
|
"name": "test-app",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"description": "An Ionic project",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run sync && vue-cli-service serve",
|
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"test:unit": "vue-cli-service test:unit",
|
"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",
|
"test:e2e": "concurrently \"npm run start\" \"wait-on http-get://localhost:8080 && npm run cypress\" --kill-others --success first",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"cypress": "node_modules/.bin/cypress run --headless --browser chrome",
|
"cypress": "node_modules/.bin/cypress run --headless --browser chrome",
|
||||||
|
"start": "npm run sync && vue-cli-service serve",
|
||||||
"sync": "sh ./scripts/sync.sh"
|
"sync": "sh ./scripts/sync.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ionic/vue": "5.6.3",
|
"@ionic/vue": "5.6.3",
|
||||||
"@ionic/vue-router": "5.6.3",
|
"@ionic/vue-router": "5.6.3",
|
||||||
"vue": "^3.1.5",
|
"vue": "^3.2.22",
|
||||||
"vue-router": "^4.0.0-rc.4"
|
"vue-router": "^4.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^24.0.19",
|
"@types/jest": "^24.0.19",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
"@typescript-eslint/parser": "^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-e2e-cypress": "^5.0.0-alpha.7",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||||
"@vue/cli-plugin-router": "~4.5.0",
|
"@vue/cli-plugin-router": "~4.5.15",
|
||||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
"@vue/cli-plugin-typescript": "~4.5.15",
|
||||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
"@vue/cli-plugin-unit-jest": "~4.5.15",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.15",
|
||||||
"@vue/compiler-sfc": "^3.0.0-0",
|
"@vue/compiler-sfc": "^3.0.0-0",
|
||||||
"@vue/eslint-config-typescript": "^5.0.2",
|
"@vue/eslint-config-typescript": "^5.0.2",
|
||||||
"@vue/test-utils": "^2.0.0-0",
|
"@vue/test-utils": "^2.0.0-0",
|
||||||
"concurrently": "^6.0.0",
|
"concurrently": "^6.0.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^7.0.0-0",
|
"eslint-plugin-vue": "^7.0.0-0",
|
||||||
"typescript": "~3.9.3",
|
"typescript": "~4.1.5",
|
||||||
"vue-jest": "^5.0.0-0",
|
"vue-jest": "^5.0.0-0",
|
||||||
"wait-on": "^5.3.0"
|
"wait-on": "^5.3.0"
|
||||||
},
|
}
|
||||||
"description": "An Ionic project"
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: '/lifecycle',
|
path: '/lifecycle',
|
||||||
component: () => import('@/views/Lifecycle.vue')
|
component: () => import('@/views/Lifecycle.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/lifecycle-setup',
|
||||||
|
component: () => import('@/views/LifecycleSetup.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/overlays',
|
path: '/overlays',
|
||||||
name: 'Overlays',
|
name: 'Overlays',
|
||||||
|
@ -47,6 +47,9 @@
|
|||||||
<ion-item button router-link="/lifecycle" id="lifecycle">
|
<ion-item button router-link="/lifecycle" id="lifecycle">
|
||||||
<ion-label>Lifecycle</ion-label>
|
<ion-label>Lifecycle</ion-label>
|
||||||
</ion-item>
|
</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-item button router-link="/delayed-redirect" id="delayed-redirect">
|
||||||
<ion-label>Delayed Redirect</ion-label>
|
<ion-label>Delayed Redirect</ion-label>
|
||||||
</ion-item>
|
</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-item button router-link="/tabs" id="tabs-primary">
|
||||||
<ion-label>Go to Primary Tabs</ion-label>
|
<ion-label>Go to Primary Tabs</ion-label>
|
||||||
</ion-item>
|
</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-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
</template>
|
</template>
|
||||||
|
@ -55,14 +55,62 @@ describe('Lifecycle', () => {
|
|||||||
onIonViewDidLeave: 0
|
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 = {}) => {
|
const testLifecycle = (selector, expected = {}) => {
|
||||||
cy.get(`[data-pageid=${selector}] #willEnter`).should('have.text', expected.ionViewWillEnter);
|
if (expected.ionViewWillEnter) {
|
||||||
cy.get(`[data-pageid=${selector}] #didEnter`).should('have.text', expected.ionViewDidEnter);
|
cy.get(`[data-pageid=${selector}] #willEnter`).should('have.text', expected.ionViewWillEnter);
|
||||||
cy.get(`[data-pageid=${selector}] #willLeave`).should('have.text', expected.ionViewWillLeave);
|
}
|
||||||
cy.get(`[data-pageid=${selector}] #didLeave`).should('have.text', expected.ionViewDidLeave);
|
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}] #onWillEnter`).should('have.text', expected.onIonViewWillEnter);
|
||||||
cy.get(`[data-pageid=${selector}] #onDidEnter`).should('have.text', expected.onIonViewDidEnter);
|
cy.get(`[data-pageid=${selector}] #onDidEnter`).should('have.text', expected.onIonViewDidEnter);
|
||||||
cy.get(`[data-pageid=${selector}] #onWillLeave`).should('have.text', expected.onIonViewWillLeave);
|
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-tab1').should('not.have.class', 'tab-selected');
|
||||||
cy.get('ion-tab-button#tab-button-tab4').should('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', () => {
|
describe('Tabs - Swipe to Go Back', () => {
|
||||||
|
Reference in New Issue
Block a user