Compare commits

...

90 Commits

Author SHA1 Message Date
Liam DeBeasi
926b119231 remove old lines 2019-11-18 15:03:42 -05:00
Liam DeBeasi
256c7de469 only fire pressUp if press was fired; allow overriding of other gesture params 2019-11-18 15:02:38 -05:00
Liam DeBeasi
b7b5b94cce add angular integration 2019-11-12 11:39:21 -05:00
Liam DeBeasi
5e1e883585 sync with master 2019-11-12 11:33:42 -05:00
Liam DeBeasi
21484f1f3a fix(gesture): release gesture when disabling (#19855)
fixes #19848
2019-11-12 10:45:06 -05:00
Tawfiek Khalaf
b28cf02ef3 feat(alert): add support for textarea (#16851)
resolves #14153
2019-11-11 14:27:45 -05:00
Liam DeBeasi
bef0f53d0d feat(select): add --placeholder-opacity and --placeholder-color, expose shadow parts (#19893)
resolves #17446
2019-11-11 12:49:10 -05:00
Brandy Carney
1a8b7a4559 fix(card): update background to use the same as item (#19602)
uses item background but falls back to the background of the content
2019-11-08 12:55:19 -05:00
Brandy Carney
b23c759456 docs(slides): document css vars (#19872) 2019-11-08 12:37:00 -05:00
Antoine
1ab7066aa0 feat(searchbar): add --box-shadow variable to style searchbar input (#19838)
* Updates searchbar.md.scss to expose box-shadow

We would like to style the box-shadow property in searchbar and give the parent the ability to control it.

* fix(searchbar): add box shadow to main input for both modes
2019-11-08 10:53:42 -05:00
Liam DeBeasi
48a766246d feat(angular): expose Ionic Gestures via GestureController (#19864)
* expose gestures thru angular

* run linter
2019-11-08 10:35:52 -05:00
Brandy Carney
fce3e24600 fix(content): set fixed content to position absolute (#19867)
Adds absolute position to fixed slot in order to display text that is inside of the slot.

Fixes #17754

Co-authored-by: Matthew de Nobrega matthew@caro.health
2019-11-08 10:31:33 -05:00
Masahiko Sakakibara
be0ab5c2ff chore(menu): change deprecated error message (#19717) 2019-11-07 18:58:32 -05:00
Simon
c5be8831a2 docs(searchbar): update vue usage to kebab-case for the attributes (#19625) 2019-11-07 18:55:47 -05:00
Ely Lucas
edf64ef121 merge release-4.11.4 2019-11-07 15:54:48 -07:00
Himujjal Upadhyaya
504051d709 fix(nav-params): set generic type on navigation parameters get() (#19195) 2019-11-07 17:45:31 -05:00
Dylan Ballandras
90be7ec4b6 docs(toggle): update prop name for toggle OnChange in react (#19344)
closes #19343
2019-11-07 17:36:37 -05:00
Ely Lucas
1af1e2a14f chore(core): fixing test from bad rebase 2019-11-07 15:32:39 -07:00
Ely Lucas
2cc4244c05 4.11.4 2019-11-07 15:26:04 -07:00
Aubrey Holland
74e40cdc35 fix(react): check for component unmount, fixes #19859 2019-11-07 15:24:52 -07:00
Ely Lucas
13b323f25d chore(react): lowering the timeout for the ionpage check to avoid false positives 2019-11-07 15:24:52 -07:00
Ely Lucas
3e14a57f84 fix(react): adding multiple subscriptions to lifecycle events, fixes #19792 (#19858) 2019-11-07 15:24:52 -07:00
Ely Lucas
9864c17c1c fix(react): add check to warn if no ionpage is found, fixes #19832 (#19857) 2019-11-07 15:24:52 -07:00
Ely Lucas
e2ed0e9e87 fix(react): expand the location stack to better support back button, fixes #19748 (#19856) 2019-11-07 15:24:52 -07:00
Ely Lucas
a4b2de5730 fix(react): adding hardware back button support, fixes(19819) (#19851) 2019-11-07 15:24:52 -07:00
Ely Lucas
f3ac682010 fix(react): adding swipe back functionality and routerOutlet ready improvements, fixes #19818 (#19849) 2019-11-07 15:24:52 -07:00
Ely Lucas
2f5d823748 fix(react): create a new overlay each time component is presented, fixes #19841, #19823 (#19842) 2019-11-07 15:24:52 -07:00
Liam DeBeasi
e5e9dd5dfe fix(menu): clamp out of bounds swipe value (#19684)
fixes #18927
2019-11-07 15:24:09 -07:00
Liam DeBeasi
ad747b05f1 add press up handler 2019-11-07 15:20:49 -05:00
Liam DeBeasi
3b52074a10 fix typo 2019-11-07 15:17:26 -05:00
Liam DeBeasi
9eac7266a0 Fix 2019-11-07 15:04:18 -05:00
Liam DeBeasi
12a4c974b4 add press recognizer 2019-11-07 15:03:05 -05:00
Liam DeBeasi
a3666ddf0c fix(header): avoid flicker on ios when collapsing (#19850)
fixes #19839
2019-11-07 09:51:03 -05:00
Liam DeBeasi
cace1b357e fix(ios): only animate large title if back button is in start slot (#19846)
fixes #19840
2019-11-06 11:14:09 -05:00
Mike Hartington
ab9d3b2696 chore(): update stencil (#19734)
* chore(): update stencil

* bump deps

* chore(): bump jest
2019-11-01 10:06:23 -07:00
Mike Hartington
60aa027903 chore(): fix tabs (#19821)
* fix(tabs): do not wait on child tab setActive() lazy loading

* whenDefined example

* chore(): fix lint
2019-11-01 09:29:30 -07:00
Liam DeBeasi
7bd4412889 fix(animation): track correctly when updating CSS Animation (#19813)
* fix bug

* update menu

* fix
2019-10-31 12:50:54 -04:00
Liam DeBeasi
96a5e600e5 feat(animation): cubic-bezier easing conversion utility (experimental) (#19788)
resolves #19789
2019-10-31 10:16:33 -04:00
Ely Lucas
33cf08bf0e chore(react): removing beta verbiage from readme (#19803) 2019-10-30 17:10:00 -06:00
Ely Lucas
7e6e81db55 chore(build) setting max workers in core spec and e2e tests to 2 (#19805) 2019-10-30 16:47:49 -06:00
Ray Clanan
f2d18a2346 chore(react): update readme to remove capacitor instruction 2019-10-30 15:51:37 -06:00
Ely Lucas
77d18c7172 release-4.11.3 2019-10-30 13:31:36 -06:00
Ely Lucas
ec43b8c9b8 chore(changelog): fixing changelog items 2019-10-30 13:18:05 -06:00
Ely Lucas
b4938db5f9 4.11.3 2019-10-30 13:18:04 -06:00
Ely Lucas
9c7beff910 chore(build): setting max workers to 2 in core tests 2019-10-30 13:16:15 -06:00
Ely Lucas
16815b3cc8 chore(build): awaiting on test task 2019-10-30 13:16:15 -06:00
Ely Lucas
e23d91c6e7 fix(react): checking if node is actually an element before treating it like one, fixes #19769 (#19783) 2019-10-30 13:16:15 -06:00
Ely Lucas
1f2b161ccd fix(react): unmount leaving view when using browser back button, fixes #19749 (#19781) 2019-10-30 13:16:15 -06:00
Ely Lucas
67737bbb54 fix(react): checking isOpen again after async call before opening overlay, fixes #19755 2019-10-30 13:16:15 -06:00
Ely Lucas
29f8178794 fix(react): don't remove current view, provide a better method to determine showGoBack fixes #19731 and #19732 2019-10-30 13:16:15 -06:00
Liam DeBeasi
d80f45516d feat(split-pane): convert to shadow component, add width, max-width, and min-width vars (#19754)
resolves #17088


Co-authored-by: troyanskiy <roman.rosluk@utopix.ch>
2019-10-30 14:16:39 -04:00
Liam DeBeasi
420aa66392 fix(toast): call button handler on cancel (#19793)
fixes #19791
2019-10-30 14:02:18 -04:00
Brandy Carney
7988720b1c fix(picker): pass data and role to the dismiss based on button click or backdrop (#19787)
- Pass the button role on dismiss of the picker (on button click or backdrop tap)
- Pass the selected values in the data on dismiss ONLY if the dismiss role is not "cancel" or "backdrop"
- Call the cancel handler when dismissing if the role is "cancel" or "backdrop"

Fixes #18454
2019-10-30 13:25:11 -04:00
Liam DeBeasi
67a7e232b9 feat(angular): Expose Ionic Animations via the AnimationController (#19745)
* Add animation controller for angular

* remove empty constructor

* sync with master
2019-10-29 16:14:26 -04:00
Liam DeBeasi
7d417154c5 feat(animation): Animation Identifiers (#19771)
resolves #19550
2019-10-29 15:44:41 -04:00
Liam DeBeasi
b642b532e8 fix(header): translucent toolbars now work with collapsible headers (#19774)
fixes #19773
2019-10-29 15:43:26 -04:00
Liam DeBeasi
0a7aae28a7 fix(header): avoid flicker on collapsible header load (#19682)
* fix flicker on collapse load

* remove old test
2019-10-29 14:36:26 -04:00
Liam DeBeasi
39fb8f6720 fix(menu): clicking backdrop closes menu properly (#19785) 2019-10-29 13:26:15 -04:00
Brandy Carney
f63d37a4c5 fix(textarea): remove padding from textarea placeholder (#19694)
fixes #19616
2019-10-22 11:01:15 -04:00
Liam DeBeasi
9568f228b2 merge release-4.11.2
Sync 4.11.2
2019-10-21 17:54:25 -04:00
Liam DeBeasi
a2e1c55911 4.11.2 2019-10-21 17:36:16 -04:00
Ely Lucas
834666c754 fix(react): adding change events to iontabs, fixes #19665 (#19711) 2019-10-21 17:32:37 -04:00
Ely Lucas
5acb58a03d fix(react): removing pages from DOM on nav, fixes #19701 (#19712) 2019-10-21 17:32:32 -04:00
Ely Lucas
ce7314db0b chore(react): removing console log and tree shaking change 2019-10-21 17:32:28 -04:00
Ely Lucas
71fdcbc1b4 fix(react): adding HashRouter to available ion routers, fixes #19621 (#19683) 2019-10-21 17:32:22 -04:00
Brandy Carney
4ed525dbac chore(github): add weekly digest 2019-10-18 12:07:02 -04:00
Liam DeBeasi
e6e01381eb fix(ios): hide leaving view after nav transition to avoid flicker (#19691)
fixes #19674
2019-10-18 10:08:42 -04:00
Liam DeBeasi
e3233000c6 fix(menu): clamp out of bounds swipe value (#19684)
fixes #18927
2019-10-18 08:56:53 -04:00
Liam DeBeasi
f0e4215f41 docs(header): add collapse header styling details (#19669)
* update large title styles

* add missing space
2019-10-17 14:19:33 -04:00
Liam DeBeasi
df1bc1e627 refactor(gesture): update Gesture API to latest (#19671) 2019-10-17 14:18:39 -04:00
Liam DeBeasi
f69f9e4d54 fix(header): collapsible header works in tabs (#19658)
fixes #19640
2019-10-17 12:59:08 -04:00
Michael Asimakopoulos
ed6f3b9f3f fix(searchbar): use back button config value (#19353)
fixes #19347
2019-10-16 11:41:25 -04:00
Brandy Carney
2981eaa501 docs(contributing): add an example of multi-line breaking changes 2019-10-15 13:14:48 -04:00
Brandy Carney
c1bff121d9 docs(contributing): update commit message guidelines 2019-10-15 12:21:44 -04:00
Brandy Carney
7fc93b61cd merge release-5.0.0-beta.0
release 5.0.0-beta.0
2019-10-15 11:44:54 -04:00
Brandy Carney
cbf30ea9b7 5.0.0-beta.0 2019-10-15 11:29:02 -04:00
Brandy Carney
43ecf6ce5f chore(release): update target branch to current branch for release tag 2019-10-14 20:07:55 -04:00
Brandy Carney
ce0bed4cf3 chore(release): update release script to move the npm link 2019-10-14 20:07:55 -04:00
Brandy Carney
b3e035085c chore: increase timeout 2019-10-14 20:07:55 -04:00
Brandy Carney
94c13c2994 chore(scripts): update release script to include tag for rc
- adds support for changing the tag from `latest` to `next`
- checks the user's confirmation on the prepare and tag to end the script if the user selects no
- adds support for passing --dry-run to see what the publish will be
2019-10-14 20:07:55 -04:00
Liam DeBeasi
2f882373bf fix(animations): ensure all elements are cleaned up when calling .destroy() (#19654)
* fix race condition

* remove zero
2019-10-14 14:18:29 -04:00
Ely Lucas
91008ac9a4 merge release-4.11.1 2019-10-14 11:36:08 -06:00
Ely Lucas
53010057bb 4.11.1 2019-10-14 11:14:53 -06:00
Ely Lucas
48575f8163 chore(): bumping timeout 2019-10-14 11:14:53 -06:00
Ely Lucas
28d21ae55f chore(react): fixing test and another method to tab nav 2019-10-14 11:13:20 -06:00
Ely Lucas
8935d4d1b6 fix(react): handle tab back nav better, fixes #19646 (#19647) 2019-10-14 11:13:20 -06:00
Ely Lucas
3ba9e71a1f fix(react): add IonPicker as controller component, fixes #19620 (#19643) 2019-10-14 11:13:20 -06:00
Ely Lucas
889f282450 fix(build): properly update peer dependencies (#19639) 2019-10-14 11:13:20 -06:00
Ely Lucas
b5f077b60e fix(react): moving tslint and friends to devDependencies (#19624) 2019-10-14 11:13:20 -06:00
Brandy Carney
1cb97504d9 chore(release): npm link react package for the release script (#19657) 2019-10-14 12:54:30 -04:00
121 changed files with 1868 additions and 637 deletions

View File

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

2
.github/PROCESS.md vendored
View File

@@ -240,6 +240,8 @@ Hotfixes bypass `master` and should only be used for urgent fixes that can't wai
- For major or minor releases, ensure that the version number has an associated title (for example: `4.5.0 Boron`)
- Commit these changes with the version number as the message, e.g. `git commit -m "4.5.0"`
1. *(Optional)* Run `npm run release -- --dry-run` to run the release without publishing and verify the version.
1. Run `npm run release`
1. Click **Merge pull request**. Use the dropdown to select this option if necessary.

7
.github/weekly-digest.yml vendored Normal file
View 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

View File

@@ -1,6 +1,7 @@
const fs = require('fs-extra');
const path = require('path');
const execa = require('execa');
const inquirer = require('inquirer');
const Listr = require('listr');
const semver = require('semver');
const tc = require('turbocolor');
@@ -34,6 +35,34 @@ function projectPath(project) {
return path.join(rootDir, project);
}
async function askTag() {
const prompts = [
{
type: 'list',
name: 'tag',
message: 'Select npm tag or specify a new tag',
choices: ['latest', 'next']
.concat([
new inquirer.Separator(),
{
name: 'Other (specify)',
value: null
}
])
},
{
type: 'confirm',
name: 'confirm',
message: answers => {
return `Will publish to ${tc.cyan(answers.tag)}. Continue?`;
}
}
];
const { tag, confirm } = await inquirer.prompt(prompts);
return { tag, confirm };
}
function checkGit(tasks) {
tasks.push(
{
@@ -127,16 +156,9 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: lint`,
task: () => execa('npm', ['run', 'lint'], { cwd: projectRoot })
});
projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => {
updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg);
}
});
projectTasks.push({
title: `${pkg.name}: test`,
task: () => execa('npm', ['test'], { cwd: projectRoot })
task: async () => await execa('npm', ['test'], { cwd: projectRoot })
});
}
@@ -144,12 +166,22 @@ function preparePackage(tasks, package, version, install) {
title: `${pkg.name}: build`,
task: () => execa('npm', ['run', 'build'], { cwd: projectRoot })
});
if (package === 'core') {
if (package === 'core' || package === 'packages/react') {
projectTasks.push({
title: `${pkg.name}: npm link`,
task: () => execa('npm', ['link'], { cwd: projectRoot })
});
}
if (version) {
projectTasks.push({
title: `${pkg.name}: update ionic/core dep to ${version}`,
task: () => {
updateDependency(pkg, '@ionic/core', version);
writePkg(package, pkg);
}
});
}
}
// Add project tasks
@@ -186,7 +218,7 @@ function prepareDevPackage(tasks, package, version) {
task: () => execa('npm', ['run', 'build'], { cwd: projectRoot })
});
if (package === 'core') {
if (package === 'core' || package === 'packages/react') {
projectTasks.push({
title: `${pkg.name}: npm link`,
task: () => execa('npm', ['link'], { cwd: projectRoot })
@@ -278,6 +310,9 @@ function updateDependency(pkg, dependency, version) {
if (pkg.devDependencies && pkg.devDependencies[dependency]) {
pkg.devDependencies[dependency] = version;
}
if (pkg.peerDependencies && pkg.peerDependencies[dependency]) {
pkg.peerDependencies[dependency] = version;
}
}
function isVersionGreater(oldVersion, newVersion) {
@@ -297,6 +332,7 @@ function copyCDNLoader(tasks, version) {
module.exports = {
checkTestDist,
checkGit,
askTag,
isValidVersion,
isVersionGreater,
copyCDNLoader,

View File

@@ -17,9 +17,13 @@ async function main() {
throw new Error('env.GH_TOKEN is undefined');
}
const version = await askVersion();
const { version, confirm } = await askVersion();
const install = process.argv.indexOf('--no-install') < 0;
if (!confirm) {
return;
}
// compile and verify packages
await preparePackages(common.packages, version, install);
@@ -85,8 +89,8 @@ async function askVersion() {
}
];
const {version} = await inquirer.prompt(prompts);
return version;
const { version, confirm } = await inquirer.prompt(prompts);
return { version, confirm };
}

View File

@@ -13,6 +13,8 @@ const fs = require('fs-extra');
async function main() {
try {
const dryRun = process.argv.indexOf('--dry-run') > -1;
if (!process.env.GH_TOKEN) {
throw new Error('env.GH_TOKEN is undefined');
}
@@ -26,15 +28,31 @@ async function main() {
// repo must be clean
common.checkGit(tasks);
// publish each package in NPM
common.publishPackages(tasks, common.packages, version);
const { tag, confirm } = await common.askTag();
// push tag to git remote
publishGit(tasks, version, changelog);
if (!confirm) {
return;
}
if(!dryRun) {
// publish each package in NPM
common.publishPackages(tasks, common.packages, version, tag);
// push tag to git remote
publishGit(tasks, version, changelog);
}
const listr = new Listr(tasks);
await listr.run();
console.log(`\nionic ${version} published!! 🎉\n`);
// Dry run doesn't publish to npm or git
if (dryRun) {
console.log(`
\n${tc.yellow('Did not publish. Remove the "--dry-run" flag to publish:')}\n${tc.green(version)} to ${tc.cyan(tag)}\n
`);
} else {
console.log(`\nionic ${version} published to ${tag}!! 🎉\n`);
}
} catch (err) {
console.log('\n', tc.red(err), '\n');
@@ -104,10 +122,16 @@ async function publishGithub(version, tag, changelog) {
token: process.env.GH_TOKEN
});
let branch = await execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']);
if (!branch) {
branch = 'master';
}
await octokit.repos.createRelease({
owner: 'ionic-team',
repo: 'ionic',
target_commitish: 'master',
target_commitish: branch,
tag_name: tag,
name: version,
body: changelog,

View File

@@ -1,6 +1,167 @@
## [4.11.4](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.4) (2019-11-07)
### Bug Fixes
* **react:** check for component unmount, fixes [#19859](https://github.com/ionic-team/ionic/issues/19859) ([7356c40](https://github.com/ionic-team/ionic/commit/7356c401742ce2b3241d6ab05fce0fa65d2f1f8a))
* **react:** adding multiple subscriptions to lifecycle events, fixes [#19792](https://github.com/ionic-team/ionic/issues/19792) ([#19858](https://github.com/ionic-team/ionic/issues/19858)) ([0a3014d](https://github.com/ionic-team/ionic/commit/0a3014d35e2102570fd3d8c5ada29eb01aab18e9))
* **react:** add check to warn if no ionpage is found, fixes [#19832](https://github.com/ionic-team/ionic/issues/19832) ([#19857](https://github.com/ionic-team/ionic/issues/19857)) ([628e766](https://github.com/ionic-team/ionic/commit/628e76668ea72baebdb02b9dcfe24c0da837fb08))
* **react:** expand the location stack to better support back button, fixes [#19748](https://github.com/ionic-team/ionic/issues/19748) ([#19856](https://github.com/ionic-team/ionic/issues/19856)) ([d89508b](https://github.com/ionic-team/ionic/commit/d89508b1b58481d518b89362a8792d05f3f451c9))
* **react:** adding hardware back button support, fixes(19819) ([#19851](https://github.com/ionic-team/ionic/issues/19851)) ([fd9745d](https://github.com/ionic-team/ionic/commit/fd9745ddcddded76d64220838aef0f599bf4352f))
* **react:** adding swipe back functionality and routerOutlet ready improvements, fixes [#19818](https://github.com/ionic-team/ionic/issues/19818) ([#19849](https://github.com/ionic-team/ionic/issues/19849)) ([bcc40c8](https://github.com/ionic-team/ionic/commit/bcc40c8d59b723bbdb1dfd318bfb2219eb8df3cf))
* **react:** create a new overlay each time component is presented, fixes [#19841](https://github.com/ionic-team/ionic/issues/19841), [#19823](https://github.com/ionic-team/ionic/issues/19823) ([#19842](https://github.com/ionic-team/ionic/issues/19842)) ([9fad416](https://github.com/ionic-team/ionic/commit/9fad4161be4859969e14d4d33169ef022052d6bf))
## [4.11.3](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.3) (2019-10-30)
### Bug Fixes
* **react:** adding change events to iontabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24e5053a379156e6c3d82c2b5d3afa999f1))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3ce98747d3b37107904ca110daad95e48bc))
* **react:** checking if node is actually an element before treating it like one, fixes [#19769](https://github.com/ionic-team/ionic/issues/19769) ([#19783](https://github.com/ionic-team/ionic/issues/19783)) ([9d0caf6](https://github.com/ionic-team/ionic/commit/9d0caf6de070145c4af618847b27e24c49027b8e))
* **react:** checking isOpen again after async call before opening overlay, fixes [#19755](https://github.com/ionic-team/ionic/issues/19755) ([f70e71a](https://github.com/ionic-team/ionic/commit/f70e71a3d461cdab65626a5a7e1b6f4d03b852b1))
* **react:** don't remove current view, provide a better method to determine showGoBack fixes [#19731](https://github.com/ionic-team/ionic/issues/19731) and [#19732](https://github.com/ionic-team/ionic/issues/19732) ([31c754d](https://github.com/ionic-team/ionic/commit/31c754dab7ada494ff5f0026d5cf3f7f65198eff))
* **react:** removing pages from DOM on nav, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3ae43d8c6b076387a58bca655a56c920bcd))
* **react:** unmount leaving view when using browser back button, fixes [#19749](https://github.com/ionic-team/ionic/issues/19749) ([#19781](https://github.com/ionic-team/ionic/issues/19781)) ([2dc5540](https://github.com/ionic-team/ionic/commit/2dc554091056612f1bcd2751d6eeb41cae488751))
## [4.11.2](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.2) (2019-10-21)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([d97e167](https://github.com/ionic-team/ionic/commit/d97e167))
* **header:** collapsible header works in tabs ([#19658](https://github.com/ionic-team/ionic/issues/19658)) ([4853909](https://github.com/ionic-team/ionic/commit/4853909)), closes [#19640](https://github.com/ionic-team/ionic/issues/19640)
* **ios:** hide leaving view after nav transition to avoid flicker ([#19691](https://github.com/ionic-team/ionic/issues/19691)) ([70e0562](https://github.com/ionic-team/ionic/commit/70e0562)), closes [#19674](https://github.com/ionic-team/ionic/issues/19674)
* **menu:** clamp out of bounds swipe value ([#19684](https://github.com/ionic-team/ionic/issues/19684)) ([1535e95](https://github.com/ionic-team/ionic/commit/1535e95)), closes [#18927](https://github.com/ionic-team/ionic/issues/18927)
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** adding change events to IonTabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3c))
* **react:** pages no longer hidden when navigating between tabs, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** ensure views are removed from DOM after navigating back, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3a))
# [5.0.0-beta.0](https://github.com/ionic-team/ionic/compare/v4.11.1...v5.0.0-beta.0) (2019-10-15)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([2f88237](https://github.com/ionic-team/ionic/commit/2f882373bf08ce6ff7ec2ffb36b73e94c20881ca))
* **content:** set overscroll only on iOS ([#19470](https://github.com/ionic-team/ionic/issues/19470)) ([63c2008](https://github.com/ionic-team/ionic/commit/63c2008a86de19847677fda7b9fedce73ed7669f)), closes [#19465](https://github.com/ionic-team/ionic/issues/19465)
* **css:** update responsive display media queries ([#18601](https://github.com/ionic-team/ionic/issues/18601)) ([5d6e077](https://github.com/ionic-team/ionic/commit/5d6e077067c269d1589e7432e5074af6fc64b2fb)), closes [#18600](https://github.com/ionic-team/ionic/issues/18600)
* **grid:** remove padding on children columns when grid has ion-no-padding ([#19592](https://github.com/ionic-team/ionic/issues/19592)) ([17119f5](https://github.com/ionic-team/ionic/commit/17119f59cf525b90b966e99117137512dbd397b4)), closes [#17459](https://github.com/ionic-team/ionic/issues/17459)
* **searchbar:** update alignment of chips and other elements in toolbar ([#19596](https://github.com/ionic-team/ionic/issues/19596)) ([637f26b](https://github.com/ionic-team/ionic/commit/637f26b3642a266b6ef3b9d3d71b7327a5d3cc37)), closes [#19495](https://github.com/ionic-team/ionic/issues/19495) [#19502](https://github.com/ionic-team/ionic/issues/19502)
* **toast:** inherit height in container to center align content ([#19409](https://github.com/ionic-team/ionic/issues/19409)) ([250718a](https://github.com/ionic-team/ionic/commit/250718a40f159fbd99f96d54c11e9a6aea080f04))
### Features
* **components:** cascade mode from parent to child components ([#19369](https://github.com/ionic-team/ionic/issues/19369)) ([3dd5f05](https://github.com/ionic-team/ionic/commit/3dd5f057604dcae7b017accdd37f4f3518a1f113)), closes [#18285](https://github.com/ionic-team/ionic/issues/18285)
* **menu:** default to overlay for ios menu ([#19063](https://github.com/ionic-team/ionic/issues/19063)) ([dbf6a44](https://github.com/ionic-team/ionic/commit/dbf6a448ff3539fda2f182e00fa21435fcd60493)), closes [#18662](https://github.com/ionic-team/ionic/issues/18662)
* **overlays:** add global backdrop opacity variable for animations ([#19533](https://github.com/ionic-team/ionic/issues/19533)) ([bd22926](https://github.com/ionic-team/ionic/commit/bd22926c4995b2d953a08aac7125241929f78f9e)), closes [#16446](https://github.com/ionic-team/ionic/issues/16446)
### Performance Improvements
* **animations:** do not create setTimeout if infinite iterations ([#19632](https://github.com/ionic-team/ionic/issues/19632)) ([0d699fb](https://github.com/ionic-team/ionic/commit/0d699fb2e40b1481d8a63457dce3c58fa45976a3)), closes [#19627](https://github.com/ionic-team/ionic/issues/19627)
* **animations:** wrap loops in requestAnimationFrame call ([#19630](https://github.com/ionic-team/ionic/issues/19630)) ([589e67e](https://github.com/ionic-team/ionic/commit/589e67e4af3c2c5e42984f82cbd83d0246029535)), closes [#19629](https://github.com/ionic-team/ionic/issues/19629)
### Breaking Changes
> We recommend updating to the latest version of 4.x before trying out version 5 in order to see deprecation warnings related to your app in the console.
* **all:** mode is now cascaded from parent to child component. If this is not desired set a different mode on the child component. ([#19369](https://github.com/ionic-team/ionic/issues/19369)) ([55462d7](https://github.com/ionic-team/ionic/commit/55462d7a0935f57b6855f9bd1bf788bfcf532bc3))
* **anchor:** remove `ion-anchor`, use `ion-router-link` instead. ([#18935](https://github.com/ionic-team/ionic/issues/18935)) ([e7cd197](https://github.com/ionic-team/ionic/commit/e7cd197af79cdf87f04bc769e0367c7e93c0aa0b))
* **card:** convert card to shadow. ([#19395](https://github.com/ionic-team/ionic/issues/19395)) ([08bb60d](https://github.com/ionic-team/ionic/commit/08bb60dcbba3140bb2da64bb74217af8a36a266d))
* **css:** responsive display media queries in the display CSS file have been updated. Instead of using the maximum value of that breakpoint (for `.ion-hide-{breakpoint}-down` classes) the maximum of the media query will be the minimum of that breakpoint. ([#18601](https://github.com/ionic-team/ionic/issues/18601)) ([40a8bff](https://github.com/ionic-team/ionic/commit/40a8bffcd54882300906243781abc12776c61ca6))
* **css:** remove all CSS utility attributes. Use CSS classes instead. See the documentation for the correct class names: https://ionicframework.com/docs/layout/css-utilities ([#18956](https://github.com/ionic-team/ionic/issues/18956)) ([04862df](https://github.com/ionic-team/ionic/commit/04862df7e1029a4e6c188536cb573ddfd57e9c85))
BEFORE:
```html
<ion-header text-center></ion-header>
<ion-content padding></ion-content>
<ion-label text-wrap></ion-label>
<ion-item wrap></ion-item>
```
AFTER:
```html
<ion-header class="ion-text-center"></ion-header>
<ion-content class="ion-padding"></ion-content>
<ion-label class="ion-text-wrap"></ion-label>
<ion-item class="ion-wrap"></ion-item>
```
* **events:** remove the Events service. ([#19600](https://github.com/ionic-team/ionic/issues/19600)) ([8d4a721](https://github.com/ionic-team/ionic/commit/8d4a721c6675c13d1f84c455bb222af60be34312))
- Use "Observables" for a similar pub/sub architecture: https://angular.io/guide/observables
- Use "Redux" for advanced state management: https://ngrx.io
* **header/footer:** remove `no-border` attribute from header/footer, use `ion-no-border` class instead. ([#18954](https://github.com/ionic-team/ionic/issues/18954)) ([d9f6119](https://github.com/ionic-team/ionic/commit/d9f61197ffc62af10243a6719cc08200fd57ff8b))
* **menu:** iOS menu now defaults to overlay, set `type` to `"reveal"` to get the old behavior. ([#19063](https://github.com/ionic-team/ionic/issues/19063)) ([ccb54a1](https://github.com/ionic-team/ionic/commit/ccb54a1255ca2a303c27e5a0cf68f4e3fb86a2fb))
* **menu-controller:** remove `swipeEnable()`, use `swipeGesture()` instead. ([#19526](https://github.com/ionic-team/ionic/issues/19526)) ([30bd8fd](https://github.com/ionic-team/ionic/commit/30bd8fd739974926f0eaadddc33c50b546be4887))
* **nav:** remove `ion-nav-pop`, `ion-nav-push` and `ion-nav-set-root`. Use `ion-nav-link` with `routerDirection` instead. ([#19240](https://github.com/ionic-team/ionic/issues/19240)) ([e334d73](https://github.com/ionic-team/ionic/commit/e334d73e544efddf7335c4ce9a5382f8a34d013e))
* **searchbar:** remove boolean values from `showCancelButton`, use string values: `"always"`, `"focus"`, `"never"`. ([#18953](https://github.com/ionic-team/ionic/issues/18953)) ([508e186](https://github.com/ionic-team/ionic/commit/508e1868ad672ded49ddababd7a506ff1721534d))
BEFORE:
```html
<ion-searchbar show-cancel-button>
<ion-searchbar show-cancel-button="true">
<ion-searchbar show-cancel-button="false">
```
AFTER:
```html
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="focus">
<ion-searchbar show-cancel-button="never">
```
* **scss:** remove `scss` files from `dist/`, use CSS variables to theme instead. ([#19292](https://github.com/ionic-team/ionic/issues/19292)) ([6450aff](https://github.com/ionic-team/ionic/commit/6450aff080c561c60c140000b89ff340edeeaeca))
* **skeleton-text:** remove `width` property. Use CSS instead. ([#18936](https://github.com/ionic-team/ionic/issues/18936)) ([7c3db79](https://github.com/ionic-team/ionic/commit/7c3db79c23dac599aa8131753b33b1ed3487d776))
* **split-pane:** remove `main` attribute. Use `contentId` instead. ([#19511](https://github.com/ionic-team/ionic/issues/19511)) ([02d7841](https://github.com/ionic-team/ionic/commit/02d784140ce0fc75718781454e8dd8a2f621ee0d))
BEFORE:
```html
<ion-split-pane>
...
<div main>...</div>
</ion-split-pane>
```
AFTER:
```html
<ion-split-pane content-id="main-content">
...
<div id="main-content">...</div>
</ion-split-pane>
```
* **theming:** ionic default colors have been updated. ([#19279](https://github.com/ionic-team/ionic/issues/19279)) ([7f4cf08](https://github.com/ionic-team/ionic/commit/7f4cf08d996f90ae90064a351f8e373ce7a16799))
* **toast:** remove `showCloseButton` and `closeButtonText`, add a button using the `buttons` property with `role: 'cancel'` instead. ([#18957](https://github.com/ionic-team/ionic/issues/18957)) ([ad7f112](https://github.com/ionic-team/ionic/commit/ad7f112e1eb04e41e697936454e63977d1bde34b))
## [4.11.1](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.1) (2019-10-14)
### Bug Fixes
* **build:** properly update peer dependencies ([#19639](https://github.com/ionic-team/ionic/issues/19639)) ([b552daa](https://github.com/ionic-team/ionic/commit/b552daa))
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** handle tab back nav better, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** moving tslint and friends to devDependencies ([#19624](https://github.com/ionic-team/ionic/issues/19624)) ([7f4b77d](https://github.com/ionic-team/ionic/commit/7f4b77d))
# [4.11.0 Sodium](https://github.com/ionic-team/ionic/compare/v4.10.3...v4.11.0) (2019-10-09)
Ionic React! Enjoy! 🧂 🌊 🐼
Ionic React! Enjoy! 🧂 🌊 🐼
## [4.10.3](https://github.com/ionic-team/ionic/compare/v4.10.2...v4.10.3) (2019-10-09)

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "4.11.0",
"version": "5.0.0-beta.0",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -49,7 +49,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "4.11.0",
"@ionic/core": "5.0.0-beta.0",
"tslib": "^1.9.3"
},
"peerDependencies": {

View File

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

View File

@@ -30,6 +30,8 @@ export { ToastController } from './providers/toast-controller';
export { NavController } from './providers/nav-controller';
export { DomController } from './providers/dom-controller';
export { Config } from './providers/config';
export { AnimationController } from './providers/animation-controller';
export { GestureController } from './providers/gesture-controller';
// ROUTER STRATEGY
export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy';

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// @ts-check
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
@@ -23,7 +23,7 @@ exports.config = {
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 50000,
defaultTimeoutInterval: 70000,
print: function() {}
},
onPrepare() {

View File

@@ -942,7 +942,7 @@ ion-searchbar,scoped
ion-searchbar,prop,animated,boolean,false,false,false
ion-searchbar,prop,autocomplete,"off" | "on",'off',false,false
ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false
ion-searchbar,prop,cancelButtonIcon,string,'md-arrow-back',false,false
ion-searchbar,prop,cancelButtonIcon,string,config.get('backButtonIcon', 'md-arrow-back') as string,false,false
ion-searchbar,prop,cancelButtonText,string,'Cancel',false,false
ion-searchbar,prop,clearIcon,string | undefined,undefined,false,false
ion-searchbar,prop,color,string | undefined,undefined,false,false
@@ -965,6 +965,7 @@ ion-searchbar,event,ionClear,void,true
ion-searchbar,event,ionFocus,void,true
ion-searchbar,event,ionInput,KeyboardEvent,true
ion-searchbar,css-prop,--background
ion-searchbar,css-prop,--box-shadow
ion-searchbar,css-prop,--cancel-button-color
ion-searchbar,css-prop,--clear-button-color
ion-searchbar,css-prop,--color
@@ -1037,6 +1038,8 @@ ion-select,css-prop,--padding-bottom
ion-select,css-prop,--padding-end
ion-select,css-prop,--padding-start
ion-select,css-prop,--padding-top
ion-select,css-prop,--placeholder-color
ion-select,css-prop,--placeholder-opacity
ion-select-option,shadow
ion-select-option,prop,disabled,boolean,false,false,false
@@ -1090,6 +1093,10 @@ ion-slides,event,ionSlideTransitionStart,void,true
ion-slides,event,ionSlideWillChange,void,true
ion-slides,css-prop,--bullet-background
ion-slides,css-prop,--bullet-background-active
ion-slides,css-prop,--progress-bar-background
ion-slides,css-prop,--progress-bar-background-active
ion-slides,css-prop,--scroll-bar-background
ion-slides,css-prop,--scroll-bar-background-active
ion-spinner,shadow
ion-spinner,prop,color,string | undefined,undefined,false,false
@@ -1098,12 +1105,15 @@ ion-spinner,prop,name,"bubbles" | "circles" | "circular" | "crescent" | "dots" |
ion-spinner,prop,paused,boolean,false,false,false
ion-spinner,css-prop,--color
ion-split-pane,none
ion-split-pane,shadow
ion-split-pane,prop,contentId,string | undefined,undefined,false,false
ion-split-pane,prop,disabled,boolean,false,false,false
ion-split-pane,prop,when,boolean | string,QUERY['lg'],false,false
ion-split-pane,event,ionSplitPaneVisible,{ visible: boolean; },true
ion-split-pane,css-prop,--border
ion-split-pane,css-prop,--side-max-width
ion-split-pane,css-prop,--side-min-width
ion-split-pane,css-prop,--side-width
ion-tab,shadow
ion-tab,prop,component,Function | HTMLElement | null | string | undefined,undefined,false,false

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "4.11.0",
"version": "5.0.0-beta.0",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -34,18 +34,18 @@
"tslib": "^1.10.0"
},
"devDependencies": {
"@stencil/core": "1.6.1",
"@stencil/core": "1.7.5",
"@stencil/sass": "1.0.1",
"@types/jest": "24.0.17",
"@types/node": "12.7.1",
"@types/jest": "24.0.21",
"@types/node": "12.12.3",
"@types/puppeteer": "1.19.1",
"@types/swiper": "4.4.4",
"aws-sdk": "^2.497.0",
"clean-css-cli": "^4.1.11",
"domino": "^2.1.3",
"fs-extra": "^8.0.1",
"jest": "24.8.0",
"jest-cli": "24.8.0",
"jest": "24.9.0",
"jest-cli": "24.9.0",
"np": "^5.0.3",
"pixelmatch": "4.0.2",
"puppeteer": "1.20.0",
@@ -66,7 +66,6 @@
"build.css": "npm run css.sass && npm run css.minify",
"build.debug": "npm run clean && stencil build --debug",
"build.docs.json": "stencil build --docs-json dist/docs.json",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean": "node scripts/clean.js",
"cdnloader": "node scripts/copy-cdn-loader.js",
"css.minify": "cleancss -O2 -o ./css/ionic.bundle.css ./css/ionic.bundle.css",
@@ -80,10 +79,10 @@
"prerelease": "npm run validate && np prerelease --yolo --any-branch --tag next",
"prerender.e2e": "node scripts/testing/prerender.js",
"start": "npm run build.css && stencil build --dev --watch --serve",
"test": "stencil test --spec --e2e",
"test.spec": "stencil test --spec",
"test": "stencil test --spec --e2e --max-workers=2",
"test.spec": "stencil test --spec --max-workers=2",
"test.spec.debug": "npx --node-arg=\"--inspect-brk\" stencil test --spec",
"test.e2e": "stencil test --e2e",
"test.e2e": "stencil test --e2e --max-workers=2",
"test.screenshot": "stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/dev.js",
"test.screenshot.ci": "stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci",
"test.watch": "jest --watch --no-cache",

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
/* tslint:disable */
/**
* This is an autogenerated file created by the Stencil compiler.

View File

@@ -20,7 +20,7 @@ export interface AlertOptions {
}
export interface AlertInput {
type?: TextFieldTypes | 'checkbox' | 'radio';
type?: TextFieldTypes | 'checkbox' | 'radio' | 'textarea';
name?: string;
placeholder?: string;
value?: any;

View File

@@ -197,3 +197,8 @@
.alert-checkbox-inner {
box-sizing: border-box;
}
textarea.alert-input {
min-height: $alert-input-min-height;
resize: none;
}

View File

@@ -373,25 +373,46 @@ export class Alert implements ComponentInterface, OverlayInterface {
}
return (
<div class="alert-input-group" aria-labelledby={labelledby}>
{ inputs.map(i => (
<div class="alert-input-wrapper">
<input
placeholder={i.placeholder}
value={i.value}
type={i.type}
min={i.min}
max={i.max}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
))}
{ inputs.map(i => {
if (i.type === 'textarea') {
return (
<div class="alert-input-wrapper">
<textarea
placeholder={i.placeholder}
value={i.value}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
);
} else {
return (
<div class="alert-input-wrapper">
<input
placeholder={i.placeholder}
value={i.value}
type={i.type}
min={i.min}
max={i.max}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
);
}
})}
</div>
);
}

View File

@@ -14,3 +14,6 @@ $alert-button-line-height: 20px !default;
/// @prop - Font size of the alert button
$alert-button-font-size: 14px !default;
/// @prop - Minimum height of a textarea in the alert
$alert-input-min-height: 37px !default;

View File

@@ -17,7 +17,7 @@ Optionally, a `role` property can be added to a button, such as `cancel`. If a `
### Inputs
Alerts can also include several different inputs whose data can be passed back to the app. Inputs can be used as a simple way to prompt users for information. Radios, checkboxes and text inputs are all accepted, but they cannot be mixed. For example, an alert could have all radio button inputs, or all checkbox inputs, but the same alert cannot mix radio and checkbox inputs. Do note however, different types of "text" inputs can be mixed, such as `url`, `email`, `text`, etc. If you require a complex form UI which doesn't fit within the guidelines of an alert then we recommend building the form within a modal instead.
Alerts can also include several different inputs whose data can be passed back to the app. Inputs can be used as a simple way to prompt users for information. Radios, checkboxes and text inputs are all accepted, but they cannot be mixed. For example, an alert could have all radio button inputs, or all checkbox inputs, but the same alert cannot mix radio and checkbox inputs. Do note however, different types of "text" inputs can be mixed, such as `url`, `email`, `text`, `textarea` etc. If you require a complex form UI which doesn't fit within the guidelines of an alert then we recommend building the form within a modal instead.
<!-- Auto Generated Below -->
@@ -102,6 +102,13 @@ export class AlertExample {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',
@@ -346,6 +353,13 @@ function presentAlertPrompt() {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -131,6 +131,11 @@
placeholder: 'Placeholder 3',
disabled: true
},
{
type: 'textarea',
placeholder: 'Placeholder 4',
value: 'Textarea hello'
},
{
name: 'name3',
type: 'text',

View File

View File

@@ -73,6 +73,13 @@ export class AlertExample {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -58,6 +58,13 @@ function presentAlertPrompt() {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -5,7 +5,7 @@
// --------------------------------------------------
:host {
--background: var(--ion-item-background, transparent);
--background: #{$item-ios-background};
--color: #{$card-ios-text-color};
@include margin($card-ios-margin-top, $card-ios-margin-end, $card-ios-margin-bottom, $card-ios-margin-start);

View File

@@ -5,7 +5,7 @@
// --------------------------------------------------
:host {
--background: var(--ion-item-background, transparent);
--background: #{$item-md-background};
--color: #{$card-md-text-color};
@include margin($card-md-margin-top, $card-md-margin-end, $card-md-margin-bottom, $card-md-margin-start);

View File

@@ -130,7 +130,7 @@
height: 100%;
opacity: 0;
pointer-events: none;
}
@@ -164,3 +164,11 @@
background-repeat: repeat-y;
background-size: 10px 16px;
}
// Content: Fixed
// --------------------------------------------------
::slotted([slot="fixed"]) {
position: absolute;
}

View File

@@ -36,14 +36,14 @@ export class Content implements ComponentInterface {
event: undefined!,
startX: 0,
startY: 0,
startTimeStamp: 0,
startTime: 0,
currentX: 0,
currentY: 0,
velocityX: 0,
velocityY: 0,
deltaX: 0,
deltaY: 0,
timeStamp: 0,
currentTime: 0,
data: undefined,
isScrolling: true,
};
@@ -379,19 +379,19 @@ const updateScrollDetail = (
) => {
const prevX = detail.currentX;
const prevY = detail.currentY;
const prevT = detail.timeStamp;
const prevT = detail.currentTime;
const currentX = el.scrollLeft;
const currentY = el.scrollTop;
const timeDelta = timestamp - prevT;
if (shouldStart) {
// remember the start positions
detail.startTimeStamp = timestamp;
detail.startTime = timestamp;
detail.startX = currentX;
detail.startY = currentY;
detail.velocityX = detail.velocityY = 0;
}
detail.timeStamp = timestamp;
detail.currentTime = timestamp;
detail.currentX = detail.scrollLeft = currentX;
detail.currentY = detail.scrollTop = currentY;
detail.deltaX = currentX - detail.startX;

View File

@@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';
test('content: fixed', async () => {
const page = await newE2EPage({
url: '/src/components/content/test/fixed?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Content - Fixed</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
<body>
<ion-app>
<ion-header id="header">
<ion-toolbar>
<ion-title>Content - Fixed</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<h1>Regular Content</h1>
<div slot="fixed">
<h1>Fixed content</h1>
<p>Fixed paragraph</p>
<ion-button>Button</ion-button>
</div>
<ion-fab vertical="top" horizontal="end" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-fab vertical="bottom" horizontal="start" slot="fixed">
<ion-fab-button color="tertiary">
<ion-icon name="star"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -51,23 +51,24 @@
padding-bottom: 13px;
}
ion-toolbar.in-toolbar ion-title,
ion-toolbar.in-toolbar ion-buttons {
.header-collapse-main ion-toolbar.in-toolbar ion-title,
.header-collapse-main ion-toolbar.in-toolbar ion-buttons {
transition: all 0.2s ease-in-out;
}
/**
* There is a bug in Safari where animating the opacity
* on an element in a scrollable container while scrolling
* causes the scroll position to jump to the top
*/
.header-collapse-condense ion-toolbar ion-title,
.header-collapse-condense ion-toolbar ion-buttons {
transition: none;
}
.header-collapse-condense-inactive ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
opacity: 0;
pointer-events: none;
}
/**
* There is a bug in Safari where changing
* the opacity of an element in a scrollable container
* while rubber-banding causes the scroll position
* to jump to the top
*/
.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
visibility: hidden;
}

View File

@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Host, Prop, h, readTask, writeT
import { getIonMode } from '../../global/ionic-global';
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive } from './header.utils';
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*/
@@ -19,6 +19,7 @@ export class Header implements ComponentInterface {
private scrollEl?: HTMLElement;
private contentScrollCallback?: any;
private intersectionObserver?: any;
private collapsibleMainHeader?: HTMLElement;
@Element() el!: HTMLElement;
@@ -57,14 +58,10 @@ export class Header implements ComponentInterface {
// Determine if the header can collapse
const hasCollapse = this.collapse === 'condense';
const canCollapse = (hasCollapse && getIonMode(this) === 'ios') ? hasCollapse : false;
if (!canCollapse && this.collapsibleHeaderInitialized) {
this.destroyCollapsibleHeader();
} else if (canCollapse && !this.collapsibleHeaderInitialized) {
const tabs = this.el.closest('ion-tabs');
const page = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
const pageEl = (tabs) ? tabs : (page) ? page : null;
const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner');
const contentEl = (pageEl) ? pageEl.querySelector('ion-content') : null;
await this.setupCollapsibleHeader(contentEl, pageEl);
@@ -81,6 +78,11 @@ export class Header implements ComponentInterface {
this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
this.contentScrollCallback = undefined;
}
if (this.collapsibleMainHeader) {
this.collapsibleMainHeader.classList.remove('header-collapse-main');
this.collapsibleMainHeader = undefined;
}
}
private async setupCollapsibleHeader(contentEl: HTMLIonContentElement | null, pageEl: Element | null) {
@@ -88,49 +90,46 @@ export class Header implements ComponentInterface {
this.scrollEl = await contentEl.getScrollElement();
const headers = pageEl.querySelectorAll('ion-header');
this.collapsibleMainHeader = Array.from(headers).find((header: any) => header.collapse !== 'condense') as HTMLElement | undefined;
if (!this.collapsibleMainHeader) { return; }
const mainHeaderIndex = createHeaderIndex(this.collapsibleMainHeader);
const scrollHeaderIndex = createHeaderIndex(this.el);
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
setHeaderActive(mainHeaderIndex, false);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], 0);
readTask(() => {
const headers = pageEl.querySelectorAll('ion-header');
const mainHeader = Array.from(headers).find((header: any) => header.collapse !== 'condense') as HTMLElement | undefined;
if (!mainHeader || !this.scrollEl) { return; }
const mainHeaderIndex = createHeaderIndex(mainHeader);
const scrollHeaderIndex = createHeaderIndex(this.el);
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
setHeaderActive(mainHeaderIndex, false);
// TODO: Find a better way to do this
let remainingHeight = 0;
for (let i = 1; i <= scrollHeaderIndex.toolbars.length - 1; i++) {
remainingHeight += scrollHeaderIndex.toolbars[i].el.clientHeight;
}
/**
* Handle interaction between toolbar collapse and
* showing/hiding content in the primary ion-header
*/
const toolbarIntersection = (ev: any) => { handleToolbarIntersection(ev, mainHeaderIndex, scrollHeaderIndex); };
readTask(() => {
const mainHeaderHeight = mainHeaderIndex.el.clientHeight;
this.intersectionObserver = new IntersectionObserver(toolbarIntersection, { threshold: 0.25, rootMargin: `-${mainHeaderHeight}px 0px 0px 0px` });
/**
* Handle interaction between toolbar collapse and
* showing/hiding content in the primary ion-header
* as well as progressively showing/hiding the main header
* border as the top-most toolbar collapses or expands.
*/
const toolbarIntersection = (ev: any) => { handleToolbarIntersection(ev, mainHeaderIndex, scrollHeaderIndex); };
this.intersectionObserver = new IntersectionObserver(toolbarIntersection, { threshold: [0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], rootMargin: `-${mainHeaderHeight}px 0px 0px 0px` });
this.intersectionObserver.observe(scrollHeaderIndex.toolbars[0].el);
});
/**
* Handle scaling of large iOS titles and
* showing/hiding border on last toolbar
* in primary header
*/
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, mainHeaderIndex, scrollHeaderIndex, remainingHeight); };
this.scrollEl.addEventListener('scroll', this.contentScrollCallback);
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, scrollHeaderIndex); };
this.scrollEl!.addEventListener('scroll', this.contentScrollCallback);
});
writeTask(() => {
cloneElement('ion-title');
cloneElement('ion-back-button');
this.collapsibleMainHeader!.classList.add('header-collapse-main');
});
this.collapsibleHeaderInitialized = true;

View File

@@ -45,29 +45,22 @@ export const createHeaderIndex = (headerEl: HTMLElement | undefined): HeaderInde
innerTitleEl: (ionTitleEl) ? ionTitleEl.shadowRoot!.querySelector('.toolbar-title') : null,
ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')) || []
} as ToolbarIndex;
}) || [[]]
}) || []
} as HeaderIndex;
};
export const handleContentScroll = (scrollEl: HTMLElement, mainHeaderIndex: HeaderIndex, scrollHeaderIndex: HeaderIndex, remainingHeight = 0) => {
export const handleContentScroll = (scrollEl: HTMLElement, scrollHeaderIndex: HeaderIndex) => {
readTask(() => {
const scrollTop = scrollEl.scrollTop;
const lastMainToolbar = mainHeaderIndex.toolbars[mainHeaderIndex.toolbars.length - 1];
const scale = clamp(1, 1 + (-scrollTop / 500), 1.1);
const borderOpacity = clamp(0, (scrollTop - remainingHeight) / lastMainToolbar.el.clientHeight, 1);
const maxOpacity = 1;
const scaledOpacity = borderOpacity * maxOpacity;
writeTask(() => {
scaleLargeTitles(scrollHeaderIndex.toolbars, scale);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], (scaledOpacity === 1) ? undefined : scaledOpacity);
});
});
};
const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity: number | undefined) => {
export const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity?: number) => {
if (opacity === undefined) {
toolbar.background.style.removeProperty('--opacity');
} else {
@@ -75,6 +68,13 @@ const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity: number | un
}
};
const handleToolbarBorderIntersection = (ev: any, mainHeaderIndex: HeaderIndex) => {
if (!ev[0].isIntersecting) { return; }
const scale = ((1 - ev[0].intersectionRatio) * 100) / 75;
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], (scale === 1) ? undefined : scale);
};
/**
* If toolbars are intersecting, hide the scrollable toolbar content
* and show the primary toolbar content. If the toolbars are not intersecting,
@@ -82,7 +82,10 @@ const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity: number | un
*/
export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex, scrollHeaderIndex: HeaderIndex) => {
writeTask(() => {
handleToolbarBorderIntersection(ev, mainHeaderIndex);
const event = ev[0];
const intersection = event.intersectionRect;
const intersectionArea = intersection.width * intersection.height;
const rootArea = event.rootBounds.width * event.rootBounds.height;
@@ -114,20 +117,18 @@ export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex,
if (hasValidIntersection) {
setHeaderActive(mainHeaderIndex);
setHeaderActive(scrollHeaderIndex, false);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0]);
}
}
});
};
export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
writeTask(() => {
if (active) {
headerIndex.el.classList.remove('header-collapse-condense-inactive');
} else {
headerIndex.el.classList.add('header-collapse-condense-inactive');
}
setToolbarBackgroundOpacity(headerIndex.toolbars[0], (active) ? undefined : 0);
});
if (active) {
headerIndex.el.classList.remove('header-collapse-condense-inactive');
} else {
headerIndex.el.classList.add('header-collapse-condense-inactive');
}
};
export const scaleLargeTitles = (toolbars: ToolbarIndex[] = [], scale = 1, transition = false) => {

View File

@@ -55,7 +55,7 @@ export class ItemSliding implements ComponentInterface {
@Watch('disabled')
disabledChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
}

View File

@@ -126,6 +126,12 @@ ion-backdrop {
// Menu Split Pane
// --------------------------------------------------
:host(.menu-pane-visible) {
width: var(--width);
min-width: var(--min-width);
max-width: var(--max-width);
}
:host(.menu-pane-visible) .menu-inner {
@include position-horizontal(0, 0);

View File

@@ -3,9 +3,9 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, Side } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { GESTURE_CONTROLLER } from '../../utils/gesture';
import { assert, isEndSide as isEnd } from '../../utils/helpers';
import { assert, clamp, isEndSide as isEnd } from '../../utils/helpers';
import { menuController } from '../../utils/menu-controller';
const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
@@ -163,8 +163,8 @@ BEFORE:
<div main>...</div>
AFTER:
<ion-menu contentId="my-content"></ion-menu>
<div id="my-content">...</div>
<ion-menu contentId="main-content"></ion-menu>
<div id="main-content">...</div>
`);
}
const content = this.contentId !== undefined
@@ -337,7 +337,12 @@ AFTER:
const isReversed = !shouldOpen;
const ani = (this.animation as Animation)!
.direction((isReversed) ? 'reverse' : 'normal')
.easing((isReversed) ? this.easingReverse : this.easing);
.easing((isReversed) ? this.easingReverse : this.easing)
.onFinish(() => {
if (ani.getDirection() === 'reverse') {
ani.direction('normal');
}
});
if (animated) {
await ani.play();
@@ -384,9 +389,7 @@ AFTER:
}
// the cloned animation should not use an easing curve during seek
(this.animation as Animation)
.direction((this._isOpen) ? 'reverse' : 'normal')
.progressStart(true);
(this.animation as Animation).progressStart(true, (this._isOpen) ? 1 : 0);
}
private onMove(detail: GestureDetail) {
@@ -398,7 +401,7 @@ AFTER:
const delta = computeDelta(detail.deltaX, this._isOpen, this.isEndSide);
const stepValue = delta / this.width;
this.animation.progressStep(stepValue);
this.animation.progressStep((this._isOpen) ? 1 - stepValue : stepValue);
}
private onEnd(detail: GestureDetail) {
@@ -428,7 +431,7 @@ AFTER:
shouldOpen = true;
}
this.lastOnEnd = detail.timeStamp;
this.lastOnEnd = detail.currentTime;
// Account for rounding errors in JS
let newStepValue = (shouldComplete) ? 0.001 : -0.001;
@@ -449,14 +452,16 @@ AFTER:
* to the new easing curve, as `stepValue` is going to be given
* in terms of a linear curve.
*/
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.4, 0), new Point(0.6, 1), new Point(1, 1), adjustedStepValue);
newStepValue += getTimeGivenProgression([0, 0], [0.4, 0], [0.6, 1], [1, 1], clamp(0, adjustedStepValue, 1))[0];
const playTo = (this._isOpen) ? !shouldComplete : shouldComplete;
this.animation
.easing('cubic-bezier(0.4, 0.0, 0.6, 1)')
.onFinish(
() => this.afterAnimation(shouldOpen),
{ oneTimeCallback: true })
.progressEnd(shouldComplete ? 1 : 0, newStepValue, 300);
.progressEnd((playTo) ? 1 : 0, (this._isOpen) ? 1 - newStepValue : newStepValue, 300);
}
private beforeAnimation(shouldOpen: boolean) {
@@ -520,7 +525,7 @@ AFTER:
private updateState() {
const isActive = this._isActive();
if (this.gesture) {
this.gesture.setDisabled(!isActive || !this.swipeGesture);
this.gesture.enable(isActive && this.swipeGesture);
}
// Close menu immediately

View File

@@ -3,7 +3,7 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch, h
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { assert } from '../../utils/helpers';
import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition';
@@ -38,7 +38,7 @@ export class Nav implements NavOutlet {
@Watch('swipeGesture')
swipeGestureChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.swipeGesture !== true);
this.gesture.enable(this.swipeGesture === true);
}
}
@@ -981,9 +981,9 @@ export class Nav implements NavOutlet {
*/
if (!shouldComplete) {
this.sbAni.easing('cubic-bezier(1, 0, 0.68, 0.28)');
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(1, 0), new Point(0.68, 0.28), new Point(1, 1), stepValue);
newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], stepValue)[0];
} else {
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), stepValue);
newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], stepValue)[0];
}
(this.sbAni as Animation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);

View File

@@ -70,7 +70,7 @@ export class PickerColumnCmp implements ComponentInterface {
onMove: ev => this.onMove(ev),
onEnd: ev => this.onEnd(ev),
});
this.gesture.setDisabled(false);
this.gesture.enable();
this.tmrId = setTimeout(() => {
this.noAnimate = false;
this.refresh(true);

View File

@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface, PickerButton, PickerColumn } from '../../interface';
import { dismiss, eventMethod, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { getClassMap } from '../../utils/theme';
import { iosEnterAnimation } from './animations/ios.enter';
@@ -163,19 +163,29 @@ export class Picker implements ComponentInterface, OverlayInterface {
return Promise.resolve(this.columns.find(column => column.name === name));
}
private buttonClick(button: PickerButton) {
// if (this.disabled) {
// return;
// }
// keep the time of the most recent button click
// a handler has been provided, execute it
// pass the handler the values from the inputs
const shouldDismiss = safeCall(button.handler, this.getSelected()) !== false;
if (shouldDismiss) {
return this.dismiss();
private async buttonClick(button: PickerButton) {
const role = button.role;
if (isCancel(role)) {
return this.dismiss(undefined, role);
}
return Promise.resolve(false);
const shouldDismiss = await this.callButtonHandler(button);
if (shouldDismiss) {
return this.dismiss(this.getSelected(), button.role);
}
return Promise.resolve();
}
private async callButtonHandler(button: PickerButton | undefined) {
if (button) {
// a handler has been provided, execute it
// pass the handler the values from the inputs
const rtn = await safeCall(button.handler);
if (rtn === false) {
// if the return value of the handler is false then do not dismiss
return false;
}
}
return true;
}
private getSelected() {
@@ -194,11 +204,14 @@ export class Picker implements ComponentInterface, OverlayInterface {
}
private onBackdropTap = () => {
const cancelBtn = this.buttons.find(b => b.role === 'cancel');
if (cancelBtn) {
this.buttonClick(cancelBtn);
} else {
this.dismiss();
this.dismiss(undefined, BACKDROP);
}
private dispatchCancelHandler = (ev: CustomEvent) => {
const role = ev.detail.role;
if (isCancel(role)) {
const cancelButton = this.buttons.find(b => b.role === 'cancel');
this.callButtonHandler(cancelButton);
}
}
@@ -219,6 +232,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
zIndex: `${20000 + this.overlayIndex}`
}}
onIonBackdropTap={this.onBackdropTap}
onIonPickerWillDismiss={this.dispatchCancelHandler}
>
<ion-backdrop
visible={this.showBackdrop}

View File

@@ -48,6 +48,10 @@
const pickerController = document.querySelector('ion-picker-controller');
const pickerElement = await pickerController.create({
buttons: [{
text: 'Cancel',
role: 'cancel',
handler: () => console.log('Clicked Cancel!')
}, {
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
@@ -112,7 +116,12 @@
}],
cssClass: customClass
});
return await pickerElement.present();
await pickerElement.present();
const { data, role } = await pickerElement.onDidDismiss();
console.log('Picker dismissed!', data, role);
}
</script>
</body>

View File

@@ -113,7 +113,7 @@ export class Range implements ComponentInterface {
@Watch('disabled')
protected disabledChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
this.emitStyle();
}
@@ -194,7 +194,7 @@ export class Range implements ComponentInterface {
onMove: ev => this.onMove(ev),
onEnd: ev => this.onEnd(ev),
});
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
}

View File

@@ -75,7 +75,7 @@ export class Refresher implements ComponentInterface {
@Watch('disabled')
disabledChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
}

View File

@@ -41,7 +41,7 @@ export class ReorderGroup implements ComponentInterface {
@Watch('disabled')
disabledChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
}

View File

@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Pr
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, NavOutlet, RouteID, RouteWrite, RouterDirection, RouterOutletOptions, SwipeGestureHandler } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
import { transition } from '../../utils/transition';
@@ -47,7 +47,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
@Watch('swipeHandler')
swipeHandlerChanged() {
if (this.gesture) {
this.gesture.setDisabled(this.swipeHandler === undefined);
this.gesture.enable(this.swipeHandler !== undefined);
}
}
@@ -91,9 +91,9 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
*/
if (!shouldComplete) {
this.ani.easing('cubic-bezier(1, 0, 0.68, 0.28)');
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(1, 0), new Point(0.68, 0.28), new Point(1, 1), step);
newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], step)[0];
} else {
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), step);
newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], step)[0];
}
(this.ani as Animation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);

View File

@@ -171,15 +171,15 @@ export const SearchbarExample: React.FC = () => (
<template>
<!-- Default Searchbar -->
<ion-searchbar></ion-searchbar>
<!-- Searchbar with cancel button always shown -->
<ion-searchbar showCancelButton="always"></ion-searchbar>
<ion-searchbar show-cancel-button="always"></ion-searchbar>
<!-- Searchbar with cancel button never shown -->
<ion-searchbar showCancelButton="never"></ion-searchbar>
<ion-searchbar show-cancel-button="never"></ion-searchbar>
<!-- Searchbar with cancel button shown on focus -->
<ion-searchbar showCancelButton="focus"></ion-searchbar>
<ion-searchbar show-cancel-button="focus"></ion-searchbar>
<!-- Searchbar with danger color -->
<ion-searchbar color="danger"></ion-searchbar>
@@ -197,7 +197,7 @@ export const SearchbarExample: React.FC = () => (
<ion-searchbar disabled="true"></ion-searchbar>
<!-- Searchbar with a cancel button and custom cancel button text -->
<ion-searchbar showCancelButton="focus" cancelButtonText="Custom Cancel"></ion-searchbar>
<ion-searchbar show-cancel-button="focus" cancel-button-text="Custom Cancel"></ion-searchbar>
<!-- Searchbar with a custom debounce -->
<ion-searchbar debounce="500"></ion-searchbar>
@@ -219,25 +219,25 @@ export const SearchbarExample: React.FC = () => (
## Properties
| Property | Attribute | Description | Type | Default |
| ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------- |
| `animated` | `animated` | If `true`, enable searchbar animation. | `boolean` | `false` |
| `autocomplete` | `autocomplete` | Set the input's autocomplete property. | `"off" \| "on"` | `'off'` |
| `autocorrect` | `autocorrect` | Set the input's autocorrect property. | `"off" \| "on"` | `'off'` |
| `cancelButtonIcon` | `cancel-button-icon` | Set the cancel button icon. Only applies to `md` mode. | `string` | `'md-arrow-back'` |
| `cancelButtonText` | `cancel-button-text` | Set the the cancel button text. Only applies to `ios` mode. | `string` | `'Cancel'` |
| `clearIcon` | `clear-icon` | Set the clear icon. Defaults to `"close-circle"` for `ios` and `"close"` for `md`. | `string \| undefined` | `undefined` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `debounce` | `debounce` | Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. | `number` | `250` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the input. | `boolean` | `false` |
| `inputmode` | `inputmode` | A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. | `"decimal" \| "email" \| "none" \| "numeric" \| "search" \| "tel" \| "text" \| "url"` | `'search'` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `placeholder` | `placeholder` | Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string` | `'Search'` |
| `searchIcon` | `search-icon` | The icon to use as the search icon. | `string` | `'search'` |
| `showCancelButton` | `show-cancel-button` | Sets the behavior for the cancel button. Defaults to `"never"`. Setting to `"focus"` shows the cancel button on focus. Setting to `"never"` hides the cancel button. Setting to `"always"` shows the cancel button regardless of focus state. | `"always" \| "focus" \| "never"` | `'never'` |
| `spellcheck` | `spellcheck` | If `true`, enable spellcheck on the input. | `boolean` | `false` |
| `type` | `type` | Set the type of the input. | `"email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "url"` | `'search'` |
| `value` | `value` | the value of the searchbar. | `null \| string \| undefined` | `''` |
| Property | Attribute | Description | Type | Default |
| ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `animated` | `animated` | If `true`, enable searchbar animation. | `boolean` | `false` |
| `autocomplete` | `autocomplete` | Set the input's autocomplete property. | `"off" \| "on"` | `'off'` |
| `autocorrect` | `autocorrect` | Set the input's autocorrect property. | `"off" \| "on"` | `'off'` |
| `cancelButtonIcon` | `cancel-button-icon` | Set the cancel button icon. Only applies to `md` mode. | `string` | `config.get('backButtonIcon', 'md-arrow-back') as string` |
| `cancelButtonText` | `cancel-button-text` | Set the the cancel button text. Only applies to `ios` mode. | `string` | `'Cancel'` |
| `clearIcon` | `clear-icon` | Set the clear icon. Defaults to `"close-circle"` for `ios` and `"close"` for `md`. | `string \| undefined` | `undefined` |
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
| `debounce` | `debounce` | Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. | `number` | `250` |
| `disabled` | `disabled` | If `true`, the user cannot interact with the input. | `boolean` | `false` |
| `inputmode` | `inputmode` | A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. | `"decimal" \| "email" \| "none" \| "numeric" \| "search" \| "tel" \| "text" \| "url"` | `'search'` |
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
| `placeholder` | `placeholder` | Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `<Ionic>` would become `&lt;Ionic&gt;` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string` | `'Search'` |
| `searchIcon` | `search-icon` | The icon to use as the search icon. | `string` | `'search'` |
| `showCancelButton` | `show-cancel-button` | Sets the behavior for the cancel button. Defaults to `"never"`. Setting to `"focus"` shows the cancel button on focus. Setting to `"never"` hides the cancel button. Setting to `"always"` shows the cancel button regardless of focus state. | `"always" \| "focus" \| "never"` | `'never'` |
| `spellcheck` | `spellcheck` | If `true`, enable spellcheck on the input. | `boolean` | `false` |
| `type` | `type` | Set the type of the input. | `"email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "url"` | `'search'` |
| `value` | `value` | the value of the searchbar. | `null \| string \| undefined` | `''` |
## Events
@@ -280,7 +280,8 @@ Type: `Promise<void>`
| Name | Description |
| --------------------------- | ---------------------------------------- |
| `--background` | Background of the searchbar |
| `--background` | Background of the searchbar input |
| `--box-shadow` | Box shadow of the searchbar input |
| `--cancel-button-color` | Color of the searchbar cancel button |
| `--clear-button-color` | Color of the searchbar clear button |
| `--color` | Color of the searchbar text |

View File

@@ -5,11 +5,12 @@
// --------------------------------------------------
:host {
--clear-button-color: #{$searchbar-ios-input-clear-icon-color};
--background: #{$searchbar-ios-input-background-color};
--box-shadow: none;
--cancel-button-color: #{$searchbar-ios-cancel-button-color};
--clear-button-color: #{$searchbar-ios-input-clear-icon-color};
--color: #{$searchbar-ios-input-text-color};
--icon-color: #{$searchbar-ios-input-search-icon-color};
--background: #{$searchbar-ios-input-background-color};
@include padding($searchbar-ios-padding-top, $searchbar-ios-padding-end, $searchbar-ios-padding-bottom, $searchbar-ios-padding-start);
@@ -20,8 +21,6 @@
.searchbar-input-container {
height: $searchbar-ios-input-height;
contain: strict;
}

View File

@@ -5,11 +5,12 @@
// --------------------------------------------------
:host {
--clear-button-color: initial;
--background: #{$searchbar-md-input-background-color};
--box-shadow: #{$searchbar-md-input-box-shadow};
--cancel-button-color: #{$searchbar-md-cancel-button-color};
--clear-button-color: initial;
--color: #{$searchbar-md-input-text-color};
--icon-color: #{$searchbar-md-input-search-icon-color};
--background: #{$searchbar-md-input-background-color};
@include padding($searchbar-md-padding-top, $searchbar-md-padding-end, $searchbar-md-padding-bottom, $searchbar-md-padding-start);
@@ -67,8 +68,6 @@
font-weight: 400;
line-height: $searchbar-md-input-line-height;
box-shadow: $searchbar-md-input-box-shadow;
}

View File

@@ -5,7 +5,8 @@
:host {
/**
* @prop --background: Background of the searchbar
* @prop --background: Background of the searchbar input
* @prop --box-shadow: Box shadow of the searchbar input
* @prop --cancel-button-color: Color of the searchbar cancel button
* @prop --clear-button-color: Color of the searchbar clear button
* @prop --color: Color of the searchbar text
@@ -68,8 +69,6 @@
.searchbar-input {
@include text-inherit();
box-sizing: border-box;
display: block;
width: 100%;
@@ -81,6 +80,10 @@
background: var(--background);
font-family: inherit;
box-shadow: var(--box-shadow);
box-sizing: border-box;
appearance: none;
&::placeholder {

View File

@@ -54,7 +54,7 @@ export class Searchbar implements ComponentInterface {
/**
* Set the cancel button icon. Only applies to `md` mode.
*/
@Prop() cancelButtonIcon = 'md-arrow-back';
@Prop() cancelButtonIcon = config.get('backButtonIcon', 'md-arrow-back') as string;
/**
* Set the the cancel button text. Only applies to `ios` mode.

View File

@@ -26,7 +26,7 @@
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - No Cancel Button </h5>
<ion-searchbar id="noCancel" value="after view" autocorrect="off" autocomplete="off" spellcheck="true" type="text" show-cancel-button="never">
<ion-searchbar class="red-box" id="noCancel" value="after view" autocorrect="off" autocomplete="off" spellcheck="true" type="text" show-cancel-button="never">
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - Always Show Cancel Button</h5>
@@ -78,6 +78,10 @@
<ion-searchbar show-cancel-button="focus" cancel-button-text="Really Long Cancel" color="danger">
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - Custom Cancel Button Icon through property</h5>
<ion-searchbar show-cancel-button="focus" cancel-button-icon="rewind">
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - Value passed </h5>
<ion-searchbar value="mysearch" cancel-button-text="Really Long Cancel" color="dark" show-cancel-button="focus">
</ion-searchbar>
@@ -114,6 +118,12 @@
<ion-button expand="block" color="secondary" onClick="toggleDisabled()">Toggle disabled</ion-button>
</div>
<style>
.red-box {
--box-shadow: 0 2px 2px 0 rgba(255, 0, 0, 0.14), 0 3px 1px -2px rgba(255, 0, 0, 0.2), 0 1px 5px 0 rgba(255, 0, 0, 0.12);
}
</style>
<script>
function toggleAttr() {
var dynamicAttr = document.getElementById('dynamicAttr');

View File

@@ -2,15 +2,15 @@
<template>
<!-- Default Searchbar -->
<ion-searchbar></ion-searchbar>
<!-- Searchbar with cancel button always shown -->
<ion-searchbar showCancelButton="always"></ion-searchbar>
<ion-searchbar show-cancel-button="always"></ion-searchbar>
<!-- Searchbar with cancel button never shown -->
<ion-searchbar showCancelButton="never"></ion-searchbar>
<ion-searchbar show-cancel-button="never"></ion-searchbar>
<!-- Searchbar with cancel button shown on focus -->
<ion-searchbar showCancelButton="focus"></ion-searchbar>
<ion-searchbar show-cancel-button="focus"></ion-searchbar>
<!-- Searchbar with danger color -->
<ion-searchbar color="danger"></ion-searchbar>
@@ -28,7 +28,7 @@
<ion-searchbar disabled="true"></ion-searchbar>
<!-- Searchbar with a cancel button and custom cancel button text -->
<ion-searchbar showCancelButton="focus" cancelButtonText="Custom Cancel"></ion-searchbar>
<ion-searchbar show-cancel-button="focus" cancel-button-text="Custom Cancel"></ion-searchbar>
<!-- Searchbar with a custom debounce -->
<ion-searchbar debounce="500"></ion-searchbar>

View File

@@ -793,12 +793,14 @@ Type: `Promise<any>`
## CSS Custom Properties
| Name | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `--padding-bottom` | Bottom padding of the select |
| `--padding-end` | Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select |
| `--padding-start` | Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select |
| `--padding-top` | Top padding of the select |
| Name | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `--padding-bottom` | Bottom padding of the select |
| `--padding-end` | Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select |
| `--padding-start` | Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select |
| `--padding-top` | Top padding of the select |
| `--placeholder-color` | Color of the select placeholder text |
| `--placeholder-opacity` | Opacity of the select placeholder text |
----------------------------------------------

View File

@@ -9,7 +9,13 @@
* @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select
* @prop --padding-bottom: Bottom padding of the select
* @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select
*
* @prop --placeholder-color: Color of the select placeholder text
* @prop --placeholder-opacity: Opacity of the select placeholder text
*/
--placeholder-color: currentColor;
--placeholder-opacity: 0.33;
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
display: flex;
@@ -37,9 +43,9 @@
}
.select-placeholder {
color: currentColor;
color: var(--placeholder-color);
opacity: .33;
opacity: var(--placeholder-opacity);
}
button {

View File

@@ -471,10 +471,10 @@ export class Select implements ComponentInterface {
'select-disabled': disabled,
}}
>
<div class={selectTextClasses}>
<div class={selectTextClasses} part="text">
{selectText}
</div>
<div class="select-icon" role="presentation">
<div class="select-icon" role="presentation" part="icon">
<div class="select-icon-inner"></div>
</div>
<button

View File

@@ -26,7 +26,7 @@
<ion-item>
<ion-label>Gender</ion-label>
<ion-select id="gender" placeholder="Select One">
<ion-select id="gender" placeholder="Select One" class="custom-select">
<ion-select-option value="fn">Female</ion-select-option>
<ion-select-option value="ml">Male</ion-select-option>
</ion-select>
@@ -286,6 +286,9 @@
.outer-content {
--background: #f2f2f2;
}
.custom-select {
--placeholder-opacity: 0.5;
}
</style>
<script>

View File

@@ -761,10 +761,14 @@ Type: `Promise<void>`
## CSS Custom Properties
| Name | Description |
| ---------------------------- | ------------------------------------------ |
| `--bullet-background` | Background of the pagination bullets |
| `--bullet-background-active` | Background of the active pagination bullet |
| Name | Description |
| ---------------------------------- | ------------------------------------------------ |
| `--bullet-background` | Background of the pagination bullets |
| `--bullet-background-active` | Background of the active pagination bullet |
| `--progress-bar-background` | Background of the pagination progress-bar |
| `--progress-bar-background-active` | Background of the active pagination progress-bar |
| `--scroll-bar-background` | Background of the pagination scroll-bar |
| `--scroll-bar-background-active` | Background of the active pagination scroll-bar |
----------------------------------------------

View File

@@ -8,6 +8,12 @@ ion-slides {
/**
* @prop --bullet-background: Background of the pagination bullets
* @prop --bullet-background-active: Background of the active pagination bullet
*
* @prop --progress-bar-background: Background of the pagination progress-bar
* @prop --progress-bar-background-active: Background of the active pagination progress-bar
*
* @prop --scroll-bar-background: Background of the pagination scroll-bar
* @prop --scroll-bar-background-active: Background of the active pagination scroll-bar
*/
display: block;

View File

@@ -149,9 +149,12 @@ export const SplitPlaneExample: React.SFC<{}> = () => (
## CSS Custom Properties
| Name | Description |
| ---------- | -------------------- |
| `--border` | Border between panes |
| Name | Description |
| ------------------ | ---------------------------------------------------------------------------- |
| `--border` | Border between panes |
| `--side-max-width` | Maximum width of the side pane. Does not apply when split pane is collapsed. |
| `--side-min-width` | Minimum width of the side pane. Does not apply when split pane is collapsed. |
| `--side-width` | Width of the side pane. Does not apply when split pane is collapsed. |
----------------------------------------------

View File

@@ -4,21 +4,23 @@
// Split Pane
// --------------------------------------------------
.split-pane-ios {
:host {
--border: #{$split-pane-ios-border};
--side-min-width: #{$split-pane-ios-side-min-width};
--side-max-width: #{$split-pane-ios-side-max-width};
}
.split-pane-ios.split-pane-visible > .split-pane-side {
min-width: $split-pane-ios-side-min-width;
max-width: $split-pane-ios-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: var(--border);
border-left: 0;
}
.split-pane-ios.split-pane-visible > .split-pane-side[side=end] {
min-width: $split-pane-ios-side-min-width;
max-width: $split-pane-ios-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: 0;
border-left: var(--border);

View File

@@ -4,21 +4,23 @@
// Split Pane
// --------------------------------------------------
.split-pane-md {
:host {
--border: #{$split-pane-md-border};
--side-min-width: #{$split-pane-md-side-min-width};
--side-max-width: #{$split-pane-md-side-max-width};
}
.split-pane-md.split-pane-visible > .split-pane-side {
min-width: $split-pane-md-side-min-width;
max-width: $split-pane-md-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: var(--border);
border-left: 0;
}
.split-pane-md.split-pane-visible > .split-pane-side[side=end] {
min-width: $split-pane-md-side-min-width;
max-width: $split-pane-md-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: 0;
border-left: var(--border);

View File

@@ -1,12 +1,17 @@
@import "./split-pane.vars";
@import "../menu/menu.vars";
// Split Pane
// --------------------------------------------------
ion-split-pane {
:host {
/**
* @prop --border: Border between panes
* @prop --side-min-width: Minimum width of the side pane. Does not apply when split pane is collapsed.
* @prop --side-max-width: Maximum width of the side pane. Does not apply when split pane is collapsed.
* @prop --side-width: Width of the side pane. Does not apply when split pane is collapsed.
*/
--side-width: 100%;
@include position(0, 0, 0, 0);
@@ -19,34 +24,50 @@ ion-split-pane {
contain: strict;
}
.split-pane-visible > .split-pane-side,
.split-pane-visible > .split-pane-main {
/**
* Do not pass CSS Variables down on larger
* screens as we want them to affect the outer
* `ion-menu` rather than the inner content
*/
::slotted(ion-menu.menu-pane-visible) {
flex: 0 1 auto;
width: var(--side-width);
min-width: var(--side-min-width);
max-width: var(--side-max-width);
}
:host(.split-pane-visible) ::slotted(.split-pane-side),
:host(.split-pane-visible) ::slotted(.split-pane-main) {
@include position(0, 0, 0, 0);
position: relative;
flex: 1;
/* stylelint-disable-next-line declaration-no-important */
box-shadow: none !important;
z-index: 0;
}
.split-pane-visible > .split-pane-side:not(ion-menu),
.split-pane-visible > ion-menu.split-pane-side.menu-enabled {
:host(.split-pane-visible) ::slotted(.split-pane-main) {
flex: 1;
}
:host(.split-pane-visible) ::slotted(.split-pane-side:not(ion-menu)),
:host(.split-pane-visible) ::slotted(ion-menu.split-pane-side.menu-enabled) {
display: flex;
flex-shrink: 0;
}
.split-pane-side:not(ion-menu) {
::slotted(.split-pane-side:not(ion-menu)) {
display: none;
}
.split-pane-visible > .split-pane-side {
:host(.split-pane-visible) ::slotted(.split-pane-side) {
order: -1;
}
.split-pane-visible > .split-pane-side[side=end] {
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
order: 1;
}

View File

@@ -18,7 +18,8 @@ const QUERY: { [key: string]: string } = {
styleUrls: {
ios: 'split-pane.ios.scss',
md: 'split-pane.md.scss'
}
},
shadow: true
})
export class SplitPane implements ComponentInterface {
@@ -159,6 +160,7 @@ export class SplitPane implements ComponentInterface {
'split-pane-visible': this.visible
}}
>
<slot></slot>
</Host>
);
}

View File

@@ -5,6 +5,28 @@ test('split-pane: basic', async () => {
url: '/src/components/split-pane/test/basic?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const screenshotCompares = [];
const MIN_WIDTH = '#side-min-width';
const MAX_WIDTH = '#side-max-width';
const WIDTH = '#side-width';
screenshotCompares.push(await page.compareScreenshot());
await page.click(MIN_WIDTH);
screenshotCompares.push(await page.compareScreenshot());
await page.click(MIN_WIDTH);
await page.click(MAX_WIDTH);
screenshotCompares.push(await page.compareScreenshot());
await page.click(MAX_WIDTH);
await page.click(WIDTH);
screenshotCompares.push(await page.compareScreenshot());
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});

View File

@@ -69,8 +69,22 @@
<f></f>
<f></f>
<f></f>
<ion-item>
<ion-label>Toggle Side Min Width</ion-label>
<ion-toggle id="side-min-width"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Toggle Side Max Width</ion-label>
<ion-toggle id="side-max-width"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Toggle Side Fixed Width</ion-label>
<ion-toggle id="side-width"></ion-toggle>
</ion-item>
</ion-content>
</div>
</ion-split-pane>
@@ -78,6 +92,16 @@
<ion-menu-controller></ion-menu-controller>
<script>
const sideMinWidth = document.querySelector('ion-toggle#side-min-width');
const sideMaxWidth = document.querySelector('ion-toggle#side-max-width');
const sideWidth = document.querySelector('ion-toggle#side-width');
const splitPane = document.querySelector('ion-split-pane');
sideMinWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-min-width'));
sideMaxWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-max-width'));
sideWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-width'));
const menuCtrl = document.querySelector('ion-menu-controller');
function openStart() {
menuCtrl.open('start');
@@ -89,6 +113,20 @@
</script>
<style>
.side-width {
--side-width: 150px;
--side-min-width: 150px;
}
.side-min-width {
--side-min-width: 500px;
}
.side-max-width {
--side-max-width: 75px;
--side-min-width: 0px;
}
f {
display: block;
height: 150px;

View File

@@ -1,4 +1,4 @@
import { Build, Component, ComponentInterface, Element, Host, Method, Prop, h } from '@stencil/core';
import { Build, Component, ComponentInterface, Element, Host, Method, Prop, Watch, h } from '@stencil/core';
import { ComponentRef, FrameworkDelegate } from '../../interface';
import { attachComponent } from '../../utils/framework-delegate';
@@ -30,7 +30,7 @@ export class Tab implements ComponentInterface {
*/
@Prop() component?: ComponentRef;
componentWillLoad() {
async componentWillLoad() {
if (Build.isDev) {
if (this.component !== undefined && this.el.childElementCount > 0) {
console.error('You can not use a lazy-loaded component in a tab and inlined content at the same time.' +
@@ -39,6 +39,9 @@ export class Tab implements ComponentInterface {
`- Remove the embedded content inside the ion-tab: <ion-tab></ion-tab>`);
}
}
if (this.active) {
await this.setActive();
}
}
/** Set the active component for the tab */
@@ -48,6 +51,13 @@ export class Tab implements ComponentInterface {
this.active = true;
}
@Watch('active')
changeActive(isActive: boolean) {
if (isActive) {
this.prepareLazyLoaded();
}
}
private prepareLazyLoaded(): Promise<HTMLElement | undefined> {
if (!this.loaded && this.component != null) {
this.loaded = true;

View File

@@ -46,7 +46,9 @@ export class Tabs implements NavOutlet {
}
if (!this.useRouter) {
const tabs = this.tabs;
await this.select(tabs[0]);
if (tabs.length > 0) {
await this.select(tabs[0]);
}
}
this.ionNavWillLoad.emit();
}
@@ -127,7 +129,8 @@ export class Tabs implements NavOutlet {
this.leavingTab = this.selectedTab;
this.selectedTab = selectedTab;
this.ionTabsWillChange.emit({ tab: selectedTab.tab });
return selectedTab.setActive();
selectedTab.active = true;
return Promise.resolve();
}
private tabSwitch() {

View File

@@ -12,7 +12,7 @@
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body onload="selectTab()">
<body>
<ion-tabs>
<ion-tab tab="tab-one">
<div class="content-div div-one">
@@ -71,11 +71,11 @@
</style>
<script>
async function selectTab() {
customElements.whenDefined('ion-tabs').then(async () => {
const tabs = document.querySelector('ion-tabs');
await tabs.componentOnReady();
await tabs.select('tab-three');
}
});
</script>
</body>

View File

@@ -96,6 +96,8 @@
appearance: none;
&::placeholder {
@include padding(0);
color: var(--placeholder-color);
font-family: inherit;

View File

@@ -89,6 +89,15 @@ In this example, notice that we have added two sets of `ion-buttons` both with `
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```
### React
@@ -208,6 +217,15 @@ In this example, notice that we have added two sets of `IonButtons` both with `c
`IonButtons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```
### Vue
@@ -297,6 +315,15 @@ In this example, notice that we have added two sets of `ion-buttons` both with `
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```
## Properties

View File

@@ -76,4 +76,13 @@ In the example above, notice there are two `ion-header` elements. The first `ion
In this example, notice that we have added two sets of `ion-buttons` both with `collapse="true"`. When the secondary header collapses, the buttons in the secondary header will hide, and the buttons in the primary header will show. This is useful for ensuring that your header buttons always appear next to an `ion-title` element.
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```

View File

@@ -76,4 +76,13 @@ In the example above, notice there are two `ion-header` elements. The first `ion
In this example, notice that we have added two sets of `ion-buttons` both with `collapse="true"`. When the secondary header collapses, the buttons in the secondary header will hide, and the buttons in the primary header will show. This is useful for ensuring that your header buttons always appear next to an `ion-title` element.
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```

View File

@@ -113,3 +113,12 @@ export const LargeTitleExample: React.FC = () => (
In this example, notice that we have added two sets of `IonButtons` both with `collapse="true"`. When the secondary header collapses, the buttons in the secondary header will hide, and the buttons in the primary header will show. This is useful for ensuring that your header buttons always appear next to an `IonTitle` element.
`IonButtons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```

View File

@@ -83,3 +83,12 @@ In the example above, notice there are two `ion-header` elements. The first `ion
In this example, notice that we have added two sets of `ion-buttons` both with `collapse="true"`. When the secondary header collapses, the buttons in the secondary header will hide, and the buttons in the primary header will show. This is useful for ensuring that your header buttons always appear next to an `ion-title` element.
`ion-buttons` elements that do not have `collapse` set will always be visible, regardless of collapsed state.
When styling the large title, you should target the large title globally as opposed to within the context of a particular page or tab, otherwise its styles will not be applied during the navigation animation.
```css
ion-title.large-title {
color: purple;
font-size: 30px;
}
```

View File

@@ -191,7 +191,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
}
const shouldDismiss = await this.callButtonHandler(button);
if (shouldDismiss) {
return this.dismiss(undefined, button.role);
return this.dismiss(undefined, role);
}
return Promise.resolve();
}
@@ -213,6 +213,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
return true;
}
private dispatchCancelHandler = (ev: CustomEvent) => {
const role = ev.detail.role;
if (isCancel(role)) {
const cancelButton = this.getButtons().find(b => b.role === 'cancel');
this.callButtonHandler(cancelButton);
}
}
renderButtons(buttons: ToastButton[], side: 'start' | 'end') {
if (buttons.length === 0) {
return;
@@ -265,6 +273,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
...getClassMap(this.cssClass),
'toast-translucent': this.translucent
}}
onIonToastWillDismiss={this.dispatchCancelHandler}
>
<div class={wrapperClass}>
<div class="toast-container">

View File

@@ -115,17 +115,17 @@ export const ToggleExample: React.FC = () => (
<IonList>
<IonItem>
<IonLabel>Pepperoni</IonLabel>
<IonToggle value="pepperoni" onChange={() => {}} />
<IonToggle value="pepperoni" onIonChange={() => {}} />
</IonItem>
<IonItem>
<IonLabel>Sausage</IonLabel>
<IonToggle value="sausage" onChange={() => {}} disabled={true} />
<IonToggle value="sausage" onIonChange={() => {}} disabled={true} />
</IonItem>
<IonItem>
<IonLabel>Mushrooms</IonLabel>
<IonToggle value="mushrooms" onChange={() => {}} />
<IonToggle value="mushrooms" onIonChange={() => {}} />
</IonItem>
</IonList>
</IonContent>

View File

@@ -92,7 +92,7 @@ export class Toggle implements ComponentInterface {
disabledChanged() {
this.emitStyle();
if (this.gesture) {
this.gesture.setDisabled(this.disabled);
this.gesture.enable(!this.disabled);
}
}

View File

@@ -24,17 +24,17 @@ export const ToggleExample: React.FC = () => (
<IonList>
<IonItem>
<IonLabel>Pepperoni</IonLabel>
<IonToggle value="pepperoni" onChange={() => {}} />
<IonToggle value="pepperoni" onIonChange={() => {}} />
</IonItem>
<IonItem>
<IonLabel>Sausage</IonLabel>
<IonToggle value="sausage" onChange={() => {}} disabled={true} />
<IonToggle value="sausage" onIonChange={() => {}} disabled={true} />
</IonItem>
<IonItem>
<IonLabel>Mushrooms</IonLabel>
<IonToggle value="mushrooms" onChange={() => {}} />
<IonToggle value="mushrooms" onIonChange={() => {}} />
</IonItem>
</IonList>
</IonContent>

View File

@@ -30,6 +30,10 @@
.create ion-icon {
transform: scale(0.9);
}
.main-toolbar {
--background: rgba(0, 0, 0, 0.05);
}
</style>
</head>
@@ -77,8 +81,8 @@
connectedCallback() {
this.innerHTML = `
<ion-header class="main-header">
<ion-toolbar>
<ion-header class="main-header" translucent>
<ion-toolbar class="main-toolbar">
<ion-buttons collapse>
<ion-back-button default-href="/"></ion-back-button>
</ion-buttons>
@@ -89,7 +93,7 @@
</ion-toolbar>
</ion-header>
<ion-content id="content" class="main-content">
<ion-content id="content" class="main-content" fullscreen>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Page Two</ion-title>

View File

@@ -76,6 +76,10 @@ body.backdrop-no-scroll {
z-index: $z-index-page-container;
}
.split-pane-visible > .ion-page.split-pane-main {
position: relative;
}
ion-route,
ion-route-redirect,
ion-router,

View File

@@ -1,7 +1,10 @@
import 'ionicons';
export { createAnimation } from './utils/animation/animation';
export { getTimeGivenProgression } from './utils/animation/cubic-bezier';
export { createGesture } from './utils/gesture';
export { createPressRecognizer } from './utils/gesture/recognizers/press';
export { isPlatform, Platforms, getPlatforms } from './utils/platform';
export * from './utils/config';

View File

@@ -34,9 +34,9 @@ export * from './components/virtual-scroll/virtual-scroll-interface';
export { Animation, AnimationBuilder } from './utils/animation/animation-interface';
export * from './utils/overlays-interface';
export * from './global/config';
export { Gesture, GestureDetail } from './utils/gesture';
export { Gesture, GestureConfig, GestureDetail } from './utils/gesture';
export { PressRecognizerOptions } from './utils/gesture/recognizers/press';
// Global aux types
export type TextFieldTypes = 'date' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'url' | 'time';
export type Side = 'start' | 'end';
export type PredefinedColors = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'light' | 'medium' | 'dark';

View File

@@ -2,6 +2,7 @@ export interface Animation {
parentAnimation: Animation | undefined;
elements: HTMLElement[];
childAnimations: Animation[];
id: string | undefined;
/**
* Play the animation
@@ -28,7 +29,7 @@ export interface Animation {
*/
destroy(): void;
progressStart(forceLinearEasing: boolean): void;
progressStart(forceLinearEasing: boolean, step?: number): void;
progressStep(step: number): void;
progressEnd(playTo: 0 | 1 | undefined, step: number, dur?: number): void;

View File

@@ -19,12 +19,12 @@ interface AnimationInternal extends Animation {
/**
* Updates any existing animations.
*/
update(deep: boolean): Animation;
update(deep: boolean, toggleAnimationName: boolean, step?: number): Animation;
animationFinish(): void;
}
export const createAnimation = (): Animation => {
export const createAnimation = (animationId?: string): Animation => {
let _delay: number | undefined;
let _duration: number | undefined;
let _easing: string | undefined;
@@ -32,6 +32,8 @@ export const createAnimation = (): Animation => {
let _fill: AnimationFill | undefined;
let _direction: AnimationDirection | undefined;
let _keyframes: AnimationKeyFrames = [];
const id: string | undefined = animationId;
let beforeAddClasses: string[] = [];
let beforeRemoveClasses: string[] = [];
let initialized = false;
@@ -128,8 +130,10 @@ export const createAnimation = (): Animation => {
webAnimations.length = 0;
} else {
const elementsArray = elements.slice();
raf(() => {
elements.forEach(element => {
elementsArray.forEach(element => {
removeStyleProperty(element, 'animation-name');
removeStyleProperty(element, 'animation-duration');
removeStyleProperty(element, 'animation-timing-function');
@@ -531,7 +535,7 @@ export const createAnimation = (): Animation => {
elements.forEach(element => {
if (_keyframes.length > 0) {
const keyframeRules = generateKeyframeRules(_keyframes);
keyframeName = generateKeyframeName(keyframeRules);
keyframeName = (animationId !== undefined) ? animationId : generateKeyframeName(keyframeRules);
const stylesheet = createKeyframeStylesheet(keyframeName, keyframeRules, element);
stylesheets.push(stylesheet);
@@ -562,6 +566,7 @@ export const createAnimation = (): Animation => {
const initializeWebAnimation = () => {
elements.forEach(element => {
const animation = element.animate(_keyframes, {
id,
delay: getDelay(),
duration: getDuration(),
easing: getEasing(),
@@ -606,8 +611,7 @@ export const createAnimation = (): Animation => {
});
} else {
const animationDelay = getDelay() || 0;
const animationDuration = `-${animationDelay + (getDuration()! * step)}ms`;
const animationDuration = `-${getDuration()! * step}ms`;
elements.forEach(element => {
if (_keyframes.length > 0) {
@@ -618,7 +622,7 @@ export const createAnimation = (): Animation => {
}
};
const updateWebAnimation = () => {
const updateWebAnimation = (step?: number) => {
webAnimations.forEach(animation => {
animation.effect.updateTiming({
delay: getDelay(),
@@ -629,15 +633,19 @@ export const createAnimation = (): Animation => {
direction: getDirection()
});
});
if (step !== undefined) {
setAnimationStep(step);
}
};
const updateCSSAnimation = (toggleAnimationName = true) => {
const updateCSSAnimation = (toggleAnimationName = true, step?: number) => {
raf(() => {
elements.forEach(element => {
setStyleProperty(element, 'animation-name', keyframeName || null);
setStyleProperty(element, 'animation-duration', `${getDuration()}ms`);
setStyleProperty(element, 'animation-timing-function', getEasing());
setStyleProperty(element, 'animation-delay', `${getDelay()}ms`);
setStyleProperty(element, 'animation-delay', (step !== undefined) ? `-${step! * getDuration()}ms` : `${getDelay()}ms`);
setStyleProperty(element, 'animation-fill-mode', getFill() || null);
setStyleProperty(element, 'animation-direction', getDirection() || null);
@@ -658,25 +666,25 @@ export const createAnimation = (): Animation => {
});
};
const update = (deep = false, toggleAnimationName = true) => {
const update = (deep = false, toggleAnimationName = true, step?: number) => {
if (deep) {
childAnimations.forEach(animation => {
animation.update(deep);
animation.update(deep, toggleAnimationName, step);
});
}
if (supportsWebAnimations) {
updateWebAnimation();
updateWebAnimation(step);
} else {
updateCSSAnimation(toggleAnimationName);
updateCSSAnimation(toggleAnimationName, step);
}
return ani;
};
const progressStart = (forceLinearEasing = false) => {
const progressStart = (forceLinearEasing = false, step?: number) => {
childAnimations.forEach(animation => {
animation.progressStart(forceLinearEasing);
animation.progressStart(forceLinearEasing, step);
});
pauseAnimation();
@@ -685,8 +693,7 @@ export const createAnimation = (): Animation => {
if (!initialized) {
initializeAnimation();
} else {
update();
setAnimationStep(0);
update(false, true, step);
}
return ani;
@@ -955,6 +962,7 @@ export const createAnimation = (): Animation => {
parentAnimation,
elements,
childAnimations,
id,
animationFinish,
from,
to,

View File

@@ -5,11 +5,8 @@
* TODO: Reduce rounding error
*/
export class Point {
constructor(public x: number, public y: number) {}
}
/**
* EXPERIMENTAL
* Given a cubic-bezier curve, get the x value (time) given
* the y value (progression).
* Ex: cubic-bezier(0.32, 0.72, 0, 1);
@@ -17,10 +14,14 @@ export class Point {
* P1: (0.32, 0.72)
* P2: (0, 1)
* P3: (1, 1)
*
* If you give a cubic bezier curve that never reaches the
* provided progression, this function will return an empty array.
*/
export const getTimeGivenProgression = (p0: Point, p1: Point, p2: Point, p3: Point, progression: number) => {
const tValues = solveCubicBezier(p0.y, p1.y, p2.y, p3.y, progression);
return solveCubicParametricEquation(p0.x, p1.x, p2.x, p3.x, tValues[0]); // TODO: Add better strategy for dealing with multiple solutions
export const getTimeGivenProgression = (p0: number[], p1: number[], p2: number[], p3: number[], progression: number): number[] => {
return solveCubicBezier(p0[1], p1[1], p2[1], p3[1], progression).map(tValue => {
return solveCubicParametricEquation(p0[0], p1[0], p2[0], p3[0], tValue);
});
};
/**

View File

@@ -1,5 +1,5 @@
import { createAnimation } from '../animation';
import { getTimeGivenProgression, Point } from '../cubic-bezier';
import { getTimeGivenProgression } from '../cubic-bezier';
import { Animation } from '../animation-interface';
describe('Animation Class', () => {
@@ -313,58 +313,95 @@ describe('cubic-bezier conversion', () => {
describe('should properly get a time value (x value) given a progression value (y value)', () => {
it('cubic-bezier(0.32, 0.72, 0, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0.32, 0.72),
new Point(0, 1),
new Point(1, 1)
[0, 0],
[0.32, 0.72],
[0, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), 0.16);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), 0.56);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), 0.11);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), [0.16]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), [0.56]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), [0.11]);
});
it('cubic-bezier(1, 0, 0.68, 0.28)', () => {
const equation = [
new Point(0, 0),
new Point(1, 0),
new Point(0.68, 0.28),
new Point(1, 1)
[0, 0],
[1, 0],
[0.68, 0.28],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), 0.60);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.50), 0.84);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), 0.98);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), [0.60]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.50), [0.84]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), [0.98]);
})
it('cubic-bezier(0.4, 0, 0.6, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0.4, 0),
new Point(0.6, 1),
new Point(1, 1)
[0, 0],
[0.4, 0],
[0.6, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), 0.43);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), 0.11);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), 0.78);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), [0.43]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), [0.11]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), [0.78]);
})
it('cubic-bezier(0, 0, 0.2, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0, 0),
new Point(0.2, 1),
new Point(1, 1)
[0, 0],
[0, 0],
[0.2, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), 0.71);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), 0.03);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.70), 0.35);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), [0.71]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), [0.03]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.70), [0.35]);
})
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
const equation = [
[0, 0],
[0.05, 0.2],
[.14, 1.72],
[1, 1]
];
expect(getTimeGivenProgression(...equation, 1.32)[0]).toBeUndefined();
expect(getTimeGivenProgression(...equation, -0.32)[0]).toBeUndefined();
})
it('cubic-bezier(0.21, 1.71, 0.88, 0.9) (multiple solutions)', () => {
const equation = [
[0, 0],
[0.21, 1.71],
[0.88, 0.9],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 1.02), [0.35, 0.87]);
})
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
const equation = [
[0, 0],
[0.05, 0.2],
[.14, 1.72],
[1, 1]
];
expect(getTimeGivenProgression(...equation, 1.32)).toEqual([]);
expect(getTimeGivenProgression(...equation, -0.32)).toEqual([]);
})
})
});
const shouldApproximatelyEqual = (givenValue: number, expectedValue: number): boolean => {
expect(Math.abs(expectedValue - givenValue)).toBeLessThanOrEqual(0.01);
const shouldApproximatelyEqual = (givenValues: number[], expectedValues: number[]): void => {
givenValues.forEach((givenValue, i) => {
expect(Math.abs(expectedValues[i] - givenValue)).toBeLessThanOrEqual(0.01);
});
}

View File

@@ -21,7 +21,7 @@
const squareA = document.querySelector('.square-a');
const rootAnimation = createAnimation();
const rootAnimation = createAnimation('root-animation-id');
rootAnimation
.addElement(squareA)

View File

@@ -31,14 +31,14 @@ export const createGesture = (config: GestureConfig): Gesture => {
type: 'pan',
startX: 0,
startY: 0,
startTimeStamp: 0,
startTime: 0,
currentX: 0,
currentY: 0,
velocityX: 0,
velocityY: 0,
deltaX: 0,
deltaY: 0,
timeStamp: 0,
currentTime: 0,
event: undefined as any,
data: undefined
};
@@ -59,7 +59,7 @@ export const createGesture = (config: GestureConfig): Gesture => {
updateDetail(ev, detail);
detail.startX = detail.currentX;
detail.startY = detail.currentY;
detail.startTimeStamp = detail.timeStamp = timeStamp;
detail.startTime = detail.currentTime = timeStamp;
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
detail.event = ev;
@@ -131,7 +131,7 @@ export const createGesture = (config: GestureConfig): Gesture => {
// more accurate value of the velocity.
detail.startX = detail.currentX;
detail.startY = detail.currentY;
detail.startTimeStamp = detail.timeStamp;
detail.startTime = detail.currentTime;
if (onWillStart) {
onWillStart(detail).then(fireOnStart);
@@ -202,11 +202,15 @@ export const createGesture = (config: GestureConfig): Gesture => {
};
return {
setDisabled(disabled: boolean) {
if (disabled && hasCapturedPan) {
pointerUp(undefined);
enable(enable = true) {
if (!enable) {
if (hasCapturedPan) {
pointerUp(undefined);
}
reset();
}
pointerEvents.setDisabled(disabled);
pointerEvents.enable(enable);
},
destroy() {
gesture.destroy();
@@ -221,13 +225,13 @@ const calcGestureData = (detail: GestureDetail, ev: UIEvent | undefined) => {
}
const prevX = detail.currentX;
const prevY = detail.currentY;
const prevT = detail.timeStamp;
const prevT = detail.currentTime;
updateDetail(ev, detail);
const currentX = detail.currentX;
const currentY = detail.currentY;
const timestamp = detail.timeStamp = now(ev);
const timestamp = detail.currentTime = now(ev);
const timeDelta = timestamp - prevT;
if (timeDelta > 0 && timeDelta < 100) {
const velocityX = (currentX - prevX) / timeDelta;
@@ -268,14 +272,14 @@ export interface GestureDetail {
type: string;
startX: number;
startY: number;
startTimeStamp: number;
startTime: number;
currentX: number;
currentY: number;
velocityX: number;
velocityY: number;
deltaX: number;
deltaY: number;
timeStamp: number;
currentTime: number;
event: UIEvent;
data?: any;
}
@@ -283,7 +287,7 @@ export interface GestureDetail {
export type GestureCallback = (detail: GestureDetail) => boolean | void;
export interface Gesture {
setDisabled(disabled: boolean): void;
enable(enable?: boolean): void;
destroy(): void;
}

View File

@@ -92,8 +92,8 @@ export const createPointerEvents = (
stopMouse();
};
const setDisabled = (disabled: boolean) => {
if (disabled) {
const enable = (isEnabled = true) => {
if (!isEnabled) {
if (rmTouchStart) {
rmTouchStart();
}
@@ -114,12 +114,12 @@ export const createPointerEvents = (
};
const destroy = () => {
setDisabled(true);
enable(false);
pointerUp = pointerMove = pointerDown = undefined;
};
return {
setDisabled,
enable,
stop,
destroy
};

View File

@@ -0,0 +1,91 @@
import { GestureConfig, GestureDetail, createGesture } from '../index';
export interface PressRecognizerOptions extends GestureConfig {
/**
* How long the user must press the
* element before onPressHandler is fired.
*/
time: number;
/**
* The maximum amount of movement on the x and y
* axes that is allowed and still have onPressHandler fire.
*/
maxThreshold: number;
/**
* Fired when the long press occurs.
*/
onPressHandler?: () => void;
/**
* Fired when the pointer is lifted.
*/
onPressUpHandler?: () => void;
}
export const createPressRecognizer = (opts: PressRecognizerOptions) => {
const THRESHOLD = opts.maxThreshold || 10;
let timeout: any;
let pressed = false;
const onStart = (detail: GestureDetail) => {
if (opts.onStart) {
opts.onStart(detail);
}
clearGestureTimeout();
timeout = setTimeout(() => {
if (opts.onPressHandler) {
opts.onPressHandler();
}
pressed = true;
clearGestureTimeout();
}, opts.time || 500);
};
const onMove = (detail: GestureDetail) => {
if (opts.onMove) {
opts.onMove(detail);
}
if (Math.abs(detail.deltaX) + Math.abs(detail.deltaY) <= THRESHOLD) {
return;
}
clearGestureTimeout();
};
const onEnd = (detail: GestureDetail) => {
if (opts.onEnd) {
opts.onEnd(detail);
}
if (!pressed) { return; }
if (opts.onPressUpHandler) {
opts.onPressUpHandler();
}
pressed = false;
clearGestureTimeout();
};
const clearGestureTimeout = () => {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
};
return createGesture({
...opts,
gestureName: opts.gestureName || 'press',
threshold: opts.threshold || 0,
onStart,
onMove,
onEnd,
});
};

View File

@@ -0,0 +1,77 @@
import { newE2EPage } from '@stencil/core/testing';
test(`gesture: press`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
const square = await page.$('.square');
const { x, y } = await coords(square);
page.mouse.move(x, y);
page.mouse.down();
await page.waitFor(300);
expect(await page.find('.square')).toHaveClass('pressed');
page.mouse.up();
expect(await page.find('.square')).toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
test(`gesture: press:short press`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
const square = await page.$('.square');
const { x, y } = await coords(square);
page.mouse.move(x, y);
page.mouse.down();
await page.waitFor(50);
page.mouse.up();
const squareAgain = await page.find('.square');
expect(squareAgain).not.toHaveClass('pressed');
expect(squareAgain).not.toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
test(`gesture: press:click`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
await page.click('.square');
const square = await page.find('.square');
expect(square).not.toHaveClass('pressed');
expect(square).not.toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
const coords = async el => {
const box = await el.boundingBox();
return {
x: Math.floor(box.x + (box.width / 2)),
y: Math.floor(box.y + (box.height / 2))
};
};

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Gesture - Press</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../../dist/ionic/ionic.esm.js"></script>
<script type="module">
import { createPressRecognizer } from '../../../../../dist/ionic/index.esm.js';
const square = document.querySelector('.square');
const pressRecognizer = createPressRecognizer({
el: square,
time: 251,
minThreshold: 10,
onStart: () => square.classList.remove('pressedUp'),
onPressHandler: () => square.classList.toggle('pressed'),
onPressUpHandler: () => square.classList.add('pressedUp')
});
pressRecognizer.enable(true);
</script>
<style>
.square {
width: 100px;
height: 100px;
background: rgba(255, 0, 0, 0.5);
text-align: center;
line-height: 100px;
margin-left: 25px;
margin-top: 25px;
transition: 0.2s all ease-in-out;
cursor: pointer;
}
.pressed {
transform: scale(1.5);
background: rgba(0, 255, 0, 0.5);
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Gesture - Press</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="ion-padding">
<div class="square">Hello</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -223,6 +223,19 @@ const setZIndex = (
}
};
export const getIonPageElement = (element: HTMLElement) => {
if (element.classList.contains('ion-page')) {
return element;
}
const ionPage = element.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs');
if (ionPage) {
return ionPage;
}
// idk, return the original element so at least something animates and we don't have a null pointer
return element;
};
export interface TransitionOptions extends NavOptions {
progressCallback?: ((ani: Animation | undefined) => void);
baseEl: any;

View File

@@ -1,6 +1,6 @@
import { Animation } from '../../interface';
import { createAnimation } from '../animation/animation';
import { TransitionOptions } from '../transition';
import { TransitionOptions, getIonPageElement } from '../transition';
const DURATION = 540;
const addSafeArea = (val: number, side = 'top'): string => {
@@ -27,8 +27,9 @@ const getBackButton = (refEl: any, backDirection: boolean) => {
const activeHeader = parentHeader && !parentHeader.classList.contains('header-collapse-condense-inactive');
const backButton = buttons.querySelector('ion-back-button');
const buttonsCollapse = buttons.classList.contains('buttons-collapse');
const startSlot = buttons.slot === 'start' || buttons.slot === '';
if (backButton !== null && ((buttonsCollapse && activeHeader && backDirection) || !buttonsCollapse)) {
if (backButton !== null && startSlot && ((buttonsCollapse && activeHeader && backDirection) || !buttonsCollapse)) {
return backButton;
}
}
@@ -376,6 +377,13 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio
.beforeClearStyles([OPACITY])
.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
const leavingPage = getIonPageElement(leavingEl) as HTMLElement;
rootAnimation.afterAddWrite(() => {
if (rootAnimation.getDirection() === 'normal') {
leavingPage.style.setProperty('display', 'none');
}
});
} else {
// leaving content, forward direction
leavingContent

View File

@@ -1,6 +1,6 @@
import { Animation } from '../../interface';
import { createAnimation } from '../animation/animation';
import { TransitionOptions } from '../transition';
import { TransitionOptions, getIonPageElement } from '../transition';
export const mdTransitionAnimation = (_: HTMLElement, opts: TransitionOptions): Animation => {
const OFF_BOTTOM = '40px';
@@ -59,16 +59,3 @@ export const mdTransitionAnimation = (_: HTMLElement, opts: TransitionOptions):
return rootTransition;
};
const getIonPageElement = (element: HTMLElement) => {
if (element.classList.contains('ion-page')) {
return element;
}
const ionPage = element.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs');
if (ionPage) {
return ionPage;
}
// idk, return the original element so at least something animates and we don't have a null pointer
return element;
};

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "4.11.0",
"version": "5.0.0-beta.0",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "4.11.0",
"version": "5.0.0-beta.0",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -36,22 +36,19 @@
"dist/"
],
"dependencies": {
"tslib": "*",
"tslint": "^5.20.0",
"tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.1.0"
"tslib": "*"
},
"peerDependencies": {
"@ionic/core": "^4.10.0",
"@ionic/react": "4.10.0-rc.3",
"@ionic/core": "5.0.0-beta.0",
"@ionic/react": "5.0.0-beta.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1"
},
"devDependencies": {
"@ionic/core": "4.11.0",
"@ionic/react": "4.11.0",
"@ionic/core": "5.0.0-beta.0",
"@ionic/react": "5.0.0-beta.0",
"@types/jest": "^23.3.9",
"@types/node": "12.6.9",
"@types/react": "^16.9.2",
@@ -70,6 +67,9 @@
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.4.2",
"ts-jest": "^24.0.2",
"tslint": "^5.20.0",
"tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.1.0",
"typescript": "3.5.3"
},
"jest": {

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { HashRouter, HashRouterProps } from 'react-router-dom';
import { RouteManagerWithRouter } from './Router';
export class IonReactHashRouter extends React.Component<HashRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<HashRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</HashRouter>
);
}
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { BrowserRouter, BrowserRouterProps } from 'react-router-dom';
import { RouteManagerWithRouter } from './Router';
export class IonReactRouter extends React.Component<BrowserRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<BrowserRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</BrowserRouter>
);
}
}

View File

@@ -4,52 +4,43 @@ import { Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { generateId } from '../utils';
import { LocationHistory } from '../utils/LocationHistory';
import { StackManager } from './StackManager';
import { ViewItem } from './ViewItem';
import { ViewStack } from './ViewStacks';
interface NavManagerProps extends RouteComponentProps {
findViewInfoByLocation: (location: HistoryLocation) => { view?: ViewItem, viewStack?: ViewStack };
findViewInfoById: (id: string) => { view?: ViewItem, viewStack?: ViewStack };
getActiveIonPage: () => { view?: ViewItem, viewStack?: ViewStack };
onNavigateBack: (defaultHref?: string) => void;
onNavigate: (type: 'push' | 'replace', path: string, state?: any) => void;
}
export class NavManager extends React.Component<NavManagerProps, NavContextState> {
listenUnregisterCallback: UnregisterCallback | undefined;
locationHistory: LocationHistory = new LocationHistory();
constructor(props: NavManagerProps) {
super(props);
this.state = {
goBack: this.goBack.bind(this),
hasIonicRouter: () => true,
getHistory: this.getHistory.bind(this),
getLocation: this.getLocation.bind(this),
navigate: this.navigate.bind(this),
getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this),
currentPath: this.props.location.pathname,
registerIonPage: () => { return; } // overridden in View for each IonPage
registerIonPage: () => { return; }, // overridden in View for each IonPage
tabNavigate: this.tabNavigate.bind(this)
};
this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => {
this.setState({
currentPath: location.pathname
});
this.locationHistory.add(location);
});
this.locationHistory.add({
hash: window.location.hash,
key: generateId(),
pathname: window.location.pathname,
search: window.location.search,
state: {}
});
if (document) {
document.addEventListener('ionBackButton', (e: any) => {
e.detail.register(0, () => {
this.props.history.goBack();
});
});
}
}
componentWillUnmount() {
@@ -59,38 +50,15 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
}
goBack(defaultHref?: string) {
const { view: activeIonPage } = this.props.getActiveIonPage();
if (activeIonPage) {
const { view: enteringView } = this.props.findViewInfoById(activeIonPage.prevId!);
if (enteringView) {
const lastLocation = this.locationHistory.findLastLocation(enteringView.routeData.match.url);
if (lastLocation) {
this.props.history.replace(lastLocation.pathname + lastLocation.search, { direction: 'back' });
} else {
this.props.history.replace(enteringView.routeData.match.url, { direction: 'back' });
}
} else {
if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
}
}
} else {
if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
}
}
}
getHistory() {
return this.props.history as any;
}
getLocation() {
return this.props.location as any;
this.props.onNavigateBack(defaultHref);
}
navigate(path: string, direction?: RouterDirection | 'none') {
this.props.history.push(path, { direction });
this.props.onNavigate('push', path, direction);
}
tabNavigate(path: string) {
this.props.onNavigate('replace', path, 'back');
}
getPageManager() {

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