Compare commits

..

3 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
1359 changed files with 47185 additions and 71460 deletions

View File

@@ -60,7 +60,7 @@ jobs:
working_directory: /tmp/workspace/core
- save_cache: *save-cache-core
- run:
command: npm run build -- --ci
command: npm run build -- --max-workers 1
working_directory: /tmp/workspace/core
- save_cache: *save-cache-core-stencil
- persist_to_workspace:
@@ -91,75 +91,6 @@ jobs:
paths:
- "*"
build-angular-server:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm install
working_directory: /tmp/workspace/packages/angular-server
- run:
command: npm run build.prod
working_directory: /tmp/workspace/packages/angular-server
- persist_to_workspace:
root: /tmp/workspace
paths:
- "*"
build-react:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm install
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: npm run build
working_directory: /tmp/workspace/packages/react
- persist_to_workspace:
root: /tmp/workspace
paths:
- "*"
build-react-router:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm install
working_directory: /tmp/workspace/packages/react-router
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react-router
- run:
command: sudo npm link
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link @ionic/react
working_directory: /tmp/workspace/packages/react-router
- run:
command: npm run build
working_directory: /tmp/workspace/packages/react-router
- persist_to_workspace:
root: /tmp/workspace
paths:
- "*"
test-core-clean-build:
<<: *defaults
steps:
@@ -181,16 +112,6 @@ jobs:
command: npm run lint
working_directory: /tmp/workspace/core
test-core-e2e:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run test.e2e --ci
working_directory: /tmp/workspace/core
test-core-spec:
<<: *defaults
steps:
@@ -198,7 +119,7 @@ jobs:
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run test.spec --ci
command: npm run test.spec
working_directory: /tmp/workspace/core
test-core-treeshake:
@@ -208,7 +129,7 @@ jobs:
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run test.treeshake --ci
command: npm run test.treeshake
working_directory: /tmp/workspace/core
test-core-screenshot:
@@ -243,64 +164,6 @@ jobs:
command: npm run lint
working_directory: /tmp/workspace/angular
test-react-lint:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run lint
working_directory: /tmp/workspace/packages/react
test-react-router-lint:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: npm run lint
working_directory: /tmp/workspace/packages/react-router
test-react-spec:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: npm run test.spec
working_directory: /tmp/workspace/packages/react
test-react-router-spec:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run:
command: sudo npm link @ionic/core
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link
working_directory: /tmp/workspace/packages/react
- run:
command: sudo npm link @ionic/react
working_directory: /tmp/workspace/packages/react-router
- run:
command: npm run test.spec
working_directory: /tmp/workspace/packages/react-router
test-angular-e2e:
<<: *defaults
steps:
@@ -325,8 +188,6 @@ workflows:
requires: [build-core]
- test-core-lint:
requires: [build-core]
- test-core-e2e:
requires: [build-core]
- test-core-spec:
requires: [build-core]
- test-core-treeshake:
@@ -344,23 +205,7 @@ workflows:
- build-angular:
requires: [build-core]
- build-angular-server:
requires: [build-angular]
- build-react:
requires: [build-core]
- build-react-router:
requires: [build-core, build-react]
- test-react-lint:
requires: [build-react]
- test-react-router-lint:
requires: [build-react-router]
- test-react-spec:
requires: [build-react]
- test-react-router-spec:
requires: [build-react-router]
- test-angular-lint:
requires: [build-angular]
- test-angular-e2e:
requires:
- build-angular
- build-angular-server
requires: [build-angular]

View File

@@ -1,443 +0,0 @@
# Ionic Component Implementation Guide
- [Button States](#button-states)
* [Component Structure](#component-structure)
* [Activated](#activated)
* [Disabled](#disabled)
* [Focused](#focused)
* [Hover](#hover)
* [Ripple Effect](#ripple-effect)
* [Example Components](#example-components)
* [References](#references)
- [Rendering Anchor or Button](#rendering-anchor-or-button)
* [Example Components](#example-components-1)
* [Component Structure](#component-structure-1)
- [Converting Scoped to Shadow](#converting-scoped-to-shadow)
## Button States
Any component that renders a button should have the following states: [`activated`](#activated), [`disabled`](#disabled), [`focused`](#focused), [`hover`](#hover). It should also have a [Ripple Effect](#ripple-effect) component added for Material Design.
### Component Structure
#### JavaScript
A component that renders a native button should use the following structure:
```jsx
<Host>
<button class="button-native">
<span class="button-inner">
<slot></slot>
</span>
</button>
</Host>
```
Any other attributes and classes that are included are irrelevant to the button states, but it's important that this structure is followed and the classes above exist. In some cases they may be named something else that makes more sense, such as in item.
#### CSS
A mixin called `button-state()` has been added to make it easier to setup the states in each component.
```scss
@mixin button-state() {
@include position(0, 0, 0, 0);
position: absolute;
content: "";
opacity: 0;
}
```
The following styles should be set for the CSS to work properly. Note that the `button-state()` mixin is included in the `::after` pseudo element of the native button.
```scss
.button-native {
/**
* All other CSS in this selector is irrelevant to button states
* but the following are required styles
*/
position: relative;
overflow: hidden;
}
.button-native::after {
@include button-state();
}
.button-inner {
/**
* All other CSS in this selector is irrelevant to button states
* but the following are required styles
*/
position: relative;
z-index: 1;
}
```
### Activated
The activated state should be enabled for elements with actions on "press". It usually changes the opacity or background of an element.
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### JavaScript
The `ion-activatable` class needs to be set on an element that can be activated:
```jsx
render() {
return (
<Host class='ion-activatable'>
<slot></slot>
</Host>
);
}
```
Once that is done, the element will get the `ion-activated` class added on press.
In addition to setting that class, `ion-activatable-instant` can be set in order to have an instant press with no delay:
```jsx
<Host class='ion-activatable ion-activatable-instant'>
```
#### CSS
```css
/**
* @prop --color-activated: Color of the button when pressed
* @prop --background-activated: Background of the button when pressed
* @prop --background-activated-opacity: Opacity of the background when pressed
*/
```
Style the `ion-activated` class based on the spec for that element:
```scss
:host(.ion-activated) .button-native {
color: var(--color-activated);
&::after {
background: var(--background-activated);
opacity: var(--background-activated-opacity);
}
}
```
> Order is important! Activated should be before the focused & hover states.
#### User Customization
Setting the activated state on the `::after` pseudo-element allows the user to customize the activated state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on press, or they can leave out `--background-activated-opacity` and the button will use the default activated opacity to match the spec.
```css
ion-button {
--background-activated: red;
--background-activated-opacity: 1;
}
```
### Disabled
The disabled state should be set via prop on all components that render a native button. Setting a disabled state will change the opacity or color of the button and remove click events from firing.
#### JavaScript
The `disabled` property should be set on the component:
```jsx
/**
* If `true`, the user cannot interact with the button.
*/
@Prop({ reflectToAttr: true }) disabled = false;
```
Then, the render function should add the [`aria-disabled`](https://www.w3.org/WAI/PF/aria/states_and_properties#aria-disabled) role to the host, a class that is the element tag name followed by `disabled`, and pass the `disabled` attribute to the native button:
```jsx
render() {
const { disabled } = this;
return (
<Host
aria-disabled={disabled ? 'true' : null}
class={{
'button-disabled': disabled
}}
>
<button disabled={disabled}>
<slot></slot>
</button>
</Host>
);
}
```
> Note: if the class being added was for `ion-back-button` it would be `back-button-disabled`.
#### CSS
The following CSS _at the bare minimum_ should be added for the disabled class, but it should be styled to match the spec:
```css
:host(.button-disabled) {
cursor: default;
opacity: .5;
pointer-events: none;
}
```
#### User Customization
TODO
### Focused
The focused state should be enabled for elements with actions when tabbed to via the keyboard. This will only work inside of an `ion-app`. It usually changes the opacity or background of an element.
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### JavaScript
The `ion-focusable` class needs to be set on an element that can be focused:
```jsx
render() {
return (
<Host class='ion-focusable'>
<slot></slot>
</Host>
);
}
```
Once that is done, the element will get the `ion-focused` class added when the element is tabbed to.
#### CSS
Components should be written to include the following focused variables for styling:
```css
/**
* @prop --color-focused: Color of the button when tabbed to with the keyboard
* @prop --background-focused: Background of the button when tabbed to with the keyboard
* @prop --background-focused-opacity: Opacity of the background when tabbed to with the keyboard
*/
```
Style the `ion-focused` class based on the spec for that element:
```scss
:host(.ion-focused) .button-native {
color: var(--color-focused);
&::after {
background: var(--background-focused);
opacity: var(--background-focused-opacity);
}
}
```
> Order is important! Focused should be after the activated and before the hover state.
#### User Customization
Setting the focused state on the `::after` pseudo-element allows the user to customize the focused state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on focus, or they can leave out `--background-focused-opacity` and the button will use the default focus opacity to match the spec.
```css
ion-button {
--background-focused: red;
--background-focused-opacity: 1;
}
```
### Hover
The [hover state](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) happens when a user moves their cursor on top of an element without pressing on it. It should not happen on mobile, only on desktop devices that support hover.
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### CSS
Components should be written to include the following hover variables for styling:
```css
/**
* @prop --color-hover: Color of the button on hover
* @prop --background-hover: Background of the button on hover
* @prop --background-hover-opacity: Opacity of the background on hover
*/
```
Style the `:hover` based on the spec for that element:
```scss
@media (any-hover: hover) {
:host(:hover) .button-native {
color: var(--color-hover);
&::after {
background: var(--background-hover);
opacity: var(--background-hover-opacity);
}
}
}
```
> Order is important! Hover should be after the activated and focused states.
#### User Customization
Setting the hover state on the `::after` pseudo-element allows the user to customize the hover state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on hover, or they can leave out `--background-hover-opacity` and the button will use the default hover opacity to match the spec.
```css
ion-button {
--background-hover: red;
--background-hover-opacity: 1;
}
```
### Ripple Effect
The ripple effect should be added to elements for Material Design. It *requires* the `ion-activatable` class to be set on the parent element to work, and relative positioning on the parent.
```jsx
render() {
const mode = getIonMode(this);
return (
<Host
class={{
'ion-activatable': true,
}}
>
<button>
<slot></slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
</Host>
);
```
The ripple effect can also accept a different `type`. By default it is `"bounded"` which will expand the ripple effect from the click position outwards. To add a ripple effect that always starts in the center of the element and expands in a circle, set the type to `"unbounded"`. An unbounded ripple will exceed the container, so add `overflow: hidden` to the parent to prevent this.
Make sure to style the ripple effect for that component to accept a color:
```css
ion-ripple-effect {
color: var(--ripple-color);
}
```
### Example Components
- [ion-button](https://github.com/ionic-team/ionic/tree/master/core/src/components/button)
- [ion-back-button](https://github.com/ionic-team/ionic/tree/master/core/src/components/back-button)
- [ion-menu-button](https://github.com/ionic-team/ionic/tree/master/core/src/components/menu-button)
### References
- [Material Design States](https://material.io/design/interaction/states.html)
- [iOS Buttons](https://developer.apple.com/design/human-interface-guidelines/ios/controls/buttons/)
## Rendering Anchor or Button
Certain components can render an `<a>` or a `<button>` depending on the presence of an `href` attribute.
### Example Components
- [ion-button](https://github.com/ionic-team/ionic/tree/master/core/src/components/button)
- [ion-card](https://github.com/ionic-team/ionic/tree/master/core/src/components/card)
- [ion-fab-button](https://github.com/ionic-team/ionic/tree/master/core/src/components/fab-button)
- [ion-item-option](https://github.com/ionic-team/ionic/tree/master/core/src/components/item-option)
- [ion-item](https://github.com/ionic-team/ionic/tree/master/core/src/components/item)
### Component Structure
#### JavaScript
In order to implement a component with a dynamic tag type, set the property that it uses to switch between them, we use `href`:
```jsx
/**
* Contains a URL or a URL fragment that the hyperlink points to.
* If this property is set, an anchor tag will be rendered.
*/
@Prop() href: string | undefined;
```
Then use that in order to render the tag:
```jsx
render() {
const TagType = href === undefined ? 'button' : 'a' as any;
return (
<Host>
<TagType>
<slot></slot>
</TagType>
</Host>
);
}
```
If the component can render an `<a>`, `<button>` or a `<div>` add in more properties such as a `button` attribute in order to check if it should render a button.
## Converting Scoped to Shadow
### CSS
There will be some CSS issues when converting to shadow. Below are some of the differences.
**Targeting host + slotted child**
```css
/* IN SCOPED */
:host(.ion-color)::slotted(ion-segment-button)
/* IN SHADOW*/
:host(.ion-color) ::slotted(ion-segment-button)
```
**Targeting host-context + host (with a :not)**
```css
/* IN SCOPED */
:host-context(ion-toolbar.ion-color):not(.ion-color) {
/* IN SHADOW */
:host-context(ion-toolbar.ion-color):host(:not(.ion-color)) {
```
**Targeting host-context + host (with a :not) > slotted child**
```css
/* IN SCOPED */
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(ion-segment-button) {
/* IN SHADOW*/
:host-context(ion-toolbar:not(.ion-color)):host(:not(.ion-color)) ::slotted(ion-segment-button) {
```

View File

@@ -2,30 +2,6 @@
Thanks for your interest in contributing to the Ionic Framework! :tada:
- [Contributing Etiquette](#contributing-etiquette)
- [Creating an Issue](#creating-an-issue)
- [Creating a Pull Request](#creating-a-pull-request)
* [Setup](#setup)
* [Core](#core)
+ [Modifying Components](#modifying-components)
+ [Preview Changes](#preview-changes)
+ [Lint Changes](#lint-changes)
+ [Modifying Documentation](#modifying-documentation)
+ [Modifying Tests](#modifying-tests)
- [Screenshot Tests](#screenshot-tests)
+ [Building Changes](#building-changes)
* [Submit Pull Request](#submit-pull-request)
- [Commit Message Guidelines](#commit-message-guidelines)
* [Commit Message Format](#commit-message-format)
* [Revert](#revert)
* [Type](#type)
* [Scope](#scope)
* [Subject](#subject)
* [Body](#body)
* [Footer](#footer)
* [Examples](#examples)
- [License](#license)
## Contributing Etiquette
@@ -140,32 +116,13 @@ Please see our [Contributor Code of Conduct](https://github.com/ionic-team/ionic
3. Please fill out the provided Pull Request template to the best of your ability and include any issues that are related.
## Commit Message Guidelines
## Commit Message Format
We have very precise rules over how our git commit messages should be formatted. This leads to readable messages that are easy to follow when looking through the project history. We also use the git commit messages to generate our [changelog](https://github.com/ionic-team/ionic/blob/master/CHANGELOG.md). Our format closely resembles Angular's [commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit).
We have very precise rules over how our git commit messages should be formatted. This leads to readable messages that are easy to follow when looking through the project history. We also use the git commit messages to generate our [changelog](https://github.com/ionic-team/ionic/blob/master/CHANGELOG.md). (Ok you got us, it's basically Angular's commit message format).
### Commit Message Format
We follow the [Conventional Commits specification](https://www.conventionalcommits.org/). A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
`type(scope): subject`
#### Type
Must be one of the following:
* **feat**: A new feature
@@ -177,12 +134,10 @@ Must be one of the following:
* **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
### Scope
The scope can be anything specifying place of the commit change. Usually it will refer to a component but it can also refer to a utility. For example `action-sheet`, `button`, `css`, `menu`, `nav`, etc. If you make multiple commits for the same component, please keep the naming of this component consistent. For example, if you make a change to navigation and the first commit is `fix(nav)`, you should continue to use `nav` for any more commits related to navigation. As a general rule, if you're modifying a component use the name of the folder.
### Subject
#### Scope
The scope can be anything specifying place of the commit change. For example `action-sheet`, `button`, `menu`, `nav`, etc. If you make multiple commits for the same component, please keep the naming of this component consistent. For example, if you make a change to navigation and the first commit is `fix(nav)`, you should continue to use `nav` for any more commits related to navigation.
#### Subject
The subject contains succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
@@ -192,66 +147,6 @@ The subject contains succinct description of the change:
* describe what the commit does, not what issue it relates to or fixes
* **be brief, yet descriptive** - we should have a good understanding of what the commit does by reading the subject
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
### Examples
Does not appear in the generated changelog:
```
docs(changelog): update steps to update
```
Appears under "Features" header, toast subheader:
```
feat(toast): add 'buttons' property
```
Appears under "Bug Fixes" header, skeleton-text subheader, with a link to issue #28:
```
fix(skeleton-text): use proper color when animated
closes #28
```
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
```
perf(css): remove all css utility attributes
BREAKING CHANGE: The CSS utility attributes have been removed. Use CSS classes instead.
```
Appears under "Breaking Changes" with the breaking change explanation:
```
refactor(animations): update to new animation system
BREAKING CHANGE:
Removes the old animation system to use the new Ionic animations.
```
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
```
revert: feat(skeleton-text): add animated property
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```
## License

View File

@@ -10,7 +10,6 @@
<!-- (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1) -->
<!-- (For Ionic 2.x & 3.x issues, please use https://github.com/ionic-team/ionic-v3) -->
[x] **4.x**
[ ] **5.x**
**I'm submitting a ...**
<!-- (check one with "x") -->

View File

@@ -37,7 +37,6 @@ assignees: ''
A sample application via GitHub
StackBlitz (https://stackblitz.com)
Ionic Angular StackBlitz: https://stackblitz.com/edit/ionic-v4-angular-tabs
Plunker (http://plnkr.co/edit/cpeRJs?p=preview)

21
.github/PROCESS.md vendored
View File

@@ -64,7 +64,7 @@ If the issue is a support question, the submitter should be redirected to our [f
### Incomplete Template
If the issue template has not been filled out completely, the issue should be closed and locked. The submitter should be informed to re-submit the issue making sure they fill the form out completely. Use the `ionitron: missing template` label to accomplish this.
If the issue template has not been filled out completely, the issue should be closed and locked. The submitter should be informed top re-submit the issue making sure they fill the form out completely. Use the `ionitron: missing template` label to accomplish this.
### Issues with Open Questions
@@ -77,7 +77,7 @@ NOTE: be sure to perform those actions in the order stated. If you add the comme
If there is a response to the question, the bot will remove the `needs: reply` and apply the `triage` label. The issue will then go through the triage handling again.
If there is no response within 30 days, the issue will be closed and locked.
if there is no response within 30 days, the issue will be closed and locked.
## Workflow
@@ -221,11 +221,9 @@ Hotfixes bypass `master` and should only be used for urgent fixes that can't wai
## Releasing
1. Create the release branch from `master`, for example: `release-4.5.0`.
1. Create the release branch from `master`, for example: `release-4.1.0`.
1. For major or minor releases, create a version branch based off the latest version branch. For example, if releasing 4.5.0, create a branch called `4.5.x` based off `4.4.x`.
1. Submit a pull request from the release branch into the version branch. Do not merge this pull request yet.
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.
@@ -237,10 +235,7 @@ Hotfixes bypass `master` and should only be used for urgent fixes that can't wai
- 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))
- For major or minor releases, ensure that the version number has an associated title (for example: `4.5.0 Boron`)
- Commit these changes with the version number as the message, e.g. `git commit -m "4.5.0"`
1. *(Optional)* Run `npm run release -- --dry-run` to run the release without publishing and verify the version.
- Commit these changes with the version number as the message, e.g. `git commit -m "4.1.0"`
1. Run `npm run release`
@@ -248,6 +243,8 @@ Hotfixes bypass `master` and should only be used for urgent fixes that can't wai
<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-[VERSION]` with the proper release branch. For example, if this release is for `4.5.0`, the message would be `merge release-4.5.0`.
1. Rewrite the commit message to `merge release-4.1.0` with the proper release branch.
1. Submit a pull request from the release branch into `master`. Merge this pull request using the same commit format in the last step, to ensure any changes made on the release branch get added to future releases.
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,7 +0,0 @@
# Configuration for weekly-digest - https://github.com/apps/weekly-digest
publishDay: wed
canPublishIssues: true
canPublishPullRequests: true
canPublishContributors: true
canPublishStargazers: true
canPublishCommits: true

8
.gitignore vendored
View File

@@ -15,7 +15,6 @@ log.txt
coverage/
collection/
dist/
dist-transpiled/
node_modules/
tmp/
temp/
@@ -50,17 +49,10 @@ demos/src/**/*.ngfactory.ts
demos/src/**/*.d.ts
demos/src/**/*.metadata.json
demos/src/**/*.css.shim.ts
prerender.html
prerender-domino.html
prerender-hydrated.html
prerender-static.html
# stencil
angular/css/
packages/react/css/
core/css/
core/hydrate/
core/loader/
core/www/
.stencil/
angular/build/

View File

@@ -1,7 +1,6 @@
const fs = require('fs-extra');
const path = require('path');
const execa = require('execa');
const inquirer = require('inquirer');
const Listr = require('listr');
const semver = require('semver');
const tc = require('turbocolor');
@@ -12,9 +11,6 @@ const packages = [
'core',
'docs',
'angular',
'packages/react',
'packages/react-router',
'packages/angular-server'
];
function readPkg(project) {
@@ -36,80 +32,38 @@ function projectPath(project) {
return path.join(rootDir, project);
}
async function askNpmTag(version) {
const prompts = [
{
type: 'list',
name: 'npmTag',
message: 'Select npm tag or specify a new tag',
choices: ['latest', 'next', 'v4-lts']
.concat([
new inquirer.Separator(),
{
name: 'Other (specify)',
value: null
}
])
},
{
type: 'confirm',
name: 'confirm',
message: answers => {
return `Will publish ${tc.cyan(version)} to ${tc.cyan(answers.npmTag)}. Continue?`;
}
}
];
const { npmTag, confirm } = await inquirer.prompt(prompts);
return { npmTag, confirm };
}
function checkGit(tasks) {
tasks.push(
{
title: 'Check current branch',
task: () =>
execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => {
if (branch.indexOf('release') === -1 && branch.indexOf('hotfix') === -1) {
throw new Error(`Must be on a "release" or "hotfix" branch.`);
}
})
task: () => execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => {
if (branch.indexOf('release') === -1 && branch.indexOf('hotfix') === -1) {
throw new Error(`Must be on a "release" or "hotfix" branch.`);
}
})
},
{
title: 'Check local working tree',
task: () =>
execa.stdout('git', ['status', '--porcelain']).then(status => {
if (status !== '') {
throw new Error(`Unclean working tree. Commit or stash changes first.`);
}
})
task: () => execa.stdout('git', ['status', '--porcelain']).then(status => {
if (status !== '') {
throw new Error(`Unclean working tree. Commit or stash changes first.`);
}
})
},
{
title: 'Check remote history',
task: () =>
execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => {
if (result !== '0') {
throw new Error(`Remote history differs. Please pull changes.`);
}
})
task: () => execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => {
if (result !== '0') {
throw new Error(`Remote history differs. Please pull changes.`);
}
})
}
);
}
function checkTestDist(tasks) {
tasks.push({
title: 'Check dist folders for required files',
task: () =>
execa.stdout('node', ['.scripts/test-dist.js']).then(status => {
if (status.indexOf('✅ test.dist') === -1) {
throw new Error(`Test Dist did not find some required files`);
}
})
});
}
const isValidVersion = input => Boolean(semver.valid(input));
function preparePackage(tasks, package, version, install) {
const projectRoot = projectPath(package);
const pkg = readPkg(package);
@@ -120,9 +74,7 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: validate new version`,
task: () => {
if (!isVersionGreater(pkg.version, version)) {
throw new Error(
`New version \`${version}\` should be higher than current version \`${pkg.version}\``
);
throw new Error(`New version \`${version}\` should be higher than current version \`${pkg.version}\``);
}
}
});
@@ -130,7 +82,7 @@ function preparePackage(tasks, package, version, install) {
projectTasks.push({
title: `${pkg.name}: install npm dependencies`,
task: async () => {
await fs.remove(path.join(projectRoot, 'node_modules'));
await fs.remove(path.join(projectRoot, 'node_modules'))
await execa('npm', ['i'], { cwd: projectRoot });
}
});
@@ -143,51 +95,36 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: npm link @ionic/core`,
task: () => execa('npm', ['link', '@ionic/core'], { cwd: projectRoot })
});
if (package === 'packages/react-router') {
projectTasks.push({
title: `${pkg.name}: npm link @ionic/react`,
task: () => execa('npm', ['link', '@ionic/react'], { cwd: projectRoot })
});
}
}
// Lint, Test, Bump Core dependency
if (version) {
projectTasks.push({
title: `${pkg.name}: lint`,
task: () => execa('npm', ['run', 'lint'], { cwd: projectRoot })
});
// TODO will not work due to https://github.com/ionic-team/ionic/issues/20136
// projectTasks.push({
// title: `${pkg.name}: test`,
// task: async () => await execa('npm', ['test'], { cwd: projectRoot })
// });
projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => {
updateDependency(pkg, "@ionic/core", version);
writePkg(package, pkg);
}
});
projectTasks.push({
title: `${pkg.name}: test`,
task: () => execa('npm', ['test'], { cwd: projectRoot })
});
}
// Build
projectTasks.push({
title: `${pkg.name}: build`,
task: () => execa('npm', ['run', 'build'], { cwd: projectRoot })
});
// Link core or react for sub projects
if (package === 'core' || package === 'packages/react') {
if (package === 'core') {
projectTasks.push({
title: `${pkg.name}: npm link`,
task: () => execa('npm', ['link'], { cwd: projectRoot })
});
}
if (version) {
projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => {
updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg);
}
});
}
}
// Add project tasks
@@ -197,6 +134,7 @@ function preparePackage(tasks, package, version, install) {
});
}
function prepareDevPackage(tasks, package, version) {
const projectRoot = projectPath(package);
const pkg = readPkg(package);
@@ -214,7 +152,7 @@ function prepareDevPackage(tasks, package, version) {
projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => {
updateDependency(pkg, '@ionic/core', version);
updateDependency(pkg, "@ionic/core", version);
writePkg(package, pkg);
}
});
@@ -224,7 +162,7 @@ function prepareDevPackage(tasks, package, version) {
task: () => execa('npm', ['run', 'build'], { cwd: projectRoot })
});
if (package === 'core' || package === 'packages/react') {
if (package === 'core') {
projectTasks.push({
title: `${pkg.name}: npm link`,
task: () => execa('npm', ['link'], { cwd: projectRoot })
@@ -243,74 +181,36 @@ function updatePackageVersions(tasks, packages, version) {
packages.forEach(package => {
updatePackageVersion(tasks, package, version);
tasks.push({
title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`,
task: async () => {
if (package !== 'core') {
const pkg = readPkg(package);
updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg);
}
}
});
// angular & angular-server need to update their dist versions
if (package === 'angular' || package === 'packages/angular-server') {
const distPackage = path.join(package, 'dist');
updatePackageVersion(tasks, distPackage, version);
tasks.push({
tasks.push(
{
title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`,
task: async () => {
const pkg = readPkg(distPackage);
updateDependency(pkg, '@ionic/core', version);
writePkg(distPackage, pkg);
}
});
}
if (package === 'packages/react-router') {
tasks.push({
title: `${package} update @ionic/react dependency, if present ${tc.dim(`(${version})`)}`,
task: async () => {
const pkg = readPkg(package);
updateDependency(pkg, '@ionic/react', version);
writePkg(package, pkg);
}
});
}
if (package !== 'core') {
const pkg = readPkg(package);
updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg);
}
},
}
)
});
}
function updatePackageVersion(tasks, package, version) {
const projectRoot = projectPath(package);
tasks.push({
title: `${package}: update package.json ${tc.dim(`(${version})`)}`,
task: async () => {
await execa('npm', ['version', version], { cwd: projectRoot });
tasks.push(
{
title: `${package}: update package.json ${tc.dim(`(${version})`)}`,
task: async () => {
await execa('npm', ['version', version], { cwd: projectRoot });
}
}
});
);
}
function copyPackageToDist(tasks, packages) {
packages.forEach(package => {
const projectRoot = projectPath(package);
// angular and angular-server are the only packages that publish dist
if (package !== 'angular' && package !== 'packages/angular-server') {
return;
}
tasks.push({
title: `${package}: Copy package.json to dist`,
task: () => execa('node', ['copy-package.js', package], { cwd: path.join(rootDir, '.scripts') })
});
});
}
function publishPackages(tasks, packages, version, npmTag = 'latest') {
function publishPackages(tasks, packages, version, tag = 'latest') {
// first verify version
packages.forEach(package => {
if (package === 'core') {
@@ -329,19 +229,15 @@ function publishPackages(tasks, packages, version, npmTag = 'latest') {
});
});
// Publish
// next publish
packages.forEach(package => {
let projectRoot = projectPath(package);
if (package === 'packages/angular-server' || package === 'angular') {
projectRoot = path.join(projectRoot, 'dist')
}
const projectRoot = projectPath(package);
tasks.push({
title: `${package}: publish to ${npmTag} tag`,
title: `${package}: publish to ${tag} tag`,
task: async () => {
await execa('npm', ['publish', '--tag', npmTag], { cwd: projectRoot });
}
await execa('npm', ['publish', '--tag', tag], { cwd: projectRoot });
},
});
});
}
@@ -353,9 +249,6 @@ function updateDependency(pkg, dependency, version) {
if (pkg.devDependencies && pkg.devDependencies[dependency]) {
pkg.devDependencies[dependency] = version;
}
if (pkg.peerDependencies && pkg.peerDependencies[dependency]) {
pkg.peerDependencies[dependency] = version;
}
}
function isVersionGreater(oldVersion, newVersion) {
@@ -365,21 +258,11 @@ function isVersionGreater(oldVersion, newVersion) {
return true;
}
function copyCDNLoader(tasks, version) {
tasks.push({
title: `Copy CDN loader`,
task: () => execa('node', ['copy-cdn-loader.js', version], { cwd: path.join(rootDir, 'core', 'scripts') })
});
}
module.exports = {
checkTestDist,
checkGit,
askNpmTag,
isValidVersion,
isVersionGreater,
copyCDNLoader,
copyPackageToDist,
packages,
packagePath,
prepareDevPackage,
@@ -391,5 +274,5 @@ module.exports = {
updateDependency,
updatePackageVersion,
updatePackageVersions,
writePkg
writePkg,
};

View File

@@ -17,13 +17,9 @@ async function main() {
throw new Error('env.GH_TOKEN is undefined');
}
const { version, confirm } = await askVersion();
const version = await askVersion();
const install = process.argv.indexOf('--no-install') < 0;
if (!confirm) {
return;
}
// compile and verify packages
await preparePackages(common.packages, version, install);
@@ -89,8 +85,8 @@ async function askVersion() {
}
];
const { version, confirm } = await inquirer.prompt(prompts);
return { version, confirm };
const {version} = await inquirer.prompt(prompts);
return version;
}
@@ -111,17 +107,15 @@ async function preparePackages(packages, version, install) {
});
// add update package.json of each project
common.updatePackageVersions(tasks, packages, version);
packages.forEach(package => {
common.updatePackageVersion(tasks, package, version);
});
// generate changelog
generateChangeLog(tasks);
// check dist folders
common.checkTestDist(tasks);
// update core readme with version number
updateCoreReadme(tasks, version);
common.copyCDNLoader(tasks, version);
const listr = new Listr(tasks, { showSubtasks: true });
await listr.run();
@@ -177,6 +171,7 @@ function updateCoreReadme(tasks, version) {
});
}
const SEMVER_INCREMENTS = ['patch', 'minor', 'major'];
const isValidVersionInput = input => SEMVER_INCREMENTS.indexOf(input) !== -1 || common.isValidVersion(input);

View File

@@ -7,7 +7,6 @@ const fs = require('fs-extra');
const common = require('./common');
const DIST_NPM_TAG = 'dev';
const DIST_TAG = 'dev';
async function main() {
@@ -38,8 +37,7 @@ async function main() {
packages.forEach(package => {
common.prepareDevPackage(tasks, package, devVersion);
});
common.copyCDNLoader(tasks, devVersion);
common.publishPackages(tasks, packages, devVersion, DIST_NPM_TAG);
common.publishPackages(tasks, packages, devVersion, DIST_TAG);
const listr = new Listr(tasks);
await listr.run();

View File

@@ -5,22 +5,17 @@
const tc = require('turbocolor');
const execa = require('execa');
const Listr = require('listr');
const path = require('path');
const { Octokit } = require('@octokit/rest');
const octokit = require('@octokit/rest')()
const common = require('./common');
const fs = require('fs-extra');
async function main() {
try {
const dryRun = process.argv.indexOf('--dry-run') > -1;
if (!process.env.GH_TOKEN) {
throw new Error('env.GH_TOKEN is undefined');
}
checkProductionRelease();
const tasks = [];
const { version } = common.readPkg('core');
const changelog = findChangelog();
@@ -28,31 +23,15 @@ async function main() {
// repo must be clean
common.checkGit(tasks);
const { npmTag, confirm } = await common.askNpmTag(version);
// publish each package in NPM
common.publishPackages(tasks, common.packages, version);
if (!confirm) {
return;
}
if(!dryRun) {
// publish each package in NPM
common.publishPackages(tasks, common.packages, version, npmTag);
// push tag to git remote
publishGit(tasks, version, changelog, npmTag);
}
// push tag to git remote
publishGit(tasks, version, changelog);
const listr = new Listr(tasks);
await listr.run();
// Dry run doesn't publish to npm or git
if (dryRun) {
console.log(`
\n${tc.yellow('Did not publish. Remove the "--dry-run" flag to publish:')}\n${tc.green(version)} to ${tc.cyan(npmTag)}\n
`);
} else {
console.log(`\nionic ${version} published to ${npmTag}!! 🎉\n`);
}
console.log(`\nionic ${version} published!! 🎉\n`);
} catch (err) {
console.log('\n', tc.red(err), '\n');
@@ -60,23 +39,14 @@ async function main() {
}
}
function checkProductionRelease() {
const corePath = common.projectPath('core');
const hasEsm = fs.existsSync(path.join(corePath, 'dist', 'esm'));
const hasEsmEs5 = fs.existsSync(path.join(corePath, 'dist', 'esm-es5'));
const hasCjs = fs.existsSync(path.join(corePath, 'dist', 'cjs'));
if (!hasEsm || !hasEsmEs5 || !hasCjs) {
throw new Error('core build is not a production build');
}
}
function publishGit(tasks, version, changelog, npmTag) {
const gitTag = `v${version}`;
function publishGit(tasks, version, changelog) {
const tag = `v${version}`;
tasks.push(
{
title: `Tag latest commit ${tc.dim(`(${gitTag})`)}`,
task: () => execa('git', ['tag', `${gitTag}`], { cwd: common.rootDir })
title: `Tag latest commit ${tc.dim(`(${tag})`)}`,
task: () => execa('git', ['tag', `${tag}`], { cwd: common.rootDir })
},
{
title: 'Push branches to remote',
@@ -84,11 +54,11 @@ function publishGit(tasks, version, changelog, npmTag) {
},
{
title: 'Push tags to remove',
task: () => execa('git', ['push', '--follow-tags'], { cwd: common.rootDir })
task: () => execa('git', ['push', '--tags'], { cwd: common.rootDir })
},
{
title: 'Publish Github release',
task: () => publishGithub(version, gitTag, changelog, npmTag)
task: () => publishGithub(version, tag, changelog)
}
);
}
@@ -116,28 +86,19 @@ function findChangelog() {
return lines.slice(start, end).join('\n').trim();
}
async function publishGithub(version, gitTag, changelog, npmTag) {
// If the npm tag is next then publish as a prerelease
const prerelease = npmTag === 'next' ? true : false;
const octokit = new Octokit({
auth: process.env.GH_TOKEN
async function publishGithub(version, tag, changelog) {
octokit.authenticate({
type: 'oauth',
token: process.env.GH_TOKEN
});
let branch = await execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']);
if (!branch) {
branch = 'master';
}
await octokit.repos.createRelease({
owner: 'ionic-team',
repo: 'ionic',
target_commitish: branch,
tag_name: gitTag,
target_commitish: 'master',
tag_name: tag,
name: version,
body: changelog,
prerelease: prerelease
});
}

View File

@@ -1,97 +0,0 @@
const path = require('path');
const fs = require('fs');
// Test dist build:
// Double-triple check all the packages
// and files are good to go before publishing
[
// core
{
files: ['../core/dist/index.js', '../core/dist/ionic/index.esm.js']
},
// angular
{
files: [
'../angular/dist/schematics/collection.json',
'../angular/dist/fesm5/ionic-angular.js',
'../angular/dist/fesm2015/ionic-angular.js',
'../angular/dist/ionic-angular.d.ts',
'../angular/dist/ionic-angular.metadata.json'
]
},
// angular-server
{
files: [
'../packages/angular-server/dist/fesm5/ionic-angular-server.js',
'../packages/angular-server/dist/fesm2015/ionic-angular-server.js',
'../packages/angular-server/dist/ionic-angular-server.d.ts',
'../packages/angular-server/dist/ionic-angular-server.metadata.json'
]
},
// react
{
files: ['../packages/react/dist/index.js']
},
// react-router
{
files: ['../packages/react-router/dist/index.js']
}
].forEach(testPackage);
function testPackage(testPkg) {
if (testPkg.packageJson) {
const pkgDir = path.dirname(testPkg.packageJson);
const pkgJson = require(testPkg.packageJson);
if (!pkgJson.name) {
throw new Error('missing package.json name: ' + testPkg.packageJson);
}
if (!pkgJson.main) {
throw new Error('missing package.json main: ' + testPkg.packageJson);
}
const pkgPath = path.join(pkgDir, pkgJson.main);
const pkgImport = require(pkgPath);
if (testPkg.files) {
if (!Array.isArray(pkgJson.files)) {
throw new Error(testPkg.packageJson + ' missing "files" property');
}
testPkg.files.forEach(testPkgFile => {
if (!pkgJson.files.includes(testPkgFile)) {
throw new Error(testPkg.packageJson + ' missing file ' + testPkgFile);
}
const filePath = path.join(__dirname, pkgDir, testPkgFile);
fs.accessSync(filePath);
});
}
if (pkgJson.module) {
const moduleIndex = path.join(__dirname, pkgDir, pkgJson.module);
fs.accessSync(moduleIndex);
}
if (pkgJson.types) {
const pkgTypes = path.join(__dirname, pkgDir, pkgJson.types);
fs.accessSync(pkgTypes);
}
if (testPkg.exports) {
testPkg.exports.forEach(exportName => {
const m = pkgImport[exportName];
if (!m) {
throw new Error('export "' + exportName + '" not found in: ' + testPkg.packageJson);
}
});
}
} else if (testPkg.files) {
testPkg.files.forEach(file => {
const filePath = path.join(__dirname, file);
fs.statSync(filePath);
});
}
}
console.log(`✅ test.dist`);

View File

@@ -1,671 +1,3 @@
# Breaking Changes
This is a comprehensive list of the breaking changes introduced in the major version releases of Ionic Framework.
## Versions
- [Version 5.x](#version-5x)
- [Version 4.x](#version-4x)
- [Legacy](#legacy)
## Version 5.x
- [CSS](#css)
* [CSS Utilities](#css-utilities)
* [Display Classes](#display-classes)
* [Activated, Focused, Hover States](#activated-focused-hover-states)
* [Distributed Sass](#distributed-sass)
- [Components](#components)
* [Action Sheet](#action-sheet)
* [Anchor](#anchor)
* [Back Button](#back-button)
* [Button](#button)
* [Card](#card)
* [Controllers](#controllers)
* [FAB Button](#fab-button)
* [Item](#item)
* [Header / Footer](#header---footer)
* [List Header](#list-header)
* [Menu](#menu)
* [Menu Button](#menu-button)
* [Nav Link](#nav-link)
* [Radio](#radio)
* [Searchbar](#searchbar)
* [Segment](#segment)
* [Segment Button](#segment-button)
* [Select Option](#select-option)
* [Skeleton Text](#skeleton-text)
* [Split Pane](#split-pane)
* [Toast](#toast)
* [Tabs](#tabs)
- [Colors](#colors)
- [Events](#events)
- [Mode](#mode)
- [Ionicons](#ionicons)
### CSS
#### CSS Utilities
We originally added CSS utility attributes for styling components because it was a quick and easy way to wrap text or add padding to an element. Once we added support for multiple frameworks as part of our "Ionic for everyone" approach, we quickly determined there were problems with using CSS attributes with frameworks that use JSX and Typescript. In order to solve this we added CSS classes. Rather than support CSS attributes in certain frameworks and classes in others, we decided to remove the CSS attributes and support what works in all of them, classes, for consistency. In addition to this, changing to classes prefixed with `ion` avoids conflict with native attributes and user's CSS. In the latest version of Ionic 4, there are deprecation warnings printed in the console to show what the new classes are, and the documentation has been updated since support for classes was added to remove all references to attributes: https://ionicframework.com/docs/layout/css-utilities.
Some examples of what's changed are below. *This is not all-inclusive, see the documentation linked above for all of the available CSS utility classes.*
**Before**
```html
<ion-header text-center></ion-header>
<ion-content padding></ion-content>
<ion-label text-wrap></ion-label>
<ion-item wrap></ion-item>
```
**After**
```html
<ion-header class="ion-text-center"></ion-header>
<ion-content class="ion-padding"></ion-content>
<ion-label class="ion-text-wrap"></ion-label>
<ion-item class="ion-wrap"></ion-item>
```
#### Display Classes
The responsive display classes found in the `display.css` file have had their media queries updated to better reflect how they should work. Instead of using the maximum value of the breakpoint for `.ion-hide-{breakpoint}-down` classes it will use the minimum of that breakpoint.
The [Ionic breakpoints](https://ionicframework.com/docs/layout/css-utilities#ionic-breakpoints) are the following:
| Breakpoint Name | Width |
| ----------------| --------|
| xs | 0 |
| sm | 576px |
| md | 768px |
| lg | 992px |
| xl | 1200px |
Previously, if you added the class `ion-hide-md-down` to an element, it would hide the element when the screen size was `991px` (the maximum of the `md` breakpoint) or smaller. Now, using this same class will hide the element when the maximum screen size is `768px`.
Below is a table of how the media queries have changed for each class:
| Class Name | Ionic 4 | Ionic 5 |
| --------------------| -----------------------------| -----------------------------|
| `.ion-hide-down` | `@media (max-width: 575px)` | all screen sizes |
| `.ion-hide-sm-down` | `@media (max-width: 767px)` | `@media (max-width: 576px)` |
| `.ion-hide-md-down` | `@media (max-width: 991px)` | `@media (max-width: 768px)` |
| `.ion-hide-lg-down` | `@media (max-width: 1199px)` | `@media (max-width: 992px)` |
| `.ion-hide-xl-down` | all screen sizes | `@media (max-width: 1200px)` |
_Note that no changes were made to the `.ion-hide-{breakpoint}-up` classes._
See the [CSS Utilities responsive display documentation](https://ionicframework.com/docs/layout/css-utilities#responsive-display-attributes) for more information.
#### Activated, Focused, Hover States
The `.activated` class that is automatically added to clickable components has been renamed to `.ion-activated`.
The way the CSS variables are used for targeting the activated, focused and hover backgrounds have been updated on the following components:
- Action Sheet
- Back Button
- Button
- FAB Button
- Item
- Menu Button
- Segment Button
- Tab Button
Previously, in order to update any of the background colors for the states you would have to know what the opacity was set to. Using the Material Design spec as an example, it would require you to know that the hover state uses a white overlay with an opacity of `.08`. This means that if we had the following set by default:
```css
--background-hover: rgba(255, 255, 255, 0.08);
```
If you wanted to change the hover overlay to use black but still match the spec, you'd have to set it to:
```css
--background-hover: rgba(0, 0, 0, 0.08);
```
The new way adds the following variables:
```css
--background-activated-opacity
--background-focused-opacity
--background-hover-opacity
```
It also updates the Action Sheet component so that the variables will be prefixed with `button`. See the [Action Sheet](#action-sheet) section in this document for all of the new variable names.
This allows you to still have control over the opacity if desired, but when updating the state, you only have to set the main variables: `--background-activated`, `--background-focused`, `--background-hover` and the button will still match the spec. This is most important when changing the global theme, as updating the toolbar color will automatically update the hover states for all of the buttons in a toolbar, regardless of their fill and without having to know what each opacity is.
##### Examples
```css
/* Setting the button background on hover to solid red */
ion-button {
--background-hover: red;
--background-hover-opacity: 1;
}
/* Setting the action sheet button background on focus to an opaque green */
ion-action-sheet {
--button-background-focus: green;
--button-background-focus-opacity: 0.5;
}
/*
* Setting the fab button background on hover to match the text color with
* the default --background-hover-opacity on md
*/
.md ion-fab-button {
--color: #222;
--background-hover: #222;
}
```
##### Global CSS Properties
Some variables were renamed, removed or added. See the chart below for the changes.
| Old variable | Status | New variable |
| ----------------------------------------| --------|-------------------------------------------|
| `--ion-toolbar-color-unchecked` | renamed | `--ion-toolbar-segment-color` |
| `--ion-toolbar-color-checked` | renamed | `--ion-toolbar-segment-color-checked` |
| `--ion-toolbar-background-unchecked` | renamed | `--ion-toolbar-segment-background` |
| `--ion-toolbar-background-checked` | renamed | `--ion-toolbar-segment-background-checked`|
| `--ion-tab-bar-color-activated` | renamed | `--ion-tab-bar-color-selected` |
| | added | `--ion-toolbar-segment-indicator-color` |
| `--ion-toolbar-color-activated` | removed | |
| `--ion-item-background-activated` | removed | |
| `--ion-item-background-focused` | removed | |
| `--ion-item-background-hover` | removed | |
#### Distributed Sass
The `scss` files have been removed from `dist/`. CSS variables should be used to theme instead.
### Components
#### Action Sheet
The following CSS variables have been renamed or added:
| Old | New |
|--------------------------| -------------------------------------------|
| | `--button-background` |
| `--background-activated` | `--button-background-activated` |
| | `--button-background-activated-opacity` |
| `--background-selected` | `--button-background-selected` |
| | `--button-background-focused` |
| | `--button-background-focused-opacity` |
| | `--button-background-hover` |
| | `--button-background-hover-opacity` |
| | `--button-background-selected` |
| | `--button-background-selected-opacity` |
| | `--button-color` |
| | `--button-color-activated` |
| | `--button-color-focused` |
| | `--button-color-hover` |
| | `--button-color-selected` |
See the [Action Sheet CSS Custom Properties](https://ionicframework.com/docs/api/action-sheet#css-custom-properties) documentation for descriptions.
#### Anchor
The `ion-anchor` component has been renamed to `ion-router-link` as this is a better description of which component it should be used with. This component should still only be used in vanilla and Stencil JavaScript projects. Angular projects should use an `<a>` and `routerLink` with the Angular router. See the [documentation for router-link](https://ionicframework.com/docs/api/router-link) for more information.
#### Back Button
- Converted `ion-back-button` to use [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
- [Focused, Hover States](#activated-focused-hover-states) have been updated.
#### Button
- [Activated, Focused, Hover States](#activated-focused-hover-states) have been updated.
#### Card
Converted `ion-card` to use [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
#### Controllers
The controller components (`ion-action-sheet-controller`, `ion-alert-controller`, `ion-loading-controller`, `ion-menu-controller`, `ion-modal-controller`, `ion-picker-controller`, `ion-popover-controller`, `ion-toast-controller`) have been removed from Ionic core as elements. They should be imported from `@ionic/core` instead. This will not affect projects that use Angular or React. Below is an example of the loading controller change in a JavaScript project, but this change applies to all controller elements.
**Before**
```html
<ion-loading-controller></ion-loading-controller>
<script>
async function presentLoading() {
const loadingController = document.querySelector('ion-loading-controller');
const loading = await loadingController.create({
message: 'Hello',
duration: 2000
});
await loading.present();
}
</script>
```
**After**
```html
<script type="module">
import { loadingController } from '@ionic/core';
window.loadingController = loadingController;
</script>
<script>
async function presentLoading() {
const loading = await loadingController.create({
message: 'Hello',
duration: 2000
});
await loading.present();
}
</script>
```
#### FAB Button
- [Activated, Focused, Hover States](#activated-focused-hover-states) have been updated.
#### Item
- [Activated, Focused, Hover States](#activated-focused-hover-states) have been updated.
#### Header / Footer
The `no-border` attribute has been removed, use `ion-no-border` class instead. See [CSS Utilities](#css-utilities) above for more information on why this change was made.
#### List Header
The list header has been redesigned to match the latest iOS spec. This may break the design of your application as the previous design had a small font size with uppercase text. The latest design includes a larger, bolder text.
In addition, any text content inside of an `<ion-list-header>` should be wrapped in an `<ion-label>` in order to get the proper styling of the new design. If the label is missing, the button alignment in the list header may look off.
**Before**
```html
<ion-list-header>
New This Week
<ion-button>See All</ion-button>
</ion-list-header>
```
**After**
```html
<ion-list-header>
<ion-label>New This Week</ion-label>
<ion-button>See All</ion-button>
</ion-list-header>
```
The button has also been updated to default to `fill="clear"` and `size="small"` when inside of a list header. If the old look of the list header or buttons is desired, use custom CSS or button properties to achieve it.
For more information see the [List Header usage](https://ionicframework.com/docs/api/list-header#usage).
#### Menu
- The `swipeEnable()` function has been removed in Angular, use `swipeGesture()` instead.
- The `side` values `left` and `right` have been removed, use `start` and `end` instead.
- Removed the `main` attribute, use `content-id` (for vanilla JS / Vue) and `contentId` (for Angular / React) instead.
**Before**
```html
<ion-menu>...</ion-menu>
<ion-content main>...</ion-content>
```
**After**
```html
<ion-menu content-id="main"></ion-menu>
<ion-content id="main">...</ion-content>
```
- The presentation type in `ios` now defaults to `"overlay"`.
#### Menu Button
- [Focused, Hover States](#activated-focused-hover-states) have been updated.
#### Nav Link
The `ion-nav-push`, `ion-nav-back`, and `ion-nav-set-root` components have been removed in favor of using `ion-nav-link` with a `router-direction` property which accepts `”root”`, `“forward”`, and `“back”`. This reduces the need for maintaining multiple components when they all do the same thing with different transition directions. See the [documentation for nav-link](https://ionicframework.com/docs/api/nav-link) for more information.
#### Radio
The `ion-radio` must be used inside of an `ion-radio-group` even if there is only one `ion-radio`. Additionally, the `checked` property has been removed. Developers should set the `value` property on the parent `ion-radio-group` to match the value of the desired checked radio button.
`ion-radio` no longer emits an `ionSelect` event. Developers should listen for an `ionChange` event to be emitted on `ion-radio-group` instead.
**Before**
```html
<ion-radio checked>One</ion-radio>
<ion-radio-group>
<ion-radio>One</ion-radio>
<ion-radio checked>Two</ion-radio>
</ion-radio-group>
```
**After**
```html
<ion-radio-group value="one">
<ion-radio value="one">One</ion-radio>
</ion-radio-group>
<ion-radio-group value="two">
<ion-radio value="one">One</ion-radio>
<ion-radio value="two">Two</ion-radio>
</ion-radio-group>
```
#### Searchbar
##### Show Cancel Button
The `show-cancel-button` property of the searchbar no longer accepts boolean values. Accepted values are strings: `"focus"`, `"always"`, `"never"`.
**Before**
```html
<ion-searchbar show-cancel-button>
<ion-searchbar show-cancel-button="true">
<ion-searchbar show-cancel-button="false">
```
**After**
```html
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="never">
```
See the [Searchbar documentation](https://ionicframework.com/docs/api/searchbar#properties) for more information.
##### Inputmode
The `inputmode` property for `ion-searchbar` now defaults to `undefined`. To get the old behavior, set the inputmode property to `"search"`.
#### Segment
Segment was completely revamped to use the new iOS design including an all new gesture that applies for both Material Design and iOS. Due to these changes, some breaking changes were inevitably introduced in order to support the new design.
##### Renamed Events
`ion-segment` no longer emits an `ionSelect` event. Developers should listen for an `ionChange` event to be emitted on `ion-segment` instead.
##### Button States
- The activated styles and custom CSS properties have been removed. These are no longer being used in the latest spec as the indicator and ripple are used to show activation. Properties removed:
```
--color-activated
--background-activated
```
- The [Focused & Hover States](#activated-focused-hover-states) have been updated.
##### Indicator Color
- `--indicator-color` now applies to the checked segment button (for both `ios` and `md`)
- `--indicator-color-checked` has been removed
- The Material Design spec does not include an indicator color on non-checked buttons: https://material.io/components/tabs/
- In order to style the Segment to match the old spec, please use custom CSS. For example, to update Material Design to include a bottom line all of the time:
```css
.md ion-segment::after {
position: absolute;
bottom: 0;
height: 2px;
width: 100%;
content: '';
background: rgba(0,0,0,0.5);
z-index: -1;
}
```
##### Background & Color
A `--background` variable has been added to style the `ion-segment` component. As a result of this, the following background variables for a child segment button must now be set on the `ion-segment-button`:
```
--background: Background of the segment button
--background-checked: Background of the checked segment button
--background-disabled: Background of the disabled segment button
--background-hover: Background of the segment button on hover
```
> Note: iOS no longer checks the button background, so setting the `--background-checked` variable may have an undesired outcome. Instead, Segment uses an indicator to slide between the buttons, showing which one is checked. See the previous section on the indicator color variables.
The above variables *will not* be inherited in the button if set on the `ion-segment`. In addition to this, all color variables should also be set on the button for consistency:
```
--color: Color of the segment button
--color-checked: Color of the checked segment button
--color-disabled: Color of the disabled segment button
--color-hover: Color of the segment button on hover
```
###### Removed variables
The following variables were removed due to the current spec no longer using them.
- `--color-checked-disabled`
- `--background-disabled`
- `--color-disabled`
- `--background-activated`
- `--color-activated`
##### Global CSS Properties
Some variables were renamed or added. See the chart below for the new names.
| Old variable | Status | New variable |
| ----------------------------------------| --------|-------------------------------------------|
| `--ion-toolbar-color-unchecked` | renamed | `--ion-toolbar-segment-color` |
| `--ion-toolbar-color-checked` | renamed | `--ion-toolbar-segment-color-checked` |
| `--ion-toolbar-background-unchecked` | renamed | `--ion-toolbar-segment-background` |
| `--ion-toolbar-background-checked` | renamed | `--ion-toolbar-segment-background-checked`|
| | added | `--ion-toolbar-segment-indicator-color` |
#### Segment Button
The `checked` property has been removed. Developers should set the `value` property on the parent `ion-segment` to match the value of the desired checked segment button.
**Before**
```html
<ion-segment>
<ion-segment-button>One</ion-segment-button>
<ion-segment-button checked>Two</ion-segment-button>
<ion-segment-button>Three</ion-segment-button>
</ion-segment>
```
**After**
```html
<ion-segment value="two">
<ion-segment-button value="one">One</ion-segment-button>
<ion-segment-button value="two">Two</ion-segment-button>
<ion-segment-button value="three">Three</ion-segment-button>
</ion-segment>
```
#### Select Option
The `selected` property has been removed. Developers should set the `value` property on the parent `ion-select` to match the desired selected option.
**Before**
```html
<ion-select>
<ion-select-option>One</ion-select-option>
<ion-select-option selected>Two</ion-select-option>
</ion-select>
```
**After**
```html
<ion-select value="two">
<ion-select-option value="one">One</ion-select-option>
<ion-select-option value="two">Two</ion-select-option>
</ion-select>
```
#### Skeleton Text
The `width` property has been removed in favor of using CSS styling.
#### Split Pane
- Converted to use [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
- Removed the `main` attribute, use `content-id` (for vanilla JS / Vue) and `contentId` (for Angular / React) instead.
**Before**
```html
<ion-split-pane>
...
<div main>...</div>
</ion-split-pane>
```
**After**
```html
<ion-split-pane content-id="main">
...
<div id="main">...</div>
</ion-split-pane>
```
#### Tabs
- [Focused State](#activated-focused-hover-states) have been updated.
#### Toast
The close button properties (`showCloseButton` and `closeButtonText`) have been removed. Use the `buttons` array instead with `role: 'cancel'`. See the [usage documentation](https://ionicframework.com/docs/api/toast#usage) for more information.
**Before**
```javascript
async presentToast() {
const toast = await this.toastController.create({
message: 'Your settings have been saved.',
showCloseButton: true,
closeButtonText: 'Close'
});
toast.present();
}
```
**After**
```javascript
async presentToast() {
const toast = await this.toastController.create({
message: 'Your settings have been saved.',
buttons: [
{
text: 'Close',
role: 'cancel',
handler: () => {
console.log('Close clicked');
}
}
]
});
toast.present();
}
```
### Colors
The default Ionic colors have been updated to the following:
```scss
primary: #3880ff
secondary: #3dc2ff
tertiary: #5260ff
success: #2dd36f
warning: #ffc409
danger: #eb445a
light: #f4f5f8
medium: #92949c
dark: #222428
```
`primary`, `light` and `dark` have not changed. The contrast color for `warning` has been updated to `#000`.
This will only be a breaking change in your app if you are not using one of our starters and not overriding the defaults. If you are overriding the defaults already these will need to be manually updated if desired.
### Events
The `@ionic/angular` Events service has been removed.
- Use "Observables" for a similar pub/sub architecture: https://angular.io/guide/observables
- Use "Redux" for advanced state management: https://ngrx.io
### Mode
Mode is now cascaded from the parent to the child component. Previously, if you wanted to update a component and its children to use the same mode, you'd have to set it on all components. For example, if you wanted to use a `md` segment no matter the mode, you'd have to write the following:
```html
<ion-segment mode="md">
<ion-segment-button mode="md">Button</ion-segment-button>
<ion-segment-button mode="md">Button</ion-segment-button>
</ion-segment>
```
Now, the `mode` only needs to be set on the `ion-segment` and it will be inherited. If this behavior is not desired set a different mode on the child component.
### Ionicons
Ionicons 5 has been released! :tada: This brings many changes including a top to bottom re-draw of every icon, variants for each icon (filled, outline, and sharp), and the removal of auto-switching icons based on the platform.
For more information, check out the [Ionicons Changelog](https://github.com/ionic-team/ionicons/blob/master/CHANGELOG.md)!
--------------------------------------------------------------------------------------------------
## Version 4.x
The list of the breaking changes introduced in Ionic Angular v4 can be found in [angular/BREAKING.md](https://github.com/ionic-team/ionic/blob/master/angular/BREAKING.md).
## Legacy
For the breaking changes of the older legacy versions (versions 2.x & 3.x) of Ionic Framework, see the [v3 changelog](https://github.com/ionic-team/ionic-v3/blob/master/CHANGELOG.md).
The list of the breaking changes introduced in Ionic Angular v4 has been moved to [angular/BREAKING.md](https://github.com/ionic-team/ionic/blob/master/angular/BREAKING.md).

View File

@@ -1,973 +1,10 @@
## [5.0.2](https://github.com/ionic-team/ionic/compare/v5.0.1...v5.0.2) (2020-02-26)
### Bug Fixes
* **ios:** large title transition works properly in tabbed applications ([#20555](https://github.com/ionic-team/ionic/issues/20555)) ([7187541](https://github.com/ionic-team/ionic/commit/71875417f207d26bd7115655f239251460a1e3d8)), closes [#20482](https://github.com/ionic-team/ionic/issues/20482)
* **menu:** hardware back button now dismisses side menu if open in Cordova/Capacitor app ([#20558](https://github.com/ionic-team/ionic/issues/20558)) ([6b2a929](https://github.com/ionic-team/ionic/commit/6b2a929cd7e70b16383cb3336b399a1aee2d6101)), closes [#20559](https://github.com/ionic-team/ionic/issues/20559)
* **modal:** allow swipe to close animation to be overridden ([#20585](https://github.com/ionic-team/ionic/issues/20585)) ([8d3ce8d](https://github.com/ionic-team/ionic/commit/8d3ce8d29c9abd89ce47c882e0d7b2ac0f861966)), closes [#20577](https://github.com/ionic-team/ionic/issues/20577)
* **modal:** card style modal now adds appropriate contrast ([#20604](https://github.com/ionic-team/ionic/issues/20604)) ([b5310ef](https://github.com/ionic-team/ionic/commit/b5310effe3f9b47459f22221da1853a55dbb0279))
* **modal:** allow swipeable modal background to be overridden ([#20584](https://github.com/ionic-team/ionic/issues/20584)) ([ad6fac8](https://github.com/ionic-team/ionic/commit/ad6fac83cb7c4acb377b4b1620ab1a3f852bc6d3)), closes [#20572](https://github.com/ionic-team/ionic/issues/20572)
* **modal:** swipeable modal styles only apply to ios ([#20571](https://github.com/ionic-team/ionic/issues/20571)) ([3a2d828](https://github.com/ionic-team/ionic/commit/3a2d82814b22a3987a5abfe4412d83a93a97b6b7)), closes [#20569](https://github.com/ionic-team/ionic/issues/20569)
* **refresher:** ensure that translate is cleaned up to avoid stacking context ([#20621](https://github.com/ionic-team/ionic/issues/20621)) ([e3e5c69](https://github.com/ionic-team/ionic/commit/e3e5c69681f376cbc4b1305f719fc6895b21a9b7)), closes [#17949](https://github.com/ionic-team/ionic/issues/17949)
* **segment:** segment functions properly on older versions of Android ([#20554](https://github.com/ionic-team/ionic/issues/20554)) ([0224bed](https://github.com/ionic-team/ionic/commit/0224bed0c9f91bcb54bb4b3064df56928cf5ed8a)), closes [#20466](https://github.com/ionic-team/ionic/issues/20466)
* **select:** properly align text, add icon-inner and placeholder part ([#20605](https://github.com/ionic-team/ionic/issues/20605)) ([926ac3f](https://github.com/ionic-team/ionic/commit/926ac3fb47228be19146ccdfab92a05cf6677ff4))
* **slides:** set height to 100% for vertical slides ([#20603](https://github.com/ionic-team/ionic/issues/20603)) ([20af652](https://github.com/ionic-team/ionic/commit/20af652a1be5e1aff2836422489642c8baed6939)), closes [#17341](https://github.com/ionic-team/ionic/issues/17341)
## [5.0.1](https://github.com/ionic-team/ionic/compare/v5.0.0...v5.0.1) (2020-02-19)
### Bug Fixes
* **button:** reduce font size of icon only button in toolbar on iOS ([#20547](https://github.com/ionic-team/ionic/issues/20547)) ([59fa340](https://github.com/ionic-team/ionic/commit/59fa340650840b2abf86bd643350da7064ee9ead))
* **card:** inherit background in inner button ([#20461](https://github.com/ionic-team/ionic/issues/20461)) ([c16de96](https://github.com/ionic-team/ionic/commit/c16de9663329993698c10e4251dbb0b75167701f)), closes [#20458](https://github.com/ionic-team/ionic/issues/20458)
* **fab:** add close icon to internal icons for react ([#20490](https://github.com/ionic-team/ionic/issues/20490)) ([c4fb314](https://github.com/ionic-team/ionic/commit/c4fb31403eb4704bcf04e6c32bfa46422f08bf4f)), closes [#20489](https://github.com/ionic-team/ionic/issues/20489)
* **fab:** show close icon on hover, focused, activated ([#20497](https://github.com/ionic-team/ionic/issues/20497)) ([e42c85d](https://github.com/ionic-team/ionic/commit/e42c85d64161b7fac7147325cb6c2ceff990042b))
* **input:** do not clear input if "Enter" key pressed ([#20462](https://github.com/ionic-team/ionic/issues/20462)) ([89bf08b](https://github.com/ionic-team/ionic/commit/89bf08b6276f2a25d66fc0e74ba3303f923af652)), closes [#20442](https://github.com/ionic-team/ionic/issues/20442)
* **ios:** clamp out of bounds values for swipe to go back ([#20540](https://github.com/ionic-team/ionic/issues/20540)) ([dd32a5e](https://github.com/ionic-team/ionic/commit/dd32a5e2788a11a5c2be0d1840f5775c7307c57f)), closes [#20505](https://github.com/ionic-team/ionic/issues/20505)
* **menu:** swipe gesture should not open menu when a modal is displayed ([#20546](https://github.com/ionic-team/ionic/issues/20546)) ([3252c2f](https://github.com/ionic-team/ionic/commit/3252c2f8dc46e0853a7f626baa6023a01ac21a25)), closes [#20467](https://github.com/ionic-team/ionic/issues/20467)
* **modal:** presenting multiple card-style modals now adds border radius properly ([#20476](https://github.com/ionic-team/ionic/issues/20476)) ([abf594a](https://github.com/ionic-team/ionic/commit/abf594aa611562a76e3baecfa38456d41a1410f3)), closes [#20475](https://github.com/ionic-team/ionic/issues/20475)
* **modal:** prevent card style modal styles from being overridden ([#20470](https://github.com/ionic-team/ionic/issues/20470)) ([86ab77a](https://github.com/ionic-team/ionic/commit/86ab77a6e2cb124510c244110fc78a4bc0654cd1)), closes [#20469](https://github.com/ionic-team/ionic/issues/20469)
* **react:** do a better job matching up route to sync ([#20446](https://github.com/ionic-team/ionic/issues/20446)) ([c0aadd6](https://github.com/ionic-team/ionic/commit/c0aadd60077e5ba379959d93006e3a9c1418263c)), closes [#20363](https://github.com/ionic-team/ionic/issues/20363)
* **react:** do not remove pages when navigating between tabs ([#20431](https://github.com/ionic-team/ionic/issues/20431)) ([b6fbe98](https://github.com/ionic-team/ionic/commit/b6fbe98812fbab5ef9e0723802605c0711581dd2)), closes [#20398](https://github.com/ionic-team/ionic/issues/20398)
* **react:** icons with MD set should work in browser ([#20463](https://github.com/ionic-team/ionic/issues/20463)) ([82670fe](https://github.com/ionic-team/ionic/commit/82670fe4d0592451cbc243b3008beb3f8f483c30))
* **react:** update paths of tab buttons when href changes in ion buttons ([#20480](https://github.com/ionic-team/ionic/issues/20480)) ([45d03ba](https://github.com/ionic-team/ionic/commit/45d03baf981d0e10eb1fe689908532adef2ba31d)), closes [#20321](https://github.com/ionic-team/ionic/issues/20321)
* **searchbar:** properly align placeholder ([#20460](https://github.com/ionic-team/ionic/issues/20460)) ([4d6e15a](https://github.com/ionic-team/ionic/commit/4d6e15ab18fc894c3826b89362163256adc227f4)), closes [#20456](https://github.com/ionic-team/ionic/issues/20456)
* **segment:** border radius applies to indicator on ios ([#20541](https://github.com/ionic-team/ionic/issues/20541)) ([9b5854d](https://github.com/ionic-team/ionic/commit/9b5854d79712356f8a3772442c0cc412db09d5e0)), closes [#20539](https://github.com/ionic-team/ionic/issues/20539)
* **segment:** do not show ripple effect if disabled via config ([#20542](https://github.com/ionic-team/ionic/issues/20542)) ([7a461c5](https://github.com/ionic-team/ionic/commit/7a461c59c5d9a23de0bcdd53397f452d17251fd6)), closes [#20533](https://github.com/ionic-team/ionic/issues/20533)
* **segment:** inner div no longer interferes with click events ([#20522](https://github.com/ionic-team/ionic/issues/20522)) ([06b828b](https://github.com/ionic-team/ionic/commit/06b828b4ffb12b076b0805274f53fa158ee65c5a)), closes [#20381](https://github.com/ionic-team/ionic/issues/20381)
* **segment:** only emit ionChange when user releases pointer from screen ([#20495](https://github.com/ionic-team/ionic/issues/20495)) ([4d50064](https://github.com/ionic-team/ionic/commit/4d5006476479bc376d3bb4edad6db0b3ce806ef7)), closes [#20500](https://github.com/ionic-team/ionic/issues/20500) [#20257](https://github.com/ionic-team/ionic/issues/20257)
* **tab-bar:** update ios icon and label design to match native ([#20548](https://github.com/ionic-team/ionic/issues/20548)) ([34f8576](https://github.com/ionic-team/ionic/commit/34f8576b959d41896502e4f7b0c4240156e001eb))
# [5.0.0 Magnesium](https://github.com/ionic-team/ionic/compare/v4.11.10...v5.0.0) (2020-02-11)
Enjoy! :fire:
> We recommend updating to version `4.11.10` before updating to this version in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
Run the following commands based on your project type:
```
# for an angular app
npm i @ionic/angular@latest --save
# for a react app
npm i @ionic/react@latest --save
npm i @ionic/react-router@latest --save
npm i ionicons@latest --save
# for a stencil / vanilla JS app
npm i @ionic/core@latest --save
```
Then take a look at the [Breaking Changes](./BREAKING.md) file for API changes.
# [5.0.0-rc.5](https://github.com/ionic-team/ionic/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2020-02-11)
### Bug Fixes
* **angular:** correct path for angular projects ([#20436](https://github.com/ionic-team/ionic/issues/20436)) ([fd9c7a9](https://github.com/ionic-team/ionic/commit/fd9c7a9601e7f21b74c76be1f8bb305bf008915c)), closes [#20435](https://github.com/ionic-team/ionic/issues/20435)
# [5.0.0-rc.4](https://github.com/ionic-team/ionic/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2020-02-10)
### Bug Fixes
* **content:** only emit scroll events if enabled ([#20401](https://github.com/ionic-team/ionic/issues/20401)) ([fd1b44a](https://github.com/ionic-team/ionic/commit/fd1b44a40b741088e099f6538dd14caa0dc5540c))
* **header:** backdrop filter no longer distorts content with collapsible header ([#20388](https://github.com/ionic-team/ionic/issues/20388)) ([11d3945](https://github.com/ionic-team/ionic/commit/11d39457d56c091e9c41c180391d27464ae748b5)), closes [#20385](https://github.com/ionic-team/ionic/issues/20385)
* **item:** remove unneeded box-shadow CSS variable ([#20412](https://github.com/ionic-team/ionic/issues/20412)) ([a6764c4](https://github.com/ionic-team/ionic/commit/a6764c4724e1e7eed19a1902b563aeb61bfde38e)), closes [#20392](https://github.com/ionic-team/ionic/issues/20392)
* **label:** remove subpixel font-size to prevent visual glitches ([#20415](https://github.com/ionic-team/ionic/issues/20415)) ([3d6f287](https://github.com/ionic-team/ionic/commit/3d6f287d87bfaa0d81a34182baf38192e08fb3c1)), closes [#20407](https://github.com/ionic-team/ionic/issues/20407)
* **segment:** add activated class directly to segment button ([#20400](https://github.com/ionic-team/ionic/issues/20400)) ([e8886e9](https://github.com/ionic-team/ionic/commit/e8886e98f188044227bf5757892341cb598fdd27))
# [5.0.0-rc.3](https://github.com/ionic-team/ionic/compare/v5.0.0-rc.2...v5.0.0-rc.3) (2020-02-05)
### Bug Fixes
* **refresher:** ensure gesture does not interfere with item-sliding ([#20380](https://github.com/ionic-team/ionic/issues/20380)) ([8983c70](https://github.com/ionic-team/ionic/commit/8983c7006e54743873cd45ae1acdfa974d74547a)), closes [#20379](https://github.com/ionic-team/ionic/issues/20379)
* **refresher:** translate background content when refreshing ([#20378](https://github.com/ionic-team/ionic/issues/20378)) ([cf70916](https://github.com/ionic-team/ionic/commit/cf7091625ecb46c3f9882ae9eff5c946523fab75)), closes [#20377](https://github.com/ionic-team/ionic/issues/20377)
* **segment:** allow background to be set on iOS segment in a toolbar ([#20350](https://github.com/ionic-team/ionic/issues/20350)) ([0f31624](https://github.com/ionic-team/ionic/commit/0f31624104d195367df197eda9b8d6c5bda4cf75))
* **toolbar:** properly apply safe area and border ([#20375](https://github.com/ionic-team/ionic/issues/20375)) ([4971499](https://github.com/ionic-team/ionic/commit/4971499026fcee70a32cc9480302bb14a1bebcb7)), closes [#20354](https://github.com/ionic-team/ionic/issues/20354)
# [5.0.0-rc.2](https://github.com/ionic-team/ionic/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2020-01-30)
### Bug Fixes
* **header:** fix race condition in collapsible header ([#20334](https://github.com/ionic-team/ionic/issues/20334)) ([215d55f](https://github.com/ionic-team/ionic/commit/215d55f1ebeb93988b513c5869faae14d1d51919))
* **ios:** translucent toolbar blur no longer obscures entering page toolbar content ([#20314](https://github.com/ionic-team/ionic/issues/20314)) ([e580b88](https://github.com/ionic-team/ionic/commit/e580b884770a086ee5d8acf61588ea50181786e6)), closes [#19158](https://github.com/ionic-team/ionic/issues/19158)
* **radio:** do not clear radio group value from radio ([#20343](https://github.com/ionic-team/ionic/issues/20343)) ([ff78e6e](https://github.com/ionic-team/ionic/commit/ff78e6e8ca8ae4dc2a6d401b377dd3977c48824a)), closes [#20323](https://github.com/ionic-team/ionic/issues/20323)
* **radio:** set default radio value if undefined ([#20329](https://github.com/ionic-team/ionic/issues/20329)) ([eb57723](https://github.com/ionic-team/ionic/commit/eb57723785ce5b05585bf48bf9c8ae1b62235ba2))
* **refresher:** add correct fallbacks for native refreshers ([#20333](https://github.com/ionic-team/ionic/issues/20333)) ([fd55427](https://github.com/ionic-team/ionic/commit/fd55427991e94488d86971aaa10acb13d7fa1c23))
* **refresher:** resolve undefined issues when updating component ([#20322](https://github.com/ionic-team/ionic/issues/20322)) ([59d8687](https://github.com/ionic-team/ionic/commit/59d86873a2ab913358b084bb05180ba176893a8f)), closes [#20320](https://github.com/ionic-team/ionic/issues/20320)
# [5.0.0-rc.1](https://github.com/ionic-team/ionic/compare/v4.11.10...v5.0.0-rc.1) (2020-01-27)
### Bug Fixes
* **components:** use proper colors for button states and add back input highlight ([#20278](https://github.com/ionic-team/ionic/issues/20278)) ([628db18](https://github.com/ionic-team/ionic/commit/628db18a97293731ecfee8e4d2f0d8c1cf672c96)), closes [#20276](https://github.com/ionic-team/ionic/issues/20276)
* **components:** inherit text indent in all components with text inherit ([#20300](https://github.com/ionic-team/ionic/issues/20300)) ([767b005](https://github.com/ionic-team/ionic/commit/767b005eacf00b640973bfb381de4dcedf083399)), closes [#17786](https://github.com/ionic-team/ionic/issues/17786)
* **content:** resolve height inheritance issues ([#20309](https://github.com/ionic-team/ionic/issues/20309)) ([09bef71](https://github.com/ionic-team/ionic/commit/09bef71ccd5a261233180bc19023bc562b905764)), closes [#20305](https://github.com/ionic-team/ionic/issues/20305)
* **picker:** include showBackdrop in interface ([#20301](https://github.com/ionic-team/ionic/issues/20301)) ([33186ba](https://github.com/ionic-team/ionic/commit/33186ba716de77edc92ae8e7d307f90fff8b8ed1)), closes [#18893](https://github.com/ionic-team/ionic/issues/18893)
* **react:** export proper types of animations and gestures ([#20311](https://github.com/ionic-team/ionic/issues/20311)) ([0034088](https://github.com/ionic-team/ionic/commit/00340885fb031d3dbc7c458fddeed9d28a2deda4))
* **refresher:** update animation for dashed property values ([#20310](https://github.com/ionic-team/ionic/issues/20310)) ([44211c1](https://github.com/ionic-team/ionic/commit/44211c11ee929b9966d5e67e99fb6a495380432c))
* **toast:** inherit color in cancel button for a toast with color ([#20299](https://github.com/ionic-team/ionic/issues/20299)) ([7b44ae2](https://github.com/ionic-team/ionic/commit/7b44ae2a400bb0c0616012e9c42bfc67edbfc793)), closes [#20139](https://github.com/ionic-team/ionic/issues/20139)
## [4.11.10](https://github.com/ionic-team/ionic/compare/v4.11.9...v4.11.10) (2020-01-24)
### Bug Fixes
* **input:** revert previous type change ([db1fd1d](https://github.com/ionic-team/ionic/commit/db1fd1d72a8a0ade824ad2309d1adb2953731f37))
# [5.0.0-rc.0](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.6...v5.0.0-rc.0) (2020-01-23)
Release Candidate is here! :tada:
# [5.0.0-beta.6](https://github.com/ionic-team/ionic/compare/v4.11.9...v5.0.0-beta.6) (2020-01-23)
### Bug Fixes
* **animation:** add property conversions for CSS Animations ([#20252](https://github.com/ionic-team/ionic/issues/20252)), fixes [#20251](https://github.com/ionic-team/ionic/issues/20251) ([32a7401](https://github.com/ionic-team/ionic/commit/32a7401576a9c91fdee66d2cede06b6a16884d35))
* **content:** set min-height to allow for sticky headers ([#20265](https://github.com/ionic-team/ionic/issues/20252)), fixes [#20258](https://github.com/ionic-team/ionic/issues/20258) ([e613f63](https://github.com/ionic-team/ionic/commit/e613f63590f3d6a4fa4f3a361811224394ba0be2))
* **modal:** card-style modal now opens at full width on larger devices ([#20256](https://github.com/ionic-team/ionic/issues/20256)), fixes [#20255](https://github.com/ionic-team/ionic/issues/20255) ([443cbd9](https://github.com/ionic-team/ionic/commit/443cbd9eb273767d8405b6a05ffabee037e9f3b7))
* **segment:** clicking disabled button no longer adds ripple to active button ([#20254](https://github.com/ionic-team/ionic/issues/20254)), fixes [#20253](https://github.com/ionic-team/ionic/issues/20253) ([f896821](https://github.com/ionic-team/ionic/commit/f8968217533ff60948047510064d2f15d01c249c))
### Features
* **components:** improve button states and add new css properties ([#19440](https://github.com/ionic-team/ionic/issues/19440)) ([9415929](https://github.com/ionic-team/ionic/commit/94159291b27ddf1a859c8f3f87a0d6e54a8b5f13)), closes [#20213](https://github.com/ionic-team/ionic/issues/20213) [#19965](https://github.com/ionic-team/ionic/issues/19965)
* **react:** add Ionic Animations wrapper (experimental) ([#20273](https://github.com/ionic-team/ionic/issues/20273)) ([b59d764](https://github.com/ionic-team/ionic/commit/b59d7647fd6f9a645a4ec0fe9aca526ea5eda4e0))
* **segment-button:** add --indicator-height property to segment button ([#19653](https://github.com/ionic-team/ionic/issues/19653)) ([d76a503](https://github.com/ionic-team/ionic/commit/d76a5031c4c96b5fdf691a56ed61d3dcc4e4dafb))
### BREAKING CHANGES
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
*Activated Class*
The `activated` class that is automatically added to buttons on press has been renamed to `ion-activated`. This will be more consistent with our `ion-focused` class we add and also will reduce conflicts with users' CSS.
*CSS Variables*
The `--background-hover`, `--background-focused` and `--background-activated` CSS variables on components that render native buttons will now have an opacity automatically set. If you are setting any of these like the following:
```
--background-hover: rgba(44, 44, 44, 0.08);
```
You will likely not see a hover state anymore. It should be updated to only set the desired color:
```
--background-hover: rgba(44, 44, 44);
```
If the opacity desired is something other than what the spec asks for, use:
```
--background-hover: rgba(44, 44, 44);
--background-hover-opacity: 1;
```
## [4.11.9](https://github.com/ionic-team/ionic/compare/v4.11.8...v4.11.9) (2020-01-23)
### Bug Fixes
* **core:** updating type of input value to accept numbers, fixes [#20173](https://github.com/ionic-team/ionic/issues/20173) ([#20267](https://github.com/ionic-team/ionic/issues/20267)) ([7080205](https://github.com/ionic-team/ionic/commit/708020551f9c51ca3b32d7b49bf4572db3dda12e))
* **react:** adding missing overlay component events, fixes [#19923](https://github.com/ionic-team/ionic/issues/19923) ([#20266](https://github.com/ionic-team/ionic/issues/20266)) ([ec6a8dd](https://github.com/ionic-team/ionic/commit/ec6a8dd86f3854edba367f79a6ebac7d60eed839))
* **react:** Don't render overlay children if isOpen is false, fixes [#20225](https://github.com/ionic-team/ionic/issues/20225) ([#20226](https://github.com/ionic-team/ionic/issues/20226)) ([aff9612](https://github.com/ionic-team/ionic/commit/aff9612d1197dca48eab6eff9d749032c380cf82))
* **react:** re attach props on update, fixes 20192 ([#20228](https://github.com/ionic-team/ionic/issues/20228)) ([9e35ebe](https://github.com/ionic-team/ionic/commit/9e35ebed4a1590ef2521f5f8c393bdd9dea32a04))
* **react:** remove leaving view when routerdirection is back, fixes [#20124](https://github.com/ionic-team/ionic/issues/20124) ([#20268](https://github.com/ionic-team/ionic/issues/20268)) ([63d4e87](https://github.com/ionic-team/ionic/commit/63d4e877fb18c90d70c4cbd5f66ffccb8ee6489c))
* **react:** support routes without a path for notfound routes, fixes [#20259](https://github.com/ionic-team/ionic/issues/20259) ([#20261](https://github.com/ionic-team/ionic/issues/20261)) ([2f8c13b](https://github.com/ionic-team/ionic/commit/2f8c13b6960f9bcfb941c36fa6e1742b96f80ba9))
* **react:** update icon types to be a string as well, fixes [#20229](https://github.com/ionic-team/ionic/issues/20229) ([#20230](https://github.com/ionic-team/ionic/issues/20230)) ([1411d8a](https://github.com/ionic-team/ionic/commit/1411d8a173bfefd7db5241218fd5641b7e9da823))
# [5.0.0-beta.5](https://github.com/ionic-team/ionic/compare/v4.11.8...v5.0.0-beta.5) (2020-01-17)
### Bug Fixes
* **action-sheet:** allow scrollable action sheet with many options ([#20145](https://github.com/ionic-team/ionic/issues/20145)) ([53fad97](https://github.com/ionic-team/ionic/commit/53fad978c5a57efe34671db6cbede49c4a5af866)), closes [#17311](https://github.com/ionic-team/ionic/issues/17311)
* **card:** remove top padding of content in iOS if under header ([#20223](https://github.com/ionic-team/ionic/issues/20223)) ([9232f16](https://github.com/ionic-team/ionic/commit/9232f16eea8163c1ac0797abd9b6e92da44bacb1))
* **content:** scroll-content div now takes up full height of container ([#20194](https://github.com/ionic-team/ionic/issues/20194)) ([9d63b41](https://github.com/ionic-team/ionic/commit/9d63b41a5296688524c64f828f92090d73d6b556)), closes [#20185](https://github.com/ionic-team/ionic/issues/20185)
* **header:** header opacity properly resets on collapsible titles ([#20202](https://github.com/ionic-team/ionic/issues/20202)) ([8e11f79](https://github.com/ionic-team/ionic/commit/8e11f79fcca94a9c50ccc7e18e0fe44ef9764b1d))
* **modal:** prevent double dismiss via gesture and backdrop tap on card-style modal ([#20203](https://github.com/ionic-team/ionic/issues/20203)) ([5b0400d](https://github.com/ionic-team/ionic/commit/5b0400d5afec308861408967a5f61c9b93af0004))
* **picker:** pick correct option at low velocities ([#19660](https://github.com/ionic-team/ionic/issues/19660)) ([39d1262](https://github.com/ionic-team/ionic/commit/39d12629dbc12e4a86037b09350ec1c49ed6e2e4)), closes [#19659](https://github.com/ionic-team/ionic/issues/19659)
* **react:** updating icon type and add caret to internal icons ([#20216](https://github.com/ionic-team/ionic/issues/20216)) ([dc78f98](https://github.com/ionic-team/ionic/commit/dc78f981531960791a425b3b1b81d45d5065a263))
* **ssr:** add reflect content-id attribute to applicable properties ([#20169](https://github.com/ionic-team/ionic/issues/20169)) ([3aa47e6](https://github.com/ionic-team/ionic/commit/3aa47e6e2f18c3a07f2c0560a0946571e8e6815d))
### Code Refactoring
* removed checked/selected properties in favor of setting value on parent ([#19449](https://github.com/ionic-team/ionic/issues/19449)) ([a5229d9](https://github.com/ionic-team/ionic/commit/a5229d90ca2a608e8bf4db0c8f71c86d481dd649))
### Features
* **datetime:** add custom timezone display property ([#19519](https://github.com/ionic-team/ionic/issues/19519)) ([7b032c5](https://github.com/ionic-team/ionic/commit/7b032c5e9b396fcfb0b0e313aff1bebcbc43305e)), closes [#19401](https://github.com/ionic-team/ionic/issues/19401)
* **segment:** update design for iOS and MD spec ([#19036](https://github.com/ionic-team/ionic/issues/19036)) ([dc66ce4](https://github.com/ionic-team/ionic/commit/dc66ce48e1f210ca57cecae5c89e5dc3b7e95de5)), closes [#18663](https://github.com/ionic-team/ionic/issues/18663)
* **toast:** expose shadow parts ([#20146](https://github.com/ionic-team/ionic/issues/20146)) ([3b4988a](https://github.com/ionic-team/ionic/commit/3b4988aa60dc6d31e1bc3367cb8f5e8d85710ac6))
### BREAKING CHANGES
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
* The following components have been updated to remove the checked or selected properties:
- Radio
- Segment Button
- Select
Developers should set the value property on the respective parent components in order to managed checked/selected status. See the [Breaking Changes](./BREAKING.md) document for updated usage examples.
* Controller components have been removed. Developers should user their respective imports instead. This only affects vanilla JS applications.
Before:
```html
<ion-modal-controller></ion-modal-controller>
```
After:
```javascript
import { modalController } from '@ionic/core';
```
## [4.11.8](https://github.com/ionic-team/ionic/compare/v4.11.7...v4.11.8) (2020-01-13)
* **react:** add missing react memory router ([8a5aba2](https://github.com/ionic-team/ionic/commit/8a5aba206865ce2af7f8bb85f4e7cd8dec37831d))
* **react:** fixing type of icon in ToastOptions, ActionSheetOptions, fixes [#20100](https://github.com/ionic-team/ionic/issues/20100) ([857bab6](https://github.com/ionic-team/ionic/commit/857bab66419a851c6d189cd1456cd67c1c2d934c))
* **react:** supporting ios and md props on icons ([#20170](https://github.com/ionic-team/ionic/issues/20170)) ([676cc19](https://github.com/ionic-team/ionic/commit/676cc19b89cd6374346aaac9cc3292872c7148fa))
# [5.0.0-beta.4](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.3...v5.0.0-beta.4) (2020-01-06)
### Features
* **refresher:** add MD native refresher ([#20096](https://github.com/ionic-team/ionic/issues/20096)) ([5b81bdf](https://github.com/ionic-team/ionic/commit/5b81bdfcf18ed182bde14bbea4957b49ea886322)), closes [#17316](https://github.com/ionic-team/ionic/issues/17316)
# [5.0.0-beta.3](https://github.com/ionic-team/ionic/compare/v4.11.7...v5.0.0-beta.3) (2020-01-03)
### Bug Fixes
* **animation:** support css animation on older devices ([#20020](https://github.com/ionic-team/ionic/issues/20020)) ([49c394c](https://github.com/ionic-team/ionic/commit/49c394c3d335795fd100f54a5b29db009d413dff)), closes [#20017](https://github.com/ionic-team/ionic/issues/20017)
* **icons:** fix the ellipsis fill in ionicons ([#20137](https://github.com/ionic-team/ionic/issues/20137)) ([9318d24](https://github.com/ionic-team/ionic/commit/9318d2418ec144bbce4a3b7ead33cf099d6ec25b))
* **modal:** account for safe area on devices with a notch ([#20072](https://github.com/ionic-team/ionic/issues/20072)) ([1cabb53](https://github.com/ionic-team/ionic/commit/1cabb5365097d0675447a36223583824a58a140c))
* **react:** fire lifecycle events on initial render, fixes [#20071](https://github.com/ionic-team/ionic/issues/20071) ([2dcf3ee](https://github.com/ionic-team/ionic/commit/2dcf3ee7b570be73be35c52f03ccfa09baf5d830))
### Code Refactoring
* **searchbar:** set inputmode default to undefined ([#20080](https://github.com/ionic-team/ionic/issues/20080)) ([6612604](https://github.com/ionic-team/ionic/commit/6612604733ac1b3e46546625f24ef6efa5be1538)), closes [#20074](https://github.com/ionic-team/ionic/issues/20074)
### Features
* **angular:** angular 9 support ([#19515](https://github.com/ionic-team/ionic/issues/19515)) ([2344d0b](https://github.com/ionic-team/ionic/commit/2344d0b272105e368c00ef611f28909215162f7c))
* **checkbox:** add --checkmark-width variable ([#19933](https://github.com/ionic-team/ionic/issues/19933)) ([c32a7bc](https://github.com/ionic-team/ionic/commit/c32a7bcd202993056923857a5d9eed14f5be8580)), closes [#16803](https://github.com/ionic-team/ionic/issues/16803)
* **radio:** add --border-radius and --inner-border-radius variables ([#20140](https://github.com/ionic-team/ionic/issues/20140)) ([a01c102](https://github.com/ionic-team/ionic/commit/a01c10267e18a48f30af2f552c556d31dad582e9))
* **refresher:** add iOS native refresher ([#20037](https://github.com/ionic-team/ionic/issues/20037)) ([04e7c03](https://github.com/ionic-team/ionic/commit/04e7c031326ec551531af291ef1a03878d168378)), closes [#18664](https://github.com/ionic-team/ionic/issues/18664)
* **toggle:** add --border-radius and --handle-border-radius variables ([#20141](https://github.com/ionic-team/ionic/issues/20141)) ([02a46a1](https://github.com/ionic-team/ionic/commit/02a46a1007dde820cb158d34d4e3f243c07251dc))
### BREAKING CHANGES
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
* **searchbar:** The `inputmode` property for `ion-searchbar` now defaults to `undefined`. To get the old behavior, set the `inputmode` property to `"search"`.
## [4.11.7](https://github.com/ionic-team/ionic/compare/v4.11.6...v4.11.7) (2019-12-12)
### Bug Fixes
* **react:** fire lifecycle events on initial render, fixes [#20071](https://github.com/ionic-team/ionic/issues/20071) ([9ea75eb](https://github.com/ionic-team/ionic/commit/9ea75ebec7b1367fc0e319fe61c1f42516357e10))
# [5.0.0-beta.2](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2019-12-11)
### Bug Fixes
* **animation:** convert hyphenated properties to camel case when using Web Animations ([#20059](https://github.com/ionic-team/ionic/issues/20059)) ([56f67bd](https://github.com/ionic-team/ionic/commit/56f67bd9a5c8c81768e310560b2605e44bf7a9f0)), closes [#20058](https://github.com/ionic-team/ionic/issues/20058)
* **animation:** properly update Web Animation object ([#19964](https://github.com/ionic-team/ionic/issues/19964)) ([e766194](https://github.com/ionic-team/ionic/commit/e76619478c3c49469fcc22e264cc831d498abf8d))
* **picker:** pass selected value to handler on dismiss ([#20042](https://github.com/ionic-team/ionic/issues/20042)) ([6e0b9c4](https://github.com/ionic-team/ionic/commit/6e0b9c45489889266620ee2ca38c33fdf8ce3f3b)), closes [#20036](https://github.com/ionic-team/ionic/issues/20036)
* **tabs:** preserve route navigation extras when changing tabs ([#18493](https://github.com/ionic-team/ionic/issues/18493)) ([4c8f32f](https://github.com/ionic-team/ionic/commit/4c8f32fae99db4022aa9dc75187e2f161e8e678e)), closes [#18717](https://github.com/ionic-team/ionic/issues/18717)
* **title:** add correct safe area to large title nav transition ([#20029](https://github.com/ionic-team/ionic/issues/20029)) ([300d543](https://github.com/ionic-team/ionic/commit/300d54356df925bb94f22b6805e48c88d1e56a26)), closes [#20028](https://github.com/ionic-team/ionic/issues/20028)
### Features
* **modal:** add card-style presentation with swipe to close gesture ([#19428](https://github.com/ionic-team/ionic/issues/19428)) ([b3b3312](https://github.com/ionic-team/ionic/commit/b3b33127115bb966980a1288a0005dfb09306881)), closes [#18660](https://github.com/ionic-team/ionic/issues/18660)
## [4.11.6](https://github.com/ionic-team/ionic/compare/v4.11.5...v4.11.6) (2019-12-11)
### Bug Fixes
* **react:** don't show back button when not appropriate ([684293d](https://github.com/ionic-team/ionic/commit/684293ddbf1ad4edce590d56f7ff66fcd6c817a5))
* **react:** first render performance improvements ([1c7d1e5](https://github.com/ionic-team/ionic/commit/1c7d1e5cf1ad7e53ebbee2566e8fa89f567f7fb5))
* **react:** fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes [#19924](https://github.com/ionic-team/ionic/issues/19924) ([#20012](https://github.com/ionic-team/ionic/issues/20012)) ([eef55bb](https://github.com/ionic-team/ionic/commit/eef55bb0072a9e54b1fd7d1c8c69e7fd43b2a5c5))
* **react:** support for 'root' router direction, fixes [#19982](https://github.com/ionic-team/ionic/issues/19982) ([#20052](https://github.com/ionic-team/ionic/issues/20052)) ([e116712](https://github.com/ionic-team/ionic/commit/e1167122758b23221935e897bcd65839b75c59aa))
* **react:** support navigating to same page and route updates in IonRouterOutlet, fixes [#19891](https://github.com/ionic-team/ionic/issues/19891), [#19892](https://github.com/ionic-team/ionic/issues/19892), [#19986](https://github.com/ionic-team/ionic/issues/19986) ([f9bf8db](https://github.com/ionic-team/ionic/commit/f9bf8dbe6f952ee53b6b213a4c0d043d25f49b93))
### Upgrade Note
If you run into a "Property 'translate' is missing in type" error building after updating to 4.11.6, update your React Typings library to the latest:
npm i @types/react@latest @types/react-dom@latest
# [5.0.0-beta.1](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2019-11-20)
### Bug Fixes
* **animation:** track correctly when updating CSS Animation ([#19813](https://github.com/ionic-team/ionic/issues/19813)) ([7bd4412](https://github.com/ionic-team/ionic/commit/7bd44128895b9fa4992142c0cc30cf75092cb794))
* **card:** update background to use the same as item ([#19602](https://github.com/ionic-team/ionic/issues/19602)) ([1a8b7a4](https://github.com/ionic-team/ionic/commit/1a8b7a4559860b3efa4778a78c905e30f18587bf))
* **content:** set fixed content to position absolute ([#19867](https://github.com/ionic-team/ionic/issues/19867)) ([fce3e24](https://github.com/ionic-team/ionic/commit/fce3e24600be6f04b285cda62fe2f21c49d809e2)), closes [#17754](https://github.com/ionic-team/ionic/issues/17754)
* **gesture:** release gesture when disabling ([#19855](https://github.com/ionic-team/ionic/issues/19855)) ([21484f1](https://github.com/ionic-team/ionic/commit/21484f1f3a9ebe46096c979fe3f2035892a53a62)), closes [#19848](https://github.com/ionic-team/ionic/issues/19848)
* **header:** avoid flicker on collapsible header load ([#19682](https://github.com/ionic-team/ionic/issues/19682)) ([0a7aae2](https://github.com/ionic-team/ionic/commit/0a7aae28a7eb0270cdcd100933c01850403b66db))
* **header:** avoid flicker when collapsing ([#19850](https://github.com/ionic-team/ionic/issues/19850)) ([a3666dd](https://github.com/ionic-team/ionic/commit/a3666ddf0ccc44c696121a8d6107015dbe7aeabb)), closes [#19839](https://github.com/ionic-team/ionic/issues/19839)
* **header:** support collapsible header with multiple toolbars ([#19909](https://github.com/ionic-team/ionic/issues/19909)) ([fc4bb2d](https://github.com/ionic-team/ionic/commit/fc4bb2db5c5715841347135bdfa1bf66718d647d))
* **header:** translucent toolbars now work with collapsible header ([#19774](https://github.com/ionic-team/ionic/issues/19774)) ([b642b53](https://github.com/ionic-team/ionic/commit/b642b532e8846042f1317dc936191d0934b23945)), closes [#19773](https://github.com/ionic-team/ionic/issues/19773)
* **title:** only animate large title if back button is in start slot ([#19846](https://github.com/ionic-team/ionic/issues/19846)) ([cace1b3](https://github.com/ionic-team/ionic/commit/cace1b357e5acd54d49f2b662ecee5de90add708)), closes [#19840](https://github.com/ionic-team/ionic/issues/19840)
* **nav-params:** set generic type on navigation parameters get() ([#19195](https://github.com/ionic-team/ionic/issues/19195)) ([504051d](https://github.com/ionic-team/ionic/commit/504051d709c8afe08d588747866d2ee924baf804))
* **picker:** pass data and role to dismiss ([#19787](https://github.com/ionic-team/ionic/issues/19787)) ([7988720](https://github.com/ionic-team/ionic/commit/7988720b1cf46a651d9c140f0fe95726d3feb48c)), closes [#18454](https://github.com/ionic-team/ionic/issues/18454)
* **searchbar:** use back button config value for cancel icon ([#19353](https://github.com/ionic-team/ionic/issues/19353)) ([3d6f3b9](https://github.com/ionic-team/ionic/commit/ed6f3b9f3f42ef85f3f2d083fa7fe37a69b491c8))
* **textarea:** remove padding from textarea placeholder ([#19694](https://github.com/ionic-team/ionic/issues/19694)) ([f63d37a](https://github.com/ionic-team/ionic/commit/f63d37a4c506801a19b9bf6e5ef05d415d680b0c)), closes [#19616](https://github.com/ionic-team/ionic/issues/19616)
* **toast:** call button handler on cancel ([#19793](https://github.com/ionic-team/ionic/issues/19793)) ([420aa66](https://github.com/ionic-team/ionic/commit/420aa6639214e7d2e7b7413e699ace3d7fd35e40)), closes [#19791](https://github.com/ionic-team/ionic/issues/19791)
### Features
* **animation:** animation identifiers ([#19771](https://github.com/ionic-team/ionic/issues/19771)) ([7d41715](https://github.com/ionic-team/ionic/commit/7d417154c5f1ba89e0a30084807ff7e164dd6eba)), closes [#19550](https://github.com/ionic-team/ionic/issues/19550)
* **animation:** cubic-bezier easing conversion utility (experimental) ([#19788](https://github.com/ionic-team/ionic/issues/19788)) ([96a5e60](https://github.com/ionic-team/ionic/commit/96a5e600e563489d93a26d5956d210f246f6fea5)), closes [#19789](https://github.com/ionic-team/ionic/issues/19789)
* **alert:** add support for textarea inputs ([#16851](https://github.com/ionic-team/ionic/issues/16851)) ([b28cf02](https://github.com/ionic-team/ionic/commit/b28cf02ef3979c844c498a8e30ee977937984828)), closes [#14153](https://github.com/ionic-team/ionic/issues/14153)
* **angular:** expose Ionic Animations via AnimationController ([#19745](https://github.com/ionic-team/ionic/issues/19745)) ([67a7e23](https://github.com/ionic-team/ionic/commit/67a7e232b9058620653feec5ed99e0ebf22b6620))
* **angular:** expose Ionic Gestures via GestureController ([#19864](https://github.com/ionic-team/ionic/issues/19864)) ([48a7662](https://github.com/ionic-team/ionic/commit/48a766246d08170c709345bba235275eef0bd020))
* **searchbar:** add --box-shadow variable to style searchbar input ([#19838](https://github.com/ionic-team/ionic/issues/19838)) ([1ab7066](https://github.com/ionic-team/ionic/commit/1ab7066aa085bc0185a6dd3d162439f7f82415fa))
* **select:** add --placeholder-opacity and --placeholder-color, expose shadow parts ([#19893](https://github.com/ionic-team/ionic/issues/19893)) ([bef0f53](https://github.com/ionic-team/ionic/commit/bef0f53d0dcb15c58221c2dec8c4c274d3b5c77e)), closes [#17446](https://github.com/ionic-team/ionic/issues/17446)
* **split-pane:** convert to shadow component, add width, max-width, and min-width vars ([#19754](https://github.com/ionic-team/ionic/issues/19754)) ([d80f455](https://github.com/ionic-team/ionic/commit/d80f45516d5dca62b77db1773206ef274d42f3ef)), closes [#17088](https://github.com/ionic-team/ionic/issues/17088)
### Breaking Changes
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
* **back-button:** convert back button to shadow ([#19411](https://github.com/ionic-team/ionic/pull/19411)) ([0d40d3f](https://github.com/ionic-team/ionic/commit/0d40d3f3b72aac7932ac71e6573d8bbb65a01515))
* **ionicons:** update to Ionicons v5. See https://ionicons.com for more information. ([#19670](https://github.com/ionic-team/ionic/pull/19670)) ([69e10de](https://github.com/ionic-team/ionic/commit/69e10de718dcba4d43e82bd37aeacd2585dd9a79))
* **list-header:** redesign list header to match latest iOS spec ([#19915](https://github.com/ionic-team/ionic/issues/19915)) ([5bbb95f](https://github.com/ionic-team/ionic/commit/5bbb95fae1e371021d6a0edc17fbb021a598b285))
* **split-pane:** convert split-pane to shadow ([#19754](https://github.com/ionic-team/ionic/issues/19754)) ([d80f455](https://github.com/ionic-team/ionic/commit/d80f45516d5dca62b77db1773206ef274d42f3ef))
## [4.11.5](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.5) (2019-11-14)
### Bug Fixes
* **react:** improved lifecycle hooks to deal with stale closures, fixes [#19873](https://github.com/ionic-team/ionic/issues/19873) ([#19874](https://github.com/ionic-team/ionic/issues/19874)) ([5ff786a](https://github.com/ionic-team/ionic/commit/5ff786a23d5aa32281bbf5daaa7f8156de39caca))
## [4.11.4](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.4) (2019-11-07)
### Bug Fixes
* **react:** check for component unmount, fixes [#19859](https://github.com/ionic-team/ionic/issues/19859) ([7356c40](https://github.com/ionic-team/ionic/commit/7356c401742ce2b3241d6ab05fce0fa65d2f1f8a))
* **react:** adding multiple subscriptions to lifecycle events, fixes [#19792](https://github.com/ionic-team/ionic/issues/19792) ([#19858](https://github.com/ionic-team/ionic/issues/19858)) ([0a3014d](https://github.com/ionic-team/ionic/commit/0a3014d35e2102570fd3d8c5ada29eb01aab18e9))
* **react:** add check to warn if no ionpage is found, fixes [#19832](https://github.com/ionic-team/ionic/issues/19832) ([#19857](https://github.com/ionic-team/ionic/issues/19857)) ([628e766](https://github.com/ionic-team/ionic/commit/628e76668ea72baebdb02b9dcfe24c0da837fb08))
* **react:** expand the location stack to better support back button, fixes [#19748](https://github.com/ionic-team/ionic/issues/19748) ([#19856](https://github.com/ionic-team/ionic/issues/19856)) ([d89508b](https://github.com/ionic-team/ionic/commit/d89508b1b58481d518b89362a8792d05f3f451c9))
* **react:** adding hardware back button support, fixes(19819) ([#19851](https://github.com/ionic-team/ionic/issues/19851)) ([fd9745d](https://github.com/ionic-team/ionic/commit/fd9745ddcddded76d64220838aef0f599bf4352f))
* **react:** adding swipe back functionality and routerOutlet ready improvements, fixes [#19818](https://github.com/ionic-team/ionic/issues/19818) ([#19849](https://github.com/ionic-team/ionic/issues/19849)) ([bcc40c8](https://github.com/ionic-team/ionic/commit/bcc40c8d59b723bbdb1dfd318bfb2219eb8df3cf))
* **react:** create a new overlay each time component is presented, fixes [#19841](https://github.com/ionic-team/ionic/issues/19841), [#19823](https://github.com/ionic-team/ionic/issues/19823) ([#19842](https://github.com/ionic-team/ionic/issues/19842)) ([9fad416](https://github.com/ionic-team/ionic/commit/9fad4161be4859969e14d4d33169ef022052d6bf))
## [4.11.3](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.3) (2019-10-30)
### Bug Fixes
* **react:** adding change events to iontabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24e5053a379156e6c3d82c2b5d3afa999f1))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3ce98747d3b37107904ca110daad95e48bc))
* **react:** checking if node is actually an element before treating it like one, fixes [#19769](https://github.com/ionic-team/ionic/issues/19769) ([#19783](https://github.com/ionic-team/ionic/issues/19783)) ([9d0caf6](https://github.com/ionic-team/ionic/commit/9d0caf6de070145c4af618847b27e24c49027b8e))
* **react:** checking isOpen again after async call before opening overlay, fixes [#19755](https://github.com/ionic-team/ionic/issues/19755) ([f70e71a](https://github.com/ionic-team/ionic/commit/f70e71a3d461cdab65626a5a7e1b6f4d03b852b1))
* **react:** don't remove current view, provide a better method to determine showGoBack fixes [#19731](https://github.com/ionic-team/ionic/issues/19731) and [#19732](https://github.com/ionic-team/ionic/issues/19732) ([31c754d](https://github.com/ionic-team/ionic/commit/31c754dab7ada494ff5f0026d5cf3f7f65198eff))
* **react:** removing pages from DOM on nav, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3ae43d8c6b076387a58bca655a56c920bcd))
* **react:** unmount leaving view when using browser back button, fixes [#19749](https://github.com/ionic-team/ionic/issues/19749) ([#19781](https://github.com/ionic-team/ionic/issues/19781)) ([2dc5540](https://github.com/ionic-team/ionic/commit/2dc554091056612f1bcd2751d6eeb41cae488751))
## [4.11.2](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.2) (2019-10-21)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([d97e167](https://github.com/ionic-team/ionic/commit/d97e167))
* **header:** collapsible header works in tabs ([#19658](https://github.com/ionic-team/ionic/issues/19658)) ([4853909](https://github.com/ionic-team/ionic/commit/4853909)), closes [#19640](https://github.com/ionic-team/ionic/issues/19640)
* **ios:** hide leaving view after nav transition to avoid flicker ([#19691](https://github.com/ionic-team/ionic/issues/19691)) ([70e0562](https://github.com/ionic-team/ionic/commit/70e0562)), closes [#19674](https://github.com/ionic-team/ionic/issues/19674)
* **menu:** clamp out of bounds swipe value ([#19684](https://github.com/ionic-team/ionic/issues/19684)) ([1535e95](https://github.com/ionic-team/ionic/commit/1535e95)), closes [#18927](https://github.com/ionic-team/ionic/issues/18927)
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** adding change events to IonTabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3c))
* **react:** pages no longer hidden when navigating between tabs, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** ensure views are removed from DOM after navigating back, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3a))
# [5.0.0-beta.0](https://github.com/ionic-team/ionic/compare/v4.11.1...v5.0.0-beta.0) (2019-10-15)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([2f88237](https://github.com/ionic-team/ionic/commit/2f882373bf08ce6ff7ec2ffb36b73e94c20881ca))
* **content:** set overscroll only on iOS ([#19470](https://github.com/ionic-team/ionic/issues/19470)) ([63c2008](https://github.com/ionic-team/ionic/commit/63c2008a86de19847677fda7b9fedce73ed7669f)), closes [#19465](https://github.com/ionic-team/ionic/issues/19465)
* **css:** update responsive display media queries ([#18601](https://github.com/ionic-team/ionic/issues/18601)) ([5d6e077](https://github.com/ionic-team/ionic/commit/5d6e077067c269d1589e7432e5074af6fc64b2fb)), closes [#18600](https://github.com/ionic-team/ionic/issues/18600)
* **grid:** remove padding on children columns when grid has ion-no-padding ([#19592](https://github.com/ionic-team/ionic/issues/19592)) ([17119f5](https://github.com/ionic-team/ionic/commit/17119f59cf525b90b966e99117137512dbd397b4)), closes [#17459](https://github.com/ionic-team/ionic/issues/17459)
* **searchbar:** update alignment of chips and other elements in toolbar ([#19596](https://github.com/ionic-team/ionic/issues/19596)) ([637f26b](https://github.com/ionic-team/ionic/commit/637f26b3642a266b6ef3b9d3d71b7327a5d3cc37)), closes [#19495](https://github.com/ionic-team/ionic/issues/19495) [#19502](https://github.com/ionic-team/ionic/issues/19502)
* **toast:** inherit height in container to center align content ([#19409](https://github.com/ionic-team/ionic/issues/19409)) ([250718a](https://github.com/ionic-team/ionic/commit/250718a40f159fbd99f96d54c11e9a6aea080f04))
### Features
* **components:** cascade mode from parent to child components ([#19369](https://github.com/ionic-team/ionic/issues/19369)) ([3dd5f05](https://github.com/ionic-team/ionic/commit/3dd5f057604dcae7b017accdd37f4f3518a1f113)), closes [#18285](https://github.com/ionic-team/ionic/issues/18285)
* **menu:** default to overlay for ios menu ([#19063](https://github.com/ionic-team/ionic/issues/19063)) ([dbf6a44](https://github.com/ionic-team/ionic/commit/dbf6a448ff3539fda2f182e00fa21435fcd60493)), closes [#18662](https://github.com/ionic-team/ionic/issues/18662)
* **overlays:** add global backdrop opacity variable for animations ([#19533](https://github.com/ionic-team/ionic/issues/19533)) ([bd22926](https://github.com/ionic-team/ionic/commit/bd22926c4995b2d953a08aac7125241929f78f9e)), closes [#16446](https://github.com/ionic-team/ionic/issues/16446)
### Performance Improvements
* **animations:** do not create setTimeout if infinite iterations ([#19632](https://github.com/ionic-team/ionic/issues/19632)) ([0d699fb](https://github.com/ionic-team/ionic/commit/0d699fb2e40b1481d8a63457dce3c58fa45976a3)), closes [#19627](https://github.com/ionic-team/ionic/issues/19627)
* **animations:** wrap loops in requestAnimationFrame call ([#19630](https://github.com/ionic-team/ionic/issues/19630)) ([589e67e](https://github.com/ionic-team/ionic/commit/589e67e4af3c2c5e42984f82cbd83d0246029535)), closes [#19629](https://github.com/ionic-team/ionic/issues/19629)
### Breaking Changes
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app [in the developer console](https://javascript.info/devtools).
* **all:** mode is now cascaded from parent to child component. If this is not desired set a different mode on the child component. ([#19369](https://github.com/ionic-team/ionic/issues/19369)) ([55462d7](https://github.com/ionic-team/ionic/commit/55462d7a0935f57b6855f9bd1bf788bfcf532bc3))
* **anchor:** remove `ion-anchor`, use `ion-router-link` instead. ([#18935](https://github.com/ionic-team/ionic/issues/18935)) ([e7cd197](https://github.com/ionic-team/ionic/commit/e7cd197af79cdf87f04bc769e0367c7e93c0aa0b))
* **card:** convert card to shadow. ([#19395](https://github.com/ionic-team/ionic/issues/19395)) ([08bb60d](https://github.com/ionic-team/ionic/commit/08bb60dcbba3140bb2da64bb74217af8a36a266d))
* **css:** responsive display media queries in the display CSS file have been updated. Instead of using the maximum value of that breakpoint (for `.ion-hide-{breakpoint}-down` classes) the maximum of the media query will be the minimum of that breakpoint. ([#18601](https://github.com/ionic-team/ionic/issues/18601)) ([40a8bff](https://github.com/ionic-team/ionic/commit/40a8bffcd54882300906243781abc12776c61ca6))
* **css:** remove all CSS utility attributes. Use CSS classes instead. See the documentation for the correct class names: https://ionicframework.com/docs/layout/css-utilities ([#18956](https://github.com/ionic-team/ionic/issues/18956)) ([04862df](https://github.com/ionic-team/ionic/commit/04862df7e1029a4e6c188536cb573ddfd57e9c85))
BEFORE:
```html
<ion-header text-center></ion-header>
<ion-content padding></ion-content>
<ion-label text-wrap></ion-label>
<ion-item wrap></ion-item>
```
AFTER:
```html
<ion-header class="ion-text-center"></ion-header>
<ion-content class="ion-padding"></ion-content>
<ion-label class="ion-text-wrap"></ion-label>
<ion-item class="ion-wrap"></ion-item>
```
* **events:** remove the Events service. ([#19600](https://github.com/ionic-team/ionic/issues/19600)) ([8d4a721](https://github.com/ionic-team/ionic/commit/8d4a721c6675c13d1f84c455bb222af60be34312))
- Use "Observables" for a similar pub/sub architecture: https://angular.io/guide/observables
- Use "Redux" for advanced state management: https://ngrx.io
* **header/footer:** remove `no-border` attribute from header/footer, use `ion-no-border` class instead. ([#18954](https://github.com/ionic-team/ionic/issues/18954)) ([d9f6119](https://github.com/ionic-team/ionic/commit/d9f61197ffc62af10243a6719cc08200fd57ff8b))
* **menu:** iOS menu now defaults to overlay, set `type` to `"reveal"` to get the old behavior. ([#19063](https://github.com/ionic-team/ionic/issues/19063)) ([ccb54a1](https://github.com/ionic-team/ionic/commit/ccb54a1255ca2a303c27e5a0cf68f4e3fb86a2fb))
* **menu-controller:** remove `swipeEnable()`, use `swipeGesture()` instead. ([#19526](https://github.com/ionic-team/ionic/issues/19526)) ([30bd8fd](https://github.com/ionic-team/ionic/commit/30bd8fd739974926f0eaadddc33c50b546be4887))
* **nav:** remove `ion-nav-pop`, `ion-nav-push` and `ion-nav-set-root`. Use `ion-nav-link` with `routerDirection` instead. ([#19240](https://github.com/ionic-team/ionic/issues/19240)) ([e334d73](https://github.com/ionic-team/ionic/commit/e334d73e544efddf7335c4ce9a5382f8a34d013e))
* **searchbar:** remove boolean values from `showCancelButton`, use string values: `"always"`, `"focus"`, `"never"`. ([#18953](https://github.com/ionic-team/ionic/issues/18953)) ([508e186](https://github.com/ionic-team/ionic/commit/508e1868ad672ded49ddababd7a506ff1721534d))
BEFORE:
```html
<ion-searchbar show-cancel-button>
<ion-searchbar show-cancel-button="true">
<ion-searchbar show-cancel-button="false">
```
AFTER:
```html
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="never">
```
* **scss:** remove `scss` files from `dist/`, use CSS variables to theme instead. ([#19292](https://github.com/ionic-team/ionic/issues/19292)) ([6450aff](https://github.com/ionic-team/ionic/commit/6450aff080c561c60c140000b89ff340edeeaeca))
* **skeleton-text:** remove `width` property. Use CSS instead. ([#18936](https://github.com/ionic-team/ionic/issues/18936)) ([7c3db79](https://github.com/ionic-team/ionic/commit/7c3db79c23dac599aa8131753b33b1ed3487d776))
* **split-pane:** remove `main` attribute. Use `contentId` instead. ([#19511](https://github.com/ionic-team/ionic/issues/19511)) ([02d7841](https://github.com/ionic-team/ionic/commit/02d784140ce0fc75718781454e8dd8a2f621ee0d))
BEFORE:
```html
<ion-split-pane>
...
<div main>...</div>
</ion-split-pane>
```
AFTER:
```html
<ion-split-pane content-id="main-content">
...
<div id="main-content">...</div>
</ion-split-pane>
```
* **theming:** ionic default colors have been updated. ([#19279](https://github.com/ionic-team/ionic/issues/19279)) ([7f4cf08](https://github.com/ionic-team/ionic/commit/7f4cf08d996f90ae90064a351f8e373ce7a16799))
* **toast:** remove `showCloseButton` and `closeButtonText`, add a button using the `buttons` property with `role: 'cancel'` instead. ([#18957](https://github.com/ionic-team/ionic/issues/18957)) ([ad7f112](https://github.com/ionic-team/ionic/commit/ad7f112e1eb04e41e697936454e63977d1bde34b))
## [4.11.1](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.1) (2019-10-14)
### Bug Fixes
* **build:** properly update peer dependencies ([#19639](https://github.com/ionic-team/ionic/issues/19639)) ([b552daa](https://github.com/ionic-team/ionic/commit/b552daa))
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** handle tab back nav better, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** moving tslint and friends to devDependencies ([#19624](https://github.com/ionic-team/ionic/issues/19624)) ([7f4b77d](https://github.com/ionic-team/ionic/commit/7f4b77d))
# [4.11.0 Sodium](https://github.com/ionic-team/ionic/compare/v4.10.3...v4.11.0) (2019-10-09)
Ionic React! Enjoy! 🧂 🌊 🐼
## [4.10.3](https://github.com/ionic-team/ionic/compare/v4.10.2...v4.10.3) (2019-10-09)
### Bug Fixes
* **content:** set overscroll only on iOS ([#19470](https://github.com/ionic-team/ionic/issues/19470)) ([63c2008](https://github.com/ionic-team/ionic/commit/63c2008a86de19847677fda7b9fedce73ed7669f)), closes [#19465](https://github.com/ionic-team/ionic/issues/19465)
* **searchbar:** update alignment of chips and other elements in toolbar ([#19596](https://github.com/ionic-team/ionic/issues/19596)) ([637f26b](https://github.com/ionic-team/ionic/commit/637f26b3642a266b6ef3b9d3d71b7327a5d3cc37)), closes [#19495](https://github.com/ionic-team/ionic/issues/19495) [#19502](https://github.com/ionic-team/ionic/issues/19502)
## [4.10.2](https://github.com/ionic-team/ionic/compare/v4.10.1...v4.10.2) (2019-10-08)
### Bug Fixes
* **overlay:** ensure lifecycle events fire properly ([#19579](https://github.com/ionic-team/ionic/issues/19579)) ([a7b9642](https://github.com/ionic-team/ionic/commit/a7b9642)), closes [#19576](https://github.com/ionic-team/ionic/issues/19576)
## [4.10.1](https://github.com/ionic-team/ionic/compare/v4.10.0...v4.10.1) (2019-10-07)
### Bug Fixes
* **animation:** animation timer fallback now accounts for iterations ([#19527](https://github.com/ionic-team/ionic/issues/19527)) ([9f5ed23](https://github.com/ionic-team/ionic/commit/9f5ed23))
* **css:** update deprecations to remove wrap ([#19535](https://github.com/ionic-team/ionic/issues/19535)) ([bfb704e](https://github.com/ionic-team/ionic/commit/bfb704e)), closes [#19499](https://github.com/ionic-team/ionic/issues/19499)
* **header:** fix collapsing iOS header when using with split pane ([#19480](https://github.com/ionic-team/ionic/issues/19480)) ([dea1c26](https://github.com/ionic-team/ionic/commit/dea1c26)), closes [#19541](https://github.com/ionic-team/ionic/issues/19541)
* **list:** add bottom border for first item in inset list ([#19525](https://github.com/ionic-team/ionic/issues/19525)) ([71b8853](https://github.com/ionic-team/ionic/commit/71b8853)), closes [#19507](https://github.com/ionic-team/ionic/issues/19507)
* **md:** fix flicker when navigating back in MD mode on certain Android devices ([#19553](https://github.com/ionic-team/ionic/issues/19553)) ([19ee21a](https://github.com/ionic-team/ionic/commit/19ee21a)), closes [#19491](https://github.com/ionic-team/ionic/issues/19491)
* **searchbar:** update padding and button alignment ([#19532](https://github.com/ionic-team/ionic/issues/19532)) ([77658e6](https://github.com/ionic-team/ionic/commit/77658e6)), closes [#19502](https://github.com/ionic-team/ionic/issues/19502)
# [4.10.0 Neon](https://github.com/ionic-team/ionic/compare/v4.9.1...v4.10.0) (2019-09-27)
### Bug Fixes
* **angular:** add warning if initialized more than once ([#19393](https://github.com/ionic-team/ionic/issues/19393)) ([e98769e](https://github.com/ionic-team/ionic/commit/e98769e))
* **animation:** set property defaults to avoid inconsistencies ([#19321](https://github.com/ionic-team/ionic/issues/19321)) ([1cbb52c](https://github.com/ionic-team/ionic/commit/1cbb52c))
* **animation:** fallback to CSS Animations on older versions of Chrome ([#19288](https://github.com/ionic-team/ionic/issues/19288)) ([2d39c07](https://github.com/ionic-team/ionic/commit/2d39c07)), closes [#19272](https://github.com/ionic-team/ionic/issues/19272)
* **animation:** animations of duration 0 now run in Safari ([#19287](https://github.com/ionic-team/ionic/issues/19287)) ([4e544f1](https://github.com/ionic-team/ionic/commit/4e544f1)), closes [#19285](https://github.com/ionic-team/ionic/issues/19285)
* **components:** fix crash in IE11 when using classList add() or remove() ([#19460](https://github.com/ionic-team/ionic/issues/19460)) ([b4d92c6](https://github.com/ionic-team/ionic/commit/b4d92c6))
* **components:** improve CSS Variable support in IE11 ([#19473](https://github.com/ionic-team/ionic/issues/19473)) ([44ad074](https://github.com/ionic-team/ionic/commit/44ad074))
* **content:** remove pointer-events from iOS transition shadow ([#19471](https://github.com/ionic-team/ionic/issues/19471)) ([8a52c7d](https://github.com/ionic-team/ionic/commit/8a52c7d)), closes [#19466](https://github.com/ionic-team/ionic/issues/19466)
* **menu:** menus show proper drop shadows ([#19454](https://github.com/ionic-team/ionic/issues/19454)) ([eab0865](https://github.com/ionic-team/ionic/commit/eab0865)), closes [#19387](https://github.com/ionic-team/ionic/issues/19387)
* **radio-group:** get radios before caching value to avoid infinite loop ([#19448](https://github.com/ionic-team/ionic/issues/19448)) ([cf223e4](https://github.com/ionic-team/ionic/commit/cf223e4)), closes [#19277](https://github.com/ionic-team/ionic/issues/19277)
* **react:** update events to use proper types ([c79e74b](https://github.com/ionic-team/ionic/commit/c79e74b))
* **router-outlet:** hide leaving view after transition finishes ([#19335](https://github.com/ionic-team/ionic/issues/19335)) ([bfa17d1](https://github.com/ionic-team/ionic/commit/bfa17d1))
### Features
* **config:** expose getMode() and deprecate Config.set() ([#19104](https://github.com/ionic-team/ionic/issues/19104)) ([0f05ea4](https://github.com/ionic-team/ionic/commit/0f05ea4))
* **docs:** add VSCode docs support ([#19309](https://github.com/ionic-team/ionic/issues/19309)) ([a3f345c](https://github.com/ionic-team/ionic/commit/a3f345c))
* **title:** add support for small title ([#19215](https://github.com/ionic-team/ionic/issues/19215)) ([e27962d](https://github.com/ionic-team/ionic/commit/e27962d)), closes [#18898](https://github.com/ionic-team/ionic/issues/18898)
* **title:** add support for large title (experimental) ([#19268](https://github.com/ionic-team/ionic/issues/19268)) ([923312e](https://github.com/ionic-team/ionic/commit/923312e)), closes [#16885](https://github.com/ionic-team/ionic/issues/16885)
### Performance Improvements
* **animation:** avoid ngzone with requestAnimationFrame ([#19457](https://github.com/ionic-team/ionic/issues/19457)) ([8ca97ce](https://github.com/ionic-team/ionic/commit/8ca97ce))
## [4.9.1](https://github.com/ionic-team/ionic/compare/v4.9.0...v4.9.1) (2019-09-16)
### Bug Fixes
* **platform:** properly detect iPads running iPadOS ([#19258](https://github.com/ionic-team/ionic/issues/19258)) ([4a90096](https://github.com/ionic-team/ionic/commit/4a90096))
# [4.9.0 Fluorine](https://github.com/ionic-team/ionic/compare/v4.8.1...v4.9.0) (2019-09-04)
### Bug Fixes
* **all:** allow elements to be reused once removed from the DOM ([#18963](https://github.com/ionic-team/ionic/pull/18963)) ([48a2763](https://github.com/ionic-team/ionic/commit/48a2763)), closes [#18843](https://github.com/ionic-team/ionic/issues/18843) [#17344](https://github.com/ionic-team/ionic/issues/17344) [#16453](https://github.com/ionic-team/ionic/issues/16453) [#15879](https://github.com/ionic-team/ionic/issues/15879) [#15788](https://github.com/ionic-team/ionic/issues/15788) [#15484](https://github.com/ionic-team/ionic/issues/15484) [#17890](https://github.com/ionic-team/ionic/issues/17890) [#16364](https://github.com/ionic-team/ionic/issues/16364)
* **animation:** add correct `onFinish` interface parameters ([#19199](https://github.com/ionic-team/ionic/issues/19199)) ([a81653b](https://github.com/ionic-team/ionic/commit/a81653b))
* **animation:** improve Web Animation feature detection accuracy ([#19212](https://github.com/ionic-team/ionic/issues/19212)) ([6eca5b0](https://github.com/ionic-team/ionic/commit/6eca5b0)), closes [#19205](https://github.com/ionic-team/ionic/issues/19205)
* **animation:** properly clean up elements when using `destroy` ([#19210](https://github.com/ionic-team/ionic/issues/19210)) ([93f2064](https://github.com/ionic-team/ionic/commit/93f2064))
* **segment:** do not emit ionChange until didLoad ([#19218](https://github.com/ionic-team/ionic/issues/19218)) ([9751f14](https://github.com/ionic-team/ionic/commit/9751f14)), closes [#19204](https://github.com/ionic-team/ionic/issues/19204)
### Features
* **nav-link:** add `nav-link` and deprecate `nav-push`, `nav-pop`, and `nav-set-root` ([#18909](https://github.com/ionic-team/ionic/issues/18909)) ([c3044f5](https://github.com/ionic-team/ionic/commit/c3044f5))
* **slides:** expose full Swiper API ([#19137](https://github.com/ionic-team/ionic/issues/19137)) ([e1fa461](https://github.com/ionic-team/ionic/commit/e1fa461))
## [4.8.1](https://github.com/ionic-team/ionic/compare/v4.8.0...v4.8.1) (2019-08-27)
### Bug Fixes
* **animation:** enable backwards compatibility for overlay animations ([#19160](https://github.com/ionic-team/ionic/issues/19160)) ([fb70980](https://github.com/ionic-team/ionic/commit/fb70980))
* **gesture:** account for negative step values with Web Animations ([#19196](https://github.com/ionic-team/ionic/issues/19196)) ([b3c7436](https://github.com/ionic-team/ionic/commit/b3c7436))
* **ios:** clear opacity on toolbar background after iOS transition ([#19169](https://github.com/ionic-team/ionic/issues/19169)) ([fa958a5](https://github.com/ionic-team/ionic/commit/fa958a5))
* **md:** set fill mode on MD transition to `both` ([#19161](https://github.com/ionic-team/ionic/issues/19161)) ([0e8ab49](https://github.com/ionic-team/ionic/commit/0e8ab49))
# [4.8.0 Oxygen](https://github.com/ionic-team/ionic/compare/v4.7.4...v4.8.0) (2019-08-21)
### Bug Fixes
* **alert:** apply styling to disabled items ([#18545](https://github.com/ionic-team/ionic/issues/18545)) ([67ed89d](https://github.com/ionic-team/ionic/commit/67ed89d))
* **platform:** properly detect Electron platform ([#19044](https://github.com/ionic-team/ionic/issues/19044)) ([e8cdda0](https://github.com/ionic-team/ionic/commit/e8cdda0)), closes [#19043](https://github.com/ionic-team/ionic/issues/19043)
* **toggle:** change background to use CSS variable ([#19012](https://github.com/ionic-team/ionic/issues/19012)) ([94e525c](https://github.com/ionic-team/ionic/commit/94e525c)), closes [#18940](https://github.com/ionic-team/ionic/issues/18940)
* **transition:** enable iOS transition shadow by default ([#19051](https://github.com/ionic-team/ionic/issues/19051)) ([a5d3c6b](https://github.com/ionic-team/ionic/commit/a5d3c6b))
### Features
* **animation:** add animation utility (experimental) ([#18918](https://github.com/ionic-team/ionic/issues/18918)) ([30ca46a](https://github.com/ionic-team/ionic/commit/30ca46a))
* **gesture:** add gesture utility (experimental) ([#18918](https://github.com/ionic-team/ionic/issues/18918)) ([30ca46a](https://github.com/ionic-team/ionic/commit/30ca46a))
* **searchbar:** add `inputmode` property ([#18980](https://github.com/ionic-team/ionic/issues/18980)) ([1187dc2](https://github.com/ionic-team/ionic/commit/1187dc2))
* **spinner:** add circular spinner for MD default ([#19052](https://github.com/ionic-team/ionic/issues/19052)) ([e33cf85](https://github.com/ionic-team/ionic/commit/e33cf85))
## [4.7.4](https://github.com/ionic-team/ionic/compare/v4.7.3...v4.7.4) (2019-08-07)
### Bug Fixes
* **core:** remove extra semicolons being rendered ([#19033](https://github.com/ionic-team/ionic/issues/19033)) ([39f0768](https://github.com/ionic-team/ionic/commit/39f0768))
## [4.7.3](https://github.com/ionic-team/ionic/compare/v4.7.2...v4.7.3) (2019-08-07)
* **core:** fix an issue with production builds of `@ionic/core`
## [4.7.2](https://github.com/ionic-team/ionic/compare/v4.7.1...v4.7.2) (2019-08-07)
### Bug Fixes
* **angular:** hardware back button subscribeWithPriority triggers change detection ([#18962](https://github.com/ionic-team/ionic/issues/18962)) ([3a22105](https://github.com/ionic-team/ionic/commit/3a22105)), closes [#18959](https://github.com/ionic-team/ionic/issues/18959)
* **angular:** nested inputs no longer conflict with each other ([#18976](https://github.com/ionic-team/ionic/issues/18976)) ([6bbdb80](https://github.com/ionic-team/ionic/commit/6bbdb80)), closes [#18248](https://github.com/ionic-team/ionic/issues/18248)
* **range:** ion-range value now submitted with form ([#19008](https://github.com/ionic-team/ionic/issues/19008)) ([8f7853c](https://github.com/ionic-team/ionic/commit/8f7853c))
* **reorder:** only move item if reorder happens ([#19007](https://github.com/ionic-team/ionic/issues/19007)) ([d237e80](https://github.com/ionic-team/ionic/commit/d237e80))
* **router:** partial swipe to go back gesture no longer breaks view([#18977](https://github.com/ionic-team/ionic/issues/18977)) ([713ea8a](https://github.com/ionic-team/ionic/commit/713ea8a)), closes [#18462](https://github.com/ionic-team/ionic/issues/18462)
* **toast:** allow loading ion-icon from asset path ([#18969](https://github.com/ionic-team/ionic/issues/18969)) ([23f327e](https://github.com/ionic-team/ionic/commit/23f327e))
* **vue:** rename swipeEnable to swipeGesture ([#17346](https://github.com/ionic-team/ionic/issues/17346)) ([c2348f7](https://github.com/ionic-team/ionic/commit/c2348f7)), closes [#16002](https://github.com/ionic-team/ionic/issues/16002)
## [4.7.1](https://github.com/ionic-team/ionic/compare/v4.7.0...v4.7.1) (2019-07-26)
### Bug Fixes
* **angular:** ensure change detection fires properly ([#18896](https://github.com/ionic-team/ionic/issues/18896)) ([962783b](https://github.com/ionic-team/ionic/commit/962783b)), closes [#18894](https://github.com/ionic-team/ionic/issues/18894)
# [4.7.0 Nitrogen](https://github.com/ionic-team/ionic/compare/v4.6.2...v4.7.0) (2019-07-24)
### Angular 8 Support
With this version comes support for Angular 8! Follow the below steps to update.
1. Update `@ionic/angular` and `@ionic/angular-toolkit` to the latest releases:
```shell
$ npm install @ionic/angular@4.7.0
$ npm install @ionic/angular-toolkit@2.0.0 -D
```
2. Update `@angular/core` and `@angular/cli`:
```shell
$ npx ng update @angular/core @angular/cli
```
3. Update `@angular-devkit` dependencies:
```shell
$ npm i @angular-devkit/architect@latest @angular-devkit/build-angular@latest @angular-devkit/core@latest @angular-devkit/schematics@latest
```
View our [Angular 8 Update Guide](https://docs.google.com/document/d/1QOpQeDifPSg6F9WycDLcbQnpqjN96ew-Ap0_CB7CcCQ/edit?usp=sharing) for tips on potential issues!
### Bug Fixes
* **angular:** copy input form classes to parent ion-item ([#18820](https://github.com/ionic-team/ionic/issues/18820)) ([53179c4](https://github.com/ionic-team/ionic/commit/53179c4)), closes [#18800](https://github.com/ionic-team/ionic/issues/18800)
* **angular:** add the swipeGesture method for enabling or disabling the ability to swipe open a menu ([#18806](https://github.com/ionic-team/ionic/issues/18806)) ([fbfc076](https://github.com/ionic-team/ionic/commit/fbfc076)), closes [#16002](https://github.com/ionic-team/ionic/issues/16002)
* **angular:** webview "pause", "resume", and "resize" events now trigger change detection ([#18853](https://github.com/ionic-team/ionic/issues/18853)) ([544e550](https://github.com/ionic-team/ionic/commit/544e550)), closes [#18831](https://github.com/ionic-team/ionic/issues/18831)
* **core:** apply translucent if backdrop-filter is supported ([#18832](https://github.com/ionic-team/ionic/issues/18832)) ([6b5a59d](https://github.com/ionic-team/ionic/commit/6b5a59d)), closes [ionic-team/ionic-docs#666](https://github.com/ionic-team/ionic-docs/issues/666)
* **datetime:** allow AM/PM to be changed ([#18684](https://github.com/ionic-team/ionic/issues/18684)) ([b7761fe](https://github.com/ionic-team/ionic/commit/b7761fe)), closes [#18585](https://github.com/ionic-team/ionic/issues/18585)
* **datetime:** properly apply disabled classes when updating columns ([#18875](https://github.com/ionic-team/ionic/issues/18875)) ([7ba718c](https://github.com/ionic-team/ionic/commit/7ba718c)), closes [#18793](https://github.com/ionic-team/ionic/issues/18793)
* **hardware-back-button:** hardware back button no longer erroneously restarts app ([#18794](https://github.com/ionic-team/ionic/issues/18794)) ([978cc39](https://github.com/ionic-team/ionic/commit/978cc39)), closes [#18792](https://github.com/ionic-team/ionic/issues/18792)
* **ripple-effect:** ensure ripple is removed from components after pointer release ([#18854](https://github.com/ionic-team/ionic/issues/18854)) ([71137a2](https://github.com/ionic-team/ionic/commit/71137a2)), closes [#18836](https://github.com/ionic-team/ionic/issues/18836)
* **searchbar:** add aria and role for improved accessibility ([#18797](https://github.com/ionic-team/ionic/issues/18797)) ([798103b](https://github.com/ionic-team/ionic/commit/798103b)), closes [#18796](https://github.com/ionic-team/ionic/issues/18796)
* **ssr:** avoid window reference ([#18865](https://github.com/ionic-team/ionic/issues/18865)) ([23ce6fa](https://github.com/ionic-team/ionic/commit/23ce6fa))
* **ssr:** check for client runtime method ([#18866](https://github.com/ionic-team/ionic/issues/18866)) ([c52b3b4](https://github.com/ionic-team/ionic/commit/c52b3b4))
* **textarea:** autogrow now resets textarea back to original number of rows when text is cleared ([#18822](https://github.com/ionic-team/ionic/issues/18822)) ([26e6d6f](https://github.com/ionic-team/ionic/commit/26e6d6f)), closes [#18744](https://github.com/ionic-team/ionic/issues/18744)
* **theming:** update components to use the proper colors for dark themes ([#18735](https://github.com/ionic-team/ionic/issues/18735)) ([045bc59](https://github.com/ionic-team/ionic/commit/045bc59)), closes [#18713](https://github.com/ionic-team/ionic/issues/18713)
* **virtual-scroll:** card rendering is no longer distorted ([#18877](https://github.com/ionic-team/ionic/issues/18877)) ([3ef6ecf](https://github.com/ionic-team/ionic/commit/3ef6ecf)), closes [#18870](https://github.com/ionic-team/ionic/issues/18870)
* **virtual-scroll:** element dimensions are recalculated on resize ([#18878](https://github.com/ionic-team/ionic/issues/18878)) ([c91819c](https://github.com/ionic-team/ionic/commit/c91819c))
### Features
* **core:** add support for Stackblitz ([#18846](https://github.com/ionic-team/ionic/issues/18846)) ([fb18f3b](https://github.com/ionic-team/ionic/commit/fb18f3b))
* **ssr:** add @ionic/core/hydrate app ([#18867](https://github.com/ionic-team/ionic/issues/18867)) ([815fa2e](https://github.com/ionic-team/ionic/commit/815fa2e))
* **navigation:** add experimental shadow to iOS page transitions ([#18695](https://github.com/ionic-team/ionic/issues/18695)) ([9b075ef](https://github.com/ionic-team/ionic/commit/9b075ef)), closes [#18661](https://github.com/ionic-team/ionic/issues/18661)
* **virtual-scroll:** adds headerHeight and footerHeight to help prevent flickering ([#18851](https://github.com/ionic-team/ionic/issues/18851)) ([0089111](https://github.com/ionic-team/ionic/commit/0089111)), closes [#17540](https://github.com/ionic-team/ionic/issues/17540)
### Performance
* **angular:** attach entering view before first change detection and detach leaving page ([#18821](https://github.com/ionic-team/ionic/issues/18821)) ([97fec92](https://github.com/ionic-team/ionic/commit/97fec92))
## [4.6.2](https://github.com/ionic-team/ionic/compare/v4.6.1...v4.6.2) (2019-07-10)
### Bug Fixes
* **menu-button:** hide menu button when auto hide or split pane ([#18702](https://github.com/ionic-team/ionic/issues/18702)) ([24840d4](https://github.com/ionic-team/ionic/commit/24840d4)), closes [#18666](https://github.com/ionic-team/ionic/issues/18666)
* **menu-button:** move font-size to host for easier customization ([#18699](https://github.com/ionic-team/ionic/issues/18699)) ([876ab41](https://github.com/ionic-team/ionic/commit/876ab41)), closes [#18667](https://github.com/ionic-team/ionic/issues/18667)
* **overlays:** fallback to step color if overlay background variable is unset ([#18709](https://github.com/ionic-team/ionic/issues/18709)) ([f16b118](https://github.com/ionic-team/ionic/commit/f16b118)), closes [#18658](https://github.com/ionic-team/ionic/issues/18658)
* **virtual-scroll:** remove runOutsideAngular error ([#18752](https://github.com/ionic-team/ionic/issues/18752)) ([8beeff2](https://github.com/ionic-team/ionic/commit/8beeff2)), closes [#18746](https://github.com/ionic-team/ionic/issues/18746)
* **vue:** update imports for types and ionicons ([f56fea6](https://github.com/ionic-team/ionic/commit/f56fea6)), closes [#18701](https://github.com/ionic-team/ionic/issues/18701)
### Performance Improvements
* **all:** minify better by using arrow functions ([#18730](https://github.com/ionic-team/ionic/issues/18730)) ([03c1d19](https://github.com/ionic-team/ionic/commit/03c1d19))
## [4.6.1](https://github.com/ionic-team/ionic/compare/v4.6.0...v4.6.1) (2019-07-09)
### Bug Fixes
* **app:** add hydrated to hide white screen with multiple ionic dependencies ([#18649](https://github.com/ionic-team/ionic/issues/18649))
* **datetime:** datetime no longer reports having a value if none is set ([#18541](https://github.com/ionic-team/ionic/issues/18541)) ([92e0f98](https://github.com/ionic-team/ionic/commit/92e0f98)), closes [#17979](https://github.com/ionic-team/ionic/issues/17979) [#18540](https://github.com/ionic-team/ionic/issues/18540)
* **fab-button:** set opacity on disabled fab button ([#18685](https://github.com/ionic-team/ionic/issues/18685)) ([6042b39](https://github.com/ionic-team/ionic/commit/6042b39)), closes [#18682](https://github.com/ionic-team/ionic/issues/18682)
* **icon:** load icons properly with baseHref ([#18650](https://github.com/ionic-team/ionic/issues/18650)), ([#18637](https://github.com/ionic-team/ionic/issues/18637))
* **icon:** bind icon name properly ([#18707](https://github.com/ionic-team/ionic/issues/18707))
* **infinite-scroll:** fix scroll listener ([0d58101](https://github.com/ionic-team/ionic/commit/0d58101))
* **item:** do not disable entire item if there are multiple inputs ([#18696](https://github.com/ionic-team/ionic/issues/18696)) ([dfa2b13](https://github.com/ionic-team/ionic/commit/dfa2b13)), closes [#18655](https://github.com/ionic-team/ionic/issues/18655) [#18670](https://github.com/ionic-team/ionic/issues/18670)
* **router-link:** add missing target prop ([#18659](https://github.com/ionic-team/ionic/issues/18659)) ([1f51ab2](https://github.com/ionic-team/ionic/commit/1f51ab2)), closes [#18655](https://github.com/ionic-team/ionic/issues/18655)
* **router-outlet:** fix swipe to go back ([b69fb69](https://github.com/ionic-team/ionic/commit/b69fb69))
* **scss:** copy all scss files ([36a58df](https://github.com/ionic-team/ionic/commit/36a58df))
* **searchbar:** proper styling after navigating ([#18642](https://github.com/ionic-team/ionic/issues/18642))
* **slides:** use correct order for pushing slides dynamically ([#18633](https://github.com/ionic-team/ionic/issues/18633))
* **tabs:** select proper tab by default and do not emit tab change if selectedTab is undefined ([03c834c](https://github.com/ionic-team/ionic/commit/03c834c))
* **overlay:** make create opts optional ([44c88ad](https://github.com/ionic-team/ionic/commit/44c88ad))
### Performance Improvements
* **angular:** skip zone ([e059fc8](https://github.com/ionic-team/ionic/commit/e059fc8))
# [4.6.0 Carbon](https://github.com/ionic-team/ionic/compare/v4.5.0...v4.6.0) (2019-06-26)
### Bug Fixes
* **button:** default opacity for disabled clear buttons ([#18560](https://github.com/ionic-team/ionic/issues/18560)) ([f48dc3d](https://github.com/ionic-team/ionic/commit/f48dc3d)), closes [#18555](https://github.com/ionic-team/ionic/issues/18555)
* **button:** update solid buttons to use tint and shade colors ([#18537](https://github.com/ionic-team/ionic/issues/18537)) ([26ecf2b](https://github.com/ionic-team/ionic/commit/26ecf2b))
* **menu:** change ARIA role from complementary to navigation ([#18330](https://github.com/ionic-team/ionic/issues/18330)) ([9e4346b](https://github.com/ionic-team/ionic/commit/9e4346b)), closes [#18318](https://github.com/ionic-team/ionic/issues/18318)
* **segment:** apply hover properly for segment with color ([#18549](https://github.com/ionic-team/ionic/issues/18549)) ([78e477b](https://github.com/ionic-team/ionic/commit/78e477b))
* **segment:** default ripple to currentColor ([#18547](https://github.com/ionic-team/ionic/issues/18547)) ([832306c](https://github.com/ionic-team/ionic/commit/832306c))
### Features
* **components:** add missing button/a props to components that render them ([#17883](https://github.com/ionic-team/ionic/issues/17883)) ([eca4121](https://github.com/ionic-team/ionic/commit/eca4121)), closes [#16848](https://github.com/ionic-team/ionic/issues/16848) [#16889](https://github.com/ionic-team/ionic/issues/16889)
* **fab-button:** add hover state using tint colors ([#18536](https://github.com/ionic-team/ionic/issues/18536)) ([ad00679](https://github.com/ionic-team/ionic/commit/ad00679)), closes [#17624](https://github.com/ionic-team/ionic/issues/17624)
* **item:** add hover and focused states ([#18606](https://github.com/ionic-team/ionic/issues/18606)) ([8a88dd2](https://github.com/ionic-team/ionic/commit/8a88dd2)), closes [#18279](https://github.com/ionic-team/ionic/issues/18279) [#17624](https://github.com/ionic-team/ionic/issues/17624)
* **router-link:** add router-link and deprecate anchor ([#18620](https://github.com/ionic-team/ionic/issues/18620)) ([d4c7b03](https://github.com/ionic-team/ionic/commit/d4c7b03))
### Enhancements
* **stencil:** update to Stencil One to improve app performance 🎉🎊 ([b40f7d3](https://github.com/ionic-team/ionic/commit/b40f7d3))
# [4.5.0 Boron](https://github.com/ionic-team/ionic/compare/v4.4.2...v4.5.0) (2019-06-12)
### Bug Fixes
* **angular:** ensure all NavigationExtras values are preserved when navigating ([#18468](https://github.com/ionic-team/ionic/issues/18468)) ([7610787](https://github.com/ionic-team/ionic/commit/7610787)), closes [#18469](https://github.com/ionic-team/ionic/issues/18469)
* **button:** set opacity on the host element for disabled button ([#18509](https://github.com/ionic-team/ionic/issues/18509)) ([320719b](https://github.com/ionic-team/ionic/commit/320719b)), closes [#16965](https://github.com/ionic-team/ionic/issues/16965)
* **button:** use correct border-radius on menu and back button ([#18501](https://github.com/ionic-team/ionic/issues/18501)) ([055e125](https://github.com/ionic-team/ionic/commit/055e125)), closes [#17624](https://github.com/ionic-team/ionic/issues/17624)
* **button:** use correct size on a dynamic button in an item ([#18395](https://github.com/ionic-team/ionic/issues/18395)) ([a3e23fc](https://github.com/ionic-team/ionic/commit/a3e23fc)), closes [#18085](https://github.com/ionic-team/ionic/issues/18085)
* **card:** remove white space from bottom of card ([#18328](https://github.com/ionic-team/ionic/issues/18328)) ([d53e7aa](https://github.com/ionic-team/ionic/commit/d53e7aa))
* **content:** prevent ion-searchbar from receiving padding adjustment when keyboard is open ([#18008](https://github.com/ionic-team/ionic/issues/18008)) ([b2290a6](https://github.com/ionic-team/ionic/commit/b2290a6)), closes [#18007](https://github.com/ionic-team/ionic/issues/18007)
* **datetime:** recalculate time columns on change ([#18380](https://github.com/ionic-team/ionic/issues/18380)) ([292b24a](https://github.com/ionic-team/ionic/commit/292b24a))
* **item:** start align the content under stacked and floating labels ([#18379](https://github.com/ionic-team/ionic/issues/18379)) ([f0af707](https://github.com/ionic-team/ionic/commit/f0af707)), closes [#16375](https://github.com/ionic-team/ionic/issues/16375)
* **item:** inherit overflow to allow better customization ([#18502](https://github.com/ionic-team/ionic/issues/18502)) ([8d2a47e](https://github.com/ionic-team/ionic/commit/8d2a47e)), closes [#17670](https://github.com/ionic-team/ionic/issues/17670)
* **item:** use a step color if the activated background is not set ([#18450](https://github.com/ionic-team/ionic/issues/18450)) ([1899c13](https://github.com/ionic-team/ionic/commit/1899c13)), closes [#18449](https://github.com/ionic-team/ionic/issues/18449)
* **item-sliding:** use the correct gesture direction and side for rtl ([#18366](https://github.com/ionic-team/ionic/issues/18366)) ([4545100](https://github.com/ionic-team/ionic/commit/4545100)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **label:** include the ion-text-wrap class styles for larger font ([#18374](https://github.com/ionic-team/ionic/issues/18374)) ([4bba540](https://github.com/ionic-team/ionic/commit/4bba540))
* **platform:** prevent error with Platform.is on Android 4.4 ([#18387](https://github.com/ionic-team/ionic/issues/18387)) ([54bdb36](https://github.com/ionic-team/ionic/commit/54bdb36))
* **react:** ensure element exists in controller before dismissing it ([0fd3e5d](https://github.com/ionic-team/ionic/commit/0fd3e5d))
* **slides:** resolve issue where double tap to zoom was enabled by default ([10de1da](https://github.com/ionic-team/ionic/commit/10de1da)), closes [#18035](https://github.com/ionic-team/ionic/issues/18035)
* **tabs:** allow selection on enter and spacebar press ([#18381](https://github.com/ionic-team/ionic/issues/18381)) ([11cde99](https://github.com/ionic-team/ionic/commit/11cde99)), closes [#18363](https://github.com/ionic-team/ionic/issues/18363)
* **textarea:** inherit white-space for better customization ([#18508](https://github.com/ionic-team/ionic/issues/18508)) ([a583902](https://github.com/ionic-team/ionic/commit/a583902)), closes [#18495](https://github.com/ionic-team/ionic/issues/18495)
* **virtual-scroll:** do not crash with an empty cell list ([#17799](https://github.com/ionic-team/ionic/issues/17799)) ([20c146e](https://github.com/ionic-team/ionic/commit/20c146e))
### Features
* **back-button:** add variables and support for focused and hover states ([#18451](https://github.com/ionic-team/ionic/issues/18451)) ([58672fb](https://github.com/ionic-team/ionic/commit/58672fb)), closes [#18465](https://github.com/ionic-team/ionic/issues/18465)
* **button:** add variables for customizing hover state ([#18499](https://github.com/ionic-team/ionic/issues/18499)) ([5c5934b](https://github.com/ionic-team/ionic/commit/5c5934b)), closes [#17974](https://github.com/ionic-team/ionic/issues/17974)
* **item-divider:** add inner padding CSS variables ([#18490](https://github.com/ionic-team/ionic/issues/18490)) ([35c143a](https://github.com/ionic-team/ionic/commit/35c143a)), closes [#18484](https://github.com/ionic-team/ionic/issues/18484)
* **menu-button:** add variables for hover and focused states ([#18434](https://github.com/ionic-team/ionic/issues/18434)) ([5ba0aa9](https://github.com/ionic-team/ionic/commit/5ba0aa9)), closes [#18279](https://github.com/ionic-team/ionic/issues/18279)
* **searchbar:** add cancel button options ([b959e0b](https://github.com/ionic-team/ionic/commit/b959e0b))
* **toast:** allow html content ([#18423](https://github.com/ionic-team/ionic/issues/18423)) ([c8104a2](https://github.com/ionic-team/ionic/commit/c8104a2))
## [4.4.2](https://github.com/ionic-team/ionic/compare/v4.4.1...v4.4.2) (2019-05-22)
### Bug Fixes
* **angular:** account for query params and fragments within a string when navigating ([#18356](https://github.com/ionic-team/ionic/issues/18356)) ([b79f68a](https://github.com/ionic-team/ionic/commit/b79f68a))
## [4.4.1](https://github.com/ionic-team/ionic/compare/v4.4.0...v4.4.1) (2019-05-22)
### Bug Fixes
* **angular:** ensure active page is not removed from change detection ([#18299](https://github.com/ionic-team/ionic/issues/18299)) ([b8d4961](https://github.com/ionic-team/ionic/commit/b8d4961)), closes [#18293](https://github.com/ionic-team/ionic/issues/18293)
* **angular:** preserve queryParams and fragment when going back ([#18298](https://github.com/ionic-team/ionic/issues/18298)) ([bdd5109](https://github.com/ionic-team/ionic/commit/bdd5109)), closes [#16744](https://github.com/ionic-team/ionic/issues/16744)
* **buttons:** use theme/color toolbar colors for buttons ([#18191](https://github.com/ionic-team/ionic/issues/18191)) ([0511989](https://github.com/ionic-team/ionic/commit/0511989)), closes [#18184](https://github.com/ionic-team/ionic/issues/18184) [#17840](https://github.com/ionic-team/ionic/issues/17840)
* **datetime:** update label direction in RTL ([#18340](https://github.com/ionic-team/ionic/issues/18340)) ([17345ef](https://github.com/ionic-team/ionic/commit/17345ef))
* **fab:** position fab buttons properly in RTL ([#18325](https://github.com/ionic-team/ionic/issues/18325)) ([845def8](https://github.com/ionic-team/ionic/commit/845def8)), references [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **icon:** remove stroke and move fill to host element ([#18241](https://github.com/ionic-team/ionic/issues/18241)) ([394cf8d](https://github.com/ionic-team/ionic/commit/394cf8d)), closes [#16483](https://github.com/ionic-team/ionic/issues/16483)
* **input:** keep entire input in view when scrolling with keyboard open ([#18253](https://github.com/ionic-team/ionic/issues/18253)) ([3cad778](https://github.com/ionic-team/ionic/commit/3cad778)), closes [#17457](https://github.com/ionic-team/ionic/issues/17457)
* **label:** position floating/stacked labels properly in RTL ([#18315](https://github.com/ionic-team/ionic/issues/18315)) ([00a27dc](https://github.com/ionic-team/ionic/commit/00a27dc)), references [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **loading:** allow html content ([#18242](https://github.com/ionic-team/ionic/issues/18242)) ([a6cb5f2](https://github.com/ionic-team/ionic/commit/a6cb5f2)), closes [#18135](https://github.com/ionic-team/ionic/issues/18135)
* **overlay:** hide scrollbars on non-scrollable content ([#16767](https://github.com/ionic-team/ionic/issues/16767)) ([875d563](https://github.com/ionic-team/ionic/commit/875d563)), closes [#14178](https://github.com/ionic-team/ionic/issues/14178)
* **picker:** update the column positions in RTL ([#18339](https://github.com/ionic-team/ionic/issues/18339)) ([788a56c](https://github.com/ionic-team/ionic/commit/788a56c)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **range:** update border-radius on range pin for RTL ([#18321](https://github.com/ionic-team/ionic/issues/18321)) ([4855351](https://github.com/ionic-team/ionic/commit/4855351)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **searchbar:** position buttons properly in RTL ([#18325](https://github.com/ionic-team/ionic/issues/18325)) ([845def8](https://github.com/ionic-team/ionic/commit/845def8)), references [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **segment:** update segment border for RTL ([#18326](https://github.com/ionic-team/ionic/issues/18326)) ([805b225](https://github.com/ionic-team/ionic/commit/805b225)), closes [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **slides:** allow clicks to propagate to buttons ([#16728](https://github.com/ionic-team/ionic/issues/16728)) ([a8f9dfe](https://github.com/ionic-team/ionic/commit/a8f9dfe))
* **tab-button:** apply background-focused when tabbing into tab button ([#17502](https://github.com/ionic-team/ionic/issues/17502)) ([d788a8e](https://github.com/ionic-team/ionic/commit/d788a8e)), closes [#17042](https://github.com/ionic-team/ionic/issues/17042)
* **tabs:** position badge properly in RTL (only in Chrome) ([#18325](https://github.com/ionic-team/ionic/issues/18325)) ([845def8](https://github.com/ionic-team/ionic/commit/845def8)), references [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **tabs:** select the tab called by the select method after initialization ([#18300](https://github.com/ionic-team/ionic/issues/18300)) ([da38647](https://github.com/ionic-team/ionic/commit/da38647)), closes [#17957](https://github.com/ionic-team/ionic/issues/17957)
* **toggle:** position toggle icon properly in RTL ([#18325](https://github.com/ionic-team/ionic/issues/18325)) ([845def8](https://github.com/ionic-team/ionic/commit/845def8)), references [#17012](https://github.com/ionic-team/ionic/issues/17012)
* **toolbar:** update md toolbar button spacing and padding to match spec ([#17537](https://github.com/ionic-team/ionic/issues/17537)) ([fa87e35](https://github.com/ionic-team/ionic/commit/fa87e35)), closes [#16950](https://github.com/ionic-team/ionic/issues/16950) [#14444](https://github.com/ionic-team/ionic/issues/14444)
# [4.4.0 Beryllium](https://github.com/ionic-team/ionic/compare/v4.3.1...v4.4.0) (2019-05-08)
### Bug Fixes
* **button:** apply round property to button sizes in iOS ([#18125](https://github.com/ionic-team/ionic/issues/18125)) ([ae0eccc](https://github.com/ionic-team/ionic/commit/ae0eccc)), closes [#18108](https://github.com/ionic-team/ionic/issues/18108)
* **datetime:** default to current date when value is null ([#18105](https://github.com/ionic-team/ionic/issues/18105)) ([ca233b5](https://github.com/ionic-team/ionic/commit/ca233b5)), closes [#18099](https://github.com/ionic-team/ionic/issues/18099)
* **input:** clear on edit from inside native input ([#17115](https://github.com/ionic-team/ionic/issues/17115)) ([85093d6](https://github.com/ionic-team/ionic/commit/85093d6)), closes [#17055](https://github.com/ionic-team/ionic/issues/17055)
* **item:** use the global activated background for md ripple color ([#16752](https://github.com/ionic-team/ionic/issues/16752)) ([95945c0](https://github.com/ionic-team/ionic/commit/95945c0)), closes [#16585](https://github.com/ionic-team/ionic/issues/16585)
* **label:** use primary color on focus for md input labels ([#18183](https://github.com/ionic-team/ionic/issues/18183)) ([ddb8ef8](https://github.com/ionic-team/ionic/commit/ddb8ef8)), closes [#15602](https://github.com/ionic-team/ionic/issues/15602)
* **menu** add prefixed transform for side menu animation ([#18128](https://github.com/ionic-team/ionic/issues/18128)) ([2457a23](https://github.com/ionic-team/ionic/commit/2457a23)), closes [#17729](https://github.com/ionic-team/ionic/issues/17729)
* **reorder-group:** remove required parameter for the complete method ([#18084](https://github.com/ionic-team/ionic/issues/18084)) ([bd96491](https://github.com/ionic-team/ionic/commit/bd96491)), closes [#16302](https://github.com/ionic-team/ionic/issues/16302)
* **segment:** decrease icon size on ios and stretch segment buttons to fill height ([#17751](https://github.com/ionic-team/ionic/issues/17751)) ([0fa645b](https://github.com/ionic-team/ionic/commit/0fa645b)), closes [#17069](https://github.com/ionic-team/ionic/issues/17069)
* **textarea:** reposition textarea when keyboard appears ([#18098](https://github.com/ionic-team/ionic/issues/18098)) ([3cdab10](https://github.com/ionic-team/ionic/commit/3cdab10)), closes [#17847](https://github.com/ionic-team/ionic/issues/17847)
* **toast:** allow button-color CSS variable to be overridden ([#18133](https://github.com/ionic-team/ionic/issues/18133)) ([0c83fd3](https://github.com/ionic-team/ionic/commit/0c83fd3)), closes [#18127](https://github.com/ionic-team/ionic/issues/18127)
### Features
* **card:** add button functionality ([#17997](https://github.com/ionic-team/ionic/issues/17997)) ([669ec0d](https://github.com/ionic-team/ionic/commit/669ec0d)), closes [#17773](https://github.com/ionic-team/ionic/issues/17773)
* **img:** add ionImgWillLoad event and emit ionImgDidLoad when image is loaded ([#18159](https://github.com/ionic-team/ionic/issues/18159)) ([38ffb98](https://github.com/ionic-team/ionic/commit/38ffb98)), closes [#17652](https://github.com/ionic-team/ionic/issues/17652) [#18161](https://github.com/ionic-team/ionic/issues/18161)
* **item-sliding:** add open method ([#17964](https://github.com/ionic-team/ionic/issues/17964)) ([f912206](https://github.com/ionic-team/ionic/commit/f912206)), closes [#17899](https://github.com/ionic-team/ionic/issues/17899)
* **menu-button:** add css variables for padding ([#18188](https://github.com/ionic-team/ionic/issues/18188)) ([ef98977](https://github.com/ionic-team/ionic/commit/ef98977)), closes [#18187](https://github.com/ionic-team/ionic/issues/18187)
* **refresher:** add pullFactor property to control speed ([#16697](https://github.com/ionic-team/ionic/issues/16697)) ([9030dcc](https://github.com/ionic-team/ionic/commit/9030dcc)), closes [#15425](https://github.com/ionic-team/ionic/issues/15425)
* **searchbar:** add disabled property ([#17935](https://github.com/ionic-team/ionic/issues/17935)) ([a5b9066](https://github.com/ionic-team/ionic/commit/a5b9066)), closes [#17921](https://github.com/ionic-team/ionic/issues/17921)
* **textarea:** add option to expand textarea as value changes ([#16916](https://github.com/ionic-team/ionic/issues/16916)) ([cc8678a](https://github.com/ionic-team/ionic/commit/cc8678a))
## [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))
* **security:** sanitize components using innerHTML ([#18146](https://github.com/ionic-team/ionic/issues/18146)) ([b839e6f](https://github.com/ionic-team/ionic/commit/b839e6f))
* sanitize components using innerHTML ([#18146](https://github.com/ionic-team/ionic/issues/18146)) ([b839e6f](https://github.com/ionic-team/ionic/commit/b839e6f))

View File

@@ -13,7 +13,7 @@ Ionic is based on [Web Components](https://www.webcomponents.org/introduction) a
| **Core** | [`@ionic/core`](https://www.npmjs.com/package/@ionic/core) | [![version](https://img.shields.io/npm/v/@ionic/core/latest.svg)](https://www.npmjs.com/package/@ionic/core) | [`README.md`](core/README.md)
| **Angular** | [`@ionic/angular`](https://www.npmjs.com/package/@ionic/angular) | [![version](https://img.shields.io/npm/v/@ionic/angular/latest.svg)](https://www.npmjs.com/package/@ionic/angular) | [`README.md`](angular/README.md)
| **Vue** | [`@ionic/vue`](https://www.npmjs.com/package/@ionic/vue) | [![version](https://img.shields.io/npm/v/@ionic/vue/latest.svg)](https://www.npmjs.com/package/@ionic/vue) | [`README.md`](vue/README.md)
| **React** | [`@ionic/react`](https://www.npmjs.com/package/@ionic/react) | [![version](https://img.shields.io/npm/v/@ionic/react/latest.svg)](https://www.npmjs.com/package/@ionic/react) | [`README.md`](packages/react/README.md)
| **React** | [`@ionic/react`](https://www.npmjs.com/package/@ionic/react) | [![version](https://img.shields.io/npm/v/@ionic/react/latest.svg)](https://www.npmjs.com/package/@ionic/react) | [`README.md`](react/README.md)
Looking for the `ionic-angular` package? Ionic 3 has been moved to the [`ionic-v3`](https://github.com/ionic-team/ionic-v3) repo. See [Earlier Versions](#earlier-versions).

View File

@@ -62,7 +62,7 @@ A list of the breaking changes introduced to each component in Ionic Angular v4.
## Action Sheet
The `title`, `subTitle` and `enableBackdropDismiss` properties have been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
The `title`, `subTitle` and `enableBackdropDismiss` properties has been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
**Old Usage Example:**
@@ -89,7 +89,7 @@ await actionSheet.present();
## Alert
The `title`, `subTitle` and `enableBackdropDismiss` properties have been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
The `title`, `subTitle` and `enableBackdropDismiss` properties has been renamed to `header`, `subHeader` and `backdropDismiss` respectively.
**Old Usage Example:**
@@ -498,7 +498,7 @@ The `<ion-fab>` container was previously placed inside of the fixed content by d
### Markup Changed
The Grid has been refactored in order to support CSS variables and a dynamic number of columns. The following column attributes have been changed.
The Grid has been refactored in order to support css variables and a dynamic number of columns. The following column attributes have been changed.
_In the following examples, `{breakpoint}` refers to the optional screen breakpoint (xs, sm, md, lg, xl) and `{value}` refers to the number of columns (`auto` or a number between `1` and `12`)._
@@ -507,7 +507,7 @@ _In the following examples, `{breakpoint}` refers to the optional screen breakpo
- `push-{breakpoint}-{value}` attributes have been renamed to `push-{breakpoint}=“{value}”`
- `pull-{breakpoint}-{value}` attributes have been renamed to `pull-{breakpoint}=“{value}”`
Customizing the padding and width of a grid should now be done with CSS variables. For more information, see [Grid Layout](https://github.com/ionic-team/ionic-docs/blob/master/src/pages/layout/grid.md).
Customizing the padding and width of a grid should now be done with css variables. For more information, see [Grid Layout](https://github.com/ionic-team/ionic-docs/blob/master/src/content/layout/grid.md).
## Icon
@@ -783,26 +783,6 @@ The option component should now be written as an `ion-item-option`. Previously i
The `getSlidingPercent` method has been renamed to `getSlidingRatio` since the function is returning a ratio of the open amount of the item compared to the width of the options.
### Arguments Changed
The `ionDrag` event no longer gets the sliding item as an argument. It now takes an event with a property `details` which contains two properties `amount` and `ratio` reflecting the absolute and ratio values of the sliding action respectively.
**Old Usage Example:**
```typescript
dragged(item: ItemSliding) {
console.log(item.getSlidingPercent());
console.log(item.getOpenAmount());
}
```
**New Usage Example:**
```typescript
dragged(ev: { details: { amount: number, ratio: number } }) {
console.log(ev.details.ratio);
console.log(ev.details.amount);
}
```
## Label
@@ -1092,7 +1072,7 @@ openLoading() {
```javascript
async openLoading() {
let loading = this.loadingCtrl.create({
message: 'Loading...'
content: 'Loading...'
});
await loading.present();
@@ -1798,7 +1778,7 @@ To include the stylesheet for testing such as in a Plunker, Codepen, or anywhere
#### Production
To use the CSS in production, we recommend importing it into a global file, such as `app/global.scss`:
To use the css in production, we recommend importing it into a global file, such as `app/global.scss`:
```css
/** Basic CSS for Ionic Apps */

View File

@@ -23,7 +23,7 @@ Ionic Angular specific building blocks on top of [@ionic/core](https://www.npmjs
1. Pull the latest from master
2. Build ionic/angular: `npm run build`
3. Run `npm link` from `ionic/angular/dist` directory
3. Run `npm link` from ionic/angular directory
4. Create a blank angular project
```

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "5.0.2",
"version": "4.3.1",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -19,16 +19,12 @@
"type": "git",
"url": "https://github.com/ionic-team/ionic.git"
},
"bugs": {
"url": "https://github.com/ionic-team/ionic/issues"
},
"homepage": "https://ionicframework.com/",
"scripts": {
"build": "npm run clean && npm run build.ng && npm run build.core && npm run clean-generated",
"build": "npm run clean && npm run build.core && npm run build.ng && npm run clean-generated",
"build.core": "node scripts/build-core.js",
"build.fesm": "rollup --config ./scripts/rollup.config.js",
"build.link": "npm run build && node scripts/link-copy.js",
"build.ng": "ng-packagr -p package.json",
"build.ng": "npm run build.es2015 && npm run build.es5",
"build.es2015": "ngc -p tsconfig.json && rollup --config ./scripts/rollup.config.js",
"build.es5": "ngc -p tsconfig.legacy.json && rollup --config ./scripts/rollup.config.legacy.js",
"clean": "node scripts/clean.js",
@@ -41,46 +37,53 @@
"tsc": "tsc -p .",
"validate": "npm i && npm run lint && npm run test && npm run build"
},
"module": "dist/fesm5.js",
"main": "dist/fesm5.js",
"types": "dist/core.d.ts",
"files": [
"dist/",
"css/"
],
"dependencies": {
"@ionic/core": "5.0.2",
"@ionic/core": "4.3.1",
"tslib": "^1.9.3"
},
"peerDependencies": {
"@angular/core": ">=8.2.7",
"@angular/forms": ">=8.2.7",
"@angular/router": ">=8.2.7",
"@angular-devkit/core": "^7.2.1",
"@angular-devkit/schematics": "^7.2.1",
"@angular/core": "^7.2.1",
"@angular/common": "^7.2.1",
"@angular/forms": "^7.2.1",
"@angular/router": "^7.2.1",
"@angular/compiler": "^7.2.1",
"@angular/compiler-cli": "^7.2.1",
"@angular/platform-browser": "^7.2.1",
"@angular/platform-browser-dynamic": "^7.2.1",
"rxjs": ">=6.2.0",
"zone.js": ">=0.8.26"
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/core": "8.3.17",
"@angular-devkit/schematics": "8.3.17",
"@angular/common": "8.2.13",
"@angular/compiler": "8.2.13",
"@angular/compiler-cli": "8.2.13",
"@angular/core": "8.2.13",
"@angular/forms": "8.2.13",
"@angular/router": "8.2.13",
"@types/node": "12.12.5",
"@angular-devkit/core": "^7.2.1",
"@angular-devkit/schematics": "^7.2.1",
"@angular/common": "^7.2.1",
"@angular/compiler": "^7.2.1",
"@angular/compiler-cli": "^7.2.1",
"@angular/core": "^7.2.1",
"@angular/forms": "^7.2.1",
"@angular/platform-browser": "^7.2.1",
"@angular/platform-browser-dynamic": "^7.2.1",
"@angular/router": "^7.2.1",
"@types/node": "~10.12.0",
"fs-extra": "^7.0.0",
"glob": "^7.1.4",
"ng-packagr": "5.7.1",
"rollup": "~1.17.0",
"rollup-plugin-node-resolve": "~5.2.0",
"rxjs": "^6.5.2",
"glob": "^7.1.3",
"rollup": "^1.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rxjs": "^6.2.0",
"tsickle": "^0.34.0",
"tslint": "^5.12.1",
"tslint-ionic-rules": "0.0.21",
"typescript": "3.4.5",
"typescript": "3.2.4",
"zone.js": "^0.8.28"
},
"schematics": "./schematics/collection.json",
"ngPackage": {
"lib": {
"entryFile": "src/index.ts"
},
"whitelistedNonPeerDependencies": [
"@ionic/core"
]
}
"schematics": "./dist/schematics/collection.json"
}

View File

@@ -15,7 +15,3 @@
npm run build.link ../ionic-conference-app
When the command above is ran from the `angular` directory, it will build `@ionic/angular` and copy the `dist` directory to the correct location of another local project. In the example above, the end result is that it copies the `dist` directory to `../ionic-conference-app/node_modules/@ionic/angular/dist`. The path given should be relative to the root of this mono repo.
## package.json note
The `package.json` file in this directory references __Ionic 3__ and is in here to get GitHub to properly show the Used By counts on the repo. __Do not remove it!__

View File

@@ -15,7 +15,7 @@ function copyIonicons() {
function copyCSS() {
const src = path.join(__dirname, '..', '..', 'core', 'css');
const dst = path.join(__dirname, '..','dist', 'css');
const dst = path.join(__dirname, '..', 'css');
fs.removeSync(dst);
fs.copySync(src, dst);

View File

@@ -1,12 +1,12 @@
const fs = require('fs-extra');
const path = require('path');
const ROOT_DIR = path.join(__dirname, '..');
const cleanDirs = [
path.join(ROOT_DIR, 'dist')
'dist'
];
cleanDirs.forEach(dir => {
fs.emptyDirSync(dir);
const cleanDir = path.join(__dirname, '../', dir);
fs.removeSync(cleanDir);
});

View File

@@ -1,16 +0,0 @@
{
"name": "ionic-angular",
"version": "`gulp package` generates dist/package.json from this template using the root ./package.json",
"description": "",
"keywords": [],
"repository": {
"type": "git",
"url": "https://github.com/ionic-team/ionic.git"
},
"license": "MIT",
"main": "umd/index.js",
"module": "index.js",
"es2015": "es2015/index.js",
"peerDependencies": {
}
}

View File

@@ -2,13 +2,15 @@ import resolve from 'rollup-plugin-node-resolve';
export default {
input: 'build/es2015/core.js',
output: [
{
file: 'dist/fesm2015.js',
format: 'es'
}
],
output: {
file: 'dist/fesm2015.js',
format: 'es'
},
external: (id) => {
// inline @ionic/core deps
if (id === '@ionic/core') {
return false;
}
// anything else is external
// Windows: C:\xxxxxx\xxx
const colonPosition = 1;
@@ -16,7 +18,7 @@ export default {
},
plugins: [
resolve({
mainFields: ['module']
module: true,
})
]
};
};

View File

@@ -4,15 +4,6 @@ const newConfig = {
...config,
input: 'build/es5/core.js',
};
newConfig.output = [
{
file: 'dist/fesm5.js',
format: 'es'
},
{
file: 'dist/fesm5.cjs.js',
format: 'cjs'
}
];
newConfig.output.file = 'dist/fesm5.js';
export { newConfig as default };

View File

@@ -1,45 +1,55 @@
import { NgZone } from '@angular/core';
import { applyPolyfills, defineCustomElements } from '@ionic/core/loader';
import { defineCustomElements } from '@ionic/core/loader';
import { Config } from './providers/config';
import { IonicWindow } from './types/interfaces';
import { raf } from './util/util';
let didInitialize = false;
export const appInitialize = (config: Config, doc: Document, zone: NgZone) => {
export function appInitialize(config: Config, doc: Document) {
return (): any => {
const win: IonicWindow | undefined = doc.defaultView as any;
if (win && typeof (window as any) !== 'undefined') {
if (didInitialize) {
console.warn('Ionic Angular was already initialized. Make sure IonicModule.forRoot() is just called once.');
}
didInitialize = true;
if (win) {
const Ionic = win.Ionic = win.Ionic || {};
Ionic.config = {
...config,
_zoneGate: (h: any) => zone.run(h)
Ionic.config = config;
Ionic.asyncQueue = false;
Ionic.ael = (elm, eventName, cb, opts) => {
if (elm.__zone_symbol__addEventListener && skipZone(eventName)) {
elm.__zone_symbol__addEventListener(eventName, cb, opts);
} else {
elm.addEventListener(eventName, cb, opts);
}
};
const aelFn = '__zone_symbol__addEventListener' in (doc.body as any)
? '__zone_symbol__addEventListener'
: 'addEventListener';
Ionic.rel = (elm, eventName, cb, opts) => {
if (elm.__zone_symbol__removeEventListener && skipZone(eventName)) {
elm.__zone_symbol__removeEventListener(eventName, cb, opts);
} else {
elm.removeEventListener(eventName, cb, opts);
}
};
return applyPolyfills().then(() => {
return defineCustomElements(win, {
exclude: ['ion-tabs', 'ion-tab'],
syncQueue: true,
raf,
jmp: (h: any) => zone.runOutsideAngular(h),
ael(elm, eventName, cb, opts) {
(elm as any)[aelFn](eventName, cb, opts);
},
rel(elm, eventName, cb, opts) {
elm.removeEventListener(eventName, cb, opts);
}
});
return defineCustomElements(win, {
exclude: ['ion-tabs', 'ion-tab']
});
}
};
};
}
const SKIP_ZONE = [
'scroll',
'resize',
'touchstart',
'touchmove',
'touchend',
'mousedown',
'mousemove',
'mouseup',
'ionStyle',
];
function skipZone(eventName: string) {
return SKIP_ZONE.indexOf(eventName) >= 0;
}

View File

@@ -25,8 +25,8 @@ export class BooleanValueAccessor extends ValueAccessor {
setIonicClasses(this.el);
}
@HostListener('ionChange', ['$event.target'])
_handleIonChange(el: any) {
this.handleChangeEvent(el, el.checked);
@HostListener('ionChange', ['$event.target.checked'])
_handleIonChange(value: any) {
this.handleChangeEvent(value);
}
}

View File

@@ -20,9 +20,9 @@ export class NumericValueAccessor extends ValueAccessor {
super(el);
}
@HostListener('ionChange', ['$event.target'])
_handleIonChange(el: any) {
this.handleChangeEvent(el, el.value);
@HostListener('ionChange', ['$event.target.value'])
_handleIonChange(value: any) {
this.handleChangeEvent(value);
}
registerOnChange(fn: (_: number | null) => void) {

View File

@@ -20,8 +20,8 @@ export class RadioValueAccessor extends ValueAccessor {
super(el);
}
@HostListener('ionSelect', ['$event.target'])
_handleIonSelect(el: any) {
this.handleChangeEvent(el, el.checked);
@HostListener('ionSelect', ['$event.target.checked'])
_handleIonSelect(value: any) {
this.handleChangeEvent(value);
}
}

View File

@@ -20,8 +20,8 @@ export class SelectValueAccessor extends ValueAccessor {
super(el);
}
@HostListener('ionChange', ['$event.target'])
_handleChangeEvent(el: any) {
this.handleChangeEvent(el, el.value);
@HostListener('ionChange', ['$event.target.value'])
_handleChangeEvent(value: any) {
this.handleChangeEvent(value);
}
}

View File

@@ -20,8 +20,8 @@ export class TextValueAccessor extends ValueAccessor {
super(el);
}
@HostListener('ionChange', ['$event.target'])
_handleInputEvent(el: any) {
this.handleChangeEvent(el, el.value);
@HostListener('ionChange', ['$event.target.value'])
_handleInputEvent(value: any) {
this.handleChangeEvent(value);
}
}

View File

@@ -1,8 +1,6 @@
import { ElementRef, HostListener } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { raf } from '../../util/util';
export class ValueAccessor implements ControlValueAccessor {
private onChange: (value: any) => void = () => {/**/};
@@ -12,33 +10,22 @@ export class ValueAccessor implements ControlValueAccessor {
constructor(protected el: ElementRef) {}
writeValue(value: any) {
/**
* TODO for Ionic 6:
* Change `value == null ? '' : value;`
* to `value`. This was a fix for IE9, but IE9
* is no longer supported; however, this change
* is potentially a breaking change
*/
this.el.nativeElement.value = this.lastValue = value == null ? '' : value;
setIonicClasses(this.el);
}
handleChangeEvent(el: HTMLElement, value: any) {
if (el === this.el.nativeElement) {
if (value !== this.lastValue) {
this.lastValue = value;
this.onChange(value);
}
setIonicClasses(this.el);
handleChangeEvent(value: any) {
if (value !== this.lastValue) {
this.lastValue = value;
this.onChange(value);
}
setIonicClasses(this.el);
}
@HostListener('ionBlur', ['$event.target'])
_handleBlurEvent(el: any) {
if (el === this.el.nativeElement) {
this.onTouched();
setIonicClasses(this.el);
}
@HostListener('ionBlur')
_handleBlurEvent() {
this.onTouched();
setIonicClasses(this.el);
}
registerOnChange(fn: (value: any) => void) {
@@ -54,8 +41,8 @@ export class ValueAccessor implements ControlValueAccessor {
}
}
export const setIonicClasses = (element: ElementRef) => {
raf(() => {
export function setIonicClasses(element: ElementRef) {
requestAnimationFrame(() => {
const input = element.nativeElement as HTMLElement;
const classes = getClasses(input);
setClasses(input, classes);
@@ -65,9 +52,9 @@ export const setIonicClasses = (element: ElementRef) => {
setClasses(item, classes);
}
});
};
}
const getClasses = (element: HTMLElement) => {
function getClasses(element: HTMLElement) {
const classList = element.classList;
const classes = [];
for (let i = 0; i < classList.length; i++) {
@@ -77,22 +64,22 @@ const getClasses = (element: HTMLElement) => {
}
}
return classes;
};
}
const setClasses = (element: HTMLElement, classes: string[]) => {
function setClasses(element: HTMLElement, classes: string[]) {
const classList = element.classList;
[
classList.remove(
'ion-valid',
'ion-invalid',
'ion-touched',
'ion-untouched',
'ion-dirty',
'ion-pristine'
].forEach(c => classList.remove(c));
);
classList.add(...classes);
}
classes.forEach(c => classList.add(c));
};
const startsWith = (input: string, search: string): boolean => {
function startsWith(input: string, search: string): boolean {
return input.substr(0, search.length) === search;
};
}

View File

@@ -6,7 +6,7 @@ import { IonRouterOutlet } from './ion-router-outlet';
@Directive({
selector: 'ion-back-button',
inputs: ['defaultHref'],
inputs: ['defaultHref']
})
export class IonBackButtonDelegate {

View File

@@ -1,5 +1,4 @@
import { Location } from '@angular/common';
import { Attribute, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
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';
@@ -16,8 +15,6 @@ import { RouteView, getUrl } from './stack-utils';
inputs: ['animated', 'swipeGesture']
})
export class IonRouterOutlet implements OnDestroy, OnInit {
nativeEl: HTMLIonRouterOutletElement;
private activated: ComponentRef<any> | null = null;
private activatedView: RouteView | null = null;
@@ -25,6 +22,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
private _swipeGesture?: boolean;
private name: string;
private stackCtrl: StackController;
private nativeEl: HTMLIonRouterOutletElement;
// Maintain map of activated route proxies for each component instance
private proxyMap = new WeakMap<any, ActivatedRoute>();
@@ -58,9 +56,9 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
private resolver: ComponentFactoryResolver,
@Attribute('name') name: string,
@Optional() @Attribute('tabs') tabs: string,
private changeDetector: ChangeDetectorRef,
private config: Config,
private navCtrl: NavController,
commonLocation: Location,
elementRef: ElementRef,
router: Router,
zone: NgZone,
@@ -70,7 +68,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
this.nativeEl = elementRef.nativeElement;
this.name = name || PRIMARY_OUTLET;
this.tabsPrefix = tabs === 'true' ? getUrl(router, activatedRoute) : undefined;
this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, navCtrl, zone, commonLocation);
this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, navCtrl, zone);
parentContexts.onChildOutletCreated(this.name, this as any);
}
@@ -94,7 +92,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
if ((this.nativeEl as any).componentOnReady) {
this.nativeEl.componentOnReady().then(() => {
if (this._swipeGesture === undefined) {
this.swipeGesture = this.config.getBoolean('swipeBackEnabled', (this.nativeEl as any).mode === 'ios');
this.swipeGesture = this.config.getBoolean('swipeBackEnabled', this.nativeEl.mode === 'ios');
}
});
}
@@ -143,20 +141,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
if (this.activated) {
if (this.activatedView) {
this.activatedView.savedData = new Map(this.getContext()!.children['contexts']);
/**
* Ensure we are saving the NavigationExtras
* data otherwise it will be lost
*/
this.activatedView.savedExtras = {};
const context = this.getContext()!;
if (context.route) {
const contextSnapshot = context.route.snapshot;
this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams;
this.activatedView.savedExtras.fragment = contextSnapshot.fragment;
}
}
const c = this.component;
this.activatedView = null;
@@ -210,6 +194,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
// Store references to the proxy by component
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
this.changeDetector.markForCheck();
}
this.activatedView = enteringView;
@@ -242,22 +228,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
return active ? active.url : undefined;
}
/**
* Returns the RouteView of the active page of each stack.
* @internal
*/
getLastRouteView(stackId?: string): RouteView | undefined {
return this.stackCtrl.getLastUrl(stackId);
}
/**
* Returns the root view in the tab stack.
* @internal
*/
getRootView(stackId?: string): RouteView | undefined {
return this.stackCtrl.getRootUrl(stackId);
}
/**
* Returns the active stack ID. In the context of ion-tabs, it means the active tab.
*/
@@ -331,7 +301,7 @@ class OutletInjector implements Injector {
private route: ActivatedRoute,
private childContexts: ChildrenOutletContexts,
private parent: Injector
) { }
) {}
get(token: any, notFoundValue?: any): any {
if (token === ActivatedRoute) {

View File

@@ -42,15 +42,15 @@ import { StackEvent } from './stack-utils';
})
export class IonTabs {
@ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet;
@ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined;
@ViewChild('outlet', { read: IonRouterOutlet }) outlet: IonRouterOutlet;
@ContentChild(IonTabBar) tabBar: IonTabBar | undefined;
@Output() ionTabsWillChange = new EventEmitter<{ tab: string }>();
@Output() ionTabsDidChange = new EventEmitter<{ tab: string }>();
@Output() ionTabsWillChange = new EventEmitter<{tab: string}>();
@Output() ionTabsDidChange = new EventEmitter<{tab: string}>();
constructor(
private navCtrl: NavController,
) { }
) {}
/**
* @internal
@@ -66,53 +66,18 @@ export class IonTabs {
}
}
/**
* When a tab button is clicked, there are several scenarios:
* 1. If the selected tab is currently active (the tab button has been clicked
* again), then it should go to the root view for that tab.
*
* a. Get the saved root view from the router outlet. If the saved root view
* matches the tabRootUrl, set the route view to this view including the
* navigation extras.
* b. If the saved root view from the router outlet does
* not match, navigate to the tabRootUrl. No navigation extras are
* included.
*
* 2. If the current tab tab is not currently selected, get the last route
* view from the router outlet.
*
* a. If the last route view exists, navigate to that view including any
* navigation extras
* b. If the last route view doesn't exist, then navigate
* to the default tabRootUrl
*/
@HostListener('ionTabButtonClick', ['$event.detail.tab'])
select(tab: string) {
const alreadySelected = this.outlet.getActiveStackId() === tab;
const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`;
if (alreadySelected) {
const rootView = this.outlet.getRootView(tab);
const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras;
return this.navCtrl.navigateRoot(tabRootUrl, {
...(navigationExtras),
animated: true,
animationDirection: 'back',
});
} else {
const lastRoute = this.outlet.getLastRouteView(tab);
/**
* If there is a lastRoute, goto that, otherwise goto the fallback url of the
* selected tab
*/
const url = lastRoute && lastRoute.url || tabRootUrl;
const navigationExtras = lastRoute && lastRoute.savedExtras;
const href = `${this.outlet.tabsPrefix}/${tab}`;
const url = alreadySelected
? href
: this.outlet.getLastUrl(tab) || href;
return this.navCtrl.navigateRoot(url, {
...(navigationExtras),
animated: true,
animationDirection: 'back',
});
}
return this.navCtrl.navigateRoot(url, {
animated: true,
animationDirection: 'back'
});
}
getSelected(): string | undefined {

View File

@@ -1,17 +1,11 @@
import { ComponentFactoryResolver, Directive, ElementRef, Injector, ViewContainerRef } from '@angular/core';
import { AngularDelegate } from '../../providers/angular-delegate';
import { ProxyCmp, proxyOutputs } from '../proxies-utils';
@ProxyCmp({
inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'],
methods: ['push', 'insert', 'insertPages', 'pop', 'popTo', 'popToRoot', 'removeIndex', 'setRoot', 'setPages', 'getActive', 'getByIndex', 'canGoBack', 'getPrevious']
})
@Directive({
selector: 'ion-nav'
selector: 'ion-nav',
})
export class NavDelegate {
protected el: HTMLElement;
constructor(
ref: ElementRef,
resolver: ComponentFactoryResolver,
@@ -19,8 +13,6 @@ export class NavDelegate {
angularDelegate: AngularDelegate,
location: ViewContainerRef
) {
this.el = ref.nativeElement;
ref.nativeElement.delegate = angularDelegate.create(resolver, injector, location);
proxyOutputs(this, this.el, ['ionNavDidChange' , 'ionNavWillChange' ]);
}
}

View File

@@ -38,7 +38,7 @@ export class NavParams {
*
* @param param Which param you want to look up
*/
get<T = any>(param: string): T {
get(param: string): any {
return this.data[param];
}
}

View File

@@ -1,4 +1,3 @@
import { Location } from '@angular/common';
import { ComponentRef, NgZone } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterDirection } from '@ionic/core';
@@ -23,7 +22,6 @@ export class StackController {
private router: Router,
private navCtrl: NavController,
private zone: NgZone,
private location: Location
) {
this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;
}
@@ -31,7 +29,7 @@ export class StackController {
createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {
const url = getUrl(this.router, activatedRoute);
const element = (ref && ref.location && ref.location.nativeElement) as HTMLElement;
const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
const unlistenEvents = bindLifecycleEvents(ref.instance, element);
return {
id: this.nextId++,
stackId: computeStackId(this.tabsPrefix, url),
@@ -59,7 +57,6 @@ export class StackController {
direction = 'back';
animation = undefined;
}
const viewsSnapshot = this.views.slice();
let currentNavigation;
@@ -70,7 +67,7 @@ export class StackController {
if (router.getCurrentNavigation) {
currentNavigation = router.getCurrentNavigation();
// Angular < 7.2.0
// Angular < 7.2.0
} else if (
router.navigations &&
router.navigations.value
@@ -95,36 +92,16 @@ export class StackController {
}
}
const reused = this.views.includes(enteringView);
const views = this.insertView(enteringView, direction);
// Trigger change detection before transition starts
// This will call ngOnInit() the first time too, just after the view
// was attached to the dom, but BEFORE the transition starts
if (!reused) {
enteringView.ref.changeDetectorRef.detectChanges();
}
// Wait until previous transitions finish
return this.zone.runOutsideAngular(() => {
return this.wait(() => {
// disconnect leaving page from change detection to
// reduce jank during the page transition
if (leavingView) {
leavingView.ref.changeDetectorRef.detach();
}
// In case the enteringView is the same as the leavingPage we need to reattach()
enteringView.ref.changeDetectorRef.reattach();
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
.then(() => ({
enteringView,
direction,
animation,
tabSwitch
}));
});
return this.wait(async () => {
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
await cleanupAsync(enteringView, views, viewsSnapshot);
return {
enteringView,
direction,
animation,
tabSwitch
};
});
}
@@ -155,34 +132,32 @@ export class StackController {
}
}
return this.navCtrl.navigateBack(url, view.savedExtras).then(() => true);
return this.navCtrl.navigateBack(url).then(() => true);
});
}
startBackTransition() {
async startBackTransition() {
const leavingView = this.activeView;
if (leavingView) {
const views = this.getStack(leavingView.stackId);
const enteringView = views[views.length - 2];
return this.wait(() => {
enteringView.ref.changeDetectorRef.reattach();
await this.wait(() => {
return this.transition(
enteringView, // entering view
leavingView, // leaving view
'back',
this.canGoBack(2),
true,
true
);
});
}
return Promise.resolve();
}
endBackTransition(shouldComplete: boolean) {
if (shouldComplete) {
this.skipTransition = true;
this.pop(1);
} else if (this.activeView) {
cleanup(this.activeView, this.views, this.views, this.location);
}
}
@@ -191,14 +166,6 @@ export class StackController {
return views.length > 0 ? views[views.length - 1] : undefined;
}
/**
* @internal
*/
getRootUrl(stackId?: string) {
const views = this.getStack(stackId);
return views.length > 0 ? views[0] : undefined;
}
getActiveStackId(): string | undefined {
return this.activeView ? this.activeView.stackId : undefined;
}
@@ -220,7 +187,7 @@ export class StackController {
return this.views.slice();
}
private transition(
private async transition(
enteringView: RouteView | undefined,
leavingView: RouteView | undefined,
direction: 'forward' | 'back' | undefined,
@@ -229,32 +196,26 @@ export class StackController {
) {
if (this.skipTransition) {
this.skipTransition = false;
return Promise.resolve(false);
}
if (leavingView === enteringView) {
return Promise.resolve(false);
return;
}
const enteringEl = enteringView ? enteringView.element : undefined;
const leavingEl = leavingView ? leavingView.element : undefined;
const containerEl = this.containerEl;
if (enteringEl && enteringEl !== leavingEl) {
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
enteringEl.classList.add('ion-page', 'ion-page-invisible');
if (enteringEl.parentElement !== containerEl) {
containerEl.appendChild(enteringEl);
}
if ((containerEl as any).commit) {
return containerEl.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
});
}
await containerEl.componentOnReady();
await containerEl.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
});
}
return Promise.resolve(false);
}
private async wait<T>(task: () => Promise<T>): Promise<T> {
@@ -267,41 +228,26 @@ export class StackController {
}
}
const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
if (typeof (requestAnimationFrame as any) === 'function') {
return new Promise<any>(resolve => {
requestAnimationFrame(() => {
cleanup(activeRoute, views, viewsSnapshot, location);
resolve();
});
function cleanupAsync(activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[]) {
return new Promise(resolve => {
requestAnimationFrame(() => {
cleanup(activeRoute, views, viewsSnapshot);
resolve();
});
}
return Promise.resolve();
};
});
}
const cleanup = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
function cleanup(activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[]) {
viewsSnapshot
.filter(view => !views.includes(view))
.forEach(destroyView);
views.forEach(view => {
/**
* In the event that a user navigated multiple
* times in rapid succession, we want to make sure
* we don't pre-emptively detach a view while
* it is in mid-transition.
*
* In this instance we also do not care about query
* params or fragments as it will be the same view regardless
*/
const locationWithoutParams = location.path().split('?')[0];
const locationWithoutFragment = locationWithoutParams.split('#')[0];
if (view !== activeRoute && view.url !== locationWithoutFragment) {
if (view !== activeRoute) {
const element = view.element;
element.setAttribute('aria-hidden', 'true');
element.classList.add('ion-page-hidden');
view.ref.changeDetectorRef.detach();
}
});
};
}

View File

@@ -1,8 +1,8 @@
import { ComponentRef } from '@angular/core';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { NavDirection, RouterDirection } from '@ionic/core';
export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection) => {
export function insertView(views: RouteView[], view: RouteView, direction: RouterDirection) {
if (direction === 'root') {
return setRoot(views, view);
} else if (direction === 'forward') {
@@ -10,15 +10,15 @@ export const insertView = (views: RouteView[], view: RouteView, direction: Route
} else {
return setBack(views, view);
}
};
}
const setRoot = (views: RouteView[], view: RouteView) => {
function setRoot(views: RouteView[], view: RouteView) {
views = views.filter(v => v.stackId !== view.stackId);
views.push(view);
return views;
};
}
const setForward = (views: RouteView[], view: RouteView) => {
function setForward(views: RouteView[], view: RouteView) {
const index = views.indexOf(view);
if (index >= 0) {
views = views.filter(v => v.stackId !== view.stackId || v.id <= view.id);
@@ -26,30 +26,30 @@ const setForward = (views: RouteView[], view: RouteView) => {
views.push(view);
}
return views;
};
}
const setBack = (views: RouteView[], view: RouteView) => {
function setBack(views: RouteView[], view: RouteView) {
const index = views.indexOf(view);
if (index >= 0) {
return views.filter(v => v.stackId !== view.stackId || v.id <= view.id);
} else {
return setRoot(views, view);
}
};
}
export const getUrl = (router: Router, activatedRoute: ActivatedRoute) => {
export function getUrl(router: Router, activatedRoute: ActivatedRoute) {
const urlTree = router.createUrlTree(['.'], { relativeTo: activatedRoute });
return router.serializeUrl(urlTree);
};
}
export const isTabSwitch = (enteringView: RouteView, leavingView: RouteView | undefined) => {
export function isTabSwitch(enteringView: RouteView, leavingView: RouteView | undefined) {
if (!leavingView) {
return true;
}
return enteringView.stackId !== leavingView.stackId;
};
}
export const computeStackId = (prefixUrl: string[] | undefined, url: string) => {
export function computeStackId(prefixUrl: string[] | undefined, url: string) {
if (!prefixUrl) {
return undefined;
}
@@ -63,22 +63,22 @@ export const computeStackId = (prefixUrl: string[] | undefined, url: string) =>
}
}
return undefined;
};
}
export const toSegments = (path: string) => {
export function toSegments(path: string): string[] {
return path
.split('/')
.map(s => s.trim())
.filter(s => s !== '');
};
}
export const destroyView = (view: RouteView | undefined) => {
export function destroyView(view: RouteView | undefined) {
if (view) {
// TODO lifecycle event
view.ref.destroy();
view.unlistenEvents();
}
};
}
export interface StackEvent {
enteringView: RouteView;
@@ -94,6 +94,5 @@ export interface RouteView {
element: HTMLElement;
ref: ComponentRef<any>;
savedData?: any;
savedExtras?: NavigationExtras;
unlistenEvents: () => void;
}

View File

@@ -43,7 +43,9 @@ d.IonApp,
d.IonMenuButton,
d.IonMenuToggle,
d.IonNav,
d.IonNavLink,
d.IonNavPop,
d.IonNavPush,
d.IonNavSetRoot,
d.IonNote,
d.IonProgressBar,
d.IonRadio,

View File

@@ -1,46 +1,26 @@
/* tslint:disable */
import { fromEvent } from 'rxjs';
export const proxyInputs = (Cmp: any, inputs: string[]) => {
export function proxyInputs(Cmp: any, inputs: string[]) {
const Prototype = Cmp.prototype;
inputs.forEach(item => {
Object.defineProperty(Prototype, item, {
get() {
return this.el[item];
},
set(val: any) {
this.z.runOutsideAngular(() => (this.el[item] = val));
}
get() { return this.el[item]; },
set(val: any) { this.el[item] = val; },
});
});
};
}
export const proxyMethods = (Cmp: any, methods: string[]) => {
export function proxyMethods(Cmp: any, methods: string[]) {
const Prototype = Cmp.prototype;
methods.forEach(methodName => {
Prototype[methodName] = function () {
Prototype[methodName] = function() {
const args = arguments;
return this.z.runOutsideAngular(() =>
this.el[methodName].apply(this.el, args)
);
return this.el.componentOnReady().then((el: any) => el[methodName].apply(el, args));
};
});
};
}
export const proxyOutputs = (instance: any, el: any, events: string[]) => {
export function proxyOutputs(instance: any, el: any, events: string[]) {
events.forEach(eventName => instance[eventName] = fromEvent(el, eventName));
}
// tslint:disable-next-line: only-arrow-functions
export function ProxyCmp(opts: { inputs?: any; methods?: any }) {
const decorator = function(cls: any){
if (opts.inputs) {
proxyInputs(cls, opts.inputs);
}
if (opts.methods) {
proxyMethods(cls, opts.methods);
}
return cls;
};
return decorator;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EmbeddedViewRef, IterableDiffer, IterableDiffers, NgZone, SimpleChanges, TrackByFunction } from '@angular/core';
import { Cell, CellType, FooterHeightFn, HeaderFn, HeaderHeightFn, ItemHeightFn } from '@ionic/core';
import { Cell, CellType, HeaderFn, ItemHeightFn } from '@ionic/core';
import { ProxyCmp } from '../proxies-utils';
import { proxyInputs, proxyMethods } from '../proxies-utils';
import { VirtualFooter } from './virtual-footer';
import { VirtualHeader } from './virtual-header';
@@ -75,7 +75,7 @@ export declare interface IonVirtualScroll {
/**
* An optional function that maps each item within their height.
* When this function is provided, heavy optimizations and fast path can be taked by
* When this function is provides, heavy optimizations and fast path can be taked by
* `ion-virtual-scroll` leading to massive performance improvements.
*
* This function allows to skip all DOM reads, which can be Doing so leads
@@ -83,16 +83,6 @@ export declare interface IonVirtualScroll {
*/
itemHeight?: ItemHeightFn;
/**
* An optional function that maps each item header within their height.
*/
headerHeight?: HeaderHeightFn;
/**
* An optional function that maps each item footer within their height.
*/
footerHeight?: FooterHeightFn;
/**
* Same as `ngForTrackBy` which can be used on `ngFor`.
*/
@@ -112,10 +102,6 @@ export declare interface IonVirtualScroll {
'positionForItem': (index: number) => Promise<number>;
}
@ProxyCmp({
inputs: ['approxItemHeight', 'approxHeaderHeight', 'approxFooterHeight', 'headerFn', 'footerFn', 'items', 'itemHeight', 'headerHeight', 'footerHeight'],
methods: ['checkEnd', 'checkRange', 'positionForItem']
})
@Component({
selector: 'ion-virtual-scroll',
template: '<ng-content></ng-content>',
@@ -128,8 +114,6 @@ export declare interface IonVirtualScroll {
'footerFn',
'items',
'itemHeight',
'headerHeight',
'footerHeight',
'trackBy'
]
})
@@ -137,14 +121,14 @@ export class IonVirtualScroll {
private differ?: IterableDiffer<any>;
private el: HTMLIonVirtualScrollElement;
private refMap = new WeakMap<HTMLElement, EmbeddedViewRef<VirtualContext>>();
private refMap = new WeakMap<HTMLElement, EmbeddedViewRef<VirtualContext>> ();
@ContentChild(VirtualItem, { static: false }) itmTmp!: VirtualItem;
@ContentChild(VirtualHeader, { static: false }) hdrTmp!: VirtualHeader;
@ContentChild(VirtualFooter, { static: false }) ftrTmp!: VirtualFooter;
@ContentChild(VirtualItem) itmTmp!: VirtualItem;
@ContentChild(VirtualHeader) hdrTmp!: VirtualHeader;
@ContentChild(VirtualFooter) ftrTmp!: VirtualFooter;
constructor(
private z: NgZone,
private zone: NgZone,
private iterableDiffers: IterableDiffers,
elementRef: ElementRef,
) {
@@ -178,7 +162,7 @@ export class IonVirtualScroll {
}
private nodeRender(el: HTMLElement | null, cell: Cell, index: number): HTMLElement {
return this.z.run(() => {
return this.zone.run(() => {
let node: EmbeddedViewRef<VirtualContext>;
if (!el) {
node = this.itmTmp.viewContainer.createEmbeddedView(
@@ -210,7 +194,7 @@ export class IonVirtualScroll {
}
}
const getElement = (view: EmbeddedViewRef<VirtualContext>): HTMLElement => {
function getElement(view: EmbeddedViewRef<VirtualContext>): HTMLElement {
const rootNodes = view.rootNodes;
for (let i = 0; i < rootNodes.length; i++) {
if (rootNodes[i].nodeType === 1) {
@@ -218,4 +202,20 @@ const getElement = (view: EmbeddedViewRef<VirtualContext>): HTMLElement => {
}
}
throw new Error('virtual element was not created');
};
}
proxyInputs(IonVirtualScroll, [
'approxItemHeight',
'approxHeaderHeight',
'approxFooterHeight',
'headerFn',
'footerFn',
'items',
'itemHeight'
]);
proxyMethods(IonVirtualScroll, [
'checkEnd',
'checkRange',
'positionForItem'
]);

View File

@@ -20,6 +20,7 @@ export * from './directives/proxies';
export { AngularDelegate } from './providers/angular-delegate';
export { ActionSheetController } from './providers/action-sheet-controller';
export { AlertController } from './providers/alert-controller';
export { Events } from './providers/events';
export { LoadingController } from './providers/loading-controller';
export { MenuController } from './providers/menu-controller';
export { PickerController } from './providers/picker-controller';
@@ -30,8 +31,6 @@ export { ToastController } from './providers/toast-controller';
export { NavController } from './providers/nav-controller';
export { DomController } from './providers/dom-controller';
export { Config } from './providers/config';
export { AnimationController } from './providers/animation-controller';
export { GestureController } from './providers/gesture-controller';
// ROUTER STRATEGY
export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy';

View File

@@ -1,5 +1,5 @@
import { CommonModule, DOCUMENT } from '@angular/common';
import { APP_INITIALIZER, ModuleWithProviders, NgModule, NgZone } from '@angular/core';
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { IonicConfig } from '@ionic/core';
import { appInitialize } from './app-initialize';
@@ -13,7 +13,7 @@ import { IonRouterOutlet } from './directives/navigation/ion-router-outlet';
import { IonTabs } from './directives/navigation/ion-tabs';
import { NavDelegate } from './directives/navigation/nav-delegate';
import { RouterLinkDelegate } from './directives/navigation/router-link-delegate';
import { IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonList, IonListHeader, IonMenu, IonMenuButton, IonMenuToggle, IonNav, IonNavLink, IonNote, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSelect, IonSelectOption, IonSkeletonText, IonSlide, IonSlides, IonSpinner, IonSplitPane, IonTabBar, IonTabButton, IonText, IonTextarea, IonThumbnail, IonTitle, IonToggle, IonToolbar } from './directives/proxies';
import { IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonList, IonListHeader, IonMenu, IonMenuButton, IonMenuToggle, IonNav, IonNavPop, IonNavPush, IonNavSetRoot, IonNote, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSelect, IonSelectOption, IonSkeletonText, IonSlide, IonSlides, IonSpinner, IonSplitPane, IonTabBar, IonTabButton, IonText, IonTextarea, IonThumbnail, IonTitle, IonToggle, IonToolbar } from './directives/proxies';
import { VirtualFooter } from './directives/virtual-scroll/virtual-footer';
import { VirtualHeader } from './directives/virtual-scroll/virtual-header';
import { VirtualItem } from './directives/virtual-scroll/virtual-item';
@@ -66,7 +66,9 @@ const DECLARATIONS = [
IonMenuButton,
IonMenuToggle,
IonNav,
IonNavLink,
IonNavPop,
IonNavPush,
IonNavSetRoot,
IonNote,
IonProgressBar,
IonRadio,
@@ -126,7 +128,7 @@ const DECLARATIONS = [
imports: [CommonModule]
})
export class IonicModule {
static forRoot(config?: IonicConfig): ModuleWithProviders<IonicModule> {
static forRoot(config?: IonicConfig): ModuleWithProviders {
return {
ngModule: IonicModule,
providers: [
@@ -140,8 +142,7 @@ export class IonicModule {
multi: true,
deps: [
ConfigToken,
DOCUMENT,
NgZone
DOCUMENT
]
}
]

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { ActionSheetOptions, actionSheetController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { ActionSheetOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay';
providedIn: 'root',
})
export class ActionSheetController extends OverlayBaseController<ActionSheetOptions, HTMLIonActionSheetElement> {
constructor() {
super(actionSheetController);
constructor(@Inject(DOCUMENT) doc: any) {
super('ion-action-sheet-controller', doc);
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { AlertOptions, alertController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { AlertOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay';
providedIn: 'root',
})
export class AlertController extends OverlayBaseController<AlertOptions, HTMLIonAlertElement> {
constructor() {
super(alertController);
constructor(@Inject(DOCUMENT) doc: any) {
super('ion-alert-controller', doc);
}
}

View File

@@ -34,10 +34,10 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
) {}
attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
return this.zone.run(() => {
return new Promise(resolve => {
return new Promise(resolve => {
this.zone.run(() => {
const el = attachView(
this.zone, this.resolver, this.injector, this.location, this.appRef,
this.resolver, this.injector, this.location, this.appRef,
this.elRefMap, this.elEventsMap,
container, component, params, cssClasses
);
@@ -47,8 +47,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
}
removeViewFromDom(_container: any, component: any): Promise<void> {
return this.zone.run(() => {
return new Promise(resolve => {
return new Promise(resolve => {
this.zone.run(() => {
const componentRef = this.elRefMap.get(component);
if (componentRef) {
componentRef.destroy();
@@ -65,8 +65,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
}
}
export const attachView = (
zone: NgZone,
export function attachView(
resolver: ComponentFactoryResolver,
injector: Injector,
location: ViewContainerRef | undefined,
@@ -74,7 +73,7 @@ export const attachView = (
elRefMap: WeakMap<HTMLElement, any>,
elEventsMap: WeakMap<HTMLElement, () => void>,
container: any, component: any, params: any, cssClasses: string[] | undefined
) => {
) {
const factory = resolver.resolveComponentFactory(component);
const childInjector = Injector.create({
providers: getProviders(params),
@@ -94,7 +93,7 @@ export const attachView = (
hostElement.classList.add(clazz);
}
}
const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
const unbindEvents = bindLifecycleEvents(instance, hostElement);
container.appendChild(hostElement);
if (!location) {
@@ -104,7 +103,7 @@ export const attachView = (
elRefMap.set(hostElement, componentRef);
elEventsMap.set(hostElement, unbindEvents);
return hostElement;
};
}
const LIFECYCLES = [
LIFECYCLE_WILL_ENTER,
@@ -114,22 +113,26 @@ const LIFECYCLES = [
LIFECYCLE_WILL_UNLOAD
];
export const bindLifecycleEvents = (zone: NgZone, instance: any, element: HTMLElement) => {
return zone.run(() => {
const unregisters = LIFECYCLES
.filter(eventName => typeof instance[eventName] === 'function')
.map(eventName => {
const handler = (ev: any) => instance[eventName](ev.detail);
element.addEventListener(eventName, handler);
return () => element.removeEventListener(eventName, handler);
});
return () => unregisters.forEach(fn => fn());
export function bindLifecycleEvents(instance: any, element: HTMLElement) {
const unregisters = LIFECYCLES.map(eventName => {
const handler = (ev: any) => {
if (typeof instance[eventName] === 'function') {
instance[eventName](ev.detail);
}
};
element.addEventListener(eventName, handler);
return () => {
element.removeEventListener(eventName, handler);
};
});
};
return () => {
unregisters.forEach(fn => fn());
};
}
const NavParamsToken = new InjectionToken<any>('NavParamsToken');
const getProviders = (params: {[key: string]: any}) => {
function getProviders(params: {[key: string]: any}) {
return [
{
provide: NavParamsToken, useValue: params
@@ -138,8 +141,8 @@ const getProviders = (params: {[key: string]: any}) => {
provide: NavParams, useFactory: provideNavParamsInjectable, deps: [NavParamsToken]
}
];
};
}
const provideNavParamsInjectable = (params: {[key: string]: any}) => {
function provideNavParamsInjectable(params: {[key: string]: any}) {
return new NavParams(params);
};
}

View File

@@ -1,32 +0,0 @@
import { Injectable } from '@angular/core';
import { Animation, createAnimation, getTimeGivenProgression } from '@ionic/core';
@Injectable({
providedIn: 'root',
})
export class AnimationController {
/**
* Create a new animation
*/
create(animationId?: string): Animation {
return createAnimation(animationId);
}
/**
* EXPERIMENTAL
*
* Given a progression and a cubic bezier function,
* this utility returns the time value(s) at which the
* cubic bezier reaches the given time progression.
*
* If the cubic bezier never reaches the progression
* the result will be an empty array.
*
* This is most useful for switching between easing curves
* when doing a gesture animation (i.e. going from linear easing
* during a drag, to another easing when `progressEnd` is called)
*/
easingTime(p0: number[], p1: number[], p2: number[], p3: number[], progression: number): number[] {
return getTimeGivenProgression(p0, p1, p2, p3, progression);
}
}

View File

@@ -33,7 +33,6 @@ export class Config {
}
set(key: keyof IonicConfig, value?: any) {
console.warn(`[DEPRECATION][Config]: The Config.set() method is deprecated and will be removed in Ionic Framework 6.0. Please see https://ionicframework.com/docs/angular/config for alternatives.`);
const c = getConfig();
if (c) {
c.set(key, value);
@@ -43,12 +42,12 @@ export class Config {
export const ConfigToken = new InjectionToken<any>('USERCONFIG');
const getConfig = (): CoreConfig | null => {
function getConfig(): CoreConfig | null {
if (typeof (window as any) !== 'undefined') {
const Ionic = (window as any as IonicWindow).Ionic;
const Ionic = (window as IonicWindow).Ionic;
if (Ionic && Ionic.config) {
return Ionic.config;
}
}
return null;
};
}

View File

@@ -22,7 +22,7 @@ export class DomController {
}
}
const getQueue = () => {
function getQueue() {
const win = typeof (window as any) !== 'undefined' ? window : null as any;
if (win != null) {
@@ -41,6 +41,6 @@ const getQueue = () => {
read: (cb: any) => cb(),
write: (cb: any) => cb()
};
};
}
export type RafCallback = (timeStamp?: number) => void;

View File

@@ -0,0 +1,76 @@
import { Injectable } from '@angular/core';
export type EventHandler = (...args: any[]) => any;
@Injectable({
providedIn: 'root',
})
export class Events {
private c = new Map<string, EventHandler[]>();
/**
* Subscribe to an event topic. Events that get posted to that topic will trigger the provided handler.
*
* @param topic the topic to subscribe to
* @param handler the event handler
*/
subscribe(topic: string, ...handlers: EventHandler[]) {
let topics = this.c.get(topic);
if (!topics) {
this.c.set(topic, topics = []);
}
topics.push(...handlers);
}
/**
* Unsubscribe from the given topic. Your handler will no longer receive events published to this topic.
*
* @param topic the topic to unsubscribe from
* @param handler the event handler
*
* @return true if a handler was removed
*/
unsubscribe(topic: string, handler?: EventHandler): boolean {
if (!handler) {
return this.c.delete(topic);
}
const topics = this.c.get(topic);
if (!topics) {
return false;
}
// We need to find and remove a specific handler
const index = topics.indexOf(handler);
if (index < 0) {
// Wasn't found, wasn't removed
return false;
}
topics.splice(index, 1);
if (topics.length === 0) {
this.c.delete(topic);
}
return true;
}
/**
* Publish an event to the given topic.
*
* @param topic the topic to publish to
* @param eventData the data to send as the event
*/
publish(topic: string, ...args: any[]): any[] | null {
const topics = this.c.get(topic);
if (!topics) {
return null;
}
return topics.map(handler => {
try {
return handler(...args);
} catch (e) {
console.error(e);
return null;
}
});
}
}

View File

@@ -1,14 +0,0 @@
import { Injectable } from '@angular/core';
import { Gesture, GestureConfig, createGesture } from '@ionic/core';
@Injectable({
providedIn: 'root',
})
export class GestureController {
/**
* Create a new gesture
*/
create(opts: GestureConfig): Gesture {
return createGesture(opts);
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { LoadingOptions, loadingController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { LoadingOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay';
providedIn: 'root',
})
export class LoadingController extends OverlayBaseController<LoadingOptions, HTMLIonLoadingElement> {
constructor() {
super(loadingController);
constructor(@Inject(DOCUMENT) doc: any) {
super('ion-loading-controller', doc);
}
}

View File

@@ -1,18 +1,24 @@
import { Injectable } from '@angular/core';
import { menuController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { proxyMethod } from '../util/util';
const CTRL = 'ion-menu-controller';
@Injectable({
providedIn: 'root',
})
export class MenuController {
constructor(@Inject(DOCUMENT) private doc: any) {
}
/**
* Programmatically open the Menu.
* @param [menuId] Optionally get the menu by its id, or side.
* @return returns a promise when the menu is fully opened
*/
open(menuId?: string) {
return menuController.open(menuId);
open(menuId?: string): Promise<boolean> {
return proxyMethod(CTRL, this.doc, 'open', menuId);
}
/**
@@ -22,8 +28,8 @@ export class MenuController {
* @param [menuId] Optionally get the menu by its id, or side.
* @return returns a promise when the menu is fully closed
*/
close(menuId?: string) {
return menuController.close(menuId);
close(menuId?: string): Promise<boolean> {
return proxyMethod(CTRL, this.doc, 'close', menuId);
}
/**
@@ -32,8 +38,8 @@ export class MenuController {
* @param [menuId] Optionally get the menu by its id, or side.
* @return returns a promise when the menu has been toggled
*/
toggle(menuId?: string) {
return menuController.toggle(menuId);
toggle(menuId?: string): Promise<boolean> {
return proxyMethod(CTRL, this.doc, 'toggle', menuId);
}
/**
@@ -44,8 +50,8 @@ export class MenuController {
* @param [menuId] Optionally get the menu by its id, or side.
* @return Returns the instance of the menu, which is useful for chaining.
*/
enable(shouldEnable: boolean, menuId?: string) {
return menuController.enable(shouldEnable, menuId);
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLIonMenuElement> {
return proxyMethod(CTRL, this.doc, 'enable', shouldEnable, menuId);
}
/**
@@ -54,8 +60,8 @@ export class MenuController {
* @param [menuId] Optionally get the menu by its id, or side.
* @return Returns the instance of the menu, which is useful for chaining.
*/
swipeGesture(shouldEnable: boolean, menuId?: string) {
return menuController.swipeGesture(shouldEnable, menuId);
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLIonMenuElement> {
return proxyMethod(CTRL, this.doc, 'swipeEnable', shouldEnable, menuId);
}
/**
@@ -63,16 +69,16 @@ export class MenuController {
* @return Returns true if the specified menu is currently open, otherwise false.
* If the menuId is not specified, it returns true if ANY menu is currenly open.
*/
isOpen(menuId?: string) {
return menuController.isOpen(menuId);
isOpen(menuId?: string): Promise<boolean> {
return proxyMethod(CTRL, this.doc, 'isOpen', menuId);
}
/**
* @param [menuId] Optionally get the menu by its id, or side.
* @return Returns true if the menu is currently enabled, otherwise false.
*/
isEnabled(menuId?: string) {
return menuController.isEnabled(menuId);
isEnabled(menuId?: string): Promise<boolean> {
return proxyMethod(CTRL, this.doc, 'isEnabled', menuId);
}
/**
@@ -84,21 +90,21 @@ export class MenuController {
* @param [menuId] Optionally get the menu by its id, or side.
* @return Returns the instance of the menu if found, otherwise `null`.
*/
get(menuId?: string) {
return menuController.get(menuId);
get(menuId?: string): Promise<HTMLIonMenuElement> {
return proxyMethod(CTRL, this.doc, 'get', menuId);
}
/**
* @return Returns the instance of the menu already opened, otherwise `null`.
*/
getOpen() {
return menuController.getOpen();
getOpen(): Promise<HTMLIonMenuElement> {
return proxyMethod(CTRL, this.doc, 'getOpen');
}
/**
* @return Returns an array of all menu instances.
*/
getMenus() {
return menuController.getMenus();
getMenus(): Promise<HTMLIonMenuElement[]> {
return proxyMethod(CTRL, this.doc, 'getMenus');
}
}

View File

@@ -1,5 +1,6 @@
import { ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
import { ModalOptions, modalController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { ComponentFactoryResolver, Inject, Injectable, Injector } from '@angular/core';
import { ModalOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -12,8 +13,9 @@ export class ModalController extends OverlayBaseController<ModalOptions, HTMLIon
private angularDelegate: AngularDelegate,
private resolver: ComponentFactoryResolver,
private injector: Injector,
@Inject(DOCUMENT) doc: any
) {
super(modalController);
super('ion-modal-controller', doc);
}
create(opts: ModalOptions): Promise<HTMLIonModalElement> {

View File

@@ -1,6 +1,6 @@
import { Location } from '@angular/common';
import { Injectable, Optional } from '@angular/core';
import { NavigationExtras, NavigationStart, Router, UrlSerializer, UrlTree } from '@angular/router';
import { NavigationExtras, NavigationStart, Router, UrlTree } from '@angular/router';
import { NavDirection, RouterDirection } from '@ionic/core';
import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet';
@@ -29,7 +29,6 @@ export class NavController {
constructor(
platform: Platform,
private location: Location,
private serializer: UrlSerializer,
@Optional() private router?: Router,
) {
// Subscribe to router events to detect direction
@@ -50,12 +49,12 @@ export class NavController {
/**
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
* it's equivalent to calling `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.
* it's equivalent to call `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.
*
* Going **forward** means that a new page is going to be pushed to the stack of the outlet (ion-router-outlet),
* Going **forward** means that a new page it's going to be pushed to the stack of the outlet (ion-router-outlet),
* and that it will show a "forward" animation by default.
*
* Navigating forward can also be triggered in a declarative manner by using the `[routerDirection]` directive:
* Navigating forward can also be trigger in a declarative manner by using the `[routerDirection]` directive:
*
* ```html
* <a routerLink="/path/to/page" routerDirection="forward">Link</a>
@@ -68,17 +67,17 @@ export class NavController {
/**
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
* it's equivalent to calling:
* it's equivalent to call:
*
* ```ts
* this.navController.setDirection('back');
* this.router.navigateByUrl(path);
* ```
*
* Going **back** means that all the pages in the stack until the navigated page is found will be popped,
* Going **back** means that all the pages in the stack until the navigated page is found will be pop,
* and that it will show a "back" animation by default.
*
* Navigating back can also be triggered in a declarative manner by using the `[routerDirection]` directive:
* Navigating back can also be trigger in a declarative manner by using the `[routerDirection]` directive:
*
* ```html
* <a routerLink="/path/to/page" routerDirection="back">Link</a>
@@ -91,7 +90,7 @@ export class NavController {
/**
* This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
* it's equivalent to calling:
* it's equivalent to call:
*
* ```ts
* this.navController.setDirection('root');
@@ -101,7 +100,7 @@ export class NavController {
* Going **root** means that all existing pages in the stack will be removed,
* and the navigated page will become the single page in the stack.
*
* Navigating root can also be triggered in a declarative manner by using the `[routerDirection]` directive:
* Navigating root can also be trigger in a declarative manner by using the `[routerDirection]` directive:
*
* ```html
* <a routerLink="/path/to/page" routerDirection="root">Link</a>
@@ -114,8 +113,7 @@ export class NavController {
/**
* Same as [Location](https://angular.io/api/common/Location)'s back() method.
* It will use the standard `window.history.back()` under the hood, but featuring a `back` animation
* by default.
* It will use the standard `window.history.back()` under the hood, but featuring a `back` animation.
*/
back(options: AnimationOptions = { animated: true, animationDirection: 'back' }) {
this.setDirection('back', options.animated, options.animationDirection);
@@ -123,9 +121,9 @@ export class NavController {
}
/**
* This methods goes back in the context of Ionic's stack navigation.
* This methods goes back in the context of ionic's stack navigation.
*
* It recursively finds the top active `ion-router-outlet` and calls `pop()`.
* It recursivelly finds the top active `ion-router-outlet` and calls `pop()`.
* This is the recommended way to go back when you are using `ion-router-outlet`.
*/
async pop() {
@@ -141,11 +139,11 @@ export class NavController {
}
/**
* This methods specifies the direction of the next navigation performed by the Angular router.
* This methods specifies the direction of the next navigation performed by the angular router.
*
* `setDirection()` does not trigger any transition, it just sets some flags to be consumed by `ion-router-outlet`.
* `setDirection()` does not trigger any transition, it just sets a set of flags to be consumed by `ion-router-outlet`.
*
* It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
* It's recommended to use `navigateForward()`, `navigateBack()` and `navigateBack()` instead of `setDirection()`.
*/
setDirection(direction: RouterDirection, animated?: boolean, animationDirection?: 'forward' | 'back') {
this.direction = direction;
@@ -186,34 +184,12 @@ export class NavController {
if (Array.isArray(url)) {
return this.router!.navigate(url, options);
} else {
/**
* navigateByUrl ignores any properties that
* would change the url, so things like queryParams
* would be ignored unless we create a url tree
* More Info: https://github.com/angular/angular/issues/18798
*/
const urlTree = this.serializer.parse(url.toString());
if (options.queryParams !== undefined) {
urlTree.queryParams = { ...options.queryParams };
}
if (options.fragment !== undefined) {
urlTree.fragment = options.fragment;
}
/**
* `navigateByUrl` will still apply `NavigationExtras` properties
* that do not modify the url, such as `replaceUrl` which is why
* `options` is passed in here.
*/
return this.router!.navigateByUrl(urlTree, options);
return this.router!.navigateByUrl(url, options);
}
}
}
const getAnimation = (direction: RouterDirection, animated: boolean | undefined, animationDirection: 'forward' | 'back' | undefined): NavDirection | undefined => {
function getAnimation(direction: RouterDirection, animated: boolean | undefined, animationDirection: 'forward' | 'back' | undefined): NavDirection | undefined {
if (animated === false) {
return undefined;
}
@@ -226,7 +202,7 @@ const getAnimation = (direction: RouterDirection, animated: boolean | undefined,
return 'forward';
}
return undefined;
};
}
const DEFAULT_DIRECTION = 'auto';
const DEFAULT_ANIMATED = undefined;

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { PickerOptions, pickerController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { PickerOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay';
providedIn: 'root',
})
export class PickerController extends OverlayBaseController<PickerOptions, HTMLIonPickerElement> {
constructor() {
super(pickerController);
constructor(@Inject(DOCUMENT) doc: any) {
super('ion-picker-controller', doc);
}
}

View File

@@ -1,5 +1,5 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, NgZone } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { BackButtonEventDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core';
import { Subject, Subscription } from 'rxjs';
@@ -42,30 +42,29 @@ export class Platform {
*/
resize = new Subject<void>();
constructor(@Inject(DOCUMENT) private doc: any, zone: NgZone) {
zone.run(() => {
this.win = doc.defaultView;
this.backButton.subscribeWithPriority = function(priority, callback) {
return this.subscribe(ev => (
ev.register(priority, () => zone.run(callback))
));
};
constructor(@Inject(DOCUMENT) private doc: any) {
this.win = doc.defaultView;
proxyEvent(this.pause, doc, 'pause');
proxyEvent(this.resume, doc, 'resume');
proxyEvent(this.backButton, doc, 'ionBackButton');
proxyEvent(this.resize, this.win, 'resize');
this.backButton.subscribeWithPriority = function(priority, callback) {
return this.subscribe(ev => {
ev.register(priority, callback);
});
};
let readyResolve: (value: string) => void;
this._readyPromise = new Promise(res => { readyResolve = res; });
if (this.win && this.win['cordova']) {
doc.addEventListener('deviceready', () => {
readyResolve('cordova');
}, { once: true });
} else {
readyResolve!('dom');
}
});
proxyEvent(this.pause, doc, 'pause');
proxyEvent(this.resume, doc, 'resume');
proxyEvent(this.backButton, doc, 'ionBackButton');
proxyEvent(this.resize, this.win, 'resize');
let readyResolve: (value: string) => void;
this._readyPromise = new Promise(res => { readyResolve = res; });
if (this.win && this.win['cordova']) {
doc.addEventListener('deviceready', () => {
readyResolve('cordova');
}, { once: true });
} else {
readyResolve!('dom');
}
}
/**

View File

@@ -1,5 +1,6 @@
import { ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
import { PopoverOptions, popoverController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { ComponentFactoryResolver, Inject, Injectable, Injector } from '@angular/core';
import { PopoverOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -12,8 +13,9 @@ export class PopoverController extends OverlayBaseController<PopoverOptions, HTM
private angularDelegate: AngularDelegate,
private resolver: ComponentFactoryResolver,
private injector: Injector,
@Inject(DOCUMENT) doc: any
) {
super(popoverController);
super('ion-popover-controller', doc);
}
create(opts: PopoverOptions): Promise<HTMLIonPopoverElement> {

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { ToastOptions, toastController } from '@ionic/core';
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { ToastOptions } from '@ionic/core';
import { OverlayBaseController } from '../util/overlay';
@@ -7,7 +8,7 @@ import { OverlayBaseController } from '../util/overlay';
providedIn: 'root',
})
export class ToastController extends OverlayBaseController<ToastOptions, HTMLIonToastElement> {
constructor() {
super(toastController);
constructor(@Inject(DOCUMENT) doc: any) {
super('ion-toast-controller', doc);
}
}

View File

@@ -2,7 +2,7 @@ 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 { addModuleImportToRootModule } from './../utils/ast';
import { addArchitectBuilder, addStyle, getWorkspace, addAsset, WorkspaceProject, WorkspaceSchema } from './../utils/config';
import { addArchitectBuilder, addStyle, getWorkspace, addAsset } from './../utils/config';
import { addPackageToPackageJson } from './../utils/package';
import { Schema as IonAddOptions } from './schema';
@@ -25,7 +25,7 @@ function addIonicAngularToolkitToPackageJson(): Rule {
};
}
function addIonicAngularModuleToAppModule(projectSourceRoot: Path): Rule {
function addIonicAngularModuleToAppModule(projectSourceRoot): Rule {
return (host: Tree) => {
addModuleImportToRootModule(
host,
@@ -37,7 +37,7 @@ function addIonicAngularModuleToAppModule(projectSourceRoot: Path): Rule {
};
}
function addIonicStyles(projectSourceRoot: Path): Rule {
function addIonicStyles(): Rule {
return (host: Tree) => {
const ionicStyles = [
'node_modules/@ionic/angular/css/normalize.css',
@@ -49,7 +49,7 @@ function addIonicStyles(projectSourceRoot: Path): Rule {
'node_modules/@ionic/angular/css/text-alignment.css',
'node_modules/@ionic/angular/css/text-transformation.css',
'node_modules/@ionic/angular/css/flex-utils.css',
`${projectSourceRoot}/theme/variables.css`
'src/theme/variables.css'
].forEach(entry => {
addStyle(host, entry);
});
@@ -69,29 +69,29 @@ function addIonicons(): Rule {
};
}
function addIonicBuilder(projectName: string): Rule {
function addIonicBuilder(): Rule {
return (host: Tree) => {
addArchitectBuilder(host, 'ionic-cordova-serve', {
builder: '@ionic/angular-toolkit:cordova-serve',
options: {
cordovaBuildTarget: `${projectName}:ionic-cordova-build`,
devServerTarget: `${projectName}:serve`
cordovaBuildTarget: 'app:ionic-cordova-build',
devServerTarget: 'app:serve'
},
configurations: {
production: {
cordovaBuildTarget: `${projectName}:ionic-cordova-build:production`,
devServerTarget: `${projectName}:serve:production`
cordovaBuildTarget: 'app:ionic-cordova-build:production',
devServerTarget: 'app:serve:production'
}
}
});
addArchitectBuilder(host, 'ionic-cordova-build', {
builder: '@ionic/angular-toolkit:cordova-build',
options: {
browserTarget: `${projectName}:build`
browserTarget: 'app:build'
},
configurations: {
production: {
browserTarget: `${projectName}:build:production`
browserTarget: 'app:build:production'
}
}
});
@@ -100,24 +100,25 @@ function addIonicBuilder(projectName: string): Rule {
}
function installNodeDeps() {
return (_host: Tree, context: SchematicContext) => {
return (host: Tree, context: SchematicContext) => {
context.addTask(new NodePackageInstallTask());
};
}
export default function ngAdd(options: IonAddOptions): Rule {
return (host: Tree) => {
const workspace: WorkspaceSchema = getWorkspace(host);
const workspace = getWorkspace(host);
if (!options.project) {
options.project = Object.keys(workspace.projects)[0];
}
const project: WorkspaceProject = workspace.projects[options.project];
const project = workspace.projects[options.project];
if (project.projectType !== 'application') {
throw new SchematicsException(
`Ionic Add requires a project type of "application".`
);
}
const sourcePath: Path = join(project.sourceRoot as Path);
const sourcePath = join(project.root as Path, 'src');
const rootTemplateSource = apply(url('./files/root'), [
template({ ...options }),
move(sourcePath)
@@ -127,8 +128,8 @@ export default function ngAdd(options: IonAddOptions): Rule {
addIonicAngularToPackageJson(),
addIonicAngularToolkitToPackageJson(),
addIonicAngularModuleToAppModule(sourcePath),
addIonicBuilder(options.project),
addIonicStyles(sourcePath),
addIonicBuilder(),
addIonicStyles(),
addIonicons(),
mergeWith(rootTemplateSource),
// install freshly added dependencies

View File

@@ -89,7 +89,6 @@ export function addArchitectBuilder(host: Tree, builderName: string, builderOpts
}
export type WorkspaceSchema = experimental.workspace.WorkspaceSchema;
export type WorkspaceProject = experimental.workspace.WorkspaceProject;
export function getWorkspacePath(host: Tree): string {
const possibleFiles = ['/angular.json', '/.angular.json'];

View File

@@ -1,15 +1,13 @@
export interface IonicGlobal {
config?: any;
ael?: (elm: any, eventName: string, cb: (ev: Event) => void, opts: any) => void;
raf?: (ts: number) => void;
rel?: (elm: any, eventName: string, cb: (ev: Event) => void, opts: any) => void;
asyncQueue?: boolean;
}
export interface IonicWindow extends Window {
Ionic: IonicGlobal;
__zone_symbol__requestAnimationFrame?: (ts: FrameRequestCallback) => number;
}
export interface HTMLStencilElement extends HTMLElement {
componentOnReady(): Promise<this>;
forceUpdate(): void;
__zone_symbol__requestAnimationFrame: (ts: number) => void;
}

View File

@@ -25,6 +25,9 @@ export class IonicRouteStrategy implements RouteReuseStrategy {
if (future.routeConfig !== curr.routeConfig) {
return false;
}
if (future.component !== curr.component) {
return false;
}
// checking router params
const futureParams = future.params;

View File

@@ -1,32 +1,26 @@
import { proxyMethod } from './util';
interface ControllerShape<Opts, HTMLElm> {
create(options: Opts): Promise<HTMLElm>;
dismiss(data?: any, role?: string, id?: string): Promise<boolean>;
getTop(): Promise<HTMLElm | undefined>;
}
export class OverlayBaseController<Opts, Overlay> implements ControllerShape<Opts, Overlay> {
constructor(private ctrl: ControllerShape<Opts, Overlay>) {}
export class OverlayBaseController<Opts, Overlay> {
constructor(private ctrl: string, private doc: Document) {}
/**
* Creates a new overlay
*/
create(opts?: Opts) {
// TODO: next major release opts is not optional
return this.ctrl.create((opts || {}) as any);
create(opts?: Opts): Promise<Overlay> {
return proxyMethod(this.ctrl, this.doc, 'create', opts);
}
/**
* When `id` is not provided, it dismisses the top overlay.
*/
dismiss(data?: any, role?: string, id?: string) {
return this.ctrl.dismiss(data, role, id);
dismiss(data?: any, role?: string, id?: string): Promise<void> {
return proxyMethod(this.ctrl, this.doc, 'dismiss', data, role, id);
}
/**
* Returns the top overlay.
*/
getTop() {
return this.ctrl.getTop();
getTop(): Promise<Overlay | undefined> {
return proxyMethod(this.ctrl, this.doc, 'getTop');
}
}

View File

@@ -1,13 +1,15 @@
declare const __zone_symbol__requestAnimationFrame: any;
declare const requestAnimationFrame: any;
export function proxyMethod(ctrlName: string, doc: Document, methodName: string, ...args: any[]) {
const controller = ensureElementInBody(ctrlName, doc);
return controller.componentOnReady()
.then(() => (controller as any)[methodName].apply(controller, args));
}
export const raf = (h: any) => {
if (typeof __zone_symbol__requestAnimationFrame === 'function') {
return __zone_symbol__requestAnimationFrame(h);
export function ensureElementInBody(elementName: string, doc: Document) {
let element = doc.querySelector(elementName);
if (!element) {
element = doc.createElement(elementName);
doc.body.appendChild(element);
}
if (typeof requestAnimationFrame === 'function') {
return requestAnimationFrame(h);
}
return setTimeout(h);
};
return element as HTMLStencilElement;
}

View File

@@ -13,27 +13,18 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist/test-app/browser",
"outputPath": "dist/test-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"buildOptimizer": true,
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
{
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
}
"src/assets"
],
"styles": [
"src/styles.css"
],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
@@ -59,10 +50,6 @@
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
}
@@ -90,13 +77,37 @@
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"styles": ["src/styles.css"],
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": ["src/favicon.ico", "src/assets"]
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"test-app-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
@@ -106,63 +117,20 @@
"configurations": {
"production": {
"devServerTarget": "test-app:serve:production"
},
"ci": {
"devServerTarget": "test-app:serve:ci"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["tsconfig.app.json", "tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/test-app/server",
"main": "server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
"outputHashing": "media",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"sourceMap": false,
"optimization": true
}
}
},
"serve-ssr": {
"builder": "@nguniversal/builders:ssr-dev-server",
"options": {
"browserTarget": "test-app:build",
"serverTarget": "test-app:server"
},
"configurations": {
"production": {
"browserTarget": "test-app:build:production",
"serverTarget": "test-app:server:production"
}
}
},
"prerender": {
"builder": "@nguniversal/builders:prerender",
"options": {
"browserTarget": "test-app:build:production",
"serverTarget": "test-app:server:production",
"routes": []
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "test-app"
}
}

View File

@@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@@ -1,35 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
'./**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=400,1000", "--start-maximized" ]
browserName: 'chrome',
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=400,1000", "--start-maximized" ]
}
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 100000,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
};

View File

@@ -4,13 +4,12 @@ import { handleErrorMessages, setProperty, getText, waitTime } from './utils';
describe('form', () => {
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
describe('change', () => {
beforeEach(async () => {
await browser.get('/form');
await waitTime(30);
});
it('should have default values', async () => {
@@ -82,7 +81,6 @@ describe('form', () => {
describe('blur', () => {
beforeEach(async () => {
await browser.get('/form#blur');
await waitTime(30);
});
it('ion-toggle should change only after blur', async () => {

View File

@@ -1,14 +1,13 @@
import { browser, element, by } from 'protractor';
import { getProperty, setProperty, handleErrorMessages, waitTime } from './utils';
import { getProperty, setProperty, handleErrorMessages } from './utils';
describe('inputs', () => {
beforeEach(async () => {
await browser.get('/inputs');
await waitTime(30);
});
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should have default value', async () => {
@@ -60,10 +59,4 @@ describe('inputs', () => {
expect(await element(by.css('#select-note')).getText()).toEqual('playstation');
expect(await element(by.css('#range-note')).getText()).toEqual('20');
});
it('nested components should not interfere with NgModel', async () => {
expect(await element(by.css('#range-note')).getText()).toEqual('10');
await element(by.css('#nested-toggle')).click();
expect(await element(by.css('#range-note')).getText()).toEqual('10');
});
});

View File

@@ -5,10 +5,9 @@ describe('modals', () => {
beforeEach(async () => {
await browser.get('/modals');
await waitTime(30);
});
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should open standalone modal and close', async () => {

View File

@@ -4,44 +4,9 @@ import { handleErrorMessages, waitTime, testStack } from './utils';
describe('navigation', () => {
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
// TODO: Fix flaky tests
xit ('should swipe and abort', async () => {
await browser.get('/router-link?ionic:mode=ios');
await waitTime(500);
await element(by.css('#routerLink')).click();
await waitTime(500);
await swipeLeft(5);
await waitTime(500);
const pageHidden = element(by.css('app-router-link'));
expect(await pageHidden.getAttribute('aria-hidden')).toEqual('true');
expect(await pageHidden.getAttribute('class')).toEqual('ion-page ion-page-hidden');
const pageVisible = element(by.css('app-router-link-page'));
expect(await pageVisible.getAttribute('aria-hidden')).toEqual(null);
expect(await pageVisible.getAttribute('class')).toEqual('ion-page can-go-back');
});
xit ('should swipe and go back', async () => {
await browser.get('/router-link?ionic:mode=ios');
await waitTime(500);
await element(by.css('#routerLink')).click();
await waitTime(500);
await testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
await swipeLeft(300);
await waitTime(1000);
await testStack('ion-router-outlet', ['app-router-link']);
const page = element(by.css('app-router-link'));
expect(await page.getAttribute('aria-hidden')).toEqual(null);
expect(await page.getAttribute('class')).toEqual('ion-page');
})
it('should navigate correctly', async () => {
await browser.get('/navigation/page1');
await waitTime(2000);
@@ -57,17 +22,3 @@ describe('navigation', () => {
});
});
function swipeLeft(end: number) {
return browser.driver.touchActions()
.tapAndHold({x: 5, y: 1})
.move({x: 6, y: 1})
.move({x: 7, y: 1})
.move({x: 8, y: 1})
.move({x: 30, y: 1})
.move({x: 300, y: 1})
.move({x: end, y: 1})
.move({x: end, y: 1})
.release({x: end, y: 1})
.perform();
}

View File

@@ -4,7 +4,7 @@ import { waitTime, handleErrorMessages, goBack } from './utils';
describe('nested-outlet', () => {
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should navigate correctly', async () => {

View File

@@ -4,7 +4,7 @@ import { handleErrorMessages, waitTime } from './utils';
describe('providers', () => {
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should load all providers', async () => {
@@ -12,9 +12,6 @@ describe('providers', () => {
expect(await element(by.css('#is-loaded')).getText()).toEqual('true');
expect(await element(by.css('#is-ready')).getText()).toEqual('true');
expect(await element(by.css('#is-paused')).getText()).toEqual('true');
expect(await element(by.css('#is-resumed')).getText()).toEqual('true');
expect(await element(by.css('#is-resized')).getText()).toEqual('true');
expect(await element(by.css('#is-testing')).getText()).toEqual('false');
expect(await element(by.css('#is-desktop')).getText()).toEqual('true');
expect(await element(by.css('#is-mobile')).getText()).toEqual('false');

View File

@@ -1,61 +1,13 @@
import { browser, element, by, protractor } from 'protractor';
import { waitTime, testStack, testLifeCycle, handleErrorMessages, getText } from './utils';
const EC = protractor.ExpectedConditions;
describe('router-link params and fragments', () => {
const queryParam = 'A&=#Y';
const fragment = 'myDiv1';
const id = 'MyPageID==';
afterEach(() => {
return handleErrorMessages();
});
it('should go to a page with properly encoded values', async () => {
await browser.get('/router-link?ionic:_testing=true');
await element(by.css('#queryParamsFragment')).click();
const expectedRoute = `${encodeURIComponent(id)}?token=${encodeURIComponent(queryParam)}#${encodeURIComponent(fragment)}`;
browser.wait(EC.urlContains(expectedRoute), 5000);
});
it('should return to a page with preserved query param and fragment', async () => {
await browser.get('/router-link?ionic:_testing=true');
await waitTime(30);
await element(by.css('#queryParamsFragment')).click();
await waitTime(400);
await element(by.css('#goToPage3')).click();
browser.wait(EC.urlContains('router-link-page3'), 5000);
await waitTime(400);
await element(by.css('#goBackFromPage3')).click();
const expectedRoute = `${encodeURIComponent(id)}?token=${encodeURIComponent(queryParam)}#${encodeURIComponent(fragment)}`;
browser.wait(EC.urlContains(expectedRoute), 5000);
});
it('should preserve query param and fragment with defaultHref string', async () => {
await browser.get('/router-link-page3?ionic:_testing=true');
await waitTime(30);
await element(by.css('#goBackFromPage3')).click();
const expectedRoute = '?token=ABC#fragment';
browser.wait(EC.urlContains(expectedRoute), 5000);
});
});
import { browser, element, by } from 'protractor';
import { waitTime, testStack, testLifeCycle, handleErrorMessages } from './utils';
describe('router-link', () => {
beforeEach(async () => {
await browser.get('/router-link');
await waitTime(30);
});
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
@@ -73,6 +25,18 @@ describe('router-link', () => {
it('should go forward with ion-button[routerLink]', async () => {
await element(by.css('#routerLink')).click();
await testForward();
// test go back
await element(by.css('ion-back-button')).click();
await waitTime(500);
await testStack('ion-router-outlet', ['app-router-link']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 2,
ionViewDidEnter: 2,
ionViewWillLeave: 1,
ionViewDidLeave: 1,
});
});
it('should go forward with a[routerLink]', async () => {
@@ -113,6 +77,7 @@ describe('router-link', () => {
it('should go back with ion-button[routerLink][routerDirection=back]', async () => {
await element(by.css('#routerLink-back')).click();
await testBack();
});
it('should go back with a[routerLink][routerDirection=back]', async () => {
@@ -128,25 +93,21 @@ describe('router-link', () => {
});
async function testForward() {
await waitTime(2500);
await waitTime(500);
await testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 1,
ionViewDidLeave: 1,
});
await testLifeCycle('app-router-link-page', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('true');
await browser.navigate().back();
await waitTime(100);
await testStack('ion-router-outlet', ['app-router-link']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 2,
ionViewDidEnter: 2,
ionViewWillLeave: 1,
ionViewDidLeave: 1,
});
}
async function testRoot() {
@@ -158,17 +119,6 @@ async function testRoot() {
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('false');
await browser.navigate().back();
await waitTime(100);
await testStack('ion-router-outlet', ['app-router-link']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
}
async function testBack() {
@@ -180,15 +130,4 @@ async function testBack() {
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('false');
await browser.navigate().back();
await waitTime(100);
await testStack('ion-router-outlet', ['app-router-link']);
await testLifeCycle('app-router-link', {
ionViewWillEnter: 1,
ionViewDidEnter: 1,
ionViewWillLeave: 0,
ionViewDidLeave: 0,
});
}

View File

@@ -5,10 +5,9 @@ describe('slides', () => {
beforeEach(async () => {
await browser.get('/slides');
await waitTime(30);
});
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should change index on slide change', async () => {

View File

@@ -1,14 +1,13 @@
import { browser, by, element, ElementFinder, ExpectedConditions } from 'protractor';
import { handleErrorMessages, testStack, waitTime } from './utils';
import { browser, element, by, ElementFinder } from 'protractor';
import { waitTime, testStack, handleErrorMessages } from './utils';
describe('tabs', () => {
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
describe('entry url - /tabs', () => {
beforeEach(async () => {
await browser.get('/tabs');
await waitTime(30);
});
it('should redirect and load tab-account', async () => {
@@ -17,19 +16,10 @@ describe('tabs', () => {
await testState(1, 'account');
});
it('should navigate between tabs and ionChange events should be dispatched ', async () => {
let tab = await testTabTitle('Tab 1 - Page 1');
expect(await tab.$('.segment-changed').getText()).toEqual('false');
await element(by.css('#tab-button-contact')).click();
tab = await testTabTitle('Tab 2 - Page 1');
expect(await tab.$('.segment-changed').getText()).toEqual('false');
});
it('should simulate stack + double tab click', async () => {
let tab = await getSelectedTab() as ElementFinder;
await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testTabTitle('Tab 1 - Page 2');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']);
await testState(1, 'account');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
@@ -40,7 +30,7 @@ describe('tabs', () => {
await testState(2, 'contact');
await element(by.css('#tab-button-account')).click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
tab = await testTabTitle('Tab 1 - Page 2');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
await testState(3, 'account');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
@@ -54,7 +44,7 @@ describe('tabs', () => {
it('should simulate stack + back button click', async () => {
const tab = await getSelectedTab();
await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testTabTitle('Tab 1 - Page 2');
await testState(1, 'account');
await element(by.css('#tab-button-contact')).click();
@@ -62,7 +52,7 @@ describe('tabs', () => {
await testState(2, 'contact');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testTabTitle('Tab 1 - Page 2');
await testState(3, 'account');
await element(by.css('ion-back-button')).click();
@@ -71,33 +61,6 @@ describe('tabs', () => {
await testState(3, 'account');
});
it('should navigate deep then go home', async () => {
let tab = await getSelectedTab();
await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await element(by.css('#tab-button-contact')).click();
tab = await testTabTitle('Tab 2 - Page 1');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 2 (2)');
await testStack('ion-tabs ion-router-outlet', [
'app-tabs-tab1',
'app-tabs-tab1-nested',
'app-tabs-tab1-nested',
'app-tabs-tab2'
]);
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testStack('ion-tabs ion-router-outlet', [
'app-tabs-tab1',
'app-tabs-tab2'
]);
});
it('should switch tabs and go back', async () => {
await element(by.css('#tab-button-contact')).click();
const tab = await testTabTitle('Tab 2 - Page 1');
@@ -112,7 +75,7 @@ describe('tabs', () => {
const tab = await testTabTitle('Tab 2 - Page 1');
await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testTabTitle('Tab 1 - Page 2');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
});
@@ -131,104 +94,15 @@ describe('tabs', () => {
await testTabTitle('Tab 3 - Page 1');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']);
});
it('should preserve navigation extras when switching tabs', async () => {
const expectUrlToContain = 'search=hello#fragment';
let tab = await getSelectedTab() as ElementFinder;
await tab.$('#goto-nested-page1-with-query-params').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testUrlContains(expectUrlToContain);
await element(by.css('#tab-button-contact')).click();
await testTabTitle('Tab 2 - Page 1');
await element(by.css('#tab-button-account')).click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
await testUrlContains(expectUrlToContain);
});
it('should set root when clicking on an active tab to navigate to the root', async () => {
const expectNestedTabUrlToContain = 'search=hello#fragment';
let tab = await getSelectedTab() as ElementFinder;
const initialUrl = await browser.getCurrentUrl();
await tab.$('#goto-nested-page1-with-query-params').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testUrlContains(expectNestedTabUrlToContain);
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testUrlEquals(initialUrl);
});
});
describe('entry tab contains navigation extras', () => {
const expectNestedTabUrlToContain = 'search=hello#fragment';
const rootUrlParams = 'test=123#rootFragment';
const rootUrl = `/tabs/account?${rootUrlParams}`;
describe('entry url - /tabs/account/nested/12', () => {
beforeEach(async () => {
await browser.get(rootUrl);
await waitTime(30);
});
it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', async () => {
await browser.get(rootUrl);
let tab = await getSelectedTab() as ElementFinder;
await tab.$('#goto-nested-page1-with-query-params').click();
await testTabTitle('Tab 1 - Page 2 (1)');
await testUrlContains(expectNestedTabUrlToContain);
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testUrlContains(rootUrl);
});
it('should preserve root url navigation extras when changing tabs', async () => {
await browser.get(rootUrl);
let tab = await getSelectedTab() as ElementFinder;
await element(by.css('#tab-button-contact')).click();
tab = await testTabTitle('Tab 2 - Page 1');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testUrlContains(rootUrl);
});
it('should navigate deep then go home and preserve navigation extras', async () => {
let tab = await getSelectedTab();
await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await element(by.css('#tab-button-contact')).click();
tab = await testTabTitle('Tab 2 - Page 1');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 2 (2)');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testUrlContains(rootUrl);
});
});
describe('entry url - /tabs/account/nested/1', () => {
beforeEach(async () => {
await browser.get('/tabs/account/nested/1');
await waitTime(30);
await browser.get('/tabs/account/nested/12');
});
it('should only display the back-button when there is a page in the stack', async () => {
let tab = await testTabTitle('Tab 1 - Page 2 (1)') as ElementFinder;
let tab = await testTabTitle('Tab 1 - Page 2') as ElementFinder;
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
@@ -236,38 +110,14 @@ describe('tabs', () => {
tab = await testTabTitle('Tab 1 - Page 1');
await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
tab = await testTabTitle('Tab 1 - Page 2');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
});
it('should not reuse the same page', async () => {
let tab = await testTabTitle('Tab 1 - Page 2 (1)') as ElementFinder;
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (3)');
await testStack('ion-tabs ion-router-outlet', [
'app-tabs-tab1-nested',
'app-tabs-tab1-nested',
'app-tabs-tab1-nested'
]);
await tab.$('ion-back-button').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await tab.$('ion-back-button').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
});
});
describe('entry url - /tabs/lazy', () => {
beforeEach(async () => {
await browser.get('/tabs/lazy');
await waitTime(30);
});
it('should not display the back-button if coming from a different stack', async () => {
@@ -275,29 +125,27 @@ describe('tabs', () => {
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
tab = await testTabTitle('Tab 1 - Page 2');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']);
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
});
});
describe('enter url - /tabs/contact/one', () => {
beforeEach(async () => {
await browser.get('/tabs/contact/one');
await waitTime(30);
});
it('should return to correct tab after going to page in different outlet', async () => {
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 waitTime(600);
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');
});
})
@@ -308,24 +156,12 @@ async function testState(count: number, tab: string) {
}
async function testTabTitle(title: string) {
await waitTime(1000);
await waitTime(600);
const tab = await getSelectedTab();
expect(await tab.$('ion-title').getText()).toEqual(title);
return tab;
}
async function testUrlContains(urlFragment: string) {
await browser.wait(ExpectedConditions.urlContains(urlFragment),
5000,
`expected ${browser.getCurrentUrl()} to contain ${urlFragment}`);
}
async function testUrlEquals(url: string) {
await browser.wait(ExpectedConditions.urlIs(url),
5000,
`expected ${browser.getCurrentUrl()} to equal ${url}`);
}
async function getSelectedTab(): Promise<ElementFinder> {
const tabs = element.all(by.css('ion-tabs ion-router-outlet > *:not(.ion-page-hidden)'));
expect(await tabs.count()).toEqual(1);

View File

@@ -37,30 +37,29 @@ export interface LifeCycleCount {
}
export function handleErrorMessages() {
return browser.manage().logs().get('browser').then(function (browserLog) {
for (let i = 0; i <= browserLog.length - 1; i++) {
if (browserLog[i].level.name_ === 'SEVERE') {
fail(browserLog[i].message);
}
browser.manage().logs().get('browser').then(function(browserLog) {
let severWarnings = false;
for (let i; i <= browserLog.length - 1; i++) {
if (browserLog[i].level.name === 'SEVERE') {
console.log('\n' + browserLog[i].level.name);
console.log('(Possibly exception) \n' + browserLog[i].message);
severWarnings = true;
}
}
expect(severWarnings).toBe(false);
});
}
export async function testLifeCycle(selector: string, expected: LifeCycleCount) {
await waitTime(50);
const results = await Promise.all([
getText(`${selector} #ngOnInit`),
getText(`${selector} #ionViewWillEnter`),
getText(`${selector} #ionViewDidEnter`),
getText(`${selector} #ionViewWillLeave`),
getText(`${selector} #ionViewDidLeave`),
]);
expect(results[0]).toEqual('1');
expect(results[1]).toEqual(expected.ionViewWillEnter.toString());
expect(results[2]).toEqual(expected.ionViewDidEnter.toString());
expect(results[3]).toEqual(expected.ionViewWillLeave.toString());
expect(results[4]).toEqual(expected.ionViewDidLeave.toString());
expect(await getText(`${selector} #ngOnInit`)).toEqual('1');
expect(await getText(`${selector} #ionViewWillEnter`)).toEqual(expected.ionViewWillEnter.toString());
expect(await getText(`${selector} #ionViewDidEnter`)).toEqual(expected.ionViewDidEnter.toString());
expect(await getText(`${selector} #ionViewWillLeave`)).toEqual(expected.ionViewWillLeave.toString());
expect(await getText(`${selector} #ionViewDidLeave`)).toEqual(expected.ionViewDidLeave.toString());
}
export async function testStack(selector: string, expected: string[]) {

View File

@@ -1,14 +1,13 @@
import { browser, element, by } from 'protractor';
import { handleErrorMessages, waitTime } from './utils';
import { handleErrorMessages } from './utils';
describe('view-child', () => {
beforeEach(async () => {
await browser.get('/view-child');
await waitTime(30);
});
afterEach(() => {
return handleErrorMessages();
handleErrorMessages();
});
it('should get a reference to all children', async () => {

View File

@@ -1,18 +0,0 @@
import { browser, element, by } from 'protractor';
import { waitTime, handleErrorMessages } from './utils';
describe('virtual-scroll', () => {
afterEach(() => {
return handleErrorMessages();
});
beforeEach(async () => {
await browser.get('/virtual-scroll');
await waitTime(30);
});
it('should open virtual-scroll', () => {
const virtualElements = element.all(by.css('ion-virtual-scroll > *'));
expect(virtualElements.count()).toBeGreaterThan(0);
});
});

11108
angular/test/test-app/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,51 @@
{
"name": "ionic-angular-test-app",
"name": "test-app",
"version": "0.0.0",
"private": true,
"scripts": {
"ng": "ng",
"start": "npm run sync && ng serve",
"sync:build": "sh scripts/build-ionic.sh",
"sync": "sh scripts/sync.sh",
"build": "npm run sync && ng build --prod --no-progress",
"build": "ng build --prod --no-progress",
"test": "ng e2e --prod",
"test.dev": "npm run sync && ng e2e",
"lint": "ng lint",
"postinstall": "npm run sync && ngcc",
"serve:ssr": "node dist/test-app/server/main.js",
"build:ssr": "ng build --prod && ng run test-app:server:production",
"dev:ssr": "ng run test-app:serve-ssr",
"prerender": "ng run test-app:prerender"
"postinstall": "npm run sync"
},
"private": true,
"dependencies": {
"@angular/animations": "^9.0.0-rc.2",
"@angular/common": "^9.0.0-rc.2",
"@angular/compiler": "^9.0.0-rc.2",
"@angular/core": "^9.0.0-rc.2",
"@angular/forms": "^9.0.0-rc.2",
"@angular/platform-browser": "^9.0.0-rc.2",
"@angular/platform-browser-dynamic": "^9.0.0-rc.2",
"@angular/platform-server": "^9.0.0-rc.2",
"@angular/router": "^9.0.0-rc.2",
"@ionic/angular": "^4.7.0",
"@ionic/angular-server": "^0.0.2",
"@nguniversal/express-engine": "9.0.0-next.9",
"@angular/animations": "~7.2.1",
"@angular/common": "~7.2.1",
"@angular/compiler": "~7.2.1",
"@angular/core": "~7.2.1",
"@angular/forms": "~7.2.1",
"@angular/platform-browser": "~7.2.1",
"@angular/platform-browser-dynamic": "~7.2.1",
"@angular/router": "~7.2.1",
"@ionic/angular": "^4.0.0-rc.1",
"core-js": "^2.6.2",
"express": "^4.15.2",
"rxjs": "^6.5.3",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
"rxjs": "~6.3.3",
"tslib": "^1.9.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.0-rc.2",
"@angular/cli": "^9.0.0-rc.2",
"@angular/compiler-cli": "^9.0.0-rc.2",
"@angular/language-service": "^9.0.0-rc.2",
"@nguniversal/builders": "^9.0.0-next.9",
"@types/express": "^4.17.0",
"@types/jasmine": "3.4.1",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"@angular-devkit/build-angular": "~0.12.2",
"@angular/cli": "~7.2.1",
"@angular/compiler-cli": "~7.2.1",
"@angular/language-service": "~7.2.1",
"@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"karma": "~3.1.4",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.4.2",
"ts-loader": "^6.1.2",
"ts-node": "8.4.1",
"tslint": "~5.18.0",
"typescript": "~3.6.4",
"webpack-cli": "^3.3.9"
"ts-node": "~7.0.0",
"tslint": "~5.12.1",
"typescript": "~3.2.4"
}
}

View File

@@ -10,14 +10,6 @@ popd
pushd angular
npm link @ionic/core
npm run build
npm link
popd
# Build angular-server
pushd packages/angular-server
npm link @ionic/core
npm link @ionic/angular
npm run build
popd
popd

View File

@@ -1,20 +1,14 @@
# Copy angular dist
rm -rf node_modules/@ionic/angular
cp -a ../../dist node_modules/@ionic/angular
rm -rf node_modules/@ionic/angular/dist
cp -a ../../dist node_modules/@ionic/angular/dist
cp -a ../../package.json node_modules/@ionic/angular/package.json
# Copy angular server
rm -rf node_modules/@ionic/angular-server
cp -a ../../../packages/angular-server/dist node_modules/@ionic/angular-server
# # Copy core dist
rm -rf node_modules/@ionic/core
mkdir node_modules/@ionic/core
cp -a ../../../core/css node_modules/@ionic/core/css
# Copy core dist
rm -rf node_modules/@ionic/core/dist
cp -a ../../../core/dist node_modules/@ionic/core/dist
cp -a ../../../core/hydrate node_modules/@ionic/core/hydrate
cp -a ../../../core/loader node_modules/@ionic/core/loader
cp -a ../../../core/package.json node_modules/@ionic/core/package.json
# # Copy ionicons
# Copy ionicons
rm -rf node_modules/ionicons
cp -a ../../../core/node_modules/ionicons node_modules/ionicons

View File

@@ -1,57 +0,0 @@
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/test-app/browser');
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render('index', { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
if (mainModule && mainModule.filename === __filename) {
run();
}
export * from './src/main.server';

View File

@@ -1,10 +0,0 @@
<ion-header>
<ion-toolbar>
<ion-title>
Alert test
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<p>Change Detections: <span id="counter">{{counter()}}</span></p>
</ion-content>

View File

@@ -1,39 +0,0 @@
import { Component, NgZone } from '@angular/core';
import { AlertController } from '@ionic/angular';
import { NavComponent } from '../nav/nav.component';
@Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
})
export class AlertComponent {
changes = 0;
constructor(
private alertCtrl: AlertController
) { }
counter() {
this.changes++;
return Math.floor(this.changes / 2);
}
async openAlert() {
const alert = await this.alertCtrl.create({
header: 'Hello',
message: 'Some text',
buttons: [
{
role: 'cancel',
text: 'Cancel',
handler: () => {
console.log(NgZone.isInAngularZone());
NgZone.assertInAngularZone();
}
}
]
});
await alert.present();
}
}

View File

@@ -4,9 +4,11 @@ import { InputsComponent } from './inputs/inputs.component';
import { ModalComponent } from './modal/modal.component';
import { RouterLinkComponent } from './router-link/router-link.component';
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
import { HomePageComponent } from './home-page/home-page.component';
import { TabsComponent } from './tabs/tabs.component';
import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component';
import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component';
import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component';
import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component';
import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component';
import { NestedOutletComponent } from './nested-outlet/nested-outlet.component';
@@ -19,11 +21,9 @@ import { FormComponent } from './form/form.component';
import { NavigationPage1Component } from './navigation-page1/navigation-page1.component';
import { NavigationPage2Component } from './navigation-page2/navigation-page2.component';
import { NavigationPage3Component } from './navigation-page3/navigation-page3.component';
import { AlertComponent } from './alert/alert.component';
const routes: Routes = [
{ path: '', component: HomePageComponent },
{ path: 'alerts', component: AlertComponent },
{ path: 'inputs', component: InputsComponent },
{ path: 'form', component: FormComponent },
{ path: 'modals', component: ModalComponent },
@@ -31,8 +31,6 @@ const routes: Routes = [
{ path: 'providers', component: ProvidersComponent },
{ path: 'router-link', component: RouterLinkComponent },
{ path: 'router-link-page', component: RouterLinkPageComponent },
{ path: 'router-link-page2/:id', component: RouterLinkPage2Component },
{ path: 'router-link-page3', component: RouterLinkPage3Component },
{ path: 'slides', component: SlidesComponent },
{ path: 'virtual-scroll', component: VirtualScrollComponent },
{ path: 'virtual-scroll-detail/:itemId', component: VirtualScrollDetailComponent },
@@ -47,7 +45,40 @@ const routes: Routes = [
},
{
path: 'tabs',
loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
component: TabsComponent,
children: [
{
path: 'account',
children: [
{
path: 'nested/:id',
component: TabsTab1NestedComponent
},
{
path: '',
component: TabsTab1Component
}
]
},
{
path: 'contact',
children: [
{
path: 'one',
component: TabsTab2Component
},
{
path: '',
redirectTo: 'one',
pathMatch: 'full'
}
]
},
{
path: 'lazy',
loadChildren: './tabs-lazy/tabs-lazy.module#TabsLazyModule'
}
]
},
{
path: 'nested-outlet',

View File

@@ -1,20 +1,21 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { RouteReuseStrategy } from '@angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { InputsComponent } from './inputs/inputs.component';
import { ModalComponent } from './modal/modal.component';
import { ModalExampleComponent } from './modal-example/modal-example.component';
import { RouterLinkComponent } from './router-link/router-link.component';
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
import { HomePageComponent } from './home-page/home-page.component';
import { TabsComponent } from './tabs/tabs.component';
import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component';
import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component';
import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component';
import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component';
import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component';
import { VirtualScrollInnerComponent } from './virtual-scroll-inner/virtual-scroll-inner.component';
@@ -29,7 +30,6 @@ import { FormComponent } from './form/form.component';
import { NavigationPage1Component } from './navigation-page1/navigation-page1.component';
import { NavigationPage2Component } from './navigation-page2/navigation-page2.component';
import { NavigationPage3Component } from './navigation-page3/navigation-page3.component';
import { AlertComponent } from './alert/alert.component';
@NgModule({
declarations: [
@@ -39,9 +39,11 @@ import { AlertComponent } from './alert/alert.component';
ModalExampleComponent,
RouterLinkComponent,
RouterLinkPageComponent,
RouterLinkPage2Component,
RouterLinkPage3Component,
HomePageComponent,
TabsComponent,
TabsTab1Component,
TabsTab2Component,
TabsTab1NestedComponent,
VirtualScrollComponent,
VirtualScrollDetailComponent,
VirtualScrollInnerComponent,
@@ -55,11 +57,10 @@ import { AlertComponent } from './alert/alert.component';
FormComponent,
NavigationPage1Component,
NavigationPage2Component,
NavigationPage3Component,
AlertComponent
NavigationPage3Component
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
@@ -69,9 +70,7 @@ import { AlertComponent } from './alert/alert.component';
ModalExampleComponent,
NavComponent
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -1,15 +0,0 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { IonicServerModule } from '@ionic/angular-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
IonicServerModule
],
bootstrap: [AppComponent],
})
export class AppServerModule {}

View File

@@ -20,9 +20,7 @@ export class FormComponent {
input2: ['Default Value'],
checkbox: [false],
range: [5, Validators.min(10)],
}, {
updateOn: typeof (window as any) !== 'undefined' && window.location.hash === '#blur' ? 'blur' : 'change'
});
}, {updateOn: window.location.hash === '#blur' ? 'blur' : 'change'});
}
onSubmit(_ev) {

View File

@@ -7,11 +7,6 @@
</ion-header>
<ion-content>
<ion-list>
<ion-item routerLink="/alerts">
<ion-label>
Alerts test
</ion-label>
</ion-item>
<ion-item routerLink="/inputs">
<ion-label>
Inputs test

View File

@@ -6,7 +6,6 @@
</ion-toolbar>
</ion-header>
<ion-content>
<p>Change Detections: <span id="counter">{{counter()}}</span></p>
<ion-list>
<ion-item>
@@ -90,12 +89,10 @@
<ion-range [(ngModel)]="range"></ion-range>
<ion-note slot="end" id="range-note">{{range}}</ion-note>
</ion-item>
<ion-item color="dark">
<ion-label>Range Mirror</ion-label>
<ion-range [(ngModel)]="range">
<ion-toggle slot="start" id="nested-toggle" [(ngModel)]="toggle"></ion-toggle>
</ion-range>
<ion-range [(ngModel)]="range"></ion-range>
<ion-note slot="end">{{range}}</ion-note>
</ion-item>

View File

@@ -12,7 +12,6 @@ export class InputsComponent {
toggle = true;
select = 'nes';
range = 10;
changes = 0;
setValues() {
console.log('set values');
@@ -33,8 +32,4 @@ export class InputsComponent {
this.select = undefined;
this.range = undefined;
}
counter() {
this.changes++;
return Math.floor(this.changes / 2);
}
}

View File

@@ -8,7 +8,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content padding>
<h1>Value</h1>
<h2>{{value}}</h2>
<h3>{{valueFromParams}}</h3>

View File

@@ -5,7 +5,7 @@
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content padding>
<ion-button (click)="openModal()" id="action-button">Open Modal</ion-button>
<ion-button (click)="openNav()" id="action-button-2">Open Nav in Modal</ion-button>
<p>

View File

@@ -5,22 +5,13 @@
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content padding>
<p>
isLoaded: <span id="is-loaded">{{isLoaded}}</span>
</p>
<p>
isReady: <span id="is-ready">{{isReady}}</span>
</p>
<p>
isResumed: <span id="is-resumed">{{isResumed}}</span>
</p>
<p>
isPaused: <span id="is-paused">{{isPaused}}</span>
</p>
<p>
isResized: <span id="is-resized">{{isResized}}</span>
</p>
<p>
isTesting: <span id="is-testing">{{isTesting}}</span>
</p>

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