mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7947a26ba4 | ||
|
|
ffb056d50e | ||
|
|
2d9724947d | ||
|
|
02ef5ae179 | ||
|
|
4aab72b061 | ||
|
|
9c9e28ccc9 | ||
|
|
1c2875044a | ||
|
|
d665ace5c4 | ||
|
|
672ab80807 | ||
|
|
da3b93b4a2 | ||
|
|
5e5054d369 | ||
|
|
8f188eaae7 | ||
|
|
f6a00ea954 | ||
|
|
b083ae4e58 | ||
|
|
f7bd4c02c3 | ||
|
|
047d3c7772 | ||
|
|
4b26feaa47 | ||
|
|
e41b0e0cf2 | ||
|
|
7f61b06895 | ||
|
|
89e4bc56a1 | ||
|
|
fb96ab5a26 | ||
|
|
6f01c3b73d | ||
|
|
07d83ccd24 | ||
|
|
816096f897 | ||
|
|
615dcc0461 | ||
|
|
351c30ce42 | ||
|
|
3b9b9082b8 | ||
|
|
0774cca2cd | ||
|
|
6c366aaf87 | ||
|
|
6876fd089f | ||
|
|
22a8842ac2 | ||
|
|
2d5faa75db | ||
|
|
cab2a5103f | ||
|
|
d36050918a | ||
|
|
64f128be07 | ||
|
|
87999e3c7a | ||
|
|
bb4554211d | ||
|
|
f71109b088 | ||
|
|
44e18bd795 | ||
|
|
f4d265eb60 | ||
|
|
1e8dfb7d85 | ||
|
|
9f023c92c4 | ||
|
|
694d47b794 | ||
|
|
b87c555a6e |
@@ -41,7 +41,7 @@ runs:
|
||||
run: npm run test:unit
|
||||
shell: bash
|
||||
working-directory: ./packages/vue/test-app
|
||||
- name: Run E2E ests
|
||||
- name: Run E2E Tests
|
||||
run: npm run test:e2e
|
||||
shell: bash
|
||||
working-directory: ./packages/vue/test-app
|
||||
|
||||
@@ -44,7 +44,7 @@ async function askNpmTag(version) {
|
||||
type: 'list',
|
||||
name: 'npmTag',
|
||||
message: 'Select npm tag or specify a new tag',
|
||||
choices: ['latest', 'next', 'v4-lts']
|
||||
choices: ['latest', 'next', 'v4-lts', 'v5-lts']
|
||||
.concat([
|
||||
new inquirer.Separator(),
|
||||
{
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
||||
## [5.9.4](https://github.com/ionic-team/ionic/compare/v5.9.3...v5.9.4) (2022-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** inherit aria attributes on host elements ([#25156](https://github.com/ionic-team/ionic/issues/25156)) ([#25169](https://github.com/ionic-team/ionic/issues/25169)) ([ffb056d](https://github.com/ionic-team/ionic/commit/ffb056d50e126a1b89f5133de1e7516d0c29a61a))
|
||||
|
||||
|
||||
|
||||
## [5.9.3](https://github.com/ionic-team/ionic/compare/v5.9.2...v5.9.3) (2021-12-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **vue:** improve query params handling in tabs ([#24355](https://github.com/ionic-team/ionic/issues/24355)) ([1c28750](https://github.com/ionic-team/ionic/commit/1c2875044ad4d93fdca866017159a89f4dc8872d)), closes [#24353](https://github.com/ionic-team/ionic/issues/24353)
|
||||
* **vue:** tabs no longer get unmounted when navigating back to a tabs context ([#24337](https://github.com/ionic-team/ionic/issues/24337)) ([4aab72b](https://github.com/ionic-team/ionic/commit/4aab72b06159729d2dcd18b2ef0b76f693e5a74e)), closes [#24332](https://github.com/ionic-team/ionic/issues/24332)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **content:** remove global click listener to improve interaction performance ([#24360](https://github.com/ionic-team/ionic/issues/24360)) ([9c9e28c](https://github.com/ionic-team/ionic/commit/9c9e28ccc9f899c403c757d911ac02d9099415af)), closes [#24359](https://github.com/ionic-team/ionic/issues/24359)
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
## [5.9.1](https://github.com/ionic-team/ionic/compare/v5.9.0...v5.9.1) (2021-11-17)
|
||||
|
||||
|
||||
|
||||
18
angular/package-lock.json
generated
18
angular/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.9.0",
|
||||
"@ionic/core": "5.9.3",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -204,9 +204,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
@@ -5156,9 +5156,9 @@
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -42,7 +42,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.9.1",
|
||||
"@ionic/core": "5.9.4",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -86,11 +86,11 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
|
||||
*/
|
||||
const formControl = ngControl.control;
|
||||
if (formControl) {
|
||||
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
|
||||
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'] as const;
|
||||
methodsToPatch.forEach(method => {
|
||||
if (formControl[method]) {
|
||||
const oldFn = formControl[method].bind(formControl);
|
||||
formControl[method] = (...params) => {
|
||||
formControl[method] = (...params: any[]) => {
|
||||
oldFn(...params);
|
||||
setIonicClasses(this.el);
|
||||
};
|
||||
|
||||
@@ -305,7 +305,7 @@ export class StackController {
|
||||
|
||||
const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
|
||||
if (typeof (requestAnimationFrame as any) === 'function') {
|
||||
return new Promise<any>(resolve => {
|
||||
return new Promise<void>(resolve => {
|
||||
requestAnimationFrame(() => {
|
||||
cleanup(activeRoute, views, viewsSnapshot, location);
|
||||
resolve();
|
||||
|
||||
4
core/package-lock.json
generated
4
core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
||||
4
core/src/components.d.ts
vendored
4
core/src/components.d.ts
vendored
@@ -176,7 +176,7 @@ export namespace Components {
|
||||
*/
|
||||
"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;
|
||||
/**
|
||||
@@ -3511,7 +3511,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"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;
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/
|
||||
|
||||
import { config } from '../../global/config';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AnimationBuilder, Color } from '../../interface';
|
||||
import { ButtonInterface } from '../../utils/element-interface';
|
||||
import { inheritAttributes } from '../../utils/helpers';
|
||||
import type { AnimationBuilder, Color } from '../../interface';
|
||||
import type { ButtonInterface } from '../../utils/element-interface';
|
||||
import { inheritAriaAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,8 @@ export class BackButton implements ComponentInterface, ButtonInterface {
|
||||
@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;
|
||||
|
||||
@@ -66,7 +67,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
|
||||
@Prop() routerAnimation: AnimationBuilder | undefined;
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
|
||||
if (this.defaultHref === undefined) {
|
||||
this.defaultHref = config.get('backButtonDefaultHref');
|
||||
|
||||
@@ -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` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AnimationBuilder, Color, RouterDirection } from '../../interface';
|
||||
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
|
||||
import { hasShadowDom, inheritAttributes } from '../../utils/helpers';
|
||||
import type { AnimationBuilder, Color, RouterDirection } from '../../interface';
|
||||
import type { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
|
||||
import { hasShadowDom, inheritAriaAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -135,7 +135,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
this.inToolbar = !!this.el.closest('ion-buttons');
|
||||
this.inListHeader = !!this.el.closest('ion-list-header');
|
||||
this.inItem = !!this.el.closest('ion-item') || !!this.el.closest('ion-item-divider');
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
private get hasIconOnly() {
|
||||
|
||||
@@ -140,10 +140,37 @@
|
||||
}
|
||||
|
||||
: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;
|
||||
}
|
||||
:host(.content-sizing) .inner-scroll {
|
||||
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 {
|
||||
@@ -199,4 +226,15 @@
|
||||
|
||||
::slotted([slot="fixed"]) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -119,14 +119,6 @@ export class Content implements ComponentInterface {
|
||||
this.resize();
|
||||
}
|
||||
|
||||
@Listen('click', { capture: true })
|
||||
onClick(ev: Event) {
|
||||
if (this.isScrolling) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private shouldForceOverscroll() {
|
||||
const { forceOverscroll } = this;
|
||||
const mode = getIonMode(this);
|
||||
@@ -374,10 +366,17 @@ const getPageElement = (el: HTMLElement) => {
|
||||
if (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) {
|
||||
return page;
|
||||
}
|
||||
|
||||
return getParentElement(el);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { inheritAttributes } from '../../utils/helpers';
|
||||
import { inheritAriaAttributes } from '../../utils/helpers';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
|
||||
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
|
||||
@@ -46,7 +46,7 @@ export class Header implements ComponentInterface {
|
||||
@Prop() translucent = false;
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['role']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AutocompleteTypes, Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
|
||||
import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
|
||||
import type {
|
||||
AutocompleteTypes,
|
||||
Color,
|
||||
InputChangeEventDetail,
|
||||
StyleEventDetail,
|
||||
TextFieldTypes,
|
||||
} from '../../interface';
|
||||
import { debounceEvent, findItemLabel, inheritAriaAttributes, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -234,7 +240,10 @@ export class Input implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']);
|
||||
this.inheritedAttributes = {
|
||||
...inheritAriaAttributes(this.el),
|
||||
...inheritAttributes(this.el, ['tabindex', 'title']),
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Component, ComponentInterface, Element, Host, Listen, Prop, State, h }
|
||||
|
||||
import { config } from '../../global/config';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color } from '../../interface';
|
||||
import { ButtonInterface } from '../../utils/element-interface';
|
||||
import { inheritAttributes } from '../../utils/helpers';
|
||||
import type { Color } from '../../interface';
|
||||
import type { ButtonInterface } from '../../utils/element-interface';
|
||||
import { inheritAriaAttributes } from '../../utils/helpers';
|
||||
import { menuController } from '../../utils/menu-controller';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
|
||||
@@ -58,7 +58,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
|
||||
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getIonMode } from '../../global/ionic-global';
|
||||
import { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, Side } from '../../interface';
|
||||
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
|
||||
import { GESTURE_CONTROLLER } from '../../utils/gesture';
|
||||
import { assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers';
|
||||
import { assert, clamp, inheritAriaAttributes, isEndSide as isEnd } from '../../utils/helpers';
|
||||
import { menuController } from '../../utils/menu-controller';
|
||||
|
||||
const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
|
||||
@@ -213,7 +213,7 @@ AFTER:
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
|
||||
@@ -75,5 +75,9 @@
|
||||
--ion-safe-area-right: 0px;
|
||||
--ion-safe-area-bottom: 0px;
|
||||
--ion-safe-area-left: 0px;
|
||||
}
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ A Popover is a dialog that appears on top of the current page. It can be used fo
|
||||
|
||||
## Presenting
|
||||
|
||||
To present a popover, call the `present` method on a popover instance. In order to position the popover relative to the element clicked, a click event needs to be passed into the options of the the `present` method. If the event is not passed, the popover will be positioned in the center of the viewport.
|
||||
To present a popover, call the `present` method on a popover instance. In order to position the popover relative to the element clicked, a click event needs to be passed into the options of the `present` method. If the event is not passed, the popover will be positioned in the center of the viewport.
|
||||
|
||||
## Customization
|
||||
|
||||
|
||||
@@ -57,6 +57,14 @@ test('popover: custom class', async () => {
|
||||
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
|
||||
*/
|
||||
@@ -81,6 +89,14 @@ test('popover:rtl: custom class', async () => {
|
||||
await testPopover(DIRECTORY, '#custom-class-popover', 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 () => {
|
||||
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="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="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-footer>
|
||||
@@ -126,6 +128,56 @@
|
||||
}
|
||||
|
||||
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>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, Gesture, GestureDetail, KnobName, RangeChangeEventDetail, RangeValue, StyleEventDetail } from '../../interface';
|
||||
import { clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers';
|
||||
import type {
|
||||
Color,
|
||||
Gesture,
|
||||
GestureDetail,
|
||||
KnobName,
|
||||
RangeChangeEventDetail,
|
||||
RangeValue,
|
||||
StyleEventDetail,
|
||||
} from '../../interface';
|
||||
import { clamp, debounceEvent, getAriaLabel, inheritAriaAttributes, renderHiddenInput } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -205,7 +213,7 @@ export class Range implements ComponentInterface {
|
||||
*/
|
||||
this.rangeId = (this.el.hasAttribute('id')) ? this.el.getAttribute('id')! : `ion-r-${rangeIds++}`;
|
||||
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
|
||||
@@ -30,15 +30,24 @@ const CHAIN_3: RouteChain = [
|
||||
describe('matchesIDs', () => {
|
||||
it('should match simple set of ids', () => {
|
||||
const chain: RouteChain = CHAIN_1;
|
||||
expect(matchesIDs(['2'], chain)).toBe(1);
|
||||
expect(matchesIDs(['2', '1'], chain)).toBe(2);
|
||||
expect(matchesIDs(['2', '1', '3'], chain)).toBe(3);
|
||||
expect(matchesIDs(['2', '1', '3', '4'], chain)).toBe(4);
|
||||
expect(matchesIDs(['2', '1', '3', '4', '5'], chain)).toBe(4);
|
||||
expect(matchesIDs([{ id: '2' }], chain)).toBe(1);
|
||||
expect(matchesIDs([{ id: '2' }, { id: '1' }], chain)).toBe(2);
|
||||
expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }], chain)).toBe(3);
|
||||
expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }, { id: '4' }], 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(['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', () => {
|
||||
@@ -227,7 +236,7 @@ describe('mergeParams', () => {
|
||||
});
|
||||
|
||||
describe('RouterSegments', () => {
|
||||
it ('should initialize with empty array', () => {
|
||||
it('should initialize with empty array', () => {
|
||||
const s = new RouterSegments([]);
|
||||
expect(s.next()).toEqual('');
|
||||
expect(s.next()).toEqual('');
|
||||
@@ -236,7 +245,7 @@ describe('RouterSegments', () => {
|
||||
expect(s.next()).toEqual('');
|
||||
});
|
||||
|
||||
it ('should initialize with array', () => {
|
||||
it('should initialize with array', () => {
|
||||
const s = new RouterSegments(['', 'path', 'to', 'destination']);
|
||||
expect(s.next()).toEqual('');
|
||||
expect(s.next()).toEqual('path');
|
||||
|
||||
@@ -32,16 +32,60 @@ export const findRouteRedirect = (path: string[], redirects: RouteRedirect[]) =>
|
||||
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);
|
||||
let i = 0;
|
||||
for (; i < len; i++) {
|
||||
if (ids[i].toLowerCase() !== chain[i].id) {
|
||||
|
||||
let score = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
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 => {
|
||||
const segments = new RouterSegments(inputPath);
|
||||
@@ -90,16 +134,16 @@ export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain
|
||||
|
||||
// Merges the route parameter objects.
|
||||
// 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;
|
||||
};
|
||||
|
||||
export const routerIDsToChain = (ids: RouteID[], chains: RouteChain[]): RouteChain | null => {
|
||||
let match: RouteChain | null = null;
|
||||
let maxMatches = 0;
|
||||
const plainIDs = ids.map(i => i.id);
|
||||
|
||||
for (const chain of chains) {
|
||||
const score = matchesIDs(plainIDs, chain);
|
||||
const score = matchesIDs(ids, chain);
|
||||
if (score > maxMatches) {
|
||||
match = chain;
|
||||
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 { componentOnReady } from '../../utils/helpers'
|
||||
@@ -24,8 +24,6 @@ export class Slides implements ComponentInterface {
|
||||
private mutationO?: MutationObserver;
|
||||
private readySwiper!: (swiper: SwiperInterface) => void;
|
||||
private swiper: Promise<SwiperInterface> = new Promise(resolve => { this.readySwiper = resolve; });
|
||||
private syncSwiper?: SwiperInterface;
|
||||
private didInit = false;
|
||||
|
||||
@Element() el!: HTMLIonSlidesElement;
|
||||
|
||||
@@ -141,8 +139,7 @@ export class Slides implements ComponentInterface {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// tslint:disable-next-line: strict-type-predicates
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
if (Build.isBrowser) {
|
||||
const mut = this.mutationO = new MutationObserver(() => {
|
||||
if (this.swiperReady) {
|
||||
this.update();
|
||||
@@ -154,10 +151,7 @@ export class Slides implements ComponentInterface {
|
||||
});
|
||||
|
||||
componentOnReady(this.el, () => {
|
||||
if (!this.didInit) {
|
||||
this.didInit = true;
|
||||
this.initSwiper();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -167,23 +161,6 @@ export class Slides implements ComponentInterface {
|
||||
this.mutationO.disconnect();
|
||||
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);
|
||||
const swiper = new Swiper(this.el, finalOptions);
|
||||
this.swiperReady = true;
|
||||
this.syncSwiper = swiper;
|
||||
this.readySwiper(swiper);
|
||||
}
|
||||
|
||||
@@ -483,6 +459,8 @@ export class Slides implements ComponentInterface {
|
||||
init: () => {
|
||||
setTimeout(() => {
|
||||
this.ionSlidesDidLoad.emit();
|
||||
// Forces the swiper instance to update after initializing.
|
||||
this.update();
|
||||
}, 20);
|
||||
},
|
||||
slideChangeTransitionStart: this.ionSlideWillChange.emit,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
||||
import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
|
||||
import type { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
||||
import { debounceEvent, findItemLabel, inheritAriaAttributes, inheritAttributes, raf } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -214,7 +214,10 @@ export class Textarea implements ComponentInterface {
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['title']);
|
||||
this.inheritedAttributes = {
|
||||
...inheritAriaAttributes(this.el),
|
||||
...inheritAttributes(this.el, ['title']),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
|
||||
@@ -51,6 +51,74 @@ export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) =>
|
||||
return attributeObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of available ARIA attributes + `role`.
|
||||
* Removed deprecated attributes.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
|
||||
*/
|
||||
const ariaAttributes = [
|
||||
'role',
|
||||
'aria-activedescendant',
|
||||
'aria-atomic',
|
||||
'aria-autocomplete',
|
||||
'aria-braillelabel',
|
||||
'aria-brailleroledescription',
|
||||
'aria-busy',
|
||||
'aria-checked',
|
||||
'aria-colcount',
|
||||
'aria-colindex',
|
||||
'aria-colindextext',
|
||||
'aria-colspan',
|
||||
'aria-controls',
|
||||
'aria-current',
|
||||
'aria-describedby',
|
||||
'aria-description',
|
||||
'aria-details',
|
||||
'aria-disabled',
|
||||
'aria-errormessage',
|
||||
'aria-expanded',
|
||||
'aria-flowto',
|
||||
'aria-haspopup',
|
||||
'aria-hidden',
|
||||
'aria-invalid',
|
||||
'aria-keyshortcuts',
|
||||
'aria-label',
|
||||
'aria-labelledby',
|
||||
'aria-level',
|
||||
'aria-live',
|
||||
'aria-multiline',
|
||||
'aria-multiselectable',
|
||||
'aria-orientation',
|
||||
'aria-owns',
|
||||
'aria-placeholder',
|
||||
'aria-posinset',
|
||||
'aria-pressed',
|
||||
'aria-readonly',
|
||||
'aria-relevant',
|
||||
'aria-required',
|
||||
'aria-roledescription',
|
||||
'aria-rowcount',
|
||||
'aria-rowindex',
|
||||
'aria-rowindextext',
|
||||
'aria-rowspan',
|
||||
'aria-selected',
|
||||
'aria-setsize',
|
||||
'aria-sort',
|
||||
'aria-valuemax',
|
||||
'aria-valuemin',
|
||||
'aria-valuenow',
|
||||
'aria-valuetext',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of aria attributes that should be copied from
|
||||
* the shadow host element to a target within the light DOM.
|
||||
* @param el The element that the attributes should be copied from.
|
||||
*/
|
||||
export const inheritAriaAttributes = (el: HTMLElement) => {
|
||||
return inheritAttributes(el, ariaAttributes);
|
||||
};
|
||||
|
||||
export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
|
||||
if (typeof (window as any) !== 'undefined') {
|
||||
const win = window as any;
|
||||
@@ -164,8 +232,8 @@ export const getAriaLabel = (componentEl: HTMLElement, inputId: string): { label
|
||||
labelText = label.textContent;
|
||||
label.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// if there is no label, check to see if the user has provided
|
||||
// one by setting an id on the component and using the label element
|
||||
// if there is no label, check to see if the user has provided
|
||||
// one by setting an id on the component and using the label element
|
||||
} else if (componentId.trim() !== '') {
|
||||
label = document.querySelector(`label[for="${componentId}"]`);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inheritAttributes } from '../helpers';
|
||||
import { inheritAttributes, inheritAriaAttributes } from '../helpers';
|
||||
|
||||
describe('inheritAttributes()', () => {
|
||||
it('should create an attribute inheritance object', () => {
|
||||
@@ -37,3 +37,29 @@ describe('inheritAttributes()', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inheritAriaAttributes()', () => {
|
||||
it('should inherit ARIA attributes defined on the HTML element', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('aria-label', 'myLabel');
|
||||
el.setAttribute('aria-describedby', 'myDescription');
|
||||
|
||||
const attributeObject = inheritAriaAttributes(el);
|
||||
|
||||
expect(attributeObject).toEqual({
|
||||
'aria-label': 'myLabel',
|
||||
'aria-describedby': 'myDescription',
|
||||
});
|
||||
});
|
||||
|
||||
it('should inherit the role attribute defined on the HTML element', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('role', 'button');
|
||||
|
||||
const attributeObject = inheritAriaAttributes(el);
|
||||
|
||||
expect(attributeObject).toEqual({
|
||||
role: 'button',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Pre-packaged API documentation for the Ionic docs.",
|
||||
"main": "core.json",
|
||||
"types": "core.d.ts",
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"release.dev": "node .scripts/release-dev.js",
|
||||
"release.prepare": "node .scripts/prepare.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": {
|
||||
"@commitlint/cli": "^13.1.0",
|
||||
@@ -18,6 +19,7 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"execa": "^0.10.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"git-branch-is": "^4.0.0",
|
||||
"husky": "^4.3.8",
|
||||
"inquirer": "^6.0.0",
|
||||
"listr": "^0.14.0",
|
||||
@@ -34,8 +36,8 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
|
||||
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true"
|
||||
"commit-msg": "npm run commitizenBranches --silent && commitlint -E HUSKY_GIT_PARAMS || true",
|
||||
"prepare-commit-msg": "npm run commitizenBranches --silent && exec < /dev/tty && git cz --hook || true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
packages/angular-server/package-lock.json
generated
18
packages/angular-server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@angular/animations": "8.2.13",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@angular/core": "8.2.13",
|
||||
"@angular/platform-browser": "8.2.13",
|
||||
"@angular/platform-server": "8.2.13",
|
||||
"@ionic/core": "5.9.0",
|
||||
"@ionic/core": "5.9.3",
|
||||
"ng-packagr": "5.7.1",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-ionic-rules": "0.0.21",
|
||||
@@ -137,9 +137,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
@@ -5424,9 +5424,9 @@
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Angular SSR Module for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -49,7 +49,7 @@
|
||||
"@angular/core": "8.2.13",
|
||||
"@angular/platform-browser": "8.2.13",
|
||||
"@angular/platform-server": "8.2.13",
|
||||
"@ionic/core": "5.9.1",
|
||||
"@ionic/core": "5.9.4",
|
||||
"ng-packagr": "5.7.1",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-ionic-rules": "0.0.21",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/react-router",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "React Router wrapper for @ionic/react",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -40,14 +40,14 @@
|
||||
"tslib": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ionic/react": "5.9.1",
|
||||
"@ionic/react": "5.9.4",
|
||||
"react": ">=16.8.6",
|
||||
"react-dom": ">=16.8.6",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/react": "5.9.1",
|
||||
"@ionic/react": "5.9.4",
|
||||
"@rollup/plugin-node-resolve": "^8.1.0",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
"@testing-library/react": "^11.2.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/react",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "React specific wrapper for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -40,7 +40,7 @@
|
||||
"css/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.9.1",
|
||||
"@ionic/core": "5.9.4",
|
||||
"ionicons": "^5.1.2",
|
||||
"tslib": "*"
|
||||
},
|
||||
|
||||
@@ -16,9 +16,9 @@ class IonTabsElement extends HTMLElementSSR {
|
||||
}
|
||||
|
||||
if (typeof (window as any) !== 'undefined' && window.customElements) {
|
||||
const element = customElements.get('ion-tabs');
|
||||
const element = window.customElements.get('ion-tabs');
|
||||
if (!element) {
|
||||
customElements.define('ion-tabs', IonTabsElement);
|
||||
window.customElements.define('ion-tabs', IonTabsElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ export function useIonActionSheet(): UseIonActionSheetResult {
|
||||
header?: string
|
||||
) => {
|
||||
if (Array.isArray(buttonsOrOptions)) {
|
||||
controller.present({
|
||||
return controller.present({
|
||||
buttons: buttonsOrOptions,
|
||||
header,
|
||||
});
|
||||
} else {
|
||||
controller.present(buttonsOrOptions);
|
||||
return controller.present(buttonsOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
@@ -41,15 +41,15 @@ export type UseIonActionSheetResult = [
|
||||
* @param buttons An array of buttons 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
|
||||
* @param options The options to pass to the IonActionSheet
|
||||
*/
|
||||
(options: ActionSheetOptions & HookOverlayOptions): void;
|
||||
(options: ActionSheetOptions & HookOverlayOptions): Promise<void>;
|
||||
},
|
||||
/**
|
||||
* Dismisses the action sheet
|
||||
*/
|
||||
() => void
|
||||
() => Promise<void>
|
||||
];
|
||||
|
||||
@@ -14,12 +14,12 @@ export function useIonAlert(): UseIonAlertResult {
|
||||
const present = useCallback(
|
||||
(messageOrOptions: string | (AlertOptions & HookOverlayOptions), buttons?: AlertButton[]) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
return controller.present({
|
||||
message: messageOrOptions,
|
||||
buttons: buttons ?? [{ text: 'Ok' }],
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
return controller.present(messageOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
@@ -35,15 +35,15 @@ export type UseIonAlertResult = [
|
||||
* @param message The main message to be displayed in 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
|
||||
* @param options The options to pass to the IonAlert
|
||||
*/
|
||||
(options: AlertOptions & HookOverlayOptions): void;
|
||||
(options: AlertOptions & HookOverlayOptions): Promise<void>;
|
||||
},
|
||||
/**
|
||||
* Dismisses the alert
|
||||
*/
|
||||
() => void
|
||||
() => Promise<void>
|
||||
];
|
||||
|
||||
@@ -21,13 +21,13 @@ export function useIonLoading(): UseIonLoadingResult {
|
||||
spinner?: SpinnerTypes
|
||||
) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
return controller.present({
|
||||
message: messageOrOptions,
|
||||
duration,
|
||||
spinner: spinner ?? 'lines',
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
return controller.present(messageOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
@@ -44,15 +44,15 @@ export type UseIonLoadingResult = [
|
||||
* @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"
|
||||
*/
|
||||
(message?: string, duration?: number, spinner?: SpinnerTypes): void;
|
||||
(message?: string, duration?: number, spinner?: SpinnerTypes): Promise<void>;
|
||||
/**
|
||||
* Presents the loading indicator
|
||||
* @param options The options to pass to the IonLoading
|
||||
*/
|
||||
(options: LoadingOptions & HookOverlayOptions): void;
|
||||
(options: LoadingOptions & HookOverlayOptions): Promise<void>;
|
||||
},
|
||||
/**
|
||||
* Dismisses the loading indicator
|
||||
*/
|
||||
() => void
|
||||
() => Promise<void>
|
||||
];
|
||||
|
||||
@@ -19,12 +19,12 @@ export function useIonPicker(): UseIonPickerResult {
|
||||
buttons?: PickerButton[]
|
||||
) => {
|
||||
if (Array.isArray(columnsOrOptions)) {
|
||||
controller.present({
|
||||
return controller.present({
|
||||
columns: columnsOrOptions,
|
||||
buttons: buttons ?? [{ text: 'Ok' }],
|
||||
});
|
||||
} else {
|
||||
controller.present(columnsOrOptions);
|
||||
return controller.present(columnsOrOptions);
|
||||
}
|
||||
}, [controller.present]);
|
||||
|
||||
@@ -38,15 +38,15 @@ export type UseIonPickerResult = [
|
||||
* @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.
|
||||
*/
|
||||
(columns: PickerColumn[], buttons?: PickerButton[]): void;
|
||||
(columns: PickerColumn[], buttons?: PickerButton[]): Promise<void>;
|
||||
/**
|
||||
* Presents the picker
|
||||
* @param options The options to pass to the IonPicker
|
||||
*/
|
||||
(options: PickerOptions & HookOverlayOptions): void;
|
||||
(options: PickerOptions & HookOverlayOptions): Promise<void>;
|
||||
},
|
||||
/**
|
||||
* Dismisses the picker
|
||||
*/
|
||||
() => void
|
||||
() => Promise<void>
|
||||
];
|
||||
|
||||
@@ -16,12 +16,12 @@ export function useIonToast(): UseIonToastResult {
|
||||
|
||||
const present = useCallback((messageOrOptions: string | ToastOptions & HookOverlayOptions, duration?: number) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
return controller.present({
|
||||
message: messageOrOptions,
|
||||
duration
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
return controller.present(messageOrOptions);
|
||||
}
|
||||
}, [controller.present]);
|
||||
|
||||
@@ -38,15 +38,15 @@ export type UseIonToastResult = [
|
||||
* @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.
|
||||
*/
|
||||
(message: string, duration?: number): void;
|
||||
(message: string, duration?: number): Promise<void>;
|
||||
/**
|
||||
* Presents the Toast
|
||||
* @param options The options to pass to the IonToast.
|
||||
*/
|
||||
(options: ToastOptions & HookOverlayOptions): void;
|
||||
(options: ToastOptions & HookOverlayOptions): Promise<void>;
|
||||
},
|
||||
/**
|
||||
* Dismisses the toast
|
||||
*/
|
||||
() => void
|
||||
() => Promise<void>
|
||||
];
|
||||
|
||||
4
packages/vue-router/package-lock.json
generated
4
packages/vue-router/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@ionic/vue": "5.4.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/vue-router",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Vue Router integration for @ionic/vue",
|
||||
"scripts": {
|
||||
"test.spec": "jest",
|
||||
|
||||
18
packages/vue/package-lock.json
generated
18
packages/vue/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/vue",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/vue",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.9.0",
|
||||
"@ionic/core": "5.9.3",
|
||||
"ionicons": "^5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -53,9 +53,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
@@ -633,9 +633,9 @@
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.0.tgz",
|
||||
"integrity": "sha512-0mUnNPFzQK89/ZsuiKb9tQ1rRzILDSeNsp+4ASjf9z8FJuULeTqyDEHU3Pwnje7cLwl8lezGlvNpOXu7Xlz+/w==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.9.3.tgz",
|
||||
"integrity": "sha512-WM50vVxAAw+MQYqWXKUK4usBgkr7iQ9UWSb6t59mG4ZSy/fPAb7ZIdAjxY0U5i1ykk6A7Ur4B9ZJMpC/a7nnug==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/vue",
|
||||
"version": "5.9.1",
|
||||
"version": "5.9.4",
|
||||
"description": "Vue specific wrapper for @ionic/core",
|
||||
"scripts": {
|
||||
"lint": "echo add linter",
|
||||
@@ -59,7 +59,7 @@
|
||||
"vue-router": "^4.0.0-rc.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.9.1",
|
||||
"@ionic/core": "5.9.4",
|
||||
"ionicons": "^5.1.2"
|
||||
},
|
||||
"vetur": {
|
||||
|
||||
@@ -14,6 +14,10 @@ import { AnimationBuilder, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_W
|
||||
import { matchedRouteKey, routeLocationKey, useRoute } from 'vue-router';
|
||||
import { fireLifecycle, generateId, getConfig } from '../utils';
|
||||
|
||||
const isViewVisible = (enteringEl: HTMLElement) => {
|
||||
return !enteringEl.classList.contains('ion-page-hidden') && !enteringEl.classList.contains('ion-page-invisible');
|
||||
}
|
||||
|
||||
let viewDepthKey: InjectionKey<0> = Symbol(0);
|
||||
export const IonRouterOutlet = defineComponent({
|
||||
name: 'IonRouterOutlet',
|
||||
@@ -230,13 +234,35 @@ export const IonRouterOutlet = defineComponent({
|
||||
|
||||
See https://ionicframework.com/docs/vue/navigation#ionpage for more information.`);
|
||||
}
|
||||
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
if (!leavingViewItem && prevRouteLastPathname) {
|
||||
leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id, usingDeprecatedRouteSetup);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the entering view is already
|
||||
* visible, then no transition is needed.
|
||||
* This is most common when navigating
|
||||
* from a tabs page to a non-tabs page
|
||||
* and then back to the tabs page.
|
||||
* Even when the tabs context navigated away,
|
||||
* the inner tabs page was still active.
|
||||
* This also avoids an issue where
|
||||
* the previous tabs page is incorrectly
|
||||
* unmounted since it would automatically
|
||||
* unmount the previous view.
|
||||
*
|
||||
* This should also only apply to entering and
|
||||
* leaving items in the same router outlet (i.e.
|
||||
* Tab1 and Tab2), otherwise this will
|
||||
* return early for swipe to go back when
|
||||
* going from a non-tabs page to a tabs page.
|
||||
*/
|
||||
if (isViewVisible(enteringEl) && leavingViewItem !== undefined && !isViewVisible(leavingViewItem.ionPageElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
|
||||
|
||||
if (leavingViewItem && enteringViewItem !== leavingViewItem) {
|
||||
|
||||
@@ -113,9 +113,17 @@ export const IonTabBar = defineComponent({
|
||||
* land on /tabs/tab1/child instead of /tabs/tab1.
|
||||
*/
|
||||
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}` : '';
|
||||
tabs[activeTab] = {
|
||||
...tabs[activeTab],
|
||||
currentHref: currentRoute.pathname + (currentRoute.search || '')
|
||||
currentHref: currentRoute.pathname + search
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,12 @@ const injectHook = (lifecycleType: LifecycleHooks, hook: Function, component: Co
|
||||
// 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;
|
||||
|
||||
3872
packages/vue/test-app/package-lock.json
generated
3872
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.0.0-0",
|
||||
"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);
|
||||
|
||||
@@ -285,6 +285,77 @@ 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');
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24353
|
||||
it('should handle clicking tab multiple times without query string', () => {
|
||||
cy.visit('http://localhost:8080/tabs/tab1');
|
||||
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||
cy.ionPageVisible('tab2');
|
||||
cy.ionPageHidden('tab1');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab1').click();
|
||||
cy.ionPageVisible('tab1');
|
||||
cy.ionPageHidden('tab2');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab1').click();
|
||||
cy.ionPageVisible('tab1');
|
||||
cy.ionPageHidden('tab2');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||
cy.ionPageVisible('tab2');
|
||||
cy.ionPageHidden('tab1');
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24332
|
||||
it('should not unmount tab 1 when leaving tabs context', () => {
|
||||
cy.visit('http://localhost:8080/tabs');
|
||||
cy.ionPageVisible('tab1');
|
||||
|
||||
// Dynamically add tab 4 because tab 3 redirects to tab 1
|
||||
cy.get('#add-tab').click();
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab4').click();
|
||||
cy.ionPageHidden('tab1');
|
||||
cy.ionPageVisible('tab4');
|
||||
|
||||
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||
cy.ionPageHidden('tab4');
|
||||
cy.ionPageVisible('tab2');
|
||||
|
||||
cy.get('[data-pageid="tab2"] #routing').click();
|
||||
cy.ionPageVisible('routing');
|
||||
cy.ionPageHidden('tabs');
|
||||
|
||||
cy.ionBackClick('routing');
|
||||
cy.ionPageDoesNotExist('routing');
|
||||
cy.ionPageVisible('tabs');
|
||||
cy.ionPageVisible('tab2');
|
||||
cy.ionPageHidden('tab1');
|
||||
});
|
||||
})
|
||||
|
||||
describe('Tabs - Swipe to Go Back', () => {
|
||||
|
||||
Reference in New Issue
Block a user