Compare commits

...

66 Commits

Author SHA1 Message Date
Liam DeBeasi
349e8cd5b0 5.3.0 2020-07-23 11:43:23 -04:00
Liam DeBeasi
bcbe8cbb8d fix(overlay): do not try to trap focus on hidden inputs (#21799) 2020-07-23 11:19:31 -04:00
Liam DeBeasi
03ca0c5968 docs(modal): add correct card-style modal usage for react (#21780)
resolves #21773
2020-07-22 13:49:32 -04:00
Liam DeBeasi
fff4aec6cf fix(overlays): trap focus inside overlay components except toast (#21716)
fixes #21647
2020-07-22 12:09:31 -04:00
Liam DeBeasi
eb592b8917 fix(nav): insertPages method correctly inserts multiple pages with props (#21725)
fixes #21724
2020-07-22 12:06:29 -04:00
Ely Lucas
a15cd01bc3 fix(react): fixes swipe to go back regression (#21791) 2020-07-21 22:30:44 -06:00
Adam Bradley
4199762d3e chore(): export ionicons jsx interface (#21788) 2020-07-21 14:45:19 -04:00
Adam Bradley
79518468dd fix(overlays): move prepareOverlay to connectedCallback
For custom elements builds, overlays cannot use hasAttribute() in the constructor, so moving it to connectedCallback instead.
2020-07-21 13:07:54 -05:00
Ely Lucas
f4a08b7ed4 fix(react): fixng ion-router-outlet ref regresssion (#21786) 2020-07-21 08:48:25 -06:00
Liam DeBeasi
dbe6853884 fix(title): allow overriding of large title transform origin (#21770)
resolves #21761
2020-07-20 13:40:27 -04:00
Liam DeBeasi
096eef4a79 feat(card): expose global card css variable (#21756)
resolves #21694
2020-07-20 12:46:58 -04:00
Ely Lucas
d4a5fbd955 fix(react): adding custom history to IonReactRouter, closes #20297 (#21775) 2020-07-20 10:03:35 -06:00
Brandy Carney
591c133344 docs(readme): Ionic -> Ionic Framework 2020-07-20 11:55:59 -04:00
Christian Lüdemann
d297ecb87a fix(virtual-scroll): properly calculate top offset when nested (#21581) 2020-07-20 10:29:39 -04:00
Liam DeBeasi
a625c837a6 feat(input, textarea): expose native events for ionBlur and ionFocus (#21777)
resolves #17363
2020-07-17 17:43:17 -04:00
Liam DeBeasi
77464ef21a feat(router): add navigation hooks (#21709) 2020-07-17 11:08:16 -04:00
Liam DeBeasi
fa93dffdb4 feat(input): accept datetime-local, month, and week type values (#21758)
resolves #21757
2020-07-17 10:46:07 -04:00
Brandy Carney
6f200ac751 chore(release): update repository name (#21699) 2020-07-14 17:01:08 -04:00
Simon Hänisch
7c2d0c981a feat(select): add optional generic typings (#21514)
resolves https://github.com/ionic-team/ionic-framework/issues/20220
2020-07-14 16:06:48 -04:00
Ely Lucas
1351b2eafc fix(react): fix regression on IonTabsContext not working properly (#21751) 2020-07-14 13:26:04 -06:00
Liam DeBeasi
88f1828bd8 fix(segment-button): allow min-width to be overridden (#21722)
fixes #21105
2020-07-14 10:58:40 -04:00
Liam DeBeasi
020f3cc56c fix(keyboard): keyboard events now consistently fire on android (#21741)
fixes #21734
2020-07-14 10:43:58 -04:00
Liam DeBeasi
3cbf9e7c4c fix(ios): improve scroll assist reliability on password inputs (#21703)
fixes #21688
2020-07-10 09:41:43 -04:00
Liam DeBeasi
2664587749 fix(angular): per-page animations now work with swipe to go back (#21706)
resolves #21692
2020-07-10 09:38:58 -04:00
Liam DeBeasi
f00ad8a835 fix(datetime): remove unneeded combox role (#21708)
resolves https://github.com/ionic-team/ionic-framework/issues/21667
2020-07-10 09:37:39 -04:00
Ely Lucas
81ef3f1ecd fix(react): fix regression with history.replace in new react router (#21698) 2020-07-08 10:22:08 -06:00
Ely Lucas
c171ccbd37 feat(react): React Router Enhancements (#21693) 2020-07-07 12:02:05 -05:00
Ely Lucas
a0735b97bf fix(build): Fixes to dev build for react (#21684)
* fix(react): removing lock files in react to get npm llink to work

* fix(scripts) updating ionic/react dep in react-router

* fix(scripts): linking ionic/react in router

* removing log
2020-07-06 21:11:45 -05:00
Adam Bradley
b4423a816f chore(deps): update stencil and rollup (#21680) 2020-07-06 17:37:10 +02:00
Liam DeBeasi
ff23e4f267 merge release-5.2.3
5.2.3
2020-07-02 09:11:27 -04:00
Liam DeBeasi
97123cd6c3 5.2.3 2020-07-01 13:28:43 -04:00
Liam DeBeasi
1dcd9de50a fix(input): clear button can now be tabbed to (#21633)
fixes https://github.com/ionic-team/ionic/issues/21549
2020-07-01 10:13:12 -04:00
Adam Bradley
c458523b0d chore(stencil): update to stencil 1.15.0 (#21653) 2020-06-30 11:34:16 -05:00
Liam DeBeasi
a5e4669c4b feat(segment-button, toast): add additional parts docs (#21532) 2020-06-30 10:31:54 -05:00
Brandy Carney
9413aa0bac docs(coc): add Contributor Covenant Code of Conduct (#21550) 2020-06-29 11:35:29 -04:00
Adam Bradley
ce6e637787 chore(deps): bump deps (#21632) 2020-06-29 10:21:06 -05:00
Adam Bradley
7a29e0e3c7 chore(ionicons): bump to ionicons 5.1.2, add package-lock.json (#21629) 2020-06-26 09:42:05 -05:00
Liam DeBeasi
8c79e2c5b5 fix(select): change role to listbox (#21609)
fixes https://github.com/ionic-team/ionic/issues/21601
2020-06-25 11:46:52 -04:00
Liam DeBeasi
26674f1dfa fix(slides): enable keyboard integration (#21608)
resolves #21554
2020-06-23 16:31:22 -04:00
Liam DeBeasi
88f23b1626 fix(textarea): add aria-labelledby to native textarea (#21606)
resolves #21600
2020-06-23 16:29:41 -04:00
Chris
a5b3750ee2 fix(angular): expose createAnimation in addition to AnimationController (#21616)
closes #21615
2020-06-23 16:23:22 -04:00
Liam DeBeasi
fbcd3f8c08 docs(select-option): clarify that disabled does not apply for action sheets (#21584)
resolves https://github.com/ionic-team/ionic/issues/21578
2020-06-19 13:45:49 -04:00
Liam DeBeasi
04ce642369 merge release-5.2.2
5.2.2
2020-06-17 12:46:28 -04:00
Liam DeBeasi
84c421a4b0 5.2.2 2020-06-17 12:03:58 -04:00
Liam DeBeasi
1decc13cb8 docs(modal): clarify backdrop usage for card modals (#21556) 2020-06-17 11:25:07 -04:00
Liam DeBeasi
17308f247f fix(segment): ensure checked classes get set after not having a value (#21547) 2020-06-16 11:22:17 -04:00
Liam DeBeasi
d8b377ffeb fix(input): add aria-label to clear button (#21538) 2020-06-15 13:36:11 -04:00
Liam DeBeasi
24cfdc308f fix(ios): respect toolbar opacity when doing nav transition (#21512) 2020-06-15 09:39:06 -04:00
Liam DeBeasi
bcccc217b8 fix(action-sheet, alert): resolve double click issue when running in iOS mode on chrome (#21506) 2020-06-12 10:36:09 -04:00
Liam DeBeasi
e968bd029a fix(angular): fix issue where navAnimation was being incorrectly overridden (#21508) 2020-06-11 13:30:58 -04:00
Masahiko Sakakibara
7c8f621536 chore(template): add v5.0 option to issue templates (#21498) 2020-06-11 11:21:18 -04:00
Liam DeBeasi
edceac0745 merge release-5.2.1
Release 5.2.1
2020-06-11 11:20:23 -04:00
Liam DeBeasi
2969f9f9f2 5.2.1 2020-06-11 10:43:55 -04:00
Liam DeBeasi
9223abc1f8 fix(angular): resolve issue when not using ngModel on components 2020-06-11 10:43:49 -04:00
Liam DeBeasi
b37c158eea merge release-5.2.0
Release 5.2.0
2020-06-10 13:26:16 -04:00
Liam DeBeasi
ce6448b068 chore(): add Silicon to version 5.2.0 2020-06-10 12:54:30 -04:00
Liam DeBeasi
fd65ceec84 5.2.0 2020-06-10 12:52:24 -04:00
Evgeniy
0bf9449ee1 fix(img): use setTimeout fallback on older versions of chrome (#21358) 2020-06-10 12:03:37 -04:00
Brandy Carney
cf3035778c chore(stencil): update to v1.14 (#21458) 2020-06-10 11:36:02 -04:00
Brandy Carney
17375d2325 feat(all): add shadow parts to missing components (#21436) 2020-06-10 09:58:33 -04:00
Stefanos Anagnostou
df408f91f1 feat(fab-button): add close icon font size css variable (#19628)
Co-authored-by: Brandy Carney <brandy@ionic.io>
2020-06-10 09:34:48 -04:00
Liam DeBeasi
e95b481a53 fix(angular): patch FormControl methods to properly sync Ionic form classes (#21429)
Co-authored-by: Mark Levy <MarkChrisLevy@users.noreply.github.com>
2020-06-09 11:54:40 -04:00
Stefanos Anagnostou
698e526b9f feat(fab-button): add closeIcon property (#19626) 2020-06-08 16:46:03 -04:00
Liam DeBeasi
f34d3752e3 feat(all): add support for configuring animations on a per-page basis (#21433) 2020-06-08 15:49:14 -04:00
Matthias Max
c8db0d5eeb fix(router): going back uses correct nav transition (#21301) 2020-06-08 12:23:27 -04:00
Liam DeBeasi
a4f0bdb4c3 fix(modal): set card-style modal height using the --height css variable (#21453) 2020-06-08 10:29:39 -04:00
410 changed files with 42503 additions and 1964 deletions

View File

@@ -19,7 +19,8 @@ assignees: ''
**Ionic version:**
<!-- (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1) -->
<!-- (For Ionic 2.x & 3.x issues, please use https://github.com/ionic-team/ionic-v3) -->
[x] **4.x**
[ ] **4.x**
[x] **5.x**
**Current behavior:**
<!-- Describe how the bug manifests. -->

1
.gitignore vendored
View File

@@ -64,3 +64,4 @@ core/loader/
core/www/
.stencil/
angular/build/
core/components/

View File

@@ -209,6 +209,13 @@ function prepareDevPackage(tasks, package, version) {
title: `${pkg.name}: npm link @ionic/core`,
task: () => execa('npm', ['link', '@ionic/core'], { cwd: projectRoot })
});
if (package === 'packages/react-router') {
projectTasks.push({
title: `${pkg.name}: npm link @ionic/react`,
task: () => execa('npm', ['link', '@ionic/react'], { cwd: projectRoot })
});
}
}
projectTasks.push({

View File

@@ -78,6 +78,9 @@ async function setPackageVersionChanges(packages, version) {
if (package !== 'core') {
const pkg = common.readPkg(package);
common.updateDependency(pkg, '@ionic/core', version);
if(package === 'packages/react-router') {
common.updateDependency(pkg, '@ionic/react', version);
}
common.writePkg(package, pkg);
}
const projectRoot = common.projectPath(package);

View File

@@ -132,7 +132,7 @@ async function publishGithub(version, gitTag, changelog, npmTag) {
await octokit.repos.createRelease({
owner: 'ionic-team',
repo: 'ionic',
repo: 'ionic-framework',
target_commitish: branch,
tag_name: gitTag,
name: version,

View File

@@ -1,3 +1,106 @@
# [5.3.0 Phosphorus](https://github.com/ionic-team/ionic/compare/v5.2.3...v5.3.0) (2020-07-23)
### Bug Fixes
* **angular:** per-page animations now work with swipe to go back ([#21706](https://github.com/ionic-team/ionic/issues/21706)) ([2664587](https://github.com/ionic-team/ionic/commit/2664587749e45100a04f70796733de162b26cdf7)), closes [#21692](https://github.com/ionic-team/ionic/issues/21692)
* **datetime:** remove unneeded combobox role ([#21708](https://github.com/ionic-team/ionic/issues/21708)) ([f00ad8a](https://github.com/ionic-team/ionic/commit/f00ad8a8357ccd7fe85631dad0c841f2d4c72487))
* **input:** clear button can now be tabbed to ([#21633](https://github.com/ionic-team/ionic/issues/21633)) ([1dcd9de](https://github.com/ionic-team/ionic/commit/1dcd9de50ae16bfa102e98120a022de5b0287baa))
* **ios:** improve scroll assist reliability on password inputs ([#21703](https://github.com/ionic-team/ionic/issues/21703)) ([3cbf9e7](https://github.com/ionic-team/ionic/commit/3cbf9e7c4c225d6b02237d8ea8f16fb924ba360e)), closes [#21688](https://github.com/ionic-team/ionic/issues/21688)
* **keyboard:** keyboard events now consistently fire on android ([#21741](https://github.com/ionic-team/ionic/issues/21741)) ([020f3cc](https://github.com/ionic-team/ionic/commit/020f3cc56cb6dac23dd8766a3802422500b510e2)), closes [#21734](https://github.com/ionic-team/ionic/issues/21734)
* **nav:** insertPages method correctly inserts multiple pages with props ([#21725](https://github.com/ionic-team/ionic/issues/21725)) ([eb592b8](https://github.com/ionic-team/ionic/commit/eb592b8917b8a7412d8c346f41b47d3b79002b95)), closes [#21724](https://github.com/ionic-team/ionic/issues/21724)
* **overlays:** trap focus inside overlay components except toast ([#21716](https://github.com/ionic-team/ionic/issues/21716)) ([fff4aec](https://github.com/ionic-team/ionic/commit/fff4aec6cfbd48566594a05f4af57dd0578977a8)), closes [#21647](https://github.com/ionic-team/ionic/issues/21647)
* **segment-button:** allow min-width to be overridden ([#21722](https://github.com/ionic-team/ionic/issues/21722)) ([88f1828](https://github.com/ionic-team/ionic/commit/88f1828bd8f6b9a1c1f3dcb220d93067bed7f404)), closes [#21105](https://github.com/ionic-team/ionic/issues/21105)
* **title:** allow overriding of large title transform-origin ([#21770](https://github.com/ionic-team/ionic/issues/21770)) ([dbe6853](https://github.com/ionic-team/ionic/commit/dbe6853884bd76c3d8e229cd58e1571d9b3a7249)), closes [#21761](https://github.com/ionic-team/ionic/issues/21761)
* **virtual-scroll:** properly calculate top offset when nested ([#21581](https://github.com/ionic-team/ionic/issues/21581)) ([d297ecb](https://github.com/ionic-team/ionic/commit/d297ecb87ad3e1c8f0988f0571a475081ce368f8))
### Features
* **card:** expose global card css variables ([#21756](https://github.com/ionic-team/ionic/issues/21756)) ([096eef4](https://github.com/ionic-team/ionic/commit/096eef4a79c2d05c37eb224466c6d7d512d2be20)), closes [#21694](https://github.com/ionic-team/ionic/issues/21694)
* **input:** accept datetime-local, month, and week type values ([#21758](https://github.com/ionic-team/ionic/issues/21758)) ([fa93dff](https://github.com/ionic-team/ionic/commit/fa93dffdb4f350e8db8acc7f06b06761974eea8e)), closes [#21757](https://github.com/ionic-team/ionic/issues/21757)
* **input, textarea:** expose native events for ionBlur and ionFocus ([#21777](https://github.com/ionic-team/ionic/issues/21777)) ([a625c83](https://github.com/ionic-team/ionic/commit/a625c837a60abc07ad71c696196a89f1a25a4c27)), closes [#17363](https://github.com/ionic-team/ionic/issues/17363)
* **react:** add custom history to IonReactRouter ([#21775](https://github.com/ionic-team/ionic/issues/21775)) ([d4a5fbd](https://github.com/ionic-team/ionic/commit/d4a5fbd955e8ecccba8b77491943d81fdf5a5ef4)), closes [#20297](https://github.com/ionic-team/ionic/issues/20297)
* **react:** add new react router ([#21693](https://github.com/ionic-team/ionic/issues/21693)) ([c171ccb](https://github.com/ionic-team/ionic/commit/c171ccbd37c1ee4b4934758a3a759170ff357cb2))
* **router:** add navigation hooks ([#21709](https://github.com/ionic-team/ionic/issues/21709)) ([77464ef](https://github.com/ionic-team/ionic/commit/77464ef21aaaa5afa7a02e5417f3ec295b240601))
* **segment-button, toast:** expose additional shadow parts ([#21532](https://github.com/ionic-team/ionic/issues/21532)) ([a5e4669](https://github.com/ionic-team/ionic/commit/a5e4669c4bcbcb2cdd605ed17c35e42438bd5596))
* **select:** add optional generic typings ([#21514](https://github.com/ionic-team/ionic/issues/21514)) ([7c2d0c9](https://github.com/ionic-team/ionic/commit/7c2d0c981ab91930478c4b76220ce4ec4ed4e471))
## [5.2.3](https://github.com/ionic-team/ionic/compare/v5.2.2...v5.2.3) (2020-07-01)
### Bug Fixes
* **angular:** expose createAnimation in addition to AnimationController ([#21616](https://github.com/ionic-team/ionic/issues/21616)) ([a5b3750](https://github.com/ionic-team/ionic/commit/a5b3750ee2a7c005f80f8453b03c67dd1a5622ca)), closes [#21615](https://github.com/ionic-team/ionic/issues/21615)
* **select:** change role to listbox ([#21609](https://github.com/ionic-team/ionic/issues/21609)) ([8c79e2c](https://github.com/ionic-team/ionic/commit/8c79e2c5b58ad562967f2d559c6b548e57536936))
* **slides:** enable keyboard integration ([#21608](https://github.com/ionic-team/ionic/issues/21608)) ([26674f1](https://github.com/ionic-team/ionic/commit/26674f1dfa8c9a28f5525f1b16070e8ec494c232)), closes [#21554](https://github.com/ionic-team/ionic/issues/21554)
* **textarea:** add aria-labelledby to native textarea ([#21606](https://github.com/ionic-team/ionic/issues/21606)) ([88f23b1](https://github.com/ionic-team/ionic/commit/88f23b1626eb400336f2f52a3e0d34ac3c161e64)), closes [#21600](https://github.com/ionic-team/ionic/issues/21600)
## [5.2.2](https://github.com/ionic-team/ionic/compare/v5.2.1...v5.2.2) (2020-06-17)
### Bug Fixes
* **action-sheet, alert:** resolve double click issue when running in iOS mode on chrome ([#21506](https://github.com/ionic-team/ionic/issues/21506)) ([bcccc21](https://github.com/ionic-team/ionic/commit/bcccc217b8833a284a1781e287db5e46043b3548)), closes [#21503](https://github.com/ionic-team/ionic/issues/21503)
* **angular:** fix issue where navAnimation was being incorrectly overridden ([#21508](https://github.com/ionic-team/ionic/issues/21508)) ([e968bd0](https://github.com/ionic-team/ionic/commit/e968bd029a4fb37b4001d96a490c6091a948785a)), closes [#21495](https://github.com/ionic-team/ionic/issues/21495)
* **input:** add aria-label to clear button ([#21538](https://github.com/ionic-team/ionic/issues/21538)) ([d8b377f](https://github.com/ionic-team/ionic/commit/d8b377ffeb88eaae23b33eadeae5c8e54e1bc77c)), closes [#21537](https://github.com/ionic-team/ionic/issues/21537)
* **ios:** respect toolbar opacity when doing nav transition ([#21512](https://github.com/ionic-team/ionic/issues/21512)) ([24cfdc3](https://github.com/ionic-team/ionic/commit/24cfdc308f63b7a55969ac58806eafd67116b017))
* **segment:** ensure checked classes get set after not having a value ([#21547](https://github.com/ionic-team/ionic/issues/21547)) ([17308f2](https://github.com/ionic-team/ionic/commit/17308f247f8750029ece39548c9f457e15326189)), closes [#21546](https://github.com/ionic-team/ionic/issues/21546)
## [5.2.1](https://github.com/ionic-team/ionic/compare/v5.2.0...v5.2.1) (2020-06-11)
### Bug Fixes
* **angular:** resolve error when not using ngModel on components ([4083e32](https://github.com/ionic-team/ionic/commit/4083e32e103db71f6db86ed1ecd398fda407c28b))
# [5.2.0 Silicon](https://github.com/ionic-team/ionic/compare/v5.1.1...v5.2.0) (2020-06-10)
### Bug Fixes
* **action-sheet, toast:** allow button handler to return `Promise<void>` ([#21259](https://github.com/ionic-team/ionic/issues/21259)) ([7703da2](https://github.com/ionic-team/ionic/commit/7703da28f8181b02390b97a7d4d02df99b2ad34c))
* **angular:** patch FormControl methods to properly sync Ionic form classes ([#21429](https://github.com/ionic-team/ionic/issues/21429)) ([e95b481](https://github.com/ionic-team/ionic/commit/e95b481a53191582bca635f322ad07eadbd62d64))
* **datetime:** ensure year-only values are not affected by timezone when parsing ([#21309](https://github.com/ionic-team/ionic/issues/21309)) ([3937101](https://github.com/ionic-team/ionic/commit/3937101e5c2b181a6b7926eb8386c22b0f887716))
* **header:** large title transition now works on older versions of iOS ([#21339](https://github.com/ionic-team/ionic/issues/21339)) ([2dac12c](https://github.com/ionic-team/ionic/commit/2dac12c577e0c7a5310857389dbda2b2b3dfadd1))
* **img:** use setTimeout fallback on older versions of chrome ([#21358](https://github.com/ionic-team/ionic/issues/21358)) ([0bf9449](https://github.com/ionic-team/ionic/commit/0bf9449ee1f9b2498e35f61511cb3e018814c6ca))
* **ios:** add haptic drag gesture for action sheet and alert components ([#21060](https://github.com/ionic-team/ionic/issues/21060)) ([33be1f0](https://github.com/ionic-team/ionic/commit/33be1f061ebbe27ee22e357c394f112af42ec360))
* **item:** inherit align-items from parent item ([#19278](https://github.com/ionic-team/ionic/issues/19278)) ([882f8fe](https://github.com/ionic-team/ionic/commit/882f8fef07dfb6ebda17ca09046d1af281075098)), closes [#18703](https://github.com/ionic-team/ionic/issues/18703)
* **item:** input-wrapper now inherits overflow ([#21282](https://github.com/ionic-team/ionic/issues/21282)) ([29d208d](https://github.com/ionic-team/ionic/commit/29d208de88f340e216e22badb6366bba4eda8bfb))
* **menu-button:** screen readers now properly announce menu button ([#21324](https://github.com/ionic-team/ionic/issues/21324)) ([eaf4fb6](https://github.com/ionic-team/ionic/commit/eaf4fb6b2a6d68f5c3d8d49ef41b4885f096070d))
* **modal:** card style modal no longer gets stuck when swiping quickly ([#21224](https://github.com/ionic-team/ionic/issues/21224)) ([448dfa0](https://github.com/ionic-team/ionic/commit/448dfa0a6955008ce4dc73354f5b8319ae1a1cc2))
* **modal:** set card-style modal height using the --height css variable ([#21453](https://github.com/ionic-team/ionic/issues/21453)) ([a4f0bdb](https://github.com/ionic-team/ionic/commit/a4f0bdb4c3ceeeaf902d520e9aa72e04a6ec2302))
* **reorder-group:** revert item to original position when passing false to complete ([#21396](https://github.com/ionic-team/ionic/issues/21396)) ([5f2001c](https://github.com/ionic-team/ionic/commit/5f2001c43c0846ec8973cbb8eb5662976ba7e31a)), closes [#19128](https://github.com/ionic-team/ionic/issues/19128)
* **router:** use correct nav transition when going back ([#21301](https://github.com/ionic-team/ionic/issues/21301)) ([c8db0d5](https://github.com/ionic-team/ionic/commit/c8db0d5eeba6f10d1492e95e6df6b4d871d43400))
* **scroll-assist:** improve scroll detection accuracy ([#21416](https://github.com/ionic-team/ionic/issues/21416)) ([137c49d](https://github.com/ionic-team/ionic/commit/137c49d70be45e1b0ee73d41fed6e9d69a2caa32))
* **slides:** update Swiper dependency to resolve error when doing SSR ([#21350](https://github.com/ionic-team/ionic/issues/21350)) ([3290604](https://github.com/ionic-team/ionic/commit/32906048a491961ec7340ba2e085807ea8a9c118))
* **textarea:** native textarea inherits max/min width and height ([#21333](https://github.com/ionic-team/ionic/issues/21333)) ([2377480](https://github.com/ionic-team/ionic/commit/237748049c7644ae8a7a74101ece5cfd7a160470))
### Features
* **alert:** add destructive role to alert buttons ([#21269](https://github.com/ionic-team/ionic/issues/21269)) ([e53f024](https://github.com/ionic-team/ionic/commit/e53f0241e2bf91461c55097fde271ae85ca644ea))
* **alert:** add support for custom input attributes ([#21365](https://github.com/ionic-team/ionic/issues/21365)) ([1ed8169](https://github.com/ionic-team/ionic/commit/1ed81693f225b6801a0897ef1a8314999c122484))
* **all:** add all autocomplete values to input and searchbar ([#21297](https://github.com/ionic-team/ionic/issues/21297)) ([4fd7c0c](https://github.com/ionic-team/ionic/commit/4fd7c0cc5a6c97100fa380e4ff1be0bb84c7006b))
* **all:** add optional generic typings for overlay component methods ([#21393](https://github.com/ionic-team/ionic/issues/21393)) ([5bf83b8](https://github.com/ionic-team/ionic/commit/5bf83b80d7d2749719dd1a280ae8e205fbc4b2a9))
* **all:** add shadow parts to missing components ([#21436](https://github.com/ionic-team/ionic/issues/21436)) ([17375d2](https://github.com/ionic-team/ionic/commit/17375d232500b47ef1cacd7c028c38990d307984))
* **all:** add support for configuring animations on a per-page basis ([#21433](https://github.com/ionic-team/ionic/issues/21433)) ([f34d375](https://github.com/ionic-team/ionic/commit/f34d3752e3462c9d81487fc86af5ec185cc3d1e3))
* **angular:** expose activatedView ([#21302](https://github.com/ionic-team/ionic/issues/21302)) ([829a0d9](https://github.com/ionic-team/ionic/commit/829a0d9be5a20c5fc96b5c5f18fedc4eb5e5b9ec))
* **angular:** expose getPlatforms and isPlatform ([#21308](https://github.com/ionic-team/ionic/issues/21308)) ([4af54a2](https://github.com/ionic-team/ionic/commit/4af54a2fea5d9cef843b1ebce849fb4a5c206f0b))
* **angular:** add strongly typed Ionic lifecycle hooks ([#18044](https://github.com/ionic-team/ionic/issues/18044)) ([53fc8e3](https://github.com/ionic-team/ionic/commit/53fc8e37c89cea793d0e00246d52805166730108)), closes [#18043](https://github.com/ionic-team/ionic/issues/18043)
* **fab-button:** add close icon font size css variable ([#19628](https://github.com/ionic-team/ionic/issues/19628)) ([df408f9](https://github.com/ionic-team/ionic/commit/df408f91f1aef903adaa5e635fef9bc7542e8739))
* **fab-button:** add closeIcon property ([#19626](https://github.com/ionic-team/ionic/issues/19626)) ([698e526](https://github.com/ionic-team/ionic/commit/698e526b9f882e98efc4bf160999182c645b772c))
* **select-option:** pass class from the option to the interface for individual styling ([#21304](https://github.com/ionic-team/ionic/issues/21304)) ([5285824](https://github.com/ionic-team/ionic/commit/5285824da5258ea638420fc60c50e0a19952f89c))
## [5.1.1](https://github.com/ionic-team/ionic/compare/v5.1.0...v5.1.1) (2020-05-13)

View File

@@ -1,11 +1,129 @@
# Contributor Code of Conduct
As contributors and maintainers of the Ionic project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
# Contributor Covenant Code of Conduct
Communication through any of Ionic's channels (GitHub, Slack, Forum, IRC, mailing lists, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
## Our Pledge
We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the Ionic project to do the same.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
If any member of the community violates this code of conduct, the maintainers of the Ionic project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
If you are subject to or witness unacceptable behavior, or have any other concerns, please email us at [hi@ionicframework.com](mailto:hi@ionicframework.com).
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[contact@ionic.io](mailto:contact@ionic.io).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,9 +1,9 @@
# Ionic
# Ionic Framework
[Ionic](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to
[Ionic Framework](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to
build top quality native and progressive web apps with web technologies.
Ionic is based on [Web Components](https://www.webcomponents.org/introduction) and comes with many significant performance, usability, and feature improvements over the past versions.
Ionic Framework is based on [Web Components](https://www.webcomponents.org/introduction) and comes with many significant performance, usability, and feature improvements over the past versions.
### Packages
@@ -31,6 +31,8 @@ Thanks for your interest in contributing! Read up on our guidelines for
and then look through our issues with a [help wanted](https://github.com/ionic-team/ionic/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
label.
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/ionic-team/ionic/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
### Examples
@@ -40,7 +42,7 @@ It is the perfect starting point for learning and building your own app.
### Future Goals
As Ionic components migrate to the web component standard, a goal of ours is to have Ionic components easily work within all of the popular frameworks.
As Ionic Framework components migrate to the web component standard, a goal of ours is to have Ionic Framework easily work within all of the popular frameworks.
### Earlier Versions

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "5.1.1",
"version": "5.3.0",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -42,7 +42,7 @@
"validate": "npm i && npm run lint && npm run test && npm run build"
},
"dependencies": {
"@ionic/core": "5.1.1",
"@ionic/core": "5.3.0",
"tslib": "^1.9.3"
},
"peerDependencies": {

View File

@@ -1,4 +1,4 @@
import resolve from 'rollup-plugin-node-resolve';
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'build/es2015/core.js',

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor, setIonicClasses } from './value-accessor';
@@ -16,8 +16,8 @@ import { ValueAccessor, setIonicClasses } from './value-accessor';
})
export class BooleanValueAccessor extends ValueAccessor {
constructor(el: ElementRef) {
super(el);
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
writeValue(value: any) {

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
})
export class NumericValueAccessor extends ValueAccessor {
constructor(el: ElementRef) {
super(el);
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
})
export class RadioValueAccessor extends ValueAccessor {
constructor(el: ElementRef) {
super(el);
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionSelect', ['$event.target'])

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
})
export class SelectValueAccessor extends ValueAccessor {
constructor(el: ElementRef) {
super(el);
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
})
export class TextValueAccessor extends ValueAccessor {
constructor(el: ElementRef) {
super(el);
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])

View File

@@ -1,15 +1,17 @@
import { ElementRef, HostListener } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { AfterViewInit, ElementRef, HostListener, Injector, OnDestroy, Type } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import { raf } from '../../util/util';
export class ValueAccessor implements ControlValueAccessor {
export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDestroy {
private onChange: (value: any) => void = () => {/**/};
private onTouched: () => void = () => {/**/};
protected lastValue: any;
private statusChanges?: Subscription;
constructor(protected el: ElementRef) {}
constructor(protected injector: Injector, protected el: ElementRef) {}
writeValue(value: any) {
/**
@@ -52,6 +54,50 @@ export class ValueAccessor implements ControlValueAccessor {
setDisabledState(isDisabled: boolean) {
this.el.nativeElement.disabled = isDisabled;
}
ngOnDestroy() {
if (this.statusChanges) {
this.statusChanges.unsubscribe();
}
}
ngAfterViewInit() {
let ngControl;
try {
ngControl = this.injector.get<NgControl>(NgControl as Type<NgControl>);
} catch { /* No FormControl or ngModel binding */ }
if (!ngControl) { return; }
// Listen for changes in validity, disabled, or pending states
if (ngControl.statusChanges) {
this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.el));
}
/**
* TODO Remove this in favor of https://github.com/angular/angular/issues/10887
* whenever it is implemented. Currently, Ionic's form status classes
* do not react to changes when developers manually call
* Angular form control methods such as markAsTouched.
* This results in Ionic's form status classes being out
* of sync with the ng form status classes.
* This patches the methods to manually sync
* the classes until this feature is implemented in Angular.
*/
const formControl = ngControl.control;
if (formControl) {
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
methodsToPatch.forEach(method => {
if (formControl[method]) {
const oldFn = formControl[method].bind(formControl);
formControl[method] = (...params) => {
oldFn(...params);
setIonicClasses(this.el);
};
}
});
}
}
}
export const setIonicClasses = (element: ElementRef) => {

View File

@@ -1,4 +1,5 @@
import { Directive, HostListener, Optional } from '@angular/core';
import { AnimationBuilder } from '@ionic/core';
import { Config } from '../../providers/config';
import { NavController } from '../../providers/nav-controller';
@@ -7,11 +8,12 @@ import { IonRouterOutlet } from './ion-router-outlet';
@Directive({
selector: 'ion-back-button',
inputs: ['defaultHref'],
inputs: ['defaultHref', 'routerAnimation'],
})
export class IonBackButtonDelegate {
defaultHref: string | undefined | null;
routerAnimation?: AnimationBuilder;
constructor(
@Optional() private routerOutlet: IonRouterOutlet,
@@ -27,10 +29,11 @@ export class IonBackButtonDelegate {
const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref');
if (this.routerOutlet && this.routerOutlet.canGoBack()) {
this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation);
this.routerOutlet.pop();
ev.preventDefault();
} else if (defaultHref != null) {
this.navCtrl.navigateBack(defaultHref);
this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation });
ev.preventDefault();
}
}

View File

@@ -1,20 +1,21 @@
import { LocationStrategy } from '@angular/common';
import { Directive, ElementRef, HostListener, Optional } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import { RouterDirection } from '@ionic/core';
import { AnimationBuilder, RouterDirection } from '@ionic/core';
import { Subscription } from 'rxjs';
import { NavController } from '../../providers/nav-controller';
@Directive({
selector: '[routerLink]',
inputs: ['routerDirection']
inputs: ['routerDirection', 'routerAnimation']
})
export class RouterLinkDelegate {
private subscription?: Subscription;
routerDirection: RouterDirection = 'forward';
routerAnimation?: AnimationBuilder;
constructor(
private locationStrategy: LocationStrategy,
@@ -50,7 +51,7 @@ export class RouterLinkDelegate {
*/
@HostListener('click', ['$event'])
onClick(ev: UIEvent) {
this.navCtrl.setDirection(this.routerDirection);
this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
ev.preventDefault();
}
}

View File

@@ -1,7 +1,7 @@
import { Location } from '@angular/common';
import { ComponentRef, NgZone } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterDirection } from '@ionic/core';
import { AnimationBuilder, RouterDirection } from '@ionic/core';
import { bindLifecycleEvents } from '../../providers/angular-delegate';
import { NavController } from '../../providers/nav-controller';
@@ -52,7 +52,8 @@ export class StackController {
}
setActive(enteringView: RouteView): Promise<StackEvent> {
let { direction, animation } = this.navCtrl.consumeTransition();
const consumeResult = this.navCtrl.consumeTransition();
let { direction, animation, animationBuilder } = consumeResult;
const leavingView = this.activeView;
const tabSwitch = isTabSwitch(enteringView, leavingView);
if (tabSwitch) {
@@ -105,6 +106,31 @@ export class StackController {
enteringView.ref.changeDetectorRef.detectChanges();
}
/**
* If we are going back from a page that
* was presented using a custom animation
* we should default to using that
* unless the developer explicitly
* provided another animation.
*/
const customAnimation = enteringView.animationBuilder;
if (
animationBuilder === undefined &&
direction === 'back' &&
!tabSwitch &&
customAnimation !== undefined
) {
animationBuilder = customAnimation;
}
/**
* Save any custom animation so that navigating
* back will use this custom animation by default.
*/
if (leavingView) {
leavingView.animationBuilder = animationBuilder;
}
// Wait until previous transitions finish
return this.zone.runOutsideAngular(() => {
return this.wait(() => {
@@ -116,7 +142,7 @@ export class StackController {
// In case the enteringView is the same as the leavingPage we need to reattach()
enteringView.ref.changeDetectorRef.reattach();
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
.then(() => ({
enteringView,
@@ -154,8 +180,8 @@ export class StackController {
url = primaryOutlet.route._routerState.snapshot.url;
}
}
return this.navCtrl.navigateBack(url, view.savedExtras).then(() => true);
const { animationBuilder } = this.navCtrl.consumeTransition();
return this.navCtrl.navigateBack(url, { ...view.savedExtras, animation: animationBuilder }).then(() => true);
});
}
@@ -164,13 +190,16 @@ export class StackController {
if (leavingView) {
const views = this.getStack(leavingView.stackId);
const enteringView = views[views.length - 2];
const customAnimation = enteringView.animationBuilder;
return this.wait(() => {
return this.transition(
enteringView, // entering view
leavingView, // leaving view
'back',
this.canGoBack(2),
true
true,
customAnimation
);
});
}
@@ -225,7 +254,8 @@ export class StackController {
leavingView: RouteView | undefined,
direction: 'forward' | 'back' | undefined,
showGoBack: boolean,
progressAnimation: boolean
progressAnimation: boolean,
animationBuilder?: AnimationBuilder
) {
if (this.skipTransition) {
this.skipTransition = false;
@@ -250,7 +280,8 @@ export class StackController {
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
progressAnimation,
animationBuilder
});
}
}

View File

@@ -1,6 +1,6 @@
import { ComponentRef } from '@angular/core';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { NavDirection, RouterDirection } from '@ionic/core';
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection) => {
if (direction === 'root') {
@@ -96,4 +96,5 @@ export interface RouteView {
savedData?: any;
savedExtras?: NavigationExtras;
unlistenEvents: () => void;
animationBuilder?: AnimationBuilder;
}

View File

@@ -26,8 +26,8 @@ export class IonAvatar {
}
export declare interface IonBackButton extends Components.IonBackButton {
}
@ProxyCmp({ inputs: ["color", "defaultHref", "disabled", "icon", "mode", "text", "type"] })
@Component({ selector: "ion-back-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "defaultHref", "disabled", "icon", "mode", "text", "type"] })
@ProxyCmp({ inputs: ["color", "defaultHref", "disabled", "icon", "mode", "routerAnimation", "text", "type"] })
@Component({ selector: "ion-back-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "defaultHref", "disabled", "icon", "mode", "routerAnimation", "text", "type"] })
export class IonBackButton {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -61,8 +61,8 @@ export class IonBadge {
}
export declare interface IonButton extends Components.IonButton {
}
@ProxyCmp({ inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerDirection", "shape", "size", "strong", "target", "type"] })
@Component({ selector: "ion-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerDirection", "shape", "size", "strong", "target", "type"] })
@ProxyCmp({ inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] })
@Component({ selector: "ion-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] })
export class IonButton {
ionFocus!: EventEmitter<CustomEvent>;
ionBlur!: EventEmitter<CustomEvent>;
@@ -86,8 +86,8 @@ export class IonButtons {
}
export declare interface IonCard extends Components.IonCard {
}
@ProxyCmp({ inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerDirection", "target", "type"] })
@Component({ selector: "ion-card", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerDirection", "target", "type"] })
@ProxyCmp({ inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] })
@Component({ selector: "ion-card", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] })
export class IonCard {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -220,8 +220,8 @@ export class IonFab {
}
export declare interface IonFabButton extends Components.IonFabButton {
}
@ProxyCmp({ inputs: ["activated", "color", "disabled", "download", "href", "mode", "rel", "routerDirection", "show", "size", "target", "translucent", "type"] })
@Component({ selector: "ion-fab-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["activated", "color", "disabled", "download", "href", "mode", "rel", "routerDirection", "show", "size", "target", "translucent", "type"] })
@ProxyCmp({ inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] })
@Component({ selector: "ion-fab-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] })
export class IonFabButton {
ionFocus!: EventEmitter<CustomEvent>;
ionBlur!: EventEmitter<CustomEvent>;
@@ -344,8 +344,8 @@ export class IonInput {
}
export declare interface IonItem extends Components.IonItem {
}
@ProxyCmp({ inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerDirection", "target", "type"] })
@Component({ selector: "ion-item", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerDirection", "target", "type"] })
@ProxyCmp({ inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] })
@Component({ selector: "ion-item", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] })
export class IonItem {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -462,8 +462,8 @@ export class IonMenu {
}
export declare interface IonMenuButton extends Components.IonMenuButton {
}
@ProxyCmp({ inputs: ["autoHide", "color", "disabled", "menu", "type"] })
@Component({ selector: "ion-menu-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["autoHide", "color", "disabled", "menu", "type"] })
@ProxyCmp({ inputs: ["autoHide", "color", "disabled", "menu", "mode", "type"] })
@Component({ selector: "ion-menu-button", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["autoHide", "color", "disabled", "menu", "mode", "type"] })
export class IonMenuButton {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
@@ -498,8 +498,8 @@ export class IonNav {
}
export declare interface IonNavLink extends Components.IonNavLink {
}
@ProxyCmp({ inputs: ["component", "componentProps", "routerDirection"] })
@Component({ selector: "ion-nav-link", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["component", "componentProps", "routerDirection"] })
@ProxyCmp({ inputs: ["component", "componentProps", "routerAnimation", "routerDirection"] })
@Component({ selector: "ion-nav-link", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["component", "componentProps", "routerAnimation", "routerDirection"] })
export class IonNavLink {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {

View File

@@ -43,7 +43,7 @@ export * from './types/ionic-lifecycle-hooks';
export { IonicModule } from './ionic-module';
// UTILS
export { IonicSafeString, getPlatforms, isPlatform } from '@ionic/core';
export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core';
// CORE TYPES
export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation } from '@ionic/core';
export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation, NavComponentWithProps } from '@ionic/core';

View File

@@ -1,7 +1,7 @@
import { Location } from '@angular/common';
import { Injectable, Optional } from '@angular/core';
import { NavigationExtras, NavigationStart, Router, UrlSerializer, UrlTree } from '@angular/router';
import { NavDirection, RouterDirection } from '@ionic/core';
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet';
@@ -9,6 +9,7 @@ import { Platform } from './platform';
export interface AnimationOptions {
animated?: boolean;
animation?: AnimationBuilder;
animationDirection?: 'forward' | 'back';
}
@@ -22,6 +23,7 @@ export class NavController {
private topOutlet?: IonRouterOutlet;
private direction: 'forward' | 'back' | 'root' | 'auto' = DEFAULT_DIRECTION;
private animated?: NavDirection = DEFAULT_ANIMATED;
private animationBuilder?: AnimationBuilder;
private guessDirection: RouterDirection = 'forward';
private guessAnimation?: NavDirection;
private lastNavId = -1;
@@ -65,7 +67,7 @@ export class NavController {
* ```
*/
navigateForward(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
this.setDirection('forward', options.animated, options.animationDirection);
this.setDirection('forward', options.animated, options.animationDirection, options.animation);
return this.navigate(url, options);
}
@@ -88,7 +90,7 @@ export class NavController {
* ```
*/
navigateBack(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
this.setDirection('back', options.animated, options.animationDirection);
this.setDirection('back', options.animated, options.animationDirection, options.animation);
return this.navigate(url, options);
}
@@ -111,7 +113,7 @@ export class NavController {
* ```
*/
navigateRoot(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
this.setDirection('root', options.animated, options.animationDirection);
this.setDirection('root', options.animated, options.animationDirection, options.animation);
return this.navigate(url, options);
}
@@ -121,7 +123,7 @@ export class NavController {
* by default.
*/
back(options: AnimationOptions = { animated: true, animationDirection: 'back' }) {
this.setDirection('back', options.animated, options.animationDirection);
this.setDirection('back', options.animated, options.animationDirection, options.animation);
return this.location.back();
}
@@ -150,9 +152,10 @@ export class NavController {
*
* It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
*/
setDirection(direction: RouterDirection, animated?: boolean, animationDirection?: 'forward' | 'back') {
setDirection(direction: RouterDirection, animated?: boolean, animationDirection?: 'forward' | 'back', animationBuilder?: AnimationBuilder) {
this.direction = direction;
this.animated = getAnimation(direction, animated, animationDirection);
this.animationBuilder = animationBuilder;
}
/**
@@ -168,6 +171,7 @@ export class NavController {
consumeTransition() {
let direction: RouterDirection = 'root';
let animation: NavDirection | undefined;
const animationBuilder = this.animationBuilder;
if (this.direction === 'auto') {
direction = this.guessDirection;
@@ -178,10 +182,12 @@ export class NavController {
}
this.direction = DEFAULT_DIRECTION;
this.animated = DEFAULT_ANIMATED;
this.animationBuilder = undefined;
return {
direction,
animation
animation,
animationBuilder
};
}

View File

@@ -1,5 +1,5 @@
import { browser, element, by } from 'protractor';
import { handleErrorMessages, setProperty, getText, waitTime } from './utils';
import { handleErrorMessages, getProperty, setProperty, getText, waitTime } from './utils';
describe('form', () => {
@@ -7,6 +7,20 @@ describe('form', () => {
return handleErrorMessages();
});
describe('status updates', () => {
beforeEach(async () => {
await browser.get('/form');
await waitTime(30);
});
it('should update Ionic form classes when calling form methods programatically', async () => {
await element(by.css('form #input-touched')).click();
await waitTime(100);
const classList = (await getProperty('#touched-input-test', 'classList')) as string[];
expect(classList.includes('ion-touched')).toEqual(true);
});
});
describe('change', () => {
beforeEach(async () => {
await browser.get('/form');

View File

@@ -1,5 +1,8 @@
<ion-header>
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>
Alert test
</ion-title>

View File

@@ -35,9 +35,11 @@
<ion-item>
<ion-label>Input (required)</ion-label>
<ion-input formControlName="input" class="required"></ion-input>
<ion-input formControlName="input" class="required" id="touched-input-test"></ion-input>
</ion-item>
<ion-button id="input-touched" (click)="setTouched()">Set Input Touched</ion-button>
<ion-item>
<ion-label>Input</ion-label>
<ion-input formControlName="input2"></ion-input>

View File

@@ -25,6 +25,11 @@ export class FormComponent {
});
}
setTouched() {
const formControl = this.profileForm.get('input');
formControl.markAsTouched();
}
onSubmit(_ev) {
this.submitted = 'true';
}

View File

@@ -7,7 +7,7 @@
</ion-header>
<ion-content>
<ion-list>
<ion-item routerLink="/alerts">
<ion-item routerLink="/alerts" [routerAnimation]="routerAnimation">
<ion-label>
Alerts test
</ion-label>

View File

@@ -1,9 +1,25 @@
import { Component } from '@angular/core';
import { AnimationBuilder, AnimationController } from '@ionic/angular';
@Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
})
export class HomePageComponent {
routerAnimation: AnimationBuilder = (_, opts) => {
const { direction, enteringEl, leavingEl } = opts;
const animation = this.animationCtrl.create().duration(500).easing('ease-out');
const enteringAnimation = this.animationCtrl.create().addElement(enteringEl).beforeRemoveClass(['ion-page-invisible']);
const leavingAnimation = this.animationCtrl.create().addElement(leavingEl).beforeRemoveClass(['ion-page-invisible']);
if (direction === 'back') {
enteringAnimation.fromTo('transform', 'translateX(-100%)', 'translateX(0%)');
leavingAnimation.fromTo('transform', 'translateX(0%)', 'translateX(100%)');
} else {
enteringAnimation.fromTo('transform', 'translateX(100%)', 'translateX(0%)');
leavingAnimation.fromTo('transform', 'translateX(0%)', 'translateX(-100%)');
}
return animation.addAnimation([enteringAnimation, leavingAnimation]);
};
constructor(private animationCtrl: AnimationController) {}
}

View File

@@ -1 +0,0 @@
package-lock=false

View File

@@ -1,121 +0,0 @@
# Contribution guide
## Contribute to Ionic Core
Ionic Core is the fundation of Ionic v4. It's based in [Stencil](https://stenciljs.com) and consist in a set of web components a long of JS and CSS utils.
### Installing dependencies
Before you can build ionic, we assume the following list of software is already installed in your system
- Git
- Node 8 or higher
- Npm 6.0 or higher
### Fork repository
In order to contributo to Ionic, you must have a github account so you can push code and create a new Pull Request (PR).
Once you are all setup, following the Github's guide of how to fork a repository: https://guides.github.com/activities/forking/
### Build Ionic Core
```bash
# Clone your Github repository:
git clone https://github.com/<github username>/ionic.git
# Go to the core directory:
cd ionic/core
# Install npm dependencies
npm install
# Run Ionic dev server
npm start
```
### Development Workflow
#### 1. Run Dev Server
```bash
# Move to the core folder
cd core
# Run dev server
npm start
```
You should be able to navigate to `http://localhost:3333` which will look like a file browser.
E2E tests are located inside the `src/components` folder, in the following way: `http://localhost:3333/src/components/{COMPONENT}/test/`
**Path examples:**
- ActionSheet basic test: http://localhost:3333/src/components/action-sheet/test/basic
- Nav basic test: http://localhost:3333/src/components/nav/test/basic
- Button basic test:
http://localhost:3333/src/components/button/test/basic
**IMPORTANT**
Leave the dev server running in the background while you make changes. The dev server listen for changes and automatically recompile Ionic for you.
#### 2. Open `core` folder in your IDE
Components implementations live inside the `core/src/components` folder.
You can find each ionic component inside their directory.
#### 3. Run test suite
Before commiting your changes make sure tests are passing:
```
npm run validate
```
#### 4. Create a branch and commit
```bash
# Create a git branch
git checkout -b my-improvement
# Add changes
git add .
# Create commit
git commit -m "fix(component): message"
```
Create a PR:
https://guides.github.com/activities/forking/
### Summary
```bash
# Clone repo
git clone git@github.com:ionic-team/ionic.git
# Move to ionic/core folder
cd ionic/core
# Install npm dependencies
npm i
# Run dev server
npm start
# Run test suite
npm run validate
```

View File

@@ -85,6 +85,7 @@ ion-back-button,prop,defaultHref,string | undefined,undefined,false,false
ion-back-button,prop,disabled,boolean,false,false,true
ion-back-button,prop,icon,null | string | undefined,undefined,false,false
ion-back-button,prop,mode,"ios" | "md",undefined,false,false
ion-back-button,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-back-button,prop,text,null | string | undefined,undefined,false,false
ion-back-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-back-button,css-prop,--background
@@ -119,6 +120,9 @@ ion-back-button,css-prop,--padding-start
ion-back-button,css-prop,--padding-top
ion-back-button,css-prop,--ripple-color
ion-back-button,css-prop,--transition
ion-back-button,part,icon
ion-back-button,part,native
ion-back-button,part,text
ion-backdrop,shadow
ion-backdrop,prop,stopPropagation,boolean,true,false,false
@@ -146,6 +150,7 @@ ion-button,prop,fill,"clear" | "default" | "outline" | "solid" | undefined,undef
ion-button,prop,href,string | undefined,undefined,false,false
ion-button,prop,mode,"ios" | "md",undefined,false,false
ion-button,prop,rel,string | undefined,undefined,false,false
ion-button,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-button,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-button,prop,shape,"round" | undefined,undefined,false,true
ion-button,prop,size,"default" | "large" | "small" | undefined,undefined,false,true
@@ -177,6 +182,7 @@ ion-button,css-prop,--padding-start
ion-button,css-prop,--padding-top
ion-button,css-prop,--ripple-color
ion-button,css-prop,--transition
ion-button,part,native
ion-buttons,scoped
ion-buttons,prop,collapse,boolean,false,false,false
@@ -189,11 +195,13 @@ ion-card,prop,download,string | undefined,undefined,false,false
ion-card,prop,href,string | undefined,undefined,false,false
ion-card,prop,mode,"ios" | "md",undefined,false,false
ion-card,prop,rel,string | undefined,undefined,false,false
ion-card,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-card,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-card,prop,target,string | undefined,undefined,false,false
ion-card,prop,type,"button" | "reset" | "submit",'button',false,false
ion-card,css-prop,--background
ion-card,css-prop,--color
ion-card,part,native
ion-card-content,none
ion-card-content,prop,mode,"ios" | "md",undefined,false,false
@@ -288,7 +296,7 @@ ion-content,prop,scrollY,boolean,true,false,false
ion-content,method,getScrollElement,getScrollElement() => Promise<HTMLElement>
ion-content,method,scrollByPoint,scrollByPoint(x: number, y: number, duration: number) => Promise<void>
ion-content,method,scrollToBottom,scrollToBottom(duration?: number) => Promise<void>
ion-content,method,scrollToPoint,scrollToPoint(x: number | null | undefined, y: number | null | undefined, duration?: number) => Promise<void>
ion-content,method,scrollToPoint,scrollToPoint(x: number | undefined | null, y: number | undefined | null, duration?: number) => Promise<void>
ion-content,method,scrollToTop,scrollToTop(duration?: number) => Promise<void>
ion-content,event,ionScroll,ScrollDetail,true
ion-content,event,ionScrollEnd,ScrollBaseDetail,true
@@ -351,12 +359,14 @@ ion-fab,method,close,close() => Promise<void>
ion-fab-button,shadow
ion-fab-button,prop,activated,boolean,false,false,false
ion-fab-button,prop,closeIcon,string,'close',false,false
ion-fab-button,prop,color,string | undefined,undefined,false,false
ion-fab-button,prop,disabled,boolean,false,false,false
ion-fab-button,prop,download,string | undefined,undefined,false,false
ion-fab-button,prop,href,string | undefined,undefined,false,false
ion-fab-button,prop,mode,"ios" | "md",undefined,false,false
ion-fab-button,prop,rel,string | undefined,undefined,false,false
ion-fab-button,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-fab-button,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-fab-button,prop,show,boolean,false,false,false
ion-fab-button,prop,size,"small" | undefined,undefined,false,false
@@ -377,6 +387,7 @@ ion-fab-button,css-prop,--border-radius
ion-fab-button,css-prop,--border-style
ion-fab-button,css-prop,--border-width
ion-fab-button,css-prop,--box-shadow
ion-fab-button,css-prop,--close-icon-font-size
ion-fab-button,css-prop,--color
ion-fab-button,css-prop,--color-activated
ion-fab-button,css-prop,--color-focused
@@ -387,6 +398,8 @@ ion-fab-button,css-prop,--padding-start
ion-fab-button,css-prop,--padding-top
ion-fab-button,css-prop,--ripple-color
ion-fab-button,css-prop,--transition
ion-fab-button,part,close-icon
ion-fab-button,part,native
ion-fab-list,shadow
ion-fab-list,prop,activated,boolean,false,false,false
@@ -462,13 +475,13 @@ ion-input,prop,required,boolean,false,false,false
ion-input,prop,size,number | undefined,undefined,false,false
ion-input,prop,spellcheck,boolean,false,false,false
ion-input,prop,step,string | undefined,undefined,false,false
ion-input,prop,type,"date" | "email" | "number" | "password" | "search" | "tel" | "text" | "time" | "url",'text',false,false
ion-input,prop,type,"date" | "datetime-local" | "email" | "month" | "number" | "password" | "search" | "tel" | "text" | "time" | "url" | "week",'text',false,false
ion-input,prop,value,null | number | string | undefined,'',false,false
ion-input,method,getInputElement,getInputElement() => Promise<HTMLInputElement>
ion-input,method,setFocus,setFocus() => Promise<void>
ion-input,event,ionBlur,void,true
ion-input,event,ionBlur,FocusEvent,true
ion-input,event,ionChange,InputChangeEventDetail,true
ion-input,event,ionFocus,void,true
ion-input,event,ionFocus,FocusEvent,true
ion-input,event,ionInput,KeyboardEvent,true
ion-input,css-prop,--background
ion-input,css-prop,--color
@@ -492,6 +505,7 @@ ion-item,prop,href,string | undefined,undefined,false,false
ion-item,prop,lines,"full" | "inset" | "none" | undefined,undefined,false,false
ion-item,prop,mode,"ios" | "md",undefined,false,false
ion-item,prop,rel,string | undefined,undefined,false,false
ion-item,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-item,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-item,prop,target,string | undefined,undefined,false,false
ion-item,prop,type,"button" | "reset" | "submit",'button',false,false
@@ -531,6 +545,7 @@ ion-item,css-prop,--padding-top
ion-item,css-prop,--ripple-color
ion-item,css-prop,--transition
ion-item,part,detail-icon
ion-item,part,native
ion-item-divider,shadow
ion-item-divider,prop,color,string | undefined,undefined,false,false
@@ -561,6 +576,7 @@ ion-item-option,prop,target,string | undefined,undefined,false,false
ion-item-option,prop,type,"button" | "reset" | "submit",'button',false,false
ion-item-option,css-prop,--background
ion-item-option,css-prop,--color
ion-item-option,part,native
ion-item-options,none
ion-item-options,prop,side,"end" | "start",'end',false,false
@@ -572,7 +588,7 @@ ion-item-sliding,method,close,close() => Promise<void>
ion-item-sliding,method,closeOpened,closeOpened() => Promise<boolean>
ion-item-sliding,method,getOpenAmount,getOpenAmount() => Promise<number>
ion-item-sliding,method,getSlidingRatio,getSlidingRatio() => Promise<number>
ion-item-sliding,method,open,open(side: "start" | "end" | undefined) => Promise<void>
ion-item-sliding,method,open,open(side: Side | undefined) => Promise<void>
ion-item-sliding,event,ionDrag,any,true
ion-label,scoped
@@ -662,6 +678,7 @@ ion-menu-button,prop,autoHide,boolean,true,false,false
ion-menu-button,prop,color,string | undefined,undefined,false,false
ion-menu-button,prop,disabled,boolean,false,false,false
ion-menu-button,prop,menu,string | undefined,undefined,false,false
ion-menu-button,prop,mode,"ios" | "md",undefined,false,false
ion-menu-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-menu-button,css-prop,--background
ion-menu-button,css-prop,--background-focused
@@ -676,6 +693,8 @@ ion-menu-button,css-prop,--padding-bottom
ion-menu-button,css-prop,--padding-end
ion-menu-button,css-prop,--padding-start
ion-menu-button,css-prop,--padding-top
ion-menu-button,part,icon
ion-menu-button,part,native
ion-menu-toggle,shadow
ion-menu-toggle,prop,autoHide,boolean,true,false,false
@@ -726,13 +745,13 @@ ion-nav,method,getActive,getActive() => Promise<ViewController | undefined>
ion-nav,method,getByIndex,getByIndex(index: number) => Promise<ViewController | undefined>
ion-nav,method,getPrevious,getPrevious(view?: ViewController | undefined) => Promise<ViewController | undefined>
ion-nav,method,insert,insert<T extends NavComponent>(insertIndex: number, component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,pop,pop(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,popTo,popTo(indexOrViewCtrl: number | ViewController, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,popToRoot,popToRoot(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,push,push<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,removeIndex,removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,setPages,setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,setPages,setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,setRoot,setRoot<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,event,ionNavDidChange,void,false
ion-nav,event,ionNavWillChange,void,false
@@ -740,6 +759,7 @@ ion-nav,event,ionNavWillChange,void,false
ion-nav-link,none
ion-nav-link,prop,component,Function | HTMLElement | ViewController | null | string | undefined,undefined,false,false
ion-nav-link,prop,componentProps,undefined | { [key: string]: any; },undefined,false,false
ion-nav-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-nav-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-note,shadow
@@ -913,6 +933,8 @@ ion-ripple-effect,prop,type,"bounded" | "unbounded",'bounded',false,false
ion-ripple-effect,method,addRipple,addRipple(x: number, y: number) => Promise<() => void>
ion-route,none
ion-route,prop,beforeEnter,(() => boolean | NavigationHookOptions | Promise<NavigationHookResult>) | undefined,undefined,false,false
ion-route,prop,beforeLeave,(() => boolean | NavigationHookOptions | Promise<NavigationHookResult>) | undefined,undefined,false,false
ion-route,prop,component,string,undefined,true,false
ion-route,prop,componentProps,undefined | { [key: string]: any; },undefined,false,false
ion-route,prop,url,string,'',false,false
@@ -927,7 +949,7 @@ ion-router,none
ion-router,prop,root,string,'/',false,false
ion-router,prop,useHash,boolean,true,false,false
ion-router,method,back,back() => Promise<void>
ion-router,method,push,push(url: string, direction?: RouterDirection) => Promise<boolean>
ion-router,method,push,push(url: string, direction?: RouterDirection, animation?: AnimationBuilder | undefined) => Promise<boolean>
ion-router,event,ionRouteDidChange,RouterEventDetail,true
ion-router,event,ionRouteWillChange,RouterEventDetail,true
@@ -935,6 +957,7 @@ ion-router-link,shadow
ion-router-link,prop,color,string | undefined,undefined,false,false
ion-router-link,prop,href,string | undefined,undefined,false,false
ion-router-link,prop,rel,string | undefined,undefined,false,false
ion-router-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-router-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-router-link,prop,target,string | undefined,undefined,false,false
ion-router-link,css-prop,--background
@@ -1029,6 +1052,9 @@ ion-segment-button,css-prop,--padding-end
ion-segment-button,css-prop,--padding-start
ion-segment-button,css-prop,--padding-top
ion-segment-button,css-prop,--transition
ion-segment-button,part,indicator
ion-segment-button,part,indicator-background
ion-segment-button,part,native
ion-select,shadow
ion-select,prop,cancelText,string,'Cancel',false,false
@@ -1046,7 +1072,7 @@ ion-select,prop,value,any,undefined,false,false
ion-select,method,open,open(event?: UIEvent | undefined) => Promise<any>
ion-select,event,ionBlur,void,true
ion-select,event,ionCancel,void,true
ion-select,event,ionChange,SelectChangeEventDetail,true
ion-select,event,ionChange,SelectChangeEventDetail<any>,true
ion-select,event,ionFocus,void,true
ion-select,css-prop,--padding-bottom
ion-select,css-prop,--padding-end
@@ -1166,6 +1192,7 @@ ion-tab-button,css-prop,--padding-end
ion-tab-button,css-prop,--padding-start
ion-tab-button,css-prop,--padding-top
ion-tab-button,css-prop,--ripple-color
ion-tab-button,part,native
ion-tabs,shadow
ion-tabs,method,getSelected,getSelected() => Promise<string | undefined>
@@ -1202,9 +1229,9 @@ ion-textarea,prop,value,null | string | undefined,'',false,false
ion-textarea,prop,wrap,"hard" | "off" | "soft" | undefined,undefined,false,false
ion-textarea,method,getInputElement,getInputElement() => Promise<HTMLTextAreaElement>
ion-textarea,method,setFocus,setFocus() => Promise<void>
ion-textarea,event,ionBlur,void,true
ion-textarea,event,ionBlur,FocusEvent,true
ion-textarea,event,ionChange,TextareaChangeEventDetail,true
ion-textarea,event,ionFocus,void,true
ion-textarea,event,ionFocus,FocusEvent,true
ion-textarea,event,ionInput,KeyboardEvent,true
ion-textarea,css-prop,--background
ion-textarea,css-prop,--border-radius
@@ -1266,6 +1293,10 @@ ion-toast,css-prop,--min-width
ion-toast,css-prop,--start
ion-toast,css-prop,--white-space
ion-toast,css-prop,--width
ion-toast,part,button
ion-toast,part,container
ion-toast,part,header
ion-toast,part,message
ion-toggle,shadow
ion-toggle,prop,checked,boolean,false,false,false

11820
core/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "5.1.1",
"version": "5.3.0",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -30,29 +30,29 @@
"loader/"
],
"dependencies": {
"ionicons": "^5.0.1",
"ionicons": "^5.1.2",
"tslib": "^1.10.0"
},
"devDependencies": {
"@stencil/core": "1.13.0",
"@stencil/sass": "1.3.1",
"@types/jest": "24.9.1",
"@types/node": "12.12.3",
"@types/puppeteer": "1.19.1",
"@rollup/plugin-node-resolve": "^8.1.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/core": "1.16.0",
"@stencil/sass": "1.3.2",
"@types/jest": "26.0.3",
"@types/node": "14.0.14",
"@types/puppeteer": "3.0.1",
"@types/swiper": "5.3.1",
"aws-sdk": "^2.497.0",
"aws-sdk": "^2.709.0",
"clean-css-cli": "^4.1.11",
"domino": "^2.1.3",
"fs-extra": "^8.0.1",
"jest": "24.9.0",
"jest-cli": "24.9.0",
"fs-extra": "^9.0.1",
"jest": "26.1.0",
"jest-cli": "26.1.0",
"np": "^5.0.3",
"pixelmatch": "4.0.2",
"puppeteer": "1.20.0",
"rollup": "1.32.0",
"rollup-plugin-node-resolve": "5.2.0",
"rollup-plugin-virtual": "^1.0.1",
"sass": "^1.22.9",
"puppeteer": "4.0.1",
"rollup": "^2.19.0",
"sass": "^1.26.9",
"stylelint": "10.1.0",
"stylelint-order": "3.0.1",
"swiper": "5.4.1",

View File

@@ -1,4 +1,4 @@
import resolve from 'rollup-plugin-node-resolve';
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'src/components/slides/swiper/swiper.js',

View File

@@ -1,7 +1,7 @@
const path = require('path');
const { rollup } = require('rollup');
const virtual = require('rollup-plugin-virtual');
const virtual = require('@rollup/plugin-virtual');
const fs = require('fs');
function main() {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ import { mdLeaveAnimation } from './animations/md.leave';
export class ActionSheet implements ComponentInterface, OverlayInterface {
presented = false;
lastFocus?: HTMLElement;
animation?: any;
private wrapperEl?: HTMLElement;
private groupEl?: HTMLElement;
@@ -117,7 +118,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
return present(this, 'actionSheetEnter', iosEnterAnimation, mdEnterAnimation);
}
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -250,7 +251,10 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
onIonBackdropTap={this.onBackdropTap}
>
<ion-backdrop tappable={this.backdropDismiss}/>
<div class="action-sheet-wrapper" role="dialog" ref={el => this.wrapperEl = el}>
<div tabindex="0"></div>
<div class="action-sheet-wrapper ion-overlay-wrapper" role="dialog" ref={el => this.wrapperEl = el}>
<div class="action-sheet-container">
<div class="action-sheet-group" ref={el => this.groupEl = el}>
{this.header !== undefined &&
@@ -292,6 +296,8 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
}
</div>
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -1,6 +1,40 @@
import { testActionSheet, testActionSheetAlert, testActionSheetBackdrop } from '../test.utils';
import { newE2EPage } from '@stencil/core/testing';
const DIRECTORY = 'basic';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('action-sheet: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' });
await page.click('#basic');
await page.waitForSelector('#basic');
let actionSheet = await page.find('ion-action-sheet');
expect(actionSheet).not.toBe(null);
await actionSheet.waitForVisible();
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Delete');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('Cancel');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Delete');
});
test('action-sheet: basic', async () => {
await testActionSheet(DIRECTORY, '#basic');

View File

@@ -34,6 +34,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
private gesture?: Gesture;
presented = false;
lastFocus?: HTMLElement;
@Element() el!: HTMLIonAlertElement;
@@ -167,7 +168,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
}) as AlertInput);
}
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -514,7 +515,9 @@ export class Alert implements ComponentInterface, OverlayInterface {
<ion-backdrop tappable={this.backdropDismiss}/>
<div class="alert-wrapper" ref={el => this.wrapperEl = el}>
<div tabindex="0"></div>
<div class="alert-wrapper ion-overlay-wrapper" ref={el => this.wrapperEl = el}>
<div class="alert-head">
{header && <h2 id={hdrId} class="alert-title">{header}</h2>}
@@ -527,6 +530,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
{this.renderAlertButtons()}
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -1,6 +1,40 @@
import { testAlert } from '../test.utils';
import { newE2EPage } from '@stencil/core/testing';
const DIRECTORY = 'basic';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('alert: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/alert/test/basic?ionic:_testing=true' });
await page.click('#multipleButtons');
await page.waitForSelector('#multipleButtons');
let alert = await page.find('ion-alert');
expect(alert).not.toBe(null);
await alert.waitForVisible();
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Open Modal');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('Cancel');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Open Modal');
});
test(`alert: basic`, async () => {
await testAlert(DIRECTORY, '#basic');

View File

@@ -2,12 +2,16 @@ import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Color } from '../../interface';
import { AnimationBuilder, Color } from '../../interface';
import { ButtonInterface } from '../../utils/element-interface';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @part native - The native HTML button element that wraps all child elements.
* @part icon - The back button icon (uses ion-icon).
* @part text - The back button text.
*/
@Component({
tag: 'ion-back-button',
@@ -53,6 +57,12 @@ export class BackButton implements ComponentInterface, ButtonInterface {
*/
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
/**
* When using a router, it specifies the transition animation when navigating to
* another page.
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
componentWillLoad() {
if (this.defaultHref === undefined) {
this.defaultHref = config.get('backButtonDefaultHref');
@@ -99,9 +109,9 @@ export class BackButton implements ComponentInterface, ButtonInterface {
ev.preventDefault();
if (nav && await nav.canGoBack()) {
return nav.pop({ skipIfBusy: true });
return nav.pop({ animationBuilder: this.routerAnimation, skipIfBusy: true });
}
return openURL(this.defaultHref, ev, 'back');
return openURL(this.defaultHref, ev, 'back', this.routerAnimation);
}
render() {
@@ -126,10 +136,16 @@ export class BackButton implements ComponentInterface, ButtonInterface {
'show-back-button': showBackButton
}}
>
<button type={type} disabled={disabled} class="button-native" aria-label={backButtonText || 'back'}>
<button
type={type}
disabled={disabled}
class="button-native"
part="native"
aria-label={backButtonText || 'back'}
>
<span class="button-inner">
{backButtonIcon && <ion-icon icon={backButtonIcon} aria-hidden="true" lazy={false}></ion-icon>}
{backButtonText && <span aria-hidden="true" class="button-text">{backButtonText}</span>}
{backButtonIcon && <ion-icon part="icon" icon={backButtonIcon} aria-hidden="true" lazy={false}></ion-icon>}
{backButtonText && <span part="text" aria-hidden="true" class="button-text">{backButtonText}</span>}
</span>
{mode === 'md' && <ion-ripple-effect type={this.rippleType}></ion-ripple-effect>}
</button>

View File

@@ -301,15 +301,25 @@ export class BackButtonExample {
## Properties
| Property | Attribute | Description | Type | Default |
| ------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------- |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `defaultHref` | `default-href` | The url to navigate back to by default when there is no history. | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the button. | `boolean` | `false` |
| `icon` | `icon` | The icon name to use for the back button. | `null \| string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `text` | `text` | The text to display in the back button. | `null \| string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
| Property | Attribute | Description | Type | Default |
| ----------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `defaultHref` | `default-href` | The url to navigate back to by default when there is no history. | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the button. | `boolean` | `false` |
| `icon` | `icon` | The icon name to use for the back button. | `null \| string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `text` | `text` | The text to display in the back button. | `null \| string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
## Shadow Parts
| Part | Description |
| ---------- | ------------------------------------------------------------- |
| `"icon"` | The back button icon (uses ion-icon). |
| `"native"` | The native HTML button element that wraps all child elements. |
| `"text"` | The back button text. |
## CSS Custom Properties

View File

@@ -1,7 +1,7 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { Color, RouterDirection } from '../../interface';
import { AnimationBuilder, Color, RouterDirection } from '../../interface';
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import { hasShadowDom } from '../../utils/helpers';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
@@ -13,6 +13,8 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme';
* @slot icon-only - Should be used on an icon in a button that has no text.
* @slot start - Content is placed to the left of the button text in LTR, and to the right in RTL.
* @slot end - Content is placed to the right of the button text in LTR, and to the left in RTL.
*
* @part native - The native HTML button or anchor element that wraps all child elements.
*/
@Component({
tag: 'ion-button',
@@ -65,6 +67,12 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
*/
@Prop() routerDirection: RouterDirection = 'forward';
/**
* When using a router, it specifies the transition animation when navigating to
* another page using `href`.
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
/**
* This attribute instructs browsers to download a URL instead of navigating to
* it, so the user will be prompted to save it as a local file. If the attribute
@@ -146,7 +154,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
private handleClick = (ev: Event) => {
if (this.type === 'button') {
openURL(this.href, ev, this.routerDirection);
openURL(this.href, ev, this.routerDirection, this.routerAnimation);
} else if (hasShadowDom(this.el)) {
// this button wants to specifically submit a form
@@ -216,6 +224,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
<TagType
{...attrs}
class="button-native"
part="native"
disabled={disabled}
onFocus={this.onFocus}
onBlur={this.onBlur}

View File

@@ -291,6 +291,7 @@ export class ButtonExample {
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page using `href`. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `shape` | `shape` | The button shape. | `"round" \| undefined` | `undefined` |
| `size` | `size` | The button size. | `"default" \| "large" \| "small" \| undefined` | `undefined` |
@@ -317,6 +318,13 @@ export class ButtonExample {
| `"start"` | Content is placed to the left of the button text in LTR, and to the right in RTL. |
## Shadow Parts
| Part | Description |
| ---------- | ----------------------------------------------------------------------- |
| `"native"` | The native HTML button or anchor element that wraps all child elements. |
## CSS Custom Properties
| Name | Description |

View File

@@ -5,8 +5,8 @@
// --------------------------------------------------
:host {
--background: #{$item-ios-background};
--color: #{$card-ios-text-color};
--background: #{$card-ios-background};
--color: #{$card-ios-color};
@include margin($card-ios-margin-top, $card-ios-margin-end, $card-ios-margin-bottom, $card-ios-margin-start);
@include border-radius($card-ios-border-radius);
@@ -22,4 +22,4 @@
:host(.ion-activated) {
transform: #{$card-ios-transform-activated};
}
}

View File

@@ -15,9 +15,6 @@ $card-ios-margin-bottom: $card-ios-margin-top !default;
/// @prop - Margin start of the card
$card-ios-margin-start: 16px !default;
/// @prop - Background color of the card
$card-ios-background-color: $item-ios-background !default;
/// @prop - Box shadow color of the card
$card-ios-box-shadow-color: rgba(0, 0, 0, .12) !default;
@@ -30,11 +27,8 @@ $card-ios-border-radius: 8px !default;
/// @prop - Font size of the card
$card-ios-font-size: 14px !default;
/// @prop - Color of the card text
$card-ios-text-color: $text-color-step-400 !default;
/// @prop - Transition timing function of the card
$card-ios-transition-timing-function: cubic-bezier(0.12, 0.72, 0.29, 1) !default;
/// @prop - Transform of the card on activate
$card-ios-transform-activated: scale3d(.97, .97, 1) !default;
$card-ios-transform-activated: scale3d(.97, .97, 1) !default;

View File

@@ -5,8 +5,8 @@
// --------------------------------------------------
:host {
--background: #{$item-md-background};
--color: #{$card-md-text-color};
--background: #{$card-md-background};
--color: #{$card-md-color};
@include margin($card-md-margin-top, $card-md-margin-end, $card-md-margin-bottom, $card-md-margin-start);
@include border-radius($card-md-border-radius);

View File

@@ -15,9 +15,6 @@ $card-md-margin-bottom: 10px !default;
/// @prop - Margin start of the card
$card-md-margin-start: 10px !default;
/// @prop - Background color of the card
$card-md-background-color: $item-md-background !default;
/// @prop - Box shadow of the card
$card-md-box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12) !default;
@@ -29,6 +26,3 @@ $card-md-font-size: 14px !default;
/// @prop - Line height of the card
$card-md-line-height: 1.5 !default;
/// @prop - Color of the card text
$card-md-text-color: $text-color-step-450 !default;

View File

@@ -1,12 +1,14 @@
import { Component, ComponentInterface, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { Color, Mode, RouterDirection } from '../../interface';
import { AnimationBuilder, Color, Mode, RouterDirection } from '../../interface';
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import { createColorClasses, openURL } from '../../utils/theme';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @part native - The native HTML button, anchor, or div element that wraps all child elements.
*/
@Component({
tag: 'ion-card',
@@ -66,6 +68,12 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
*/
@Prop() routerDirection: RouterDirection = 'forward';
/**
* When using a router, it specifies the transition animation when navigating to
* another page using `href`.
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
/**
* Specifies where to display the linked URL.
* Only applies when an `href` is provided.
@@ -85,7 +93,7 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
<slot></slot>
];
}
const { href, routerDirection } = this;
const { href, routerAnimation, routerDirection } = this;
const TagType = clickable ? (href === undefined ? 'button' : 'a') : 'div' as any;
const attrs = (TagType === 'button')
? { type: this.type }
@@ -100,8 +108,9 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
<TagType
{...attrs}
class="card-native"
part="native"
disabled={this.disabled}
onClick={(ev: Event) => openURL(href, ev, routerDirection)}
onClick={(ev: Event) => openURL(href, ev, routerDirection, routerAnimation)}
>
<slot></slot>
{clickable && mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}

View File

@@ -254,18 +254,26 @@ export class CardExample {
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------- |
| `button` | `button` | If `true`, a button tag will be rendered and the card will be tappable. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the card. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- |
| `button` | `button` | If `true`, a button tag will be rendered and the card will be tappable. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the card. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page using `href`. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
## Shadow Parts
| Part | Description |
| ---------- | ----------------------------------------------------------------------------- |
| `"native"` | The native HTML button, anchor, or div element that wraps all child elements. |
## CSS Custom Properties

View File

@@ -196,7 +196,7 @@ Type: `Promise<void>`
### `scrollToPoint(x: number | null | undefined, y: number | null | undefined, duration?: number) => Promise<void>`
### `scrollToPoint(x: number | undefined | null, y: number | undefined | null, duration?: number) => Promise<void>`
Scroll to a specified X/Y location in the component.

View File

@@ -632,7 +632,6 @@ export class Datetime implements ComponentInterface {
return (
<Host
onClick={this.onClick}
role="combobox"
aria-disabled={disabled ? 'true' : null}
aria-expanded={`${isExpanded}`}
aria-haspopup="true"

View File

@@ -1,5 +1,42 @@
import { newE2EPage } from '@stencil/core/testing';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('datetime/picker: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/datetime/test/basic?ionic:_testing=true' });
await page.click('#datetime-part');
await page.waitForSelector('#datetime-part');
let datetime = await page.find('ion-datetime');
expect(datetime).not.toBe(null);
await datetime.waitForVisible();
// TODO fix
await page.waitFor(100);
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Cancel');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('1920');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Cancel');
});
test('datetime: basic', async () => {
const page = await newE2EPage({
url: '/src/components/datetime/test/basic?ionic:_testing=true'

View File

@@ -15,6 +15,7 @@
--color: #{$fab-ios-text-color};
--box-shadow: #{$fab-ios-box-shadow};
--transition: #{$fab-ios-transition};
--close-icon-font-size: #{$fab-ios-icon-font-size};
}
:host(.ion-activated) {
@@ -23,11 +24,11 @@
--transition: #{$fab-ios-transition-activated};
}
::slotted(ion-icon),
.close-icon {
::slotted(ion-icon) {
font-size: $fab-ios-icon-font-size;
}
// FAB buttons in a list
// --------------------------------------------------
@@ -112,4 +113,4 @@
:host(.ion-color.ion-activated.fab-button-translucent) .button-native {
background: #{current-color(base)};
}
}
}

View File

@@ -21,14 +21,14 @@
opacity 15ms linear 30ms,
transform 270ms cubic-bezier(0, 0, .2, 1) 0ms
};
--close-icon-font-size: #{$fab-md-icon-font-size};
}
:host(.ion-activated) {
--box-shadow: #{$fab-md-box-shadow-activated};
}
::slotted(ion-icon),
.close-icon {
::slotted(ion-icon) {
font-size: $fab-md-icon-font-size;
}

View File

@@ -20,6 +20,8 @@
*
* @prop --transition: Transition of the button
*
* @prop --close-icon-font-size: Font size of the close icon
*
* @prop --border-radius: Border radius of the button
* @prop --border-width: Border width of the button
* @prop --border-style: Border style of the button
@@ -207,14 +209,11 @@
// --------------------------------------------------
.close-icon {
@include margin(0, auto);
@include position(0, 0, null, 0);
display: flex;
position: absolute;
align-items: center;
justify-content: center;
height: 100%;
transform: scale(.4) rotateZ(-45deg);
@@ -222,11 +221,14 @@
transition: all ease-in-out 300ms;
transition-property: transform, opacity;
font-size: var(--close-icon-font-size);
opacity: 0;
z-index: 1;
}
// FAB Animation
// --------------------------------------------------

View File

@@ -1,12 +1,15 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { Color, RouterDirection } from '../../interface';
import { AnimationBuilder, Color, RouterDirection } from '../../interface';
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @part native - The native HTML button or anchor element that wraps all child elements.
* @part close-icon - The close icon that is displayed when a fab list opens (uses ion-icon).
*/
@Component({
tag: 'ion-fab-button',
@@ -62,6 +65,12 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
*/
@Prop() routerDirection: RouterDirection = 'forward';
/**
* When using a router, it specifies the transition animation when navigating to
* another page using `href`.
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
/**
* Specifies where to display the linked URL.
* Only applies when an `href` is provided.
@@ -87,10 +96,17 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
/**
* The size of the button. Set this to `small` in order to have a mini fab.
* The size of the button. Set this to `small` in order to have a mini fab button.
*/
@Prop() size?: 'small';
/**
* The icon name to use for the close icon. This will appear when the fab button
* is pressed. Only applies if it is the main button inside of a fab containing a
* fab list.
*/
@Prop() closeIcon = 'close';
/**
* Emitted when the button has focus.
*/
@@ -144,14 +160,13 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
<TagType
{...attrs}
class="button-native"
part="native"
disabled={disabled}
onFocus={this.onFocus}
onBlur={this.onBlur}
onClick={(ev: Event) => openURL(href, ev, this.routerDirection)}
onClick={(ev: Event) => openURL(href, ev, this.routerDirection, this.routerAnimation)}
>
<span class="close-icon">
<ion-icon name="close" lazy={false}></ion-icon>
</span>
<ion-icon icon={this.closeIcon} part="close-icon" class="close-icon" lazy={false}></ion-icon>
<span class="button-inner">
<slot></slot>
</span>

View File

@@ -137,21 +137,23 @@ export class FabButtonExample {
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------- |
| `activated` | `activated` | If `true`, the fab button will be show a close icon. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the fab button. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `show` | `show` | If `true`, the fab button will show when in a fab-list. | `boolean` | `false` |
| `size` | `size` | The size of the button. Set this to `small` in order to have a mini fab. | `"small" \| undefined` | `undefined` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the fab button will be translucent. Only applies when the mode is `"ios"` and the device supports [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility). | `boolean` | `false` |
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- |
| `activated` | `activated` | If `true`, the fab button will be show a close icon. | `boolean` | `false` |
| `closeIcon` | `close-icon` | The icon name to use for the close icon. This will appear when the fab button is pressed. Only applies if it is the main button inside of a fab containing a fab list. | `string` | `'close'` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the fab button. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page using `href`. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `show` | `show` | If `true`, the fab button will show when in a fab-list. | `boolean` | `false` |
| `size` | `size` | The size of the button. Set this to `small` in order to have a mini fab button. | `"small" \| undefined` | `undefined` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the fab button will be translucent. Only applies when the mode is `"ios"` and the device supports [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility). | `boolean` | `false` |
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
## Events
@@ -162,6 +164,14 @@ export class FabButtonExample {
| `ionFocus` | Emitted when the button has focus. | `CustomEvent<void>` |
## Shadow Parts
| Part | Description |
| -------------- | ----------------------------------------------------------------------- |
| `"close-icon"` | The close icon that is displayed when a fab list opens (uses ion-icon). |
| `"native"` | The native HTML button or anchor element that wraps all child elements. |
## CSS Custom Properties
| Name | Description |
@@ -178,6 +188,7 @@ export class FabButtonExample {
| `--border-style` | Border style of the button |
| `--border-width` | Border width of the button |
| `--box-shadow` | Box shadow of the button |
| `--close-icon-font-size` | Font size of the close icon |
| `--color` | Text color of the button |
| `--color-activated` | Text color of the button when pressed |
| `--color-focused` | Text color of the button when focused with the tab key |

View File

@@ -71,6 +71,22 @@
<ion-fab-button class="fab-button-in-list custom-border"><ion-icon name="star"></ion-icon></ion-fab-button>
<ion-fab-button class="fab-button-in-list custom-border ion-activated"><ion-icon name="star"></ion-icon></ion-fab-button>
<ion-fab slot="fixed" horizontal="center" vertical="bottom">
<ion-fab-button class="custom-close"><ion-icon name="star"></ion-icon></ion-fab-button>
<ion-fab-list side="top">
<ion-fab-button>
<ion-icon name="heart"></ion-icon>
</ion-fab-button>
<ion-fab-button>
<ion-icon name="square"></ion-icon>
</ion-fab-button>
<ion-fab-button>
<ion-icon name="triangle"></ion-icon>
</ion-fab-button>
</ion-fab-list>
</ion-fab>
<style>
ion-fab-button {
display: inline-block;
@@ -104,6 +120,12 @@
--border-style: dashed;
--border-color: red;
}
.custom-close::part(close-icon) {
color: red;
font-size: 44px;
}
</style>
</body>
</html>

View File

@@ -200,7 +200,6 @@
f:last-of-type {
background: yellow;
}
</style>
</ion-app>
</body>

View File

@@ -150,7 +150,6 @@ export const scaleLargeTitles = (toolbars: ToolbarIndex[] = [], scale = 1, trans
const titleDiv = toolbar.innerTitleEl;
if (!ionTitle || ionTitle.size !== 'large') { return; }
titleDiv.style.transformOrigin = 'left center';
titleDiv.style.transition = (transition) ? TRANSITION : '';
titleDiv.style.transform = `scale3d(${scale}, ${scale}, 1)`;
});

View File

@@ -53,7 +53,11 @@ export class Img implements ComponentInterface {
if (this.src === undefined) {
return;
}
if ('IntersectionObserver' in window) {
if (
typeof (window as any) !== 'undefined' &&
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'isIntersecting' in window.IntersectionObserverEntry.prototype) {
this.removeIO();
this.io = new IntersectionObserver(data => {
// because there will only ever be one instance

View File

@@ -147,7 +147,11 @@
appearance: none;
}
:host(.has-focus.has-value) .input-clear-icon {
.input-clear-icon:focus {
opacity: 0.5;
}
:host(.has-value) .input-clear-icon {
visibility: visible;
}

View File

@@ -201,12 +201,12 @@ export class Input implements ComponentInterface {
/**
* Emitted when the input loses focus.
*/
@Event() ionBlur!: EventEmitter<void>;
@Event() ionBlur!: EventEmitter<FocusEvent>;
/**
* Emitted when the input has focus.
*/
@Event() ionFocus!: EventEmitter<void>;
@Event() ionFocus!: EventEmitter<FocusEvent>;
/**
* Emitted when the styles change.
@@ -293,20 +293,20 @@ export class Input implements ComponentInterface {
this.ionInput.emit(ev as KeyboardEvent);
}
private onBlur = () => {
private onBlur = (ev: FocusEvent) => {
this.hasFocus = false;
this.focusChanged();
this.emitStyle();
this.ionBlur.emit();
this.ionBlur.emit(ev);
}
private onFocus = () => {
private onFocus = (ev: FocusEvent) => {
this.hasFocus = true;
this.focusChanged();
this.emitStyle();
this.ionFocus.emit();
this.ionFocus.emit(ev);
}
private onKeydown = (ev: KeyboardEvent) => {
@@ -323,6 +323,10 @@ export class Input implements ComponentInterface {
}
}
private clearTextOnEnter = (ev: KeyboardEvent) => {
if (ev.key === 'Enter') { this.clearTextInput(ev); }
}
private clearTextInput = (ev?: Event) => {
if (this.clearInput && !this.readonly && !this.disabled && ev) {
ev.preventDefault();
@@ -393,7 +397,7 @@ export class Input implements ComponentInterface {
placeholder={this.placeholder || ''}
readOnly={this.readonly}
required={this.required}
spellcheck={this.spellcheck ? 'true' : undefined}
spellcheck={this.spellcheck}
step={this.step}
size={this.size}
tabindex={this.tabindex}
@@ -405,11 +409,12 @@ export class Input implements ComponentInterface {
onKeyDown={this.onKeydown}
/>
{(this.clearInput && !this.readonly && !this.disabled) && <button
aria-label="reset"
type="button"
class="input-clear-icon"
tabindex="-1"
onTouchStart={this.clearTextInput}
onMouseDown={this.clearTextInput}
onKeyDown={this.clearTextOnEnter}
/>}
</Host>
);

View File

@@ -317,7 +317,7 @@ export class InputExample {
| `size` | `size` | The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored. | `number \| undefined` | `undefined` |
| `spellcheck` | `spellcheck` | If `true`, the element will have its spelling and grammar checked. | `boolean` | `false` |
| `step` | `step` | Works with the min and max attributes to limit the increments at which a value can be set. Possible values are: `"any"` or a positive floating point number. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of control to display. The default type is text. | `"date" \| "email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "time" \| "url"` | `'text'` |
| `type` | `type` | The type of control to display. The default type is text. | `"date" \| "datetime-local" \| "email" \| "month" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "time" \| "url" \| "week"` | `'text'` |
| `value` | `value` | The value of the input. | `null \| number \| string \| undefined` | `''` |
@@ -325,9 +325,9 @@ export class InputExample {
| Event | Description | Type |
| ----------- | --------------------------------------- | ------------------------------------- |
| `ionBlur` | Emitted when the input loses focus. | `CustomEvent<void>` |
| `ionBlur` | Emitted when the input loses focus. | `CustomEvent<FocusEvent>` |
| `ionChange` | Emitted when the value has changed. | `CustomEvent<InputChangeEventDetail>` |
| `ionFocus` | Emitted when the input has focus. | `CustomEvent<void>` |
| `ionFocus` | Emitted when the input has focus. | `CustomEvent<FocusEvent>` |
| `ionInput` | Emitted when a keyboard input occurred. | `CustomEvent<KeyboardEvent>` |

View File

@@ -136,6 +136,7 @@
</ion-content>
<script>
document.querySelector('ion-input').addEventListener('ionBlur', (ev) => { console.log(ev)})
function toggleBoolean(id, prop) {
var el = document.getElementById(id);

View File

@@ -14,6 +14,8 @@ import { createColorClasses } from '../../utils/theme';
* @slot icon-only - Should be used on an icon in an option that has no text.
* @slot bottom - Content is placed below the option text.
* @slot end - Content is placed to the right of the option text in LTR, and to the left in RTL.
*
* @part native - The native HTML button or anchor element that wraps all child elements.
*/
@Component({
tag: 'ion-item-option',
@@ -110,6 +112,7 @@ export class ItemOption implements ComponentInterface, AnchorInterface, ButtonIn
<TagType
{...attrs}
class="button-native"
part="native"
disabled={disabled}
>
<span class="button-inner">

View File

@@ -34,6 +34,13 @@ action for the item.
| `"top"` | Content is placed above the option text. |
## Shadow Parts
| Part | Description |
| ---------- | ----------------------------------------------------------------------- |
| `"native"` | The native HTML button or anchor element that wraps all child elements. |
## CSS Custom Properties
| Name | Description |

View File

@@ -860,7 +860,7 @@ Type: `Promise<number>`
### `open(side: "start" | "end" | undefined) => Promise<void>`
### `open(side: Side | undefined) => Promise<void>`
Open the sliding item.

View File

@@ -1,7 +1,7 @@
import { Component, ComponentInterface, Element, Host, Listen, Prop, State, forceUpdate, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { Color, CssClassMap, RouterDirection, StyleEventDetail } from '../../interface';
import { AnimationBuilder, Color, CssClassMap, RouterDirection, StyleEventDetail } from '../../interface';
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
@@ -12,6 +12,7 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme';
* @slot start - Content is placed to the left of the item text in LTR, and to the right in RTL.
* @slot end - Content is placed to the right of the item text in LTR, and to the left in RTL.
*
* @part native - The native HTML button, anchor or div element that wraps all child elements.
* @part detail-icon - The chevron icon for the item. Only applies when `detail="true"`.
*/
@Component({
@@ -85,6 +86,12 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
*/
@Prop() lines?: 'full' | 'inset' | 'none';
/**
* When using a router, it specifies the transition animation when navigating to
* another page using `href`.
*/
@Prop() routerAnimation: AnimationBuilder | undefined;
/**
* When using a router, it specifies the transition direction when navigating to
* another page using `href`.
@@ -170,7 +177,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
}
render() {
const { detail, detailIcon, download, lines, disabled, href, rel, target, routerDirection } = this;
const { detail, detailIcon, download, lines, disabled, href, rel, target, routerAnimation, routerDirection } = this;
const childStyles = {};
const mode = getIonMode(this);
const clickable = this.isClickable();
@@ -208,8 +215,9 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
<TagType
{...attrs}
class="item-native"
part="native"
disabled={disabled}
onClick={(ev: Event) => openURL(href, ev, routerDirection)}
onClick={(ev: Event) => openURL(href, ev, routerDirection, routerAnimation)}
>
<slot name="start"></slot>
<div class="item-inner">

View File

@@ -1783,21 +1783,22 @@ export class ItemExample {
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------- |
| `button` | `button` | If `true`, a button tag will be rendered and the item will be tappable. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `detail` | `detail` | If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href` or `button` property is present. | `boolean \| undefined` | `undefined` |
| `detailIcon` | `detail-icon` | The icon to use when `detail` is set to `true`. | `string` | `'chevron-forward'` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the item. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `lines` | `lines` | How the bottom border should be displayed on the item. | `"full" \| "inset" \| "none" \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------- |
| `button` | `button` | If `true`, a button tag will be rendered and the item will be tappable. | `boolean` | `false` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `detail` | `detail` | If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href` or `button` property is present. | `boolean \| undefined` | `undefined` |
| `detailIcon` | `detail-icon` | The icon to use when `detail` is set to `true`. | `string` | `'chevron-forward'` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the item. | `boolean` | `false` |
| `download` | `download` | This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want). | `string \| undefined` | `undefined` |
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
| `lines` | `lines` | How the bottom border should be displayed on the item. | `"full" \| "inset" \| "none" \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `rel` | `rel` | Specifies the relationship of the target object to the link object. The value is a space-separated list of [link types](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). | `string \| undefined` | `undefined` |
| `routerAnimation` | -- | When using a router, it specifies the transition animation when navigating to another page using `href`. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
| `target` | `target` | Specifies where to display the linked URL. Only applies when an `href` is provided. Special keywords: `"_blank"`, `"_self"`, `"_parent"`, `"_top"`. | `string \| undefined` | `undefined` |
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
## Slots
@@ -1811,9 +1812,10 @@ export class ItemExample {
## Shadow Parts
| Part | Description |
| --------------- | ----------------------------------------------------------------- |
| `"detail-icon"` | The chevron icon for the item. Only applies when `detail="true"`. |
| Part | Description |
| --------------- | ---------------------------------------------------------------------------- |
| `"detail-icon"` | The chevron icon for the item. Only applies when `detail="true"`. |
| `"native"` | The native HTML button, anchor or div element that wraps all child elements. |
## CSS Custom Properties

View File

@@ -27,6 +27,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
private durationTimeout: any;
presented = false;
lastFocus?: HTMLElement;
@Element() el!: HTMLIonLoadingElement;
@@ -111,7 +112,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
*/
@Event({ eventName: 'ionLoadingDidDismiss' }) didDismiss!: EventEmitter<OverlayEventDetail>;
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -194,7 +195,10 @@ export class Loading implements ComponentInterface, OverlayInterface {
}}
>
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss} />
<div class="loading-wrapper" role="dialog">
<div tabindex="0"></div>
<div class="loading-wrapper ion-overlay-wrapper" role="dialog">
{spinner && (
<div class="loading-spinner">
<ion-spinner name={spinner} aria-hidden="true" />
@@ -203,6 +207,8 @@ export class Loading implements ComponentInterface, OverlayInterface {
{message && <div class="loading-content" innerHTML={sanitizeDOMString(message)}></div>}
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -1,6 +1,40 @@
import { testLoading } from '../test.utils';
import { newE2EPage } from '@stencil/core/testing';
const DIRECTORY = 'basic';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('loading: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/loading/test/basic?ionic:_testing=true' });
await page.click('#html-content-loading');
await page.waitForSelector('#html-content-loading');
let loading = await page.find('ion-loading');
expect(loading).not.toBe(null);
await loading.waitForVisible();
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Click impatiently to load faster');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('Click impatiently to load faster');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Click impatiently to load faster');
});
test('loading: basic', async () => {
await testLoading(DIRECTORY, '#basic-loading');

View File

@@ -8,6 +8,12 @@ import { menuController } from '../../utils/menu-controller';
import { createColorClasses, hostContext } from '../../utils/theme';
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @part native - The native HTML button element that wraps all child elements.
* @part icon - The menu button icon (uses ion-icon).
*/
@Component({
tag: 'ion-menu-button',
styleUrls: {
@@ -95,11 +101,12 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
{...attrs}
disabled={disabled}
class="button-native"
part="native"
aria-label="menu"
>
<span class="button-inner">
<slot>
<ion-icon icon={menuIcon} mode={mode} lazy={false} aria-hidden="true"></ion-icon>
<ion-icon part="icon" icon={menuIcon} mode={mode} lazy={false} aria-hidden="true"></ion-icon>
</slot>
</span>
{mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}

View File

@@ -14,9 +14,18 @@ Menu Button is component that automatically creates the icon and functionality t
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the menu button. | `boolean` | `false` |
| `menu` | `menu` | Optional property that maps to a Menu's `menuId` prop. Can also be `start` or `end` for the menu side. This is used to find the correct menu to toggle | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
## Shadow Parts
| Part | Description |
| ---------- | ------------------------------------------------------------- |
| `"icon"` | The menu button icon (uses ion-icon). |
| `"native"` | The native HTML button element that wraps all child elements. |
## CSS Custom Properties
| Name | Description |

View File

@@ -21,14 +21,14 @@
@media screen and (max-width: 767px) {
@supports (width: max(0px, 1px)) {
:host(.modal-card) .modal-wrapper {
height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
:host(.modal-card) {
--height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
}
}
@supports not (width: max(0px, 1px)) {
:host(.modal-card) .modal-wrapper {
height: calc(100% - 40px);
:host(.modal-card) {
--height: calc(100% - 40px);
}
}

View File

@@ -34,6 +34,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
// Whether or not modal is being dismissed via gesture
private gestureAnimationDismissing = false;
presented = false;
lastFocus?: HTMLElement;
animation?: Animation;
@Element() el!: HTMLIonModalElement;
@@ -130,7 +131,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
}
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -289,11 +290,16 @@ export class Modal implements ComponentInterface, OverlayInterface {
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}/>
{mode === 'ios' && <div class="modal-shadow"></div>}
<div tabindex="0"></div>
<div
role="dialog"
class="modal-wrapper"
class="modal-wrapper ion-overlay-wrapper"
>
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -173,6 +173,8 @@ export class CalendarComponentModule {}
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
If you are creating an application that uses `ion-tabs`, it is recommended that you get the parent `ion-router-outlet` using `this.routerOutlet.parentOutlet.nativeEl`, otherwise the tabbar will not scale down when the modal opens.
```javascript
@@ -302,6 +304,8 @@ console.log(data);
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```javascript
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';
@@ -346,19 +350,51 @@ export const ModalExample: React.FC = () => {
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```tsx
<IonModal
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={pageRef.current}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
<IonButton onClick={() => setShowModal(false)}>Close Modal</IonButton>
</IonModal>
const App: React.FC = () => {
const routerRef = useRef<HTMLIonRouterOutletElement | null>(null);
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet ref={routerRef}>
<Route path="/home" render={() => <Home router={routerRef.current.current} />} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
...
interface HomePageProps {
router: HTMLIonRouterOutletElement | null;
}
const Home: React.FC<HomePageProps> = ({ router }) => {
const [showModal, setShowModal] = useState(false);
return (
...
<IonModal
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
</IonModal>
...
);
};
```
In most scenarios, setting a ref on `IonPage` and passing that ref's `current` value to `presentingElement` is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most `ion-modal` ref as the `presentingElement`.
In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `current` value to `presentingElement` is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most `ion-modal` ref as the `presentingElement`.
```tsx
<IonModal
@@ -366,7 +402,7 @@ In most scenarios, setting a ref on `IonPage` and passing that ref's `current` v
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={pageRef.current}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
<IonButton onClick={() => setShow2ndModal(true)}>Show 2nd Modal</IonButton>
@@ -496,6 +532,8 @@ console.log(data);
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```tsx
import { Component, Element, h } from '@stencil/core';

View File

@@ -1,6 +1,43 @@
import { testModal } from '../test.utils';
import { newE2EPage } from '@stencil/core/testing';
const DIRECTORY = 'basic';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('modal: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' });
await page.click('#basic-modal');
await page.waitForSelector('#basic-modal');
let modal = await page.find('ion-modal');
expect(modal).not.toBe(null);
await modal.waitForVisible();
// TODO fix
await page.waitFor(50);
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Dismiss Modal');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('Dismiss Modal');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Dismiss Modal');
});
test('modal: basic', async () => {
await testModal(DIRECTORY, '#basic-modal');

View File

@@ -130,6 +130,8 @@ export class CalendarComponentModule {}
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
If you are creating an application that uses `ion-tabs`, it is recommended that you get the parent `ion-router-outlet` using `this.routerOutlet.parentOutlet.nativeEl`, otherwise the tabbar will not scale down when the modal opens.
```javascript

View File

@@ -84,6 +84,8 @@ console.log(data);
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```javascript
const modalElement = document.createElement('ion-modal');
modalElement.component = 'modal-page';

View File

@@ -21,19 +21,51 @@ export const ModalExample: React.FC = () => {
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```tsx
<IonModal
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={pageRef.current}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
<IonButton onClick={() => setShowModal(false)}>Close Modal</IonButton>
</IonModal>
const App: React.FC = () => {
const routerRef = useRef<HTMLIonRouterOutletElement | null>(null);
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet ref={routerRef}>
<Route path="/home" render={() => <Home router={routerRef.current.current} />} exact={true} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
...
interface HomePageProps {
router: HTMLIonRouterOutletElement | null;
}
const Home: React.FC<HomePageProps> = ({ router }) => {
const [showModal, setShowModal] = useState(false);
return (
...
<IonModal
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
</IonModal>
...
);
};
```
In most scenarios, setting a ref on `IonPage` and passing that ref's `current` value to `presentingElement` is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most `ion-modal` ref as the `presentingElement`.
In most scenarios, setting a ref on `IonRouterOutlet` and passing that ref's `current` value to `presentingElement` is fine. In cases where you are presenting a card-style modal from within another modal, you should pass in the top-most `ion-modal` ref as the `presentingElement`.
```tsx
<IonModal
@@ -41,7 +73,7 @@ In most scenarios, setting a ref on `IonPage` and passing that ref's `current` v
isOpen={showModal}
cssClass='my-custom-class'
swipeToClose={true}
presentingElement={pageRef.current}
presentingElement={router || undefined}
onDidDismiss={() => setShowModal(false)}>
<p>This is modal content</p>
<IonButton onClick={() => setShow2ndModal(true)}>Show 2nd Modal</IonButton>

View File

@@ -109,6 +109,8 @@ console.log(data);
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
> Card style modals when running on iPhone-sized devices do not have backdrops. As a result, the `--backdrop-opacity` variable will not have any effect.
```tsx
import { Component, Element, h } from '@stencil/core';

View File

@@ -1,19 +1,19 @@
import { ComponentProps, NavComponent } from '../../interface';
import { AnimationBuilder, ComponentProps, NavComponent } from '../../interface';
import { RouterDirection } from '../router/utils/interface';
export const navLink = (el: HTMLElement, routerDirection: RouterDirection, component?: NavComponent, componentProps?: ComponentProps) => {
export const navLink = (el: HTMLElement, routerDirection: RouterDirection, component?: NavComponent, componentProps?: ComponentProps, routerAnimation?: AnimationBuilder) => {
const nav = el.closest('ion-nav');
if (nav) {
if (routerDirection === 'forward') {
if (component !== undefined) {
return nav.push(component, componentProps, { skipIfBusy: true });
return nav.push(component, componentProps, { skipIfBusy: true, animationBuilder: routerAnimation });
}
} else if (routerDirection === 'root') {
if (component !== undefined) {
return nav.setRoot(component, componentProps, { skipIfBusy: true });
return nav.setRoot(component, componentProps, { skipIfBusy: true, animationBuilder: routerAnimation });
}
} else if (routerDirection === 'back') {
return nav.pop({ skipIfBusy: true });
return nav.pop({ skipIfBusy: true, animationBuilder: routerAnimation });
}
}
return Promise.resolve(false);

View File

@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/core';
import { ComponentProps, NavComponent, RouterDirection } from '../../interface';
import { AnimationBuilder, ComponentProps, NavComponent, RouterDirection } from '../../interface';
import { navLink } from './nav-link-utils';
@@ -25,8 +25,13 @@ export class NavLink implements ComponentInterface {
*/
@Prop() routerDirection: RouterDirection = 'forward';
/**
* The transition animation when navigating to another page.
*/
@Prop() routerAnimation?: AnimationBuilder;
private onClick = () => {
return navLink(this.el, this.routerDirection, this.component, this.componentProps);
return navLink(this.el, this.routerDirection, this.component, this.componentProps, this.routerAnimation);
}
render() {

View File

@@ -14,6 +14,7 @@ It is the element form of calling the `push()`, `pop()`, and `setRoot()` methods
| ----------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------- |
| `component` | `component` | Component to navigate to. Only used if the `routerDirection` is `"forward"` or `"root"`. | `Function \| HTMLElement \| ViewController \| null \| string \| undefined` | `undefined` |
| `componentProps` | -- | Data you want to pass to the component as props. Only used if the `"routerDirection"` is `"forward"` or `"root"`. | `undefined \| { [key: string]: any; }` | `undefined` |
| `routerAnimation` | -- | The transition animation when navigating to another page. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` |
| `routerDirection` | `router-direction` | The transition direction when navigating to another page. | `"back" \| "forward" \| "root"` | `'forward'` |

View File

@@ -1,10 +1,14 @@
import { Animation, AnimationBuilder, ComponentRef, FrameworkDelegate, Mode } from '../../interface';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Mode } from '../../interface';
import { ViewController } from './view-controller';
export type NavDirection = 'back' | 'forward';
export type NavComponent = ComponentRef | ViewController;
export interface NavComponentWithProps<T = any> {
component: NavComponent;
componentProps?: ComponentProps<T> | null;
}
export interface NavResult {
hasCompleted: boolean;

View File

@@ -2,7 +2,7 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch, h
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavComponentWithProps, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { assert } from '../../utils/helpers';
import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition';
@@ -156,7 +156,7 @@ export class Nav implements NavOutlet {
return this.queueTrns(
{
insertStart: -1,
insertViews: [{ page: component, params: componentProps }],
insertViews: [{ component, componentProps }],
opts
},
done
@@ -184,7 +184,7 @@ export class Nav implements NavOutlet {
return this.queueTrns(
{
insertStart: insertIndex,
insertViews: [{ page: component, params: componentProps }],
insertViews: [{ component, componentProps }],
opts
},
done
@@ -204,7 +204,7 @@ export class Nav implements NavOutlet {
@Method()
insertPages(
insertIndex: number,
insertComponents: NavComponent[],
insertComponents: NavComponent[] | NavComponentWithProps[],
opts?: NavOptions | null,
done?: TransitionDoneFn
): Promise<boolean> {
@@ -326,7 +326,7 @@ export class Nav implements NavOutlet {
done?: TransitionDoneFn
): Promise<boolean> {
return this.setPages(
[{ page: component, params: componentProps }],
[{ component, componentProps }],
opts,
done
);
@@ -344,7 +344,7 @@ export class Nav implements NavOutlet {
*/
@Method()
setPages(
views: any[],
views: NavComponent[] | NavComponentWithProps[],
opts?: NavOptions | null,
done?: TransitionDoneFn
): Promise<boolean> {
@@ -372,7 +372,8 @@ export class Nav implements NavOutlet {
setRouteId(
id: string,
params: ComponentProps | undefined,
direction: RouterDirection
direction: RouterDirection,
animation?: AnimationBuilder
): Promise<RouteWrite> {
const active = this.getActiveSync();
if (matches(active, id, params)) {
@@ -410,15 +411,20 @@ export class Nav implements NavOutlet {
if (viewController) {
finish = this.popTo(viewController, {
...commonOpts,
direction: 'back'
direction: 'back',
animationBuilder: animation
});
} else if (direction === 'forward') {
finish = this.push(id, params, commonOpts);
finish = this.push(id, params, {
...commonOpts,
animationBuilder: animation
});
} else if (direction === 'back') {
finish = this.setRoot(id, params, {
...commonOpts,
direction: 'back',
animated: true
animated: true,
animationBuilder: animation
});
}
}
@@ -507,7 +513,7 @@ export class Nav implements NavOutlet {
// 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath.
// 8. _transitionFinish(): called once the transition finishes
// 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them.
private queueTrns(
private async queueTrns(
ti: TransitionInstruction,
done: TransitionDoneFn | undefined
): Promise<boolean> {
@@ -521,6 +527,25 @@ export class Nav implements NavOutlet {
});
ti.done = done;
/**
* If using router, check to see if navigation hooks
* will allow us to perform this transition. This
* is required in order for hooks to work with
* the ion-back-button or swipe to go back.
*/
if (ti.opts && ti.opts.updateURL !== false && this.useRouter) {
const router = document.querySelector('ion-router');
if (router) {
const canTransition = await router.canTransition();
if (canTransition === false) {
return Promise.resolve(false);
} else if (typeof canTransition === 'string') {
router.push(canTransition, ti.opts!.direction || 'back');
return Promise.resolve(false);
}
}
}
// Normalize empty
if (ti.insertViews && ti.insertViews.length === 0) {
ti.insertViews = undefined;
@@ -623,6 +648,19 @@ export class Nav implements NavOutlet {
const requiresTransition =
(ti.enteringRequiresTransition || ti.leavingRequiresTransition) &&
enteringView !== leavingView;
if (requiresTransition && ti.opts && leavingView) {
const isBackDirection = ti.opts.direction === 'back';
/**
* If heading back, use the entering page's animation
* unless otherwise specified by the developer.
*/
if (isBackDirection) {
ti.opts.animationBuilder = ti.opts.animationBuilder || (enteringView && enteringView.animationBuilder);
}
leavingView.animationBuilder = ti.opts.animationBuilder;
}
const result = requiresTransition
? await this.transition(enteringView!, leavingView, ti)
: {
@@ -920,16 +958,27 @@ export class Nav implements NavOutlet {
for (let i = views.length - 1; i >= 0; i--) {
const view = views[i];
/**
* When inserting multiple views via insertPages
* the last page will be transitioned to, but the
* others will not be. As a result, a DOM element
* will only be created for the last page inserted.
* As a result, it is possible to have views in the
* stack that do not have `view.element` yet.
*/
const element = view.element;
if (i > activeViewIndex) {
// this view comes after the active view
// let's unload it
lifecycle(element, LIFECYCLE_WILL_UNLOAD);
this.destroyView(view);
} else if (i < activeViewIndex) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
setPageHidden(element!, true);
if (element) {
if (i > activeViewIndex) {
// this view comes after the active view
// let's unload it
lifecycle(element, LIFECYCLE_WILL_UNLOAD);
this.destroyView(view);
} else if (i < activeViewIndex) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
setPageHidden(element!, true);
}
}
}
}

View File

@@ -80,7 +80,7 @@ Type: `Promise<boolean>`
### `insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
### `insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
Inserts an array of components into the navigation stack at the specified index.
The last component in the array will become instantiated as a view, and animate
@@ -145,7 +145,7 @@ Type: `Promise<boolean>`
### `setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
### `setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
Set the views of the current navigation stack and navigate to the last view.
By default animations are disabled, but they can be enabled by passing options

View File

@@ -119,9 +119,9 @@ describe('NavController', () => {
mockViews(nav, [view1]);
const view2 = mockView(MockView2);
await nav.push(view2, null, null, trnsDone);
const hasCompleted = true;
const requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith(
@@ -818,8 +818,8 @@ describe('NavController', () => {
const view5 = mockView(MockView5);
await nav.setPages([
{ page: view4 },
{ page: view5 }
{ component: view4 },
{ component: view5 }
], null, trnsDone);
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
@@ -924,10 +924,10 @@ describe('NavController', () => {
const MockView3 = 'mock-view3';
const MockView4 = 'mock-view4';
const MockView5 = 'mock-view5';
const mockWebAnimation = (el: HTMLElement) => {
Element.prototype.animate = () => {};
el.animate = () => {
const animation = {
stop: () => {},
@@ -935,13 +935,13 @@ describe('NavController', () => {
cancel: () => {},
onfinish: undefined
}
animation.play = () => {
if (animation.onfinish) {
animation.onfinish();
}
}
return animation;
}
}
@@ -953,9 +953,9 @@ describe('NavController', () => {
const view = new ViewController(component, params);
view.element = document.createElement(component) as HTMLElement;
mockWebAnimation(view.element);
return view;
}

View File

@@ -1,4 +1,4 @@
import { ComponentProps, FrameworkDelegate } from '../../interface';
import { AnimationBuilder, ComponentProps, FrameworkDelegate, NavComponentWithProps } from '../../interface';
import { attachComponent } from '../../utils/framework-delegate';
import { assert } from '../../utils/helpers';
@@ -12,6 +12,7 @@ export class ViewController {
nav?: any;
element?: HTMLElement;
delegate?: FrameworkDelegate;
animationBuilder?: AnimationBuilder;
constructor(
public component: any,
@@ -88,13 +89,20 @@ export const convertToView = (page: any, params: ComponentProps | undefined): Vi
return new ViewController(page, params);
};
export const convertToViews = (pages: any[]): ViewController[] => {
export const convertToViews = (pages: NavComponentWithProps[]): ViewController[] => {
return pages.map(page => {
if (page instanceof ViewController) {
return page;
}
if ('page' in page) {
return convertToView(page.page, page.params);
if ('component' in page) {
/**
* TODO Ionic 6:
* Consider switching to just using `undefined` here
* as well as on the public interfaces and on
* `NavComponentWithProps`. Previously `pages` was
* of type `any[]` so TypeScript did not catch this.
*/
return convertToView(page.component, (page.componentProps === null) ? undefined : page.componentProps);
}
return convertToView(page, undefined);
}).filter(v => v !== null) as ViewController[];

View File

@@ -21,6 +21,7 @@ import { iosLeaveAnimation } from './animations/ios.leave';
})
export class Picker implements ComponentInterface, OverlayInterface {
private durationTimeout: any;
lastFocus?: HTMLElement;
@Element() el!: HTMLIonPickerElement;
@@ -100,7 +101,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
*/
@Event({ eventName: 'ionPickerDidDismiss' }) didDismiss!: EventEmitter<OverlayEventDetail>;
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -236,7 +237,10 @@ export class Picker implements ComponentInterface, OverlayInterface {
tappable={this.backdropDismiss}
>
</ion-backdrop>
<div class="picker-wrapper" role="dialog">
<div tabindex="0"></div>
<div class="picker-wrapper ion-overlay-wrapper" role="dialog">
<div class="picker-toolbar">
{this.buttons.map(b => (
<div class={buttonWrapperClass(b)}>
@@ -259,6 +263,8 @@ export class Picker implements ComponentInterface, OverlayInterface {
<div class="picker-below-highlight"></div>
</div>
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -28,6 +28,7 @@ export class Popover implements ComponentInterface, OverlayInterface {
private usersElement?: HTMLElement;
presented = false;
lastFocus?: HTMLElement;
@Element() el!: HTMLIonPopoverElement;
@@ -115,7 +116,7 @@ export class Popover implements ComponentInterface, OverlayInterface {
*/
@Event({ eventName: 'ionPopoverDidDismiss' }) didDismiss!: EventEmitter<OverlayEventDetail>;
constructor() {
connectedCallback() {
prepareOverlay(this.el);
}
@@ -219,10 +220,15 @@ export class Popover implements ComponentInterface, OverlayInterface {
onIonBackdropTap={this.onBackdropTap}
>
<ion-backdrop tappable={this.backdropDismiss} visible={this.showBackdrop}/>
<div class="popover-wrapper">
<div tabindex="0"></div>
<div class="popover-wrapper ion-overlay-wrapper">
<div class="popover-arrow"></div>
<div class="popover-content"></div>
</div>
<div tabindex="0"></div>
</Host>
);
}

View File

@@ -1,7 +1,42 @@
import { testPopover } from '../test.utils';
import { newE2EPage } from '@stencil/core/testing';
const DIRECTORY = 'basic';
const getActiveElementText = async (page) => {
const activeElement = await page.evaluateHandle(() => document.activeElement);
return await page.evaluate(el => el && el.textContent, activeElement);
}
test('popover: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
await page.click('#basic-popover');
await page.waitForSelector('#basic-popover');
let popover = await page.find('ion-popover');
expect(popover).not.toBe(null);
await popover.waitForVisible();
await page.keyboard.press('Tab');
const activeElementText = await getActiveElementText(page);
expect(activeElementText).toEqual('Item 0');
await page.keyboard.down('Shift');
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
const activeElementTextTwo = await getActiveElementText(page);
expect(activeElementTextTwo).toEqual('Item 3');
await page.keyboard.press('Tab');
const activeElementTextThree = await getActiveElementText(page);
expect(activeElementTextThree).toEqual('Item 0');
});
test('popover: basic', async () => {
await testPopover(DIRECTORY, '#basic-popover');
});

View File

@@ -75,10 +75,10 @@
<ion-content>
<ion-list>
<ion-list-header><ion-label>Ionic</ion-label></ion-list-header>
<ion-item><ion-label>Item 0</ion-label></ion-item>
<ion-item><ion-label>Item 1</ion-label></ion-item>
<ion-item><ion-label>Item 2</ion-label></ion-item>
<ion-item><ion-label>Item 3</ion-label></ion-item>
<ion-item button><ion-label>Item 0</ion-label></ion-item>
<ion-item button><ion-label>Item 1</ion-label></ion-item>
<ion-item button><ion-label>Item 2</ion-label></ion-item>
<ion-item button><ion-label>Item 3</ion-label></ion-item>
</ion-list>
</ion-content>
`;

View File

@@ -315,9 +315,9 @@ export class ReorderExample {
## Shadow Parts
| Part | Description |
| -------- | ------------------------------- |
| `"icon"` | The icon of the reorder handle. |
| Part | Description |
| -------- | ----------------------------------------------- |
| `"icon"` | The icon of the reorder handle (uses ion-icon). |
## Dependencies

View File

@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Host, Listen, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
/**
* @part icon - The icon of the reorder handle.
* @part icon - The icon of the reorder handle (uses ion-icon).
*/
@Component({
tag: 'ion-reorder',

View File

@@ -1,19 +1,233 @@
# ion-route
The route component takes a component and renders it when the Browser URl matches the url property.
The route component takes a component and renders it when the Browser URL matches the url property.
> Note: this component should only be used with vanilla and Stencil JavaScript projects. For Angular projects, use [`ion-router-outlet`](../router-outlet) and the Angular router.
## Navigation Hooks
Navigation hooks can be used to perform tasks or act as navigation guards. Hooks are used by providing functions to the `beforeEnter` and `beforeLeave` properties on each `ion-route`. Returning `true` allows navigation to proceed, while returning `false` causes it to be cancelled. Returning an object of type `NavigationHookOptions` allows you to redirect navigation to another page.
## Interfaces
```typescript
interface NavigationHookResult {
/**
* A valid path to redirect navigation to.
*/
redirect: string;
}
```
<!-- Auto Generated Below -->
## Usage
### Javascript
```html
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard"></ion-route>
<ion-route url="/new-message" component="page-new-message"></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
```
```javascript
const dashboardPage = document.querySelector('ion-route[url="/dashboard"]');
dashboardPage.beforeEnter = isLoggedInGuard;
const newMessagePage = document.querySelector('ion-route[url="/dashboard"]');
newMessagePage.beforeLeave = hasUnsavedDataGuard;
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
}
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
}
const confirmDiscardChanges = async () => {
const alert = document.createElement('ion-alert');
alert.header = 'Discard Unsaved Changes?';
alert.message = 'Are you sure you want to leave? Any unsaved changed will be lost.';
alert.buttons = [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
}
];
document.body.appendChild(alert);
await alert.present();
const { role } = await alert.onDidDismiss();
return (role === 'Cancel') ? false : true;
}
```
### Stencil
```typescript
import { Component, h } from '@stencil/core';
import { alertController } from '@ionic/core';
@Component({
tag: 'router-example',
styleUrl: 'router-example.css'
})
export class RouterExample {
render() {
return (
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard" beforeEnter={isLoggedInGuard}></ion-route>
<ion-route url="/new-message" component="page-new-message" beforeLeave={hasUnsavedDataGuard}></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
)
}
}
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
}
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
}
const confirmDiscardChanges = async () => {
const alert = await alertController.create({
header: 'Discard Unsaved Changes?',
message: 'Are you sure you want to leave? Any unsaved changed will be lost.',
buttons: [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
}
]
});
await alert.present();
const { role } = await alert.onDidDismiss();
return (role === 'Cancel') ? false : true;
}
```
### Vue
```html
<template>
<ion-router>
<ion-route url="/home" component="page-home"></ion-route>
<ion-route url="/dashboard" component="page-dashboard" :beforeEnter="isLoggedInGuard"></ion-route>
<ion-route url="/new-message" component="page-new-message" :beforeLeave="hasUnsavedDataGuard"></ion-route>
<ion-route url="/login" component="page-login"></ion-route>
</ion-router>
</template>
<script>
import { alertController } from '@ionic/vue';
const isLoggedInGuard = async () => {
const isLoggedIn = await UserData.isLoggedIn(); // Replace this with actual login validation
if (isLoggedIn) {
return true;
} else {
return { redirect: '/login' }; // If a user is not logged in, they will be redirected to the /login page
}
}
const hasUnsavedDataGuard = async () => {
const hasUnsavedData = await checkData(); // Replace this with actual validation
if (hasUnsavedData) {
return await confirmDiscardChanges();
} else {
return true;
}
}
const confirmDiscardChanges = async () => {
const alert = await alertController.create({
header: 'Discard Unsaved Changes?',
message: 'Are you sure you want to leave? Any unsaved changed will be lost.',
buttons: [
{
text: 'Cancel',
role: 'Cancel',
},
{
text: 'Discard',
role: 'destructive',
}
]
});
await alert.present();
const { role } = await alert.onDidDismiss();
return (role === 'Cancel') ? false : true;
}
</script>
```
## Properties
| Property | Attribute | Description | Type | Default |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ----------- |
| `component` _(required)_ | `component` | Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select. | `string` | `undefined` |
| `componentProps` | -- | A key value `{ 'red': true, 'blue': 'white'}` containing props that should be passed to the defined component when rendered. | `undefined \| { [key: string]: any; }` | `undefined` |
| `url` | `url` | Relative path that needs to match in order for this route to apply. Accepts paths similar to expressjs so that you can define parameters in the url /foo/:bar where bar would be available in incoming props. | `string` | `''` |
| Property | Attribute | Description | Type | Default |
| ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------- |
| `beforeEnter` | -- | A navigation hook that is fired when the route tries to enter. Returning `true` allows the navigation to proceed, while returning `false` causes it to be cancelled. Returning a `NavigationHookOptions` object causes the router to redirect to the path specified. | `(() => boolean \| NavigationHookOptions \| Promise<NavigationHookResult>) \| undefined` | `undefined` |
| `beforeLeave` | -- | A navigation hook that is fired when the route tries to leave. Returning `true` allows the navigation to proceed, while returning `false` causes it to be cancelled. Returning a `NavigationHookOptions` object causes the router to redirect to the path specified. | `(() => boolean \| NavigationHookOptions \| Promise<NavigationHookResult>) \| undefined` | `undefined` |
| `component` _(required)_ | `component` | Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select. | `string` | `undefined` |
| `componentProps` | -- | A key value `{ 'red': true, 'blue': 'white'}` containing props that should be passed to the defined component when rendered. | `undefined \| { [key: string]: any; }` | `undefined` |
| `url` | `url` | Relative path that needs to match in order for this route to apply. Accepts paths similar to expressjs so that you can define parameters in the url /foo/:bar where bar would be available in incoming props. | `string` | `''` |
## Events

View File

@@ -0,0 +1,5 @@
export type NavigationHookCallback = () => NavigationHookResult | Promise<NavigationHookResult>;
export type NavigationHookResult = boolean | NavigationHookOptions;
export interface NavigationHookOptions {
redirect: string;
}

Some files were not shown because too many files have changed in this diff Show More