mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
69 Commits
v6.0.8
...
fix/transl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cefcd02fb9 | ||
|
|
a296ca875c | ||
|
|
c52f3ad2c2 | ||
|
|
8424210049 | ||
|
|
c35077884d | ||
|
|
df71a0e625 | ||
|
|
2909b080b7 | ||
|
|
a8fd2d9199 | ||
|
|
9e84ef7f91 | ||
|
|
63842a25c3 | ||
|
|
0d0740161f | ||
|
|
43ac6ac471 | ||
|
|
02b7baf6a6 | ||
|
|
66e125bc5e | ||
|
|
fc0d7282f5 | ||
|
|
babe7d63ef | ||
|
|
8a97f6b5c9 | ||
|
|
4534c8bc0b | ||
|
|
d46e1e8506 | ||
|
|
981eeba0e1 | ||
|
|
c666deca4d | ||
|
|
8e8783190f | ||
|
|
d5efa11331 | ||
|
|
3e7dfd5f73 | ||
|
|
c45b38f28a | ||
|
|
44a6f032e7 | ||
|
|
b193b83c7c | ||
|
|
11a5edfd9d | ||
|
|
77a697ccd7 | ||
|
|
805907af4e | ||
|
|
8ed948e647 | ||
|
|
f6cde30d3e | ||
|
|
2ac9105796 | ||
|
|
331ce6d676 | ||
|
|
65b43aae2b | ||
|
|
9154ce3bdc | ||
|
|
c7f7db915a | ||
|
|
479c1bac48 | ||
|
|
c87366b186 | ||
|
|
56b550e220 | ||
|
|
7131e449e5 | ||
|
|
4b5843ed5d | ||
|
|
aacb58a322 | ||
|
|
e4ec572043 | ||
|
|
9e0917597c | ||
|
|
5faf51caef | ||
|
|
5baeeb1172 | ||
|
|
ea4a9bb694 | ||
|
|
836c01c73e | ||
|
|
3d0f99904f | ||
|
|
c35b898f1d | ||
|
|
33292fbd92 | ||
|
|
1ac8ffb1b7 | ||
|
|
1f86b4ee5a | ||
|
|
62878238fc | ||
|
|
b6d7e1c757 | ||
|
|
b0ac7de168 | ||
|
|
7f1086740b | ||
|
|
cd05961ab1 | ||
|
|
1c3b3791d0 | ||
|
|
8246112ca1 | ||
|
|
19ac2389eb | ||
|
|
243f67362f | ||
|
|
0a8f44359a | ||
|
|
2fc2de5177 | ||
|
|
99c91eff87 | ||
|
|
16647b2c72 | ||
|
|
fca3f56ca4 | ||
|
|
866f261f07 |
@@ -29,9 +29,19 @@ runs:
|
||||
name: ionic-react-router
|
||||
path: ./packages/react-router
|
||||
filename: ReactRouterBuild.zip
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
browser: chrome
|
||||
headless: true
|
||||
start: npm run start.ci
|
||||
working-directory: ./packages/react/test-app
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
shell: bash
|
||||
working-directory: ./packages/react/test-app
|
||||
- name: Sync
|
||||
run: npm run sync
|
||||
shell: bash
|
||||
working-directory: ./packages/react/test-app
|
||||
- name: Build
|
||||
run: npm run build
|
||||
shell: bash
|
||||
working-directory: ./packages/react/test-app
|
||||
- name: Run E2E Tests
|
||||
run: npm run e2e
|
||||
shell: bash
|
||||
working-directory: ./packages/react/test-app
|
||||
|
||||
@@ -29,9 +29,19 @@ runs:
|
||||
name: ionic-react-router
|
||||
path: ./packages/react-router
|
||||
filename: ReactRouterBuild.zip
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
browser: chrome
|
||||
headless: true
|
||||
start: npm run start.ci
|
||||
working-directory: ./packages/react-router/test-app
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
shell: bash
|
||||
working-directory: ./packages/react-router/test-app
|
||||
- name: Sync
|
||||
run: npm run sync
|
||||
shell: bash
|
||||
working-directory: ./packages/react-router/test-app
|
||||
- name: Build
|
||||
run: npm run build
|
||||
shell: bash
|
||||
working-directory: ./packages/react-router/test-app
|
||||
- name: Run E2E Tests
|
||||
run: npm run e2e
|
||||
shell: bash
|
||||
working-directory: ./packages/react-router/test-app
|
||||
|
||||
7
.github/workflows/dev-build.yml
vendored
7
.github/workflows/dev-build.yml
vendored
@@ -21,10 +21,13 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
# A 1 is required before the timestamp
|
||||
# as lerna will fail when there is a leading 0
|
||||
# See https://github.com/lerna/lerna/issues/2840
|
||||
- name: Create Dev Hash
|
||||
run: |
|
||||
echo "HASH=$(git log -1 --format=%H | cut -c 1-7)" >> $GITHUB_ENV
|
||||
echo "TIMESTAMP=$(date +%s)" >> $GITHUB_ENV
|
||||
echo "HASH=1$(git log -1 --format=%H | cut -c 1-7)" >> $GITHUB_ENV
|
||||
echo "TIMESTAMP=1$(date +%s)" >> $GITHUB_ENV
|
||||
echo "CURRENT_VERSION=$(node ./.scripts/bump-version.js)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- name: Create Dev Build
|
||||
|
||||
11
.github/workflows/update-screenshots.yml
vendored
Normal file
11
.github/workflows/update-screenshots.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: 'Update Screenshot References'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stub:
|
||||
steps:
|
||||
- name: Stub
|
||||
run: echo 'This is a stub'
|
||||
shell: bash
|
||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -3,6 +3,85 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.0.13](https://github.com/ionic-team/ionic-framework/compare/v6.0.12...v6.0.13) (2022-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** ngOnDestroy runs inside angular zone ([#24949](https://github.com/ionic-team/ionic-framework/issues/24949)) ([a8fd2d9](https://github.com/ionic-team/ionic-framework/commit/a8fd2d9199ca92d62bce6abf8caccc7709fa5ca1)), closes [#22571](https://github.com/ionic-team/ionic-framework/issues/22571)
|
||||
* **datetime:** presentation time emits ionChange once ([#24968](https://github.com/ionic-team/ionic-framework/issues/24968)) ([2909b08](https://github.com/ionic-team/ionic-framework/commit/2909b080b7020299a4554c1459b4b190ff739085)), closes [#24967](https://github.com/ionic-team/ionic-framework/issues/24967)
|
||||
* **popover:** dismissing nested popover via backdrop now works ([#24957](https://github.com/ionic-team/ionic-framework/issues/24957)) ([9e84ef7](https://github.com/ionic-team/ionic-framework/commit/9e84ef7f91d76ca5a1ecaffc7592287267d5368b)), closes [#24954](https://github.com/ionic-team/ionic-framework/issues/24954)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.12](https://github.com/ionic-team/ionic-framework/compare/v6.0.11...v6.0.12) (2022-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** reinit behavior on presentation change ([#24828](https://github.com/ionic-team/ionic-framework/issues/24828)) ([d46e1e8](https://github.com/ionic-team/ionic-framework/commit/d46e1e8506ca5095817b421e9edb37d41451e885))
|
||||
* **tabs:** angular, fire willChange event before selected tab changes ([#24910](https://github.com/ionic-team/ionic-framework/issues/24910)) ([d5efa11](https://github.com/ionic-team/ionic-framework/commit/d5efa113317eaf874712134dc9b8e4502aa4760f))
|
||||
* **toast:** screen readers now announce toasts when presented ([#24937](https://github.com/ionic-team/ionic-framework/issues/24937)) ([8a97f6b](https://github.com/ionic-team/ionic-framework/commit/8a97f6b5c9ca1e77c1790abd1e924955b6b6ea27)), closes [#22333](https://github.com/ionic-team/ionic-framework/issues/22333)
|
||||
* **vue:** tapping the active tab button now correctly resets the tab stack ([#24935](https://github.com/ionic-team/ionic-framework/issues/24935)) ([4534c8b](https://github.com/ionic-team/ionic-framework/commit/4534c8bc0b2bca7ab6eecd9886243116e9a039b7)), closes [#24934](https://github.com/ionic-team/ionic-framework/issues/24934)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.11](https://github.com/ionic-team/ionic-framework/compare/v6.0.10...v6.0.11) (2022-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** time picker now scrolls to correct value ([#24879](https://github.com/ionic-team/ionic-framework/issues/24879)) ([331ce6d](https://github.com/ionic-team/ionic-framework/commit/331ce6d6769900e2aec9e30d35b52cfd40cbb889)), closes [#24878](https://github.com/ionic-team/ionic-framework/issues/24878)
|
||||
* **ios:** swipe to go back now works in rtl mode ([#24866](https://github.com/ionic-team/ionic-framework/issues/24866)) ([2ac9105](https://github.com/ionic-team/ionic-framework/commit/2ac9105796a0765fabc48592b5b44ac58c568579)), closes [#19488](https://github.com/ionic-team/ionic-framework/issues/19488)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* improve treeshaking functionality ([#24895](https://github.com/ionic-team/ionic-framework/issues/24895)) ([805907a](https://github.com/ionic-team/ionic-framework/commit/805907af4e78179f1acc9cb02263b1ea10d4e3df)), closes [#24280](https://github.com/ionic-team/ionic-framework/issues/24280)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.10](https://github.com/ionic-team/ionic-framework/compare/v6.0.9...v6.0.10) (2022-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** confirm method now uses selected date ([#24827](https://github.com/ionic-team/ionic-framework/issues/24827)) ([c35b898](https://github.com/ionic-team/ionic-framework/commit/c35b898f1dc0fb706446b6971982df48fd72fe6d)), closes [#24823](https://github.com/ionic-team/ionic-framework/issues/24823)
|
||||
* **datetime:** persist minutes column on hour change ([#24829](https://github.com/ionic-team/ionic-framework/issues/24829)) ([aacb58a](https://github.com/ionic-team/ionic-framework/commit/aacb58a3224e3cc51c731d0c9aa52f52c9276692)), closes [#24821](https://github.com/ionic-team/ionic-framework/issues/24821)
|
||||
* **item-sliding:** close() will maintain disabled state ([#24847](https://github.com/ionic-team/ionic-framework/issues/24847)) ([ea4a9bb](https://github.com/ionic-team/ionic-framework/commit/ea4a9bb69465f8e97746b36638f0b3a26e45da18)), closes [#24747](https://github.com/ionic-team/ionic-framework/issues/24747)
|
||||
* **modal:** .ion-page element is now correctly added ([#24811](https://github.com/ionic-team/ionic-framework/issues/24811)) ([3d0f999](https://github.com/ionic-team/ionic-framework/commit/3d0f99904fe192fcb5f529780858a0f25f076af7)), closes [#24809](https://github.com/ionic-team/ionic-framework/issues/24809)
|
||||
* **modal:** re-enable swipe gestures when modal is dismissed ([#24846](https://github.com/ionic-team/ionic-framework/issues/24846)) ([836c01c](https://github.com/ionic-team/ionic-framework/commit/836c01c73e42caab0101ac4988f0a9b27cf96a5b)), closes [#24817](https://github.com/ionic-team/ionic-framework/issues/24817)
|
||||
* **modal:** sheet modal now allows input focusing when backdrop disabled ([#24840](https://github.com/ionic-team/ionic-framework/issues/24840)) ([e4ec572](https://github.com/ionic-team/ionic-framework/commit/e4ec572043e22bd2626dbcfd204fc22a7335282c)), closes [#24581](https://github.com/ionic-team/ionic-framework/issues/24581)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.9](https://github.com/ionic-team/ionic-framework/compare/v6.0.8...v6.0.9) (2022-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** improve datetime sizing in modals ([#24762](https://github.com/ionic-team/ionic-framework/issues/24762)) ([b0ac7de](https://github.com/ionic-team/ionic-framework/commit/b0ac7de168c353ba4899cb74a2b38e25fd0cc0f1)), closes [#23992](https://github.com/ionic-team/ionic-framework/issues/23992)
|
||||
* **datetime:** month and year column order is now locale aware ([#24802](https://github.com/ionic-team/ionic-framework/issues/24802)) ([16647b2](https://github.com/ionic-team/ionic-framework/commit/16647b2c7290389755a4093145788f281c84b7e2)), closes [#24548](https://github.com/ionic-team/ionic-framework/issues/24548)
|
||||
* **datetime:** month picker no longer gives duplicate months on ios 14 and older ([#24792](https://github.com/ionic-team/ionic-framework/issues/24792)) ([b6d7e1c](https://github.com/ionic-team/ionic-framework/commit/b6d7e1c75740a61dcd02c21692e4d4632fb8df5c)), closes [#24663](https://github.com/ionic-team/ionic-framework/issues/24663)
|
||||
* **img:** draggable attribute is now inherited to inner img element ([#24781](https://github.com/ionic-team/ionic-framework/issues/24781)) ([19ac238](https://github.com/ionic-team/ionic-framework/commit/19ac2389eb0843173f51a12de41ac808cd8f0569)), closes [#21325](https://github.com/ionic-team/ionic-framework/issues/21325)
|
||||
* **modal:** backdropBreakpoint allows interactivity behind sheet ([#24798](https://github.com/ionic-team/ionic-framework/issues/24798)) ([fca3f56](https://github.com/ionic-team/ionic-framework/commit/fca3f56ca4568e63fd493debda088263caa86c64)), closes [#24797](https://github.com/ionic-team/ionic-framework/issues/24797)
|
||||
* **popover:** default alignment to 'center' for ios mode ([#24815](https://github.com/ionic-team/ionic-framework/issues/24815)) ([243f673](https://github.com/ionic-team/ionic-framework/commit/243f67362f25699bdb373be6b72cb9c14dc95a32))
|
||||
* **react, vue:** scroll is no longer interrupted on ios ([#24791](https://github.com/ionic-team/ionic-framework/issues/24791)) ([99c91ef](https://github.com/ionic-team/ionic-framework/commit/99c91eff8764c9a1630adedab6a9765dd9754f7d)), closes [#24435](https://github.com/ionic-team/ionic-framework/issues/24435)
|
||||
* **select:** interface components now show correctly ([#24810](https://github.com/ionic-team/ionic-framework/issues/24810)) ([2fc2de5](https://github.com/ionic-team/ionic-framework/commit/2fc2de51771f4a5c3f20c6071284096e5bf31ec8)), closes [#24807](https://github.com/ionic-team/ionic-framework/issues/24807)
|
||||
* **toast:** toast is now correctly excluded from focus trapping ([#24816](https://github.com/ionic-team/ionic-framework/issues/24816)) ([8246112](https://github.com/ionic-team/ionic-framework/commit/8246112ca12f90edb98629ab82e27a792a1fafad)), closes [#24733](https://github.com/ionic-team/ionic-framework/issues/24733)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.8](https://github.com/ionic-team/ionic-framework/compare/v6.0.7...v6.0.8) (2022-02-15)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.0.13](https://github.com/ionic-team/ionic/compare/v6.0.12...v6.0.13) (2022-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** ngOnDestroy runs inside angular zone ([#24949](https://github.com/ionic-team/ionic/issues/24949)) ([a8fd2d9](https://github.com/ionic-team/ionic/commit/a8fd2d9199ca92d62bce6abf8caccc7709fa5ca1)), closes [#22571](https://github.com/ionic-team/ionic/issues/22571)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.12](https://github.com/ionic-team/ionic/compare/v6.0.11...v6.0.12) (2022-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tabs:** angular, fire willChange event before selected tab changes ([#24910](https://github.com/ionic-team/ionic/issues/24910)) ([d5efa11](https://github.com/ionic-team/ionic/commit/d5efa113317eaf874712134dc9b8e4502aa4760f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.11](https://github.com/ionic-team/ionic/compare/v6.0.10...v6.0.11) (2022-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.10](https://github.com/ionic-team/ionic/compare/v6.0.9...v6.0.10) (2022-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** .ion-page element is now correctly added ([#24811](https://github.com/ionic-team/ionic/issues/24811)) ([3d0f999](https://github.com/ionic-team/ionic/commit/3d0f99904fe192fcb5f529780858a0f25f076af7)), closes [#24809](https://github.com/ionic-team/ionic/issues/24809)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.9](https://github.com/ionic-team/ionic/compare/v6.0.8...v6.0.9) (2022-02-23)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.8](https://github.com/ionic-team/ionic/compare/v6.0.7...v6.0.8) (2022-02-15)
|
||||
|
||||
**Note:** Version bump only for package @ionic/angular
|
||||
|
||||
2
angular/package-lock.json
generated
2
angular/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.0.8",
|
||||
"version": "6.0.13",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "6.0.8",
|
||||
"version": "6.0.13",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -44,7 +44,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "^6.0.8",
|
||||
"@ionic/core": "^6.0.13",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
|
||||
@@ -57,10 +57,10 @@ export class IonTabs {
|
||||
onPageSelected(detail: StackEvent): void {
|
||||
const stackId = detail.enteringView.stackId;
|
||||
if (detail.tabSwitch && stackId !== undefined) {
|
||||
this.ionTabsWillChange.emit({ tab: stackId });
|
||||
if (this.tabBar) {
|
||||
this.tabBar.selectedTab = stackId;
|
||||
}
|
||||
this.ionTabsWillChange.emit({ tab: stackId });
|
||||
this.ionTabsDidChange.emit({ tab: stackId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { LocationStrategy } from '@angular/common';
|
||||
import { ElementRef, OnChanges, OnDestroy, OnInit, Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
import { ElementRef, OnChanges, OnInit, Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { AnimationBuilder, RouterDirection } from '@ionic/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
|
||||
@Directive({
|
||||
selector: '[routerLink]',
|
||||
})
|
||||
export class RouterLinkDelegateDirective implements OnInit, OnChanges, OnDestroy {
|
||||
private subscription?: Subscription;
|
||||
|
||||
export class RouterLinkDelegateDirective implements OnInit, OnChanges {
|
||||
@Input()
|
||||
routerDirection: RouterDirection = 'forward';
|
||||
|
||||
@@ -34,12 +31,6 @@ export class RouterLinkDelegateDirective implements OnInit, OnChanges, OnDestroy
|
||||
this.updateTargetUrlAndHref();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private updateTargetUrlAndHref() {
|
||||
if (this.routerLink?.urlTree) {
|
||||
const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));
|
||||
|
||||
@@ -139,7 +139,7 @@ export class StackController {
|
||||
enteringView.ref.changeDetectorRef.reattach();
|
||||
|
||||
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
|
||||
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
|
||||
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location, this.zone))
|
||||
.then(() => ({
|
||||
enteringView,
|
||||
direction,
|
||||
@@ -201,7 +201,7 @@ export class StackController {
|
||||
this.skipTransition = true;
|
||||
this.pop(1);
|
||||
} else if (this.activeView) {
|
||||
cleanup(this.activeView, this.views, this.views, this.location);
|
||||
cleanup(this.activeView, this.views, this.views, this.location, this.zone);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,11 +294,17 @@ export class StackController {
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
|
||||
const cleanupAsync = (
|
||||
activeRoute: RouteView,
|
||||
views: RouteView[],
|
||||
viewsSnapshot: RouteView[],
|
||||
location: Location,
|
||||
zone: NgZone
|
||||
) => {
|
||||
if (typeof (requestAnimationFrame as any) === 'function') {
|
||||
return new Promise<void>((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
cleanup(activeRoute, views, viewsSnapshot, location);
|
||||
cleanup(activeRoute, views, viewsSnapshot, location, zone);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -306,8 +312,18 @@ const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot:
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const cleanup = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
|
||||
viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView);
|
||||
const cleanup = (
|
||||
activeRoute: RouteView,
|
||||
views: RouteView[],
|
||||
viewsSnapshot: RouteView[],
|
||||
location: Location,
|
||||
zone: NgZone
|
||||
) => {
|
||||
/**
|
||||
* Re-enter the Angular zone when destroying page components. This will allow
|
||||
* lifecycle events (`ngOnDestroy`) to be run inside the Angular zone.
|
||||
*/
|
||||
zone.run(() => viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView));
|
||||
|
||||
views.forEach((view) => {
|
||||
/**
|
||||
|
||||
@@ -73,7 +73,7 @@ export declare interface IonModal extends Components.IonModal {
|
||||
@Component({
|
||||
selector: 'ion-modal',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `<div class="ion-page"><ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container></div>`,
|
||||
template: `<div class="ion-page" *ngIf="isCmpOpen"><ng-container [ngTemplateOutlet]="template"></ng-container></div>`,
|
||||
inputs: [
|
||||
'animated',
|
||||
'backdropBreakpoint',
|
||||
|
||||
@@ -10,6 +10,7 @@ describe('Modals', () => {
|
||||
|
||||
cy.get('app-modal-example h2').should('have.text', '123');
|
||||
cy.get('app-modal-example h3').should('have.text', '321');
|
||||
cy.get('#modalInstance').should('have.text', 'true');
|
||||
|
||||
cy.get('#onWillDismiss').should('have.text', 'false');
|
||||
cy.get('#onDidDismiss').should('have.text', 'false');
|
||||
@@ -52,8 +53,8 @@ describe('Modals: Inline', () => {
|
||||
cy.get('ion-list ion-item').should('not.exist');
|
||||
});
|
||||
|
||||
it('should have items after 1500ms', () => {
|
||||
cy.wait(1500);
|
||||
it('should have items after opening', () => {
|
||||
cy.get('#open-modal').click();
|
||||
|
||||
cy.get('ion-list ion-item:nth-child(1)').should('have.text', 'A');
|
||||
cy.get('ion-list ion-item:nth-child(2)').should('have.text', 'B');
|
||||
@@ -61,7 +62,18 @@ describe('Modals: Inline', () => {
|
||||
cy.get('ion-list ion-item:nth-child(4)').should('have.text', 'D');
|
||||
});
|
||||
|
||||
it('should have a div with .ion-page', () => {
|
||||
it('should have a div with .ion-page after opening', () => {
|
||||
cy.get('#open-modal').click();
|
||||
|
||||
cy.get('ion-modal').children('.ion-page').should('exist');
|
||||
});
|
||||
|
||||
it('should remove .ion-page when closing the modal', () => {
|
||||
cy.get('#open-modal').click();
|
||||
|
||||
cy.get('ion-modal').children('.ion-page').should('exist');
|
||||
cy.get('ion-modal').trigger('click', 20, 20);
|
||||
|
||||
cy.get('ion-modal').children('.ion-page').should('not.exist');
|
||||
})
|
||||
});
|
||||
|
||||
@@ -20,6 +20,12 @@ describe('Nested Outlet', () => {
|
||||
cy.ionPageVisible('app-nested-outlet-page2');
|
||||
|
||||
cy.get('ion-router-outlet ion-router-outlet app-nested-outlet-page2 h1').should('have.text', 'Nested page 2');
|
||||
|
||||
cy.get('#goto-nested-page1').click();
|
||||
cy.ionPageVisible('app-nested-outlet-page');
|
||||
|
||||
cy.get('#goto-nested-page2').click();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,30 @@ describe('Tabs', () => {
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
});
|
||||
|
||||
describe('when navigating between tabs', () => {
|
||||
|
||||
it('should emit ionTabsWillChange before setting the selected tab', () => {
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', '');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '1');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'account');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
|
||||
cy.get('#ionTabsWillChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsWillChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsWillChangeSelectedTab').should('have.text', 'account');
|
||||
|
||||
cy.get('#ionTabsDidChangeCounter').should('have.text', '2');
|
||||
cy.get('#ionTabsDidChangeEvent').should('have.text', 'contact');
|
||||
cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'contact');
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
it('should simulate stack + double tab click', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
|
||||
159
angular/test/test-app/package-lock.json
generated
159
angular/test/test-app/package-lock.json
generated
@@ -18,8 +18,8 @@
|
||||
"@angular/platform-browser-dynamic": "^13.1.3",
|
||||
"@angular/platform-server": "^13.1.3",
|
||||
"@angular/router": "^13.1.3",
|
||||
"@ionic/angular": "^5.3.1",
|
||||
"@ionic/angular-server": "^5.3.1",
|
||||
"@ionic/angular": "^6.0.12",
|
||||
"@ionic/angular-server": "^6.0.12",
|
||||
"@nguniversal/express-engine": "^12.1.1",
|
||||
"angular-in-memory-web-api": "^0.11.0",
|
||||
"core-js": "^2.6.11",
|
||||
@@ -3242,53 +3242,44 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/angular": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.8.2.tgz",
|
||||
"integrity": "sha512-m2dE8QcWyCkv4yB+6+9j9Qu6hI28+chWT7A6nmbD7A360bm26LjdvKiJaCEQUxIXH7CpFuYcURZvRrn9gMscNg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.0.12.tgz",
|
||||
"integrity": "sha512-gWY92k6tPefj2OG1ogOALfqEQah78l1OGUwjs3rOg3pliWH4aV0dq3CzgmJJwIBSnvAdW+iSSOfEoMbEVV0b7A==",
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.8.2",
|
||||
"tslib": "^1.9.3"
|
||||
"@ionic/core": "^6.0.12",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": ">=8.2.7",
|
||||
"@angular/forms": ">=8.2.7",
|
||||
"@angular/router": ">=8.2.7",
|
||||
"rxjs": ">=6.2.0",
|
||||
"zone.js": ">=0.8.26"
|
||||
"@angular/core": ">=12.0.0",
|
||||
"@angular/forms": ">=12.0.0",
|
||||
"@angular/router": ">=12.0.0",
|
||||
"rxjs": ">=6.6.0",
|
||||
"zone.js": ">=0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/angular-server": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular-server/-/angular-server-5.8.2.tgz",
|
||||
"integrity": "sha512-N141OZOmJZ0N6ASQelBSZnEzxHG8zoaVjwIgT41Q7mlEDC19jD0TtzyTzk8Ac4X/dFulKF2u6H32JG75oSEdhg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular-server/-/angular-server-6.0.12.tgz",
|
||||
"integrity": "sha512-A5vqoePVfo0bxyTKHEtagJpu8OEFMOzv/zWG4FastDsWu1K3Vx/EBnwdoq9WPQ0upbMOAWf1R4SUuNny5ZSdJA==",
|
||||
"dependencies": {
|
||||
"tslib": "^1.9.0"
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": ">=8.2.7",
|
||||
"@angular/platform-server": ">=8.2.7",
|
||||
"@ionic/angular": "*",
|
||||
"rxjs": ">=6.2.0",
|
||||
"zone.js": ">=0.8.26"
|
||||
"@angular/core": ">=12.0.0",
|
||||
"@angular/platform-server": ">=12.0.0",
|
||||
"@ionic/angular": "^6.0.2",
|
||||
"rxjs": ">=6.6.0",
|
||||
"zone.js": ">=0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/angular-server/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@ionic/angular/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.8.2.tgz",
|
||||
"integrity": "sha512-BWIh6hyhq+tzVvebPMfRa+IhTV5/yXh4wO3a0U3Qo4PL4KDzWyBJfoiPi6bOEc+BAlFnCtOooTTXTumgXXOojg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.0.12.tgz",
|
||||
"integrity": "sha512-WpcVzSXBuvdHUZuNg1NTdnF4Ur7AGNCpgc4t4lyjPcEr0j//E855gd6GtUD7imNFERdtrvVC86JTpj5t0L+OMg==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
"@stencil/core": "^2.14.1",
|
||||
"ionicons": "^6.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -4320,9 +4311,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.8.1.tgz",
|
||||
"integrity": "sha512-iv9J6oLO/lv7/aO45M05yw3pp1J7olY400vlOZgdMVs3s5zHfalY1ZPYM0KyqU4+7DZuadKYbd0aQZ/g2PInZw==",
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.14.2.tgz",
|
||||
"integrity": "sha512-NMC5Xi8sPFJxaO4rz6CbMHuD6PteE/RJWtjrbkusmpjKRtMXkfZJPIgOrleZ4xO+vXcNyL535Ru7vUADqEsTiQ==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -11231,11 +11222,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ionicons": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.5.3.tgz",
|
||||
"integrity": "sha512-L71djrMi8pAad66tpwdnO1vwcyluCFvehzxU1PpH1k/HpYBZhZ5IaYhqXipmqUvu5aEbd4cbRguYyI5Fd4bxTw==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.1.tgz",
|
||||
"integrity": "sha512-xQekOJsxH82O7oB+3F60zeRggCdND9pJ/k0E6IJDVUGGlCj5mlyFqNgxUimytKgstPGv3S+3EmCxjefvtGgWUg==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.5.0"
|
||||
"@stencil/core": "~2.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ionicons/node_modules/@stencil/core": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.12.1.tgz",
|
||||
"integrity": "sha512-u24TZ+FEvjnZt5ZgIkLjLpUNsO6Ml3mUZqwmqk81w6RWWz75hgB5p4RFI5rvuErFeh2xvMIGo+pNdG24XUBz1A==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.10.0",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
@@ -11882,8 +11885,7 @@
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "3.0.1",
|
||||
@@ -20944,43 +20946,30 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/angular": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.8.2.tgz",
|
||||
"integrity": "sha512-m2dE8QcWyCkv4yB+6+9j9Qu6hI28+chWT7A6nmbD7A360bm26LjdvKiJaCEQUxIXH7CpFuYcURZvRrn9gMscNg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.0.12.tgz",
|
||||
"integrity": "sha512-gWY92k6tPefj2OG1ogOALfqEQah78l1OGUwjs3rOg3pliWH4aV0dq3CzgmJJwIBSnvAdW+iSSOfEoMbEVV0b7A==",
|
||||
"requires": {
|
||||
"@ionic/core": "5.8.2",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
"@ionic/core": "^6.0.12",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ionic/angular-server": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular-server/-/angular-server-5.8.2.tgz",
|
||||
"integrity": "sha512-N141OZOmJZ0N6ASQelBSZnEzxHG8zoaVjwIgT41Q7mlEDC19jD0TtzyTzk8Ac4X/dFulKF2u6H32JG75oSEdhg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/angular-server/-/angular-server-6.0.12.tgz",
|
||||
"integrity": "sha512-A5vqoePVfo0bxyTKHEtagJpu8OEFMOzv/zWG4FastDsWu1K3Vx/EBnwdoq9WPQ0upbMOAWf1R4SUuNny5ZSdJA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.8.2.tgz",
|
||||
"integrity": "sha512-BWIh6hyhq+tzVvebPMfRa+IhTV5/yXh4wO3a0U3Qo4PL4KDzWyBJfoiPi6bOEc+BAlFnCtOooTTXTumgXXOojg==",
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.0.12.tgz",
|
||||
"integrity": "sha512-WpcVzSXBuvdHUZuNg1NTdnF4Ur7AGNCpgc4t4lyjPcEr0j//E855gd6GtUD7imNFERdtrvVC86JTpj5t0L+OMg==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.4.0",
|
||||
"ionicons": "^5.5.3",
|
||||
"@stencil/core": "^2.14.1",
|
||||
"ionicons": "^6.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -21745,9 +21734,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.8.1.tgz",
|
||||
"integrity": "sha512-iv9J6oLO/lv7/aO45M05yw3pp1J7olY400vlOZgdMVs3s5zHfalY1ZPYM0KyqU4+7DZuadKYbd0aQZ/g2PInZw=="
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.14.2.tgz",
|
||||
"integrity": "sha512-NMC5Xi8sPFJxaO4rz6CbMHuD6PteE/RJWtjrbkusmpjKRtMXkfZJPIgOrleZ4xO+vXcNyL535Ru7vUADqEsTiQ=="
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
@@ -27057,11 +27046,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"ionicons": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-5.5.3.tgz",
|
||||
"integrity": "sha512-L71djrMi8pAad66tpwdnO1vwcyluCFvehzxU1PpH1k/HpYBZhZ5IaYhqXipmqUvu5aEbd4cbRguYyI5Fd4bxTw==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.1.tgz",
|
||||
"integrity": "sha512-xQekOJsxH82O7oB+3F60zeRggCdND9pJ/k0E6IJDVUGGlCj5mlyFqNgxUimytKgstPGv3S+3EmCxjefvtGgWUg==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.5.0"
|
||||
"@stencil/core": "~2.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stencil/core": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.12.1.tgz",
|
||||
"integrity": "sha512-u24TZ+FEvjnZt5ZgIkLjLpUNsO6Ml3mUZqwmqk81w6RWWz75hgB5p4RFI5rvuErFeh2xvMIGo+pNdG24XUBz1A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ip": {
|
||||
@@ -27555,8 +27551,7 @@
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "3.0.1",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"prerender": "ng run test-app:prerender",
|
||||
"cy.open": "cypress open",
|
||||
"cy.run": "cypress run",
|
||||
"test": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
|
||||
"test": "concurrently \"npm run start -- --configuration production\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
|
||||
"test.watch": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.open\" --kill-others --success first"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -29,8 +29,8 @@
|
||||
"@angular/platform-browser-dynamic": "^13.1.3",
|
||||
"@angular/platform-server": "^13.1.3",
|
||||
"@angular/router": "^13.1.3",
|
||||
"@ionic/angular": "^5.3.1",
|
||||
"@ionic/angular-server": "^5.3.1",
|
||||
"@ionic/angular": "^6.0.12",
|
||||
"@ionic/angular-server": "^6.0.12",
|
||||
"@nguniversal/express-engine": "^12.1.1",
|
||||
"angular-in-memory-web-api": "^0.11.0",
|
||||
"core-js": "^2.6.11",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<h1>Value</h1>
|
||||
<h2>{{value}}</h2>
|
||||
<h3>{{valueFromParams}}</h3>
|
||||
<p>modal is defined: <span id="modalInstance">{{ !!modal }}</span></p>
|
||||
<p>ngOnInit: <span id="ngOnInit">{{onInit}}</span></p>
|
||||
<p>ionViewWillEnter: <span id="ionViewWillEnter">{{willEnter}}</span></p>
|
||||
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
|
||||
|
||||
@@ -16,6 +16,8 @@ export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnte
|
||||
willLeave = 0;
|
||||
didLeave = 0;
|
||||
|
||||
modal: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private modalCtrl: ModalController,
|
||||
@Optional() public nav: IonNav,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<ion-modal [isOpen]="true" [breakpoints]="[0.1, 0.5, 1]" [initialBreakpoint]="0.5">
|
||||
<ion-button id="open-modal">Open Modal</ion-button>
|
||||
|
||||
<ion-modal [animated]="false" trigger="open-modal" [breakpoints]="[0.1, 0.5, 1]" [initialBreakpoint]="0.5">
|
||||
<ng-template>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nested-outlet-page',
|
||||
templateUrl: './nested-outlet-page.component.html',
|
||||
})
|
||||
export class NestedOutletPageComponent {
|
||||
export class NestedOutletPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
NgZone.assertInAngularZone();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
NgZone.assertInAngularZone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<h1>Nested page 2</h1>
|
||||
<p>
|
||||
<ion-button routerLink="/nested-outlet/page">Go To FIRST</ion-button>
|
||||
</p>
|
||||
</ion-content>
|
||||
<h1>Nested page 2</h1>
|
||||
<p>
|
||||
<ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go To FIRST</ion-button>
|
||||
</p>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nested-outlet-page2',
|
||||
templateUrl: './nested-outlet-page2.component.html',
|
||||
})
|
||||
export class NestedOutletPage2Component {
|
||||
export class NestedOutletPage2Component implements OnDestroy, OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
NgZone.assertInAngularZone();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
NgZone.assertInAngularZone();
|
||||
}
|
||||
}
|
||||
|
||||
5
angular/test/test-app/src/app/tabs/tabs.component.css
Normal file
5
angular/test/test-app/src/app/tabs/tabs.component.css
Normal file
@@ -0,0 +1,5 @@
|
||||
#test {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
left: 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-tabs (ionTabsDidChange)="tabChanged($event)">
|
||||
<ion-tabs (ionTabsDidChange)="tabChanged($event)" (ionTabsWillChange)="tabsWillChange($event)">
|
||||
<ion-tab-bar>
|
||||
<ion-tab-button tab="account">
|
||||
<ion-label>Tab One</ion-label>
|
||||
@@ -18,5 +18,29 @@
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
<ion-fab horizontal="end" vertical="top">
|
||||
<ion-fab-button id="tabs-state" style="width: 100px;">{{tabCounter}}.{{tabEvent}}</ion-fab-button>
|
||||
<ion-fab-button id="tabs-state" style="width: 100px;">{{tabsDidChangeCounter}}.{{tabsDidChangeEvent}}</ion-fab-button>
|
||||
</ion-fab>
|
||||
<div id="test">
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsWillChange counter: <span id="ionTabsWillChangeCounter">{{ tabsWillChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange event: <span id="ionTabsWillChangeEvent">{{ tabsWillChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsWillChange selectedTab: <span id="ionTabsWillChangeSelectedTab">{{ tabsWillChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
ionTabsDidChange counter: <span id="ionTabsDidChangeCounter">{{ tabsDidChangeCounter }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange event: <span id="ionTabsDidChangeEvent">{{ tabsDidChangeEvent }}</span>
|
||||
</li>
|
||||
<li>
|
||||
ionTabsDidChange selectedTab: <span id="ionTabsDidChangeSelectedTab">{{ tabsDidChangeSelectedTab }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { IonTabBar } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tabs',
|
||||
templateUrl: './tabs.component.html',
|
||||
styleUrls: ['./tabs.component.css']
|
||||
})
|
||||
export class TabsComponent {
|
||||
tabCounter = 0;
|
||||
tabEvent = '';
|
||||
tabsDidChangeCounter = 0;
|
||||
tabsDidChangeEvent = '';
|
||||
tabsDidChangeSelectedTab = '';
|
||||
|
||||
tabChanged(ev: {tab: string}) {
|
||||
this.tabCounter++;
|
||||
this.tabEvent = ev.tab;
|
||||
tabsWillChangeCounter = 0;
|
||||
tabsWillChangeEvent = '';
|
||||
tabsWillChangeSelectedTab = '';
|
||||
|
||||
@ViewChild(IonTabBar) tabBar: IonTabBar;
|
||||
|
||||
tabChanged(ev: { tab: string }) {
|
||||
console.log('ionTabsDidChange', this.tabBar.selectedTab);
|
||||
this.tabsDidChangeCounter++;
|
||||
this.tabsDidChangeEvent = ev.tab;
|
||||
this.tabsDidChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
|
||||
tabsWillChange(ev: { tab: string }) {
|
||||
console.log('ionTabsWillChange', this.tabBar.selectedTab);
|
||||
this.tabsWillChangeCounter++;
|
||||
this.tabsWillChangeEvent = ev.tab;
|
||||
this.tabsWillChangeSelectedTab = this.tabBar.selectedTab;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,81 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [6.0.13](https://github.com/ionic-team/ionic/compare/v6.0.12...v6.0.13) (2022-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** presentation time emits ionChange once ([#24968](https://github.com/ionic-team/ionic/issues/24968)) ([2909b08](https://github.com/ionic-team/ionic/commit/2909b080b7020299a4554c1459b4b190ff739085)), closes [#24967](https://github.com/ionic-team/ionic/issues/24967)
|
||||
* **popover:** dismissing nested popover via backdrop now works ([#24957](https://github.com/ionic-team/ionic/issues/24957)) ([9e84ef7](https://github.com/ionic-team/ionic/commit/9e84ef7f91d76ca5a1ecaffc7592287267d5368b)), closes [#24954](https://github.com/ionic-team/ionic/issues/24954)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.12](https://github.com/ionic-team/ionic/compare/v6.0.11...v6.0.12) (2022-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** reinit behavior on presentation change ([#24828](https://github.com/ionic-team/ionic/issues/24828)) ([d46e1e8](https://github.com/ionic-team/ionic/commit/d46e1e8506ca5095817b421e9edb37d41451e885))
|
||||
* **toast:** screen readers now announce toasts when presented ([#24937](https://github.com/ionic-team/ionic/issues/24937)) ([8a97f6b](https://github.com/ionic-team/ionic/commit/8a97f6b5c9ca1e77c1790abd1e924955b6b6ea27)), closes [#22333](https://github.com/ionic-team/ionic/issues/22333)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.11](https://github.com/ionic-team/ionic/compare/v6.0.10...v6.0.11) (2022-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** time picker now scrolls to correct value ([#24879](https://github.com/ionic-team/ionic/issues/24879)) ([331ce6d](https://github.com/ionic-team/ionic/commit/331ce6d6769900e2aec9e30d35b52cfd40cbb889)), closes [#24878](https://github.com/ionic-team/ionic/issues/24878)
|
||||
* **ios:** swipe to go back now works in rtl mode ([#24866](https://github.com/ionic-team/ionic/issues/24866)) ([2ac9105](https://github.com/ionic-team/ionic/commit/2ac9105796a0765fabc48592b5b44ac58c568579)), closes [#19488](https://github.com/ionic-team/ionic/issues/19488)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* improve treeshaking functionality ([#24895](https://github.com/ionic-team/ionic/issues/24895)) ([805907a](https://github.com/ionic-team/ionic/commit/805907af4e78179f1acc9cb02263b1ea10d4e3df)), closes [#24280](https://github.com/ionic-team/ionic/issues/24280)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.10](https://github.com/ionic-team/ionic/compare/v6.0.9...v6.0.10) (2022-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** confirm method now uses selected date ([#24827](https://github.com/ionic-team/ionic/issues/24827)) ([c35b898](https://github.com/ionic-team/ionic/commit/c35b898f1dc0fb706446b6971982df48fd72fe6d)), closes [#24823](https://github.com/ionic-team/ionic/issues/24823)
|
||||
* **datetime:** persist minutes column on hour change ([#24829](https://github.com/ionic-team/ionic/issues/24829)) ([aacb58a](https://github.com/ionic-team/ionic/commit/aacb58a3224e3cc51c731d0c9aa52f52c9276692)), closes [#24821](https://github.com/ionic-team/ionic/issues/24821)
|
||||
* **item-sliding:** close() will maintain disabled state ([#24847](https://github.com/ionic-team/ionic/issues/24847)) ([ea4a9bb](https://github.com/ionic-team/ionic/commit/ea4a9bb69465f8e97746b36638f0b3a26e45da18)), closes [#24747](https://github.com/ionic-team/ionic/issues/24747)
|
||||
* **modal:** re-enable swipe gestures when modal is dismissed ([#24846](https://github.com/ionic-team/ionic/issues/24846)) ([836c01c](https://github.com/ionic-team/ionic/commit/836c01c73e42caab0101ac4988f0a9b27cf96a5b)), closes [#24817](https://github.com/ionic-team/ionic/issues/24817)
|
||||
* **modal:** sheet modal now allows input focusing when backdrop disabled ([#24840](https://github.com/ionic-team/ionic/issues/24840)) ([e4ec572](https://github.com/ionic-team/ionic/commit/e4ec572043e22bd2626dbcfd204fc22a7335282c)), closes [#24581](https://github.com/ionic-team/ionic/issues/24581)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.9](https://github.com/ionic-team/ionic/compare/v6.0.8...v6.0.9) (2022-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** improve datetime sizing in modals ([#24762](https://github.com/ionic-team/ionic/issues/24762)) ([b0ac7de](https://github.com/ionic-team/ionic/commit/b0ac7de168c353ba4899cb74a2b38e25fd0cc0f1)), closes [#23992](https://github.com/ionic-team/ionic/issues/23992)
|
||||
* **datetime:** month and year column order is now locale aware ([#24802](https://github.com/ionic-team/ionic/issues/24802)) ([16647b2](https://github.com/ionic-team/ionic/commit/16647b2c7290389755a4093145788f281c84b7e2)), closes [#24548](https://github.com/ionic-team/ionic/issues/24548)
|
||||
* **datetime:** month picker no longer gives duplicate months on ios 14 and older ([#24792](https://github.com/ionic-team/ionic/issues/24792)) ([b6d7e1c](https://github.com/ionic-team/ionic/commit/b6d7e1c75740a61dcd02c21692e4d4632fb8df5c)), closes [#24663](https://github.com/ionic-team/ionic/issues/24663)
|
||||
* **img:** draggable attribute is now inherited to inner img element ([#24781](https://github.com/ionic-team/ionic/issues/24781)) ([19ac238](https://github.com/ionic-team/ionic/commit/19ac2389eb0843173f51a12de41ac808cd8f0569)), closes [#21325](https://github.com/ionic-team/ionic/issues/21325)
|
||||
* **modal:** backdropBreakpoint allows interactivity behind sheet ([#24798](https://github.com/ionic-team/ionic/issues/24798)) ([fca3f56](https://github.com/ionic-team/ionic/commit/fca3f56ca4568e63fd493debda088263caa86c64)), closes [#24797](https://github.com/ionic-team/ionic/issues/24797)
|
||||
* **popover:** default alignment to 'center' for ios mode ([#24815](https://github.com/ionic-team/ionic/issues/24815)) ([243f673](https://github.com/ionic-team/ionic/commit/243f67362f25699bdb373be6b72cb9c14dc95a32))
|
||||
* **react, vue:** scroll is no longer interrupted on ios ([#24791](https://github.com/ionic-team/ionic/issues/24791)) ([99c91ef](https://github.com/ionic-team/ionic/commit/99c91eff8764c9a1630adedab6a9765dd9754f7d)), closes [#24435](https://github.com/ionic-team/ionic/issues/24435)
|
||||
* **select:** interface components now show correctly ([#24810](https://github.com/ionic-team/ionic/issues/24810)) ([2fc2de5](https://github.com/ionic-team/ionic/commit/2fc2de51771f4a5c3f20c6071284096e5bf31ec8)), closes [#24807](https://github.com/ionic-team/ionic/issues/24807)
|
||||
* **toast:** toast is now correctly excluded from focus trapping ([#24816](https://github.com/ionic-team/ionic/issues/24816)) ([8246112](https://github.com/ionic-team/ionic/commit/8246112ca12f90edb98629ab82e27a792a1fafad)), closes [#24733](https://github.com/ionic-team/ionic/issues/24733)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [6.0.8](https://github.com/ionic-team/ionic/compare/v6.0.7...v6.0.8) (2022-02-15)
|
||||
|
||||
|
||||
|
||||
@@ -877,7 +877,7 @@ ion-picker,css-prop,--min-width
|
||||
ion-picker,css-prop,--width
|
||||
|
||||
ion-popover,shadow
|
||||
ion-popover,prop,alignment,"center" | "end" | "start",'start',false,false
|
||||
ion-popover,prop,alignment,"center" | "end" | "start" | undefined,undefined,false,false
|
||||
ion-popover,prop,animated,boolean,true,false,false
|
||||
ion-popover,prop,arrow,boolean,true,false,false
|
||||
ion-popover,prop,backdropDismiss,boolean,true,false,false
|
||||
|
||||
18
core/package-lock.json
generated
18
core/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.0.8",
|
||||
"version": "6.0.13",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "6.0.5",
|
||||
"version": "6.0.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "~2.13.0",
|
||||
"@stencil/core": "^2.14.2",
|
||||
"ionicons": "^6.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -1366,9 +1366,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.13.0.tgz",
|
||||
"integrity": "sha512-EEKHOHgYpg3/iFUKMXTZJjUayRul7sXDwNw0OGgkEOe4t7JWiibDkzUHuruvpbqEydX+z1+ez5K2bMMY76c2wA==",
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.14.2.tgz",
|
||||
"integrity": "sha512-NMC5Xi8sPFJxaO4rz6CbMHuD6PteE/RJWtjrbkusmpjKRtMXkfZJPIgOrleZ4xO+vXcNyL535Ru7vUADqEsTiQ==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -15055,9 +15055,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.13.0.tgz",
|
||||
"integrity": "sha512-EEKHOHgYpg3/iFUKMXTZJjUayRul7sXDwNw0OGgkEOe4t7JWiibDkzUHuruvpbqEydX+z1+ez5K2bMMY76c2wA=="
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.14.2.tgz",
|
||||
"integrity": "sha512-NMC5Xi8sPFJxaO4rz6CbMHuD6PteE/RJWtjrbkusmpjKRtMXkfZJPIgOrleZ4xO+vXcNyL535Ru7vUADqEsTiQ=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.0.8",
|
||||
"version": "6.0.13",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,7 +31,7 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "~2.13.0",
|
||||
"@stencil/core": "^2.14.2",
|
||||
"ionicons": "^6.0.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
|
||||
21
core/src/components.d.ts
vendored
21
core/src/components.d.ts
vendored
@@ -1254,11 +1254,11 @@ export namespace Components {
|
||||
}
|
||||
interface IonItemSliding {
|
||||
/**
|
||||
* Close the sliding item. Items can also be closed from the [List](../list).
|
||||
* Close the sliding item. Items can also be closed from the [List](./list).
|
||||
*/
|
||||
"close": () => Promise<void>;
|
||||
/**
|
||||
* Close all of the sliding items in the list. Items can also be closed from the [List](../list).
|
||||
* Close all of the sliding items in the list. Items can also be closed from the [List](./list).
|
||||
*/
|
||||
"closeOpened": () => Promise<boolean>;
|
||||
/**
|
||||
@@ -1848,9 +1848,9 @@ export namespace Components {
|
||||
}
|
||||
interface IonPopover {
|
||||
/**
|
||||
* Describes how to align the popover content with the `reference` point.
|
||||
* Describes how to align the popover content with the `reference` point. Defaults to `'center'` for `ios` mode, and `'start'` for `md` mode.
|
||||
*/
|
||||
"alignment": PositionAlign;
|
||||
"alignment"?: PositionAlign;
|
||||
/**
|
||||
* If `true`, the popover will animate.
|
||||
*/
|
||||
@@ -2425,7 +2425,7 @@ export namespace Components {
|
||||
*/
|
||||
"interface": SelectInterface;
|
||||
/**
|
||||
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](../alert), the [ion-action-sheet docs](../action-sheet) and the [ion-popover docs](../popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
|
||||
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
|
||||
*/
|
||||
"interfaceOptions": any;
|
||||
/**
|
||||
@@ -2512,7 +2512,7 @@ export namespace Components {
|
||||
*/
|
||||
"getPreviousIndex": () => Promise<number>;
|
||||
/**
|
||||
* Get the Swiper instance. Use this to access the full Swiper API. See https://idangero.us/swiper/api/ for all API options.
|
||||
* Get the Swiper instance. Use this to access the full Swiper API. See https://swiperjs.com/swiper-api for all API options.
|
||||
*/
|
||||
"getSwiper": () => Promise<any>;
|
||||
/**
|
||||
@@ -2547,7 +2547,7 @@ export namespace Components {
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Options to pass to the swiper instance. See http://idangero.us/swiper/api/ for valid options
|
||||
* Options to pass to the swiper instance. See https://swiperjs.com/swiper-api for valid options
|
||||
*/
|
||||
"options": any;
|
||||
/**
|
||||
@@ -5485,7 +5485,7 @@ declare namespace LocalJSX {
|
||||
}
|
||||
interface IonPopover {
|
||||
/**
|
||||
* Describes how to align the popover content with the `reference` point.
|
||||
* Describes how to align the popover content with the `reference` point. Defaults to `'center'` for `ios` mode, and `'start'` for `md` mode.
|
||||
*/
|
||||
"alignment"?: PositionAlign;
|
||||
/**
|
||||
@@ -6128,7 +6128,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"interface"?: SelectInterface;
|
||||
/**
|
||||
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](../alert), the [ion-action-sheet docs](../action-sheet) and the [ion-popover docs](../popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
|
||||
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
|
||||
*/
|
||||
"interfaceOptions"?: any;
|
||||
/**
|
||||
@@ -6290,7 +6290,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onIonSlidesDidLoad"?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
* Options to pass to the swiper instance. See http://idangero.us/swiper/api/ for valid options
|
||||
* Options to pass to the swiper instance. See https://swiperjs.com/swiper-api for valid options
|
||||
*/
|
||||
"options"?: any;
|
||||
/**
|
||||
@@ -6359,6 +6359,7 @@ declare namespace LocalJSX {
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
"onIonStyle"?: (event: CustomEvent<StyleEventDetail>) => void;
|
||||
"onIonTabBarChanged"?: (event: CustomEvent<TabBarChangedEventDetail>) => void;
|
||||
/**
|
||||
* The selected tab component
|
||||
|
||||
@@ -661,6 +661,10 @@ Type: `Promise<void>`
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Used by
|
||||
|
||||
- [ion-select](../select)
|
||||
|
||||
### Depends on
|
||||
|
||||
- [ion-backdrop](../backdrop)
|
||||
@@ -673,6 +677,7 @@ graph TD;
|
||||
ion-action-sheet --> ion-backdrop
|
||||
ion-action-sheet --> ion-icon
|
||||
ion-action-sheet --> ion-ripple-effect
|
||||
ion-select --> ion-action-sheet
|
||||
style ion-action-sheet fill:#f9f,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testActionSheet = async (
|
||||
type: string,
|
||||
|
||||
@@ -1863,6 +1863,10 @@ Type: `Promise<void>`
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Used by
|
||||
|
||||
- [ion-select](../select)
|
||||
|
||||
### Depends on
|
||||
|
||||
- [ion-ripple-effect](../ripple-effect)
|
||||
@@ -1873,6 +1877,7 @@ Type: `Promise<void>`
|
||||
graph TD;
|
||||
ion-alert --> ion-ripple-effect
|
||||
ion-alert --> ion-backdrop
|
||||
ion-select --> ion-alert
|
||||
style ion-alert fill:#f9f,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testAlert = async (
|
||||
type: string,
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 { Attributes, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,7 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
shadow: true
|
||||
})
|
||||
export class BackButton implements ComponentInterface, ButtonInterface {
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { chevronForwardOutline, ellipsisHorizontal } from 'ionicons/icons';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AnimationBuilder, BreadcrumbCollapsedClickEventDetail, Color, RouterDirection } from '../../interface';
|
||||
import { inheritAttributes } from '../../utils/helpers';
|
||||
import { Attributes, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
shadow: true
|
||||
})
|
||||
export class Breadcrumb implements ComponentInterface {
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
private collapsedRef?: HTMLElement;
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop
|
||||
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 { Attributes, hasShadowDom, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
private inItem = false;
|
||||
private inListHeader = false;
|
||||
private inToolbar = false;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This attribute lets you specify how wide the button should be. By default, butto
|
||||
|
||||
## Fill
|
||||
|
||||
This attributes determines the background and border color of the button. By default, buttons have a solid background unless the button is inside of a toolbar, in which case it has a transparent background.
|
||||
This attribute determines the background and border color of the button. By default, buttons have a solid background unless the button is inside of a toolbar, in which case it has a transparent background.
|
||||
|
||||
| Value | Details |
|
||||
|----------------|------------------------------------------------------------------------------|
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
*/
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
|
||||
margin-top: calc(var(--offset-top) * -1);
|
||||
margin-bottom: calc(var(--offset-bottom) * -1);
|
||||
}
|
||||
@@ -173,10 +173,6 @@
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
left: -100%;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
@@ -185,6 +181,18 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host(.content-ltr) .transition-effect {
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
left: -100%;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
}
|
||||
|
||||
:host(.content-rtl) .transition-effect {
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
right: -100%;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
}
|
||||
|
||||
.transition-cover {
|
||||
position: absolute;
|
||||
|
||||
@@ -204,10 +212,6 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
right: 0;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
|
||||
@@ -216,6 +220,20 @@
|
||||
background-size: 10px 16px;
|
||||
}
|
||||
|
||||
:host(.content-ltr) .transition-shadow {
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
right: 0;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
}
|
||||
|
||||
:host(.content-rtl) .transition-shadow {
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
left: 0;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
// Content: Fixed
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, ScrollBaseDetail, ScrollDetail } from '../../interface';
|
||||
import { componentOnReady } from '../../utils/helpers';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
import { isRTL } from '../../utils/rtl';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -311,7 +312,8 @@ export class Content implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isMainContent, scrollX, scrollY } = this;
|
||||
const { isMainContent, scrollX, scrollY, el } = this;
|
||||
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
||||
const mode = getIonMode(this);
|
||||
const forceOverscroll = this.shouldForceOverscroll();
|
||||
const transitionShadow = mode === 'ios';
|
||||
@@ -325,6 +327,7 @@ export class Content implements ComponentInterface {
|
||||
[mode]: true,
|
||||
'content-sizing': hostContext('ion-popover', this.el),
|
||||
'overscroll': forceOverscroll,
|
||||
[`content-${rtl}`]: true
|
||||
})}
|
||||
style={{
|
||||
'--offset-top': `${this.cTop}px`,
|
||||
@@ -339,7 +342,7 @@ export class Content implements ComponentInterface {
|
||||
'scroll-y': scrollY,
|
||||
'overscroll': (scrollX || scrollY) && forceOverscroll
|
||||
}}
|
||||
ref={(el: HTMLElement) => this.scrollEl = el!}
|
||||
ref={(scrollEl: HTMLElement) => this.scrollEl = scrollEl!}
|
||||
onScroll={(this.scrollEvents) ? (ev: UIEvent) => this.onScroll(ev) : undefined}
|
||||
part="scroll"
|
||||
>
|
||||
|
||||
@@ -48,6 +48,35 @@
|
||||
|
||||
opacity: 1;
|
||||
}
|
||||
/**
|
||||
* Changing the physical order of the
|
||||
* picker columns in the DOM is added
|
||||
* work, so we just use `order` instead.
|
||||
*
|
||||
* The picker automatically configures
|
||||
* the text alignment, so when switching
|
||||
* the order we need to manually switch
|
||||
* the text alignment too.
|
||||
*/
|
||||
:host .datetime-year .order-month-first .month-column {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
:host .datetime-year .order-month-first .year-column {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
:host .datetime-year .order-year-first .month-column {
|
||||
order: 2;
|
||||
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
:host .datetime-year .order-year-first .year-column {
|
||||
order: 1;
|
||||
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
// Calendar
|
||||
// -----------------------------------
|
||||
|
||||
@@ -32,7 +32,8 @@ import {
|
||||
getMonthAndYear
|
||||
} from './utils/format';
|
||||
import {
|
||||
is24Hour
|
||||
is24Hour,
|
||||
isMonthFirstLocale
|
||||
} from './utils/helpers';
|
||||
import {
|
||||
calculateHourFromAMPM,
|
||||
@@ -93,9 +94,13 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private destroyCalendarIO?: () => void;
|
||||
private destroyKeyboardMO?: () => void;
|
||||
private destroyOverlayListener?: () => void;
|
||||
|
||||
private minParts?: any;
|
||||
private maxParts?: any;
|
||||
private todayParts = parseDate(getToday());
|
||||
|
||||
private prevPresentation: string | null = null;
|
||||
|
||||
/**
|
||||
* Duplicate reference to `activeParts` that does not trigger a re-render of the component.
|
||||
@@ -123,8 +128,6 @@ export class Datetime implements ComponentInterface {
|
||||
ampm: 'pm'
|
||||
}
|
||||
|
||||
private todayParts = parseDate(getToday())
|
||||
|
||||
@Element() el!: HTMLIonDatetimeElement;
|
||||
|
||||
@State() isPresented = false;
|
||||
@@ -423,10 +426,10 @@ export class Datetime implements ComponentInterface {
|
||||
* the date that is currently selected, otherwise
|
||||
* there can be 1 hr difference when dealing w/ DST
|
||||
*/
|
||||
const date = new Date(convertDataToISO(this.workingParts));
|
||||
this.workingParts.tzOffset = date.getTimezoneOffset() * -1;
|
||||
const date = new Date(convertDataToISO(this.activeParts));
|
||||
this.activeParts.tzOffset = date.getTimezoneOffset() * -1;
|
||||
|
||||
this.value = convertDataToISO(this.workingParts);
|
||||
this.value = convertDataToISO(this.activeParts);
|
||||
|
||||
if (closeOverlay) {
|
||||
this.closeParentOverlay();
|
||||
@@ -482,8 +485,18 @@ export class Datetime implements ComponentInterface {
|
||||
this.confirm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stencil sometimes sets calendarBodyRef to null on rerender, even though
|
||||
* the element is present. Query for it manually as a fallback.
|
||||
*
|
||||
* TODO(FW-901) Remove when issue is resolved: https://github.com/ionic-team/stencil/issues/3253
|
||||
*/
|
||||
private getCalendarBodyEl = () => {
|
||||
return this.calendarBodyRef || this.el.shadowRoot?.querySelector('.calendar-body');
|
||||
};
|
||||
|
||||
private initializeKeyboardListeners = () => {
|
||||
const { calendarBodyRef } = this;
|
||||
const calendarBodyRef = this.getCalendarBodyEl();
|
||||
if (!calendarBodyRef) { return; }
|
||||
|
||||
const root = this.el!.shadowRoot!;
|
||||
@@ -529,7 +542,7 @@ export class Datetime implements ComponentInterface {
|
||||
* We must use keydown not keyup as we want
|
||||
* to prevent scrolling when using the arrow keys.
|
||||
*/
|
||||
this.calendarBodyRef!.addEventListener('keydown', (ev: KeyboardEvent) => {
|
||||
calendarBodyRef.addEventListener('keydown', (ev: KeyboardEvent) => {
|
||||
const activeElement = root.activeElement;
|
||||
if (!activeElement || !activeElement.classList.contains('calendar-day')) { return; }
|
||||
|
||||
@@ -656,7 +669,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private initializeCalendarIOListeners = () => {
|
||||
const { calendarBodyRef } = this;
|
||||
const calendarBodyRef = this.getCalendarBodyEl();
|
||||
if (!calendarBodyRef) { return; }
|
||||
|
||||
const mode = getIonMode(this);
|
||||
@@ -810,6 +823,12 @@ export class Datetime implements ComponentInterface {
|
||||
navigator.maxTouchPoints > 1 ?
|
||||
[0.7, 1] : 1;
|
||||
|
||||
// Intersection observers cannot accurately detect the
|
||||
// intersection with a threshold of 1, when the observed
|
||||
// element width is a sub-pixel value (i.e. 334.05px).
|
||||
// Setting a root margin to 1px solves the issue.
|
||||
const rootMargin = '1px';
|
||||
|
||||
/**
|
||||
* Listen on the first month to
|
||||
* prepend a new month and on the last
|
||||
@@ -828,15 +847,18 @@ export class Datetime implements ComponentInterface {
|
||||
* it applies to active gestures which is not
|
||||
* something WebKit does.
|
||||
*/
|
||||
|
||||
endIO = new IntersectionObserver(ev => ioCallback('end', ev), {
|
||||
threshold,
|
||||
root: calendarBodyRef
|
||||
root: calendarBodyRef,
|
||||
rootMargin
|
||||
});
|
||||
endIO.observe(endMonth);
|
||||
|
||||
startIO = new IntersectionObserver(ev => ioCallback('start', ev), {
|
||||
threshold,
|
||||
root: calendarBodyRef
|
||||
root: calendarBodyRef,
|
||||
rootMargin
|
||||
});
|
||||
startIO.observe(startMonth);
|
||||
|
||||
@@ -863,7 +885,7 @@ export class Datetime implements ComponentInterface {
|
||||
* listener. This is so that we can re-create the listeners
|
||||
* if the datetime has been hidden/presented by a modal or popover.
|
||||
*/
|
||||
private destroyListeners = () => {
|
||||
private destroyInteractionListeners = () => {
|
||||
const { destroyCalendarIO, destroyKeyboardMO } = this;
|
||||
|
||||
if (destroyCalendarIO !== undefined) {
|
||||
@@ -875,6 +897,12 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private initializeListeners() {
|
||||
this.initializeCalendarIOListeners();
|
||||
this.initializeKeyboardListeners();
|
||||
this.initializeOverlayListener();
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
/**
|
||||
* If a scrollable element is hidden using `display: none`,
|
||||
@@ -888,9 +916,7 @@ export class Datetime implements ComponentInterface {
|
||||
const ev = entries[0];
|
||||
if (!ev.isIntersecting) { return; }
|
||||
|
||||
this.initializeCalendarIOListeners();
|
||||
this.initializeKeyboardListeners();
|
||||
this.initializeOverlayListener();
|
||||
this.initializeListeners();
|
||||
|
||||
/**
|
||||
* TODO: Datetime needs a frame to ensure that it
|
||||
@@ -926,7 +952,7 @@ export class Datetime implements ComponentInterface {
|
||||
const ev = entries[0];
|
||||
if (ev.isIntersecting) { return; }
|
||||
|
||||
this.destroyListeners();
|
||||
this.destroyInteractionListeners();
|
||||
|
||||
writeTask(() => {
|
||||
this.el.classList.remove('datetime-ready');
|
||||
@@ -949,6 +975,29 @@ export class Datetime implements ComponentInterface {
|
||||
root.addEventListener('ionBlur', (ev: Event) => ev.stopPropagation());
|
||||
}
|
||||
|
||||
/**
|
||||
* When the presentation is changed, all calendar content is recreated,
|
||||
* so we need to re-init behavior with the new elements.
|
||||
*/
|
||||
componentDidRender() {
|
||||
const { presentation, prevPresentation } = this;
|
||||
|
||||
if (prevPresentation === null) {
|
||||
this.prevPresentation = presentation;
|
||||
return;
|
||||
}
|
||||
|
||||
if (presentation === prevPresentation) { return; }
|
||||
this.prevPresentation = presentation;
|
||||
|
||||
this.destroyInteractionListeners();
|
||||
if (this.destroyOverlayListener !== undefined) {
|
||||
this.destroyOverlayListener();
|
||||
}
|
||||
|
||||
this.initializeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* When doing subsequent presentations of an inline
|
||||
* overlay, the IO callback will fire again causing
|
||||
@@ -960,9 +1009,15 @@ export class Datetime implements ComponentInterface {
|
||||
const overlay = this.el.closest('ion-popover, ion-modal');
|
||||
if (overlay === null) { return; }
|
||||
|
||||
overlay.addEventListener('willPresent', () => {
|
||||
const overlayListener = () => {
|
||||
this.overlayIsPresenting = true;
|
||||
});
|
||||
};
|
||||
|
||||
overlay.addEventListener('willPresent', overlayListener);
|
||||
|
||||
this.destroyOverlayListener = () => {
|
||||
overlay.removeEventListener('willPresent', overlayListener);
|
||||
};
|
||||
}
|
||||
|
||||
private processValue = (value?: string | null) => {
|
||||
@@ -1024,7 +1079,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private nextMonth = () => {
|
||||
const { calendarBodyRef } = this;
|
||||
const calendarBodyRef = this.getCalendarBodyEl();
|
||||
if (!calendarBodyRef) { return; }
|
||||
|
||||
const nextMonth = calendarBodyRef.querySelector('.calendar-month:last-of-type');
|
||||
@@ -1040,7 +1095,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private prevMonth = () => {
|
||||
const { calendarBodyRef } = this;
|
||||
const calendarBodyRef = this.getCalendarBodyEl();
|
||||
if (!calendarBodyRef) { return; }
|
||||
|
||||
const prevMonth = calendarBodyRef.querySelector('.calendar-month:first-of-type');
|
||||
@@ -1097,25 +1152,31 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private renderYearView() {
|
||||
const { presentation, workingParts } = this;
|
||||
const { presentation, workingParts, locale } = this;
|
||||
const calendarYears = getCalendarYears(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues);
|
||||
const showMonth = presentation !== 'year';
|
||||
const showYear = presentation !== 'month';
|
||||
|
||||
const months = getPickerMonths(this.locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues);
|
||||
const months = getPickerMonths(locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues);
|
||||
const years = calendarYears.map(year => {
|
||||
return {
|
||||
text: `${year}`,
|
||||
value: year
|
||||
}
|
||||
})
|
||||
const showMonthFirst = isMonthFirstLocale(locale);
|
||||
const columnOrder = showMonthFirst ? 'month-first' : 'year-first';
|
||||
return (
|
||||
<div class="datetime-year">
|
||||
<div class="datetime-year-body">
|
||||
<div class={{
|
||||
'datetime-year-body': true,
|
||||
[`order-${columnOrder}`]: true
|
||||
}}>
|
||||
<ion-picker-internal>
|
||||
{
|
||||
showMonth &&
|
||||
<ion-picker-column-internal
|
||||
class="month-column"
|
||||
color={this.color}
|
||||
items={months}
|
||||
value={workingParts.month}
|
||||
@@ -1150,6 +1211,7 @@ export class Datetime implements ComponentInterface {
|
||||
{
|
||||
showYear &&
|
||||
<ion-picker-column-internal
|
||||
class="year-column"
|
||||
color={this.color}
|
||||
items={years}
|
||||
value={workingParts.year}
|
||||
|
||||
@@ -31,8 +31,8 @@ the same ISO format which datetime value was originally given as.
|
||||
| Year and Month | YYYY-MM | 1994-12 |
|
||||
| Complete Date | YYYY-MM-DD | 1994-12-15 |
|
||||
| Date and Time | YYYY-MM-DDTHH:mm | 1994-12-15T13:47 |
|
||||
| UTC Timezone | YYYY-MM-DDTHH:mm:ssTZD | 1994-12-15T13:47:20.789Z |
|
||||
| Timezone Offset | YYYY-MM-DDTHH:mm:ssTZD | 1994-12-15T13:47:20.789+05:00 |
|
||||
| UTC Timezone | YYYY-MM-DDTHH:mm:ssZ | 1994-12-15T13:47:20Z |
|
||||
| Timezone Offset | YYYY-MM-DDTHH:mm:ssTZD | 1994-12-15T13:47:20+05:00 |
|
||||
| Hour and Minute | HH:mm | 13:47 |
|
||||
| Hour, Minute, Second | HH:mm:ss | 13:47:20 |
|
||||
|
||||
@@ -147,22 +147,6 @@ const zonedTime = dateFnsTz.utcToZonedTime(date, userTimeZone);
|
||||
format(zonedTime, 'yyyy-MM-dd HH:mm:ssXXX', { timeZone: userTimeZone });
|
||||
```
|
||||
|
||||
## Timezones
|
||||
|
||||
### Assigning Date Values
|
||||
|
||||
`ion-datetime` does not manipulate or read timezones. Developers will need to pass in a valid ISO-8601 string that is already configured for the user's timezone when assigning a value. If no value is provided, `ion-datetime` will default to the time specified on the user's machine (which will already be in the user's timezone). We recommend using [date-fns](https://date-fns.org) to format the date to ISO-8601.
|
||||
|
||||
```typescript
|
||||
import { formatISO } from 'date-fns';
|
||||
|
||||
const dateString = '2021-01-14T15:00:00.000Z';
|
||||
const formattedDateValue = formatISO(new Date(dateString));
|
||||
|
||||
// Assign `formattedDateValue` to your `ion-datetime` value.
|
||||
|
||||
```
|
||||
|
||||
### Parsing Date Values
|
||||
|
||||
The `ionChange` event will emit the date value as an ISO-8601 string in the event payload. It is the developer's responsibility to format it based on their application needs. We recommend using [date-fns](https://date-fns.org) to format the date value.
|
||||
|
||||
@@ -110,3 +110,41 @@ test('datetime:rtl: basic', async () => {
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
|
||||
describe('datetime: confirm date', () => {
|
||||
|
||||
test('should set the date value based on the selected date', async () => {
|
||||
|
||||
const page = await newE2EPage({
|
||||
html: `
|
||||
<button>Bind datetimeMonthDidChange event</button>
|
||||
<ion-datetime value="2021-12-25T12:40:00.000Z"></ion-datetime>
|
||||
<script type="module">
|
||||
import { InitMonthDidChangeEvent } from '/src/components/datetime/test/utils/month-did-change-event.js';
|
||||
document.querySelector('button').addEventListener('click', function() {
|
||||
InitMonthDidChangeEvent();
|
||||
});
|
||||
</script>
|
||||
`
|
||||
});
|
||||
|
||||
const eventButton = await page.find('button');
|
||||
await eventButton.click();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button');
|
||||
|
||||
await buttons[1].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
await page.$eval('ion-datetime', async (el: any) => {
|
||||
await el.confirm();
|
||||
});
|
||||
|
||||
const value = await (await page.find('ion-datetime')).getProperty('value');
|
||||
|
||||
expect(value).toMatch('2021-12-25T12:40:00');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
generateMonths,
|
||||
getDaysOfWeek,
|
||||
generateTime
|
||||
generateTime,
|
||||
getToday
|
||||
} from '../utils/data';
|
||||
|
||||
describe('generateMonths()', () => {
|
||||
@@ -334,3 +335,22 @@ describe('generateTime()', () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('getToday', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers('modern');
|
||||
// System time is zero based, 1 = February
|
||||
jest.setSystemTime(new Date(2022, 1, 21));
|
||||
});
|
||||
|
||||
it('should return today', () => {
|
||||
const res = getToday();
|
||||
|
||||
const expected = new Date();
|
||||
expected.setHours(expected.getHours() - (expected.getTimezoneOffset() / 60));
|
||||
|
||||
expect(res).toEqual('2022-02-21T00:00:00.000Z');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -52,3 +52,41 @@ test('display', async () => {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
test('month selection should work after changing presentation', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/display?ionic:_testing=true'
|
||||
});
|
||||
const ionWorkingPartsDidChange = await page.spyOnEvent('ionWorkingPartsDidChange', 'document');
|
||||
let calendarMonthYear;
|
||||
|
||||
await page.select('#presentation', 'date-time');
|
||||
await page.waitForChanges();
|
||||
|
||||
await page.select('#presentation', 'time-date');
|
||||
await page.waitForChanges();
|
||||
|
||||
const nextMonthButton = await page.find('ion-datetime >>> .calendar-next-prev ion-button + ion-button');
|
||||
await nextMonthButton.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
await ionWorkingPartsDidChange.next();
|
||||
|
||||
calendarMonthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(calendarMonthYear.textContent).toContain('March 2022');
|
||||
|
||||
// ensure it still works if presentation is changed more than once
|
||||
await page.select('#presentation', 'date-time');
|
||||
await page.waitForChanges();
|
||||
|
||||
const prevMonthButton = await page.find('ion-datetime >>> .calendar-next-prev ion-button:first-child');
|
||||
await prevMonthButton.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
await ionWorkingPartsDidChange.next();
|
||||
|
||||
calendarMonthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(calendarMonthYear.textContent).toContain('February 2022');
|
||||
});
|
||||
|
||||
@@ -1,52 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Datetime - Standalone</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
body {
|
||||
padding: 20px;
|
||||
}
|
||||
ion-datetime {
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<label for="presentation">Presentation</label>
|
||||
<select id="presentation" onchange="changePresentation(event)">
|
||||
<option value="date-time" selected>date-time</option>
|
||||
<option value="time-date">time-date</option>
|
||||
<option value="date">date</option>
|
||||
<option value="time">time</option>
|
||||
</select>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Datetime - Standalone</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
ion-datetime {
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<label for="presentation">Presentation</label>
|
||||
<select id="presentation" onchange="changePresentation(event)">
|
||||
<option value="date-time" selected>date-time</option>
|
||||
<option value="time-date">time-date</option>
|
||||
<option value="date">date</option>
|
||||
<option value="time">time</option>
|
||||
</select>
|
||||
|
||||
|
||||
<label for="size">Size</label>
|
||||
<select id="size" onchange="changeSize(event)">
|
||||
<option value="fixed" selected>fixed</option>
|
||||
<option value="cover">cover</option>
|
||||
</select>
|
||||
<label for="size">Size</label>
|
||||
<select id="size" onchange="changeSize(event)">
|
||||
<option value="fixed" selected>fixed</option>
|
||||
<option value="cover">cover</option>
|
||||
</select>
|
||||
|
||||
<br /><br />
|
||||
<br /><br />
|
||||
|
||||
<ion-datetime></ion-datetime>
|
||||
<ion-datetime value="2022-02-22"></ion-datetime>
|
||||
|
||||
<script>
|
||||
const datetime = document.querySelector('ion-datetime');
|
||||
<script>
|
||||
const datetime = document.querySelector('ion-datetime');
|
||||
|
||||
const mutationObserver = new MutationObserver(() => {
|
||||
document.dispatchEvent(new CustomEvent('ionWorkingPartsDidChange'));
|
||||
});
|
||||
|
||||
const initCalendarMonthChangeObserver = async () => {
|
||||
if (!datetime.componentOnReady) return;
|
||||
await datetime.componentOnReady();
|
||||
|
||||
// We have to requestAnimationFrame to allow the datetime to render completely.
|
||||
requestAnimationFrame(() => {
|
||||
const calendarBody = datetime.shadowRoot.querySelector('.calendar-body');
|
||||
if (calendarBody) {
|
||||
mutationObserver.observe(calendarBody, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const changePresentation = (ev) => {
|
||||
mutationObserver.disconnect();
|
||||
datetime.presentation = ev.target.value;
|
||||
initCalendarMonthChangeObserver();
|
||||
};
|
||||
|
||||
const changeSize = (ev) => {
|
||||
datetime.size = ev.target.value;
|
||||
};
|
||||
|
||||
initCalendarMonthChangeObserver();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
const changePresentation = (ev) => {
|
||||
datetime.presentation = ev.target.value;
|
||||
}
|
||||
const changeSize = (ev) => {
|
||||
datetime.size = ev.target.value;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -27,6 +27,11 @@ describe('generateDayAriaLabel()', () => {
|
||||
|
||||
expect(generateDayAriaLabel('en-US', false, reference)).toEqual('Monday, May 31');
|
||||
});
|
||||
it('should return Saturday, April 1', () => {
|
||||
const reference = { month: 4, day: 1, year: 2006 };
|
||||
|
||||
expect(generateDayAriaLabel('en-US', false, reference)).toEqual('Saturday, April 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMonthAndDay()', () => {
|
||||
@@ -37,6 +42,14 @@ describe('getMonthAndDay()', () => {
|
||||
it('should return mar, 11 may', () => {
|
||||
expect(getMonthAndDay('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mar, 11 may');
|
||||
});
|
||||
|
||||
it('should return Sat, Apr 1', () => {
|
||||
expect(getMonthAndDay('en-US', { month: 4, day: 1, year: 2006 })).toEqual('Sat, Apr 1');
|
||||
});
|
||||
|
||||
it('should return sáb, 1 abr', () => {
|
||||
expect(getMonthAndDay('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('sáb, 1 abr');
|
||||
});
|
||||
})
|
||||
|
||||
describe('getFormattedHour()', () => {
|
||||
@@ -63,7 +76,15 @@ describe('getMonthAndYear()', () => {
|
||||
expect(getMonthAndYear('en-US', { month: 5, day: 11, year: 2021 })).toEqual('May 2021');
|
||||
});
|
||||
|
||||
it('should return mar, 11 may', () => {
|
||||
it('should return mayo de 2021', () => {
|
||||
expect(getMonthAndYear('es-ES', { month: 5, day: 11, year: 2021 })).toEqual('mayo de 2021');
|
||||
});
|
||||
|
||||
it('should return April 2006', () => {
|
||||
expect(getMonthAndYear('en-US', { month: 4, day: 1, year: 2006 })).toEqual('April 2006');
|
||||
});
|
||||
|
||||
it('should return abril de 2006', () => {
|
||||
expect(getMonthAndYear('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('abril de 2006');
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
isLeapYear,
|
||||
getNumDaysInMonth,
|
||||
is24Hour
|
||||
is24Hour,
|
||||
isMonthFirstLocale
|
||||
} from '../utils/helpers';
|
||||
|
||||
describe('daysInMonth()', () => {
|
||||
@@ -51,3 +52,18 @@ describe('is24Hour()', () => {
|
||||
expect(is24Hour('en-GB-u-hc-h12')).toBe(false);
|
||||
})
|
||||
})
|
||||
|
||||
describe('isMonthFirstLocale()', () => {
|
||||
it('should return true if the locale shows months first', () => {
|
||||
expect(isMonthFirstLocale('en-US')).toBe(true);
|
||||
expect(isMonthFirstLocale('en-GB')).toBe(true);
|
||||
expect(isMonthFirstLocale('es-ES')).toBe(true);
|
||||
expect(isMonthFirstLocale('ro-RO')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the locale shows years first', () => {
|
||||
expect(isMonthFirstLocale('zh-CN')).toBe(false);
|
||||
expect(isMonthFirstLocale('ja-JP')).toBe(false);
|
||||
expect(isMonthFirstLocale('ko-KR')).toBe(false);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -19,3 +19,53 @@ test('locale', async () => {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
test('it should render month and year with an en-US locale', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/locale?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const screenshotCompares = [];
|
||||
const datetime = await page.find('ion-datetime');
|
||||
|
||||
datetime.setProperty('locale', 'en-US');
|
||||
await page.waitForChanges();
|
||||
|
||||
const button = await page.find('ion-datetime >>> .calendar-month-year ion-item');
|
||||
await button.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
const yearBody = await page.find('ion-datetime >>> .datetime-year-body');
|
||||
expect(yearBody).toHaveClass('order-month-first');
|
||||
|
||||
screenshotCompares.push(await page.compareScreenshot());
|
||||
|
||||
for (const screenshotCompare of screenshotCompares) {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
test('it should render year and month with a ja-JP locale', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/locale?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const screenshotCompares = [];
|
||||
const datetime = await page.find('ion-datetime');
|
||||
|
||||
datetime.setProperty('locale', 'ja-JP');
|
||||
await page.waitForChanges();
|
||||
|
||||
const button = await page.find('ion-datetime >>> .calendar-month-year ion-item');
|
||||
await button.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
const yearBody = await page.find('ion-datetime >>> .datetime-year-body');
|
||||
expect(yearBody).toHaveClass('order-year-first');
|
||||
|
||||
screenshotCompares.push(await page.compareScreenshot());
|
||||
|
||||
for (const screenshotCompare of screenshotCompares) {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { newE2EPage, E2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('presentation', async () => {
|
||||
const page = await newE2EPage({
|
||||
@@ -13,3 +13,37 @@ test('presentation', async () => {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
describe('presentation: time', () => {
|
||||
|
||||
let page: E2EPage;
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/presentation?ionic:_testing=true'
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the time picker is visible in the view', () => {
|
||||
|
||||
it('manually setting the value should emit ionChange once', async () => {
|
||||
const datetime = await page.find('ion-datetime[presentation="time"]');
|
||||
const didChange = await datetime.spyOnEvent('ionChange');
|
||||
|
||||
await page.$eval('ion-datetime[presentation="time"]', (el: any) => {
|
||||
el.scrollIntoView();
|
||||
});
|
||||
|
||||
await page.$eval('ion-datetime[presentation="time"]', (el: any) => {
|
||||
el.value = '06:02:40';
|
||||
});
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(didChange).toHaveReceivedEventTimes(1);
|
||||
expect(didChange).toHaveReceivedEventDetail({ value: '06:02:40' });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
55
core/src/components/datetime/test/sub-pixel-width/e2e.ts
Normal file
55
core/src/components/datetime/test/sub-pixel-width/e2e.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
describe('datetime: sub-pixel width', () => {
|
||||
|
||||
test('should update the month when next button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/sub-pixel-width?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const modal = await page.find('ion-modal');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[1].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('March 2022');
|
||||
});
|
||||
|
||||
test('should update the month when prev button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/sub-pixel-width?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const modal = await page.find('ion-modal');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[0].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('January 2022');
|
||||
});
|
||||
|
||||
});
|
||||
56
core/src/components/datetime/test/sub-pixel-width/index.html
Normal file
56
core/src/components/datetime/test/sub-pixel-width/index.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Datetime - Sub Pixel Width</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
ion-datetime {
|
||||
width: 334.05px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
#background {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Datetime - Sub Pixel Width</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<h2>Modal</h2>
|
||||
<ion-button id="open-modal">Present Modal</ion-button>
|
||||
<ion-modal trigger="open-modal" id="modal">
|
||||
<div id="background">
|
||||
<ion-datetime id="picker" value="2022-02-01"></ion-datetime>
|
||||
</div>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script type="module">
|
||||
import { InitMonthDidChangeEvent } from '../test/utils/month-did-change-event.js';
|
||||
|
||||
document.getElementById('open-modal').addEventListener('click', () => {
|
||||
InitMonthDidChangeEvent();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
/**
|
||||
* Initializes a mutation observer to detect when the calendar month
|
||||
* text is updated as a result of a month change in `ion-datetime`.
|
||||
*
|
||||
* @param {*} datetimeSelector The element selector for the `ion-datetime` component.
|
||||
*/
|
||||
export function InitMonthDidChangeEvent(datetimeSelector = 'ion-datetime') {
|
||||
const observer = new MutationObserver(mutationRecords => {
|
||||
if (mutationRecords[0].type === 'characterData') {
|
||||
document.dispatchEvent(new CustomEvent('datetimeMonthDidChange'));
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.querySelector(datetimeSelector).shadowRoot.querySelector('.calendar-month-year'), {
|
||||
characterData: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
140
core/src/components/datetime/test/zoom/e2e.ts
Normal file
140
core/src/components/datetime/test/zoom/e2e.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
/**
|
||||
* This test emulates zoom behavior in the browser to make sure
|
||||
* that key functions of the ion-datetime continue to function even
|
||||
* if the page is zoomed in or out.
|
||||
*/
|
||||
describe('datetime: zoom interactivity', () => {
|
||||
|
||||
let deviceScaleFactor;
|
||||
|
||||
describe('zoom out', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
deviceScaleFactor = 0.75;
|
||||
});
|
||||
|
||||
test('should update the month when next button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/zoom?ionic:_testing=true'
|
||||
});
|
||||
|
||||
page.setViewport({
|
||||
width: 640,
|
||||
height: 480,
|
||||
deviceScaleFactor
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const modal = await page.find('ion-modal');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[1].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('March 2022');
|
||||
});
|
||||
|
||||
test('should update the month when prev button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/zoom?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const modal = await page.find('ion-modal');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[0].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('January 2022');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('zoom in', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
deviceScaleFactor = 2;
|
||||
});
|
||||
|
||||
test('should update the month when next button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/zoom?ionic:_testing=true'
|
||||
});
|
||||
|
||||
page.setViewport({
|
||||
width: 640,
|
||||
height: 480,
|
||||
deviceScaleFactor
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const modal = await page.find('ion-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[1].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('March 2022');
|
||||
});
|
||||
|
||||
test('should update the month when prev button is clicked', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/datetime/test/zoom?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const openModalBtn = await page.find('#open-modal');
|
||||
const modal = await page.find('ion-modal');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await openModalBtn.click();
|
||||
|
||||
await modal.waitForVisible();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button')
|
||||
|
||||
await buttons[0].click();
|
||||
|
||||
await page.waitForEvent('datetimeMonthDidChange');
|
||||
|
||||
const monthYear = await page.find('ion-datetime >>> .calendar-month-year');
|
||||
|
||||
expect(monthYear.textContent.trim()).toBe('January 2022');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
50
core/src/components/datetime/test/zoom/index.html
Normal file
50
core/src/components/datetime/test/zoom/index.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Datetime - Zoom</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
#background {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Datetime - Zoom</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<h2>Modal</h2>
|
||||
<ion-button id="open-modal">Present Modal</ion-button>
|
||||
<ion-modal trigger="open-modal" id="modal">
|
||||
<div id="background">
|
||||
<ion-datetime id="picker" value="2022-02-01"></ion-datetime>
|
||||
</div>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script type="module">
|
||||
import { InitMonthDidChangeEvent } from '../test/utils/month-did-change-event.js';
|
||||
|
||||
document.getElementById('open-modal').addEventListener('click', () => {
|
||||
InitMonthDidChangeEvent();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -282,9 +282,9 @@ export const getPickerMonths = (
|
||||
}
|
||||
|
||||
processedMonths.forEach(processedMonth => {
|
||||
const date = new Date(`${processedMonth}/1/${year}`);
|
||||
const date = new Date(`${processedMonth}/1/${year} GMT+0000`);
|
||||
|
||||
const monthString = new Intl.DateTimeFormat(locale, { month: 'long' }).format(date);
|
||||
const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date);
|
||||
months.push({ text: monthString, value: processedMonth });
|
||||
});
|
||||
} else {
|
||||
@@ -292,9 +292,34 @@ export const getPickerMonths = (
|
||||
const minMonth = minParts && minParts.year === year ? minParts.month : 1;
|
||||
|
||||
for (let i = minMonth; i <= maxMonth; i++) {
|
||||
const date = new Date(`${i}/1/${year}`);
|
||||
|
||||
const monthString = new Intl.DateTimeFormat(locale, { month: 'long' }).format(date);
|
||||
/**
|
||||
*
|
||||
* There is a bug on iOS 14 where
|
||||
* Intl.DateTimeFormat takes into account
|
||||
* the local timezone offset when formatting dates.
|
||||
*
|
||||
* Forcing the timezone to 'UTC' fixes the issue. However,
|
||||
* we should keep this workaround as it is safer. In the event
|
||||
* this breaks in another browser, we will not be impacted
|
||||
* because all dates will be interpreted in UTC.
|
||||
*
|
||||
* Example:
|
||||
* new Intl.DateTimeFormat('en-US', { month: 'long' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "March"
|
||||
* new Intl.DateTimeFormat('en-US', { month: 'long', timeZone: 'UTC' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "April"
|
||||
*
|
||||
* In certain timezones, iOS 14 shows the wrong
|
||||
* date for .toUTCString(). To combat this, we
|
||||
* force all of the timezones to GMT+0000 (UTC).
|
||||
*
|
||||
* Example:
|
||||
* Time Zone: Central European Standard Time
|
||||
* new Date('1/1/1992').toUTCString() // "Tue, 31 Dec 1991 23:00:00 GMT"
|
||||
* new Date('1/1/1992 GMT+0000').toUTCString() // "Wed, 01 Jan 1992 00:00:00 GMT"
|
||||
*/
|
||||
const date = new Date(`${i}/1/${year} GMT+0000`);
|
||||
|
||||
const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date);
|
||||
months.push({ text: monthString, value: i });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
/**
|
||||
* MM/DD/YYYY will return midnight in the user's timezone.
|
||||
*/
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
|
||||
const labelString = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric' }).format(date);
|
||||
const labelString = new Intl.DateTimeFormat(locale, { weekday: 'long', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(date);
|
||||
|
||||
/**
|
||||
* If date is today, prepend "Today" so screen readers indicate
|
||||
@@ -72,8 +72,8 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
|
||||
* Used for the header in MD mode.
|
||||
*/
|
||||
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
|
||||
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric' }).format(date);
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +83,6 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
|
||||
* Example: May 2021
|
||||
*/
|
||||
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}`);
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(date);
|
||||
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
|
||||
}
|
||||
|
||||
@@ -62,3 +62,28 @@ export const is24Hour = (locale: string, hourCycle?: 'h23' | 'h12') => {
|
||||
export const getNumDaysInMonth = (month: number, year: number) => {
|
||||
return (month === 4 || month === 6 || month === 9 || month === 11) ? 30 : (month === 2) ? isLeapYear(year) ? 29 : 28 : 31;
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain locales display month then year while
|
||||
* others display year then month.
|
||||
* We can use Intl.DateTimeFormat to determine
|
||||
* the ordering for each locale.
|
||||
*/
|
||||
export const isMonthFirstLocale = (locale: string) => {
|
||||
|
||||
/**
|
||||
* By setting month and year we guarantee that only
|
||||
* month, year, and literal (slashes '/', for example)
|
||||
* values are included in the formatToParts results.
|
||||
*
|
||||
* The ordering of the parts will be determined by
|
||||
* the locale. So if the month is the first value,
|
||||
* then we know month should be shown first. If the
|
||||
* year is the first value, then we know year should be shown first.
|
||||
*
|
||||
* This ordering can be controlled by customizing the locale property.
|
||||
*/
|
||||
const parts = new Intl.DateTimeFormat(locale, { month: 'numeric', year: 'numeric' }).formatToParts(new Date());
|
||||
|
||||
return parts[0].type === 'month';
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
|
||||
:host(.fab-vertical-bottom) {
|
||||
bottom: $fab-content-margin;
|
||||
bottom: var(--tab-translucent-safe-area, $fab-content-margin);
|
||||
}
|
||||
|
||||
:host(.fab-vertical-bottom.fab-edge) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testFab = async (
|
||||
type: string,
|
||||
|
||||
@@ -15,5 +15,5 @@ ion-footer {
|
||||
}
|
||||
|
||||
ion-footer ion-toolbar:last-of-type {
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0);
|
||||
}
|
||||
padding-bottom: calc(var(--ion-safe-area-bottom, 0) + var(--tab-translucent-safe-area, 0));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('footer: fade', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/footer/test/fade?ionic:_testing=true'
|
||||
import { scrollToBottom } from '@utils/test';
|
||||
|
||||
describe('footer: fade: iOS', () => {
|
||||
|
||||
let page: E2EPage;
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newE2EPage({
|
||||
url: '/src/components/footer/test/fade?ionic:_testing=true&ionic:mode=ios'
|
||||
});
|
||||
});
|
||||
|
||||
it('should match existing visual screenshots', async () => {
|
||||
const compares = [];
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: blurred'));
|
||||
|
||||
await scrollToBottom(page, 'ion-content');
|
||||
|
||||
compares.push(await page.compareScreenshot('footer: not blurred'));
|
||||
|
||||
for (const compare of compares) {
|
||||
expect(compare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { checkComponentModeClasses } from '../../../../utils/test/utils';
|
||||
import { checkComponentModeClasses } from '@utils/test';
|
||||
|
||||
test('footer: translucent', async () => {
|
||||
const page = await newE2EPage({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { componentOnReady, inheritAttributes } from '../../utils/helpers';
|
||||
import { Attributes, componentOnReady, inheritAttributes } from '../../utils/helpers';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
|
||||
import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
|
||||
@@ -21,7 +21,7 @@ export class Header implements ComponentInterface {
|
||||
private contentScrollCallback?: any;
|
||||
private intersectionObserver?: any;
|
||||
private collapsibleMainHeader?: HTMLElement;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { checkComponentModeClasses } from '../../../../utils/test/utils';
|
||||
import { checkComponentModeClasses } from '@utils/test';
|
||||
|
||||
test('header: translucent', async () => {
|
||||
const page = await newE2EPage({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Attributes, inheritAttributes } from '../../utils/helpers';
|
||||
|
||||
/**
|
||||
* @part image - The inner `img` element.
|
||||
@@ -13,6 +14,7 @@ import { getIonMode } from '../../global/ionic-global';
|
||||
export class Img implements ComponentInterface {
|
||||
|
||||
private io?: IntersectionObserver;
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@@ -45,6 +47,10 @@ export class Img implements ComponentInterface {
|
||||
/** Emitted when the img fails to load */
|
||||
@Event() ionError!: EventEmitter<void>;
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['draggable']);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
this.addIO();
|
||||
}
|
||||
@@ -100,17 +106,38 @@ export class Img implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loadSrc, alt, onLoad, loadError, inheritedAttributes } = this;
|
||||
const { draggable } = inheritedAttributes;
|
||||
return (
|
||||
<Host class={getIonMode(this)}>
|
||||
<img
|
||||
decoding="async"
|
||||
src={this.loadSrc}
|
||||
alt={this.alt}
|
||||
onLoad={this.onLoad}
|
||||
onError={this.loadError}
|
||||
src={loadSrc}
|
||||
alt={alt}
|
||||
onLoad={onLoad}
|
||||
onError={loadError}
|
||||
part="image"
|
||||
draggable={isDraggable(draggable)}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerated strings must be set as booleans
|
||||
* as Stencil will not render 'false' in the DOM.
|
||||
* The need to explicitly render draggable="true"
|
||||
* as only certain elements are draggable by default.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable.
|
||||
*/
|
||||
const isDraggable = (draggable?: string): boolean | undefined => {
|
||||
switch (draggable) {
|
||||
case 'true':
|
||||
return true;
|
||||
case 'false':
|
||||
return false;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
17
core/src/components/img/test/draggable/e2e.ts
Normal file
17
core/src/components/img/test/draggable/e2e.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('img: draggable', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/img/test/draggable?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const imgDraggableTrue = await page.find('#img-draggable-true >>> img');
|
||||
expect(imgDraggableTrue.getAttribute('draggable')).toEqual('true');
|
||||
|
||||
const imgDraggableFalse = await page.find('#img-draggable-false >>> img');
|
||||
expect(imgDraggableFalse.getAttribute('draggable')).toEqual('false');
|
||||
|
||||
const imgDraggableUnset = await page.find('#img-draggable-unset >>> img');
|
||||
expect(imgDraggableUnset.getAttribute('draggable')).toEqual(null);
|
||||
|
||||
});
|
||||
60
core/src/components/img/test/draggable/index.html
Normal file
60
core/src/components/img/test/draggable/index.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Img - Draggable</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
|
||||
<style>
|
||||
ion-img::part(image) {
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Img - Draggable</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Draggable</ion-label>
|
||||
<ion-img id="img-draggable-true" draggable="true"
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAARQAQMAAAA2ut43AAAABlBMVEXMzMz////TjRV2AAAEPUlEQVR42u3RMREAMBDDsPAn/eXhavOq87Ztu7u7+6eBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBf2hvgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgatgn4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGr7S0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBwFewzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcLW9BQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq6CfQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgautrfAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAVbDPwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNX2FhgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGLgK9hkYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBi42t4CAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV8E+AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA1fbW2BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYOAq2GdgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGDgansLDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMXAX7DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDFxtb4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrYJ+BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq+0tMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcBXsMzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHC1vQUGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgaugn0GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGjvYD5WuYrpZqdmcAAAAASUVORK5CYII=">
|
||||
</ion-img>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Not draggable (draggable="false")</ion-label>
|
||||
<ion-img id="img-draggable-false" draggable="false"
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAARQAQMAAAA2ut43AAAABlBMVEXMzMz////TjRV2AAAEPUlEQVR42u3RMREAMBDDsPAn/eXhavOq87Ztu7u7+6eBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBf2hvgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgatgn4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGr7S0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBwFewzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcLW9BQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq6CfQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgautrfAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAVbDPwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNX2FhgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGLgK9hkYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBi42t4CAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV8E+AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA1fbW2BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYOAq2GdgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGDgansLDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMXAX7DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDFxtb4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrYJ+BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq+0tMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcBXsMzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHC1vQUGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgaugn0GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGjvYD5WuYrpZqdmcAAAAASUVORK5CYII=">
|
||||
</ion-img>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Draggable (draggable not set)</ion-label>
|
||||
<ion-img id="img-draggable-unset"
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAARQAQMAAAA2ut43AAAABlBMVEXMzMz////TjRV2AAAEPUlEQVR42u3RMREAMBDDsPAn/eXhavOq87Ztu7u7+6eBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBf2hvgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgatgn4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGr7S0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBwFewzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcLW9BQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq6CfQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgautrfAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAVbDPwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNX2FhgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGLgK9hkYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBi42t4CAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV8E+AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA1fbW2BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYOAq2GdgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGDgansLDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMXAX7DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDFxtb4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrYJ+BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq+0tMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcBXsMzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHC1vQUGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgaugn0GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGjvYD5WuYrpZqdmcAAAAASUVORK5CYII">
|
||||
</ion-img>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -69,7 +69,7 @@ export class InfiniteScrollExample {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
event.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
@@ -111,7 +111,7 @@ infiniteScroll.addEventListener('ionInfinite', function(event) {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
event.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
@@ -164,7 +164,7 @@ const InfiniteScrollExample: React.FC = () => {
|
||||
pushData();
|
||||
console.log('Loaded data');
|
||||
ev.target.complete();
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
setInfiniteDisabled(true);
|
||||
}
|
||||
}, 500);
|
||||
@@ -263,7 +263,7 @@ export class InfiniteScrollExample {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (this.data.length == 1000) {
|
||||
if (this.data.length === 1000) {
|
||||
ev.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
@@ -335,7 +335,8 @@ export class InfiniteScrollExample {
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
import {
|
||||
InfiniteScrollCustomEvent,
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonInfiniteScroll,
|
||||
@@ -363,7 +364,7 @@ export default defineComponent({
|
||||
const toggleInfiniteScroll = () => {
|
||||
isDisabled.value = !isDisabled.value;
|
||||
}
|
||||
const items = ref([]);
|
||||
const items = ref<number[]>([]);
|
||||
const pushData = () => {
|
||||
const max = items.value.length + 20;
|
||||
const min = max - 20;
|
||||
@@ -372,7 +373,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = (ev: CustomEvent) => {
|
||||
const loadData = (ev: InfiniteScrollCustomEvent) => {
|
||||
setTimeout(() => {
|
||||
pushData();
|
||||
console.log('Loaded data');
|
||||
@@ -380,7 +381,7 @@ export default defineComponent({
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (items.value.length == 1000) {
|
||||
if (items.value.length === 1000) {
|
||||
ev.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -1,10 +1,49 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
|
||||
|
||||
/**
|
||||
* Scrolls an `ion-content` element to the bottom, triggering the `ionInfinite` event.
|
||||
* Waits for the custom event to complete.
|
||||
*/
|
||||
async function scrollIonContentPage(page: E2EPage) {
|
||||
const content = await page.find('ion-content');
|
||||
await content.callMethod('scrollToBottom');
|
||||
await page.waitForChanges();
|
||||
|
||||
const ev = await page.spyOnEvent('ionInfiniteComplete', 'document');
|
||||
await ev.next();
|
||||
}
|
||||
|
||||
describe('infinite-scroll: basic', () => {
|
||||
|
||||
it('should match existing visual screenshots', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/infinite-scroll/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
describe('when enabled', () => {
|
||||
|
||||
it('should load more items when scrolled to the bottom', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/infinite-scroll/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const initialItems = await page.findAll('ion-item');
|
||||
|
||||
expect(initialItems.length).toBe(30);
|
||||
|
||||
await scrollIonContentPage(page);
|
||||
|
||||
const updatedItems = await page.findAll('ion-item');
|
||||
|
||||
expect(updatedItems.length).toBe(60);
|
||||
});
|
||||
|
||||
test('infinite-scroll: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/infinite-scroll/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Infinite Scroll - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
@@ -35,8 +37,6 @@
|
||||
</ion-infinite-scroll>
|
||||
</ion-content>
|
||||
|
||||
|
||||
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
@@ -48,12 +48,11 @@
|
||||
}
|
||||
|
||||
infiniteScroll.addEventListener('ionInfinite', async function () {
|
||||
console.log('Loading data...');
|
||||
await wait(500);
|
||||
infiniteScroll.complete();
|
||||
appendItems();
|
||||
|
||||
console.log('Done');
|
||||
// Custom event consumed in the e2e tests
|
||||
document.dispatchEvent(new CustomEvent('ionInfiniteComplete'));
|
||||
});
|
||||
|
||||
function appendItems() {
|
||||
|
||||
@@ -36,7 +36,7 @@ export class InfiniteScrollExample {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
event.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -25,7 +25,7 @@ infiniteScroll.addEventListener('ionInfinite', function(event) {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
event.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -37,7 +37,7 @@ const InfiniteScrollExample: React.FC = () => {
|
||||
pushData();
|
||||
console.log('Loaded data');
|
||||
ev.target.complete();
|
||||
if (data.length == 1000) {
|
||||
if (data.length === 1000) {
|
||||
setInfiniteDisabled(true);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -38,7 +38,7 @@ export class InfiniteScrollExample {
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (this.data.length == 1000) {
|
||||
if (this.data.length === 1000) {
|
||||
ev.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
import {
|
||||
InfiniteScrollCustomEvent,
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonInfiniteScroll,
|
||||
@@ -56,7 +57,7 @@ export default defineComponent({
|
||||
const toggleInfiniteScroll = () => {
|
||||
isDisabled.value = !isDisabled.value;
|
||||
}
|
||||
const items = ref([]);
|
||||
const items = ref<number[]>([]);
|
||||
const pushData = () => {
|
||||
const max = items.value.length + 20;
|
||||
const min = max - 20;
|
||||
@@ -65,7 +66,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = (ev: CustomEvent) => {
|
||||
const loadData = (ev: InfiniteScrollCustomEvent) => {
|
||||
setTimeout(() => {
|
||||
pushData();
|
||||
console.log('Loaded data');
|
||||
@@ -73,7 +74,7 @@ export default defineComponent({
|
||||
|
||||
// App logic to determine if all data is loaded
|
||||
// and disable the infinite scroll
|
||||
if (items.value.length == 1000) {
|
||||
if (items.value.length === 1000) {
|
||||
ev.target.disabled = true;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AutocompleteTypes, Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
|
||||
import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
|
||||
import { Attributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ export class Input implements ComponentInterface {
|
||||
private nativeInput?: HTMLInputElement;
|
||||
private inputId = `ion-input-${inputIds++}`;
|
||||
private didBlurAfterEdit = false;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
private isComposing = false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,7 +168,7 @@ export class ItemSliding implements ComponentInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the sliding item. Items can also be closed from the [List](../list).
|
||||
* Close the sliding item. Items can also be closed from the [List](./list).
|
||||
*/
|
||||
@Method()
|
||||
async close() {
|
||||
@@ -176,7 +176,7 @@ export class ItemSliding implements ComponentInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all of the sliding items in the list. Items can also be closed from the [List](../list).
|
||||
* Close all of the sliding items in the list. Items can also be closed from the [List](./list).
|
||||
*/
|
||||
@Method()
|
||||
async closeOpened(): Promise<boolean> {
|
||||
@@ -408,7 +408,7 @@ export class ItemSliding implements ComponentInterface {
|
||||
this.state = SlidingState.Disabled;
|
||||
this.tmr = undefined;
|
||||
if (this.gesture) {
|
||||
this.gesture.enable(true);
|
||||
this.gesture.enable(!this.disabled);
|
||||
}
|
||||
}, 600) as any;
|
||||
|
||||
|
||||
@@ -944,7 +944,7 @@ export const ItemExamples: React.FC = () => {
|
||||
<IonItem>
|
||||
<IonButton slot="start">
|
||||
Start Icon
|
||||
<IonIcon icon={home} slot="end" />>
|
||||
<IonIcon icon={home} slot="end" />
|
||||
</IonButton>
|
||||
<IonLabel>Buttons with Icons</IonLabel>
|
||||
<IonButton slot="end">
|
||||
|
||||
@@ -203,7 +203,7 @@ export const ItemExamples: React.FC = () => {
|
||||
<IonItem>
|
||||
<IonButton slot="start">
|
||||
Start Icon
|
||||
<IonIcon icon={home} slot="end" />>
|
||||
<IonIcon icon={home} slot="end" />
|
||||
</IonButton>
|
||||
<IonLabel>Buttons with Icons</IonLabel>
|
||||
<IonButton slot="end">
|
||||
|
||||
@@ -289,8 +289,6 @@ export class LoadingExample {
|
||||
<ion-button @click="presentLoadingWithOptions">Show Loading</ion-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
<script>
|
||||
import { IonButton, loadingController } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testLoading = async (
|
||||
type: string,
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
<ion-button @click="presentLoadingWithOptions">Show Loading</ion-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
<script>
|
||||
import { IonButton, loadingController } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 { Attributes, inheritAttributes } from '../../utils/helpers';
|
||||
import { menuController } from '../../utils/menu-controller';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
|
||||
@@ -25,7 +25,7 @@ import { updateVisibility } from '../menu-toggle/menu-toggle-util';
|
||||
shadow: true
|
||||
})
|
||||
export class MenuButton implements ComponentInterface, ButtonInterface {
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLIonSegmentElement;
|
||||
|
||||
|
||||
@@ -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 { Attributes, assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers';
|
||||
import { menuController } from '../../utils/menu-controller';
|
||||
import { getOverlay } from '../../utils/overlays';
|
||||
|
||||
@@ -43,7 +43,7 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
contentEl?: HTMLElement;
|
||||
lastFocus?: HTMLElement;
|
||||
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
private handleFocus = (ev: FocusEvent) => {
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ test('menu: focus trap with overlays', async () => {
|
||||
|
||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss= await page.spyOnEvent('ionModalDidDismiss');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const menu = await page.find('ion-menu');
|
||||
await menu.callMethod('open');
|
||||
@@ -40,7 +40,6 @@ test('menu: focus trap with content inside overlays', async () => {
|
||||
|
||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss= await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const menu = await page.find('ion-menu');
|
||||
await menu.callMethod('open');
|
||||
@@ -57,3 +56,36 @@ test('menu: focus trap with content inside overlays', async () => {
|
||||
|
||||
expect(await getActiveElementID(page)).toEqual('other-button');
|
||||
});
|
||||
|
||||
test('menu: should work with swipe gestures after modal is dismissed', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/menu/test/focus-trap?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const ionDidOpen = await page.spyOnEvent('ionDidOpen');
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
const menu = await page.find('ion-menu');
|
||||
await menu.callMethod('open');
|
||||
await ionDidOpen.next();
|
||||
|
||||
const openModal = await page.find('#open-modal-button');
|
||||
await openModal.click();
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const modal = await page.find('ion-modal');
|
||||
await modal.callMethod('dismiss');
|
||||
await ionModalDidDismiss.next();
|
||||
|
||||
await page.mouse.move(30, 168);
|
||||
await page.mouse.down();
|
||||
|
||||
await page.mouse.move(384, 168);
|
||||
await page.mouse.up();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(menu.classList.contains('show-menu')).toBeTruthy();
|
||||
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { menuController } from '../../../utils/menu-controller';
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testMenu = async (
|
||||
type: string,
|
||||
|
||||
@@ -7,7 +7,11 @@ import { createSheetEnterAnimation } from './sheet';
|
||||
|
||||
const createEnterAnimation = () => {
|
||||
const backdropAnimation = createAnimation()
|
||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)');
|
||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
||||
.beforeStyles({
|
||||
'pointer-events': 'none'
|
||||
})
|
||||
.afterClearStyles(['pointer-events']);
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
|
||||
@@ -27,11 +31,7 @@ export const iosEnterAnimation = (
|
||||
const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
|
||||
|
||||
backdropAnimation
|
||||
.addElement(root.querySelector('ion-backdrop')!)
|
||||
.beforeStyles({
|
||||
'pointer-events': 'none'
|
||||
})
|
||||
.afterClearStyles(['pointer-events']);
|
||||
.addElement(root.querySelector('ion-backdrop')!);
|
||||
|
||||
wrapperAnimation
|
||||
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||
|
||||
@@ -6,7 +6,11 @@ import { createSheetEnterAnimation } from './sheet';
|
||||
|
||||
const createEnterAnimation = () => {
|
||||
const backdropAnimation = createAnimation()
|
||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)');
|
||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
||||
.beforeStyles({
|
||||
'pointer-events': 'none'
|
||||
})
|
||||
.afterClearStyles(['pointer-events']);
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.keyframes([
|
||||
@@ -29,11 +33,7 @@ export const mdEnterAnimation = (
|
||||
const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
|
||||
|
||||
backdropAnimation
|
||||
.addElement(root.querySelector('ion-backdrop')!)
|
||||
.beforeStyles({
|
||||
'pointer-events': 'none'
|
||||
})
|
||||
.afterClearStyles(['pointer-events']);
|
||||
.addElement(root.querySelector('ion-backdrop')!);
|
||||
|
||||
wrapperAnimation
|
||||
.addElement(root.querySelector('.modal-wrapper')!);
|
||||
|
||||
@@ -16,6 +16,14 @@ export const createSheetEnterAnimation = (opts: ModalAnimationOptions) => {
|
||||
const backdropAnimation = createAnimation('backdropAnimation')
|
||||
.fromTo('opacity', 0, initialBackdrop);
|
||||
|
||||
if (shouldShowBackdrop) {
|
||||
backdropAnimation
|
||||
.beforeStyles({
|
||||
'pointer-events': 'none'
|
||||
})
|
||||
.afterClearStyles(['pointer-events']);
|
||||
}
|
||||
|
||||
const wrapperAnimation = createAnimation('wrapperAnimation')
|
||||
.keyframes([
|
||||
{ offset: 0, opacity: 1, transform: 'translateY(100%)' },
|
||||
|
||||
@@ -42,6 +42,32 @@ export const createSheetGesture = (
|
||||
const backdropAnimation = animation.childAnimations.find(ani => ani.id === 'backdropAnimation');
|
||||
const maxBreakpoint = breakpoints[breakpoints.length - 1];
|
||||
|
||||
const enableBackdrop = () => {
|
||||
baseEl.style.setProperty('pointer-events', 'auto');
|
||||
backdropEl.style.setProperty('pointer-events', 'auto');
|
||||
|
||||
/**
|
||||
* When the backdrop is enabled, elements such
|
||||
* as inputs should not be focusable outside
|
||||
* the sheet.
|
||||
*/
|
||||
baseEl.classList.remove('ion-disable-focus-trap');
|
||||
}
|
||||
|
||||
const disableBackdrop = () => {
|
||||
baseEl.style.setProperty('pointer-events', 'none');
|
||||
backdropEl.style.setProperty('pointer-events', 'none');
|
||||
|
||||
/**
|
||||
* When the backdrop is enabled, elements such
|
||||
* as inputs should not be focusable outside
|
||||
* the sheet.
|
||||
* Adding this class disables focus trapping
|
||||
* for the sheet temporarily.
|
||||
*/
|
||||
baseEl.classList.add('ion-disable-focus-trap');
|
||||
}
|
||||
|
||||
/**
|
||||
* After the entering animation completes,
|
||||
* we need to set the animation to go from
|
||||
@@ -56,11 +82,18 @@ export const createSheetGesture = (
|
||||
animation.progressStart(true, 1 - currentBreakpoint);
|
||||
|
||||
/**
|
||||
* Backdrop should become enabled
|
||||
* after the backdropBreakpoint value
|
||||
* If backdrop is not enabled, then content
|
||||
* behind modal should be clickable. To do this, we need
|
||||
* to remove pointer-events from ion-modal as a whole.
|
||||
* ion-backdrop and .modal-wrapper always have pointer-events: auto
|
||||
* applied, so the modal content can still be interacted with.
|
||||
*/
|
||||
const backdropEnabled = currentBreakpoint > backdropBreakpoint
|
||||
backdropEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
|
||||
const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
disableBackdrop();
|
||||
}
|
||||
}
|
||||
|
||||
if (contentEl && currentBreakpoint !== maxBreakpoint) {
|
||||
@@ -186,8 +219,12 @@ export const createSheetGesture = (
|
||||
* Backdrop should become enabled
|
||||
* after the backdropBreakpoint value
|
||||
*/
|
||||
const backdropEnabled = currentBreakpoint > backdropBreakpoint;
|
||||
backdropEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
|
||||
const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
disableBackdrop();
|
||||
}
|
||||
|
||||
gesture.enable(true);
|
||||
});
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.modal-wrapper, ion-backdrop {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
:host(.overlay-hidden) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -527,6 +527,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
const { delegate } = this.getDelegate();
|
||||
await detachComponent(delegate, this.usersElement);
|
||||
|
||||
writeTask(() => this.el.classList.remove('show-modal'));
|
||||
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
}
|
||||
|
||||
@@ -140,6 +140,33 @@ interface ModalOptions<T extends ComponentRef = ComponentRef> {
|
||||
interface ModalAttributes extends JSXBase.HTMLAttributes<HTMLElement> {}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
| Key | Function |
|
||||
| ----- | ------------------- |
|
||||
| `Esc` | Dismisses the modal |
|
||||
|
||||
### Screen Readers
|
||||
|
||||
Modals have the `aria-modal` attribute applied. This attribute can cause assistive technologies to limit navigation to the modal element's contents. As a result, using gestures that move to the next or previous items may not focus elements outside of the modal. This applies even when the backdrop is disabled in sheet modals using the `backdropBreakpoint` property.
|
||||
|
||||
Assistive technologies will not limit navigation to the modal element's contents if developers manually move focus. However, manually moving focus outside of a modal is not supported in Ionic for modals that have focus trapping enabled.
|
||||
|
||||
See https://w3c.github.io/aria/#aria-modal for more information.
|
||||
|
||||
### Focus Trapping
|
||||
|
||||
When a modal is presented, focus will be trapped inside of the presented modal. Users can focus other interactive elements inside the modal but will never be able to focus interactive elements outside the modal while the modal is presented. For applications that present multiple stacked modals, focus will be trapped on the modal that was presented last.
|
||||
|
||||
Sheet modals that have had their backdrop disabled by the `backdropBreakpoint` property are not subject to focus trapping.
|
||||
|
||||
### Sheet Modals
|
||||
|
||||
Sheet modals allow users to interact with content behind the modal when the `backdropBreakpoint` property is used. The backdrop will be disabled up to and including the specified `backdropBreakpoint` and will be enabled after it.
|
||||
|
||||
When the backdrop is disabled, users will be able to interact with elements outside the sheet modal using a pointer or keyboard. Assistive technologies may not focus outside the sheet modal by default due to the usage of `aria-modal`. We recommend avoiding features such as autofocus here as it can cause assistive technologies to jump between two interactive contexts without warning the user.
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
@@ -223,9 +250,10 @@ import { ModalPage } from '../modal/modal.page';
|
||||
styleUrls: ['./modal-example.css']
|
||||
})
|
||||
export class ModalExample {
|
||||
constructor(public modalController: ModalController) {
|
||||
// The `ion-modal` element reference.
|
||||
modal: HTMLElement;
|
||||
|
||||
}
|
||||
constructor(public modalController: ModalController) {}
|
||||
|
||||
async presentModal() {
|
||||
const modal = await this.modalController.create({
|
||||
@@ -310,6 +338,22 @@ const { data } = await modal.onWillDismiss();
|
||||
console.log(data);
|
||||
```
|
||||
|
||||
#### Accessing the Modal Element
|
||||
|
||||
When opening a modal with the modal controller, Ionic will assign the modal HTML element reference to the `modal` property on your component's class instance.
|
||||
|
||||
You can use this property to directly access the `ion-modal` element to add or remove classes or handle additional checks.
|
||||
|
||||
```ts
|
||||
export class ModalPage implements OnInit {
|
||||
// The `ion-modal` element reference.
|
||||
modal: HTMLElement;
|
||||
|
||||
ngOnInit() {
|
||||
console.log('The HTML ion-modal element', this.modal); // <ion-modal></ion-modal>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Lazy Loading
|
||||
|
||||
|
||||
@@ -84,3 +84,15 @@ test('modal: htmlAttributes', async () => {
|
||||
|
||||
expect(attribute).toEqual('basic-modal');
|
||||
});
|
||||
|
||||
test('it should dismiss the modal when clicking the backdrop', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' });
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#basic-modal');
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.mouse.click(20, 20);
|
||||
await ionModalDidDismiss.next();
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { testModal } from '../test.utils';
|
||||
import { getActiveElement, getActiveElementParent } from '@utils/test';
|
||||
|
||||
const DIRECTORY = 'sheet';
|
||||
|
||||
@@ -43,3 +44,76 @@ test('modal - open', async () => {
|
||||
expect(screenshotCompare).toMatchScreenshot();
|
||||
}
|
||||
});
|
||||
|
||||
test('should click to dismiss sheet modal', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#sheet-modal');
|
||||
|
||||
const modal = await page.find('ion-modal');
|
||||
await modal.waitForVisible();
|
||||
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('should click to dismiss sheet modal when backdrop is active', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
|
||||
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
|
||||
await page.click('#backdrop-active');
|
||||
|
||||
const modal = await page.find('ion-modal');
|
||||
await modal.waitForVisible();
|
||||
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
await ionModalDidDismiss.next();
|
||||
});
|
||||
|
||||
test('should click to present another modal when backdrop is inactive', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#backdrop-inactive');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#custom-height-modal');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
const customModal = await page.find('.custom-height');
|
||||
expect(customModal).not.toBe(null);
|
||||
});
|
||||
|
||||
test('input should be focusable when backdrop is inactive', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#backdrop-inactive');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#root-input');
|
||||
|
||||
const parentEl = await getActiveElementParent(page);
|
||||
expect(parentEl.id).toEqual('root-input');
|
||||
});
|
||||
|
||||
test('input should not be focusable when backdrop is active', async () => {
|
||||
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
|
||||
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
|
||||
|
||||
await page.click('#backdrop-active');
|
||||
|
||||
await ionModalDidPresent.next();
|
||||
|
||||
await page.click('#root-input');
|
||||
await page.waitForChanges();
|
||||
|
||||
const parentEl = await getActiveElement(page);
|
||||
expect(parentEl.tagName).toEqual('ION-BUTTON');
|
||||
});
|
||||
|
||||
@@ -85,6 +85,11 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-label>Input outside modal</ion-label>
|
||||
<ion-input id="root-input"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-button id="sheet-modal" onclick="presentModal()">Present Sheet Modal</ion-button>
|
||||
<ion-button id="custom-breakpoint-modal" onclick="presentModal({ initialBreakpoint: 0.5, breakpoints: [0, 0.5, 1] })">Present Sheet Modal (Custom Breakpoints)</ion-button>
|
||||
<ion-button id="custom-breakpoint-modal" onclick="presentModal({ initialBreakpoint: 0.5, breakpoints: [0, 0.5, 0.75] })">Present Sheet Modal (Max breakpoint is not 1)</ion-button>
|
||||
@@ -93,6 +98,8 @@
|
||||
<ion-button id="custom-height-modal" onclick="presentModal({ cssClass: 'custom-height' })">Present Sheet Modal (Custom Height)</ion-button>
|
||||
<ion-button id="custom-handle-modal" onclick="presentModal({ cssClass: 'custom-handle' })">Present Sheet Modal (Custom Handle)</ion-button>
|
||||
<ion-button id="no-handle-modal" onclick="presentModal({ handle: false })">Present Sheet Modal (No Handle)</ion-button>
|
||||
<ion-button id="backdrop-active" onclick="presentModal({ backdropBreakpoint: 0.3, initialBreakpoint: 0.5, breakpoints: [0.3, 0.5, 0.7, 1] })">Backdrop is active</ion-button>
|
||||
<ion-button id="backdrop-inactive" onclick="presentModal({ cssClass: 'backdrop-inactive', backdropBreakpoint: 0.5, initialBreakpoint: 0.3, breakpoints: [0.3, 0.5, 0.7, 1] })">Backdrop is inactive</ion-button>
|
||||
|
||||
<div class="grid">
|
||||
<div class="grid-item red"></div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { generateE2EUrl } from '../../../utils/test/utils';
|
||||
import { generateE2EUrl } from '@utils/test';
|
||||
|
||||
export const testModal = async (
|
||||
type: string,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user