mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
8 Commits
v5.6.8
...
gesture-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926b119231 | ||
|
|
256c7de469 | ||
|
|
b7b5b94cce | ||
|
|
5e1e883585 | ||
|
|
ad747b05f1 | ||
|
|
3b52074a10 | ||
|
|
9eac7266a0 | ||
|
|
12a4c974b4 |
@@ -1,72 +1,51 @@
|
||||
version: 2.1
|
||||
orbs:
|
||||
cypress: cypress-io/cypress@1.27.0
|
||||
version: 2
|
||||
|
||||
aliases:
|
||||
- &restore-cache
|
||||
keys:
|
||||
- dependency-cache-{{ checksum "package.json" }}-7
|
||||
- dependency-cache-{{ checksum "package.json" }}-3
|
||||
|
||||
- &save-cache
|
||||
key: dependency-cache-{{ checksum "package.json" }}-7
|
||||
key: dependency-cache-{{ checksum "package.json" }}-3
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
- &restore-cache-core
|
||||
keys:
|
||||
- dependency-cache-{{ checksum "core/package.json" }}-7
|
||||
- dependency-cache-{{ checksum "core/package.json" }}-3
|
||||
|
||||
- &save-cache-core
|
||||
key: dependency-cache-{{ checksum "core/package.json" }}-7
|
||||
key: dependency-cache-{{ checksum "core/package.json" }}-3
|
||||
paths:
|
||||
- core/node_modules
|
||||
|
||||
- &restore-cache-core-stencil
|
||||
keys:
|
||||
- stencil-cache-6
|
||||
- stencil-cache-2
|
||||
|
||||
- &save-cache-core-stencil
|
||||
key: stencil-cache-6
|
||||
key: stencil-cache-2
|
||||
paths:
|
||||
- core/.stencil
|
||||
- core/screenshot/images
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:latest-browsers
|
||||
- image: circleci/node:10-browsers
|
||||
working_directory: /tmp/workspace
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
|
||||
jobs:
|
||||
puppeteer-dependencies:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- run:
|
||||
name: Install headless Chrome dependencies
|
||||
command: |
|
||||
sudo apt-get install -yq \
|
||||
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
|
||||
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
|
||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
- run:
|
||||
name: Install Puppeteer with Chromium
|
||||
command: |
|
||||
npm i puppeteer
|
||||
|
||||
build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache: *restore-cache
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm install
|
||||
- save_cache: *save-cache
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- node_modules
|
||||
- "*"
|
||||
|
||||
build-core:
|
||||
<<: *defaults
|
||||
@@ -77,7 +56,7 @@ jobs:
|
||||
- restore_cache: *restore-cache-core
|
||||
- restore_cache: *restore-cache-core-stencil
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/core
|
||||
- save_cache: *save-cache-core
|
||||
- run:
|
||||
@@ -87,12 +66,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- core/node_modules
|
||||
- core/dist
|
||||
- core/components
|
||||
- core/css
|
||||
- core/hydrate
|
||||
- core/loader
|
||||
- "*"
|
||||
|
||||
build-angular:
|
||||
<<: *defaults
|
||||
@@ -101,7 +75,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/angular
|
||||
- run:
|
||||
command: sudo npm link
|
||||
@@ -115,8 +89,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- angular/node_modules
|
||||
- angular/dist
|
||||
- "*"
|
||||
|
||||
build-angular-server:
|
||||
<<: *defaults
|
||||
@@ -125,7 +98,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/angular-server
|
||||
- run:
|
||||
command: npm run build.prod
|
||||
@@ -133,7 +106,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/angular-server/dist
|
||||
- "*"
|
||||
|
||||
build-react:
|
||||
<<: *defaults
|
||||
@@ -142,7 +115,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/react
|
||||
- run:
|
||||
command: sudo npm link
|
||||
@@ -156,9 +129,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/react/node_modules
|
||||
- packages/react/dist
|
||||
- packages/react/css
|
||||
- "*"
|
||||
|
||||
build-react-router:
|
||||
<<: *defaults
|
||||
@@ -167,7 +138,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/react-router
|
||||
- run:
|
||||
command: sudo npm link
|
||||
@@ -187,56 +158,7 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/react-router/node_modules
|
||||
- packages/react-router/dist
|
||||
|
||||
build-vue:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- run:
|
||||
command: sudo npm link
|
||||
working_directory: /tmp/workspace/core
|
||||
- run:
|
||||
command: sudo npm link @ionic/core
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- run:
|
||||
command: npm run build
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/vue/node_modules
|
||||
- packages/vue/dist
|
||||
- packages/vue/css
|
||||
|
||||
build-vue-router:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
- run:
|
||||
command: sudo npm link
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- run:
|
||||
command: sudo npm link @ionic/vue
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
- run:
|
||||
command: npm run build
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/vue-router/dist
|
||||
- "*"
|
||||
|
||||
test-core-clean-build:
|
||||
<<: *defaults
|
||||
@@ -266,7 +188,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run test.e2e -- --ci --no-build
|
||||
command: npm run test.e2e --ci
|
||||
working_directory: /tmp/workspace/core
|
||||
|
||||
test-core-spec:
|
||||
@@ -276,7 +198,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run test.spec -- --ci
|
||||
command: npm run test.spec --ci
|
||||
working_directory: /tmp/workspace/core
|
||||
|
||||
test-core-treeshake:
|
||||
@@ -286,7 +208,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run test.treeshake -- --ci
|
||||
command: npm run test.treeshake --ci
|
||||
working_directory: /tmp/workspace/core
|
||||
|
||||
test-core-screenshot:
|
||||
@@ -297,7 +219,7 @@ jobs:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
name: Run Screenshot
|
||||
command: npx stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci --no-build || true
|
||||
command: npx stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci || true
|
||||
working_directory: /tmp/workspace/core
|
||||
|
||||
test-core-screenshot-master:
|
||||
@@ -308,7 +230,7 @@ jobs:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
name: Run Screenshot
|
||||
command: npx stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci --update-screenshot --no-build || true
|
||||
command: npx stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci --update-screenshot || true
|
||||
working_directory: /tmp/workspace/core
|
||||
|
||||
test-angular-lint:
|
||||
@@ -341,26 +263,6 @@ jobs:
|
||||
command: npm run lint
|
||||
working_directory: /tmp/workspace/packages/react-router
|
||||
|
||||
test-vue-lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run lint
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
|
||||
test-vue-router-lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run lint
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
|
||||
test-react-spec:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@@ -399,55 +301,6 @@ jobs:
|
||||
command: npm run test.spec
|
||||
working_directory: /tmp/workspace/packages/react-router
|
||||
|
||||
install-react-test-app:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: CYPRESS_CACHE_FOLDER=/tmp/workspace/packages/react-router/test-app npm install
|
||||
working_directory: /tmp/workspace/packages/react-router/test-app
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/react-router/test-app
|
||||
|
||||
test-react-e2e:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run sync
|
||||
working_directory: /tmp/workspace/packages/react-router/test-app
|
||||
- run:
|
||||
command: CYPRESS_CACHE_FOLDER=/tmp/workspace/packages/react-router/test-app npm run e2e
|
||||
working_directory: /tmp/workspace/packages/react-router/test-app
|
||||
|
||||
test-vue-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/vue
|
||||
- run:
|
||||
command: sudo npm link
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- run:
|
||||
command: sudo npm link @ionic/vue
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
- run:
|
||||
command: npm run test.spec
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
|
||||
test-angular-e2e:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@@ -455,47 +308,19 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/angular/test/test-app
|
||||
- run:
|
||||
command: npm run test
|
||||
working_directory: /tmp/workspace/angular/test/test-app
|
||||
|
||||
install-vue-test-app:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: CYPRESS_CACHE_FOLDER=/tmp/workspace/packages/vue/test-app npm install
|
||||
working_directory: /tmp/workspace/packages/vue/test-app
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- packages/vue/test-app
|
||||
|
||||
test-vue-spec-and-e2e:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm run sync
|
||||
working_directory: /tmp/workspace/packages/vue/test-app
|
||||
working_directory: /tmp/workspace/angular/test/test-app
|
||||
- run:
|
||||
command: npm run test:unit
|
||||
working_directory: /tmp/workspace/packages/vue/test-app
|
||||
- run:
|
||||
command: CYPRESS_CACHE_FOLDER=/tmp/workspace/packages/vue/test-app npm run test:e2e
|
||||
working_directory: /tmp/workspace/packages/vue/test-app
|
||||
command: npm test
|
||||
working_directory: /tmp/workspace/angular/test/test-app
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- puppeteer-dependencies
|
||||
- build
|
||||
- build-core:
|
||||
requires: [build]
|
||||
@@ -504,14 +329,11 @@ workflows:
|
||||
- test-core-lint:
|
||||
requires: [build-core]
|
||||
- test-core-e2e:
|
||||
requires: [puppeteer-dependencies, build-core]
|
||||
requires: [build-core]
|
||||
- test-core-spec:
|
||||
requires: [build-core]
|
||||
# Adam requested we skip this test for now
|
||||
# since it is failing on ES5 code which
|
||||
# will be removed in Ionic Framework v6
|
||||
#- test-core-treeshake:
|
||||
# requires: [build-core]
|
||||
- test-core-treeshake:
|
||||
requires: [build-core]
|
||||
- test-core-screenshot:
|
||||
requires: [build-core]
|
||||
filters:
|
||||
@@ -539,24 +361,6 @@ workflows:
|
||||
requires: [build-react]
|
||||
- test-react-router-spec:
|
||||
requires: [build-react-router]
|
||||
- install-react-test-app:
|
||||
requires: [build-core]
|
||||
- test-react-e2e:
|
||||
requires: [install-react-test-app, build-react, build-react-router]
|
||||
- build-vue:
|
||||
requires: [build-core]
|
||||
- build-vue-router:
|
||||
requires: [build-vue]
|
||||
- test-vue-lint:
|
||||
requires: [build-vue]
|
||||
- test-vue-router-lint:
|
||||
requires: [build-vue-router]
|
||||
- test-vue-router-spec:
|
||||
requires: [build-vue-router]
|
||||
- install-vue-test-app:
|
||||
requires: [build-core]
|
||||
- test-vue-spec-and-e2e:
|
||||
requires: [install-vue-test-app, build-vue, build-vue-router]
|
||||
- test-angular-lint:
|
||||
requires: [build-angular]
|
||||
- test-angular-e2e:
|
||||
|
||||
742
.github/COMPONENT-GUIDE.md
vendored
742
.github/COMPONENT-GUIDE.md
vendored
@@ -1,742 +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)
|
||||
- [Accessibility](#accessibility)
|
||||
* [Checkbox](#checkbox)
|
||||
* [Switch](#switch)
|
||||
- [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)
|
||||
- [RTL](#rtl)
|
||||
|
||||
## 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/)
|
||||
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Checkbox
|
||||
|
||||
#### Example Components
|
||||
|
||||
- [ion-checkbox](https://github.com/ionic-team/ionic/tree/master/core/src/components/checkbox)
|
||||
|
||||
#### VoiceOver
|
||||
|
||||
In order for VoiceOver to work properly with a checkbox component there must be a native `input` with `type="checkbox"`, and `aria-checked` and `role="checkbox"` **must** be on the host element. The `aria-hidden` attribute needs to be added if the checkbox is disabled, preventing iOS users from selecting it:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### NVDA
|
||||
|
||||
It is required to have `aria-checked` on the native input for checked to read properly and `disabled` to prevent tabbing to the input:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Labels
|
||||
|
||||
A helper function has been created to get the proper `aria-label` for the checkbox. This can be imported as `getAriaLabel` like the following:
|
||||
|
||||
```tsx
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
```
|
||||
|
||||
where `el` and `inputId` are the following:
|
||||
|
||||
```tsx
|
||||
export class Checkbox implements ComponentInterface {
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
This can then be added to the `Host` like the following:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
```
|
||||
|
||||
In addition to that, the checkbox input should have a label added:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Hidden Input
|
||||
|
||||
A helper function to render a hidden input has been added, it can be added in the `render`:
|
||||
|
||||
```tsx
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
```
|
||||
|
||||
> This is required for the checkbox to work with forms.
|
||||
|
||||
#### Known Issues
|
||||
|
||||
When using VoiceOver on macOS, Chrome will announce the following when you are focused on a checkbox:
|
||||
|
||||
```
|
||||
currently on a checkbox inside of a checkbox
|
||||
```
|
||||
|
||||
This is a compromise we have to make in order for it to work with the other screen readers & Safari.
|
||||
|
||||
|
||||
### Switch
|
||||
|
||||
#### Example Components
|
||||
|
||||
- [ion-toggle](https://github.com/ionic-team/ionic/tree/master/core/src/components/toggle)
|
||||
|
||||
#### Voiceover
|
||||
|
||||
In order for VoiceOver to work properly with a switch component there must be a native `input` with `type="checkbox"` and `role="switch"`, and `aria-checked` and `role="switch"` **must** be on the host element. The `aria-hidden` attribute needs to be added if the switch is disabled, preventing iOS users from selecting it:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="switch"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### NVDA
|
||||
|
||||
It is required to have `aria-checked` on the native input for checked to read properly and `disabled` to prevent tabbing to the input:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="switch"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Labels
|
||||
|
||||
A helper function has been created to get the proper `aria-label` for the switch. This can be imported as `getAriaLabel` like the following:
|
||||
|
||||
```tsx
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
```
|
||||
|
||||
where `el` and `inputId` are the following:
|
||||
|
||||
```tsx
|
||||
export class Toggle implements ComponentInterface {
|
||||
private inputId = `ion-tg-${toggleIds++}`;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
This can then be added to the `Host` like the following:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="switch"
|
||||
>
|
||||
```
|
||||
|
||||
In addition to that, the checkbox input should have a label added:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="switch"
|
||||
>
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
#### Hidden Input
|
||||
|
||||
A helper function to render a hidden input has been added, it can be added in the `render`:
|
||||
|
||||
```tsx
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
```
|
||||
|
||||
> This is required for the switch to work with forms.
|
||||
|
||||
|
||||
#### Known Issues
|
||||
|
||||
When using VoiceOver on macOS or iOS, Chrome will announce the switch as a checked or unchecked `checkbox`:
|
||||
|
||||
```
|
||||
You are currently on a switch. To select or deselect this checkbox, press Control-Option-Space.
|
||||
```
|
||||
|
||||
There is a WebKit bug open for this: https://bugs.webkit.org/show_bug.cgi?id=196354
|
||||
|
||||
|
||||
## 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) {
|
||||
```
|
||||
|
||||
## RTL
|
||||
|
||||
When you need to support both LTR and RTL modes, try to avoid using values such as `left` and `right`. For certain CSS properties, you can use the appropriate mixin to have this handled for you automatically.
|
||||
|
||||
For example, if you wanted `transform-origin` to be RTL-aware, you would use the `transform-origin` mixin:
|
||||
|
||||
```css
|
||||
@include transform-origin(start, center);
|
||||
```
|
||||
|
||||
This would output `transform-origin: left center` in LTR mode and `transform-origin: right center` in RTL mode.
|
||||
|
||||
These mixins depend on the `:host-context` pseudo-class when used inside of shadow components, which is not supported in WebKit. As a result, these mixins will not work in Safari for macOS and iOS when applied to shadow components.
|
||||
|
||||
To work around this, you should set an RTL class on the host of your component and set your RTL styles by targeting that class:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
class={{
|
||||
'my-cmp-rtl': document.dir === 'rtl'
|
||||
})
|
||||
>
|
||||
...
|
||||
</Host>
|
||||
```
|
||||
|
||||
```css
|
||||
:host {
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
:host(.my-cmp-rtl) {
|
||||
transform-origin: right center;
|
||||
}
|
||||
```
|
||||
30
.github/CONTRIBUTING.md
vendored
30
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,6 @@ Thanks for your interest in contributing to the Ionic Framework! :tada:
|
||||
|
||||
- [Contributing Etiquette](#contributing-etiquette)
|
||||
- [Creating an Issue](#creating-an-issue)
|
||||
* [Creating a Good Code Reproduction](#creating-a-good-code-reproduction)
|
||||
- [Creating a Pull Request](#creating-a-pull-request)
|
||||
* [Setup](#setup)
|
||||
* [Core](#core)
|
||||
@@ -41,40 +40,13 @@ Please see our [Contributor Code of Conduct](https://github.com/ionic-team/ionic
|
||||
|
||||
* The issue list of this repository is exclusively for bug reports and feature requests. Non-conforming issues will be closed immediately.
|
||||
|
||||
* Issues with no clear steps to reproduce will not be triaged. If an issue is labeled with "needs: reply" and receives no further replies from the author of the issue for more than 14 days, it will be closed.
|
||||
* Issues with no clear steps to reproduce will not be triaged. If an issue is labeled with "needs: reply" and receives no further replies from the author of the issue for more than 30 days, it will be closed.
|
||||
|
||||
* If you think you have found a bug, or have a new feature idea, please start by making sure it hasn't already been [reported](https://github.com/ionic-team/ionic/issues?utf8=%E2%9C%93&q=is%3Aissue). You can search through existing issues to see if there is a similar one reported. Include closed issues as it may have been closed with a solution.
|
||||
|
||||
* Next, [create a new issue](https://github.com/ionic-team/ionic/issues/new/choose) that thoroughly explains the problem. Please fill out the populated issue form before submitting the issue.
|
||||
|
||||
|
||||
## Creating a Good Code Reproduction
|
||||
|
||||
### What is a Code Reproduction?
|
||||
|
||||
A code reproduction is a small application that is built to demonstrate a particular issue. The code reproduction should contain the minimum amount of code needed to recreate the issue and should focus on a single issue.
|
||||
|
||||
### Why Should You Create a Reproduction?
|
||||
|
||||
A code reproduction of the issue you are experiencing helps us better isolate the cause of the problem. This is an important first step to getting any bug fixed!
|
||||
|
||||
Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed. In other words, creating a code reproduction of the issue helps us help you.
|
||||
|
||||
### How to Create a Reproduction
|
||||
|
||||
* Create a new Ionic application using one of our starter templates. The `blank` starter application is a great choice for this. You can create one using the following Ionic CLI command: `ionic start myApp blank`
|
||||
* Add the minimum amount of code needed to recreate the issue you are experiencing. Do not include anything that is not required to reproduce the issue. This includes any 3rd party plugins you have installed.
|
||||
* Publish the application on GitHub and include a link to it when [creating an issue](#creating-an-issue).
|
||||
* Be sure to include steps to reproduce the issue. These steps should be clear and easy to follow.
|
||||
|
||||
### Benefits of Creating a Reproduction
|
||||
|
||||
* **Uses the latest version of Ionic:** By creating a new Ionic application, you are ensuring that you are testing against the latest version of the framework. Sometimes the issues you are experiencing have already been resolved in a newer version of the framework!
|
||||
* **Minimal surface area:** By removing code that is not needed in order to reproduce the issue, it makes it easier to identify the cause of the issue.
|
||||
* **No secret code needed:** Creating a minimal reproduction of the issue prevents you from having to publish any proprietary code used in your project.
|
||||
* **Get help fixing the issue:** If we can reliably reproduce an issue, there is a good chance we will be able to address it.
|
||||
|
||||
|
||||
## Creating a Pull Request
|
||||
|
||||
* We appreciate you taking the time to contribute! Before submitting a pull request, we ask that you please [create an issue](#creating-an-issue) that explains the bug or feature request and let us know that you plan on creating a pull request for it. If an issue already exists, please comment on that issue letting us know you would like to submit a pull request for it. This helps us to keep track of the pull request and make sure there isn't duplicated effort.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -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") -->
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -10,7 +10,7 @@ assignees: ''
|
||||
|
||||
<!-- Please make sure you are posting an issue pertaining to the Ionic Framework. If you are having an issue with the Ionic Appflow services (Ionic View, Ionic Deploy, etc.) please consult the Ionic Appflow support portal (https://ionic.zendesk.com/hc/en-us) -->
|
||||
|
||||
<!-- Please do not submit support requests or "How to" questions here. Instead, please use the Ionic Forum: https://forum.ionicframework.com/ -->
|
||||
<!-- Please do not submit support requests or "How to" questions here. Instead, please use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/ -->
|
||||
|
||||
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
|
||||
|
||||
@@ -19,8 +19,7 @@ assignees: ''
|
||||
**Ionic version:**
|
||||
<!-- (For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1) -->
|
||||
<!-- (For Ionic 2.x & 3.x issues, please use https://github.com/ionic-team/ionic-v3) -->
|
||||
[ ] **4.x**
|
||||
[x] **5.x**
|
||||
[x] **4.x**
|
||||
|
||||
**Current behavior:**
|
||||
<!-- Describe how the bug manifests. -->
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/support_question.md
vendored
2
.github/ISSUE_TEMPLATE/support_question.md
vendored
@@ -8,4 +8,4 @@ assignees: ''
|
||||
|
||||
# Support Question
|
||||
|
||||
Please do not submit support requests or "How to" questions here. Instead, please use the Ionic Forum: https://forum.ionicframework.com/
|
||||
Please do not submit support requests or "How to" questions here. Instead, please use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/
|
||||
|
||||
12
.github/PROCESS.md
vendored
12
.github/PROCESS.md
vendored
@@ -77,15 +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 14 days, the issue will be closed and locked.
|
||||
|
||||
### Missing Code Reproduction
|
||||
|
||||
If the information the submitter has supplied is not enough for you to reproduce the issue, add the `ionitron: needs reproduction` label. An automated comment will be added to the thread asking the submitter to provide a code reproduction of the issue.
|
||||
|
||||
This label can also be added when the submitter has supplied some code, but not enough for you to reproduce the issue (i.e. code snippets).
|
||||
|
||||
Issues with this label are not automatically closed and locked, so we manually close and lock them if there is no response within 14 days.
|
||||
If there is no response within 30 days, the issue will be closed and locked.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -150,7 +142,7 @@ See the [steps for releasing](#releasing) below for detailed information on how
|
||||
|
||||
### Version Branches
|
||||
|
||||
Once a release has shipped and the release branch has been merged into `stable` and `master` it should also be merged into its corresponding version branch. These version branches allow us to ship updates for specific versions of the framework (i.e. Lets us ship a bug fix that only affects 4.2.x).
|
||||
Once a release has shipped and the release branch has been merged into `stable` and `master` it should also be merged into its corrsponding version branch. These version branches allow us to ship updates for specific versions of the framework (i.e. Lets us ship a bug fix that only affects 4.2.x).
|
||||
|
||||
Patch releases should be merged into their corresponding version branches. For example, a `release-4.1.1` branch should be merged into the `4.1.x` version branch and a `release-5.0.1` branch should be merged into the `5.0.x` version branch.
|
||||
|
||||
|
||||
51
.github/ionic-issue-bot.yml
vendored
51
.github/ionic-issue-bot.yml
vendored
@@ -21,19 +21,6 @@ comment:
|
||||
|
||||
|
||||
Thank you!
|
||||
- label: "ionitron: needs reproduction"
|
||||
message: >
|
||||
Thanks for the issue! This issue has been labeled as `needs reproduction`. This label
|
||||
is added to issues that need a code reproduction.
|
||||
|
||||
|
||||
Please reproduce this issue in an Ionic starter application and provide a way for us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.
|
||||
|
||||
|
||||
If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was not enough for our team to reproduce the issue.
|
||||
|
||||
|
||||
For a guide on how to create a good reproduction, see our [Contributing Guide](https://ionicframework.com/docs/contributing/how-to-contribute#creating-a-good-code-reproduction).
|
||||
dryRun: false
|
||||
|
||||
closeAndLock:
|
||||
@@ -78,10 +65,8 @@ stale:
|
||||
days: 365
|
||||
maxIssuesPerRun: 100
|
||||
exemptLabels:
|
||||
- "good first issue"
|
||||
- "triage"
|
||||
- "type: bug"
|
||||
- "type: feature request"
|
||||
- good first issue
|
||||
- triage
|
||||
exemptAssigned: true
|
||||
exemptProjects: true
|
||||
exemptMilestones: true
|
||||
@@ -98,7 +83,7 @@ stale:
|
||||
dryRun: false
|
||||
|
||||
noReply:
|
||||
days: 14
|
||||
days: 30
|
||||
maxIssuesPerRun: 100
|
||||
label: "needs: reply"
|
||||
responseLabel: triage
|
||||
@@ -115,22 +100,20 @@ noReply:
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
noReproduction:
|
||||
days: 14
|
||||
maxIssuesPerRun: 100
|
||||
label: "ionitron: needs reproduction"
|
||||
responseLabel: triage
|
||||
exemptProjects: true
|
||||
exemptMilestones: true
|
||||
message: >
|
||||
Thanks for the issue! This issue is being closed due to the lack of a code reproduction. If this is still
|
||||
an issue with the latest version of Ionic, please create a new issue and ensure the
|
||||
template is fully filled out.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
labelPullRequest:
|
||||
labels:
|
||||
- label: "package: angular"
|
||||
branch: master
|
||||
path: ^angular
|
||||
- label: "package: core"
|
||||
branch: master
|
||||
path: ^core
|
||||
- label: "package: react"
|
||||
branch: master
|
||||
path: ^react
|
||||
- label: "package: vue"
|
||||
branch: master
|
||||
path: ^vue
|
||||
dryRun: false
|
||||
|
||||
wrongRepo:
|
||||
|
||||
21
.github/labeler.yml
vendored
21
.github/labeler.yml
vendored
@@ -1,21 +0,0 @@
|
||||
# This is used with the label workflow which
|
||||
# will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# For more information, see:
|
||||
# https://github.com/actions/labeler
|
||||
|
||||
'package: core':
|
||||
- core/**/*
|
||||
|
||||
'package: angular':
|
||||
- angular/**/*
|
||||
- packages/angular-*/**/*
|
||||
|
||||
'package: react':
|
||||
- packages/react/**/*
|
||||
- packages/react-*/**/*
|
||||
|
||||
'package: vue':
|
||||
- packages/vue/**/*
|
||||
- packages/vue-*/**/*
|
||||
7
.github/weekly-digest.yml
vendored
Normal file
7
.github/weekly-digest.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Configuration for weekly-digest - https://github.com/apps/weekly-digest
|
||||
publishDay: wed
|
||||
canPublishIssues: true
|
||||
canPublishPullRequests: true
|
||||
canPublishContributors: true
|
||||
canPublishStargazers: true
|
||||
canPublishCommits: true
|
||||
19
.github/workflows/label.yml
vendored
19
.github/workflows/label.yml
vendored
@@ -1,19 +0,0 @@
|
||||
# This workflow will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||
# file with configuration. For more information, see:
|
||||
# https://github.com/actions/labeler
|
||||
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@main
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -58,8 +58,6 @@ prerender-static.html
|
||||
# stencil
|
||||
angular/css/
|
||||
packages/react/css/
|
||||
packages/vue/css/
|
||||
core/components/
|
||||
core/css/
|
||||
core/hydrate/
|
||||
core/loader/
|
||||
|
||||
@@ -4,7 +4,7 @@ const execa = require('execa');
|
||||
const inquirer = require('inquirer');
|
||||
const Listr = require('listr');
|
||||
const semver = require('semver');
|
||||
const { bold, cyan, dim } = require('colorette');
|
||||
const tc = require('turbocolor');
|
||||
|
||||
const rootDir = path.join(__dirname, '../');
|
||||
|
||||
@@ -13,10 +13,7 @@ const packages = [
|
||||
'docs',
|
||||
'angular',
|
||||
'packages/react',
|
||||
'packages/react-router',
|
||||
'packages/angular-server',
|
||||
'packages/vue',
|
||||
'packages/vue-router'
|
||||
'packages/react-router'
|
||||
];
|
||||
|
||||
function readPkg(project) {
|
||||
@@ -38,13 +35,13 @@ function projectPath(project) {
|
||||
return path.join(rootDir, project);
|
||||
}
|
||||
|
||||
async function askNpmTag(version) {
|
||||
async function askTag() {
|
||||
const prompts = [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'npmTag',
|
||||
name: 'tag',
|
||||
message: 'Select npm tag or specify a new tag',
|
||||
choices: ['latest', 'next', 'v4-lts']
|
||||
choices: ['latest', 'next']
|
||||
.concat([
|
||||
new inquirer.Separator(),
|
||||
{
|
||||
@@ -57,13 +54,13 @@ async function askNpmTag(version) {
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: answers => {
|
||||
return `Will publish ${cyan(version)} to ${cyan(answers.npmTag)}. Continue?`;
|
||||
return `Will publish to ${tc.cyan(answers.tag)}. Continue?`;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const { npmTag, confirm } = await inquirer.prompt(prompts);
|
||||
return { npmTag, confirm };
|
||||
const { tag, confirm } = await inquirer.prompt(prompts);
|
||||
return { tag, confirm };
|
||||
}
|
||||
|
||||
function checkGit(tasks) {
|
||||
@@ -133,7 +130,7 @@ function preparePackage(tasks, package, version, install) {
|
||||
title: `${pkg.name}: install npm dependencies`,
|
||||
task: async () => {
|
||||
await fs.remove(path.join(projectRoot, 'node_modules'));
|
||||
await execa('npm', ['i', '--legacy-peer-deps'], { cwd: projectRoot });
|
||||
await execa('npm', ['i'], { cwd: projectRoot });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -143,37 +140,32 @@ function preparePackage(tasks, package, version, install) {
|
||||
if (package !== 'core') {
|
||||
projectTasks.push({
|
||||
title: `${pkg.name}: npm link @ionic/core`,
|
||||
task: () => execa('npm', ['link', '@ionic/core', '--legacy-peer-deps'], { cwd: projectRoot })
|
||||
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', '--legacy-peer-deps'], { cwd: projectRoot })
|
||||
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}: test`,
|
||||
task: async () => await 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') {
|
||||
projectTasks.push({
|
||||
title: `${pkg.name}: npm link`,
|
||||
@@ -194,7 +186,7 @@ function preparePackage(tasks, package, version, install) {
|
||||
|
||||
// Add project tasks
|
||||
tasks.push({
|
||||
title: `Prepare ${bold(pkg.name)}`,
|
||||
title: `Prepare ${tc.bold(pkg.name)}`,
|
||||
task: () => new Listr(projectTasks)
|
||||
});
|
||||
}
|
||||
@@ -209,15 +201,8 @@ function prepareDevPackage(tasks, package, version) {
|
||||
if (package !== 'core') {
|
||||
projectTasks.push({
|
||||
title: `${pkg.name}: npm link @ionic/core`,
|
||||
task: () => execa('npm', ['link', '@ionic/core', '--legacy-peer-deps'], { cwd: projectRoot })
|
||||
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', '--legacy-peer-deps'], { cwd: projectRoot })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
projectTasks.push({
|
||||
@@ -243,7 +228,7 @@ function prepareDevPackage(tasks, package, version) {
|
||||
|
||||
// Add project tasks
|
||||
tasks.push({
|
||||
title: `Prepare dev build: ${bold(pkg.name)}`,
|
||||
title: `Prepare dev build: ${tc.bold(pkg.name)}`,
|
||||
task: () => new Listr(projectTasks)
|
||||
});
|
||||
}
|
||||
@@ -253,7 +238,7 @@ function updatePackageVersions(tasks, packages, version) {
|
||||
updatePackageVersion(tasks, package, version);
|
||||
|
||||
tasks.push({
|
||||
title: `${package} update @ionic/core dependency, if present ${dim(`(${version})`)}`,
|
||||
title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`,
|
||||
task: async () => {
|
||||
if (package !== 'core') {
|
||||
const pkg = readPkg(package);
|
||||
@@ -262,26 +247,9 @@ function updatePackageVersions(tasks, packages, version) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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({
|
||||
title: `${package} update @ionic/core dependency, if present ${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 ${dim(`(${version})`)}`,
|
||||
title: `${package} update @ionic/react dependency, if present ${tc.dim(`(${version})`)}`,
|
||||
task: async () => {
|
||||
const pkg = readPkg(package);
|
||||
updateDependency(pkg, '@ionic/react', version);
|
||||
@@ -296,30 +264,14 @@ function updatePackageVersion(tasks, package, version) {
|
||||
const projectRoot = projectPath(package);
|
||||
|
||||
tasks.push({
|
||||
title: `${package}: update package.json ${dim(`(${version})`)}`,
|
||||
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') {
|
||||
@@ -338,18 +290,14 @@ 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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -384,11 +332,10 @@ function copyCDNLoader(tasks, version) {
|
||||
module.exports = {
|
||||
checkTestDist,
|
||||
checkGit,
|
||||
askNpmTag,
|
||||
askTag,
|
||||
isValidVersion,
|
||||
isVersionGreater,
|
||||
copyCDNLoader,
|
||||
copyPackageToDist,
|
||||
packages,
|
||||
packagePath,
|
||||
prepareDevPackage,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Deploy script adopted from https://github.com/sindresorhus/np
|
||||
* MIT License (c) Sindre Sorhus (sindresorhus.com)
|
||||
*/
|
||||
const { cyan, dim, red, reset } = require('colorette');
|
||||
const tc = require('turbocolor');
|
||||
const execa = require('execa');
|
||||
const inquirer = require('inquirer');
|
||||
const Listr = require('listr');
|
||||
@@ -34,7 +34,7 @@ async function main() {
|
||||
console.log(` npm run release\n`);
|
||||
|
||||
} catch(err) {
|
||||
console.log('\n', red(err), '\n');
|
||||
console.log('\n', tc.red(err), '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ async function askVersion() {
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: answers => {
|
||||
return `Will bump from ${cyan(oldVersion)} to ${cyan(answers.version)}. Continue?`;
|
||||
return `Will bump from ${tc.cyan(oldVersion)} to ${tc.cyan(answers.version)}. Continue?`;
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -131,7 +131,7 @@ async function preparePackages(packages, version, install) {
|
||||
function validateGit(tasks, version) {
|
||||
tasks.push(
|
||||
{
|
||||
title: `Validate git tag ${dim(`(v${version})`)}`,
|
||||
title: `Validate git tag ${tc.dim(`(v${version})`)}`,
|
||||
task: () => execa('git', ['fetch'])
|
||||
.then(() => {
|
||||
return execa.stdout('npm', ['config', 'get', 'tag-version-prefix']);
|
||||
@@ -198,17 +198,17 @@ function prettyVersionDiff(oldVersion, inc) {
|
||||
|
||||
for (let i = 0; i < newVersion.length; i++) {
|
||||
if ((newVersion[i] !== oldVersion[i] && !firstVersionChange)) {
|
||||
output.push(`${dim(cyan(newVersion[i]))}`);
|
||||
output.push(`${tc.dim.cyan(newVersion[i])}`);
|
||||
firstVersionChange = true;
|
||||
} else if (newVersion[i].indexOf('-') >= 1) {
|
||||
let preVersion = [];
|
||||
preVersion = newVersion[i].split('-');
|
||||
output.push(`${dim(cyan(`${preVersion[0]}-${preVersion[1]}`))}`);
|
||||
output.push(`${tc.dim.cyan(`${preVersion[0]}-${preVersion[1]}`)}`);
|
||||
} else {
|
||||
output.push(reset(dim(newVersion[i])));
|
||||
output.push(tc.reset.dim(newVersion[i]));
|
||||
}
|
||||
}
|
||||
return output.join(reset(dim('.')));
|
||||
return output.join(tc.reset.dim('.'));
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { cyan, red } = require('colorette');
|
||||
const tc = require('turbocolor');
|
||||
const semver = require('semver');
|
||||
const execa = require('execa');
|
||||
const inquirer = require('inquirer');
|
||||
@@ -47,7 +47,7 @@ async function main() {
|
||||
console.log(`\nionic ${devVersion} published!! 🎉\n`);
|
||||
|
||||
} catch (err) {
|
||||
console.log('\n', red(err), '\n');
|
||||
console.log('\n', tc.red(err), '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ async function askDevVersion(devVersion) {
|
||||
name: 'confirm',
|
||||
value: true,
|
||||
message: () => {
|
||||
return `Publish the dev build ${cyan(devVersion)}?`;
|
||||
return `Publish the dev build ${tc.cyan(devVersion)}?`;
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -78,9 +78,6 @@ async function setPackageVersionChanges(packages, version) {
|
||||
if (package !== 'core') {
|
||||
const pkg = common.readPkg(package);
|
||||
common.updateDependency(pkg, '@ionic/core', version);
|
||||
if(package === 'packages/react-router') {
|
||||
common.updateDependency(pkg, '@ionic/react', version);
|
||||
}
|
||||
common.writePkg(package, pkg);
|
||||
}
|
||||
const projectRoot = common.projectPath(package);
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
* Deploy script adopted from https://github.com/sindresorhus/np
|
||||
* MIT License (c) Sindre Sorhus (sindresorhus.com)
|
||||
*/
|
||||
const { cyan, dim, green, red, yellow } = require('colorette');
|
||||
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');
|
||||
|
||||
@@ -28,7 +28,7 @@ async function main() {
|
||||
// repo must be clean
|
||||
common.checkGit(tasks);
|
||||
|
||||
const { npmTag, confirm } = await common.askNpmTag(version);
|
||||
const { tag, confirm } = await common.askTag();
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
@@ -36,10 +36,10 @@ async function main() {
|
||||
|
||||
if(!dryRun) {
|
||||
// publish each package in NPM
|
||||
common.publishPackages(tasks, common.packages, version, npmTag);
|
||||
common.publishPackages(tasks, common.packages, version, tag);
|
||||
|
||||
// push tag to git remote
|
||||
publishGit(tasks, version, changelog, npmTag);
|
||||
publishGit(tasks, version, changelog);
|
||||
}
|
||||
|
||||
const listr = new Listr(tasks);
|
||||
@@ -48,14 +48,14 @@ async function main() {
|
||||
// Dry run doesn't publish to npm or git
|
||||
if (dryRun) {
|
||||
console.log(`
|
||||
\n${yellow('Did not publish. Remove the "--dry-run" flag to publish:')}\n${green(version)} to ${cyan(npmTag)}\n
|
||||
\n${tc.yellow('Did not publish. Remove the "--dry-run" flag to publish:')}\n${tc.green(version)} to ${tc.cyan(tag)}\n
|
||||
`);
|
||||
} else {
|
||||
console.log(`\nionic ${version} published to ${npmTag}!! 🎉\n`);
|
||||
console.log(`\nionic ${version} published to ${tag}!! 🎉\n`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log('\n', red(err), '\n');
|
||||
console.log('\n', tc.red(err), '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -70,13 +70,13 @@ function checkProductionRelease() {
|
||||
}
|
||||
}
|
||||
|
||||
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 ${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',
|
||||
@@ -88,7 +88,7 @@ function publishGit(tasks, version, changelog, npmTag) {
|
||||
},
|
||||
{
|
||||
title: 'Publish Github release',
|
||||
task: () => publishGithub(version, gitTag, changelog, npmTag)
|
||||
task: () => publishGithub(version, tag, changelog)
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ function findChangelog() {
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith('## [') || line.startsWith('# [')) {
|
||||
if (line.startsWith('# [')) {
|
||||
if (start === -1) {
|
||||
start = i + 1;
|
||||
} else {
|
||||
@@ -116,12 +116,10 @@ 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']);
|
||||
@@ -132,12 +130,11 @@ async function publishGithub(version, gitTag, changelog, npmTag) {
|
||||
|
||||
await octokit.repos.createRelease({
|
||||
owner: 'ionic-team',
|
||||
repo: 'ionic-framework',
|
||||
repo: 'ionic',
|
||||
target_commitish: branch,
|
||||
tag_name: gitTag,
|
||||
tag_name: tag,
|
||||
name: version,
|
||||
body: changelog,
|
||||
prerelease: prerelease
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,44 +7,11 @@ const fs = require('fs');
|
||||
[
|
||||
// core
|
||||
{
|
||||
files: [
|
||||
'../core/css/core.css',
|
||||
'../core/css/core.css.map',
|
||||
'../core/css/normalize.css',
|
||||
'../core/css/normalize.css.map',
|
||||
'../core/components/index.js',
|
||||
'../core/components/index.d.ts',
|
||||
'../core/components/package.json',
|
||||
'../core/dist/index.js',
|
||||
'../core/dist/ionic/index.esm.js',
|
||||
]
|
||||
},
|
||||
// hydrate
|
||||
{
|
||||
files: [
|
||||
'../core/hydrate/index.js',
|
||||
'../core/hydrate/index.d.ts',
|
||||
'../core/hydrate/package.json'
|
||||
]
|
||||
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'
|
||||
]
|
||||
files: ['../angular/dist/fesm5.cjs.js']
|
||||
},
|
||||
// react
|
||||
{
|
||||
|
||||
670
BREAKING.md
670
BREAKING.md
@@ -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).
|
||||
1142
CHANGELOG.md
1142
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,129 +1,11 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
As contributors and maintainers of the Ionic project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
|
||||
|
||||
## Our Pledge
|
||||
Communication through any of Ionic's channels (GitHub, Slack, Forum, IRC, mailing lists, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the Ionic project to do the same.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
If any member of the community violates this code of conduct, the maintainers of the Ionic project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include the ability to:
|
||||
|
||||
* Demonstrate empathy and kindness towards people
|
||||
* Be respectful of differing opinions, viewpoints, and experiences
|
||||
* Give and gracefully accept constructive feedback
|
||||
* Accept responsibility and apologize to those affected by our mistakes,
|
||||
and learn from the experience
|
||||
* Focus on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[contact@ionic.io](mailto:contact@ionic.io).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
If you are subject to or witness unacceptable behavior, or have any other concerns, please email us at [hi@ionicframework.com](mailto:hi@ionicframework.com).
|
||||
|
||||
12
README.md
12
README.md
@@ -1,9 +1,9 @@
|
||||
# Ionic Framework
|
||||
# Ionic
|
||||
|
||||
[Ionic Framework](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to
|
||||
[Ionic](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to
|
||||
build top quality native and progressive web apps with web technologies.
|
||||
|
||||
Ionic Framework is based on [Web Components](https://www.webcomponents.org/introduction) and comes with many significant performance, usability, and feature improvements over the past versions.
|
||||
Ionic is based on [Web Components](https://www.webcomponents.org/introduction) and comes with many significant performance, usability, and feature improvements over the past versions.
|
||||
|
||||
|
||||
### Packages
|
||||
@@ -12,7 +12,7 @@ Ionic Framework is based on [Web Components](https://www.webcomponents.org/intro
|
||||
| ------- | ------- | ------- |:-----:|
|
||||
| **Core** | [`@ionic/core`](https://www.npmjs.com/package/@ionic/core) | [](https://www.npmjs.com/package/@ionic/core) | [`README.md`](core/README.md)
|
||||
| **Angular** | [`@ionic/angular`](https://www.npmjs.com/package/@ionic/angular) | [](https://www.npmjs.com/package/@ionic/angular) | [`README.md`](angular/README.md)
|
||||
| **Vue** | [`@ionic/vue`](https://www.npmjs.com/package/@ionic/vue) | [](https://www.npmjs.com/package/@ionic/vue) | [`README.md`](packages/vue/README.md)
|
||||
| **Vue** | [`@ionic/vue`](https://www.npmjs.com/package/@ionic/vue) | [](https://www.npmjs.com/package/@ionic/vue) | [`README.md`](vue/README.md)
|
||||
| **React** | [`@ionic/react`](https://www.npmjs.com/package/@ionic/react) | [](https://www.npmjs.com/package/@ionic/react) | [`README.md`](packages/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).
|
||||
@@ -31,8 +31,6 @@ Thanks for your interest in contributing! Read up on our guidelines for
|
||||
and then look through our issues with a [help wanted](https://github.com/ionic-team/ionic/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
|
||||
label.
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/ionic-team/ionic/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -42,7 +40,7 @@ It is the perfect starting point for learning and building your own app.
|
||||
|
||||
### Future Goals
|
||||
|
||||
As Ionic Framework components migrate to the web component standard, a goal of ours is to have Ionic Framework easily work within all of the popular frameworks.
|
||||
As Ionic components migrate to the web component standard, a goal of ours is to have Ionic components easily work within all of the popular frameworks.
|
||||
|
||||
### Earlier Versions
|
||||
|
||||
|
||||
1
angular/.npmrc
Normal file
1
angular/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
package-lock=false
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ Ionic Angular specific building blocks on top of [@ionic/core](https://www.npmjs
|
||||
|
||||
* [Ionic Core Components](https://www.npmjs.com/package/@ionic/core)
|
||||
* [Ionic Documentation](https://ionicframework.com/docs/)
|
||||
* [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/)
|
||||
* [Ionic Forum](https://forum.ionicframework.com/)
|
||||
* [Ionicons](http://ionicons.com/)
|
||||
* [Stencil](https://stenciljs.com/)
|
||||
@@ -22,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
|
||||
|
||||
```
|
||||
|
||||
9097
angular/package-lock.json
generated
9097
angular/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.6.8",
|
||||
"version": "5.0.0-beta.0",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -24,11 +24,11 @@
|
||||
},
|
||||
"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 +41,53 @@
|
||||
"tsc": "tsc -p .",
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"module": "dist/fesm5.js",
|
||||
"main": "dist/fesm5.cjs.js",
|
||||
"types": "dist/core.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
"css/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.6.8",
|
||||
"@ionic/core": "5.0.0-beta.0",
|
||||
"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 - 8",
|
||||
"@angular-devkit/schematics": "7.2.1 - 8",
|
||||
"@angular/core": "7.2.1 - 8",
|
||||
"@angular/common": "7.2.1 - 8",
|
||||
"@angular/forms": "7.2.1 - 8",
|
||||
"@angular/router": "7.2.1 - 8",
|
||||
"@angular/compiler": "7.2.1 - 8",
|
||||
"@angular/compiler-cli": "7.2.1 - 8",
|
||||
"@angular/platform-browser": "7.2.1 - 8",
|
||||
"@angular/platform-browser-dynamic": "7.2.1 - 8",
|
||||
"rxjs": ">=6.2.0",
|
||||
"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": "~12.0.12",
|
||||
"fs-extra": "^7.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"ng-packagr": "^9.1.5",
|
||||
"rollup": "~1.17.0",
|
||||
"rollup-plugin-node-resolve": "~5.2.0",
|
||||
"rxjs": "^6.6.2",
|
||||
"tsickle": "^0.39.1",
|
||||
"rxjs": "^6.5.2",
|
||||
"tsickle": "^0.34.0",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-ionic-rules": "0.0.21",
|
||||
"typescript": "3.4.5",
|
||||
"zone.js": "^0.11.1"
|
||||
"typescript": "~3.2.2",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
"schematics": "./schematics/collection.json",
|
||||
"ngPackage": {
|
||||
"lib": {
|
||||
"entryFile": "src/index.ts"
|
||||
},
|
||||
"whitelistedNonPeerDependencies": [
|
||||
"@ionic/core"
|
||||
]
|
||||
}
|
||||
"schematics": "./dist/schematics/collection.json"
|
||||
}
|
||||
|
||||
2
angular/scripts/build-core.js
vendored
2
angular/scripts/build-core.js
vendored
@@ -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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
input: 'build/es2015/core.js',
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
mainFields: ['module']
|
||||
module: true,
|
||||
})
|
||||
]
|
||||
};
|
||||
};
|
||||
@@ -5,10 +5,16 @@ 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) => {
|
||||
return (): any => {
|
||||
const win: IonicWindow | undefined = doc.defaultView as any;
|
||||
if (win && typeof (window as any) !== 'undefined') {
|
||||
if (win) {
|
||||
if (didInitialize) {
|
||||
console.warn('Ionic Angular was already initialized. Make sure IonicModule.forRoot() is just called once.');
|
||||
}
|
||||
didInitialize = true;
|
||||
const Ionic = win.Ionic = win.Ionic || {};
|
||||
|
||||
Ionic.config = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
import { ValueAccessor, setIonicClasses } from './value-accessor';
|
||||
@@ -16,8 +16,8 @@ import { ValueAccessor, setIonicClasses } from './value-accessor';
|
||||
})
|
||||
export class BooleanValueAccessor extends ValueAccessor {
|
||||
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
constructor(el: ElementRef) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
writeValue(value: any) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
import { ValueAccessor } from './value-accessor';
|
||||
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
|
||||
})
|
||||
export class NumericValueAccessor extends ValueAccessor {
|
||||
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
constructor(el: ElementRef) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
import { ValueAccessor } from './value-accessor';
|
||||
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
|
||||
})
|
||||
export class RadioValueAccessor extends ValueAccessor {
|
||||
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
constructor(el: ElementRef) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
@HostListener('ionSelect', ['$event.target'])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
import { ValueAccessor } from './value-accessor';
|
||||
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
|
||||
})
|
||||
export class SelectValueAccessor extends ValueAccessor {
|
||||
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
constructor(el: ElementRef) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
|
||||
import { Directive, ElementRef, HostListener } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
import { ValueAccessor } from './value-accessor';
|
||||
@@ -16,8 +16,8 @@ import { ValueAccessor } from './value-accessor';
|
||||
})
|
||||
export class TextValueAccessor extends ValueAccessor {
|
||||
|
||||
constructor(injector: Injector, el: ElementRef) {
|
||||
super(injector, el);
|
||||
constructor(el: ElementRef) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
import { AfterViewInit, ElementRef, HostListener, Injector, OnDestroy, Type } from '@angular/core';
|
||||
import { ControlValueAccessor, NgControl } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ElementRef, HostListener } from '@angular/core';
|
||||
import { ControlValueAccessor } from '@angular/forms';
|
||||
|
||||
import { raf } from '../../util/util';
|
||||
|
||||
export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDestroy {
|
||||
export class ValueAccessor implements ControlValueAccessor {
|
||||
|
||||
private onChange: (value: any) => void = () => {/**/};
|
||||
private onTouched: () => void = () => {/**/};
|
||||
protected lastValue: any;
|
||||
private statusChanges?: Subscription;
|
||||
|
||||
constructor(protected injector: Injector, protected el: ElementRef) {}
|
||||
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);
|
||||
}
|
||||
@@ -54,50 +45,6 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
|
||||
setDisabledState(isDisabled: boolean) {
|
||||
this.el.nativeElement.disabled = isDisabled;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.statusChanges) {
|
||||
this.statusChanges.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
let ngControl;
|
||||
try {
|
||||
ngControl = this.injector.get<NgControl>(NgControl as Type<NgControl>);
|
||||
} catch { /* No FormControl or ngModel binding */ }
|
||||
|
||||
if (!ngControl) { return; }
|
||||
|
||||
// Listen for changes in validity, disabled, or pending states
|
||||
if (ngControl.statusChanges) {
|
||||
this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.el));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Remove this in favor of https://github.com/angular/angular/issues/10887
|
||||
* whenever it is implemented. Currently, Ionic's form status classes
|
||||
* do not react to changes when developers manually call
|
||||
* Angular form control methods such as markAsTouched.
|
||||
* This results in Ionic's form status classes being out
|
||||
* of sync with the ng form status classes.
|
||||
* This patches the methods to manually sync
|
||||
* the classes until this feature is implemented in Angular.
|
||||
*/
|
||||
const formControl = ngControl.control;
|
||||
if (formControl) {
|
||||
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
|
||||
methodsToPatch.forEach(method => {
|
||||
if (formControl[method]) {
|
||||
const oldFn = formControl[method].bind(formControl);
|
||||
formControl[method] = (...params) => {
|
||||
oldFn(...params);
|
||||
setIonicClasses(this.el);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setIonicClasses = (element: ElementRef) => {
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import { Directive, HostListener, Optional } from '@angular/core';
|
||||
import { AnimationBuilder } from '@ionic/core';
|
||||
|
||||
import { Config } from '../../providers/config';
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
|
||||
import { IonRouterOutlet } from './ion-router-outlet';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-back-button',
|
||||
inputs: ['defaultHref', 'routerAnimation'],
|
||||
inputs: ['defaultHref']
|
||||
})
|
||||
export class IonBackButtonDelegate {
|
||||
|
||||
defaultHref: string | undefined | null;
|
||||
routerAnimation?: AnimationBuilder;
|
||||
|
||||
constructor(
|
||||
@Optional() private routerOutlet: IonRouterOutlet,
|
||||
private navCtrl: NavController,
|
||||
private config: Config
|
||||
private navCtrl: NavController
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -26,14 +22,11 @@ export class IonBackButtonDelegate {
|
||||
*/
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(ev: Event) {
|
||||
const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref');
|
||||
|
||||
if (this.routerOutlet && this.routerOutlet.canGoBack()) {
|
||||
this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation);
|
||||
this.routerOutlet.pop();
|
||||
ev.preventDefault();
|
||||
} else if (defaultHref != null) {
|
||||
this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation });
|
||||
} else if (this.defaultHref != null) {
|
||||
this.navCtrl.navigateBack(this.defaultHref);
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Attribute, 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 { componentOnReady } from '@ionic/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { AnimationBuilder } from '../../';
|
||||
import { Config } from '../../providers/config';
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
|
||||
@@ -15,18 +13,17 @@ import { RouteView, getUrl } from './stack-utils';
|
||||
@Directive({
|
||||
selector: 'ion-router-outlet',
|
||||
exportAs: 'outlet',
|
||||
inputs: ['animated', 'animation', 'swipeGesture']
|
||||
inputs: ['animated', 'swipeGesture']
|
||||
})
|
||||
export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
nativeEl: HTMLIonRouterOutletElement;
|
||||
|
||||
private activated: ComponentRef<any> | null = null;
|
||||
activatedView: RouteView | null = null;
|
||||
private activatedView: RouteView | null = null;
|
||||
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
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>();
|
||||
@@ -40,10 +37,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||
|
||||
set animation(animation: AnimationBuilder) {
|
||||
this.nativeEl.animation = animation;
|
||||
}
|
||||
|
||||
set animated(animated: boolean) {
|
||||
this.nativeEl.animated = animated;
|
||||
}
|
||||
@@ -52,7 +45,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this._swipeGesture = swipe;
|
||||
|
||||
this.nativeEl.swipeHandler = swipe ? {
|
||||
canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(),
|
||||
canStart: () => this.stackCtrl.canGoBack(1),
|
||||
onStart: () => this.stackCtrl.startBackTransition(),
|
||||
onEnd: shouldContinue => this.stackCtrl.endBackTransition(shouldContinue)
|
||||
} : undefined;
|
||||
@@ -97,12 +90,13 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.activateWith(context.route, context.resolver || null);
|
||||
}
|
||||
}
|
||||
|
||||
new Promise(resolve => componentOnReady(this.nativeEl, resolve)).then(() => {
|
||||
if (this._swipeGesture === undefined) {
|
||||
this.swipeGesture = this.config.getBoolean('swipeBackEnabled', (this.nativeEl as any).mode === 'ios');
|
||||
}
|
||||
});
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get isActivated(): boolean {
|
||||
@@ -147,27 +141,15 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
deactivate(): void {
|
||||
if (this.activated) {
|
||||
if (this.activatedView) {
|
||||
const context = this.getContext()!;
|
||||
this.activatedView.savedData = new Map(context.children['contexts']);
|
||||
|
||||
/**
|
||||
* Angular v11.2.10 introduced a change
|
||||
* where this route context is cleared out when
|
||||
* a router-outlet is deactivated, However,
|
||||
* we need this route information in order to
|
||||
* return a user back to the correct tab when
|
||||
* leaving and then going back to the tab context.
|
||||
*/
|
||||
const primaryOutlet = this.activatedView.savedData.get('primary');
|
||||
if (primaryOutlet && context.route) {
|
||||
primaryOutlet.route = { ...context.route };
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -259,22 +241,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.
|
||||
*/
|
||||
@@ -348,7 +314,7 @@ class OutletInjector implements Injector {
|
||||
private route: ActivatedRoute,
|
||||
private childContexts: ChildrenOutletContexts,
|
||||
private parent: Injector
|
||||
) { }
|
||||
) {}
|
||||
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
if (token === ActivatedRoute) {
|
||||
|
||||
@@ -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,59 +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 activeStackId = this.outlet.getActiveStackId();
|
||||
const activeView = this.outlet.getLastRouteView(activeStackId);
|
||||
const href = `${this.outlet.tabsPrefix}/${tab}`;
|
||||
const url = alreadySelected
|
||||
? href
|
||||
: this.outlet.getLastUrl(tab) || href;
|
||||
|
||||
// If on root tab, do not navigate to root tab again
|
||||
if (activeView.url === tabRootUrl) { return; }
|
||||
|
||||
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;
|
||||
|
||||
return this.navCtrl.navigateRoot(url, {
|
||||
...(navigationExtras),
|
||||
animated: true,
|
||||
animationDirection: 'back',
|
||||
});
|
||||
}
|
||||
return this.navCtrl.navigateRoot(url, {
|
||||
animated: true,
|
||||
animationDirection: 'back'
|
||||
});
|
||||
}
|
||||
|
||||
getSelected(): string | undefined {
|
||||
|
||||
@@ -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' ]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { LocationStrategy } from '@angular/common';
|
||||
import { Directive, ElementRef, HostListener, Optional } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { AnimationBuilder, RouterDirection } from '@ionic/core';
|
||||
import { RouterDirection } from '@ionic/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
|
||||
@Directive({
|
||||
selector: '[routerLink]',
|
||||
inputs: ['routerDirection', 'routerAnimation']
|
||||
inputs: ['routerDirection']
|
||||
})
|
||||
export class RouterLinkDelegate {
|
||||
|
||||
private subscription?: Subscription;
|
||||
|
||||
routerDirection: RouterDirection = 'forward';
|
||||
routerAnimation?: AnimationBuilder;
|
||||
|
||||
constructor(
|
||||
private locationStrategy: LocationStrategy,
|
||||
@@ -51,7 +50,7 @@ export class RouterLinkDelegate {
|
||||
*/
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(ev: UIEvent) {
|
||||
this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
|
||||
this.navCtrl.setDirection(this.routerDirection);
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { ComponentRef, NgZone } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AnimationBuilder, RouterDirection } from '@ionic/core';
|
||||
import { RouterDirection } from '@ionic/core';
|
||||
|
||||
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
@@ -52,8 +52,7 @@ export class StackController {
|
||||
}
|
||||
|
||||
setActive(enteringView: RouteView): Promise<StackEvent> {
|
||||
const consumeResult = this.navCtrl.consumeTransition();
|
||||
let { direction, animation, animationBuilder } = consumeResult;
|
||||
let { direction, animation } = this.navCtrl.consumeTransition();
|
||||
const leavingView = this.activeView;
|
||||
const tabSwitch = isTabSwitch(enteringView, leavingView);
|
||||
if (tabSwitch) {
|
||||
@@ -71,7 +70,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
|
||||
@@ -106,31 +105,6 @@ export class StackController {
|
||||
enteringView.ref.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are going back from a page that
|
||||
* was presented using a custom animation
|
||||
* we should default to using that
|
||||
* unless the developer explicitly
|
||||
* provided another animation.
|
||||
*/
|
||||
const customAnimation = enteringView.animationBuilder;
|
||||
if (
|
||||
animationBuilder === undefined &&
|
||||
direction === 'back' &&
|
||||
!tabSwitch &&
|
||||
customAnimation !== undefined
|
||||
) {
|
||||
animationBuilder = customAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any custom animation so that navigating
|
||||
* back will use this custom animation by default.
|
||||
*/
|
||||
if (leavingView) {
|
||||
leavingView.animationBuilder = animationBuilder;
|
||||
}
|
||||
|
||||
// Wait until previous transitions finish
|
||||
return this.zone.runOutsideAngular(() => {
|
||||
return this.wait(() => {
|
||||
@@ -142,7 +116,7 @@ export class StackController {
|
||||
// In case the enteringView is the same as the leavingPage we need to reattach()
|
||||
enteringView.ref.changeDetectorRef.reattach();
|
||||
|
||||
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
|
||||
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
|
||||
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
|
||||
.then(() => ({
|
||||
enteringView,
|
||||
@@ -180,8 +154,8 @@ export class StackController {
|
||||
url = primaryOutlet.route._routerState.snapshot.url;
|
||||
}
|
||||
}
|
||||
const { animationBuilder } = this.navCtrl.consumeTransition();
|
||||
return this.navCtrl.navigateBack(url, { ...view.savedExtras, animation: animationBuilder }).then(() => true);
|
||||
|
||||
return this.navCtrl.navigateBack(url, view.savedExtras).then(() => true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,16 +164,13 @@ export class StackController {
|
||||
if (leavingView) {
|
||||
const views = this.getStack(leavingView.stackId);
|
||||
const enteringView = views[views.length - 2];
|
||||
const customAnimation = enteringView.animationBuilder;
|
||||
|
||||
return this.wait(() => {
|
||||
return this.transition(
|
||||
enteringView, // entering view
|
||||
leavingView, // leaving view
|
||||
'back',
|
||||
this.canGoBack(2),
|
||||
true,
|
||||
customAnimation
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -220,22 +191,10 @@ 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;
|
||||
}
|
||||
|
||||
hasRunningTask(): boolean {
|
||||
return this.runningTask !== undefined;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.containerEl = undefined!;
|
||||
this.views.forEach(destroyView);
|
||||
@@ -258,8 +217,7 @@ export class StackController {
|
||||
leavingView: RouteView | undefined,
|
||||
direction: 'forward' | 'back' | undefined,
|
||||
showGoBack: boolean,
|
||||
progressAnimation: boolean,
|
||||
animationBuilder?: AnimationBuilder
|
||||
progressAnimation: boolean
|
||||
) {
|
||||
if (this.skipTransition) {
|
||||
this.skipTransition = false;
|
||||
@@ -284,8 +242,7 @@ export class StackController {
|
||||
duration: direction === undefined ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation,
|
||||
animationBuilder
|
||||
progressAnimation
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -298,7 +255,6 @@ export class StackController {
|
||||
this.runningTask = undefined;
|
||||
}
|
||||
const promise = this.runningTask = task();
|
||||
promise.finally(() => this.runningTask = undefined);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ComponentRef } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
|
||||
import { NavDirection, RouterDirection } from '@ionic/core';
|
||||
|
||||
export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection) => {
|
||||
if (direction === 'root') {
|
||||
@@ -96,5 +96,4 @@ export interface RouteView {
|
||||
savedData?: any;
|
||||
savedExtras?: NavigationExtras;
|
||||
unlistenEvents: () => void;
|
||||
animationBuilder?: AnimationBuilder;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
import type * as d from './proxies';
|
||||
import * as d from './proxies';
|
||||
|
||||
export const DIRECTIVES = [
|
||||
d.IonApp,
|
||||
d.IonApp,
|
||||
d.IonAvatar,
|
||||
d.IonBackButton,
|
||||
d.IonBackdrop,
|
||||
|
||||
@@ -1,46 +1,28 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
export const 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));
|
||||
}
|
||||
});
|
||||
Object.defineProperty(Prototype, item, {
|
||||
get() { return this.el[item]; },
|
||||
set(val: any) {
|
||||
this.z.runOutsideAngular(() => this.el[item] = val);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const proxyMethods = (Cmp: any, methods: string[]) => {
|
||||
const Prototype = Cmp.prototype;
|
||||
methods.forEach(methodName => {
|
||||
Prototype[methodName] = function () {
|
||||
const args = arguments;
|
||||
return this.z.runOutsideAngular(() =>
|
||||
this.el[methodName].apply(this.el, args)
|
||||
);
|
||||
};
|
||||
Prototype[methodName] = function() {
|
||||
const args = arguments;
|
||||
return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const proxyOutputs = (instance: any, el: any, events: string[]) => {
|
||||
events.forEach(eventName => instance[eventName] = fromEvent(el, eventName));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 { ProxyCmp } from '../proxies-utils';
|
||||
import { proxyInputs, proxyMethods } from '../proxies-utils';
|
||||
|
||||
import { VirtualFooter } from './virtual-footer';
|
||||
import { VirtualHeader } from './virtual-header';
|
||||
@@ -71,7 +71,7 @@ export declare interface IonVirtualScroll {
|
||||
* entire virtual scroll is reset, which is an expensive operation and
|
||||
* should be avoided if possible.
|
||||
*/
|
||||
items?: any[] | null;
|
||||
items?: any[];
|
||||
|
||||
/**
|
||||
* An optional function that maps each item within their height.
|
||||
@@ -112,10 +112,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>',
|
||||
@@ -137,11 +133,11 @@ 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,
|
||||
@@ -219,3 +215,21 @@ const getElement = (view: EmbeddedViewRef<VirtualContext>): HTMLElement => {
|
||||
}
|
||||
throw new Error('virtual element was not created');
|
||||
};
|
||||
|
||||
proxyInputs(IonVirtualScroll, [
|
||||
'approxItemHeight',
|
||||
'approxHeaderHeight',
|
||||
'approxFooterHeight',
|
||||
'headerFn',
|
||||
'footerFn',
|
||||
'items',
|
||||
'itemHeight',
|
||||
'headerHeight',
|
||||
'footerHeight'
|
||||
]);
|
||||
|
||||
proxyMethods(IonVirtualScroll, [
|
||||
'checkEnd',
|
||||
'checkRange',
|
||||
'positionForItem'
|
||||
]);
|
||||
|
||||
@@ -36,14 +36,5 @@ export { GestureController } from './providers/gesture-controller';
|
||||
// ROUTER STRATEGY
|
||||
export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy';
|
||||
|
||||
// TYPES
|
||||
export * from './types/ionic-lifecycle-hooks';
|
||||
|
||||
// PACKAGE MODULE
|
||||
export { IonicModule } from './ionic-module';
|
||||
|
||||
// UTILS
|
||||
export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core';
|
||||
|
||||
// CORE TYPES
|
||||
export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation, NavComponentWithProps } from '@ionic/core';
|
||||
|
||||
@@ -126,7 +126,7 @@ const DECLARATIONS = [
|
||||
imports: [CommonModule]
|
||||
})
|
||||
export class IonicModule {
|
||||
static forRoot(config?: IonicConfig): ModuleWithProviders<IonicModule> {
|
||||
static forRoot(config?: IonicConfig): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: IonicModule,
|
||||
providers: [
|
||||
|
||||
@@ -33,7 +33,7 @@ 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.`);
|
||||
console.warn(`[DEPRECATION][Config]: The Config.set() method is deprecated and will be removed in the next major release.`);
|
||||
const c = getConfig();
|
||||
if (c) {
|
||||
c.set(key, value);
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Gesture, GestureConfig, createGesture } from '@ionic/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Gesture, GestureConfig, PressRecognizerOptions, createGesture, createPressRecognizer } from '@ionic/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class GestureController {
|
||||
constructor(private zone: NgZone) {}
|
||||
/**
|
||||
* Create a new gesture
|
||||
*/
|
||||
create(opts: GestureConfig, runInsideAngularZone = false): Gesture {
|
||||
if (runInsideAngularZone) {
|
||||
Object.getOwnPropertyNames(opts).forEach(key => {
|
||||
if (typeof opts[key] === 'function') {
|
||||
const fn = opts[key];
|
||||
opts[key] = (...props) => this.zone.run(() => fn(...props));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
create(opts: GestureConfig): Gesture {
|
||||
return createGesture(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Press recognizer gesture
|
||||
*/
|
||||
pressRecognizer(opts: PressRecognizerOptions): Gesture {
|
||||
return createPressRecognizer(opts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Injectable, Optional } from '@angular/core';
|
||||
import { NavigationExtras, NavigationStart, Router, UrlSerializer, UrlTree } from '@angular/router';
|
||||
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
|
||||
import { NavDirection, RouterDirection } from '@ionic/core';
|
||||
|
||||
import { IonRouterOutlet } from '../directives/navigation/ion-router-outlet';
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Platform } from './platform';
|
||||
|
||||
export interface AnimationOptions {
|
||||
animated?: boolean;
|
||||
animation?: AnimationBuilder;
|
||||
animationDirection?: 'forward' | 'back';
|
||||
}
|
||||
|
||||
@@ -23,7 +22,6 @@ export class NavController {
|
||||
private topOutlet?: IonRouterOutlet;
|
||||
private direction: 'forward' | 'back' | 'root' | 'auto' = DEFAULT_DIRECTION;
|
||||
private animated?: NavDirection = DEFAULT_ANIMATED;
|
||||
private animationBuilder?: AnimationBuilder;
|
||||
private guessDirection: RouterDirection = 'forward';
|
||||
private guessAnimation?: NavDirection;
|
||||
private lastNavId = -1;
|
||||
@@ -47,10 +45,7 @@ export class NavController {
|
||||
}
|
||||
|
||||
// Subscribe to backButton events
|
||||
platform.backButton.subscribeWithPriority(0, processNextHandler => {
|
||||
this.pop();
|
||||
processNextHandler();
|
||||
});
|
||||
platform.backButton.subscribeWithPriority(0, () => this.pop());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +62,7 @@ export class NavController {
|
||||
* ```
|
||||
*/
|
||||
navigateForward(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
||||
this.setDirection('forward', options.animated, options.animationDirection, options.animation);
|
||||
this.setDirection('forward', options.animated, options.animationDirection);
|
||||
return this.navigate(url, options);
|
||||
}
|
||||
|
||||
@@ -90,7 +85,7 @@ export class NavController {
|
||||
* ```
|
||||
*/
|
||||
navigateBack(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
||||
this.setDirection('back', options.animated, options.animationDirection, options.animation);
|
||||
this.setDirection('back', options.animated, options.animationDirection);
|
||||
return this.navigate(url, options);
|
||||
}
|
||||
|
||||
@@ -113,7 +108,7 @@ export class NavController {
|
||||
* ```
|
||||
*/
|
||||
navigateRoot(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {
|
||||
this.setDirection('root', options.animated, options.animationDirection, options.animation);
|
||||
this.setDirection('root', options.animated, options.animationDirection);
|
||||
return this.navigate(url, options);
|
||||
}
|
||||
|
||||
@@ -123,7 +118,7 @@ export class NavController {
|
||||
* by default.
|
||||
*/
|
||||
back(options: AnimationOptions = { animated: true, animationDirection: 'back' }) {
|
||||
this.setDirection('back', options.animated, options.animationDirection, options.animation);
|
||||
this.setDirection('back', options.animated, options.animationDirection);
|
||||
return this.location.back();
|
||||
}
|
||||
|
||||
@@ -152,10 +147,9 @@ export class NavController {
|
||||
*
|
||||
* It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
|
||||
*/
|
||||
setDirection(direction: RouterDirection, animated?: boolean, animationDirection?: 'forward' | 'back', animationBuilder?: AnimationBuilder) {
|
||||
setDirection(direction: RouterDirection, animated?: boolean, animationDirection?: 'forward' | 'back') {
|
||||
this.direction = direction;
|
||||
this.animated = getAnimation(direction, animated, animationDirection);
|
||||
this.animationBuilder = animationBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +165,6 @@ export class NavController {
|
||||
consumeTransition() {
|
||||
let direction: RouterDirection = 'root';
|
||||
let animation: NavDirection | undefined;
|
||||
const animationBuilder = this.animationBuilder;
|
||||
|
||||
if (this.direction === 'auto') {
|
||||
direction = this.guessDirection;
|
||||
@@ -182,12 +175,10 @@ export class NavController {
|
||||
}
|
||||
this.direction = DEFAULT_DIRECTION;
|
||||
this.animated = DEFAULT_ANIMATED;
|
||||
this.animationBuilder = undefined;
|
||||
|
||||
return {
|
||||
direction,
|
||||
animation,
|
||||
animationBuilder
|
||||
animation
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable, NgZone } from '@angular/core';
|
||||
import { BackButtonEventDetail, KeyboardEventDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core';
|
||||
import { BackButtonEventDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
|
||||
export interface BackButtonEmitter extends Subject<BackButtonEventDetail> {
|
||||
subscribeWithPriority(priority: number, callback: (processNextHandler: () => void) => Promise<any> | void): Subscription;
|
||||
subscribeWithPriority(priority: number, callback: () => Promise<any> | void): Subscription;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@@ -20,18 +20,6 @@ export class Platform {
|
||||
*/
|
||||
backButton: BackButtonEmitter = new Subject<BackButtonEventDetail>() as any;
|
||||
|
||||
/**
|
||||
* The keyboardDidShow event emits when the
|
||||
* on-screen keyboard is presented.
|
||||
*/
|
||||
keyboardDidShow = new Subject<KeyboardEventDetail>() as any;
|
||||
|
||||
/**
|
||||
* The keyboardDidHide event emits when the
|
||||
* on-screen keyboard is hidden.
|
||||
*/
|
||||
keyboardDidHide = new Subject<void>();
|
||||
|
||||
/**
|
||||
* The pause event emits when the native platform puts the application
|
||||
* into the background, typically when the user switches to a different
|
||||
@@ -58,17 +46,15 @@ export class Platform {
|
||||
zone.run(() => {
|
||||
this.win = doc.defaultView;
|
||||
this.backButton.subscribeWithPriority = function(priority, callback) {
|
||||
return this.subscribe(ev => {
|
||||
return ev.register(priority, processNextHandler => zone.run(() => callback(processNextHandler)));
|
||||
});
|
||||
return this.subscribe(ev => (
|
||||
ev.register(priority, () => zone.run(callback))
|
||||
));
|
||||
};
|
||||
|
||||
proxyEvent(this.pause, doc, 'pause');
|
||||
proxyEvent(this.resume, doc, 'resume');
|
||||
proxyEvent(this.backButton, doc, 'ionBackButton');
|
||||
proxyEvent(this.resize, this.win, 'resize');
|
||||
proxyEvent(this.keyboardDidShow, this.win, 'ionKeyboardDidShow');
|
||||
proxyEvent(this.keyboardDidHide, this.win, 'ionKeyboardDidHide');
|
||||
|
||||
let readyResolve: (value: string) => void;
|
||||
this._readyPromise = new Promise(res => { readyResolve = res; });
|
||||
|
||||
@@ -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, addAsset, addStyle, getDefaultAngularAppName, getWorkspace, 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,65 +37,61 @@ function addIonicAngularModuleToAppModule(projectSourceRoot: Path): Rule {
|
||||
};
|
||||
}
|
||||
|
||||
function addIonicStyles(projectName: string, projectSourceRoot: Path): Rule {
|
||||
function addIonicStyles(): Rule {
|
||||
return (host: Tree) => {
|
||||
const ionicStyles = [
|
||||
'node_modules/@ionic/angular/css/core.css',
|
||||
'node_modules/@ionic/angular/css/normalize.css',
|
||||
'node_modules/@ionic/angular/css/structure.css',
|
||||
'node_modules/@ionic/angular/css/typography.css',
|
||||
'node_modules/@ionic/angular/css/display.css',
|
||||
'node_modules/@ionic/angular/css/core.css',
|
||||
'node_modules/@ionic/angular/css/padding.css',
|
||||
'node_modules/@ionic/angular/css/float-elements.css',
|
||||
'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`
|
||||
]
|
||||
|
||||
ionicStyles.forEach(entry => {
|
||||
addStyle(host, projectName, entry);
|
||||
'src/theme/variables.css'
|
||||
].forEach(entry => {
|
||||
addStyle(host, entry);
|
||||
});
|
||||
return host;
|
||||
return host;
|
||||
};
|
||||
}
|
||||
|
||||
function addIonicons(projectName: string): Rule {
|
||||
function addIonicons(): Rule {
|
||||
return (host: Tree) => {
|
||||
const ioniconsGlob = {
|
||||
glob: '**/*.svg',
|
||||
input: 'node_modules/ionicons/dist/ionicons/svg',
|
||||
output: './svg'
|
||||
};
|
||||
addAsset(host, projectName, 'build', ioniconsGlob);
|
||||
addAsset(host, projectName, 'test', ioniconsGlob);
|
||||
addAsset(host, ioniconsGlob);
|
||||
return host;
|
||||
};
|
||||
}
|
||||
|
||||
function addIonicBuilder(projectName: string): Rule {
|
||||
function addIonicBuilder(): Rule {
|
||||
return (host: Tree) => {
|
||||
addArchitectBuilder(host, projectName, 'ionic-cordova-serve', {
|
||||
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, projectName, 'ionic-cordova-build', {
|
||||
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'
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -104,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 = getDefaultAngularAppName(workspace);
|
||||
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)
|
||||
@@ -131,9 +128,9 @@ export default function ngAdd(options: IonAddOptions): Rule {
|
||||
addIonicAngularToPackageJson(),
|
||||
addIonicAngularToolkitToPackageJson(),
|
||||
addIonicAngularModuleToAppModule(sourcePath),
|
||||
addIonicBuilder(options.project),
|
||||
addIonicStyles(options.project, sourcePath),
|
||||
addIonicons(options.project),
|
||||
addIonicBuilder(),
|
||||
addIonicStyles(),
|
||||
addIonicons(),
|
||||
mergeWith(rootTemplateSource),
|
||||
// install freshly added dependencies
|
||||
installNodeDeps()
|
||||
|
||||
@@ -27,7 +27,7 @@ export function getSourceFile(host: Tree, path: string): ts.SourceFile {
|
||||
*/
|
||||
export function addModuleImportToRootModule(
|
||||
host: Tree,
|
||||
projectSourceRoot: string,
|
||||
projectSourceRoot,
|
||||
moduleName: string,
|
||||
importSrc: string
|
||||
) {
|
||||
|
||||
@@ -21,7 +21,7 @@ function isAngularBrowserProject(projectConfig: any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getDefaultAngularAppName(config: any): string {
|
||||
export function getAngularAppName(config: any): string | null {
|
||||
const projects = config.projects;
|
||||
const projectNames = Object.keys(projects);
|
||||
|
||||
@@ -32,52 +32,63 @@ export function getDefaultAngularAppName(config: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
return projectNames[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getAngularAppConfig(config: any, projectName: string): any | never {
|
||||
if (!config.projects.hasOwnProperty(projectName)) {
|
||||
throw new SchematicsException(`Could not find project: ${projectName}`);
|
||||
export function getAngularAppConfig(config: any): any | null {
|
||||
const projects = config.projects;
|
||||
const projectNames = Object.keys(projects);
|
||||
|
||||
for (const projectName of projectNames) {
|
||||
const projectConfig = projects[projectName];
|
||||
if (isAngularBrowserProject(projectConfig)) {
|
||||
return projectConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfig = config.projects[projectName];
|
||||
if (isAngularBrowserProject(projectConfig)) {
|
||||
return projectConfig;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.projectType !== 'application') {
|
||||
throw new SchematicsException(`Invalid projectType for ${projectName}: ${config.projectType}`);
|
||||
export function addStyle(host: Tree, stylePath: string) {
|
||||
const config = readConfig(host);
|
||||
const appConfig = getAngularAppConfig(config);
|
||||
|
||||
if (appConfig) {
|
||||
appConfig.architect.build.options.styles.push({
|
||||
input: stylePath
|
||||
});
|
||||
|
||||
writeConfig(host, config);
|
||||
} else {
|
||||
const buildConfig = projectConfig.architect.build;
|
||||
throw new SchematicsException(`Invalid builder for ${projectName}: ${buildConfig.builder}`);
|
||||
throw new SchematicsException(`Cannot find valid app`);
|
||||
}
|
||||
}
|
||||
|
||||
export function addStyle(host: Tree, projectName: string, stylePath: string) {
|
||||
export function addAsset(host: Tree, asset: string | {glob: string; input: string; output: string}) {
|
||||
const config = readConfig(host);
|
||||
const appConfig = getAngularAppConfig(config, projectName);
|
||||
appConfig.architect.build.options.styles.push({
|
||||
input: stylePath
|
||||
});
|
||||
writeConfig(host, config);
|
||||
const appConfig = getAngularAppConfig(config);
|
||||
|
||||
if (appConfig) {
|
||||
appConfig.architect.build.options.assets.push(asset);
|
||||
writeConfig(host, config);
|
||||
} else {
|
||||
throw new SchematicsException(`Cannot find valid app`);
|
||||
}
|
||||
}
|
||||
|
||||
export function addAsset(host: Tree, projectName: string, architect: string, asset: string | {glob: string; input: string; output: string}) {
|
||||
export function addArchitectBuilder(host: Tree, builderName: string, builderOpts: any){
|
||||
const config = readConfig(host);
|
||||
const appConfig = getAngularAppConfig(config, projectName);
|
||||
appConfig.architect[architect].options.assets.push(asset);
|
||||
writeConfig(host, config);
|
||||
}
|
||||
const appConfig = getAngularAppConfig(config);
|
||||
|
||||
export function addArchitectBuilder(host: Tree, projectName: string, builderName: string, builderOpts: any): void | never {
|
||||
const config = readConfig(host);
|
||||
const appConfig = getAngularAppConfig(config, projectName);
|
||||
appConfig.architect[builderName] = builderOpts;
|
||||
writeConfig(host, config);
|
||||
if (appConfig) {
|
||||
appConfig.architect[builderName] = builderOpts
|
||||
writeConfig(host, config);
|
||||
} else {
|
||||
throw new SchematicsException(`Cannot find valid app`);
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkspaceSchema = experimental.workspace.WorkspaceSchema;
|
||||
export type WorkspaceProject = experimental.workspace.WorkspaceProject;
|
||||
|
||||
export function getWorkspacePath(host: Tree): string {
|
||||
const possibleFiles = ['/angular.json', '/.angular.json'];
|
||||
|
||||
@@ -10,6 +10,6 @@ export interface IonicWindow extends Window {
|
||||
}
|
||||
|
||||
export interface HTMLStencilElement extends HTMLElement {
|
||||
componentOnReady?(): Promise<this>;
|
||||
forceUpdate?(): void;
|
||||
componentOnReady(): Promise<this>;
|
||||
forceUpdate(): void;
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* https://ionicframework.com/docs/api/router-outlet#life-cycle-hooks
|
||||
*/
|
||||
|
||||
export interface ViewWillEnter {
|
||||
/**
|
||||
* Fired when the component routing to is about to animate into view.
|
||||
*/
|
||||
ionViewWillEnter(): void;
|
||||
}
|
||||
|
||||
export interface ViewDidEnter {
|
||||
/**
|
||||
* Fired when the component routing to has finished animating.
|
||||
*/
|
||||
ionViewDidEnter(): void;
|
||||
}
|
||||
|
||||
export interface ViewWillLeave {
|
||||
/**
|
||||
* Fired when the component routing from is about to animate.
|
||||
*/
|
||||
ionViewWillLeave(): void;
|
||||
}
|
||||
|
||||
export interface ViewDidLeave {
|
||||
/**
|
||||
* Fired when the component routing to has finished animating.
|
||||
*/
|
||||
ionViewDidLeave(): void;
|
||||
}
|
||||
1
angular/test/test-app/.npmrc
Normal file
1
angular/test/test-app/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
package-lock=false
|
||||
@@ -13,13 +13,11 @@
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"aot": true,
|
||||
"outputPath": "dist/test-app/browser",
|
||||
"outputPath": "dist/browser",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"buildOptimizer": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
{
|
||||
@@ -59,10 +57,6 @@
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -85,6 +79,33 @@
|
||||
"browserTarget": "test-app:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": [],
|
||||
"assets": ["src/favicon.ico", "src/assets"]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "test-app:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "test-app:serve:production"
|
||||
},
|
||||
"ci": {
|
||||
"devServerTarget": "test-app:serve:ci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
@@ -95,44 +116,20 @@
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/test-app/server",
|
||||
"main": "server.ts",
|
||||
"outputPath": "dist/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "tsconfig.server.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"outputHashing": "media",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
"src": "src/environments/environment.ts",
|
||||
"replaceWith": "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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"integrationFolder": "./e2e",
|
||||
"testFiles": "**/*.spec.ts",
|
||||
"baseUrl": "http://localhost:4200/",
|
||||
"ignoreTestFiles": "**/examples/*",
|
||||
"video": false,
|
||||
"screenshotOnRunFailure": false
|
||||
}
|
||||
22
angular/test/test-app/cypress/plugins/index.js
vendored
22
angular/test/test-app/cypress/plugins/index.js
vendored
@@ -1,22 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('ionSwipeToGoBack', (complete = false, selector = 'ion-router-outlet') => {
|
||||
const increment = (complete) ? 60 : 25;
|
||||
cy.get(selector)
|
||||
.first()
|
||||
.trigger('mousedown', 0, 275, { which: 1, force: true })
|
||||
.trigger('mousemove', increment * 1, 275, { which: 1, force: true })
|
||||
.wait(50)
|
||||
.trigger('mousemove', increment * 2, 275, { which: 1, force: true })
|
||||
.wait(50)
|
||||
.trigger('mousemove', increment * 3, 275, { which: 1, force: true })
|
||||
.wait(50)
|
||||
.trigger('mousemove', increment * 4, 275, { which: 1, force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', increment * 4, 275, { which: 1, force: true })
|
||||
cy.wait(150);
|
||||
})
|
||||
|
||||
Cypress.Commands.add('testStack', (selector, expected) => {
|
||||
cy.document().then((doc) => {
|
||||
const children = Array.from(
|
||||
doc.querySelector(selector).children
|
||||
).map(el => el.tagName.toLowerCase());
|
||||
expect(children).to.deep.equal(expected);
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add('testLifeCycle', (selector, expected) => {
|
||||
cy.get(`${selector} #ngOnInit`).invoke('text').should('equal', '1');
|
||||
cy.get(`${selector} #ionViewWillEnter`).invoke('text').should('equal', expected.ionViewWillEnter.toString());
|
||||
cy.get(`${selector} #ionViewDidEnter`).invoke('text').should('equal', expected.ionViewDidEnter.toString());
|
||||
cy.get(`${selector} #ionViewWillLeave`).invoke('text').should('equal', expected.ionViewWillLeave.toString());
|
||||
cy.get(`${selector} #ionViewDidLeave`).invoke('text').should('equal', expected.ionViewDidLeave.toString());
|
||||
})
|
||||
|
||||
Cypress.Commands.add('ionPageVisible', (selector) => {
|
||||
cy.get(selector)
|
||||
.should('have.class', 'ion-page')
|
||||
.should('not.have.class', 'ion-page-hidden')
|
||||
.should('not.have.class', 'ion-page-invisible')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('ionPageHidden', (selector) => {
|
||||
cy.get(selector)
|
||||
.should('have.class', 'ion-page')
|
||||
.should('have.class', 'ion-page-hidden')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('ionPageDoesNotExist', (selector) => {
|
||||
cy.get(selector)
|
||||
.should('not.exist')
|
||||
});
|
||||
69
angular/test/test-app/cypress/support/index.d.ts
vendored
69
angular/test/test-app/cypress/support/index.d.ts
vendored
@@ -1,69 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable<Subject> {
|
||||
/**
|
||||
* Swipe to go back on the current selector or router outlet
|
||||
* @example
|
||||
* ```
|
||||
* cy.ionSwipeToGoBack();
|
||||
* cy.ionSwipeToGoBack(true);
|
||||
* ```
|
||||
*/
|
||||
ionSwipeToGoBack(complete: boolean, selector: string): Chainable<any>
|
||||
/**
|
||||
* Test that the proper pages are in the navigation stack
|
||||
* @example
|
||||
* ```
|
||||
* cy.testStack('ion-router-outlet', ['app-navigation-page2', 'app-navigation-page1']);
|
||||
* cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
* ```
|
||||
*/
|
||||
testStack(selector: string, expected: string[]): Chainable<any>
|
||||
/**
|
||||
* Test whether or not the lifecycle events fired
|
||||
* @example
|
||||
* ```
|
||||
* cy.testLifeCycle('app-router-link-page', {
|
||||
* ionViewWillEnter: 1,
|
||||
* ionViewDidEnter: 1,
|
||||
* ionViewWillLeave: 0,
|
||||
* ionViewDidLeave: 0,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
testLifeCycle(selector: string, expected: any): Chainable<any>
|
||||
|
||||
/**
|
||||
* Test whether or not an .ion-page element is visible.
|
||||
* Use this to test a page after navigating to it.
|
||||
* @example
|
||||
* ```
|
||||
* cy.ionPageVisible('app-my-page');
|
||||
* ```
|
||||
*/
|
||||
ionPageVisible(selector: string): Chainable<any>
|
||||
|
||||
/**
|
||||
* Test whether or not an .ion-page element is hidden
|
||||
* Use this to test a page after navigating away from it.
|
||||
* @example
|
||||
* ```
|
||||
* cy.ionPageHidden('app-my-page');
|
||||
* ```
|
||||
*/
|
||||
ionPageHidden(selector: string): Chainable<any>
|
||||
|
||||
/**
|
||||
* Test whether or not an .ion-page element exists.
|
||||
* Use this to test a page after popping it off the stack.
|
||||
* @example
|
||||
* ```
|
||||
* cy.ionPageDoesNotExist('app-my-page');
|
||||
* ```
|
||||
*/
|
||||
ionPageDoesNotExist(selector: string): Chainable<any>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
angular/test/test-app/cypress/support/index.js
vendored
20
angular/test/test-app/cypress/support/index.js
vendored
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
35
angular/test/test-app/e2e/protractor.conf.js
Normal file
35
angular/test/test-app/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @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'
|
||||
],
|
||||
capabilities: {
|
||||
'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: 70000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
119
angular/test/test-app/e2e/src/form.e2e-spec.ts
Normal file
119
angular/test/test-app/e2e/src/form.e2e-spec.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { handleErrorMessages, setProperty, getText, waitTime } from './utils';
|
||||
|
||||
describe('form', () => {
|
||||
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
describe('change', () => {
|
||||
beforeEach(async () => {
|
||||
await browser.get('/form');
|
||||
await waitTime(30);
|
||||
});
|
||||
|
||||
it('should have default values', async () => {
|
||||
await testStatus('INVALID');
|
||||
expect(await getText('#submit')).toEqual('false');
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': null,
|
||||
'toggle': false,
|
||||
'input': '',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': false,
|
||||
'range': 5
|
||||
});
|
||||
});
|
||||
|
||||
it('should become valid', async () => {
|
||||
await setProperty('ion-input.required', 'value', 'Some value');
|
||||
await testStatus('INVALID');
|
||||
await setProperty('ion-select', 'value', 'nes');
|
||||
await testStatus('INVALID');
|
||||
await setProperty('ion-range', 'value', 40);
|
||||
await testStatus('VALID');
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': 'nes',
|
||||
'toggle': false,
|
||||
'input': 'Some value',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': false,
|
||||
'range': 40
|
||||
});
|
||||
});
|
||||
|
||||
it('ion-toggle should change', async () => {
|
||||
await element(by.css('form ion-toggle')).click();
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': null,
|
||||
'toggle': true,
|
||||
'input': '',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': false,
|
||||
'range': 5
|
||||
});
|
||||
});
|
||||
|
||||
it('ion-checkbox should change', async () => {
|
||||
await element(by.css('ion-checkbox')).click();
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': null,
|
||||
'toggle': false,
|
||||
'input': '',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': true,
|
||||
'range': 5
|
||||
});
|
||||
});
|
||||
|
||||
it('should submit', async () => {
|
||||
await element(by.css('#set-values')).click();
|
||||
await waitTime(100);
|
||||
await element(by.css('#submit-button')).click();
|
||||
expect(await getText('#submit')).toEqual('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('blur', () => {
|
||||
beforeEach(async () => {
|
||||
await browser.get('/form#blur');
|
||||
await waitTime(30);
|
||||
});
|
||||
|
||||
it('ion-toggle should change only after blur', async () => {
|
||||
await element(by.css('form ion-toggle')).click();
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': null,
|
||||
'toggle': false,
|
||||
'input': '',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': false,
|
||||
'range': 5
|
||||
});
|
||||
await element(by.css('ion-checkbox')).click();
|
||||
await testData({
|
||||
'datetime': '2010-08-20',
|
||||
'select': null,
|
||||
'toggle': true,
|
||||
'input': '',
|
||||
'input2': 'Default Value',
|
||||
'checkbox': false,
|
||||
'range': 5
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function testStatus(status: string) {
|
||||
expect(await element(by.css('#status')).getText()).toEqual(status);
|
||||
}
|
||||
|
||||
async function testData(data: any) {
|
||||
expect(JSON.parse(await element(by.css('#data')).getText())).toEqual(data);
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
describe('Form', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/form');
|
||||
})
|
||||
|
||||
describe('status updates', () => {
|
||||
it('should update Ionic form classes when calling form methods programmatically', async () => {
|
||||
cy.get('#input-touched').click();
|
||||
cy.get('#touched-input-test').should('have.class', 'ion-touched');
|
||||
});
|
||||
});
|
||||
|
||||
describe('change', () => {
|
||||
it('should have default values', () => {
|
||||
testStatus('INVALID');
|
||||
cy.get('#submit').should('have.text', 'false');
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: false,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
checkbox: false,
|
||||
range: 5
|
||||
});
|
||||
});
|
||||
|
||||
it('should become valid', () => {
|
||||
cy.get('ion-input.required').invoke('prop', 'value', 'Some value');
|
||||
testStatus('INVALID');
|
||||
|
||||
cy.get('ion-select').invoke('prop', 'value', 'nes');
|
||||
testStatus('INVALID');
|
||||
|
||||
cy.get('ion-range').invoke('prop', 'value', 40);
|
||||
testStatus('VALID');
|
||||
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: 'nes',
|
||||
toggle: false,
|
||||
input: 'Some value',
|
||||
input2: 'Default Value',
|
||||
checkbox: false,
|
||||
range: 40
|
||||
});
|
||||
});
|
||||
|
||||
it('ion-toggle should change', () => {
|
||||
cy.get('form ion-toggle').click();
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: true,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
checkbox: false,
|
||||
range: 5
|
||||
});
|
||||
});
|
||||
|
||||
it('ion-checkbox should change', () => {
|
||||
cy.get('ion-checkbox').click();
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: false,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
checkbox: true,
|
||||
range: 5
|
||||
});
|
||||
});
|
||||
|
||||
it('should submit', () => {
|
||||
cy.get('#set-values').click();
|
||||
cy.get('#submit-button').click();
|
||||
cy.get('#submit').should('have.text', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('blur', () => {
|
||||
it('ion-toggle should change only after blur', () => {
|
||||
cy.get('form ion-toggle').click();
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: true,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
checkbox: false,
|
||||
range: 5
|
||||
});
|
||||
cy.get('ion-checkbox').click();
|
||||
testData({
|
||||
datetime: '2010-08-20',
|
||||
select: null,
|
||||
toggle: true,
|
||||
input: '',
|
||||
input2: 'Default Value',
|
||||
checkbox: true,
|
||||
range: 5
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testStatus(status) {
|
||||
cy.get('#status').should('have.text', status);
|
||||
}
|
||||
|
||||
function testData(data) {
|
||||
cy.get('#data').invoke('text').then(text => {
|
||||
const value = JSON.parse(text);
|
||||
console.log(value, data);
|
||||
expect(value).to.deep.equal(data);
|
||||
})
|
||||
}
|
||||
69
angular/test/test-app/e2e/src/inputs.e2e-spec.ts
Normal file
69
angular/test/test-app/e2e/src/inputs.e2e-spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { getProperty, setProperty, handleErrorMessages, waitTime } from './utils';
|
||||
|
||||
describe('inputs', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get('/inputs');
|
||||
await waitTime(30);
|
||||
});
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should have default value', async () => {
|
||||
expect(await getProperty('ion-checkbox', 'checked')).toEqual(true);
|
||||
expect(await getProperty('ion-toggle', 'checked')).toEqual(true);
|
||||
expect(await getProperty('ion-input', 'value')).toEqual('some text');
|
||||
expect(await getProperty('ion-datetime', 'value')).toEqual('1994-03-15');
|
||||
expect(await getProperty('ion-select', 'value')).toEqual('nes');
|
||||
expect(await getProperty('ion-range', 'value')).toEqual(10);
|
||||
});
|
||||
|
||||
it('should have reset value', async () => {
|
||||
await element(by.css('#reset-button')).click();
|
||||
|
||||
expect(await getProperty('ion-checkbox', 'checked')).toEqual(false);
|
||||
expect(await getProperty('ion-toggle', 'checked')).toEqual(false);
|
||||
expect(await getProperty('ion-input', 'value')).toEqual('');
|
||||
expect(await getProperty('ion-datetime', 'value')).toEqual('');
|
||||
expect(await getProperty('ion-select', 'value')).toEqual('');
|
||||
expect(await getProperty('ion-range', 'value')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should get some value', async () => {
|
||||
await element(by.css('#reset-button')).click();
|
||||
await element(by.css('#set-button')).click();
|
||||
|
||||
expect(await getProperty('ion-checkbox', 'checked')).toEqual(true);
|
||||
expect(await getProperty('ion-toggle', 'checked')).toEqual(true);
|
||||
expect(await getProperty('ion-input', 'value')).toEqual('some text');
|
||||
expect(await getProperty('ion-datetime', 'value')).toEqual('1994-03-15');
|
||||
expect(await getProperty('ion-select', 'value')).toEqual('nes');
|
||||
expect(await getProperty('ion-range', 'value')).toEqual(10);
|
||||
});
|
||||
|
||||
it('change values should update angular', async () => {
|
||||
await element(by.css('#reset-button')).click();
|
||||
|
||||
await setProperty('ion-checkbox', 'checked', true);
|
||||
await setProperty('ion-toggle', 'checked', true);
|
||||
await setProperty('ion-input', 'value', 'hola');
|
||||
await setProperty('ion-datetime', 'value', '1996-03-15');
|
||||
await setProperty('ion-select', 'value', 'playstation');
|
||||
await setProperty('ion-range', 'value', 20);
|
||||
|
||||
expect(await element(by.css('#checkbox-note')).getText()).toEqual('true');
|
||||
expect(await element(by.css('#toggle-note')).getText()).toEqual('true');
|
||||
expect(await element(by.css('#input-note')).getText()).toEqual('hola');
|
||||
expect(await element(by.css('#datetime-note')).getText()).toEqual('1996-03-15');
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
describe('Inputs', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/inputs');
|
||||
})
|
||||
|
||||
it('should have default value', () => {
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
|
||||
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
|
||||
cy.get('ion-select').should('have.prop', 'value').and('equal', 'nes');
|
||||
cy.get('ion-range').should('have.prop', 'value').and('equal', 10);
|
||||
});
|
||||
|
||||
it('should have reset value', () => {
|
||||
cy.get('#reset-button').click();
|
||||
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', false);
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', false);
|
||||
cy.get('ion-input').should('have.prop', 'value').and('equal', '');
|
||||
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '');
|
||||
cy.get('ion-select').should('have.prop', 'value').and('equal', '');
|
||||
cy.get('ion-range').should('have.prop', 'value').and('be.NaN');
|
||||
});
|
||||
|
||||
it('should get some value', () => {
|
||||
cy.get('#reset-button').click();
|
||||
cy.get('#set-button').click();
|
||||
|
||||
cy.get('ion-checkbox').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-toggle').should('have.prop', 'checked').and('equal', true);
|
||||
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
|
||||
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
|
||||
cy.get('ion-select').should('have.prop', 'value').and('equal', 'nes');
|
||||
cy.get('ion-range').should('have.prop', 'value').and('equal', 10);
|
||||
});
|
||||
|
||||
it('change values should update angular', () => {
|
||||
cy.get('#reset-button').click();
|
||||
|
||||
cy.get('ion-checkbox').invoke('prop', 'checked', true);
|
||||
cy.get('ion-toggle').invoke('prop', 'checked', true);
|
||||
cy.get('ion-input').invoke('prop', 'value', 'hola');
|
||||
cy.get('ion-datetime').invoke('prop', 'value', '1996-03-15');
|
||||
cy.get('ion-select').invoke('prop', 'value', 'playstation');
|
||||
cy.get('ion-range').invoke('prop', 'value', 20);
|
||||
|
||||
cy.get('#checkbox-note').should('have.text', 'true');
|
||||
cy.get('#toggle-note').should('have.text', 'true');
|
||||
cy.get('#input-note').should('have.text', 'hola');
|
||||
cy.get('#datetime-note').should('have.text', '1996-03-15');
|
||||
cy.get('#select-note').should('have.text', 'playstation');
|
||||
cy.get('#range-note').should('have.text', '20');
|
||||
});
|
||||
|
||||
it('nested components should not interfere with NgModel', () => {
|
||||
cy.get('#range-note').should('have.text', '10');
|
||||
cy.get('#nested-toggle').click();
|
||||
cy.get('#range-note').should('have.text', '10');
|
||||
});
|
||||
})
|
||||
55
angular/test/test-app/e2e/src/modal.e2e-spec.ts
Normal file
55
angular/test/test-app/e2e/src/modal.e2e-spec.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { waitTime, getText, handleErrorMessages } from './utils';
|
||||
|
||||
describe('modals', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get('/modals');
|
||||
await waitTime(30);
|
||||
});
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should open standalone modal and close', async () => {
|
||||
await element(by.css('#action-button')).click();
|
||||
|
||||
await waitTime(800);
|
||||
|
||||
const modal = element(by.css('app-modal-example'));
|
||||
expect(await modal.$('h2').getText()).toEqual('123');
|
||||
expect(await modal.$('h3').getText()).toEqual('321');
|
||||
|
||||
expect(await getText('#onWillDismiss')).toEqual('false');
|
||||
expect(await getText('#onDidDismiss')).toEqual('false');
|
||||
|
||||
await modal.$('#close-modal').click();
|
||||
await waitTime(800);
|
||||
|
||||
expect(await getText('#onWillDismiss')).toEqual('true');
|
||||
expect(await getText('#onDidDismiss')).toEqual('true');
|
||||
});
|
||||
|
||||
it('should open nav modal and close', async () => {
|
||||
await element(by.css('#action-button-2')).click();
|
||||
|
||||
await waitTime(800);
|
||||
|
||||
let page = element(by.css('ion-nav > *:last-child'));
|
||||
expect(await page.$('h2').getText()).toEqual('123');
|
||||
expect(await page.$('h3').getText()).toEqual('321');
|
||||
|
||||
await page.$('.push-page').click();
|
||||
await waitTime(800);
|
||||
|
||||
page = element(by.css('ion-nav > *:last-child'));
|
||||
expect(await page.$('h2').getText()).toEqual('pushed!');
|
||||
expect(await page.$('h3').getText()).toEqual('');
|
||||
|
||||
await page.$('.pop-page').click();
|
||||
await waitTime(800);
|
||||
|
||||
page = element(by.css('ion-nav > *:last-child'));
|
||||
expect(await page.$('h2').getText()).toEqual('123');
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
describe('Modals', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/modals');
|
||||
})
|
||||
|
||||
it('should open standalone modal and close', () => {
|
||||
cy.get('#action-button').click();
|
||||
|
||||
cy.get('ion-modal').should('exist').should('be.visible');
|
||||
|
||||
cy.get('app-modal-example h2').should('have.text', '123');
|
||||
cy.get('app-modal-example h3').should('have.text', '321');
|
||||
|
||||
cy.get('#onWillDismiss').should('have.text', 'false');
|
||||
cy.get('#onDidDismiss').should('have.text', 'false');
|
||||
|
||||
cy.get('#close-modal').click();
|
||||
|
||||
cy.get('ion-modal').should('not.exist');
|
||||
|
||||
cy.get('#onWillDismiss').should('have.text', 'true');
|
||||
cy.get('#onDidDismiss').should('have.text', 'true');
|
||||
});
|
||||
|
||||
it('should open nav modal and close', () => {
|
||||
cy.get('#action-button-2').click();
|
||||
|
||||
cy.get('ion-modal').should('exist').should('be.visible');
|
||||
|
||||
cy.get('ion-nav > *:last-child h2').should('have.text', '123');
|
||||
cy.get('ion-nav > *:last-child h3').should('have.text', '321');
|
||||
|
||||
cy.get('ion-nav > *:last-child .push-page').click();
|
||||
|
||||
cy.get('ion-nav > *:last-child h2').should('have.text', 'pushed!');
|
||||
cy.get('ion-nav > *:last-child h3').should('have.text', '');
|
||||
|
||||
cy.get('ion-nav > *:last-child .pop-page').click();
|
||||
|
||||
cy.get('ion-nav > *:last-child h2').should('have.text', '123');
|
||||
});
|
||||
|
||||
});
|
||||
73
angular/test/test-app/e2e/src/navigation.e2e-spec.ts
Normal file
73
angular/test/test-app/e2e/src/navigation.e2e-spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { handleErrorMessages, waitTime, testStack } from './utils';
|
||||
|
||||
describe('navigation', () => {
|
||||
|
||||
afterEach(() => {
|
||||
return 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);
|
||||
await testStack('ion-router-outlet', ['app-navigation-page2', 'app-navigation-page1']);
|
||||
|
||||
const pageHidden = element(by.css('app-navigation-page2'));
|
||||
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-navigation-page1'));
|
||||
expect(await pageVisible.getAttribute('aria-hidden')).toEqual(null);
|
||||
expect(await pageVisible.getAttribute('class')).toEqual('ion-page can-go-back');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
describe('Navigation', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/navigation');
|
||||
})
|
||||
|
||||
it('should navigate correctly', () => {
|
||||
cy.visit('/navigation/page1');
|
||||
cy.wait(2000);
|
||||
cy.testStack('ion-router-outlet', ['app-navigation-page2', 'app-navigation-page1']);
|
||||
|
||||
cy.get('app-navigation-page2').should('have.attr', 'aria-hidden').and('equal', 'true');
|
||||
cy.get('app-navigation-page2').should('have.attr', 'class').and('equal', 'ion-page ion-page-hidden');
|
||||
|
||||
cy.get('app-navigation-page1').should('not.have.attr', 'aria-hidden');
|
||||
cy.get('app-navigation-page1').should('have.attr', 'class').and('equal', 'ion-page can-go-back');
|
||||
});
|
||||
})
|
||||
|
||||
23
angular/test/test-app/e2e/src/nested-outlet.e2e-spec.ts
Normal file
23
angular/test/test-app/e2e/src/nested-outlet.e2e-spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { waitTime, handleErrorMessages, goBack } from './utils';
|
||||
|
||||
describe('nested-outlet', () => {
|
||||
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should navigate correctly', async () => {
|
||||
await browser.get('/nested-outlet/page');
|
||||
expect(await element(by.css('ion-router-outlet ion-router-outlet app-nested-outlet-page h1')).getText()).toEqual('Nested page 1');
|
||||
|
||||
await element(by.css('#goto-tabs')).click();
|
||||
await waitTime(500);
|
||||
await element(by.css('#goto-nested-page1')).click();
|
||||
await waitTime(500);
|
||||
await element(by.css('#goto-nested-page2')).click();
|
||||
await waitTime(500);
|
||||
expect(await element(by.css('ion-router-outlet ion-router-outlet app-nested-outlet-page2 h1')).getText()).toEqual('Nested page 2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
describe('Nested Outlet', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/nested-outlet/page');
|
||||
})
|
||||
|
||||
it('should navigate correctly', () => {
|
||||
cy.get('ion-router-outlet ion-router-outlet app-nested-outlet-page h1').should('have.text', 'Nested page 1');
|
||||
|
||||
cy.get('#goto-tabs').click();
|
||||
|
||||
cy.ionPageVisible('app-tabs');
|
||||
cy.ionPageVisible('app-tabs-tab1');
|
||||
|
||||
cy.get('#goto-nested-page1').click();
|
||||
|
||||
cy.ionPageVisible('app-nested-outlet-page');
|
||||
cy.ionPageDoesNotExist('app-tabs');
|
||||
|
||||
cy.get('#goto-nested-page2').click();
|
||||
cy.ionPageVisible('app-nested-outlet-page2');
|
||||
|
||||
cy.get('ion-router-outlet ion-router-outlet app-nested-outlet-page2 h1').should('have.text', 'Nested page 2');
|
||||
});
|
||||
});
|
||||
|
||||
29
angular/test/test-app/e2e/src/providers.e2e-spec.ts
Normal file
29
angular/test/test-app/e2e/src/providers.e2e-spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { handleErrorMessages, waitTime } from './utils';
|
||||
|
||||
describe('providers', () => {
|
||||
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should load all providers', async () => {
|
||||
await browser.get('/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');
|
||||
expect(await element(by.css('#keyboard-height')).getText()).toEqual('12345');
|
||||
});
|
||||
|
||||
it('should detect testing mode', async () => {
|
||||
await browser.get('/providers?ionic:_testing=true');
|
||||
|
||||
expect(await element(by.css('#is-testing')).getText()).toEqual('true');
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
describe('Providers', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/providers');
|
||||
})
|
||||
|
||||
it('should load all providers', () => {
|
||||
cy.get('#is-loaded').should('have.text', 'true');
|
||||
cy.get('#is-ready').should('have.text', 'true');
|
||||
cy.get('#is-paused').should('have.text', 'true');
|
||||
cy.get('#is-resumed').should('have.text', 'true');
|
||||
cy.get('#is-resized').should('have.text', 'true');
|
||||
cy.get('#is-testing').should('have.text', 'false');
|
||||
cy.get('#is-desktop').should('have.text', 'true');
|
||||
cy.get('#is-mobile').should('have.text', 'false');
|
||||
cy.get('#keyboard-height').should('have.text', '12345');
|
||||
});
|
||||
|
||||
it('should detect testing mode', () => {
|
||||
cy.visit('/providers?ionic:_testing=true');
|
||||
|
||||
cy.get('#is-testing').should('have.text', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
194
angular/test/test-app/e2e/src/router-link.e2e-spec.ts
Normal file
194
angular/test/test-app/e2e/src/router-link.e2e-spec.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('router-link', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get('/router-link');
|
||||
await waitTime(30);
|
||||
});
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
|
||||
it('should have correct lifecycle counts', async () => {
|
||||
await testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('forward', () => {
|
||||
|
||||
it('should go forward with ion-button[routerLink]', async () => {
|
||||
await element(by.css('#routerLink')).click();
|
||||
await testForward();
|
||||
});
|
||||
|
||||
it('should go forward with a[routerLink]', async () => {
|
||||
await element(by.css('#a')).click();
|
||||
await testForward();
|
||||
});
|
||||
|
||||
it('should go forward with button + navigateByUrl()', async () => {
|
||||
await element(by.css('#button')).click();
|
||||
await testForward();
|
||||
});
|
||||
|
||||
it('should go forward with button + navigateForward()', async () => {
|
||||
await element(by.css('#button-forward')).click();
|
||||
await testForward();
|
||||
});
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
|
||||
it('should go root with ion-button[routerLink][routerDirection=root]', async () => {
|
||||
await element(by.css('#routerLink-root')).click();
|
||||
await testRoot();
|
||||
});
|
||||
|
||||
it('should go root with a[routerLink][routerDirection=root]', async () => {
|
||||
await element(by.css('#a-root')).click();
|
||||
await testRoot();
|
||||
});
|
||||
|
||||
it('should go root with button + navigateRoot', async () => {
|
||||
await element(by.css('#button-root')).click();
|
||||
await testRoot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('back', () => {
|
||||
|
||||
it('should go back with ion-button[routerLink][routerDirection=back]', async () => {
|
||||
await element(by.css('#routerLink-back')).click();
|
||||
});
|
||||
|
||||
it('should go back with a[routerLink][routerDirection=back]', async () => {
|
||||
await element(by.css('#a-back')).click();
|
||||
await testBack();
|
||||
});
|
||||
|
||||
it('should go back with button + navigateBack', async () => {
|
||||
await element(by.css('#button-back')).click();
|
||||
await testBack();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function testForward() {
|
||||
await waitTime(2500);
|
||||
await testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
|
||||
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() {
|
||||
await waitTime(200);
|
||||
await testStack('ion-router-outlet', ['app-router-link-page']);
|
||||
await testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
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() {
|
||||
await waitTime(500);
|
||||
await testStack('ion-router-outlet', ['app-router-link-page']);
|
||||
await testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
describe('Router Link', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/router-link');
|
||||
});
|
||||
|
||||
describe('router-link params and fragments', () => {
|
||||
const queryParam = 'A&=#Y';
|
||||
const fragment = 'myDiv1';
|
||||
const id = 'MyPageID==';
|
||||
|
||||
it('should go to a page with properly encoded values', () => {
|
||||
cy.visit('/router-link?ionic:_testing=true');
|
||||
cy.get('#queryParamsFragment').click();
|
||||
|
||||
const expectedPath = `${encodeURIComponent(id)}`;
|
||||
const expectedSearch = `?token=${encodeURIComponent(queryParam)}`;
|
||||
const expectedHash = `#${encodeURIComponent(fragment)}`;
|
||||
|
||||
cy.location().should((location) => {
|
||||
expect(location.pathname).to.contain(expectedPath);
|
||||
expect(location.search).to.eq(expectedSearch);
|
||||
expect(location.hash).to.eq(expectedHash);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return to a page with preserved query param and fragment', () => {
|
||||
cy.visit('/router-link?ionic:_testing=true');
|
||||
cy.get('#queryParamsFragment').click();
|
||||
cy.get('#goToPage3').click();
|
||||
|
||||
cy.location().should((location) => {
|
||||
expect(location.pathname).to.contain('router-link-page3');
|
||||
});
|
||||
|
||||
cy.get('#goBackFromPage3').click();
|
||||
|
||||
const expectedPath = `${encodeURIComponent(id)}`;
|
||||
const expectedSearch = `?token=${encodeURIComponent(queryParam)}`;
|
||||
const expectedHash = `#${encodeURIComponent(fragment)}`;
|
||||
|
||||
cy.location().should((location) => {
|
||||
expect(location.pathname).to.contain(expectedPath);
|
||||
expect(location.search).to.eq(expectedSearch);
|
||||
expect(location.hash).to.eq(expectedHash);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve query param and fragment with defaultHref string', () => {
|
||||
cy.visit('/router-link-page3?ionic:_testing=true');
|
||||
|
||||
cy.get('#goBackFromPage3').click();
|
||||
|
||||
const expectedSearch = '?token=ABC';
|
||||
const expectedHash = '#fragment';
|
||||
|
||||
cy.location().should((location) => {
|
||||
expect(location.search).to.eq(expectedSearch);
|
||||
expect(location.hash).to.eq(expectedHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('router-link', () => {
|
||||
it('should have correct lifecycle counts', () => {
|
||||
cy.testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('forward', () => {
|
||||
it('should go forward with ion-button[routerLink]', () => {
|
||||
cy.get('#routerLink').click();
|
||||
testForward();
|
||||
});
|
||||
|
||||
it('should go forward with a[routerLink]', () => {
|
||||
cy.get('#a').click();
|
||||
testForward();
|
||||
});
|
||||
|
||||
it('should go forward with button + navigateByUrl()', () => {
|
||||
cy.get('#button').click();
|
||||
testForward();
|
||||
});
|
||||
|
||||
it('should go forward with button + navigateForward()', () => {
|
||||
cy.get('#button-forward').click();
|
||||
testForward();
|
||||
});
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should go root with ion-button[routerLink][routerDirection=root]', () => {
|
||||
cy.get('#routerLink-root').click();
|
||||
testRoot();
|
||||
});
|
||||
|
||||
it('should go root with a[routerLink][routerDirection=root]', () => {
|
||||
cy.get('#a-root').click();
|
||||
testRoot();
|
||||
});
|
||||
|
||||
it('should go root with button + navigateRoot', () => {
|
||||
cy.get('#button-root').click();
|
||||
testRoot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('back', () => {
|
||||
it('should go back with ion-button[routerLink][routerDirection=back]', () => {
|
||||
cy.get('#routerLink-back').click();
|
||||
});
|
||||
|
||||
it('should go back with a[routerLink][routerDirection=back]', () => {
|
||||
cy.get('#a-back').click();
|
||||
testBack();
|
||||
});
|
||||
|
||||
it('should go back with button + navigateBack', () => {
|
||||
cy.get('#button-back').click();
|
||||
testBack();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testForward() {
|
||||
cy.testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
|
||||
cy.testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
cy.get('app-router-link-page #canGoBack').should('have.text', 'true');
|
||||
|
||||
cy.go('back');
|
||||
cy.testStack('ion-router-outlet', ['app-router-link']);
|
||||
cy.testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 2,
|
||||
ionViewDidEnter: 2,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1,
|
||||
});
|
||||
}
|
||||
|
||||
function testRoot() {
|
||||
cy.wait(200);
|
||||
cy.testStack('ion-router-outlet', ['app-router-link-page']);
|
||||
cy.testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
cy.get('app-router-link-page #canGoBack').should('have.text', 'false');
|
||||
|
||||
cy.go('back');
|
||||
cy.wait(100);
|
||||
cy.testStack('ion-router-outlet', ['app-router-link']);
|
||||
cy.testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function testBack() {
|
||||
cy.wait(500);
|
||||
cy.testStack('ion-router-outlet', ['app-router-link-page']);
|
||||
cy.testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
cy.get('app-router-link-page #canGoBack').should('have.text', 'false');
|
||||
|
||||
cy.go('back');
|
||||
cy.wait(100);
|
||||
cy.testStack('ion-router-outlet', ['app-router-link']);
|
||||
cy.testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
describe('Routing', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/router-link?ionic:mode=ios');
|
||||
})
|
||||
|
||||
it('should swipe and abort', () => {
|
||||
cy.get('#routerLink').click();
|
||||
|
||||
cy.ionSwipeToGoBack();
|
||||
|
||||
cy.get('app-router-link').should('have.attr', 'aria-hidden').and('equal', 'true');
|
||||
cy.get('app-router-link').should('have.attr', 'class').and('equal', 'ion-page ion-page-hidden');
|
||||
|
||||
cy.get('app-router-link-page').should('not.have.attr', 'aria-hidden');
|
||||
cy.get('app-router-link-page').should('have.attr', 'class').and('equal', 'ion-page can-go-back');
|
||||
});
|
||||
|
||||
it('should swipe and go back', () => {
|
||||
cy.get('#routerLink').click();
|
||||
|
||||
cy.ionPageHidden('app-router-link');
|
||||
cy.ionPageVisible('app-router-link-page');
|
||||
|
||||
cy.testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
|
||||
|
||||
cy.ionSwipeToGoBack(true);
|
||||
|
||||
cy.ionPageVisible('app-router-link');
|
||||
cy.ionPageDoesNotExist('app-router-link-page');
|
||||
|
||||
cy.testStack('ion-router-outlet', ['app-router-link']);
|
||||
|
||||
cy.get('app-router-link').should('not.have.attr', 'aria-hidden');
|
||||
cy.get('app-router-link').should('have.attr', 'class').and('equal', 'ion-page');
|
||||
});
|
||||
})
|
||||
51
angular/test/test-app/e2e/src/slides.e2e-spec.ts
Normal file
51
angular/test/test-app/e2e/src/slides.e2e-spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { handleErrorMessages, waitTime } from './utils';
|
||||
|
||||
describe('slides', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get('/slides');
|
||||
await waitTime(30);
|
||||
});
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should change index on slide change', async () => {
|
||||
expect(await element.all(by.css('ion-slide')).count()).toEqual(0);
|
||||
await addSlides();
|
||||
expect(await element.all(by.css('ion-slide')).count()).toEqual(3);
|
||||
|
||||
await checkIndex('0');
|
||||
|
||||
await nextSlide();
|
||||
await checkIndex('1');
|
||||
|
||||
await nextSlide();
|
||||
await checkIndex('2');
|
||||
|
||||
await prevSlide();
|
||||
await checkIndex('1');
|
||||
});
|
||||
});
|
||||
|
||||
async function checkIndex(index: string) {
|
||||
expect(await element(by.css('#slide-index')).getText()).toEqual(index);
|
||||
expect(await element(by.css('#slide-index-2')).getText()).toEqual(index);
|
||||
}
|
||||
|
||||
async function addSlides() {
|
||||
await element(by.css('#add-slides')).click();
|
||||
await waitTime(800);
|
||||
}
|
||||
|
||||
|
||||
async function nextSlide() {
|
||||
await element(by.css('#btn-next')).click();
|
||||
await waitTime(800);
|
||||
}
|
||||
|
||||
async function prevSlide() {
|
||||
await element(by.css('#btn-prev')).click();
|
||||
await waitTime(800);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
describe('Slides', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/slides');
|
||||
cy.wait(30);
|
||||
})
|
||||
|
||||
it('should change index on slide change', () => {
|
||||
cy.get('ion-slide').should('have.length', 0);
|
||||
|
||||
cy.get('#add-slides').click();
|
||||
|
||||
cy.get('ion-slide').should('have.length', 3);
|
||||
|
||||
// Should be on the first slide
|
||||
checkIndex('0');
|
||||
|
||||
// Swipe to the second slide
|
||||
nextSlide();
|
||||
checkIndex('1');
|
||||
|
||||
// Swipe to the third slide
|
||||
nextSlide();
|
||||
checkIndex('2');
|
||||
|
||||
// Go back to the second slide
|
||||
prevSlide();
|
||||
checkIndex('1');
|
||||
});
|
||||
});
|
||||
|
||||
function checkIndex(index) {
|
||||
cy.get('#slide-index').should('have.text', index);
|
||||
cy.get('#slide-index-2').should('have.text', index);
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
cy.get('#btn-next').click();
|
||||
cy.wait(800);
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
cy.get('#btn-prev').click();
|
||||
cy.wait(800);
|
||||
}
|
||||
234
angular/test/test-app/e2e/src/tabs.e2e-spec.ts
Normal file
234
angular/test/test-app/e2e/src/tabs.e2e-spec.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { browser, element, by, ElementFinder } from 'protractor';
|
||||
import { waitTime, testStack, handleErrorMessages } from './utils';
|
||||
|
||||
describe('tabs', () => {
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
describe('entry url - /tabs', () => {
|
||||
beforeEach(async () => {
|
||||
await browser.get('/tabs');
|
||||
await waitTime(30);
|
||||
});
|
||||
|
||||
it('should redirect and load tab-account', async () => {
|
||||
await testTabTitle('Tab 1 - Page 1');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']);
|
||||
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 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);
|
||||
|
||||
await element(by.css('#tab-button-contact')).click();
|
||||
await testTabTitle('Tab 2 - Page 1');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
await testState(2, 'contact');
|
||||
|
||||
await element(by.css('#tab-button-account')).click();
|
||||
tab = await testTabTitle('Tab 1 - Page 2 (1)');
|
||||
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);
|
||||
|
||||
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']);
|
||||
await testState(3, 'account');
|
||||
});
|
||||
|
||||
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 testState(1, 'account');
|
||||
|
||||
await element(by.css('#tab-button-contact')).click();
|
||||
await testTabTitle('Tab 2 - Page 1');
|
||||
await testState(2, 'contact');
|
||||
|
||||
await element(by.css('#tab-button-account')).click();
|
||||
await testTabTitle('Tab 1 - Page 2 (1)');
|
||||
await testState(3, 'account');
|
||||
|
||||
await element(by.css('ion-back-button')).click();
|
||||
await testTabTitle('Tab 1 - Page 1');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
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');
|
||||
|
||||
await tab.$('#goto-tab1-page1').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 to nested', async () => {
|
||||
await element(by.css('#tab-button-contact')).click();
|
||||
const tab = await testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
await tab.$('#goto-tab1-page2').click();
|
||||
await testTabTitle('Tab 1 - Page 2 (1)');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
|
||||
});
|
||||
|
||||
it('should load lazy loaded tab', async () => {
|
||||
await element(by.css('#tab-button-lazy')).click();
|
||||
await testTabTitle('Tab 3 - Page 1');
|
||||
});
|
||||
|
||||
it('should use ion-back-button defaultHref', async () => {
|
||||
let tab = await getSelectedTab() as ElementFinder;
|
||||
await tab.$('#goto-tab3-page2').click();
|
||||
tab = await testTabTitle('Tab 3 - Page 2');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']);
|
||||
|
||||
await tab.$('ion-back-button').click();
|
||||
await testTabTitle('Tab 3 - Page 1');
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entry url - /tabs/account/nested/1', () => {
|
||||
beforeEach(async () => {
|
||||
await browser.get('/tabs/account/nested/1');
|
||||
await waitTime(30);
|
||||
});
|
||||
|
||||
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;
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
|
||||
|
||||
await element(by.css('#tab-button-account')).click();
|
||||
tab = await testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
await tab.$('#goto-tab1-page2').click();
|
||||
tab = await testTabTitle('Tab 1 - Page 2 (1)');
|
||||
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 () => {
|
||||
let tab = await testTabTitle('Tab 3 - Page 1') as ElementFinder;
|
||||
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
|
||||
|
||||
await tab.$('#goto-tab1-page2').click();
|
||||
tab = await testTabTitle('Tab 1 - Page 2 (1)');
|
||||
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 () => {
|
||||
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');
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
async function testState(count: number, tab: string) {
|
||||
expect(await element(by.css('#tabs-state')).getText()).toEqual(`${count}.${tab}`);
|
||||
}
|
||||
|
||||
async function testTabTitle(title: string) {
|
||||
await waitTime(1000);
|
||||
const tab = await getSelectedTab();
|
||||
expect(await tab.$('ion-title').getText()).toEqual(title);
|
||||
return tab;
|
||||
}
|
||||
|
||||
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);
|
||||
const tab = tabs.first();
|
||||
return tab;
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
describe('Tabs', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/tabs');
|
||||
})
|
||||
|
||||
describe('entry url - /tabs', () => {
|
||||
it('should redirect and load tab-account', () => {
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']);
|
||||
testState(1, 'account');
|
||||
});
|
||||
|
||||
it('should navigate between tabs and ionChange events should be dispatched', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
tab = testTabTitle('Tab 2 - Page 1');
|
||||
tab.find('.segment-changed').should('have.text', 'false');
|
||||
});
|
||||
|
||||
it('should simulate stack + double tab click', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']);
|
||||
testState(1, 'account');
|
||||
|
||||
// When you call find on tab above it changes the value of tab
|
||||
// so we need to redefine it
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should simulate stack + back button click', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(1, 'account');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
testState(2, 'contact');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testState(3, 'account');
|
||||
|
||||
cy.get('ion-back-button').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
testState(3, 'account');
|
||||
});
|
||||
|
||||
it('should navigate deep then go home', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
cy.get('#goto-next').click();
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1',
|
||||
'app-tabs-tab2'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch tabs and go back', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page1').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']);
|
||||
});
|
||||
|
||||
it('should switch tabs and go to nested', () => {
|
||||
cy.get('#tab-button-contact').click();
|
||||
const tab = testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
|
||||
});
|
||||
|
||||
it('should load lazy loaded tab', () => {
|
||||
cy.get('#tab-button-lazy').click();
|
||||
cy.ionPageVisible('app-tabs-tab3');
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
});
|
||||
|
||||
it('should use ion-back-button defaultHref', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab3-page2').click();
|
||||
testTabTitle('Tab 3 - Page 2');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']);
|
||||
});
|
||||
|
||||
it('should preserve navigation extras when switching tabs', () => {
|
||||
const expectUrlToContain = 'search=hello#fragment';
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectUrlToContain);
|
||||
});
|
||||
|
||||
it('should set root when clicking on an active tab to navigate to the root', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
cy.url().then(url => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlEquals(url);
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry tab contains navigation extras', () => {
|
||||
const expectNestedTabUrlToContain = 'search=hello#fragment';
|
||||
const rootUrlParams = 'test=123#rootFragment';
|
||||
const rootUrl = `/tabs/account?${rootUrlParams}`;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(rootUrl);
|
||||
})
|
||||
|
||||
it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1-with-query-params').click();
|
||||
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
testUrlContains(expectNestedTabUrlToContain);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
|
||||
it('should preserve root url navigation extras when changing tabs', () => {
|
||||
getSelectedTab();
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
|
||||
it('should navigate deep then go home and preserve navigation extras', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.get('#tab-button-contact').click();
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
testUrlContains(rootUrl);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/account/nested/1', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/tabs/account/nested/1');
|
||||
})
|
||||
|
||||
it('should only display the back-button when there is a page in the stack', () => {
|
||||
let tab = getSelectedTab();
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
testTabTitle('Tab 1 - Page 2 (1)');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
|
||||
cy.get('#tab-button-account').click();
|
||||
tab = testTabTitle('Tab 1 - Page 1');
|
||||
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('be.visible');
|
||||
});
|
||||
|
||||
it('should not reuse the same page', () => {
|
||||
let tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
|
||||
tab.find('#goto-next').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (3)');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', [
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested',
|
||||
'app-tabs-tab1-nested'
|
||||
]);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (2)');
|
||||
tab.find('ion-back-button').click();
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
|
||||
});
|
||||
})
|
||||
|
||||
describe('entry url - /tabs/lazy', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/tabs/lazy');
|
||||
});
|
||||
|
||||
it('should not display the back-button if coming from a different stack', () => {
|
||||
let tab = testTabTitle('Tab 3 - Page 1');
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
|
||||
|
||||
tab = getSelectedTab();
|
||||
tab.find('#goto-tab1-page2').click();
|
||||
cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']);
|
||||
|
||||
tab = testTabTitle('Tab 1 - Page 2 (1)');
|
||||
tab.find('ion-back-button').should('not.be.visible');
|
||||
});
|
||||
})
|
||||
|
||||
describe('enter url - /tabs/contact/one', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/tabs/contact/one');
|
||||
});
|
||||
|
||||
it('should return to correct tab after going to page in different outlet', () => {
|
||||
const tab = getSelectedTab();
|
||||
tab.find('#goto-nested-page1').click();
|
||||
cy.testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']);
|
||||
|
||||
const nestedOutlet = cy.get('app-nested-outlet');
|
||||
nestedOutlet.find('ion-back-button').click();
|
||||
|
||||
testTabTitle('Tab 2 - Page 1');
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function testTabTitle(title) {
|
||||
const tab = getSelectedTab();
|
||||
|
||||
// Find is used to get a direct descendant instead of get
|
||||
tab.find('ion-title').should('have.text', title);
|
||||
return getSelectedTab();
|
||||
}
|
||||
|
||||
function getSelectedTab() {
|
||||
cy.get('ion-tabs ion-router-outlet > *:not(.ion-page-hidden)').should('have.length', 1);
|
||||
return cy.get('ion-tabs ion-router-outlet > *:not(.ion-page-hidden)').first();
|
||||
}
|
||||
|
||||
function testState(count, tab) {
|
||||
cy.get('#tabs-state').should('have.text', `${count}.${tab}`);
|
||||
}
|
||||
|
||||
function testUrlContains(urlFragment) {
|
||||
cy.location().should((location) => {
|
||||
expect(location.href).to.contain(urlFragment);
|
||||
});
|
||||
}
|
||||
|
||||
function testUrlEquals(url) {
|
||||
cy.url().should('eq', url);
|
||||
}
|
||||
73
angular/test/test-app/e2e/src/utils.ts
Normal file
73
angular/test/test-app/e2e/src/utils.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { browser } from 'protractor';
|
||||
|
||||
export function goBack() {
|
||||
return browser.executeScript(`return window.history.back()`);
|
||||
}
|
||||
|
||||
export function getProperty(selector: string, property: string) {
|
||||
return browser.executeScript(`
|
||||
return document.querySelector('${selector}')['${property}'];
|
||||
`);
|
||||
}
|
||||
|
||||
export function getText(selector: string) {
|
||||
return browser.executeScript(`
|
||||
return document.querySelector('${selector}').textContent;
|
||||
`);
|
||||
}
|
||||
|
||||
export function setProperty(selector: string, property: string, value: any) {
|
||||
const text = JSON.stringify(value);
|
||||
return browser.executeScript(`
|
||||
document.querySelector('${selector}')['${property}'] = ${text};
|
||||
`);
|
||||
}
|
||||
|
||||
export function waitTime(time: number) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
export interface LifeCycleCount {
|
||||
ionViewWillEnter: number;
|
||||
ionViewDidEnter: number;
|
||||
ionViewWillLeave: number;
|
||||
ionViewDidLeave: number;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
export async function testStack(selector: string, expected: string[]) {
|
||||
const children = await browser.executeScript(`
|
||||
return Array.from(
|
||||
document.querySelector('${selector}').children
|
||||
).map(el => el.tagName.toLowerCase());
|
||||
`);
|
||||
expect(children).toEqual(expected);
|
||||
}
|
||||
22
angular/test/test-app/e2e/src/view-child.e2e-spec.ts
Normal file
22
angular/test/test-app/e2e/src/view-child.e2e-spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { handleErrorMessages, waitTime } from './utils';
|
||||
|
||||
describe('view-child', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get('/view-child');
|
||||
await waitTime(30);
|
||||
});
|
||||
afterEach(() => {
|
||||
return handleErrorMessages();
|
||||
});
|
||||
|
||||
it('should get a reference to all children', async () => {
|
||||
// button should be red
|
||||
expect(await element(by.css('#color-button.ion-color-danger')).isPresent()).toBeTruthy();
|
||||
|
||||
// tabs should be found
|
||||
expect(await element(by.css('#tabs-result')).getText()).toEqual('all found');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
describe('View Child', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/view-child');
|
||||
})
|
||||
|
||||
it('should get a reference to all children', () => {
|
||||
// button should be red
|
||||
cy.get('#color-button').should('have.class', 'ion-color-danger');
|
||||
|
||||
// tabs should be found
|
||||
cy.get('#tabs-result').should('have.text', 'all found');
|
||||
});
|
||||
});
|
||||
|
||||
18
angular/test/test-app/e2e/src/virtual-scroll.e2e-spec.ts
Normal file
18
angular/test/test-app/e2e/src/virtual-scroll.e2e-spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
describe('Virtual Scroll', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/virtual-scroll');
|
||||
cy.wait(30);
|
||||
})
|
||||
|
||||
it('should open virtual-scroll', () => {
|
||||
cy.document().then((doc) => {
|
||||
const virtualElements = doc.querySelectorAll('ion-virtual-scroll > *');
|
||||
expect(virtualElements.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,17 +4,14 @@
|
||||
"strictMetadataEmit" : true
|
||||
},
|
||||
"extends": "../tsconfig.json",
|
||||
"include": [
|
||||
"src/**spec.ts",
|
||||
"../cypress/support/index.d.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"cypress",
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
29
angular/test/test-app/karma.conf.js
Normal file
29
angular/test/test-app/karma.conf.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../coverage'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
38148
angular/test/test-app/package-lock.json
generated
38148
angular/test/test-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,53 +8,53 @@
|
||||
"sync:build": "sh scripts/build-ionic.sh",
|
||||
"sync": "sh scripts/sync.sh",
|
||||
"build": "npm run sync && 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",
|
||||
"cy.open": "cypress open",
|
||||
"cy.run": "cypress run",
|
||||
"test": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
|
||||
"test.watch": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.open\" --kill-others --success first"
|
||||
"postinstall": "npm run sync",
|
||||
"compile:server": "webpack --config webpack.server.config.js --progress --colors",
|
||||
"serve:ssr": "node dist/server",
|
||||
"build:ssr": "npm run build:client-and-server-bundles && npm run compile:server",
|
||||
"build:client-and-server-bundles": "npm run build && ng run test-app:server:production --bundleDependencies all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^11.2.11",
|
||||
"@angular/common": "^11.2.11",
|
||||
"@angular/compiler": "^11.2.11",
|
||||
"@angular/core": "^11.2.11",
|
||||
"@angular/forms": "^11.2.11",
|
||||
"@angular/platform-browser": "^11.2.11",
|
||||
"@angular/platform-browser-dynamic": "^11.2.11",
|
||||
"@angular/platform-server": "^11.2.11",
|
||||
"@angular/router": "^11.2.11",
|
||||
"@ionic/angular": "^5.3.1",
|
||||
"@ionic/angular-server": "^5.3.1",
|
||||
"@nguniversal/express-engine": "^11.2.1",
|
||||
"angular-in-memory-web-api": "^0.11.0",
|
||||
"core-js": "^2.6.11",
|
||||
"@angular/animations": "^8.2.8",
|
||||
"@angular/common": "^8.2.8",
|
||||
"@angular/compiler": "^8.2.8",
|
||||
"@angular/core": "^8.2.8",
|
||||
"@angular/forms": "^8.2.8",
|
||||
"@angular/platform-browser": "^8.2.8",
|
||||
"@angular/platform-browser-dynamic": "^8.2.8",
|
||||
"@angular/platform-server": "^8.2.8",
|
||||
"@angular/router": "^8.2.8",
|
||||
"@ionic/angular": "^4.7.0",
|
||||
"@ionic/angular-server": "^0.0.2",
|
||||
"@nguniversal/express-engine": "~8.1.1",
|
||||
"@nguniversal/module-map-ngfactory-loader": "~8.1.1",
|
||||
"core-js": "^2.6.2",
|
||||
"express": "^4.15.2",
|
||||
"rxjs": "^6.5.5",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "^0.10.3"
|
||||
"rxjs": "^6.5.3",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1102.10",
|
||||
"@angular/cli": "^11.2.10",
|
||||
"@angular/compiler-cli": "^11.2.11",
|
||||
"@angular/language-service": "^11.2.11",
|
||||
"@nguniversal/builders": "^11.2.1",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/node": "^12.12.54",
|
||||
"codelyzer": "^6.0.1",
|
||||
"concurrently": "^6.0.0",
|
||||
"cypress": "^6.7.1",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ts-node": "^8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "^4.0.7",
|
||||
"wait-on": "^5.2.1",
|
||||
"webpack-cli": "^3.3.12"
|
||||
"@angular-devkit/build-angular": "^0.803.6",
|
||||
"@angular/cli": "^8.3.6",
|
||||
"@angular/compiler-cli": "^8.2.8",
|
||||
"@angular/language-service": "^8.2.8",
|
||||
"@types/jasmine": "3.4.1",
|
||||
"@types/node": "12.7.8",
|
||||
"codelyzer": "^5.1.2",
|
||||
"jasmine-core": "3.5.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"karma": "4.3.0",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-jasmine": "2.0.1",
|
||||
"protractor": "5.4.2",
|
||||
"ts-loader": "^6.1.2",
|
||||
"ts-node": "8.4.1",
|
||||
"tslint": "~5.18.0",
|
||||
"typescript": "~3.5.3",
|
||||
"webpack-cli": "^3.3.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# Copy angular dist
|
||||
rm -rf node_modules/@ionic/angular
|
||||
cp -a ../../dist node_modules/@ionic/angular
|
||||
mkdir node_modules/@ionic/angular
|
||||
cp -a ../../dist node_modules/@ionic/angular/dist
|
||||
cp -a ../../css node_modules/@ionic/angular/css
|
||||
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
|
||||
mkdir node_modules/@ionic/angular-server
|
||||
cp -a ../../../packages/angular-server/dist node_modules/@ionic/angular-server/dist
|
||||
cp -a ../../../packages/angular-server/package.json node_modules/@ionic/angular-server/package.json
|
||||
|
||||
# # Copy core dist
|
||||
rm -rf node_modules/@ionic/core
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
/**
|
||||
* *** NOTE ON IMPORTING FROM ANGULAR AND NGUNIVERSAL IN THIS FILE ***
|
||||
*
|
||||
* If your application uses third-party dependencies, you'll need to
|
||||
* either use Webpack or the Angular CLI's `bundleDependencies` feature
|
||||
* in order to adequately package them for use on the server without a
|
||||
* node_modules directory.
|
||||
*
|
||||
* However, due to the nature of the CLI's `bundleDependencies`, importing
|
||||
* Angular in this file will create a different instance of Angular than
|
||||
* the version in the compiled application code. This leads to unavoidable
|
||||
* conflicts. Therefore, please do not explicitly import from @angular or
|
||||
* @nguniversal in this file. You can export any needed resources
|
||||
* from your application's main.server.ts file, as seen below with the
|
||||
* import for `ngExpressEngine`.
|
||||
*/
|
||||
|
||||
import 'zone.js/dist/zone-node';
|
||||
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
import * as express from 'express';
|
||||
import { join } from 'path';
|
||||
import {join} from 'path';
|
||||
|
||||
import { AppServerModule } from './src/main.server';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
// Express server
|
||||
const app = express();
|
||||
|
||||
// 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');
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
|
||||
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||
server.engine('html', ngExpressEngine({
|
||||
bootstrap: AppServerModule,
|
||||
}));
|
||||
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
|
||||
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
|
||||
|
||||
server.set('view engine', 'html');
|
||||
server.set('views', distFolder);
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||
app.engine('html', ngExpressEngine({
|
||||
bootstrap: AppServerModuleNgFactory,
|
||||
providers: [
|
||||
provideModuleMap(LAZY_MODULE_MAP)
|
||||
]
|
||||
}));
|
||||
|
||||
// Example Express Rest API endpoints
|
||||
// app.get('/api/**', (req, res) => { });
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(distFolder, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
app.set('view engine', 'html');
|
||||
app.set('views', DIST_FOLDER);
|
||||
|
||||
// All regular routes use the Universal engine
|
||||
server.get('*', (req, res) => {
|
||||
res.render('index', { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
|
||||
});
|
||||
// Example Express Rest API endpoints
|
||||
// app.get('/api/**', (req, res) => { });
|
||||
// Serve static files from /browser
|
||||
app.get('*.*', express.static(DIST_FOLDER, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
|
||||
return server;
|
||||
}
|
||||
// All regular routes use the Universal engine
|
||||
app.get('*', (req, res) => {
|
||||
res.render('index', { req });
|
||||
});
|
||||
|
||||
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';
|
||||
// Start up the Node server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Node Express server listening on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons>
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
Alert test
|
||||
</ion-title>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
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';
|
||||
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
ServerModule,
|
||||
ModuleMapLoaderModule,
|
||||
IonicServerModule
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
|
||||
@@ -35,11 +35,9 @@
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Input (required)</ion-label>
|
||||
<ion-input formControlName="input" class="required" id="touched-input-test"></ion-input>
|
||||
<ion-input formControlName="input" class="required"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-button id="input-touched" (click)="setTouched()">Set Input Touched</ion-button>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Input</ion-label>
|
||||
<ion-input formControlName="input2"></ion-input>
|
||||
|
||||
@@ -25,11 +25,6 @@ export class FormComponent {
|
||||
});
|
||||
}
|
||||
|
||||
setTouched() {
|
||||
const formControl = this.profileForm.get('input');
|
||||
formControl.markAsTouched();
|
||||
}
|
||||
|
||||
onSubmit(_ev) {
|
||||
this.submitted = 'true';
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user