Compare commits

..

116 Commits

Author SHA1 Message Date
Liam DeBeasi
fdfe0b10d7 4.3.1 (#18150) 2019-04-26 14:42:04 -04:00
Liam DeBeasi
b839e6fd97 fix(): sanitize components using innerHTML (#18146) 2019-04-26 12:38:28 -04:00
Liam DeBeasi
eb3cbe45a7 fix(angular): support replaceUrl with angular <7.2 (#18106)
* fix(angular): support replaceUrl with angular <7.2

* run linter
2019-04-23 10:47:05 -04:00
Brandy Carney
6fbb90896b merge release-4.3.0 2019-04-17 15:42:04 -04:00
Brandy Carney
73e2123ce8 4.3.0 2019-04-17 15:27:06 -04:00
Brandy Carney
70db7080b1 test(toast): remove the safe area to enable clicks on all buttons 2019-04-17 15:06:55 -04:00
Brandy Carney
9e63947e3c fix(action-sheet): default buttons to empty array
fixes an error with the timing of the buttons being added
2019-04-17 12:56:32 -04:00
Brandy Carney
0dd2f34dfa Merge branch 'stable' into release-4.3.0 2019-04-17 12:26:52 -04:00
Tomasz Nicieja
07e739a364 feat(toast): add variables to change position start/end of toast (#17961)
closes #17854
2019-04-16 18:13:14 -04:00
Brandy Carney
e5c8c10029 fix(components): include mode classes on components for use in shadow (#17838)
- removes mode-less component classes from the internal CSS, use element instead
- adds mode specific classes `md` or `ios` for styling inside of shadow components
- adds e2e test that verifies mode classes exist on all ionic components, plus checks for specific classes that the components need for internal styling

fixes #17608
2019-04-16 17:28:21 -04:00
Brandy Carney
38ae3620a2 fix(textarea): update label alignment for inputs and textareas (#18042)
- aligns label and textarea to baseline
- updates floating and stacked labels in items to align closer to the md spec

fixes #16187
2019-04-16 14:57:32 -04:00
Brandy Carney
72be80cb58 fix(item): use the correct input highlight for an inset line item (#18052)
fixes #18051
2019-04-16 13:14:26 -04:00
Liam DeBeasi
cc60b60135 fix(datetime): date strings no longer revert to previous day (#18018)
fixes #17977
2019-04-16 12:34:35 -04:00
Liam DeBeasi
447497427e fix(slides): expose interface to provide custom animations (#17959)
fixes #16616

Co-Authored-By: CFT-Chris <mail@chrislo.ca>
2019-04-16 12:02:47 -04:00
Liam DeBeasi
18b347b4e9 fix(slides): allow zoom to work
fixes #17981
2019-04-16 10:51:23 -04:00
konnectappdev
29bb4fcb05 fix(input): prevent input from losing focus when tapping clear button (#18004)
fixes #18002
2019-04-16 10:01:56 -04:00
Jason
11aa48c83c chore(reorder-group): correct typo in ReorderGroupState enum (#18017)
fixes #18016
2019-04-16 09:39:58 -04:00
Michael Asimakopoulos
f13722cc20 fix(item-sliding): hide opposite side options when other side is open (#17986)
* fix(item-sliding): hide opposite side options

* Fix padding issue

* add visibility hidden for item options
2019-04-15 17:20:15 -04:00
Liam DeBeasi
983382c327 fix(toast): update toast interface to include buttons 2019-04-15 17:09:00 -04:00
Liam DeBeasi
1b16e1f378 chore(): add rtl label to appropriate tests 2019-04-15 16:50:03 -04:00
Brandy Carney
494991e9fb fix(textarea): float label when a value is changed async (#18024)
emits styles to the parent item on value change so that the item will get the proper class to float the label

fixes #17555 fixes #17559
2019-04-12 13:23:04 -04:00
Liam DeBeasi
1199c53437 test(): remove custom screenshot names (#18022)
* test(): remove custom screenshot names

* update test names
2019-04-12 13:02:03 -04:00
Zachary Keeton
6e1a8f1df2 feat(toast): add header and additional custom toast buttons (#17147)
Adds a `header` and `buttons` property to toast. This allows for a toast header to be passed and multiple buttons including action buttons and icon only buttons which matches the Material Design spec. Adds hover states to the button to match the spec. Updates usage section to recommend the new way of passing a close button using the buttons array and `cancel` role. If a button is passed using the cancel role default the color to match the spec. Buttons will default to the `end` side but have the option of being placed on the `start` side.

Co-authored-by: Simon Hänisch <simonhaenisch@users.noreply.github.com>
Co-authored-by: Brandy Carney <brandy@ionic.io>

closes #16791 closes #16237 closes #17611
2019-04-11 11:46:10 -04:00
Liam DeBeasi
52e5a8d3e3 fix(angular): back button goes back to proper tab (#18005)
fixes #17278 
fixes #15216
2019-04-10 16:18:59 -04:00
Liam DeBeasi
0d17e05edc chore(docs): update PR wording 2019-04-09 11:13:35 -04:00
Michael Tintiuc
fa1317359a fix(vue): use direction type from core (#17901) 2019-04-08 14:59:09 -04:00
Brandy Carney
d87170db3a docs(github): update pull request template 2019-04-08 12:07:51 -04:00
Stefanos Anagnostou
5756789b42 docs(breaking): add ion-button shape attribute (#17985) 2019-04-08 11:01:22 -04:00
Matt Netkow
b081ca4dd0 docs(modal): update Angular usage code (#17978)
closes ionic-team/ionic-docs#620
2019-04-06 12:13:50 -04:00
Liam DeBeasi
77c980b032 chore(): add label for capacitor issues
chore(): add label for capacitor issues
2019-04-05 14:03:30 -04:00
Liam DeBeasi
05eb5ddaf8 Merge branch 'master' into ionitron-capacitor 2019-04-05 13:34:14 -04:00
Liam DeBeasi
9de09bd4f5 docs(modal): add lazy loading usage
docs(modal): add lazy loading usage
2019-04-05 13:33:52 -04:00
Liam DeBeasi
2f34f52536 move lazy loading to angular usage 2019-04-05 12:32:52 -04:00
Liam DeBeasi
ec3e19e66c Merge branch 'master' into modal-lazy-loading-docs 2019-04-05 12:14:19 -04:00
Liam DeBeasi
a512287e4b chore(): add label for capacitor issues 2019-04-05 08:51:15 -04:00
Brandy Carney
48814ab134 chore(github): update issue template title 2019-04-04 16:29:30 -04:00
Brandy Carney
28249924a8 chore(github): update issue template title 2019-04-04 16:28:27 -04:00
Brandy Carney
e620d813b3 chore(github): update issue template title 2019-04-04 16:26:49 -04:00
Liam DeBeasi
0db905a871 chore(): update release process to document version branches
chore(): update release process to document version branches
2019-04-04 10:00:20 -04:00
Liam DeBeasi
c87e26131a docs(modal): add lazy loading usage 2019-04-03 12:44:00 -04:00
Liam DeBeasi
06c4233a54 change I to lowercase 2019-04-03 12:16:48 -04:00
Liam DeBeasi
9585723dda chore(): update release process to document version branches 2019-04-03 12:13:35 -04:00
Liam DeBeasi
046ca8f732 merge release-4.2.0
Release 4.2.0
2019-04-03 10:50:26 -04:00
Liam DeBeasi
fdd2978774 merge release-4.2.0
Release 4.2.0
2019-04-03 10:49:11 -04:00
Liam DeBeasi
72a47548ce 4.2.0 2019-04-03 10:38:58 -04:00
Liam DeBeasi
db1f90d111 Merge branch 'stable' into release-4.2.0 2019-04-03 10:05:18 -04:00
Mike Hartington
9443bfeca6 fix(angular): update ng-add schematic 2019-04-03 09:51:11 -04:00
Brandy Carney
2ceacf9d50 remove commented out file 2019-04-02 17:49:15 -04:00
Asif Rahman
06c3048828 fix(angular): route observables available earlier (#17914) 2019-04-01 09:21:14 -04:00
Brandy Carney
b47c53df72 docs(issues): update bot to add more line breaks 2019-03-29 12:57:38 -04:00
Brandy Carney
fdd424a2c0 docs(issues): update issue bot to comment on help wanted issues 2019-03-29 11:50:25 -04:00
Mike Hartington
a710fdcb66 style(angular): fix linter 2019-03-29 10:07:47 -04:00
Asif Rahman
d9a7c635ee fix(angular): support relative router links
Closes #17888, closes #16534, closes #16736, closes #16954
2019-03-29 09:58:16 -04:00
Liam DeBeasi
f2c8db9a0b fix(angular): account for replaceUrl option (#17879)
fixes #15181
2019-03-29 09:40:57 -04:00
Kelvin Dart
346ecb2a3c fix(item-option): styling and behaviour for disabled item-option (#17909)
fixes #17905
2019-03-28 14:20:29 -04:00
Liam DeBeasi
51614c1b32 test(fab): increase fab timeout (#17906) 2019-03-28 10:41:40 -04:00
Michael Tintiuc
f69f221e10 docs(range): update ion-range vue docs (#17885) 2019-03-28 10:15:56 -04:00
pwespi
29d0d0ef28 chore(): update beta url refs #17900) 2019-03-28 08:29:20 -04:00
Mike Hartington
17e8c73b9d chore(vue): bump 2019-03-27 15:52:25 -04:00
David
dbb39cd00d fix(css): resolve spinner and checkbox issues on older chrome versions (#17896)
fixes #17524 
fixes #17501
2019-03-27 12:35:53 -04:00
Ivan Tham
04f931f694 feat(img): add ionError event (#17134)
resolves #16947
2019-03-27 10:58:03 -04:00
Adam LaCombe
352797e932 fix(reorder-group): add ability to reorder items inside shadow (#17747)
- adds ability to reorder items from within a shadow dom component
- adds interactive test

fixes #17746
2019-03-26 17:00:44 -04:00
David Boho
a8a48a4ca4 fix(virtual-scroll): use correct item top calculation with header or footer function (#15948) (#17345)
- use the right index in updateVDom to update the top transition ()
- extend unit test to verify the top is also calculated right with a given headerFn and footerFn
- update the visibility of the node also if a given approxHeaderHeight/approxFooterHeight matches the calculated height

fixes #15948 fixes #17298
2019-03-26 16:17:03 -04:00
Seth Lilly
016fa16d44 feat(range): add ticks attribute (#17718)
closes #17717
2019-03-26 14:33:16 -04:00
Mike Hartington
583c43127b chore(vue): update vue 2019-03-26 12:15:00 -04:00
Liam DeBeasi
c47764c670 tests(): increase timeout for fab and menu tests (#17826)
* increase timeouts for fab and menu tests

* increase menu timeout

* wait for menu to be available

* change 250 to 150

* revert 150ms timeout for menu

* testing longer menu timeout

* testing increased timeout

* add enable command

* add enable menu ctrl method call

* remove debug timeouts
2019-03-26 11:41:29 -04:00
Liam DeBeasi
1ecfcd1902 fix(select): fix options being added async while overlay is open (#17860)
fixes #15716
fixes #17851
2019-03-26 11:29:24 -04:00
Michael Tintiuc
dceec07390 fix(vue): back button event handling (#17877) 2019-03-26 08:44:28 -04:00
Liam DeBeasi
eaec9ca791 docs(item): update item detail property usage (#17861)
updates the documentation for item to better explain how to use the detail prop
2019-03-25 13:39:15 -04:00
Brandy Carney
fccaaf8a91 fix(progress-bar): flip rtl using the existing reversed property (#17464)
updates the progress bar to always use the ltr dir but take advantage of the reversed property when dir is rtl.

references #17012
2019-03-25 13:03:19 -04:00
pwespi
34f6f1d736 fix(datetime): account for 0 value when setting up picker columns (#17869) 2019-03-25 08:49:29 -04:00
Lukas H Herman
0cb7db0599 fix(vue): fixed popover controller (#17865) 2019-03-24 20:21:01 -04:00
Michael Tintiuc
c148d3125b chore(vue): update rollup config (#17863) 2019-03-24 20:11:37 -04:00
Josh Thomas
c29f5a65b4 fix(react): ensure tabs are resilient to optional tabs. (#17862) 2019-03-22 14:45:40 -05:00
Bengt Weiße
424879df8e test(datetime): add daysInMonth tests (#17558)
* fix(Datetime): correct days in month calculation

* test(DateTimeUtil): daysInMonth

* revert fn changes, keep tests
2019-03-22 12:59:43 -04:00
Michael Tintiuc
71e5994c94 feat(vue): support ion-vue-router (#17821) 2019-03-22 12:04:46 -04:00
Michael Tintiuc
ee7167512f feat(vue): support for ion-tabs (#17678)
* Add ion-tabs support, QOL fixes

* Fix @ionic/core version, rebuild core to include docs

* Update router

* Add support for IonTabsWillChange and IonTabsDidChange events

* Update usage docs

* Merge core and user provided click handlers in ion-tab-button

* rename file to be consistent
2019-03-22 10:56:53 -04:00
Pierric Cistac
439b10e10d fix(angular): overlay.getTop can return undefined (#17802) 2019-03-21 13:07:08 -04:00
Mike Hartington
27168d938a docs(): update datetime docs (#17812)
* docs(): update datetime docs

* docs(): cleanup

* Update core/src/components/datetime/readme.md

Co-Authored-By: mhartington <mhartington@users.noreply.github.com>

* Update core/src/components/datetime/usage/angular.md

Co-Authored-By: mhartington <mhartington@users.noreply.github.com>
2019-03-21 12:48:39 -04:00
Liam DeBeasi
b6569254a3 fix(datetime): recalculate day column when month is changed (#17846)
Co-Authored-By: KillerCodeMonkey <bengtler@gmail.com>
Co-Authored-By: olivercodes <boliver@linux.com>
Co-Authored-By: liamdebeasi <liamdebeasi@users.noreply.github.com>
2019-03-21 09:07:44 -04:00
Liam DeBeasi
e915aeda6b Revert "fix(datetime): recalculate day column when month or year is changed (#17815)" (#17845)
This reverts commit 9273f97a0c.
2019-03-21 09:06:06 -04:00
Liam DeBeasi
9273f97a0c fix(datetime): recalculate day column when month or year is changed (#17815)
Co-Authored-By: KillerCodeMonkey<bengtler@gmail.com>
Co-Authored-By: olivercodes <boliver@linux.com>
Co-Authored-By: liamdebeasi <liamdebeasi@users.noreply.github.com>
2019-03-21 08:51:06 -04:00
Liam DeBeasi
aca78f5ac6 merge release-4.1.2
Release 4.1.2
2019-03-20 14:04:18 -04:00
Liam DeBeasi
2d77cfe27e merge release-4.1.2
Release 4.1.2
2019-03-20 14:01:41 -04:00
Liam DeBeasi
ca36139c80 4.1.2 2019-03-20 13:48:35 -04:00
Liam DeBeasi
9b71e47e0b clarify wording on process doc 2019-03-20 13:20:50 -04:00
Brandy Carney
807820f31b fix(item): add missing ripple color CSS property (#17814)
Adds missing --ripple-color var to item

fixes #17523
2019-03-19 11:14:01 -04:00
Romulo Cintra
67a913773b fix(input): use max-height for input for consistency across browsers (#17394)
This PR gives the same behavior in Chrome, Safari and FF, when the input is not wrapped in html tags
2019-03-19 10:33:15 -04:00
Abdelaziz Bennouna
617453ba43 fix(popover): update animation origin in RTL/MD (#17645)
Original main-axis origin fix was flawed in case of RTL.
Invert the animation’s main-axis origin in case the popover’s position is adjusted.

references #17012
2019-03-15 18:18:06 -04:00
Abdelaziz Bennouna
9c48fa715d fix(spinner): fix default spinner logic for relevant components (#17660)
- In the **`loading`** component, fallback to `spinner` icon config before platform default.
- In both **`refresher`** & **`infinite-scroll`** components, use platform default as final fallback.
- In **`infinite-scroll`** test templates, and the _basic_ **`virtual-scroll`** test template, fix loading _spinner_ and _text_ attributes.

fixes #17659
2019-03-15 17:52:33 -04:00
Jurgen Jocubeit
b5a393bd48 docs(tabs): correct ion-route property path to url (#17794) 2019-03-15 17:05:40 -04:00
Brandy Carney
2225941efb fix(toggle): do not use the contrast color for toggle inner color (#17798)
fixes #17536
2019-03-15 13:51:12 -04:00
Brandy Carney
14f758ca97 fix(item-option): add styling for slots other than icon-only (#17711)
styles the item option properly when icons are slotted. This was not working before at all for `top` and `bottom` slots.

- removes the e2e test from item/sliding in favor of all of them being under item-sliding
- adds more usage examples for all frameworks
- updates the styling for an anchor option so that it displays properly
- updates the standalone e2e test to take screenshots of the start icon, top icon, and anchor options
- updates the interactive e2e test to work locally
- cleaned up the CSS for start/end icons
- verified the styles for ben approval 💪

closes #17737 fixes #17402
2019-03-15 13:46:57 -04:00
Liam DeBeasi
29dbd0770c fix(platform): account for larger tablets (#17630)
* fix(platform): account for larger tablets

* lint files

* add utils file, clean up tests, add more tests

* update tests with extra desktop checks

* change window to win
2019-03-15 13:43:39 -04:00
Brandy Carney
f6783dbd2e fix(chip): use transparent outline on color chips (#17719) 2019-03-15 12:47:31 -04:00
Zachary Keeton
7d0120789c fix(transition): animate all toolbars in iOS page transitions (#17224)
* fix(transition): add all header toolbars to ios transition

* test(nav): add 2nd toolbar to test transition

* fix(transition): add remaining toolbar children to animation

* fix(transition): fix incorrect variable name

* fix(toolbar): fix bug in safari, clean up code, update test

* fix typo

* change elems to els

* change Elem to El
2019-03-14 15:38:40 -04:00
Liam DeBeasi
f097276388 fix(datetime): add additional checks for placeholders in angular (#17774) 2019-03-13 11:46:56 -04:00
Liam DeBeasi
6939f7941f fix(datetime): fix placeholder not being respected (#17772) 2019-03-13 11:07:06 -04:00
Adam Bradley
c551f891d3 chore(screenshot): slow down concurrent screenshot downloads (#17771) 2019-03-13 09:03:53 -05:00
Liam DeBeasi
ec3acc9ec8 fix(datetime): fix edge cases with datetime and invalid values (#17769)
* fix edge cases with datetime and invalid values

* clean up code

* revert let changes

* run linter
2019-03-13 09:39:38 -04:00
Liam DeBeasi
bab56e8947 fix(datetime): default to local date (#17706) 2019-03-11 09:46:17 -05:00
Brandy Carney
eb5494e932 Merge branch 'stable' 2019-03-07 17:40:04 -05:00
Brandy Carney
817f9197f5 merge release-4.1.1
fix(display): update to correct css classes
2019-03-07 17:39:30 -05:00
Brandy Carney
f9cd169cc7 4.1.1 2019-03-07 11:45:09 -05:00
Brandy Carney
cd0f03e4b6 chore(scripts): fix typo of hotfix 2019-03-07 11:32:37 -05:00
Brandy Carney
cabbeb2c2f fix(display): update to correct css classes 2019-03-07 11:26:17 -05:00
Brandy Carney
bd38f26f9c docs(label): update usage to place text-wrap on label (#17705)
closes ionic-team/ionic-docs#467
2019-03-07 11:11:56 -05:00
Brandy Carney
7e6d511969 docs(process): update process document (#17581) 2019-03-06 14:00:48 -05:00
Brandy Carney
c8ce12c70b merge release-4.1.0 2019-03-06 13:27:49 -05:00
Brandy Carney
8174de1f78 merge release-4.1.0 2019-03-06 13:25:12 -05:00
Brandy Carney
0e844a189b style(lint): lint errors too 2019-03-06 12:45:35 -05:00
Brandy Carney
b424553c0c docs(changelog): use proper tag comparison 2019-03-06 12:38:07 -05:00
Brandy Carney
d76a3442d8 docs(changelog): add hydrogen 2019-03-06 12:26:06 -05:00
Brandy Carney
97174a284a chore(build): fix docs generation 2019-03-06 12:24:37 -05:00
Brandy Carney
3a19329dbd chore(build): fix range 2019-03-06 12:15:47 -05:00
Brandy Carney
dfb0c7f3c1 style(changelog): copy editing 2019-03-06 12:01:19 -05:00
272 changed files with 6816 additions and 2019 deletions

View File

@@ -1,4 +1,4 @@
<!-- Before submitting an issue, please consult our docs (https://beta.ionicframework.com/docs/) and API reference (https://beta.ionicframework.com/docs/api/) -->
<!-- Before submitting an issue, please consult our docs (https://ionicframework.com/docs/) and API reference (https://ionicframework.com/docs/api/) -->
<!-- Please make sure you are posting an issue pertaining to the Ionic Framework. If you are having an issue with the Ionic Appflow services (Ionic View, Ionic Deploy, etc.) please consult the Ionic Appflow support portal (https://ionic.zendesk.com/hc/en-us) -->

View File

@@ -1,7 +1,7 @@
---
name: Bug Report
about: Create a report to help us improve
title: ''
title: 'bug: '
labels: ''
assignees: ''
---

View File

@@ -1,7 +1,7 @@
---
name: Feature Request
about: Suggest an idea for this project
title: ''
title: 'feat: '
labels: ''
assignees: ''
---

View File

@@ -1,7 +1,7 @@
---
name: Support Question
about: Question on how to use this project
title: ''
title: 'support: '
labels: 'ionitron: support'
assignees: ''
---

121
.github/PROCESS.md vendored
View File

@@ -1,11 +1,12 @@
# Process
This document is to describe the internal process that the Ionic team uses for issue management and project planning.
This document is to describe the internal process that the Ionic team uses for issue management, project planning and the development workflow.
## Table of contents
* [Project Boards](#project-boards)
* [Managing Issues](#managing-issues)
* [Workflow](#workflow)
* [Releasing](#releasing)
## Project Boards
@@ -80,11 +81,79 @@ if there is no response within 30 days, the issue will be closed and locked.
## Workflow
### Overview
![](https://user-images.githubusercontent.com/6577830/53817482-80e6c480-3f33-11e9-9e09-be8dcf840ef8.png)
We have two long-living branches:
- `master`: completed features, bug fixes, refactors, chores
- `stable`: the latest release
The overall flow:
1. Feature, refactor, and bug fix branches are created from `master`
1. When a feature, refactor, or fix is complete it is merged into `master`
1. A release branch is created from `master`
1. When the release branch is done it is merged into `master` and `stable`
1. If an issue in `stable` is detected a hotfix branch is created from `stable`
1. Once the hotfix is complete it is merged to both `master` and `stable`
1. All branches should follow the syntax of `{type}-{details}` where `{type}` is the type of branch (`hotfix`, `release`, or one of the [commit types](https://github.com/ionic-team/ionic/blob/master/.github/CONTRIBUTING.md#commit-message-format)) and `{details}` is a few hyphen separated words explaining the branch
### Stable and Master Branches
#### Stable Branch
Branches created from `stable`:
The following branch should be merged back to **both** `master` and `stable`:
- A `hotfix` branch (e.g. `hotfix-missing-export`): a bug fix that is fixing a regression or issue with a published release
A `hotfix` branch should be the **only** branch that is created from stable.
#### Master Branch
Branches created from `master`:
The following branches should be merged back to `master` via a pull request:
1. A feature branch (e.g. `feat-desktop-support`): an addition to the API that is not a bug fix or regression fix
1. A bug fix branch (e.g. `fix-tab-color`): a bug fix that is not fixing a regression or issue with a published release
1. All other types listed in the [commit message types](https://github.com/ionic-team/ionic/blob/master/.github/CONTRIBUTING.md#commit-message-format): `docs`, `style`, `refactor`, `perf`, `test`, `chore`
The following branch should be merged back to **both** `master` and `stable`:
1. A `release` branch (e.g. `release-4.1.x`): contains all fixes and (optionally) features that are tested and should go into the release
### Feature Branches
Each new feature should reside in its own branch, based on the `master` branch. When a feature is complete, it should go into a pull request that gets merged back into `master`. A pull request adding a feature should be approved by two team members. Features should never interact directly with `stable`.
### Release Branches
Once `master` has acquired enough features for a release (or a predetermined release date is approaching), fork a release branch off of `master`. Creating this branch starts the next release cycle, so no new features can be added after this point - only bug fixes, documentation generation, and other release-oriented tasks should go in this branch.
Once the release is ready to ship, it will get merged into `stable` and `master`, then the release branch will be deleted. Its important to merge back into `master` because critical updates may have been added to the release branch and they need to be accessible to new features. This should be done in a pull request after review.
See the [steps for releasing](#releasing) below for detailed information on how to publish a release.
### Version Branches
Once a release has shipped and the release branch has been merged into `stable` and `master` it should also be merged into its corrsponding version branch. These version branches allow us to ship updates for specific versions of the framework (i.e. Lets us ship a bug fix that only affects 4.2.x).
Patch releases should be merged into their corresponding version branches. For example, a `release-4.1.1` branch should be merged into the `4.1.x` version branch and a `release-5.0.1` branch should be merged into the `5.0.x` version branch.
When releasing a major version such as `5.0.0 ` or a minor version such as `4.1.0` , the version branch will not exist. The version branch should be created once the release branch has been merged into `stable` and `master`. For example, when releasing `4.1.0`, the `release-4.1.0` release branch should be merged into `stable` and `master` and then the `4.1.x` version branch should be created off the latest `stable`.
### Hotfix Branches
Maintenance or “hotfix” branches are used to quickly patch production releases. This is the only branch that should fork directly off of `stable`. As soon as the fix is complete, it should be merged into both `stable` and `master` (or the current release branch).
### Examples
#### Making a Change
@@ -111,7 +180,7 @@ We have two long-living branches:
1. Confirm squash and merge into `master`.
#### Merging Changes from `master` into your Branch
#### Updating from `master`
1. Pull the latest changes locally.
1. Merge the changes, fixing any conflicts.
@@ -125,23 +194,6 @@ OR
1. Pull the merged changes locally.
#### Making a Release
1. Freeze `master`. Only the person doing the release should be modifying `master`.
1. Follow the [Making a Change](#making-a-change) steps to prepare the release.
- Run `npm run release.prepare`
- Version changes
- `CHANGELOG.MD` tweaks
1. Create a PR to merge `master` into `stable`.
1. Click **Merge pull request**. Use the dropdown to select this option if necessary. This will preserve the commit history from `master` by creating a merge commit.
<img width="191" alt="Merge pull request button" src="https://user-images.githubusercontent.com/236501/47032669-8be1b980-d138-11e8-9a90-d1518c223184.png">
1. CI builds `stable`, performing the release.
1. Unfreeze `master`.
#### Hotfixes
Hotfixes bypass `master` and should only be used for urgent fixes that can't wait for the next release to be ready.
@@ -165,3 +217,34 @@ Hotfixes bypass `master` and should only be used for urgent fixes that can't wai
1. Click **Merge pull request**. Use the dropdown to select this option if necessary.
<img width="191" alt="Merge pull request button" src="https://user-images.githubusercontent.com/236501/47032669-8be1b980-d138-11e8-9a90-d1518c223184.png">
## Releasing
1. Create the release branch from `master`, for example: `release-4.1.0`.
1. Submit a pull request from the release branch into `stable`. Do not merge this pull request yet.
1. Verify all tests are passing, fix any bugs if needed and make sure no undesired commits are in.
1. Navigate to the root of the repository while on the release branch.
1. Run `npm i` if it hasn't already been done.
1. Run `npm run release.prepare`
- Select the version based on the type of commits and the [Ionic Versioning](https://ionicframework.com/docs/intro/versioning)
- After the process completes, verify the version number in all packages (`core`, `docs`, `angular`)
- Verify the changelog commits are accurate and follow the [proper format]((https://github.com/ionic-team/ionic/blob/master/.github/CONTRIBUTING.md#commit-message-format))
- Commit these changes with the version number as the message, e.g. `git commit -m "4.1.0"`
1. Run `npm run release`
1. Click **Merge pull request**. Use the dropdown to select this option if necessary.
<img width="191" alt="Merge pull request button" src="https://user-images.githubusercontent.com/236501/47032669-8be1b980-d138-11e8-9a90-d1518c223184.png">
1. Rewrite the commit message to `merge release-4.1.0` with the proper release branch.
1. Create a pull request and merge the release branch back into `master` using the same commit format in the last step, to ensure any changes made on the release branch get added to future releases.
1. Merge the release branch into its corresponding version branch. If this is a major or minor release, create the version branch off the latest `stable`.

View File

@@ -1,12 +1,51 @@
#### Short description of what this resolves:
<!-- Please refer to our contributing documentation for any questions on submitting a pull request, or let us know here if you need any help: https://ionicframework.com/docs/building/contributing -->
## Pull request checklist
Please check if your PR fulfills the following requirements:
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features)
- [ ] Build (`npm run build`) was run locally and any changes were pushed
- [ ] Lint (`npm run lint`) has passed locally and any fixes were made for failures
#### Changes proposed in this pull request:
## Pull request type
<!-- Please do not submit updates to dependencies unless it fixes an issue. -->
<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed. -->
Please check the type of change your PR introduces:
- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):
## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
Issue Number: N/A
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by this PR. -->
-
-
-
**Ionic Version**:
## Does this introduce a breaking change?
**Fixes**: #
- [ ] Yes
- [ ] No
<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->
## Other information
<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->

View File

@@ -3,6 +3,26 @@ triage:
removeLabelWhenProjectAssigned: true
dryRun: false
comment:
labels:
- label: "help wanted"
message: >
This issue has been labeled as `help wanted`. This label is added to issues
that we believe would be good for contributors.
If you'd like to work on this issue, please comment here letting us know that
you would like to submit a pull request for it. This helps us to keep track of
the pull request and make sure there isn't duplicated effort.
For a guide on how to create a pull request and test this project locally to see
your changes, see our [contributing documentation](https://ionicframework.com/docs/building/contributing).
Thank you!
dryRun: false
closeAndLock:
labels:
- label: "ionitron: support"
@@ -98,6 +118,15 @@ labelPullRequest:
wrongRepo:
repos:
- label: "ionitron: capacitor"
repo: capacitor
message: >
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
associated with the Ionic Framework. It appears that this issue is associated with Capacitor.
I am moving this issue to the Capacitor repository. Please track this issue over there.
Thank you for using Ionic!
- label: "ionitron: v3"
repo: ionic-v3
message: >

View File

@@ -37,7 +37,7 @@ function checkGit(tasks) {
{
title: 'Check current branch',
task: () => execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => {
if (branch.indexOf('release') === -1 && branch.indexOf('hofix') === -1) {
if (branch.indexOf('release') === -1 && branch.indexOf('hotfix') === -1) {
throw new Error(`Must be on a "release" or "hotfix" branch.`);
}
})

View File

@@ -1,31 +1,116 @@
# [4.1.0](https://github.com/ionic-team/ionic/compare/v4.0.1...v4.1.0) (2019-03-06)
## [4.3.1](https://github.com/ionic-team/ionic/compare/v4.3.0...v4.3.1) (2019-04-26)
### Bug Fixes
* **angular:** support replaceUrl with angular <7.2 ([#18106](https://github.com/ionic-team/ionic/issues/18106)) ([eb3cbe4](https://github.com/ionic-team/ionic/commit/eb3cbe4))
* sanitize components using innerHTML ([#18146](https://github.com/ionic-team/ionic/issues/18146)) ([b839e6f](https://github.com/ionic-team/ionic/commit/b839e6f))
# [4.3.0 Lithium](https://github.com/ionic-team/ionic/compare/v4.2.0...v4.3.0) (2019-04-17)
### Bug Fixes
* **action-sheet:** default buttons to empty array ([9e63947](https://github.com/ionic-team/ionic/commit/9e63947))
* **angular:** back button correctly goes back to proper tab ([#18005](https://github.com/ionic-team/ionic/issues/18005)) ([52e5a8d](https://github.com/ionic-team/ionic/commit/52e5a8d)), closes [#17278](https://github.com/ionic-team/ionic/issues/17278) [#15216](https://github.com/ionic-team/ionic/issues/15216)
* **components:** add mode classes to components for use in shadow elements ([#17838](https://github.com/ionic-team/ionic/issues/17838)) ([e5c8c10](https://github.com/ionic-team/ionic/commit/e5c8c10)), closes [#17608](https://github.com/ionic-team/ionic/issues/17608)
* **datetime:** date strings no longer revert to previous day ([#18018](https://github.com/ionic-team/ionic/issues/18018)) ([cc60b60](https://github.com/ionic-team/ionic/commit/cc60b60)), closes [#17977](https://github.com/ionic-team/ionic/issues/17977)
* **input:** prevent input from losing focus when tapping clear button ([#18004](https://github.com/ionic-team/ionic/issues/18004)) ([29bb4fc](https://github.com/ionic-team/ionic/commit/29bb4fc)), closes [#18002](https://github.com/ionic-team/ionic/issues/18002)
* **item:** use the correct input highlight for an inset line item ([#18052](https://github.com/ionic-team/ionic/issues/18052)) ([72be80c](https://github.com/ionic-team/ionic/commit/72be80c)), closes [#18051](https://github.com/ionic-team/ionic/issues/18051)
* **item-sliding:** hide closed side options while dragging side options open ([#17986](https://github.com/ionic-team/ionic/issues/17986)) ([f13722c](https://github.com/ionic-team/ionic/commit/f13722c)), closes [#17822](https://github.com/ionic-team/ionic/issues/17822)
* **slides:** allow zoom to work ([18b347b](https://github.com/ionic-team/ionic/commit/18b347b)), closes [#17981](https://github.com/ionic-team/ionic/issues/17981)
* **slides:** expose interface to provide custom animations ([#17959](https://github.com/ionic-team/ionic/issues/17959)) ([4474974](https://github.com/ionic-team/ionic/commit/4474974)), closes [#16616](https://github.com/ionic-team/ionic/issues/16616)
* **textarea:** float label when a value is changed async ([#18024](https://github.com/ionic-team/ionic/issues/18024)) ([494991e](https://github.com/ionic-team/ionic/commit/494991e)), closes [#17555](https://github.com/ionic-team/ionic/issues/17555) [#17559](https://github.com/ionic-team/ionic/issues/17559)
* **textarea:** update label alignment for inputs and textareas ([#18042](https://github.com/ionic-team/ionic/issues/18042)) ([38ae362](https://github.com/ionic-team/ionic/commit/38ae362)), closes [#16187](https://github.com/ionic-team/ionic/issues/16187)
* **vue:** use direction type from core ([#17901](https://github.com/ionic-team/ionic/issues/17901)) ([fa13173](https://github.com/ionic-team/ionic/commit/fa13173))
### Features
* **toast:** add header and additional custom toast buttons ([#17147](https://github.com/ionic-team/ionic/issues/17147)) ([6e1a8f1](https://github.com/ionic-team/ionic/commit/6e1a8f1)), closes [#16791](https://github.com/ionic-team/ionic/issues/16791) [#16237](https://github.com/ionic-team/ionic/issues/16237) [#17611](https://github.com/ionic-team/ionic/issues/17611)
* **toast:** add variables to change position start/end of toast ([#17961](https://github.com/ionic-team/ionic/issues/17961)) ([07e739a](https://github.com/ionic-team/ionic/commit/07e739a)), closes [#17854](https://github.com/ionic-team/ionic/issues/17854)
# [4.2.0 Helium](https://github.com/ionic-team/ionic/compare/v4.1.2...v4.2.0) (2019-04-03)
### Bug Fixes
* **angular:** properly check for replaceUrl in routing ([#17879](https://github.com/ionic-team/ionic/issues/17879)) ([f2c8db9](https://github.com/ionic-team/ionic/commit/f2c8db9)), closes [#15181](https://github.com/ionic-team/ionic/issues/15181)
* **angular:** return proper types in the overlay.getTop method ([#17802](https://github.com/ionic-team/ionic/issues/17802)) ([439b10e](https://github.com/ionic-team/ionic/commit/439b10e))
* **angular:** support relative router links ([d9a7c63](https://github.com/ionic-team/ionic/commit/d9a7c63)), closes [#17888](https://github.com/ionic-team/ionic/issues/17888) [#16534](https://github.com/ionic-team/ionic/issues/16534) [#16736](https://github.com/ionic-team/ionic/issues/16736) [#16954](https://github.com/ionic-team/ionic/issues/16954)
* **angular:** update ng-add schematic ([9443bfe](https://github.com/ionic-team/ionic/commit/9443bfe))
* **css:** resolve spinner and checkbox issues on older chrome versions ([#17896](https://github.com/ionic-team/ionic/issues/17896)) ([dbb39cd](https://github.com/ionic-team/ionic/commit/dbb39cd)), closes [#17524](https://github.com/ionic-team/ionic/issues/17524) [#17501](https://github.com/ionic-team/ionic/issues/17501)
* **datetime:** default to local date when no date given ([#17706](https://github.com/ionic-team/ionic/issues/17706)) ([bab56e8](https://github.com/ionic-team/ionic/commit/bab56e8))
* **datetime:** recalculate day column when month or year is changed ([#17815](https://github.com/ionic-team/ionic/issues/17815)) ([9273f97](https://github.com/ionic-team/ionic/commit/9273f97)), closes [#14233](https://github.com/ionic-team/ionic/issues/14233) [#14732](https://github.com/ionic-team/ionic/issues/14732) [#15452](https://github.com/ionic-team/ionic/issues/15452) [#15794](https://github.com/ionic-team/ionic/issues/15794) [#17633](https://github.com/ionic-team/ionic/issues/16733) [#17060](https://github.com/ionic-team/ionic/issues/17060) [#17510](https://github.com/ionic-team/ionic/issues/17510) [#17521](https://github.com/ionic-team/ionic/issues/17521)
* **item-option:** add styling and behaviour for disabled item-option ([#17909](https://github.com/ionic-team/ionic/issues/17909)) ([346ecb2](https://github.com/ionic-team/ionic/commit/346ecb2)), closes [#17905](https://github.com/ionic-team/ionic/issues/17905)
* **progress-bar:** flip rtl using the existing reversed property ([#17464](https://github.com/ionic-team/ionic/issues/17464)) ([fccaaf8](https://github.com/ionic-team/ionic/commit/fccaaf8)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **react:** ensure tabs are resilient to optional tabs ([#17862](https://github.com/ionic-team/ionic/issues/17862)) ([c29f5a6](https://github.com/ionic-team/ionic/commit/c29f5a6))
* **reorder-group:** add ability to reorder items inside shadow ([#17747](https://github.com/ionic-team/ionic/issues/17747)) ([352797e](https://github.com/ionic-team/ionic/commit/352797e)), closes [#17746](https://github.com/ionic-team/ionic/issues/17746)
* **select:** update overlay options when added asynchronously ([#17860](https://github.com/ionic-team/ionic/issues/17860)) ([1ecfcd1](https://github.com/ionic-team/ionic/commit/1ecfcd1)), closes [#15716](https://github.com/ionic-team/ionic/issues/15716) [#17851](https://github.com/ionic-team/ionic/issues/17851)
* **virtual-scroll:** use correct item top calculation with header or footer function ([#15948](https://github.com/ionic-team/ionic/issues/15948)) ([#17345](https://github.com/ionic-team/ionic/issues/17345)) ([a8a48a4](https://github.com/ionic-team/ionic/commit/a8a48a4)), closes [#17298](https://github.com/ionic-team/ionic/issues/17298)
* **vue:** back button event handling ([#17877](https://github.com/ionic-team/ionic/issues/17877)) ([dceec07](https://github.com/ionic-team/ionic/commit/dceec07))
* **vue:** update popover to use correct controller ([#17865](https://github.com/ionic-team/ionic/issues/17865)) ([0cb7db0](https://github.com/ionic-team/ionic/commit/0cb7db0))
### Features
* **img:** add ionError event ([#17134](https://github.com/ionic-team/ionic/issues/17134)) ([04f931f](https://github.com/ionic-team/ionic/commit/04f931f)), closes [#16947](https://github.com/ionic-team/ionic/issues/16947)
* **range:** add ticks attribute ([#17718](https://github.com/ionic-team/ionic/issues/17718)) ([016fa16](https://github.com/ionic-team/ionic/commit/016fa16)), closes [#17717](https://github.com/ionic-team/ionic/issues/17717)
* **vue:** support for ion-tabs ([#17678](https://github.com/ionic-team/ionic/issues/17678)) ([ee71675](https://github.com/ionic-team/ionic/commit/ee71675))
* **vue:** support ion-vue-router ([#17821](https://github.com/ionic-team/ionic/issues/17821)) ([71e5994](https://github.com/ionic-team/ionic/commit/71e5994))
## [4.1.2](https://github.com/ionic-team/ionic/compare/v4.1.1...v4.1.2) (2019-03-20)
### Bug Fixes
* **chip:** use transparent outline on color chips ([#17719](https://github.com/ionic-team/ionic/issues/17719)) ([f6783db](https://github.com/ionic-team/ionic/commit/f6783db))
* **datetime:** default to local date ([#17706](https://github.com/ionic-team/ionic/issues/17706)) ([bab56e8](https://github.com/ionic-team/ionic/commit/bab56e8))
* **input:** use max-height for input for consistency across browsers ([#17394](https://github.com/ionic-team/ionic/issues/17394)) ([67a9137](https://github.com/ionic-team/ionic/commit/67a9137))
* **item:** add missing ripple color CSS property ([#17814](https://github.com/ionic-team/ionic/issues/17814)) ([807820f](https://github.com/ionic-team/ionic/commit/807820f)), closes [#17523](https://github.com/ionic-team/ionic/issues/17523)
* **item-option:** add styling for slots other than icon-only ([#17711](https://github.com/ionic-team/ionic/issues/17711)) ([14f758c](https://github.com/ionic-team/ionic/commit/14f758c)), closes [#17737](https://github.com/ionic-team/ionic/issues/17737) [#17402](https://github.com/ionic-team/ionic/issues/17402)
* **platform:** account for larger tablets ([#17630](https://github.com/ionic-team/ionic/issues/17630)) ([29dbd07](https://github.com/ionic-team/ionic/commit/29dbd07))
* **popover:** update animation origin in RTL/MD ([#17645](https://github.com/ionic-team/ionic/issues/17645)) ([617453b](https://github.com/ionic-team/ionic/commit/617453b)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **spinner:** fix default spinner logic for relevant components ([#17660](https://github.com/ionic-team/ionic/issues/17660)) ([9c48fa7](https://github.com/ionic-team/ionic/commit/9c48fa7)), closes [#17659](https://github.com/ionic-team/ionic/issues/17659)
* **toggle:** do not use the contrast color for toggle inner color ([#17798](https://github.com/ionic-team/ionic/issues/17798)) ([2225941](https://github.com/ionic-team/ionic/commit/2225941)), closes [#17536](https://github.com/ionic-team/ionic/issues/17536)
* **transition:** animate all toolbars in iOS page transitions ([#17224](https://github.com/ionic-team/ionic/issues/17224)) ([7d01207](https://github.com/ionic-team/ionic/commit/7d01207))
## [4.1.1](https://github.com/ionic-team/ionic/compare/v4.1.0...v4.1.1) (2019-03-07)
### Bug Fixes
* **display:** update to correct css classes ([cabbeb2](https://github.com/ionic-team/ionic/commit/cabbeb2))
# [4.1.0 Hydrogen](https://github.com/ionic-team/ionic/compare/v4.0.2...v4.1.0) (2019-03-06)
### Bug Fixes
* **angular:** fix adding [@ionic](https://github.com/ionic)/angular when using ng add ([#17597](https://github.com/ionic-team/ionic/issues/17597)) ([484d92c](https://github.com/ionic-team/ionic/commit/484d92c)), closes [#17596](https://github.com/ionic-team/ionic/issues/17596)
* **animation:** fix header flicker on ios ([#17422](https://github.com/ionic-team/ionic/issues/17422)) ([ad20bd6](https://github.com/ionic-team/ionic/commit/ad20bd6))
* **button:** show proper shade for activated button on ios ([#17508](https://github.com/ionic-team/ionic/issues/17508)) ([89a908b](https://github.com/ionic-team/ionic/commit/89a908b)), closes [#17436](https://github.com/ionic-team/ionic/issues/17436)
* **config:** update types for scrollPadding, inputBlurring and hideCaretOnScroll to boolean ([#17302](https://github.com/ionic-team/ionic/issues/17302)) ([39fbc32](https://github.com/ionic-team/ionic/commit/39fbc32))
* **css:** add the missing css classes to flex and float utils ([#17570](https://github.com/ionic-team/ionic/issues/17570)) ([c49276c](https://github.com/ionic-team/ionic/commit/c49276c)), closes [#17569](https://github.com/ionic-team/ionic/issues/17569)
* **datetime:** default to current date when no value given ([#17443](https://github.com/ionic-team/ionic/issues/17443)) ([a1ec5f6](https://github.com/ionic-team/ionic/commit/a1ec5f6))
* **fab:** disabled fab button no longer opens fab list ([#17620](https://github.com/ionic-team/ionic/issues/17620)) ([c475dab](https://github.com/ionic-team/ionic/commit/c475dab))
* **img:** remove space under img ([#17582](https://github.com/ionic-team/ionic/issues/17582)) ([621c79b](https://github.com/ionic-team/ionic/commit/621c79b))
* **item:** slotted ion-icon receives custom color ([#17585](https://github.com/ionic-team/ionic/issues/17585)) ([14dd871](https://github.com/ionic-team/ionic/commit/14dd871))
* **item-sliding:** no longer close all open items when deleting an item ([#17579](https://github.com/ionic-team/ionic/issues/17579)) ([3de7795](https://github.com/ionic-team/ionic/commit/3de7795))
* **item-sliding:** Sliding no longer breaks after removing an item ([#17492](https://github.com/ionic-team/ionic/issues/17492)) ([08fe8e4](https://github.com/ionic-team/ionic/commit/08fe8e4))
* **loading:** add backdropDismiss to the interface ([#17668](https://github.com/ionic-team/ionic/issues/17668)) ([28fd75e](https://github.com/ionic-team/ionic/commit/28fd75e)), closes [#17369](https://github.com/ionic-team/ionic/issues/17369)
* **popover:** update placement per md spec ([#17429](https://github.com/ionic-team/ionic/issues/17429)) ([a99d179](https://github.com/ionic-team/ionic/commit/a99d179))
* **range:** clamp values that are out of bounds ([#17623](https://github.com/ionic-team/ionic/issues/17623)) ([5a197ca](https://github.com/ionic-team/ionic/commit/5a197ca))
* **range:** implement RTL (from PR 17157) ([#17384](https://github.com/ionic-team/ionic/issues/17384)) ([4f203bc](https://github.com/ionic-team/ionic/commit/4f203bc)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **range:** improved rtl support ([#17479](https://github.com/ionic-team/ionic/issues/17479)) ([f832de5](https://github.com/ionic-team/ionic/commit/f832de5))
* **refresher:** check for cancelable before preventing default ([#17351](https://github.com/ionic-team/ionic/issues/17351)) ([f205b10](https://github.com/ionic-team/ionic/commit/f205b10)), closes [#15256](https://github.com/ionic-team/ionic/issues/15256)
* **reorder:** Allow sliding items to be reordered ([#17568](https://github.com/ionic-team/ionic/issues/17568)) ([3b331b5](https://github.com/ionic-team/ionic/commit/3b331b5))
* **searchbar:** allow setting of toolbar color and searchbar color ([#17474](https://github.com/ionic-team/ionic/issues/17474)) ([ba4e117](https://github.com/ionic-team/ionic/commit/ba4e117))
* **select:** Account for when options are not loaded immediately ([#17405](https://github.com/ionic-team/ionic/issues/17405)) ([1c9c18b](https://github.com/ionic-team/ionic/commit/1c9c18b))
* **reorder:** allow sliding items to be reordered ([#17568](https://github.com/ionic-team/ionic/issues/17568)) ([3b331b5](https://github.com/ionic-team/ionic/commit/3b331b5))
* **ssr:** fix angular global window and document references ([f44c17e](https://github.com/ionic-team/ionic/commit/f44c17e))
* **ssr:** fix global window and document references ([#17590](https://github.com/ionic-team/ionic/issues/17590)) ([4646f53](https://github.com/ionic-team/ionic/commit/4646f53))
* **tab-bar:** add translucent tab-bar styles back ([#17376](https://github.com/ionic-team/ionic/issues/17376)) ([374bd77](https://github.com/ionic-team/ionic/commit/374bd77))
* **vue:** correct passing of props and data to components ([#17188](https://github.com/ionic-team/ionic/issues/17188)) ([2ce4940](https://github.com/ionic-team/ionic/commit/2ce4940))
@@ -33,7 +118,6 @@
* **checkbox:** implement indeterminate state ([#16951](https://github.com/ionic-team/ionic/issues/16951)) ([c641ae1](https://github.com/ionic-team/ionic/commit/c641ae1)), closes [#16943](https://github.com/ionic-team/ionic/issues/16943)
* **css:** add CSS display utilities ([#17359](https://github.com/ionic-team/ionic/issues/17359)) ([6bea9d3](https://github.com/ionic-team/ionic/commit/6bea9d3)), closes [#10904](https://github.com/ionic-team/ionic/issues/10904)
* **range:** add neutral point ([#17400](https://github.com/ionic-team/ionic/issues/17400)) ([15acb4b](https://github.com/ionic-team/ionic/commit/15acb4b))
* **select:** add compareWith property ([#17358](https://github.com/ionic-team/ionic/issues/17358)) ([69ecebb](https://github.com/ionic-team/ionic/commit/69ecebb))
* **skeleton-text:** adds animated prop and support for CSS styling ([#17612](https://github.com/ionic-team/ionic/issues/17612)) ([d66b12b](https://github.com/ionic-team/ionic/commit/d66b12b)), closes [ionic-team/ionic-docs#407](https://github.com/ionic-team/ionic-docs/issues/407)
@@ -461,7 +545,7 @@ Tabs got the last bit of changes needed in order to support lazy loading of comp
pathMatch: 'full'
}
];
```
```
</detail>
@@ -592,14 +676,14 @@ Segment Button now requires the text to be wrapped in an `ion-label` element for
<ion-segment-button>
Item One
</ion-segment-button>
```
```
*New usage:*
```html
<ion-segment-button>
<ion-label>Item One</ion-label>
</ion-segment-button>
```
```
#### Simplifying Chip
@@ -758,8 +842,8 @@ Here, we have an `ion-tab` element that accepts an icon, a label, and a link to
<ion-tab-bar>
<!-- No ion-tab, just a link that looks like a tab -->
<ion-tab-button href="https://beta.ionicframework.com">
<!-- <a href="https://beta.ionicframework.com"> -->
<ion-tab-button href="https://ionicframework.com">
<!-- <a href="https://ionicframework.com"> -->
<ion-icon name="globe"></ion-icon>
<ion-label>Schedule</ion-label>
</ion-tab-button>
@@ -2115,4 +2199,5 @@ The following dependencies need to be updated to resolve build errors
<a name="0.1.0"></a>
## [0.1.0](https://github.com/ionic-team/ionic/commit/43a8c4c7a719169336a84964fc1c737562d764a6) (2018-03-01)

View File

@@ -5,7 +5,7 @@
### Migration Guide
If you aren't sure where to start in upgrading to v4, we recommend reading through our [migration guide](https://beta.ionicframework.com/docs/building/migration) first.
If you aren't sure where to start in upgrading to v4, we recommend reading through our [migration guide](https://ionicframework.com/docs/building/migration) first.
### Migration Linter
@@ -182,13 +182,14 @@ These have been renamed to the following, and moved from the button element to t
In addition, several sets of mutually exclusive boolean attributes have been combined into a single string attribute.
The `small` and `large` attributes are now combined under the `size` attribute. The `clear`, `outline`, and `solid` attributes have been combined under `fill`. And, lastly, the `full` and `block` attributes have been combined under `expand`.
The `small` and `large` attributes are now combined under the `size` attribute. The `clear`, `outline`, and `solid` attributes have been combined under `fill`. The `full` and `block` attributes have been combined under `expand`. And, lastly, the `round` attribute is now used under `shape`.
| Old Property | New Property | Property Behavior |
| --------------------------- | ------------ | --------------------------- |
| `small`, `large` | `size` | Sets the button size. |
| `clear`, `outline`, `solid` | `fill` | Sets the button fill style. |
| `full`, `block`             | `expand` | Sets the button width. |
| `round`             | `shape` | Sets the button shape. |
**Old Usage Example:**
@@ -225,6 +226,10 @@ The `small` and `large` attributes are now combined under the `size` attribute.
<ion-button full>
Full-width Button
</ion-button>
<ion-button round>
Round Button
</ion-button>
```
**New Usage Example:**
@@ -251,6 +256,10 @@ The `small` and `large` attributes are now combined under the `size` attribute.
<ion-button expand="full">
Full-width Button
</ion-button>
<ion-button shape="round">
Round Button
</ion-button>
```
@@ -1065,11 +1074,11 @@ async openLoading() {
let loading = this.loadingCtrl.create({
content: 'Loading...'
});
await loading.present();
const { role, data } = await loading.onDidDismiss();
console.log('Loading dismissed!');
}
```
@@ -1740,19 +1749,19 @@ Changes the `font-family` of the whole page based on the mode selected (iOS or M
The following set of CSS files are optional and can safely be commented out if the application is not using any of the features.
- **padding.css**
Adds utility attributes that allow adding `padding` and `margin` attributes to any element. See [content space](https://beta.ionicframework.com/docs/layout/css-utilities#content-space) for what this includes.
Adds utility attributes that allow adding `padding` and `margin` attributes to any element. See [content space](https://ionicframework.com/docs/layout/css-utilities#content-space) for what this includes.
- **float-elements.css**
Adds utility attributes that allow adding `float` attributes to any element. See [element placement](https://beta.ionicframework.com/docs/layout/css-utilities/#element-placement) for what this includes.
Adds utility attributes that allow adding `float` attributes to any element. See [element placement](https://ionicframework.com/docs/layout/css-utilities/#element-placement) for what this includes.
- **text-alignment.css**
Adds utility attributes that allow adding text alignment attributes to any element. See [text alignment](https://beta.ionicframework.com/docs/layout/css-utilities/#text-alignment) for what this includes.
Adds utility attributes that allow adding text alignment attributes to any element. See [text alignment](https://ionicframework.com/docs/layout/css-utilities/#text-alignment) for what this includes.
- **text-transformation.css**
Adds utility attributes that allow adding text transformation attributes to any element. See [text transformation](https://beta.ionicframework.com/docs/layout/css-utilities/#text-transformation) for what this includes.
Adds utility attributes that allow adding text transformation attributes to any element. See [text transformation](https://ionicframework.com/docs/layout/css-utilities/#text-transformation) for what this includes.
- **flex-utils.css**
Adds utility attributes that allow adding flex container and item attributes to any element. See [flex properties](https://beta.ionicframework.com/docs/layout/css-utilities/#flex-properties) for what this includes.
Adds utility attributes that allow adding flex container and item attributes to any element. See [flex properties](https://ionicframework.com/docs/layout/css-utilities/#flex-properties) for what this includes.
#### Including the CSS Files
@@ -1831,7 +1840,7 @@ p {
Sass variables should no longer be used to change Ionic components. We have built Ionic to be customizable using CSS variables, instead.
For more information on theming, check out the [theming documentation](https://beta.ionicframework.com/docs/theming/basics).
For more information on theming, check out the [theming documentation](https://ionicframework.com/docs/theming/basics).
## Toast

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "4.1.0",
"version": "4.3.1",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -45,7 +45,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "4.1.0",
"@ionic/core": "4.3.1",
"tslib": "^1.9.3"
},
"peerDependencies": {

View File

@@ -1,5 +1,7 @@
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
import { Config } from '../../providers/config';
import { NavController } from '../../providers/nav-controller';
@@ -13,7 +15,6 @@ import { RouteView, getUrl } from './stack-utils';
inputs: ['animated', 'swipeGesture']
})
export class IonRouterOutlet implements OnDestroy, OnInit {
private activated: ComponentRef<any> | null = null;
private activatedView: RouteView | null = null;
@@ -23,6 +24,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
private stackCtrl: StackController;
private nativeEl: HTMLIonRouterOutletElement;
// Maintain map of activated route proxies for each component instance
private proxyMap = new WeakMap<any, ActivatedRoute>();
// Keep the latest activated route in a subject for the proxy routes to switch map to
private currentActivatedRoute$ = new BehaviorSubject<{ component: any; activatedRoute: ActivatedRoute } | null>(null);
tabsPrefix: string | undefined;
@Output() stackEvents = new EventEmitter<any>();
@@ -159,6 +166,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const context = this.getContext()!;
context.children['contexts'] = saved;
}
// Updated activated route proxy for this component
this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute);
} else {
const snapshot = (activatedRoute as any)._futureSnapshot;
const component = snapshot.routeConfig!.component as any;
@@ -167,12 +176,25 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
// We create an activated route proxy object that will maintain future updates for this component
// over its lifecycle in the stack.
const component$ = new BehaviorSubject<any>(null);
const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute);
const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector);
cmpRef = this.activated = this.location.createComponent(factory, this.location.length, injector);
// Once the component is created we can push it to our local subject supplied to the proxy
component$.next(cmpRef.instance);
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
// Store references to the proxy by component
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
this.changeDetector.markForCheck();
}
@@ -212,6 +234,66 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
getActiveStackId(): string | undefined {
return this.stackCtrl.getActiveStackId();
}
/**
* Since the activated route can change over the life time of a component in an ion router outlet, we create
* a proxy so that we can update the values over time as a user navigates back to components already in the stack.
*/
private createActivatedRouteProxy(component$: Observable<any>, activatedRoute: ActivatedRoute): ActivatedRoute {
const proxy: any = new ActivatedRoute();
proxy._futureSnapshot = (activatedRoute as any)._futureSnapshot;
proxy._routerState = (activatedRoute as any)._routerState;
proxy.snapshot = activatedRoute.snapshot;
proxy.outlet = activatedRoute.outlet;
proxy.component = activatedRoute.component;
// Setup wrappers for the observables so consumers don't have to worry about switching to new observables as the state updates
(proxy as any)._paramMap = this.proxyObservable(component$, 'paramMap');
(proxy as any)._queryParamMap = this.proxyObservable(component$, 'queryParamMap');
proxy.url = this.proxyObservable(component$, 'url');
proxy.params = this.proxyObservable(component$, 'params');
proxy.queryParams = this.proxyObservable(component$, 'queryParams');
proxy.fragment = this.proxyObservable(component$, 'fragment');
proxy.data = this.proxyObservable(component$, 'data');
return proxy as ActivatedRoute;
}
/**
* Create a wrapped observable that will switch to the latest activated route matched by the given component
*/
private proxyObservable(component$: Observable<any>, path: string): Observable<any> {
return component$.pipe(
// First wait until the component instance is pushed
filter(component => !!component),
switchMap(component =>
this.currentActivatedRoute$.pipe(
filter(current => current !== null && current.component === component),
switchMap(current => current && (current.activatedRoute as any)[path]),
distinctUntilChanged()
)
)
);
}
/**
* Updates the activated route proxy for the given component to the new incoming router state
*/
private updateActivatedRouteProxy(component: any, activatedRoute: ActivatedRoute): void {
const proxy = this.proxyMap.get(component);
if (!proxy) {
throw new Error(`Could not find activated route proxy for view`);
}
(proxy as any)._futureSnapshot = (activatedRoute as any)._futureSnapshot;
(proxy as any)._routerState = (activatedRoute as any)._routerState;
proxy.snapshot = activatedRoute.snapshot;
proxy.outlet = activatedRoute.outlet;
proxy.component = activatedRoute.component;
this.currentActivatedRoute$.next({ component, activatedRoute });
}
}
class OutletInjector implements Injector {

View File

@@ -58,6 +58,40 @@ export class StackController {
animation = undefined;
}
const viewsSnapshot = this.views.slice();
let currentNavigation;
const router = (this.router as any);
// Angular >= 7.2.0
if (router.getCurrentNavigation) {
currentNavigation = router.getCurrentNavigation();
// Angular < 7.2.0
} else if (
router.navigations &&
router.navigations.value
) {
currentNavigation = router.navigations.value;
}
/**
* If the navigation action
* sets `replaceUrl: true`
* then we need to make sure
* we remove the last item
* from our views stack
*/
if (
currentNavigation &&
currentNavigation.extras &&
currentNavigation.extras.replaceUrl
) {
if (this.views.length > 0) {
this.views.splice(-1, 1);
}
}
const views = this.insertView(enteringView, direction);
return this.wait(async () => {
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
@@ -82,7 +116,23 @@ export class StackController {
return Promise.resolve(false);
}
const view = views[views.length - deep - 1];
return this.navCtrl.navigateBack(view.url).then(() => true);
let url = view.url;
const viewSavedData = view.savedData;
if (viewSavedData) {
const primaryOutlet = viewSavedData.get('primary');
if (
primaryOutlet &&
primaryOutlet.route &&
primaryOutlet.route._routerState &&
primaryOutlet.route._routerState.snapshot &&
primaryOutlet.route._routerState.snapshot.url
) {
url = primaryOutlet.route._routerState.snapshot.url;
}
}
return this.navCtrl.navigateBack(url).then(() => true);
});
}

View File

@@ -294,11 +294,12 @@ export declare interface IonImg extends StencilComponents<'IonImg'> {}
@Component({ selector: 'ion-img', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['alt', 'src'] })
export class IonImg {
ionImgDidLoad!: EventEmitter<CustomEvent>;
ionError!: EventEmitter<CustomEvent>;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ionImgDidLoad']);
proxyOutputs(this, this.el, ['ionImgDidLoad', 'ionError']);
}
}
proxyInputs(IonImg, ['alt', 'src']);
@@ -586,7 +587,7 @@ export class IonRadioGroup {
proxyInputs(IonRadioGroup, ['allowEmptySelection', 'name', 'value']);
export declare interface IonRange extends StencilComponents<'IonRange'> {}
@Component({ selector: 'ion-range', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value'] })
@Component({ selector: 'ion-range', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'ticks', 'disabled', 'value'] })
export class IonRange {
ionChange!: EventEmitter<CustomEvent>;
ionFocus!: EventEmitter<CustomEvent>;
@@ -598,7 +599,7 @@ export class IonRange {
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
}
}
proxyInputs(IonRange, ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'disabled', 'value']);
proxyInputs(IonRange, ['color', 'mode', 'debounce', 'name', 'dualKnobs', 'min', 'max', 'pin', 'snaps', 'step', 'ticks', 'disabled', 'value']);
export declare interface IonRefresher extends StencilComponents<'IonRefresher'> {}
@Component({ selector: 'ion-refresher', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['pullMin', 'pullMax', 'closeDuration', 'snapbackDuration', 'disabled'] })

View File

@@ -1,6 +1,6 @@
/* Ionic Variables and Theming. */
/* This is just a placeholder file For more info, please see: */
/* https://beta.ionicframework.com/docs/theming/basics */
/* https://ionicframework.com/docs/theming/basics */
/* To quickly generate your own theme, check out the color generator */
/* https://beta.ionicframework.com/docs/theming/color-generator */
/* https://ionicframework.com/docs/theming/color-generator */

View File

@@ -1,20 +1,9 @@
import {
apply,
chain,
mergeWith,
move,
Rule,
SchematicContext,
SchematicsException,
template,
Tree,
url
} from '@angular-devkit/schematics';
import { join, Path } from '@angular-devkit/core';
import { apply, chain, mergeWith, move, Rule, SchematicContext, SchematicsException, template, Tree, url } from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { addPackageToPackageJson } from './../utils/package';
import { addModuleImportToRootModule } from './../utils/ast';
import { addArchitectBuilder, addStyle, getWorkspace } from './../utils/config';
import { addArchitectBuilder, addStyle, getWorkspace, addAsset } from './../utils/config';
import { addPackageToPackageJson } from './../utils/package';
import { Schema as IonAddOptions } from './schema';
function addIonicAngularToPackageJson(): Rule {
@@ -68,6 +57,18 @@ function addIonicStyles(): Rule {
};
}
function addIonicons(): Rule {
return (host: Tree) => {
const ioniconsGlob = {
glob: '**/*.svg',
input: 'node_modules/ionicons/dist/ionicons/svg',
output: './svg'
};
addAsset(host, ioniconsGlob);
return host;
};
}
function addIonicBuilder(): Rule {
return (host: Tree) => {
addArchitectBuilder(host, 'ionic-cordova-serve', {
@@ -119,7 +120,7 @@ export default function ngAdd(options: IonAddOptions): Rule {
const sourcePath = join(project.root as Path, 'src');
const rootTemplateSource = apply(url('./files/root'), [
template({...options}),
template({ ...options }),
move(sourcePath)
]);
return chain([
@@ -129,6 +130,7 @@ export default function ngAdd(options: IonAddOptions): Rule {
addIonicAngularModuleToAppModule(sourcePath),
addIonicBuilder(),
addIonicStyles(),
addIonicons(),
mergeWith(rootTemplateSource),
// install freshly added dependencies
installNodeDeps()

View File

@@ -64,6 +64,18 @@ export function addStyle(host: Tree, stylePath: string) {
}
}
export function addAsset(host: Tree, asset: string | {glob: string; input: string; output: string}) {
const config = readConfig(host);
const appConfig = getAngularAppConfig(config);
if (appConfig) {
appConfig.architect.build.options.assets.push(asset);
writeConfig(host, config);
} else {
throw new SchematicsException(`Cannot find valid app`);
}
}
export function addArchitectBuilder(host: Tree, builderName: string, builderOpts: any){
const config = readConfig(host);
const appConfig = getAngularAppConfig(config);

View File

@@ -20,7 +20,7 @@ export class OverlayBaseController<Opts, Overlay> {
/**
* Returns the top overlay.
*/
getTop(): Promise<Overlay> {
getTop(): Promise<Overlay | undefined> {
return proxyMethod(this.ctrl, this.doc, 'getTop');
}
}

View File

@@ -130,6 +130,25 @@ describe('tabs', () => {
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
});
});
describe('enter url - /tabs/contact/one', () => {
beforeEach(async () => {
await browser.get('/tabs/contact/one');
});
it('should return to correct tab after going to page in different outlet', async () => {
const tab = await getSelectedTab();
await tab.$('#goto-nested-page1').click();
await testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']);
const nestedOutlet = await element(by.css('app-nested-outlet'));
const backButton = await nestedOutlet.$('ion-back-button');
await backButton.click();
await testTabTitle('Tab 2 - Page 1');
});
})
});
async function testState(count: number, tab: string) {

View File

@@ -1,5 +1,8 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>
NESTED OUTLET
</ion-title>

View File

@@ -9,5 +9,6 @@
<p>
<ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button>
<ion-button routerLink="/tabs/account/nested/12" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button>
<ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go to nested</ion-button>
</p>
</ion-content>

View File

@@ -23,8 +23,8 @@ The Ionic Core package contains the Web Components that make up the reusable UI
Easiest way to start using Ionic Core is by adding a script tag to the CDN:
```html
<link href="https://unpkg.com/@ionic/core@4.1.0/css/ionic.bundle.css" rel="stylesheet">
<script src="https://unpkg.com/@ionic/core@4.1.0/dist/ionic.js"></script>
<link href="https://unpkg.com/@ionic/core@4.3.1/css/ionic.bundle.css" rel="stylesheet">
<script src="https://unpkg.com/@ionic/core@4.3.1/dist/ionic.js"></script>
```
Any Ionic component added to the webpage will automatically load. This includes writing the component tag directly in HTML, or using JavaScript such as `document.createElement('ion-toggle')`.

View File

@@ -7,7 +7,7 @@ ion-action-sheet-controller,method,getTop,getTop() => Promise<HTMLIonActionSheet
ion-action-sheet,scoped
ion-action-sheet,prop,animated,boolean,true,false,false
ion-action-sheet,prop,backdropDismiss,boolean,true,false,false
ion-action-sheet,prop,buttons,(string | ActionSheetButton)[],undefined,true,false
ion-action-sheet,prop,buttons,(string | ActionSheetButton)[],[],false,false
ion-action-sheet,prop,cssClass,string | string[] | undefined,undefined,false,false
ion-action-sheet,prop,enterAnimation,((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) | undefined,undefined,false,false
ion-action-sheet,prop,header,string | undefined,undefined,false,false
@@ -380,6 +380,7 @@ ion-header,prop,translucent,boolean,false,false,false
ion-img,shadow
ion-img,prop,alt,string | undefined,undefined,false,false
ion-img,prop,src,string | undefined,undefined,false,false
ion-img,event,ionError,void,true
ion-img,event,ionImgDidLoad,void,true
ion-infinite-scroll-content,none
@@ -509,6 +510,7 @@ ion-item,css-prop,--padding-bottom
ion-item,css-prop,--padding-end
ion-item,css-prop,--padding-start
ion-item,css-prop,--padding-top
ion-item,css-prop,--ripple-color
ion-item,css-prop,--transition
ion-label,scoped
@@ -804,6 +806,7 @@ ion-range,prop,name,string,'',false,false
ion-range,prop,pin,boolean,false,false,false
ion-range,prop,snaps,boolean,false,false,false
ion-range,prop,step,number,1,false,false
ion-range,prop,ticks,boolean,true,false,false
ion-range,prop,value,number | { lower: number; upper: number; },0,false,false
ion-range,event,ionBlur,void,true
ion-range,event,ionChange,RangeChangeEventDetail,true
@@ -1132,11 +1135,13 @@ ion-toast-controller,method,getTop,getTop() => Promise<HTMLIonToastElement | und
ion-toast,shadow
ion-toast,prop,animated,boolean,true,false,false
ion-toast,prop,buttons,(string | ToastButton)[] | undefined,undefined,false,false
ion-toast,prop,closeButtonText,string | undefined,undefined,false,false
ion-toast,prop,color,string | undefined,undefined,false,false
ion-toast,prop,cssClass,string | string[] | undefined,undefined,false,false
ion-toast,prop,duration,number,0,false,false
ion-toast,prop,enterAnimation,((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) | undefined,undefined,false,false
ion-toast,prop,header,string | undefined,undefined,false,false
ion-toast,prop,keyboardClose,boolean,false,false,false
ion-toast,prop,leaveAnimation,((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) | undefined,undefined,false,false
ion-toast,prop,message,string | undefined,undefined,false,false
@@ -1160,11 +1165,13 @@ ion-toast,css-prop,--border-width
ion-toast,css-prop,--box-shadow
ion-toast,css-prop,--button-color
ion-toast,css-prop,--color
ion-toast,css-prop,--end
ion-toast,css-prop,--height
ion-toast,css-prop,--max-height
ion-toast,css-prop,--max-width
ion-toast,css-prop,--min-height
ion-toast,css-prop,--min-width
ion-toast,css-prop,--start
ion-toast,css-prop,--width
ion-toggle,shadow

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "4.1.0",
"version": "4.3.1",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -30,7 +30,7 @@
"loader/"
],
"dependencies": {
"ionicons": "4.5.5"
"ionicons": "4.5.6"
},
"devDependencies": {
"@stencil/core": "0.17.3-0",

View File

@@ -23,8 +23,10 @@ class IonicConnector extends ScreenshotConnector {
return true
});
if (missingImages.length > 0) {
await Promise.all(missingImages.map(async image => {
while (missingImages.length > 0) {
const images = missingImages.splice(0, 20);
await Promise.all(images.map(async image => {
this.logger.debug(`downloading: ${image}`);
try {

View File

@@ -68,6 +68,7 @@ import {
TabButtonLayout,
TextareaChangeEventDetail,
TextFieldTypes,
ToastButton,
ToastOptions,
ToggleChangeEventDetail,
TransitionDoneFn,
@@ -175,7 +176,7 @@ export namespace Components {
/**
* An array of buttons for the action sheet.
*/
'buttons': (ActionSheetButton | string)[];
'buttons'?: (ActionSheetButton | string)[];
/**
* Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces.
*/
@@ -284,7 +285,7 @@ export namespace Components {
*/
'leaveAnimation'?: AnimationBuilder;
/**
* The main message to be displayed in the alert.
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'message'?: string;
/**
@@ -351,7 +352,7 @@ export namespace Components {
*/
'leaveAnimation'?: AnimationBuilder;
/**
* The main message to be displayed in the alert.
* The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'message'?: string;
/**
@@ -1193,7 +1194,7 @@ export namespace Components {
*/
'pickerFormat'?: string;
/**
* Any additional options that the picker interface can accept. See the [Picker API docs](../../picker/Picker) for the picker options.
* Any additional options that the picker interface can accept. See the [Picker API docs](../picker) for the picker options.
*/
'pickerOptions'?: DatetimeOptions;
/**
@@ -1299,7 +1300,7 @@ export namespace Components {
*/
'pickerFormat'?: string;
/**
* Any additional options that the picker interface can accept. See the [Picker API docs](../../picker/Picker) for the picker options.
* Any additional options that the picker interface can accept. See the [Picker API docs](../picker) for the picker options.
*/
'pickerOptions'?: DatetimeOptions;
/**
@@ -1546,7 +1547,11 @@ export namespace Components {
*/
'alt'?: string;
/**
* Emitted when the img src is loaded
* Emitted when the img fails to load
*/
'onIonError'?: (event: CustomEvent<void>) => void;
/**
* Emitted when the img src has been set
*/
'onIonImgDidLoad'?: (event: CustomEvent<void>) => void;
/**
@@ -1561,7 +1566,7 @@ export namespace Components {
*/
'loadingSpinner'?: SpinnerTypes | null;
/**
* Optional text to display while loading.
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'loadingText'?: string;
}
@@ -1571,7 +1576,7 @@ export namespace Components {
*/
'loadingSpinner'?: SpinnerTypes | null;
/**
* Optional text to display while loading.
* Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'loadingText'?: string;
}
@@ -1996,7 +2001,7 @@ export namespace Components {
*/
'color'?: Color;
/**
* If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href`, `onclick` or `button` property is present.
* 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.
*/
'detail'?: boolean;
/**
@@ -2038,7 +2043,7 @@ export namespace Components {
*/
'color'?: Color;
/**
* If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href`, `onclick` or `button` property is present.
* 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.
*/
'detail'?: boolean;
/**
@@ -3332,6 +3337,10 @@ export namespace Components {
*/
'step': number;
/**
* If `true`, tick marks are displayed based on the step value. Only applies when `snaps` is `true`.
*/
'ticks': boolean;
/**
* the value of the range.
*/
'value': RangeValue;
@@ -3394,6 +3403,10 @@ export namespace Components {
*/
'step'?: number;
/**
* If `true`, tick marks are displayed based on the step value. Only applies when `snaps` is `true`.
*/
'ticks'?: boolean;
/**
* the value of the range.
*/
'value'?: RangeValue;
@@ -3405,7 +3418,7 @@ export namespace Components {
*/
'pullingIcon'?: string | null;
/**
* The text you want to display when you begin to pull down
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'pullingText'?: string;
/**
@@ -3413,7 +3426,7 @@ export namespace Components {
*/
'refreshingSpinner'?: SpinnerTypes | null;
/**
* The text you want to display when performing a refresh
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'refreshingText'?: string;
}
@@ -3423,7 +3436,7 @@ export namespace Components {
*/
'pullingIcon'?: string | null;
/**
* The text you want to display when you begin to pull down
* The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'pullingText'?: string;
/**
@@ -3431,7 +3444,7 @@ export namespace Components {
*/
'refreshingSpinner'?: SpinnerTypes | null;
/**
* The text you want to display when performing a refresh
* The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'refreshingText'?: string;
}
@@ -3715,7 +3728,7 @@ export namespace Components {
*/
'mode': Mode;
/**
* Set the input's placeholder.
* Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'placeholder': string;
/**
@@ -3805,7 +3818,7 @@ export namespace Components {
*/
'onIonInput'?: (event: CustomEvent<KeyboardEvent>) => void;
/**
* Set the input's placeholder.
* Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
'placeholder'?: string;
/**
@@ -4745,6 +4758,10 @@ export namespace Components {
*/
'animated': boolean;
/**
* An array of buttons for the toast.
*/
'buttons'?: (ToastButton | string)[];
/**
* Text to display in the close button.
*/
'closeButtonText'?: string;
@@ -4769,6 +4786,10 @@ export namespace Components {
*/
'enterAnimation'?: AnimationBuilder;
/**
* Header to be shown in the toast.
*/
'header'?: string;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
'keyboardClose': boolean;
@@ -4816,6 +4837,10 @@ export namespace Components {
*/
'animated'?: boolean;
/**
* An array of buttons for the toast.
*/
'buttons'?: (ToastButton | string)[];
/**
* Text to display in the close button.
*/
'closeButtonText'?: string;
@@ -4836,6 +4861,10 @@ export namespace Components {
*/
'enterAnimation'?: AnimationBuilder;
/**
* Header to be shown in the toast.
*/
'header'?: string;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
'keyboardClose'?: boolean;

View File

@@ -51,7 +51,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
/**
* An array of buttons for the action sheet.
*/
@Prop() buttons!: (ActionSheetButton | string)[];
@Prop() buttons: (ActionSheetButton | string)[] = [];
/**
* Additional classes to apply for custom CSS. If multiple classes are
@@ -196,6 +196,8 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
zIndex: 20000 + this.overlayIndex,
},
class: {
[`${this.mode}`]: true,
...getClassMap(this.cssClass),
'action-sheet-translucent': this.translucent
}

View File

@@ -257,19 +257,19 @@ export default {
## Properties
| Property | Attribute | Description | Type | Default |
| ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- |
| `animated` | `animated` | If `true`, the action sheet will animate. | `boolean` | `true` |
| `backdropDismiss` | `backdrop-dismiss` | If `true`, the action sheet will be dismissed when the backdrop is clicked. | `boolean` | `true` |
| `buttons` _(required)_ | -- | An array of buttons for the action sheet. | `(string \| ActionSheetButton)[]` | `undefined` |
| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` |
| `enterAnimation` | -- | Animation to use when the action sheet is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `header` | `header` | Title for the action sheet. | `string \| undefined` | `undefined` |
| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` |
| `leaveAnimation` | -- | Animation to use when the action sheet is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `subHeader` | `sub-header` | Subtitle for the action sheet. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the action sheet will be translucent. Only applies when the mode is `"ios"` and the device supports backdrop-filter. | `boolean` | `false` |
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- |
| `animated` | `animated` | If `true`, the action sheet will animate. | `boolean` | `true` |
| `backdropDismiss` | `backdrop-dismiss` | If `true`, the action sheet will be dismissed when the backdrop is clicked. | `boolean` | `true` |
| `buttons` | -- | An array of buttons for the action sheet. | `(string \| ActionSheetButton)[]` | `[]` |
| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` |
| `enterAnimation` | -- | Animation to use when the action sheet is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `header` | `header` | Title for the action sheet. | `string \| undefined` | `undefined` |
| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` |
| `leaveAnimation` | -- | Animation to use when the action sheet is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `subHeader` | `sub-header` | Subtitle for the action sheet. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the action sheet will be translucent. Only applies when the mode is `"ios"` and the device supports backdrop-filter. | `boolean` | `false` |
## Events

View File

@@ -38,34 +38,34 @@ test('action-sheet: basic, scroll without cancel', async () => {
* RTL Tests
*/
test('action-sheet: basic', async () => {
test('action-sheet:rtl: basic', async () => {
await testActionSheet(DIRECTORY, '#basic', true);
});
test('action-sheet: basic, alert from action sheet', async () => {
test('action-sheet:rtl: basic, alert from action sheet', async () => {
await testActionSheet(DIRECTORY, '#alertFromActionSheet', true, testActionSheetAlert);
});
test('action-sheet: basic, cancel only', async () => {
test('action-sheet:rtl: basic, cancel only', async () => {
await testActionSheet(DIRECTORY, '#cancelOnly', true);
});
test('action-sheet: basic, custom', async () => {
test('action-sheet:rtl: basic, custom', async () => {
await testActionSheet(DIRECTORY, '#custom', true);
});
test('action-sheet: basic, icons', async () => {
test('action-sheet:rtl: basic, icons', async () => {
await testActionSheet(DIRECTORY, '#icons', true);
});
test('action-sheet: basic, no backdrop dismiss', async () => {
test('action-sheet:rtl: basic, no backdrop dismiss', async () => {
await testActionSheet(DIRECTORY, '#noBackdropDismiss', true, testActionSheetBackdrop);
});
test('action-sheet: basic, scrollable options', async () => {
test('action-sheet:rtl: basic, scrollable options', async () => {
await testActionSheet(DIRECTORY, '#scrollableOptions', true);
});
test('action-sheet: basic, scroll without cancel', async () => {
test('action-sheet:rtl: basic, scroll without cancel', async () => {
await testActionSheet(DIRECTORY, '#scrollWithoutCancel', true);
});

View File

@@ -1,25 +1,21 @@
import { newE2EPage } from '@stencil/core/testing';
import { cleanScreenshotName, generateE2EUrl } from '../../../utils/test/utils';
import { generateE2EUrl } from '../../../utils/test/utils';
export async function testActionSheet(
type: string,
selector: string,
rtl = false,
afterScreenshotHook = async (..._args: any[]): Promise<void> => {/**/},
screenshotName: string = cleanScreenshotName(selector)
afterScreenshotHook = async (..._args: any[]): Promise<void> => {/**/}
) {
try {
const pageUrl = generateE2EUrl('action-sheet', type, rtl);
if (rtl) {
screenshotName = `${screenshotName} rtl`;
}
const page = await newE2EPage({
url: pageUrl
});
const screenShotCompares = [];
const screenshotCompares = [];
const presentBtn = await page.find(selector);
await presentBtn.click();
@@ -27,20 +23,20 @@ export async function testActionSheet(
let actionSheet = await page.find('ion-action-sheet');
await actionSheet.waitForVisible();
screenShotCompares.push(await page.compareScreenshot(screenshotName));
screenshotCompares.push(await page.compareScreenshot());
await afterScreenshotHook(page, screenshotName, screenShotCompares, actionSheet);
await afterScreenshotHook(page, screenshotCompares, actionSheet);
await actionSheet.callMethod('dismiss');
await actionSheet.waitForNotVisible();
screenShotCompares.push(await page.compareScreenshot(`dismissed ${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot('dismiss'));
actionSheet = await page.find('ion-action-sheet');
expect(actionSheet).toBe(null);
for (const screenShotCompare of screenShotCompares) {
expect(screenShotCompare).toMatchScreenshot();
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
} catch (err) {
@@ -50,8 +46,7 @@ export async function testActionSheet(
export async function testActionSheetBackdrop(
page: any,
screenshotName: string,
screenShotCompares: any,
screenshotCompares: any,
actionSheet: any
) {
try {
@@ -59,7 +54,7 @@ export async function testActionSheetBackdrop(
const backdrop = await page.find('ion-backdrop');
await backdrop.click();
screenShotCompares.push(await page.compareScreenshot(`dismissed backdrop ${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot(`dismiss backdrop`));
const isVisible = await actionSheet.isVisible();
expect(isVisible).toBe(true);
@@ -70,8 +65,7 @@ export async function testActionSheetBackdrop(
export async function testActionSheetAlert(
page: any,
screenshotName: string,
screenShotCompares: any
screenshotCompares: any
) {
try {
const openAlertBtn = await page.find({ text: 'Open Alert' });
@@ -81,7 +75,7 @@ export async function testActionSheetAlert(
await alert.waitForVisible();
await page.waitFor(250);
screenShotCompares.push(await page.compareScreenshot(`alert open ${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot(`alert open`));
const alertOkayBtn = await page.find({ contains: 'Okay' });
await alertOkayBtn.click();

View File

@@ -2,6 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Me
import { AlertButton, AlertInput, Animation, AnimationBuilder, Config, CssClassMap, Mode, OverlayEventDetail, OverlayInterface } from '../../interface';
import { BACKDROP, dismiss, eventMethod, isCancel, present } from '../../utils/overlays';
import { sanitizeDOMString } from '../../utils/sanitization';
import { getClassMap } from '../../utils/theme';
import { iosEnterAnimation } from './animations/ios.enter';
@@ -72,6 +73,12 @@ export class Alert implements ComponentInterface, OverlayInterface {
/**
* The main message to be displayed in the alert.
* `message` can accept either plaintext or HTML as a string.
* To display characters normally reserved for HTML, they
* must be escaped. For example `<Ionic>` would become
* `&lt;Ionic&gt;`
*
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
@Prop() message?: string;
@@ -392,6 +399,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
},
class: {
...getClassMap(this.cssClass),
[`${this.mode}`]: true,
'alert-translucent': this.translucent
}
};
@@ -439,7 +447,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
{this.subHeader && <h2 id={subHdrId} class="alert-sub-title">{this.subHeader}</h2>}
</div>
<div id={msgId} class="alert-message" innerHTML={this.message}></div>
<div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(this.message)}></div>
{this.renderAlertInputs(labelledById)}
{this.renderAlertButtons()}

View File

@@ -1055,21 +1055,21 @@ export default {
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- |
| `animated` | `animated` | If `true`, the alert will animate. | `boolean` | `true` |
| `backdropDismiss` | `backdrop-dismiss` | If `true`, the alert will be dismissed when the backdrop is clicked. | `boolean` | `true` |
| `buttons` | -- | Array of buttons to be added to the alert. | `(string \| AlertButton)[]` | `[]` |
| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` |
| `enterAnimation` | -- | Animation to use when the alert is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `header` | `header` | The main title in the heading of the alert. | `string \| undefined` | `undefined` |
| `inputs` | -- | Array of input to show in the alert. | `AlertInput[]` | `[]` |
| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` |
| `leaveAnimation` | -- | Animation to use when the alert is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `message` | `message` | The main message to be displayed in the alert. | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `subHeader` | `sub-header` | The subtitle in the heading of the alert. Displayed under the title. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the alert will be translucent. | `boolean` | `false` |
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- |
| `animated` | `animated` | If `true`, the alert will animate. | `boolean` | `true` |
| `backdropDismiss` | `backdrop-dismiss` | If `true`, the alert will be dismissed when the backdrop is clicked. | `boolean` | `true` |
| `buttons` | -- | Array of buttons to be added to the alert. | `(string \| AlertButton)[]` | `[]` |
| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` |
| `enterAnimation` | -- | Animation to use when the alert is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `header` | `header` | The main title in the heading of the alert. | `string \| undefined` | `undefined` |
| `inputs` | -- | Array of input to show in the alert. | `AlertInput[]` | `[]` |
| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` |
| `leaveAnimation` | -- | Animation to use when the alert is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise<Animation>) \| undefined` | `undefined` |
| `message` | `message` | The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `subHeader` | `sub-header` | The subtitle in the heading of the alert. Displayed under the title. | `string \| undefined` | `undefined` |
| `translucent` | `translucent` | If `true`, the alert will be translucent. | `boolean` | `false` |
## Events

View File

@@ -37,34 +37,34 @@ test(`alert: basic, checkbox`, async () => {
// Right to Left tests
// ------------------------------------------------------
test(`alert: basic`, async () => {
test(`alert:rtl: basic`, async () => {
await testAlert(DIRECTORY, '#basic', true);
});
test(`alert: basic, long message`, async () => {
test(`alert:rtl: basic, long message`, async () => {
await testAlert(DIRECTORY, '#longMessage', true);
});
test(`alert: basic, multiple buttons`, async () => {
test(`alert:rtl: basic, multiple buttons`, async () => {
await testAlert(DIRECTORY, '#multipleButtons', true);
});
test(`alert: basic, no message`, async () => {
test(`alert:rtl: basic, no message`, async () => {
await testAlert(DIRECTORY, '#noMessage', true);
});
test(`alert: basic, confirm`, async () => {
test(`alert:rtl: basic, confirm`, async () => {
await testAlert(DIRECTORY, '#confirm', true);
});
test(`alert: basic, prompt`, async () => {
test(`alert:rtl: basic, prompt`, async () => {
await testAlert(DIRECTORY, '#prompt', true);
});
test(`alert: basic, radio`, async () => {
test(`alert:rtl: basic, radio`, async () => {
await testAlert(DIRECTORY, '#radio', true);
});
test(`alert: basic, checkbox`, async () => {
test(`alert:rtl: basic, checkbox`, async () => {
await testAlert(DIRECTORY, '#checkbox', true);
});

View File

@@ -1,24 +1,20 @@
import { newE2EPage } from '@stencil/core/testing';
import { cleanScreenshotName, generateE2EUrl } from '../../../utils/test/utils';
import { generateE2EUrl } from '../../../utils/test/utils';
export async function testAlert(
type: string,
selector: string,
rtl = false,
screenshotName: string = cleanScreenshotName(selector)
rtl = false
) {
try {
const pageUrl = generateE2EUrl('alert', type, rtl);
if (rtl) {
screenshotName = `${screenshotName} rtl`;
}
const page = await newE2EPage({
url: pageUrl
});
const screenShotCompares = [];
const screenshotCompares = [];
await page.click(selector);
await page.waitForSelector(selector);
@@ -28,18 +24,18 @@ export async function testAlert(
expect(alert).not.toBe(null);
await alert.waitForVisible();
screenShotCompares.push(await page.compareScreenshot(screenshotName));
screenshotCompares.push(await page.compareScreenshot());
await alert.callMethod('dismiss');
await alert.waitForNotVisible();
screenShotCompares.push(await page.compareScreenshot(`dismiss ${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot('dismiss'));
alert = await page.find('ion-alert');
expect(alert).toBe(null);
for (const screenShotCompare of screenShotCompares) {
expect(screenShotCompare).toMatchScreenshot();
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
} catch (err) {

View File

@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Listen, Prop } from '@stencil/core';
import { Color, RouterDirection } from '../../interface';
import { Color, Mode, RouterDirection } from '../../interface';
import { createColorClasses, openURL } from '../../utils/theme';
@Component({
@@ -9,6 +9,7 @@ import { createColorClasses, openURL } from '../../utils/theme';
shadow: true
})
export class Anchor implements ComponentInterface {
mode!: Mode;
@Prop({ context: 'window' }) win!: Window;
@@ -40,6 +41,7 @@ export class Anchor implements ComponentInterface {
return {
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'ion-activatable': true
}
};

View File

@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, Prop, QueueApi } from '@stencil/core';
import { Config } from '../../interface';
import { Config, Mode } from '../../interface';
import { rIC } from '../../utils/helpers';
import { isPlatform } from '../../utils/platform';
@@ -9,6 +9,7 @@ import { isPlatform } from '../../utils/platform';
styleUrl: 'app.scss'
})
export class App implements ComponentInterface {
mode!: Mode;
@Element() el!: HTMLElement;
@@ -34,6 +35,7 @@ export class App implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
'ion-page': true,
'force-statusbar-padding': this.config.getBoolean('_forceStatusbarPadding')
}

View File

@@ -1,5 +1,7 @@
import { Component, ComponentInterface } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-avatar',
styleUrls: {
@@ -9,6 +11,15 @@ import { Component, ComponentInterface } from '@stencil/core';
shadow: true
})
export class Avatar implements ComponentInterface {
mode!: Mode;
hostData() {
return {
class: {
[`${this.mode}`]: true,
}
};
}
render() {
return <slot></slot>;

View File

@@ -62,6 +62,7 @@ export class BackButton implements ComponentInterface {
return {
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'button': true, // ion-buttons target .button
'ion-activatable': true,

View File

@@ -1,5 +1,6 @@
import { Component, ComponentInterface, Event, EventEmitter, Listen, Prop } from '@stencil/core';
import { Mode } from '../../interface';
import { GESTURE_CONTROLLER } from '../../utils/gesture';
import { now } from '../../utils/helpers';
@@ -12,6 +13,7 @@ import { now } from '../../utils/helpers';
shadow: true
})
export class Backdrop implements ComponentInterface {
mode!: Mode;
private lastClick = -10000;
private blocker = GESTURE_CONTROLLER.createBlocker({
@@ -78,6 +80,7 @@ export class Backdrop implements ComponentInterface {
return {
tabindex: '-1',
class: {
[`${this.mode}`]: true,
'backdrop-hide': !this.visible,
'backdrop-no-tappable': !this.tappable,
}

View File

@@ -26,7 +26,10 @@ export class Badge implements ComponentInterface {
hostData() {
return {
class: createColorClasses(this.color)
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true
}
};
}

View File

@@ -148,6 +148,7 @@ export class Button implements ComponentInterface {
'aria-disabled': disabled ? 'true' : null,
class: {
...createColorClasses(color),
[`${this.mode}`]: true,
[buttonType]: true,
[`${buttonType}-${expand}`]: expand !== undefined,
[`${buttonType}-${size}`]: size !== undefined,

View File

@@ -1,5 +1,7 @@
import { Component, ComponentInterface } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-buttons',
styleUrls: {
@@ -8,4 +10,14 @@ import { Component, ComponentInterface } from '@stencil/core';
},
scoped: true,
})
export class Buttons implements ComponentInterface {}
export class Buttons implements ComponentInterface {
mode!: Mode;
hostData() {
return {
class: {
[`${this.mode}`]: true
}
};
}
}

View File

@@ -1,7 +1,6 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Mode } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({
tag: 'ion-card-content',
@@ -19,7 +18,12 @@ export class CardContent implements ComponentInterface {
hostData() {
return {
class: createThemedClasses(this.mode, 'card-content')
class: {
[`${this.mode}`]: true,
// Used internally for styling
[`card-content-${this.mode}`]: true
}
};
}
}

View File

@@ -34,6 +34,7 @@ export class CardHeader implements ComponentInterface {
class: {
...createColorClasses(this.color),
'card-header-translucent': this.translucent,
[`${this.mode}`]: true
}
};
}

View File

@@ -26,7 +26,10 @@ export class CardSubtitle implements ComponentInterface {
hostData() {
return {
class: createColorClasses(this.color),
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true
},
'role': 'heading',
'aria-level': '3'
};

View File

@@ -26,7 +26,10 @@ export class CardTitle implements ComponentInterface {
hostData() {
return {
class: createColorClasses(this.color),
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true
},
'role': 'heading',
'aria-level': '2'
};

View File

@@ -27,7 +27,10 @@ export class Card implements ComponentInterface {
hostData() {
return {
class: createColorClasses(this.color)
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true
}
};
}
}

View File

@@ -59,7 +59,6 @@ button {
background: var(--background);
contain: strict;
box-sizing: border-box;
}

View File

@@ -137,6 +137,7 @@ export class Checkbox implements ComponentInterface {
'aria-labelledby': labelId,
class: {
...createColorClasses(color),
[`${this.mode}`]: true,
'in-item': hostContext('ion-item', el),
'checkbox-checked': checked,
'checkbox-disabled': disabled,

View File

@@ -37,20 +37,9 @@
box-sizing: border-box;
}
:host(:focus) {
outline: none;
}
:host(:focus) {
--background: rgba(0, 0, 0, .16);
}
:host(.activated) {
--background: rgba(0, 0, 0, .20);
}
// Colors
// Chip Colors
// ---------------------------------------------
:host(.ion-color) {
background: current-color(base, .08);
@@ -66,14 +55,16 @@
}
// Outline
// Outline Chip
// ---------------------------------------------
:host(.chip-outline) {
border-width: 1px;
border-style: solid;
}
:host(.chip-outline:not(.ion-color)) {
:host(.chip-outline) {
border-color: rgba(0, 0, 0, .32);
background: transparent;
@@ -92,7 +83,8 @@
}
// Icon
// Chip Icon
// ---------------------------------------------
::slotted(ion-icon) {
font-size: 20px;
@@ -111,7 +103,8 @@
}
// Avatar
// Chip Avatar
// ---------------------------------------------
::slotted(ion-avatar) {
width: 24px;
@@ -127,7 +120,28 @@
}
// Hover
// Chip: Focus
// ---------------------------------------------
:host(:focus) {
outline: none;
}
:host(:focus) {
--background: rgba(0, 0, 0, .16);
}
// Chip: Activated
// ---------------------------------------------
:host(.activated) {
--background: rgba(0, 0, 0, .20);
}
// Chip: Hover
// ---------------------------------------------
@media (any-hover: hover) {
:host(:hover) {

View File

@@ -33,6 +33,7 @@ export class Chip implements ComponentInterface {
return {
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'chip-outline': this.outline,
'ion-activatable': true,
}

View File

@@ -1,5 +1,6 @@
import { Component, ComponentInterface, Element, Listen, Prop } from '@stencil/core';
import { Mode } from '../../interface';
import { matchBreakpoint } from '../../utils/media';
const win = window as any;
@@ -12,6 +13,8 @@ const BREAKPOINTS = ['', 'xs', 'sm', 'md', 'lg', 'xl'];
shadow: true
})
export class Col implements ComponentInterface {
mode!: Mode;
@Prop({ context: 'window' }) win!: Window;
@Element() el!: HTMLStencilElement;
@@ -247,6 +250,9 @@ export class Col implements ComponentInterface {
hostData() {
const isRTL = this.win.document.dir === 'rtl';
return {
class: {
[`${this.mode}`]: true
},
style: {
...this.calculateOffset(isRTL),
...this.calculatePull(isRTL),

View File

@@ -293,6 +293,7 @@ export class Content implements ComponentInterface {
return {
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'content-sizing': hostContext('ion-popover', this.el),
'overscroll': !!this.forceOverscroll,
},

View File

@@ -2,7 +2,7 @@ import { convertDataToISO, parseDate } from './datetime-util';
describe('datetime-util', () => {
describe('convertDataToISO', () => {
it('prints an emptry string for an empty datetime', () => {
it('prints an empty string for an empty datetime', () => {
expect(convertDataToISO({})).toEqual('');
});

View File

@@ -6,7 +6,7 @@
export function getDateValue(date: DatetimeData, format: string): number {
const getValue = getValueFromFormat(date, format);
if (getValue) { return getValue; }
if (getValue !== undefined) { return getValue; }
const defaultDate = parseDate(new Date().toISOString());
return getValueFromFormat((defaultDate as DatetimeData), format);
@@ -241,7 +241,49 @@ export function parseDate(val: string | undefined | null): DatetimeData | undefi
};
}
/**
* Converts a valid UTC datetime string
* To the user's local timezone
* Note: This is not meant for time strings
* such as "01:47"
*/
export const getLocalDateTime = (dateString: any = ''): Date => {
/**
* Ensures that YYYY-MM-DD, YYYY-MM,
* YYYY-DD, etc does not get affected
* by timezones and stays on the day/month
* that the user provided
*/
if (
dateString.length === 10 ||
dateString.length === 7
) {
dateString += ' ';
}
const date = (typeof dateString === 'string' && dateString.length > 0) ? new Date(dateString) : new Date();
return new Date(
Date.UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
)
);
};
export function updateDate(existingData: DatetimeData, newData: any): boolean {
if (!newData || typeof newData === 'string') {
const localDateTime = getLocalDateTime(newData);
if (!Number.isNaN(localDateTime.getTime())) {
newData = localDateTime.toISOString();
}
}
if (newData && newData !== '') {
if (typeof newData === 'string') {
@@ -368,6 +410,7 @@ export function convertDataToISO(data: DatetimeData): string {
rtn += 'Z';
} else {
// YYYY-MM-DDTHH:mm:SS+/-HH:mm
rtn += (data.tzOffset > 0 ? '+' : '-') + twoDigit(Math.floor(Math.abs(data.tzOffset / 60))) + ':' + twoDigit(data.tzOffset % 60);
}

View File

@@ -175,7 +175,7 @@ export class Datetime implements ComponentInterface {
/**
* Any additional options that the picker interface can accept.
* See the [Picker API docs](../../picker/Picker) for the picker options.
* See the [Picker API docs](../picker) for the picker options.
*/
@Prop() pickerOptions?: DatetimeOptions;
@@ -261,11 +261,36 @@ export class Datetime implements ComponentInterface {
const pickerOptions = this.generatePickerOptions();
const picker = await this.pickerCtrl.create(pickerOptions);
this.isExpanded = true;
picker.onDidDismiss().then(() => {
this.isExpanded = false;
this.setFocus();
});
picker.addEventListener('ionPickerColChange', async (event: any) => {
const data = event.detail;
/**
* Don't bother checking for non-dates as things like hours or minutes
* are always going to have the same number of column options
*/
if (data.name !== 'month' && data.name !== 'day' && data.name !== 'year') { return; }
const colSelectedIndex = data.selectedIndex;
const colOptions = data.options;
const changeData: any = {};
changeData[data.name] = {
value: colOptions[colSelectedIndex].value
};
this.updateDatetimeValue(changeData);
const columns = this.generateColumns();
picker.columns = columns;
await this.validate(picker);
});
await this.validate(picker);
await picker.present();
}
@@ -300,6 +325,7 @@ export class Datetime implements ComponentInterface {
text: this.cancelText,
role: 'cancel',
handler: () => {
this.updateDatetimeValue(this.value);
this.ionCancel.emit();
}
},
@@ -307,6 +333,19 @@ export class Datetime implements ComponentInterface {
text: this.doneText,
handler: (data: any) => {
this.updateDatetimeValue(data);
/**
* Prevent convertDataToISO from doing any
* kind of transformation based on timezone
* This cancels out any change it attempts to make
*
* Important: Take the timezone offset based on
* the date that is currently selected, otherwise
* there can be 1 hr difference when dealing w/ DST
*/
const date = new Date(convertDataToISO(this.datetimeValue));
this.datetimeValue.tzOffset = date.getTimezoneOffset() * -1;
this.value = convertDataToISO(this.datetimeValue);
}
}
@@ -360,6 +399,7 @@ export class Datetime implements ComponentInterface {
// cool, we've loaded up the columns with options
// preselect the option for this column
const optValue = getDateValue(this.datetimeValue, format);
const selectedIndex = colOptions.findIndex(opt => opt.value === optValue);
return {
@@ -527,6 +567,13 @@ export class Datetime implements ComponentInterface {
private getText() {
// create the text of the formatted data
const template = this.displayFormat || this.pickerFormat || DEFAULT_FORMAT;
if (
this.value === undefined ||
this.value === null ||
this.value.length === 0
) { return; }
return renderDatetime(template, this.datetimeValue, this.locale);
}
@@ -568,6 +615,7 @@ export class Datetime implements ComponentInterface {
'aria-haspopup': 'true',
'aria-labelledby': labelId,
class: {
[`${this.mode}`]: true,
'datetime-disabled': disabled,
'datetime-readonly': readonly,
'datetime-placeholder': addPlaceholderClass,

View File

@@ -287,30 +287,28 @@ dates in JavaScript.
</ion-item>
```
```javascript
this.customYearValues = [2020, 2016, 2008, 2004, 2000, 1996];
```typescript
@Component({})
export class MyComponent {
customYearValues = [2020, 2016, 2008, 2004, 2000, 1996];
customDayShortNames = ['s\u00f8n', 'man', 'tir', 'ons', 'tor', 'fre', 'l\u00f8r'];
customPickerOptions: any;
this.customDayShortNames = [
's\u00f8n',
'man',
'tir',
'ons',
'tor',
'fre',
'l\u00f8r'
];
this.customPickerOptions = {
buttons: [{
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
text: 'Log',
handler: () => {
console.log('Clicked Log. Do not Dismiss.');
return false;
constructor() {
this.customPickerOptions = {
buttons: [{
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
text: 'Log',
handler: () => {
console.log('Clicked Log. Do not Dismiss.');
return false;
}
}]
}
}]
}
}
```
@@ -697,7 +695,7 @@ export default Example;
| `monthValues` | `month-values` | Values used to create the list of selectable months. By default the month values range from `1` to `12`. However, to control exactly which months to display, the `monthValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if only summer months should be shown, then this input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a zero-based index, meaning January's value is `1`, and December's is `12`. | `number \| number[] \| string \| undefined` | `undefined` |
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
| `pickerFormat` | `picker-format` | The format of the date and time picker columns the user selects. A datetime input can have one or many datetime parts, each getting their own column which allow individual selection of that particular datetime part. For example, year and month columns are two individually selectable columns which help choose an exact date from the datetime picker. Each column follows the string parse format. Defaults to use `displayFormat`. | `string \| undefined` | `undefined` |
| `pickerOptions` | -- | Any additional options that the picker interface can accept. See the [Picker API docs](../../picker/Picker) for the picker options. | `undefined \| { columns?: PickerColumn[] \| undefined; buttons?: PickerButton[] \| undefined; cssClass?: string \| string[] \| undefined; backdropDismiss?: boolean \| undefined; animated?: boolean \| undefined; mode?: "ios" \| "md" \| undefined; keyboardClose?: boolean \| undefined; id?: string \| undefined; enterAnimation?: AnimationBuilder \| undefined; leaveAnimation?: AnimationBuilder \| undefined; }` | `undefined` |
| `pickerOptions` | -- | Any additional options that the picker interface can accept. See the [Picker API docs](../picker) for the picker options. | `undefined \| { columns?: PickerColumn[] \| undefined; buttons?: PickerButton[] \| undefined; cssClass?: string \| string[] \| undefined; backdropDismiss?: boolean \| undefined; animated?: boolean \| undefined; mode?: "ios" \| "md" \| undefined; keyboardClose?: boolean \| undefined; id?: string \| undefined; enterAnimation?: AnimationBuilder \| undefined; leaveAnimation?: AnimationBuilder \| undefined; }` | `undefined` |
| `placeholder` | `placeholder` | The text to display when there's no date selected yet. Using lowercase to match the input attribute | `null \| string \| undefined` | `undefined` |
| `readonly` | `readonly` | If `true`, the datetime appears normal but is not interactive. | `boolean` | `false` |
| `value` | `value` | The value of the datetime as a valid ISO 8601 datetime string. | `null \| string \| undefined` | `undefined` |

View File

@@ -99,6 +99,11 @@
<ion-datetime display-format="HH:mm"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>HH:mm (initial value 00:00)</ion-label>
<ion-datetime display-format="HH:mm" value="00:00"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>h:mm a</ion-label>
<ion-datetime display-format="h:mm a" value="01:47"></ion-datetime>

View File

@@ -1,20 +1,19 @@
import { DatetimeOptions } from '../datetime-interface';
import { DatetimeData, getDateValue } from '../datetime-util';
import { DatetimeData, daysInMonth, getDateValue, getLocalDateTime } from '../datetime-util';
describe('Datetime', () => {
describe('getDateValue()', () => {
it('it should return the date value for the current day', () => {
const today = new Date();
const dayValue = getDateValue({}, 'DD');
const monthvalue = getDateValue({}, 'MM');
const yearValue = getDateValue({}, 'YYYY');
expect(dayValue).toEqual(today.getDate());
expect(monthvalue).toEqual(today.getMonth() + 1);
expect(yearValue).toEqual(today.getFullYear());
});
it('it should return the date value for a given day', () => {
const date = new Date('15 October 1995');
const dateTimeData: DatetimeData = {
@@ -22,14 +21,75 @@ describe('Datetime', () => {
month: date.getMonth() + 1,
day: date.getDate()
}
const dayValue = getDateValue(dateTimeData, 'DD');
const monthvalue = getDateValue(dateTimeData, 'MM');
const yearValue = getDateValue(dateTimeData, 'YYYY');
expect(dayValue).toEqual(date.getDate());
expect(monthvalue).toEqual(date.getMonth() + 1);
expect(yearValue).toEqual(date.getFullYear());
});
});
});
describe('getLocalDateTime()', () => {
it('should format a datetime string according to the local timezone', () => {
const dateStringTests = [
{ expectedHourUTC: 12, input: `2019-03-02T12:08:06.601-00:00`, expectedOutput: `2019-03-02T%HOUR%:08:06.601Z` },
{ expectedHourUTC: 12, input: `2019-11-02T12:08:06.601-00:00`, expectedOutput: `2019-11-02T%HOUR%:08:06.601Z` },
{ expectedHourUTC: 8, input: `1994-12-15T13:47:20.789+05:00`, expectedOutput: `1994-12-15T%HOUR%:47:20.789Z` },
{ expectedHourUTC: 18, input: `1994-12-15T13:47:20.789-05:00`, expectedOutput: `1994-12-15T%HOUR%:47:20.789Z` },
{ expectedHourUTC: 9, input: `2019-02-14T09:00:00.000Z`, expectedOutput: `2019-02-14T%HOUR%:00:00.000Z` }
];
dateStringTests.forEach(test => {
const convertToLocal = getLocalDateTime(test.input);
const timeZoneOffset = convertToLocal.getTimezoneOffset() / 60;
const expectedDateString = test.expectedOutput.replace('%HOUR%', padNumber(test.expectedHourUTC - timeZoneOffset));
expect(convertToLocal.toISOString()).toEqual(expectedDateString);
});
});
it('should format a date string and not get affected by the timezone offset', () => {
const dateStringTests = [
{ input: '2019-03-20', expectedOutput: '2019-03-20' },
{ input: '1994-04-15', expectedOutput: '1994-04-15' },
{ input: '2008-09-02', expectedOutput: '2008-09-02' },
{ input: '1995-02', expectedOutput: '1995-02' },
{ input: '1994-03-14', expectedOutput: '1994-03-14' },
{ input: '9 01:47', expectedOutput: '09-01T01:47' }
];
dateStringTests.forEach(test => {
const convertToLocal = getLocalDateTime(test.input);
expect(convertToLocal.toISOString()).toContain(test.expectedOutput);
});
});
});
describe('daysInMonth()', () => {
it('should return correct days in month for month and year', () => {
expect(daysInMonth(1, 2019)).toBe(31);
expect(daysInMonth(2, 2019)).toBe(28);
expect(daysInMonth(3, 2019)).toBe(31);
expect(daysInMonth(4, 2019)).toBe(30);
expect(daysInMonth(5, 2019)).toBe(31);
expect(daysInMonth(6, 2019)).toBe(30);
expect(daysInMonth(7, 2019)).toBe(31);
expect(daysInMonth(8, 2019)).toBe(31);
expect(daysInMonth(9, 2019)).toBe(30);
expect(daysInMonth(10, 2019)).toBe(31);
expect(daysInMonth(11, 2019)).toBe(30);
expect(daysInMonth(12, 2019)).toBe(31);
expect(daysInMonth(2, 2020)).toBe(29);
});
});
});
function padNumber(number: number, totalLength: number = 2): string {
return number.toString().padStart(totalLength, '0');
}

View File

@@ -78,29 +78,27 @@
</ion-item>
```
```javascript
this.customYearValues = [2020, 2016, 2008, 2004, 2000, 1996];
```typescript
@Component({})
export class MyComponent {
customYearValues = [2020, 2016, 2008, 2004, 2000, 1996];
customDayShortNames = ['s\u00f8n', 'man', 'tir', 'ons', 'tor', 'fre', 'l\u00f8r'];
customPickerOptions: any;
this.customDayShortNames = [
's\u00f8n',
'man',
'tir',
'ons',
'tor',
'fre',
'l\u00f8r'
];
this.customPickerOptions = {
buttons: [{
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
text: 'Log',
handler: () => {
console.log('Clicked Log. Do not Dismiss.');
return false;
constructor() {
this.customPickerOptions = {
buttons: [{
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
text: 'Log',
handler: () => {
console.log('Clicked Log. Do not Dismiss.');
return false;
}
}]
}
}]
}
}
```
```

View File

@@ -95,6 +95,7 @@ export class FabButton implements ComponentInterface {
'aria-disabled': disabled ? 'true' : null,
class: {
...createColorClasses(color),
[`${this.mode}`]: true,
'fab-button-in-list': inList,
'fab-button-translucent-in-list': inList && translucent,
'fab-button-close-active': activated,

View File

@@ -1,11 +1,15 @@
import { Component, ComponentInterface, Element, Prop, Watch } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-fab-list',
styleUrl: 'fab-list.scss',
shadow: true
})
export class FabList implements ComponentInterface {
mode!: Mode;
@Element() el!: HTMLIonFabElement;
/**
@@ -32,6 +36,7 @@ export class FabList implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
'fab-list-active': this.activated,
[`fab-list-side-${this.side}`]: true
}

View File

@@ -1,11 +1,14 @@
import { Component, ComponentInterface, Element, Listen, Method, Prop, Watch } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-fab',
styleUrl: 'fab.scss',
shadow: true
})
export class Fab implements ComponentInterface {
mode!: Mode;
@Element() el!: HTMLElement;
@@ -75,6 +78,7 @@ export class Fab implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
[`fab-horizontal-${this.horizontal}`]: this.horizontal !== undefined,
[`fab-vertical-${this.vertical}`]: this.vertical !== undefined,
'fab-edge': this.edge

View File

@@ -1,42 +1,42 @@
import { newE2EPage } from '@stencil/core/testing';
import { cleanScreenshotName, generateE2EUrl } from '../../../utils/test/utils';
import { generateE2EUrl } from '../../../utils/test/utils';
export async function testFab(
type: string,
selector: string,
rtl = false,
screenshotName: string = cleanScreenshotName(selector)
rtl = false
) {
try {
const pageUrl = generateE2EUrl('fab', type, rtl);
if (rtl) {
screenshotName = `${screenshotName} rtl`;
}
const page = await newE2EPage({
url: pageUrl
});
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot(`${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot());
const fab = await getFabComponent(page, selector);
await fab.click();
await page.waitFor(250);
await ensureFabState(fab, 'active');
screenshotCompares.push(await page.compareScreenshot(`${screenshotName} open`));
screenshotCompares.push(await page.compareScreenshot('open'));
const fabButton = await getFabButton(fab);
await fabButton.click();
await page.waitFor(250);
await ensureFabState(fab, 'inactive');
screenshotCompares.push(await page.compareScreenshot(`${screenshotName} close`));
screenshotCompares.push(await page.compareScreenshot('close'));
for (const screenShotCompare of screenshotCompares) {
expect(screenShotCompare).toMatchScreenshot();
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
} catch (err) {
throw err;
@@ -46,31 +46,27 @@ export async function testFab(
export async function testDisabledFab(
type: string,
selector: string,
rtl = false,
screenshotName: string = cleanScreenshotName(selector)
rtl = false
) {
try {
const pageUrl = generateE2EUrl('fab', type, rtl);
if (rtl) {
screenshotName = `${screenshotName} rtl`;
}
const page = await newE2EPage({
url: pageUrl
});
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot(`disabled ${screenshotName}`));
screenshotCompares.push(await page.compareScreenshot('disabled'));
const fab = await getFabComponent(page, selector);
await fab.click();
await ensureFabState(fab, 'inactive');
screenshotCompares.push(await page.compareScreenshot(`disabled ${screenshotName} attempt open`));
screenshotCompares.push(await page.compareScreenshot('disabled, attempt open'));
for (const screenShotCompare of screenshotCompares) {
expect(screenShotCompare).toMatchScreenshot();
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
} catch (err) {
throw err;

View File

@@ -1,7 +1,6 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Mode } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({
tag: 'ion-footer',
@@ -25,13 +24,15 @@ export class Footer implements ComponentInterface {
@Prop() translucent = false;
hostData() {
const themedClasses = createThemedClasses(this.mode, 'footer');
const translucentClasses = this.translucent ? createThemedClasses(this.mode, 'footer-translucent') : null;
return {
class: {
...themedClasses,
...translucentClasses
[`${this.mode}`]: true,
// Used internally for styling
[`footer-${this.mode}`]: true,
[`footer-translucent`]: this.translucent,
[`footer-translucent-${this.mode}`]: this.translucent,
}
};
}

View File

@@ -0,0 +1,14 @@
import { newE2EPage } from '@stencil/core/testing';
import { checkComponentModeClasses } from '../../../../utils/test/utils';
test('footer: translucent', async () => {
const page = await newE2EPage({
url: '/src/components/footer/test/translucent?ionic:_testing=true'
});
await checkComponentModeClasses(await page.find('ion-footer'), 'footer-translucent');
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -1,11 +1,14 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-grid',
styleUrl: 'grid.scss',
shadow: true
})
export class Grid implements ComponentInterface {
mode!: Mode;
/**
* If `true`, the grid will have a fixed width based on the screen size.
@@ -15,6 +18,7 @@ export class Grid implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
'grid-fixed': this.fixed
}
};

View File

@@ -1,7 +1,6 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Mode } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({
tag: 'ion-header',
@@ -25,13 +24,15 @@ export class Header implements ComponentInterface {
@Prop() translucent = false;
hostData() {
const themedClasses = createThemedClasses(this.mode, 'header');
const translucentClasses = this.translucent ? createThemedClasses(this.mode, 'header-translucent') : null;
return {
class: {
...themedClasses,
...translucentClasses
[`${this.mode}`]: true,
// Used internally for styling
[`header-${this.mode}`]: true,
[`header-translucent`]: this.translucent,
[`header-translucent-${this.mode}`]: this.translucent,
}
};
}

View File

@@ -0,0 +1,14 @@
import { newE2EPage } from '@stencil/core/testing';
import { checkComponentModeClasses } from '../../../../utils/test/utils';
test('header: translucent', async () => {
const page = await newE2EPage({
url: '/src/components/header/test/translucent?ionic:_testing=true'
});
await checkComponentModeClasses(await page.find('ion-header'), 'header-translucent');
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -1,11 +1,14 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-img',
styleUrl: 'img.scss',
shadow: true
})
export class Img implements ComponentInterface {
mode!: Mode;
private io?: IntersectionObserver;
@@ -13,6 +16,8 @@ export class Img implements ComponentInterface {
@State() loadSrc?: string;
@State() loadError?: () => void;
/**
* This attribute defines the alternative text describing the image.
* Users will see this text displayed if the image URL is wrong,
@@ -29,9 +34,12 @@ export class Img implements ComponentInterface {
this.addIO();
}
/** Emitted when the img src is loaded */
/** Emitted when the img src has been set */
@Event() ionImgDidLoad!: EventEmitter<void>;
/** Emitted when the img fails to load */
@Event() ionError!: EventEmitter<void>;
componentDidLoad() {
this.addIO();
}
@@ -60,10 +68,15 @@ export class Img implements ComponentInterface {
}
private load() {
this.loadError = this.onError;
this.loadSrc = this.src;
this.ionImgDidLoad.emit();
}
private onError = () => {
this.ionError.emit();
}
private removeIO() {
if (this.io) {
this.io.disconnect();
@@ -71,12 +84,21 @@ export class Img implements ComponentInterface {
}
}
hostData() {
return {
class: {
[`${this.mode}`]: true,
}
};
}
render() {
return (
<img
src={this.loadSrc}
alt={this.alt}
decoding="async"
onError={this.loadError}
/>
);
}

View File

@@ -79,9 +79,10 @@ export default Example
## Events
| Event | Description | Type |
| --------------- | ---------------------------------- | ------------------- |
| `ionImgDidLoad` | Emitted when the img src is loaded | `CustomEvent<void>` |
| Event | Description | Type |
| --------------- | ------------------------------------- | ------------------- |
| `ionError` | Emitted when the img fails to load | `CustomEvent<void>` |
| `ionImgDidLoad` | Emitted when the img src has been set | `CustomEvent<void>` |
----------------------------------------------

View File

@@ -31,6 +31,13 @@
<f></f>
<f></f>
<ion-img id="hidden" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAARQAQMAAAA2ut43AAAABlBMVEXMzMz////TjRV2AAAEPUlEQVR42u3RMREAMBDDsPAn/eXhavOq87Ztu7u7+6eBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBf2hvgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgatgn4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGr7S0wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDBwFewzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcLW9BQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq6CfQYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgautrfAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAVbDPwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNX2FhgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGLgK9hkYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBi42t4CAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV8E+AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA1fbW2BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYOAq2GdgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGDgansLDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMXAX7DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDFxtb4GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGrYJ+BgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBq+0tMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcBXsMzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHC1vQUGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgaugn0GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGjvYD5WuYrpZqdmcAAAAASUVORK5CYII="></ion-img>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<f></f>
<ion-img id="hidden" src="data:image/png;base64,"></ion-img>
</ion-content>
@@ -63,6 +70,10 @@
document.body.addEventListener('ionImgDidLoad', (event) => {
console.log('image did load', event.target);
});
document.body.addEventListener('ionError', (event) => {
console.error('image error', event.target);
});
</script>
</body>

View File

@@ -1,7 +1,7 @@
import { Component, ComponentInterface, Prop } from '@stencil/core';
import { Config, Mode, SpinnerTypes } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
import { sanitizeDOMString } from '../../utils/sanitization';
@Component({
tag: 'ion-infinite-scroll-content',
@@ -23,6 +23,12 @@ export class InfiniteScrollContent implements ComponentInterface {
/**
* Optional text to display while loading.
* `loadingText` can accept either plaintext or HTML as a string.
* To display characters normally reserved for HTML, they
* must be escaped. For example `<Ionic>` would become
* `&lt;Ionic&gt;`
*
* For more information: [Security Documentation](https://ionicframework.com/docs/faq/security)
*/
@Prop() loadingText?: string;
@@ -30,14 +36,19 @@ export class InfiniteScrollContent implements ComponentInterface {
if (this.loadingSpinner === undefined) {
this.loadingSpinner = this.config.get(
'infiniteLoadingSpinner',
this.config.get('spinner', 'lines')
this.config.get('spinner', this.mode === 'ios' ? 'lines' : 'crescent')
);
}
}
hostData() {
return {
class: createThemedClasses(this.mode, 'infinite-scroll-content')
class: {
[`${this.mode}`]: true,
// Used internally for styling
[`infinite-scroll-content-${this.mode}`]: true
}
};
}
@@ -50,7 +61,7 @@ export class InfiniteScrollContent implements ComponentInterface {
</div>
)}
{this.loadingText && (
<div class="infinite-loading-text" innerHTML={this.loadingText} />
<div class="infinite-loading-text" innerHTML={sanitizeDOMString(this.loadingText)} />
)}
</div>
);

View File

@@ -76,10 +76,10 @@ export default Example
## Properties
| Property | Attribute | Description | Type | Default |
| ---------------- | ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- |
| `loadingSpinner` | `loading-spinner` | An animated SVG spinner that shows while loading. | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` |
| `loadingText` | `loading-text` | Optional text to display while loading. | `string \| undefined` | `undefined` |
| Property | Attribute | Description | Type | Default |
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- |
| `loadingSpinner` | `loading-spinner` | An animated SVG spinner that shows while loading. | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` |
| `loadingText` | `loading-text` | Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` |
----------------------------------------------

View File

@@ -1,10 +1,13 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Mode } from '../../interface';
@Component({
tag: 'ion-infinite-scroll',
styleUrl: 'infinite-scroll.scss'
})
export class InfiniteScroll implements ComponentInterface {
mode!: Mode;
private thrPx = 0;
private thrPc = 0;
@@ -212,6 +215,7 @@ export class InfiniteScroll implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
'infinite-scroll-loading': this.isLoading,
'infinite-scroll-enabled': !this.disabled
}

View File

@@ -30,7 +30,7 @@
<ion-list id="list"></ion-list>
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading more data...">
<ion-infinite-scroll-content loading-spinner="crescent" loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

View File

@@ -30,7 +30,7 @@
</ion-list>
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading more data...">
<ion-infinite-scroll-content loading-spinner="bubbles" loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

View File

@@ -18,8 +18,8 @@
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="Loading more data...">
loading-spinner="bubbles"
loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>

View File

@@ -24,7 +24,7 @@
<ion-content id="content" padding>
<ion-infinite-scroll threshold="100px" id="infinite-scroll" position="top">
<ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Loading more data...">
<ion-infinite-scroll-content loading-spinner="bubbles" loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>

View File

@@ -68,7 +68,7 @@
width: 100%;
max-width: 100%;
height: 100%;
max-height: 100%;
border: 0;

View File

@@ -307,7 +307,12 @@ export class Input implements ComponentInterface {
}
}
private clearTextInput = () => {
private clearTextInput = (ev?: Event) => {
if (this.clearInput && !this.readonly && !this.disabled && ev) {
ev.preventDefault();
ev.stopPropagation();
}
this.value = '';
}
@@ -327,6 +332,7 @@ export class Input implements ComponentInterface {
'aria-disabled': this.disabled ? 'true' : null,
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'has-value': this.hasValue(),
'has-focus': this.hasFocus
}

View File

@@ -0,0 +1,15 @@
import { newE2EPage } from '@stencil/core/testing';
test('input: spec', async () => {
const page = await newE2EPage({
url: '/src/components/input/test/spec?ionic:_testing=true'
});
const compares = [];
compares.push(await page.compareScreenshot());
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Input - Spec</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script src="../../../../../dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-content class="ion-padding">
<ion-item>
<ion-label position="floating">Standard</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">Standard</ion-label>
<ion-input value="value"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Stacked</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Stacked</ion-label>
<ion-input value="value"></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">Floating</ion-label>
<ion-textarea></ion-textarea>
</ion-item>
<ion-item>
<ion-label position="floating">Floating</ion-label>
<ion-textarea value="value"></ion-textarea>
</ion-item>
<ion-item>
<ion-label position="stacked">Stacked</ion-label>
<ion-textarea></ion-textarea>
</ion-item>
<ion-item>
<ion-label position="stacked">Stacked</ion-label>
<ion-textarea value="value"></ion-textarea>
</ion-item>
</ion-content>
<style>
ion-item {
--background: #f5f5f5;
}
</style>
</ion-app>
</body>
</html>

View File

@@ -55,6 +55,7 @@ export class ItemDivider implements ComponentInterface {
return {
class: {
...createColorClasses(this.color),
[`${this.mode}`]: true,
'item-divider-sticky': this.sticky,
'item': true,
}

View File

@@ -35,7 +35,7 @@
</ion-label>
<ion-icon name="trash" slot="start"></ion-icon>
</ion-item-divider>
<ion-item-divider>
<ion-label>
Divider in List
@@ -84,8 +84,8 @@
<ion-button slot="end">button</ion-button>
</ion-item-divider>
<ion-item text-wrap class="custom-item">
<ion-label>
<ion-item class="custom-item">
<ion-label text-wrap>
Multiline text that should wrap when it is too long to fit on one line in the item. Attribute on .item
</ion-label>
</ion-item>
@@ -108,7 +108,7 @@
</ion-label>
</ion-item-divider>
<ion-item text-wrap>
<ion-item>
<ion-label>
<h1>H1 Title Text</h1>
</ion-label>

View File

@@ -1,7 +1,6 @@
import { Component, ComponentInterface } from '@stencil/core';
import { Mode } from '../../interface';
import { createThemedClasses } from '../../utils/theme';
@Component({
tag: 'ion-item-group',
@@ -18,8 +17,12 @@ export class ItemGroup implements ComponentInterface {
return {
'role': 'group',
class: {
...createThemedClasses(this.mode, 'item-group'),
'item': true,
[`${this.mode}`]: true,
// Used internally for styling
[`item-group-${this.mode}`]: true,
'item': true
}
};
}

View File

@@ -38,6 +38,8 @@
@include text-inherit();
@include padding(0, .7em);
display: inline-block;
position: relative;
width: 100%;
@@ -51,13 +53,14 @@
cursor: pointer;
appearance: none;
box-sizing: border-box;
}
.button-inner {
display: flex;
flex-direction: column;
flex-flow: row nowrap;
flex-flow: column nowrap;
flex-shrink: 0;
align-items: center;
justify-content: center;
@@ -66,6 +69,29 @@
height: 100%;
}
.horizontal-wrapper {
display: flex;
flex-flow: row nowrap;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 100%;
}
::slotted(*) {
flex-shrink: 0;
}
::slotted([slot="start"]) {
@include margin(0, 5px, 0, 0);
}
::slotted([slot="end"]) {
@include margin(0, 0, 0, 5px);
}
::slotted([slot="icon-only"]) {
@include padding(0);
@include margin(0, 10px);
@@ -86,3 +112,17 @@
transition-property: none;
transition-timing-function: cubic-bezier(.65, .05, .36, 1);
}
// Item Disabled Styling
// --------------------------------------------------
:host(.item-option-disabled) {
pointer-events: none;
}
:host(.item-option-disabled) .button-native {
cursor: default;
opacity: .5;
pointer-events: none;
}

View File

@@ -60,10 +60,14 @@ export class ItemOption implements ComponentInterface {
}
hostData() {
const { disabled, expandable } = this;
return {
class: {
...createColorClasses(this.color),
'item-option-expandable': this.expandable,
[`${this.mode}`]: true,
'item-option-disabled': disabled,
'item-option-expandable': expandable,
'ion-activatable': true,
}
};
@@ -80,12 +84,14 @@ export class ItemOption implements ComponentInterface {
href={this.href}
>
<span class="button-inner">
<slot name="start"></slot>
<slot name="top" />
<slot name="icon-only" />
<slot></slot>
<slot name="bottom" />
<slot name="end"></slot>
<slot name="top"></slot>
<div class="horizontal-wrapper">
<slot name="start"></slot>
<slot name="icon-only"></slot>
<slot></slot>
<slot name="end"></slot>
</div>
<slot name="bottom"></slot>
</span>
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</TagType>

View File

@@ -77,6 +77,8 @@ ion-item-options {
ion-item-options {
display: flex;
visibility: hidden;
}
&.item-sliding-active-options-start .item-options-start,

View File

@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop } from '@stencil/core';
import { Side } from '../../interface';
import { Mode, Side } from '../../interface';
import { isEndSide } from '../../utils/helpers';
@Component({
@@ -11,6 +11,8 @@ import { isEndSide } from '../../utils/helpers';
}
})
export class ItemOptions implements ComponentInterface {
mode!: Mode;
@Element() el!: HTMLElement;
@Prop({ context: 'window' }) win!: Window;
@@ -38,6 +40,11 @@ export class ItemOptions implements ComponentInterface {
const isEnd = isEndSide(this.win, this.side);
return {
class: {
[`${this.mode}`]: true,
// Used internally for styling
[`item-options-${this.mode}`]: true,
'item-options-start': !isEnd,
'item-options-end': isEnd
}

View File

@@ -33,7 +33,7 @@ ion-item-sliding .item {
.item-sliding-active-swipe-end .item-options-end .item-option-expandable {
@include multi-dir() {
/* stylelint-disable-next-line property-blacklist */
padding-left: 90%;
padding-left: 100%;
}
@include ltr() {
@@ -51,7 +51,7 @@ ion-item-sliding .item {
.item-sliding-active-swipe-start .item-options-start .item-option-expandable {
@include multi-dir() {
/* stylelint-disable-next-line property-blacklist */
padding-right: 90%;
padding-right: 100%;
}
@include ltr() {

View File

@@ -1,6 +1,6 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Gesture, GestureDetail } from '../../interface';
import { Gesture, GestureDetail, Mode } from '../../interface';
const SWIPE_MARGIN = 30;
const ELASTIC_FACTOR = 0.55;
@@ -29,6 +29,7 @@ let openSlidingItem: HTMLIonItemSlidingElement | undefined;
styleUrl: 'item-sliding.scss'
})
export class ItemSliding implements ComponentInterface {
mode!: Mode;
private item: HTMLIonItemElement | null = null;
private openAmount = 0;
@@ -307,6 +308,7 @@ export class ItemSliding implements ComponentInterface {
hostData() {
return {
class: {
[`${this.mode}`]: true,
'item-sliding-active-slide': (this.state !== SlidingState.Disabled),
'item-sliding-active-options-end': (this.state & SlidingState.End) !== 0,
'item-sliding-active-options-start': (this.state & SlidingState.Start) !== 0,
@@ -319,10 +321,10 @@ export class ItemSliding implements ComponentInterface {
function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolean, isOnResetZone: boolean): boolean {
// The logic required to know when the sliding item should close (openAmount=0)
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
// depends on three booleans (isResetDirection, isMovingFast, isOnResetZone)
// and it ended up being too complicated to be written manually without errors
// so the truth table is attached below: (0=false, 1=true)
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
// isResetDirection | isMovingFast | isOnResetZone || shouldClose
// 0 | 0 | 0 || 0
// 0 | 0 | 1 || 1
// 0 | 1 | 0 || 0

View File

@@ -35,19 +35,145 @@ Options can be expanded to take up the full width of the item if you swipe past
```html
<ion-list>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option (click)="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" (click)="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option (click)="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
```
@@ -56,19 +182,145 @@ Options can be expanded to take up the full width of the item if you swipe past
```html
<ion-list>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option onClick="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" onClick="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option onClick="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
```
@@ -83,17 +335,143 @@ import { IonList, IonItemSliding, IonItem, IonLabel, IonItemOptions, IonItemOpti
const Example: React.SFC<{}> = () => (
<IonList>
{/* Sliding item with text options on both sides */}
<IonItemSliding>
<IonItem>
<IonLabel>Item</IonLabel>
</IonItem>
<IonItemOptions side="start">
<IonItemOption onClick={() => {}}>Favorite</IonItemOption>
<IonItemOption color="danger" onClick={() => {}}>Share</IonItemOption>
<IonItemOption onClick={favorite(item)}>Favorite</IonItemOption>
<IonItemOption color="danger" onClick={share(item)}>Share</IonItemOption>
</IonItemOptions>
<IonItem>
<IonLabel>Item Options</IonLabel>
</IonItem>
<IonItemOptions side="end">
<IonItemOption onClick={unread(item)}>Unread</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Sliding item with expandable options on both sides */}
<IonItemSliding>
<IonItemOptions side="start">
<IonItemOption color="danger" expandable>
Delete
</IonItemOption>
</IonItemOptions>
<IonItem>
<IonLabel>Expandable Options</IonLabel>
</IonItem>
<IonItemOptions side="end">
<IonItemOption color="tertiary" expandable>
Archive
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Multi-line sliding item with icon options on both sides */}
<IonItemSliding id="item100">
<IonItem href="#">
<IonLabel>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</IonLabel>
<IonNote slot="end">
10:45 AM
</IonNote>
</IonItem>
<IonItemOptions side="start">
<IonItemOption>
<IonIcon slot="icon-only" name="heart"></IonIcon>
</IonItemOption>
</IonItemOptions>
<IonItemOptions side="end">
<IonItemOption onClick={() => {}}>Unread</IonItemOption>
<IonItemOption color="danger">
<IonIcon slot="icon-only" name="trash"></IonIcon>
</IonItemOption>
<IonItemOption>
<IonIcon slot="icon-only" name="star"></IonIcon>
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Sliding item with icon start options on end side */}
<IonItemSliding>
<IonItem>
<IonLabel>
Sliding Item, Icons Start
</IonLabel>
</IonItem>
<IonItemOptions>
<IonItemOption color="primary">
<IonIcon slot="start" name="more"></IonIcon>
More
</IonItemOption>
<IonItemOption color="secondary">
<IonIcon slot="start" name="archive"></IonIcon>
Archive
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Sliding item with icon end options on end side */}
<IonItemSliding>
<IonItem>
<IonLabel>
Sliding Item, Icons End
</IonLabel>
</IonItem>
<IonItemOptions>
<IonItemOption color="primary">
<IonIcon slot="end" name="more"></IonIcon>
More
</IonItemOption>
<IonItemOption color="secondary">
<IonIcon slot="end" name="archive"></IonIcon>
Archive
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Sliding item with icon top options on end side */}
<IonItemSliding>
<IonItem>
<IonLabel>
Sliding Item, Icons Top
</IonLabel>
</IonItem>
<IonItemOptions>
<IonItemOption color="primary">
<IonIcon slot="top" name="more"></IonIcon>
More
</IonItemOption>
<IonItemOption color="secondary">
<IonIcon slot="top" name="archive"></IonIcon>
Archive
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
{/* Sliding item with icon bottom options on end side */}
<IonItemSliding>
<IonItem>
<IonLabel>
Sliding Item, Icons Bottom
</IonLabel>
</IonItem>
<IonItemOptions>
<IonItemOption color="primary">
<IonIcon slot="bottom" name="more"></IonIcon>
More
</IonItemOption>
<IonItemOption color="secondary">
<IonIcon slot="bottom" name="archive"></IonIcon>
Archive
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
</IonList>
@@ -109,19 +487,145 @@ export default Example;
```html
<template>
<ion-list>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option @click="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" @click="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option @click="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</template>
```

View File

@@ -120,6 +120,30 @@
</ion-item-sliding>
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>Disabled Buttons</h2>
<p>Buttons should not be clickable</p>
</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option disabled>
Disabled
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger" disabled>
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option disabled>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<ion-item-sliding id="item0">
<ion-item onclick="clickedItem('item0')">
<ion-label text-wrap>
@@ -286,6 +310,44 @@
</ion-item>
</ion-item-sliding>
<ion-item-sliding id="item10">
<ion-item detail>
<ion-label text-wrap>
<h2>RIGHT/LEFT side - many buttons</h2>
<p>Use mobile emulator to check</p>
</ion-label>
</ion-item>
<ion-item-options side="start" class="sliding-enabled">
<ion-item-option color="primary" expandable>
<ion-icon name="ios-checkmark"></ion-icon>Btn 1
</ion-item-option>
<ion-item-option color="secondary" expandable>
<ion-icon name="ios-checkmark"></ion-icon>Btn 2
</ion-item-option>
<ion-item-option color="danger" expandable>
<ion-icon name="ios-checkmark"></ion-icon>Btn 3
</ion-item-option>
<ion-item-option color="tertiary" expandable>
<ion-icon name="ios-checkmark"></ion-icon>Btn 4
</ion-item-option>
</ion-item-options>
<ion-item-options side="end" class="sliding-enabled">
<ion-item-option color="primary" expandable>
<ion-icon name="mail"></ion-icon>Btn 5
</ion-item-option>
<ion-item-option color="secondary" expandable>
<ion-icon name="mail"></ion-icon>Btn 6
</ion-item-option>
<ion-item-option color="danger" expandable>
<ion-icon name="mail"></ion-icon>Btn 7
</ion-item-option>
<ion-item-option color="tertiary" expandable>
<ion-icon name="mail"></ion-icon>Btn 8
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<ion-item>
<ion-label text-wrap>
<h2>Normal ion-item (no sliding)</h2>

View File

@@ -1,12 +1,14 @@
import { newE2EPage } from '@stencil/core/testing';
import { openItemSliding } from '../test.utils';
test('item-sliding: interactive', async () => {
const page = await newE2EPage({
url: '/src/components/item-sliding/test/interactive?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const compares = [];
compares.push(await page.compareScreenshot());
const items = await page.$$('ion-item-sliding');
expect(items.length).toEqual(3);
@@ -20,30 +22,30 @@ test('item-sliding: interactive', async () => {
const itemsAfterSecondSlide = await page.$$('ion-item-sliding');
expect(itemsAfterSecondSlide.length).toEqual(1);
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});
async function slideAndDelete(item: any, page: any) {
try {
// Get the element's ID
const id = await(await item.getProperty('id')).jsonValue();
// Simulate a drag
const boundingBox = await item.boundingBox();
const centerX = parseFloat(boundingBox.x + boundingBox.width / 2);
const centerY = parseFloat(boundingBox.y + boundingBox.height / 2);
await page.mouse.move(centerX, centerY);
await page.mouse.down();
await page.mouse.move(0, centerY);
await page.mouse.up();
// Click the "delete" option
const options = await item.$$('ion-item-option');
await options[0].click();
// Wait for element to be removed from DOM
await page.waitForSelector(id, { hidden: true });
await openItemSliding(`#${id}`, page);
await deleteItemSliding(item, page, id);
} catch (err) {
throw err;
}
}
async function deleteItemSliding(item: any, page: any, id: string) {
// Click the "delete" option
const options = await item.$$('ion-item-option');
await options[0].click();
// Wait for element to be removed from DOM
await page.waitForSelector(id, { hidden: true });
await page.waitFor(1000);
}

View File

@@ -1,10 +1,31 @@
import { newE2EPage } from '@stencil/core/testing';
import { closeItemSliding, openItemSliding } from '../test.utils';
test('item-sliding: standalone', async () => {
const page = await newE2EPage({
url: '/src/components/item-sliding/test/standalone?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const compares = [];
compares.push(await page.compareScreenshot());
// Pass sliding item with start icons in option
await openItemSliding('#startItem', page);
compares.push(await page.compareScreenshot(`start icons open`));
await closeItemSliding(page);
// Pass sliding item with top icons in option
await openItemSliding('#topItem', page);
compares.push(await page.compareScreenshot(`top icons open`));
await closeItemSliding(page);
// Pass sliding item with anchor option
await openItemSliding('#anchorItem', page);
compares.push(await page.compareScreenshot(`anchor option`));
await closeItemSliding(page);
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});

View File

@@ -4,7 +4,8 @@
<head>
<meta charset="UTF-8">
<title>Item Sliding - Standalone</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/core.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
@@ -12,38 +13,168 @@
</head>
<body>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option onClick="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" onClick="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>
One Line, icon only
</ion-label>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="secondary">
<ion-icon slot="icon-only" name="heart"></ion-icon>
<ion-item-options side="end">
<ion-item-option onClick="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding id="startItem">
<ion-item>
<ion-label>
One Line, icon and text
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon name="more"></ion-icon>
<span class="more-text">More</span>
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon name="archive"></ion-icon>
<span class="archive-text">Archive</span>
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding id="topItem">
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with one anchor option -->
<ion-item-sliding id="anchorItem">
<ion-item>
<ion-label>
Sliding Item, Anchor Icon Option
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option href="#" color="primary">
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="icon-only" name="archive"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</body>
<style>
body {
margin: 0;
}
</style>
</html>

View File

@@ -0,0 +1,31 @@
// Opens a sliding item by simulating a drag event
export async function openItemSliding(id: string, page: any) {
try {
const slidingItem = await page.$(id);
// Simulate a drag
const boundingBox = await slidingItem.boundingBox();
const centerX = parseFloat(boundingBox.x + boundingBox.width / 2);
const centerY = parseFloat(boundingBox.y + boundingBox.height / 2);
await page.mouse.move(centerX, centerY);
await page.mouse.down();
await page.mouse.move(centerX / 2, centerY);
await page.mouse.move(0, centerY);
await page.mouse.up();
// Add a timeout to make sure the item is open
await page.waitFor(2000);
} catch (err) {
throw err;
}
}
// Close a sliding item after taking a screenshot
// to allow other sliding items to open
export async function closeItemSliding(page: any) {
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.up();
await page.waitFor(1000);
}

View File

@@ -1,17 +1,143 @@
```html
<ion-list>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option (click)="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" (click)="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option (click)="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
```

View File

@@ -1,17 +1,143 @@
```html
<ion-list>
<!-- Sliding item with text options on both sides -->
<ion-item-sliding>
<ion-item>
<ion-label>Item</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option onClick="favorite(item)">Favorite</ion-item-option>
<ion-item-option color="danger" onClick="share(item)">Share</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Item Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option onClick="unread(item)">Unread</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with expandable options on both sides -->
<ion-item-sliding>
<ion-item-options side="start">
<ion-item-option color="danger" expandable>
Delete
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label>Expandable Options</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="tertiary" expandable>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Multi-line sliding item with icon options on both sides -->
<ion-item-sliding id="item100">
<ion-item href="#">
<ion-label>
<h2>HubStruck Notifications</h2>
<p>A new message in your network</p>
<p>Oceanic Next has joined your network</p>
</ion-label>
<ion-note slot="end">
10:45 AM
</ion-note>
</ion-item>
<ion-item-options side="start">
<ion-item-option>
<ion-icon slot="icon-only" name="heart"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger">
<ion-icon slot="icon-only" name="trash"></ion-icon>
</ion-item-option>
<ion-item-option>
<ion-icon slot="icon-only" name="star"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon start options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Start
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="start" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="start" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon end options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons End
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="end" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="end" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon top options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Top
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="top" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="top" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<!-- Sliding item with icon bottom options on end side -->
<ion-item-sliding>
<ion-item>
<ion-label>
Sliding Item, Icons Bottom
</ion-label>
</ion-item>
<ion-item-options>
<ion-item-option color="primary">
<ion-icon slot="bottom" name="more"></ion-icon>
More
</ion-item-option>
<ion-item-option color="secondary">
<ion-icon slot="bottom" name="archive"></ion-icon>
Archive
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
```

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