mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
90 Commits
v4.11.1
...
gesture-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926b119231 | ||
|
|
256c7de469 | ||
|
|
b7b5b94cce | ||
|
|
5e1e883585 | ||
|
|
21484f1f3a | ||
|
|
b28cf02ef3 | ||
|
|
bef0f53d0d | ||
|
|
1a8b7a4559 | ||
|
|
b23c759456 | ||
|
|
1ab7066aa0 | ||
|
|
48a766246d | ||
|
|
fce3e24600 | ||
|
|
be0ab5c2ff | ||
|
|
c5be8831a2 | ||
|
|
edf64ef121 | ||
|
|
504051d709 | ||
|
|
90be7ec4b6 | ||
|
|
1af1e2a14f | ||
|
|
2cc4244c05 | ||
|
|
74e40cdc35 | ||
|
|
13b323f25d | ||
|
|
3e14a57f84 | ||
|
|
9864c17c1c | ||
|
|
e2ed0e9e87 | ||
|
|
a4b2de5730 | ||
|
|
f3ac682010 | ||
|
|
2f5d823748 | ||
|
|
e5e9dd5dfe | ||
|
|
ad747b05f1 | ||
|
|
3b52074a10 | ||
|
|
9eac7266a0 | ||
|
|
12a4c974b4 | ||
|
|
a3666ddf0c | ||
|
|
cace1b357e | ||
|
|
ab9d3b2696 | ||
|
|
60aa027903 | ||
|
|
7bd4412889 | ||
|
|
96a5e600e5 | ||
|
|
33cf08bf0e | ||
|
|
7e6e81db55 | ||
|
|
f2d18a2346 | ||
|
|
77d18c7172 | ||
|
|
ec43b8c9b8 | ||
|
|
b4938db5f9 | ||
|
|
9c7beff910 | ||
|
|
16815b3cc8 | ||
|
|
e23d91c6e7 | ||
|
|
1f2b161ccd | ||
|
|
67737bbb54 | ||
|
|
29f8178794 | ||
|
|
d80f45516d | ||
|
|
420aa66392 | ||
|
|
7988720b1c | ||
|
|
67a7e232b9 | ||
|
|
7d417154c5 | ||
|
|
b642b532e8 | ||
|
|
0a7aae28a7 | ||
|
|
39fb8f6720 | ||
|
|
f63d37a4c5 | ||
|
|
9568f228b2 | ||
|
|
a2e1c55911 | ||
|
|
834666c754 | ||
|
|
5acb58a03d | ||
|
|
ce7314db0b | ||
|
|
71fdcbc1b4 | ||
|
|
4ed525dbac | ||
|
|
e6e01381eb | ||
|
|
e3233000c6 | ||
|
|
f0e4215f41 | ||
|
|
df1bc1e627 | ||
|
|
f69f9e4d54 | ||
|
|
ed6f3b9f3f | ||
|
|
2981eaa501 | ||
|
|
c1bff121d9 | ||
|
|
7fc93b61cd | ||
|
|
cbf30ea9b7 | ||
|
|
43ecf6ce5f | ||
|
|
ce0bed4cf3 | ||
|
|
b3e035085c | ||
|
|
94c13c2994 | ||
|
|
2f882373bf | ||
|
|
91008ac9a4 | ||
|
|
53010057bb | ||
|
|
48575f8163 | ||
|
|
28d21ae55f | ||
|
|
8935d4d1b6 | ||
|
|
3ba9e71a1f | ||
|
|
889f282450 | ||
|
|
b5f077b60e | ||
|
|
1cb97504d9 |
119
.github/CONTRIBUTING.md
vendored
119
.github/CONTRIBUTING.md
vendored
@@ -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
2
.github/PROCESS.md
vendored
@@ -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
7
.github/weekly-digest.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Configuration for weekly-digest - https://github.com/apps/weekly-digest
|
||||
publishDay: wed
|
||||
canPublishIssues: true
|
||||
canPublishPullRequests: true
|
||||
canPublishContributors: true
|
||||
canPublishStargazers: true
|
||||
canPublishCommits: true
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
163
CHANGELOG.md
163
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
32
angular/src/providers/animation-controller.ts
Normal file
32
angular/src/providers/animation-controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
21
angular/src/providers/gesture-controller.ts
Normal file
21
angular/src/providers/gesture-controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
14
core/api.txt
14
core/api.txt
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
1
core/src/components.d.ts
vendored
1
core/src/components.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This is an autogenerated file created by the Stencil compiler.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -197,3 +197,8 @@
|
||||
.alert-checkbox-inner {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea.alert-input {
|
||||
min-height: $alert-input-min-height;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -131,6 +131,11 @@
|
||||
placeholder: 'Placeholder 3',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
placeholder: 'Placeholder 4',
|
||||
value: 'Textarea hello'
|
||||
},
|
||||
{
|
||||
name: 'name3',
|
||||
type: 'text',
|
||||
|
||||
0
core/src/components/alert/test/preview/index.html
Normal file
0
core/src/components/alert/test/preview/index.html
Normal 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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
10
core/src/components/content/test/fixed/e2e.ts
Normal file
10
core/src/components/content/test/fixed/e2e.ts
Normal 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();
|
||||
});
|
||||
44
core/src/components/content/test/fixed/index.html
Normal file
44
core/src/components/content/test/fixed/index.html
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 `<Ionic>` 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 `<Ionic>` 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 |
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
appearance: none;
|
||||
|
||||
&::placeholder {
|
||||
@include padding(0);
|
||||
|
||||
color: var(--placeholder-color);
|
||||
|
||||
font-family: inherit;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
4
core/src/interface.d.ts
vendored
4
core/src/interface.d.ts
vendored
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
const squareA = document.querySelector('.square-a');
|
||||
|
||||
const rootAnimation = createAnimation();
|
||||
const rootAnimation = createAnimation('root-animation-id');
|
||||
|
||||
rootAnimation
|
||||
.addElement(squareA)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
91
core/src/utils/gesture/recognizers/press.ts
Normal file
91
core/src/utils/gesture/recognizers/press.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
77
core/src/utils/gesture/recognizers/tests/press/e2e.ts
Normal file
77
core/src/utils/gesture/recognizers/tests/press/e2e.ts
Normal 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))
|
||||
};
|
||||
};
|
||||
67
core/src/utils/gesture/recognizers/tests/press/index.html
Normal file
67
core/src/utils/gesture/recognizers/tests/press/index.html
Normal 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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
15
packages/react-router/src/ReactRouter/IonReactHashRouter.tsx
Normal file
15
packages/react-router/src/ReactRouter/IonReactHashRouter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
15
packages/react-router/src/ReactRouter/IonReactRouter.tsx
Normal file
15
packages/react-router/src/ReactRouter/IonReactRouter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user