Compare commits

...

89 Commits

Author SHA1 Message Date
Brandy Carney
bea21e73b6 merge release-4.11.13
4.11.13
2020-10-01 15:09:21 -04:00
Brandy Carney
e8e76324d9 4.11.13 2020-10-01 14:07:06 -04:00
Brandy Carney
210db5b265 fix(datetime): do not set ampm when the column doesn't exist (#22224) 2020-10-01 13:58:20 -04:00
Brandy Carney
13168b6d77 merge release-4.11.12
4.11.12
2020-09-29 17:15:11 -04:00
Brandy Carney
4fe3798787 4.11.12 2020-09-29 16:45:51 -04:00
Brandy Carney
0d56c51664 fix(datetime): remove the automatic switching from am to pm and vice versa (#22208)
fixes #18924
2020-09-29 16:32:41 -04:00
Brandy Carney
390ca65079 merge release-4.11.11
Release 4.11.11
2020-09-11 17:08:58 -04:00
Brandy Carney
3dbffa89b1 chore(scripts): add in v4-lts npm tag as an option to publish 2020-09-11 16:38:44 -04:00
Brandy Carney
c28c45f493 4.11.11 2020-09-11 16:18:46 -04:00
Brandy Carney
478723c55f chore(common): comment out tests due to Stencil updating the jest version and causing build errors 2020-09-11 16:12:13 -04:00
Brandy Carney
1dac5a46f5 fix(datetime): do not reset to am when changing the hour and pm is set (#22016)
fixes #19175 fixes #19260 fixes #20026 references #16630
2020-09-11 13:05:26 -04:00
Brandy Carney
b396e54d4f chore(dependencies): update dependencies, lock them, build swiper (#22015) 2020-09-01 12:55:21 -04:00
Liam DeBeasi
5bccf31bed merge release-4.11.10
Release 4.11.10
2020-01-24 16:54:08 -05:00
Liam DeBeasi
0179aad2b3 4.11.10 2020-01-24 16:15:45 -05:00
Liam DeBeasi
db1fd1d72a fix(input): revert previous type change
This reverts commit 3a56228290.
2020-01-24 16:03:03 -05:00
Ely Lucas
1bfc1c0393 release-4.11.9 2020-01-23 13:06:05 -07:00
Ely Lucas
7b9515ffa3 4.11.9 2020-01-23 13:00:15 -07:00
Ely Lucas
708020551f fix(core): updating type of input value to accept numbers, fixes #20173 (#20267) 2020-01-23 11:15:22 -07:00
Ely Lucas
63d4e877fb fix(react): remove leaving view when routerdirection is back, fixes #20124 (#20268) 2020-01-22 17:07:22 -07:00
Ely Lucas
ec6a8dd86f fix(react): adding missing overlay component events, fixes #19923 (#20266) 2020-01-22 14:52:21 -07:00
Ely Lucas
2f8c13b696 fix(react): support routes without a path for notfound routes, fixes #20259 (#20261) 2020-01-21 16:12:42 -07:00
Ely Lucas
1411d8a173 fix(react): update icon types to be a string as well, fixes #20229 (#20230) 2020-01-17 16:57:11 -07:00
Ely Lucas
9e35ebed4a fix(react): re attach props on update, fixes 20192 (#20228) 2020-01-17 16:23:26 -07:00
Ely Lucas
aff9612d11 fix(react): Don't render overlay children if isOpen is false, fixes #20225 (#20226) 2020-01-17 15:42:58 -07:00
Ely Lucas
22f92294ce chore(react): updating types for location state to fix type errors (#20206) 2020-01-14 12:17:55 -07:00
Ely Lucas
ad8f3db725 release-4.11.8 2020-01-13 09:58:01 -07:00
Ely Lucas
023b835d16 4.11.8 2020-01-13 09:52:21 -07:00
Ely Lucas
8a5aba2068 fix(react): add missing react memory router 2020-01-13 09:08:34 -07:00
Ely Lucas
676cc19b89 fix(react): supporting ios and md props on icons (#20170) 2020-01-08 16:13:52 -07:00
Ely Lucas
654d04c265 chore(docs): updating ActionSheet React sample 2019-12-17 10:52:58 -07:00
Ely Lucas
857bab6641 fix(react): fixing type of icon in ToastOptions, ActionSheetOptions, fixes #20100 2019-12-17 10:42:15 -07:00
Ely Lucas
027306a258 merge release-4.11.7 2019-12-12 15:35:01 -07:00
Ely Lucas
0f10bb29e8 4.11.7 2019-12-12 14:58:54 -07:00
Ely Lucas
4a73544502 fix(react): fire lifecycle events on initial render, fixes #20071 2019-12-12 14:31:57 -07:00
Ely Lucas
9ea75ebec7 fix(react): fire lifecycle events on initial render, fixes #20071 2019-12-12 14:30:25 -07:00
Ely Lucas
1454539cd3 chore(core): removing old test 2019-12-11 10:02:57 -07:00
Ely Lucas
6dfa0da460 merge release-4.11.6 2019-12-11 09:57:10 -07:00
Ely Lucas
78b3029665 4.11.6 2019-12-11 09:13:38 -07:00
Ely Lucas
e116712275 fix(react): support for 'root' router direction, fixes #19982 (#20052) 2019-12-09 17:23:39 -07:00
Ely Lucas
1c7d1e5cf1 fix(react): first render performance improvements 2019-12-09 14:36:47 -07:00
Ely Lucas
684293ddbf fix(react): don't show back button when not appropriate 2019-12-03 15:29:55 -07:00
Ely Lucas
f9bf8dbe6f fix(react): support navigating to same page and route updates in IonRouterOutlet, fixes #19891, #19892, #19986 2019-12-03 14:22:26 -07:00
Ely Lucas
eef55bb007 fix(react): fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes #19924 (#20012) 2019-11-27 16:08:56 -07:00
Ely Lucas
637b082976 chore(react): fix tabs docs (#19995) 2019-11-25 11:15:03 -07:00
Liam DeBeasi
6c20fecab0 merge release-4.11.5
Release 4.11.5
2019-11-14 15:15:53 -05:00
Liam DeBeasi
06c0c77673 4.11.5 2019-11-14 14:59:57 -05:00
Liam DeBeasi
dd007d55fe chore(angular): update Config deprecation message (#19908)
* update deprecation msg

* add exact release
2019-11-14 14:42:50 -05:00
Ely Lucas
5ff786a23d fix(react): improving lifecycle hooks to deal with stale closures, fixes #19873 (#19874) 2019-11-08 16:15:52 -07:00
Ely Lucas
f0efe4aed6 merge release-4.11.4 2019-11-07 14:47:51 -07:00
Ely Lucas
d8b6098e30 4.11.4 2019-11-07 14:34:46 -07:00
Aubrey Holland
7356c40174 fix(react): check for component unmount, fixes #19859 2019-11-07 13:32:45 -07:00
Ely Lucas
de0a899be7 chore(react): lowering the timeout for the ionpage check to avoid false positives 2019-11-07 13:21:23 -07:00
Ely Lucas
0a3014d35e fix(react): adding multiple subscriptions to lifecycle events, fixes #19792 (#19858) 2019-11-07 11:55:47 -07:00
Ely Lucas
628e76668e fix(react): add check to warn if no ionpage is found, fixes #19832 (#19857) 2019-11-07 10:00:17 -07:00
Ely Lucas
d89508b1b5 fix(react): expand the location stack to better support back button, fixes #19748 (#19856) 2019-11-07 09:00:01 -07:00
Ely Lucas
fd9745ddcd fix(react): adding hardware back button support, fixes(19819) (#19851) 2019-11-06 14:54:06 -07:00
Ely Lucas
bcc40c8d59 fix(react): adding swipe back functionality and routerOutlet ready improvements, fixes #19818 (#19849) 2019-11-06 10:08:57 -07:00
Ely Lucas
9fad4161be fix(react): create a new overlay each time component is presented, fixes #19841, #19823 (#19842) 2019-11-05 16:49:10 -07:00
Ely Lucas
17fe23a3b9 release-4.11.3 2019-10-30 12:23:08 -06:00
Ely Lucas
e61d7a3aeb chore(changelog): fixing changelog items 2019-10-30 12:20:01 -06:00
Ely Lucas
8d55564a0e 4.11.3 2019-10-30 12:11:29 -06:00
Ely Lucas
f74d9c6b9b chore(build): setting max workers to 2 in core tests 2019-10-30 11:17:48 -06:00
Ely Lucas
8fe8b4bbba chore(build): awaiting on test task 2019-10-30 09:50:11 -06:00
Brandy Carney
7713f7fe74 chore(build): linking react 2019-10-30 09:49:28 -06:00
Ely Lucas
9d0caf6de0 fix(react): checking if node is actually an element before treating it like one, fixes #19769 (#19783) 2019-10-29 10:36:28 -06:00
Ely Lucas
2dc5540910 fix(react): unmount leaving view when using browser back button, fixes #19749 (#19781) 2019-10-29 09:30:37 -06:00
Ely Lucas
f70e71a3d4 fix(react): checking isOpen again after async call before opening overlay, fixes #19755 2019-10-28 10:22:03 -06:00
Ely Lucas
31c754dab7 fix(react): don't remove current view, provide a better method to determine showGoBack fixes #19731 and #19732 2019-10-23 12:11:33 -06:00
Liam DeBeasi
70fefb5463 merge release-4.11.2
Release 4.11.2
2019-10-21 17:27:58 -04:00
Liam DeBeasi
8853bd5045 4.11.2 2019-10-21 15:05:43 -04:00
Brandy Carney
e63c65b644 chore(release): update release script to move the npm link 2019-10-21 13:14:22 -04:00
Liam DeBeasi
d97e167f31 fix(animations): ensure all elements are cleaned up when calling .destroy() (#19654)
* fix race condition

* remove zero
2019-10-21 12:37:19 -04:00
Liam DeBeasi
48539093bf fix(header): collapsible header works in tabs (#19658)
fixes #19640
2019-10-21 12:34:40 -04:00
Liam DeBeasi
1535e95a5f fix(menu): clamp out of bounds swipe value (#19684)
fixes #18927
2019-10-21 12:34:32 -04:00
Liam DeBeasi
70e0562dc9 fix(ios): hide leaving view after nav transition to avoid flicker (#19691)
fixes #19674
2019-10-21 12:34:26 -04:00
Ely Lucas
b7baf24e50 fix(react): adding change events to iontabs, fixes #19665 (#19711) 2019-10-21 06:29:19 -06:00
Ely Lucas
ee21d3ae43 fix(react): removing pages from DOM on nav, fixes #19701 (#19712) 2019-10-21 06:25:01 -06:00
Ely Lucas
d8ca878cb1 chore(react): removing console log and tree shaking change 2019-10-17 17:31:06 -06:00
Ely Lucas
fcdbb3ce98 fix(react): adding HashRouter to available ion routers, fixes #19621 (#19683) 2019-10-17 12:32:47 -06:00
Ely Lucas
fc6a754b38 merge r
Release 4.11.1
2019-10-14 10:41:03 -06:00
Ely Lucas
048a1a8265 4.11.1 2019-10-14 09:54:49 -06:00
Ely Lucas
ec188990d6 chore(): bumping timeout 2019-10-14 09:26:44 -06:00
Ely Lucas
81b1072322 chore(react): fixing test and another method to tab nav 2019-10-14 08:43:39 -06:00
Ely Lucas
87765564f6 fix(react): handle tab back nav better, fixes #19646 (#19647) 2019-10-13 15:20:39 -06:00
Ely Lucas
ed98d9e658 fix(react): add IonPicker as controller component, fixes #19620 (#19643) 2019-10-13 11:59:47 -06:00
Ely Lucas
b552daa6dd fix(build): properly update peer dependencies (#19639) 2019-10-11 16:57:57 -06:00
Ely Lucas
7f4b77ddba fix(react): moving tslint and friends to devDependencies (#19624) 2019-10-11 16:47:42 -06:00
Brandy Carney
c6ab439efb merge release-4.11.0
Release 4.11.0
2019-10-09 17:01:40 -04:00
Liam DeBeasi
42a2be136f merge release-4.10.3
Release 4.10.3
2019-10-09 15:14:19 -04:00
82 changed files with 11758 additions and 667 deletions

View File

@@ -1,6 +1,7 @@
const fs = require('fs-extra');
const path = require('path');
const execa = require('execa');
const inquirer = require('inquirer');
const Listr = require('listr');
const semver = require('semver');
const tc = require('turbocolor');
@@ -34,6 +35,34 @@ function projectPath(project) {
return path.join(rootDir, project);
}
async function askNpmTag(version) {
const prompts = [
{
type: 'list',
name: 'npmTag',
message: 'Select npm tag or specify a new tag',
choices: ['latest', 'next', 'v4-lts']
.concat([
new inquirer.Separator(),
{
name: 'Other (specify)',
value: null
}
])
},
{
type: 'confirm',
name: 'confirm',
message: answers => {
return `Will publish ${tc.cyan(version)} to ${tc.cyan(answers.npmTag)}. Continue?`;
}
}
];
const { npmTag, confirm } = await inquirer.prompt(prompts);
return { npmTag, confirm };
}
function checkGit(tasks) {
tasks.push(
{
@@ -127,29 +156,33 @@ 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 })
});
// Disable tests for publish, these pass locally
// projectTasks.push({
// title: `${pkg.name}: test`,
// task: async () => await execa('npm', ['test'], { cwd: projectRoot })
// });
}
projectTasks.push({
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 +219,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 +311,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 +333,7 @@ function copyCDNLoader(tasks, version) {
module.exports = {
checkTestDist,
checkGit,
askNpmTag,
isValidVersion,
isVersionGreater,
copyCDNLoader,

View File

@@ -13,6 +13,8 @@ const fs = require('fs-extra');
async function main() {
try {
const dryRun = process.argv.indexOf('--dry-run') > -1;
if (!process.env.GH_TOKEN) {
throw new Error('env.GH_TOKEN is undefined');
}
@@ -26,15 +28,31 @@ async function main() {
// repo must be clean
common.checkGit(tasks);
// publish each package in NPM
common.publishPackages(tasks, common.packages, version);
const { npmTag, confirm } = await common.askNpmTag(version);
// 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, npmTag);
// push tag to git remote
publishGit(tasks, version, changelog, npmTag);
}
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(npmTag)}\n
`);
} else {
console.log(`\nionic ${version} published to ${npmTag}!! 🎉\n`);
}
} catch (err) {
console.log('\n', tc.red(err), '\n');

View File

@@ -1,6 +1,159 @@
## [4.11.13](https://github.com/ionic-team/ionic/compare/v4.11.12...v4.11.13) (2020-10-01)
### Bug Fixes
* **datetime:** do not set ampm when the column doesn't exist ([#22224](https://github.com/ionic-team/ionic/issues/22224)) ([210db5b](https://github.com/ionic-team/ionic/commit/210db5b265c04dcdc847f173503c5c096fdb3374))
## [4.11.12](https://github.com/ionic-team/ionic/compare/v4.11.11...v4.11.12) (2020-09-29)
### Bug Fixes
* **datetime:** remove the automatic switching from am to pm and vice versa ([#22208](https://github.com/ionic-team/ionic/issues/22208)) ([0d56c51](https://github.com/ionic-team/ionic/commit/0d56c5166497de38df9be432f8c7577b4b6c0e94)), closes [#18924](https://github.com/ionic-team/ionic/issues/18924)
## [4.11.11](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.11) (2020-09-11)
### Bug Fixes
* **datetime:** do not reset to am when changing the hour and pm is set ([#22016](https://github.com/ionic-team/ionic/issues/22016)) ([1dac5a4](https://github.com/ionic-team/ionic/commit/1dac5a46f5e5adad9638e4e4e901bae1058c7287)), closes [#19175](https://github.com/ionic-team/ionic/issues/19175) [#19260](https://github.com/ionic-team/ionic/issues/19260) [#20026](https://github.com/ionic-team/ionic/issues/20026) [#16630](https://github.com/ionic-team/ionic/issues/16630)
## [4.11.10](https://github.com/ionic-team/ionic/compare/v4.11.9...v4.11.10) (2020-01-24)
### Bug Fixes
* **input:** revert previous type change ([db1fd1d](https://github.com/ionic-team/ionic/commit/db1fd1d72a8a0ade824ad2309d1adb2953731f37))
## [4.11.9](https://github.com/ionic-team/ionic/compare/v4.11.8...v4.11.9) (2020-01-23)
### Bug Fixes
* **core:** updating type of input value to accept numbers, fixes [#20173](https://github.com/ionic-team/ionic/issues/20173) ([#20267](https://github.com/ionic-team/ionic/issues/20267)) ([7080205](https://github.com/ionic-team/ionic/commit/708020551f9c51ca3b32d7b49bf4572db3dda12e))
* **react:** adding missing overlay component events, fixes [#19923](https://github.com/ionic-team/ionic/issues/19923) ([#20266](https://github.com/ionic-team/ionic/issues/20266)) ([ec6a8dd](https://github.com/ionic-team/ionic/commit/ec6a8dd86f3854edba367f79a6ebac7d60eed839))
* **react:** Don't render overlay children if isOpen is false, fixes [#20225](https://github.com/ionic-team/ionic/issues/20225) ([#20226](https://github.com/ionic-team/ionic/issues/20226)) ([aff9612](https://github.com/ionic-team/ionic/commit/aff9612d1197dca48eab6eff9d749032c380cf82))
* **react:** re attach props on update, fixes 20192 ([#20228](https://github.com/ionic-team/ionic/issues/20228)) ([9e35ebe](https://github.com/ionic-team/ionic/commit/9e35ebed4a1590ef2521f5f8c393bdd9dea32a04))
* **react:** remove leaving view when routerdirection is back, fixes [#20124](https://github.com/ionic-team/ionic/issues/20124) ([#20268](https://github.com/ionic-team/ionic/issues/20268)) ([63d4e87](https://github.com/ionic-team/ionic/commit/63d4e877fb18c90d70c4cbd5f66ffccb8ee6489c))
* **react:** support routes without a path for notfound routes, fixes [#20259](https://github.com/ionic-team/ionic/issues/20259) ([#20261](https://github.com/ionic-team/ionic/issues/20261)) ([2f8c13b](https://github.com/ionic-team/ionic/commit/2f8c13b6960f9bcfb941c36fa6e1742b96f80ba9))
* **react:** update icon types to be a string as well, fixes [#20229](https://github.com/ionic-team/ionic/issues/20229) ([#20230](https://github.com/ionic-team/ionic/issues/20230)) ([1411d8a](https://github.com/ionic-team/ionic/commit/1411d8a173bfefd7db5241218fd5641b7e9da823))
## [4.11.8](https://github.com/ionic-team/ionic/compare/v4.11.7...v4.11.8) (2020-01-13)
### Bug Fixes
* **react:** add missing react memory router ([8a5aba2](https://github.com/ionic-team/ionic/commit/8a5aba206865ce2af7f8bb85f4e7cd8dec37831d))
* **react:** fixing type of icon in ToastOptions, ActionSheetOptions, fixes [#20100](https://github.com/ionic-team/ionic/issues/20100) ([857bab6](https://github.com/ionic-team/ionic/commit/857bab66419a851c6d189cd1456cd67c1c2d934c))
* **react:** supporting ios and md props on icons ([#20170](https://github.com/ionic-team/ionic/issues/20170)) ([676cc19](https://github.com/ionic-team/ionic/commit/676cc19b89cd6374346aaac9cc3292872c7148fa))
## [4.11.7](https://github.com/ionic-team/ionic/compare/v4.11.6...v4.11.7) (2019-12-12)
### Bug Fixes
* **react:** fire lifecycle events on initial render, fixes [#20071](https://github.com/ionic-team/ionic/issues/20071) ([9ea75eb](https://github.com/ionic-team/ionic/commit/9ea75ebec7b1367fc0e319fe61c1f42516357e10))
## [4.11.6](https://github.com/ionic-team/ionic/compare/v4.11.5...v4.11.6) (2019-12-11)
### Bug Fixes
* **react:** don't show back button when not appropriate ([684293d](https://github.com/ionic-team/ionic/commit/684293ddbf1ad4edce590d56f7ff66fcd6c817a5))
* **react:** first render performance improvements ([1c7d1e5](https://github.com/ionic-team/ionic/commit/1c7d1e5cf1ad7e53ebbee2566e8fa89f567f7fb5))
* **react:** fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes [#19924](https://github.com/ionic-team/ionic/issues/19924) ([#20012](https://github.com/ionic-team/ionic/issues/20012)) ([eef55bb](https://github.com/ionic-team/ionic/commit/eef55bb0072a9e54b1fd7d1c8c69e7fd43b2a5c5))
* **react:** support for 'root' router direction, fixes [#19982](https://github.com/ionic-team/ionic/issues/19982) ([#20052](https://github.com/ionic-team/ionic/issues/20052)) ([e116712](https://github.com/ionic-team/ionic/commit/e1167122758b23221935e897bcd65839b75c59aa))
* **react:** support navigating to same page and route updates in IonRouterOutlet, fixes [#19891](https://github.com/ionic-team/ionic/issues/19891), [#19892](https://github.com/ionic-team/ionic/issues/19892), [#19986](https://github.com/ionic-team/ionic/issues/19986) ([f9bf8db](https://github.com/ionic-team/ionic/commit/f9bf8dbe6f952ee53b6b213a4c0d043d25f49b93))
### Upgrade Note
If you run into a "Property 'translate' is missing in type" error building after updating to 4.11.6, update your React Typings library to the latest:
npm i @types/react@latest @types/react-dom@latest
## [4.11.5](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.5) (2019-11-14)
### Bug Fixes
* **react:** improved lifecycle hooks to deal with stale closures, fixes [#19873](https://github.com/ionic-team/ionic/issues/19873) ([#19874](https://github.com/ionic-team/ionic/issues/19874)) ([5ff786a](https://github.com/ionic-team/ionic/commit/5ff786a23d5aa32281bbf5daaa7f8156de39caca))
## [4.11.4](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.4) (2019-11-07)
### Bug Fixes
* **react:** check for component unmount, fixes [#19859](https://github.com/ionic-team/ionic/issues/19859) ([7356c40](https://github.com/ionic-team/ionic/commit/7356c401742ce2b3241d6ab05fce0fa65d2f1f8a))
* **react:** adding multiple subscriptions to lifecycle events, fixes [#19792](https://github.com/ionic-team/ionic/issues/19792) ([#19858](https://github.com/ionic-team/ionic/issues/19858)) ([0a3014d](https://github.com/ionic-team/ionic/commit/0a3014d35e2102570fd3d8c5ada29eb01aab18e9))
* **react:** add check to warn if no ionpage is found, fixes [#19832](https://github.com/ionic-team/ionic/issues/19832) ([#19857](https://github.com/ionic-team/ionic/issues/19857)) ([628e766](https://github.com/ionic-team/ionic/commit/628e76668ea72baebdb02b9dcfe24c0da837fb08))
* **react:** expand the location stack to better support back button, fixes [#19748](https://github.com/ionic-team/ionic/issues/19748) ([#19856](https://github.com/ionic-team/ionic/issues/19856)) ([d89508b](https://github.com/ionic-team/ionic/commit/d89508b1b58481d518b89362a8792d05f3f451c9))
* **react:** adding hardware back button support, fixes(19819) ([#19851](https://github.com/ionic-team/ionic/issues/19851)) ([fd9745d](https://github.com/ionic-team/ionic/commit/fd9745ddcddded76d64220838aef0f599bf4352f))
* **react:** adding swipe back functionality and routerOutlet ready improvements, fixes [#19818](https://github.com/ionic-team/ionic/issues/19818) ([#19849](https://github.com/ionic-team/ionic/issues/19849)) ([bcc40c8](https://github.com/ionic-team/ionic/commit/bcc40c8d59b723bbdb1dfd318bfb2219eb8df3cf))
* **react:** create a new overlay each time component is presented, fixes [#19841](https://github.com/ionic-team/ionic/issues/19841), [#19823](https://github.com/ionic-team/ionic/issues/19823) ([#19842](https://github.com/ionic-team/ionic/issues/19842)) ([9fad416](https://github.com/ionic-team/ionic/commit/9fad4161be4859969e14d4d33169ef022052d6bf))
## [4.11.3](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.3) (2019-10-30)
### Bug Fixes
* **react:** adding change events to iontabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24e5053a379156e6c3d82c2b5d3afa999f1))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3ce98747d3b37107904ca110daad95e48bc))
* **react:** checking if node is actually an element before treating it like one, fixes [#19769](https://github.com/ionic-team/ionic/issues/19769) ([#19783](https://github.com/ionic-team/ionic/issues/19783)) ([9d0caf6](https://github.com/ionic-team/ionic/commit/9d0caf6de070145c4af618847b27e24c49027b8e))
* **react:** checking isOpen again after async call before opening overlay, fixes [#19755](https://github.com/ionic-team/ionic/issues/19755) ([f70e71a](https://github.com/ionic-team/ionic/commit/f70e71a3d461cdab65626a5a7e1b6f4d03b852b1))
* **react:** don't remove current view, provide a better method to determine showGoBack fixes [#19731](https://github.com/ionic-team/ionic/issues/19731) and [#19732](https://github.com/ionic-team/ionic/issues/19732) ([31c754d](https://github.com/ionic-team/ionic/commit/31c754dab7ada494ff5f0026d5cf3f7f65198eff))
* **react:** removing pages from DOM on nav, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3ae43d8c6b076387a58bca655a56c920bcd))
* **react:** unmount leaving view when using browser back button, fixes [#19749](https://github.com/ionic-team/ionic/issues/19749) ([#19781](https://github.com/ionic-team/ionic/issues/19781)) ([2dc5540](https://github.com/ionic-team/ionic/commit/2dc554091056612f1bcd2751d6eeb41cae488751))
## [4.11.2](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.2) (2019-10-21)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([d97e167](https://github.com/ionic-team/ionic/commit/d97e167))
* **header:** collapsible header works in tabs ([#19658](https://github.com/ionic-team/ionic/issues/19658)) ([4853909](https://github.com/ionic-team/ionic/commit/4853909)), closes [#19640](https://github.com/ionic-team/ionic/issues/19640)
* **ios:** hide leaving view after nav transition to avoid flicker ([#19691](https://github.com/ionic-team/ionic/issues/19691)) ([70e0562](https://github.com/ionic-team/ionic/commit/70e0562)), closes [#19674](https://github.com/ionic-team/ionic/issues/19674)
* **menu:** clamp out of bounds swipe value ([#19684](https://github.com/ionic-team/ionic/issues/19684)) ([1535e95](https://github.com/ionic-team/ionic/commit/1535e95)), closes [#18927](https://github.com/ionic-team/ionic/issues/18927)
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** adding change events to IonTabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3c))
* **react:** pages no longer hidden when navigating between tabs, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** ensure views are removed from DOM after navigating back, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3a))
## [4.11.1](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.1) (2019-10-14)
### Bug Fixes
* **build:** properly update peer dependencies ([#19639](https://github.com/ionic-team/ionic/issues/19639)) ([b552daa](https://github.com/ionic-team/ionic/commit/b552daa))
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** handle tab back nav better, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** moving tslint and friends to devDependencies ([#19624](https://github.com/ionic-team/ionic/issues/19624)) ([7f4b77d](https://github.com/ionic-team/ionic/commit/7f4b77d))
# [4.11.0 Sodium](https://github.com/ionic-team/ionic/compare/v4.10.3...v4.11.0) (2019-10-09)
Ionic React! Enjoy! 🧂 🌊 🐼
Ionic React! Enjoy! 🧂 🌊 🐼
## [4.10.3](https://github.com/ionic-team/ionic/compare/v4.10.2...v4.10.3) (2019-10-09)

View File

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

View File

@@ -33,7 +33,7 @@ export class Config {
}
set(key: keyof IonicConfig, value?: any) {
console.warn(`[DEPRECATION][Config]: The Config.set() method is deprecated and will be removed in the next major release.`);
console.warn(`[DEPRECATION][Config]: The Config.set() method is deprecated and will be removed in Ionic Framework 6.0. Please see https://ionicframework.com/docs/angular/config for alternatives.`);
const c = getConfig();
if (c) {
c.set(key, value);

View File

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

View File

@@ -1 +0,0 @@
package-lock=false

View File

@@ -254,7 +254,7 @@ rules:
- visibility
- z-index
property-blacklist:
property-disallowed-list:
- background-position
- right
- left

10028
core/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "4.11.0",
"version": "4.11.13",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -36,25 +36,25 @@
"devDependencies": {
"@stencil/core": "1.6.1",
"@stencil/sass": "1.0.1",
"@types/jest": "24.0.17",
"@types/jest": "^26.0.10",
"@types/node": "12.7.1",
"@types/puppeteer": "1.19.1",
"@types/swiper": "4.4.4",
"aws-sdk": "^2.497.0",
"aws-sdk": "^2.743.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",
"np": "^5.0.3",
"jest": "^26.4.2",
"jest-cli": "^26.4.2",
"np": "^6.5.0",
"pixelmatch": "4.0.2",
"puppeteer": "1.20.0",
"rollup": "1.19.4",
"rollup-plugin-node-resolve": "5.2.0",
"rollup-plugin-virtual": "^1.0.1",
"sass": "^1.22.9",
"stylelint": "10.1.0",
"stylelint-order": "3.0.1",
"stylelint": "^13.7.0",
"stylelint-order": "^4.1.0",
"swiper": "4.5.1",
"tslint": "^5.10.0",
"tslint-ionic-rules": "0.0.21",
@@ -80,7 +80,7 @@
"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": "stencil test --spec --e2e --max-workers=2",
"test.spec": "stencil test --spec",
"test.spec.debug": "npx --node-arg=\"--inspect-brk\" stencil test --spec",
"test.e2e": "stencil test --e2e",

View File

@@ -64,7 +64,6 @@
}
:host(.chip-outline) {
border-color: rgba(0, 0, 0, .32);
background: transparent;

View File

@@ -122,24 +122,24 @@
.transition-effect {
position: absolute;
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
left: -100%;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
width: 100%;
height: 100%;
opacity: 0;
pointer-events: none;
}
.transition-cover {
position: absolute;
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: 0;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
width: 100%;
height: 100%;
@@ -153,9 +153,9 @@
display: block;
position: absolute;
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: 0;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
width: 10px;
height: 100%;

View File

@@ -220,6 +220,7 @@ describe('datetime-util', () => {
"second": undefined,
"tzOffset": 0,
"year": 1000,
"ampm": undefined
});
});
@@ -234,6 +235,7 @@ describe('datetime-util', () => {
"second": undefined,
"tzOffset": 0,
"year": undefined,
"ampm": undefined
});
});
@@ -248,6 +250,7 @@ describe('datetime-util', () => {
"second": 20,
"tzOffset": 0,
"year": 1994,
"ampm": undefined
});
});
@@ -262,6 +265,7 @@ describe('datetime-util', () => {
"second": undefined,
"tzOffset": 0,
"year": 2018,
"ampm": undefined
});
});

View File

@@ -3,10 +3,16 @@
* Defaults to the current date if
* no date given
*/
export const getDateValue = (date: DatetimeData, format: string): number => {
export const getDateValue = (date: DatetimeData, format: string): number | string => {
const getValue = getValueFromFormat(date, format);
if (getValue !== undefined) { return getValue; }
if (getValue !== undefined) {
if (format === FORMAT_A || format === FORMAT_a) {
date.ampm = getValue;
}
return getValue;
}
const defaultDate = parseDate(new Date().toISOString());
return getValueFromFormat((defaultDate as DatetimeData), format);
@@ -308,11 +314,15 @@ export const updateDate = (existingData: DatetimeData, newData: any): boolean =>
}
} else if ((newData.year || newData.hour || newData.month || newData.day || newData.minute || newData.second)) {
// newData is from of a datetime picker's selected values
// update the existing DatetimeData data with the new values
// do some magic for 12-hour values
if (newData.ampm && newData.hour) {
// newData is from the datetime picker's selected values
// update the existing datetimeValue with the new values
if (newData.ampm !== undefined && newData.hour !== undefined) {
// change the value of the hour based on whether or not it is am or pm
// if the meridiem is pm and equal to 12, it remains 12
// otherwise we add 12 to the hour value
// if the meridiem is am and equal to 12, we change it to 0
// otherwise we use its current hour value
// for example: 8 pm becomes 20, 12 am becomes 0, 4 am becomes 4
newData.hour.value = (newData.ampm.value === 'pm')
? (newData.hour.value === 12 ? 12 : newData.hour.value + 12)
: (newData.hour.value === 12 ? 0 : newData.hour.value);
@@ -335,7 +345,8 @@ export const updateDate = (existingData: DatetimeData, newData: any): boolean =>
? (existingData.hour! < 12 ? existingData.hour! + 12 : existingData.hour!)
: (existingData.hour! >= 12 ? existingData.hour! - 12 : existingData.hour))
};
(existingData as any)['hour'] = newData['hour'].value;
existingData['hour'] = newData['hour'].value;
existingData['ampm'] = newData['ampm'].value;
return true;
}
@@ -537,6 +548,7 @@ export interface DatetimeData {
second?: number;
millisecond?: number;
tzOffset?: number;
ampm?: string;
}
export interface LocaleData {

View File

@@ -270,6 +270,12 @@ export class Datetime implements ComponentInterface {
value: colOptions[colSelectedIndex].value
};
if (data.name !== 'ampm' && this.datetimeValue.ampm !== undefined) {
changeData['ampm'] = {
value: this.datetimeValue.ampm
};
}
this.updateDatetimeValue(changeData);
picker.columns = this.generateColumns();
});

View File

@@ -31,12 +31,12 @@
<ion-label>Default</ion-label>
<ion-datetime></ion-datetime>
</ion-item>
<ion-item>
<ion-label position="floating">Default with floating label</ion-label>
<ion-datetime></ion-datetime>
</ion-item>
<ion-item>
<ion-label position="floating">Placeholder with floating label</ion-label>
<ion-datetime placeholder="Select a date"></ion-datetime>
@@ -105,8 +105,8 @@
</ion-item>
<ion-item>
<ion-label>HH:mm</ion-label>
<ion-datetime display-format="HH:mm"></ion-datetime>
<ion-label>HH:mm A</ion-label>
<ion-datetime display-format="HH:mm A"></ion-datetime>
</ion-item>
<ion-item>
@@ -119,11 +119,25 @@
<ion-datetime display-format="h:mm a" value="01:47"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>h:mm A</ion-label>
<ion-datetime display-format="h:mm A" value="14:23"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>hh:mm A (15 min steps)</ion-label>
<ion-datetime display-format="h:mm A" minute-values="0,15,30,45"></ion-datetime>
</ion-item>
<ion-item>
<ion-label>YYYY MMM DD hh:mm A</ion-label>
<ion-datetime
id="todaysDate"
display-format="YYYY MMM DD hh:mm A"
minute-values="00,05,10,15,20,25,30,35,40,45,50,55">
</ion-datetime>
</ion-item>
<ion-item>
<ion-label>Leap years, summer months</ion-label>
<ion-datetime id="customYearValues" display-format="MM/YYYY" pickerFormat="MMMM YYYY" month-values="6,7,8"></ion-datetime>
@@ -164,6 +178,9 @@
</style>
<script>
var todaysDateDatetime = document.querySelector('#todaysDate');
todaysDateDatetime.value = '2020-09-02T17:15:03.488Z';
var yearValuesArray = [2020, 2016, 2008, 2004, 2000, 1996];
var customYearValues = document.getElementById('customYearValues');
customYearValues.yearValues = yearValuesArray;

View File

@@ -30,6 +30,38 @@ describe('Datetime', () => {
expect(monthvalue).toEqual(date.getMonth() + 1);
expect(yearValue).toEqual(date.getFullYear());
});
it('it should return the date value for a given time', () => {
const dateTimeData: DatetimeData = {
hour: 2,
minute: 23,
tzOffset: 0
};
const hourValue = getDateValue(dateTimeData, 'hh');
const minuteValue = getDateValue(dateTimeData, 'mm');
const ampmValue = getDateValue(dateTimeData, 'A');
expect(hourValue).toEqual(2);
expect(minuteValue).toEqual(23);
expect(ampmValue).toEqual("am");
});
it('it should return the date value for a given time after 12', () => {
const dateTimeData: DatetimeData = {
hour: 16,
minute: 47,
tzOffset: 0
};
const hourValue = getDateValue(dateTimeData, 'hh');
const minuteValue = getDateValue(dateTimeData, 'mm');
const ampmValue = getDateValue(dateTimeData, 'a');
expect(hourValue).toEqual(4);
expect(minuteValue).toEqual(47);
expect(ampmValue).toEqual("pm");
});
});
describe('getLocalDateTime()', () => {

View File

@@ -57,14 +57,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);
@@ -101,21 +97,17 @@ export class Header implements ComponentInterface {
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);
});
@@ -124,7 +116,7 @@ export class Header implements ComponentInterface {
* showing/hiding border on last toolbar
* in primary header
*/
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, mainHeaderIndex, scrollHeaderIndex, remainingHeight); };
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, scrollHeaderIndex); };
this.scrollEl.addEventListener('scroll', this.contentScrollCallback);
});

View File

@@ -49,20 +49,13 @@ export const createHeaderIndex = (headerEl: HTMLElement | undefined): HeaderInde
} 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);
});
});
};
@@ -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,6 +117,7 @@ export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex,
if (hasValidIntersection) {
setHeaderActive(mainHeaderIndex);
setHeaderActive(scrollHeaderIndex, false);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], 1);
}
}
});
@@ -126,7 +130,6 @@ export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
} else {
headerIndex.el.classList.add('header-collapse-condense-inactive');
}
setToolbarBackgroundOpacity(headerIndex.toolbars[0], (active) ? undefined : 0);
});
};

View File

@@ -5,10 +5,10 @@
ion-item-options {
@include multi-dir() {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
top: 0;
right: 0;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
}
@include ltr() {
@@ -19,10 +19,10 @@ ion-item-options {
justify-content: flex-start;
&:not(.item-options-end) {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: auto;
left: 0;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
justify-content: flex-end;
}
@@ -41,10 +41,10 @@ ion-item-options {
.item-options-start {
@include multi-dir() {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: auto;
left: 0;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
}
@include ltr() {

View File

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

View File

@@ -63,7 +63,7 @@
--ion-safe-area-right: 0px;
@include multi-dir() {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
right: auto;
left: 0;
}
@@ -75,7 +75,7 @@
@include multi-dir() {
right: 0;
left: auto;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
}
}

View File

@@ -5,7 +5,7 @@ import { getIonMode } from '../../global/ionic-global';
import { Gesture, GestureDetail, IonicAnimation, MenuChangeEventDetail, MenuI, Side } from '../../interface';
import { Point, 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';
@Component({
@@ -441,7 +441,7 @@ 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(new Point(0, 0), new Point(0.4, 0), new Point(0.6, 1), new Point(1, 1), clamp(0, adjustedStepValue, 1));
this.animation
.easing('cubic-bezier(0.4, 0.0, 0.6, 1)')

View File

@@ -55,7 +55,7 @@
.progress,
.progress-buffer-bar {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
transform-origin: left top;
transition: transform 150ms linear;
@@ -89,12 +89,12 @@
// --------------------------------------------------
.indeterminate-bar-primary {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
top: 0;
right: 0;
bottom: 0;
left: -145.166611%;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
animation: primary-indeterminate-translate 2s infinite linear;
@@ -105,12 +105,12 @@
}
.indeterminate-bar-secondary {
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
top: 0;
right: 0;
bottom: 0;
left: -54.888891%;
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */
animation: secondary-indeterminate-translate 2s infinite linear;
@@ -138,7 +138,7 @@
:host(.progress-bar-reversed) {
.progress,
.progress-buffer-bar {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
transform-origin: right top;
}

View File

@@ -105,12 +105,12 @@
@include margin-horizontal(-13px, null);
@include multi-dir() {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
border-radius: 50% 50% 50% 0;
}
@include rtl() {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
left: unset;
}

View File

@@ -82,7 +82,7 @@
);
@include rtl() {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
left: unset;
}
@@ -104,7 +104,7 @@
@include position(calc((var(--height) - var(--bar-height)) / 2), null, null, 0);
@include rtl() {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
left: unset;
}
@@ -127,7 +127,7 @@
);
@include rtl() {
/* stylelint-disable-next-line property-blacklist */
/* stylelint-disable-next-line property-disallowed-list */
left: unset;
}

View File

@@ -17,6 +17,7 @@
cursor: grab;
pointer-events: all;
touch-action: none;
}

View File

@@ -56,7 +56,7 @@ span {
animation-timing-function: linear;
}
/* stylelint-disable property-blacklist */
/* stylelint-disable property-disallowed-list */
@keyframes shimmer {
0% {
background-position: -468px 0
@@ -66,4 +66,4 @@ span {
background-position: 468px 0
}
}
/* stylelint-enable property-blacklist */
/* stylelint-enable property-disallowed-list */

View File

@@ -1,88 +1,145 @@
/**
* SSR Window 1.0.1
* SSR Window 2.0.0
* Better handling for window object in SSR environment
* https://github.com/nolimits4web/ssr-window
*
* Copyright 2018, Vladimir Kharlampidi
* Copyright 2020, Vladimir Kharlampidi
*
* Licensed under MIT
*
* Released on: July 18, 2018
* Released on: May 12, 2020
*/
var doc = (typeof document === 'undefined') ? {
body: {},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {},
activeElement: {
blur: function blur() {},
nodeName: '',
},
querySelector: function querySelector() {
return null;
},
querySelectorAll: function querySelectorAll() {
return [];
},
getElementById: function getElementById() {
return null;
},
createEvent: function createEvent() {
return {
initEvent: function initEvent() {},
};
},
createElement: function createElement() {
return {
children: [],
childNodes: [],
style: {},
setAttribute: function setAttribute() {},
getElementsByTagName: function getElementsByTagName() {
return [];
},
};
},
location: { hash: '' },
} : document; // eslint-disable-line
/* eslint-disable no-param-reassign */
function isObject(obj) {
return (obj !== null &&
typeof obj === 'object' &&
'constructor' in obj &&
obj.constructor === Object);
}
function extend(target, src) {
if (target === void 0) { target = {}; }
if (src === void 0) { src = {}; }
Object.keys(src).forEach(function (key) {
if (typeof target[key] === 'undefined')
target[key] = src[key];
else if (isObject(src[key]) &&
isObject(target[key]) &&
Object.keys(src[key]).length > 0) {
extend(target[key], src[key]);
}
});
}
var win = (typeof window === 'undefined') ? {
document: doc,
navigator: {
userAgent: '',
},
location: {},
history: {},
CustomEvent: function CustomEvent() {
return this;
},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {},
getComputedStyle: function getComputedStyle() {
return {
getPropertyValue: function getPropertyValue() {
return '';
},
};
},
Image: function Image() {},
Date: function Date() {},
screen: {},
setTimeout: function setTimeout() {},
clearTimeout: function clearTimeout() {},
} : window; // eslint-disable-line
var doc = typeof document !== 'undefined' ? document : {};
var ssrDocument = {
body: {},
addEventListener: function () { },
removeEventListener: function () { },
activeElement: {
blur: function () { },
nodeName: '',
},
querySelector: function () {
return null;
},
querySelectorAll: function () {
return [];
},
getElementById: function () {
return null;
},
createEvent: function () {
return {
initEvent: function () { },
};
},
createElement: function () {
return {
children: [],
childNodes: [],
style: {},
setAttribute: function () { },
getElementsByTagName: function () {
return [];
},
};
},
createElementNS: function () {
return {};
},
importNode: function () {
return null;
},
location: {
hash: '',
host: '',
hostname: '',
href: '',
origin: '',
pathname: '',
protocol: '',
search: '',
},
};
extend(doc, ssrDocument);
var win = typeof window !== 'undefined' ? window : {};
var ssrWindow = {
document: ssrDocument,
navigator: {
userAgent: '',
},
location: {
hash: '',
host: '',
hostname: '',
href: '',
origin: '',
pathname: '',
protocol: '',
search: '',
},
history: {
replaceState: function () { },
pushState: function () { },
go: function () { },
back: function () { },
},
CustomEvent: function CustomEvent() {
return this;
},
addEventListener: function () { },
removeEventListener: function () { },
getComputedStyle: function () {
return {
getPropertyValue: function () {
return '';
},
};
},
Image: function () { },
Date: function () { },
screen: {},
setTimeout: function () { },
clearTimeout: function () { },
matchMedia: function () {
return {};
},
};
extend(win, ssrWindow);
/**
* Dom7 2.1.3
* Dom7 2.1.5
* Minimalistic JavaScript library for DOM manipulation, with a jQuery-compatible API
* http://framework7.io/docs/dom.html
*
* Copyright 2019, Vladimir Kharlampidi
* Copyright 2020, Vladimir Kharlampidi
* The iDangero.us
* http://www.idangero.us/
*
* Licensed under MIT
*
* Released on: February 11, 2019
* Released on: May 15, 2020
*/
class Dom7 {
@@ -764,6 +821,79 @@ function add(...args) {
return dom;
}
/**
* SSR Window 1.0.1
* Better handling for window object in SSR environment
* https://github.com/nolimits4web/ssr-window
*
* Copyright 2018, Vladimir Kharlampidi
*
* Licensed under MIT
*
* Released on: July 18, 2018
*/
var doc$1 = (typeof document === 'undefined') ? {
body: {},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {},
activeElement: {
blur: function blur() {},
nodeName: '',
},
querySelector: function querySelector() {
return null;
},
querySelectorAll: function querySelectorAll() {
return [];
},
getElementById: function getElementById() {
return null;
},
createEvent: function createEvent() {
return {
initEvent: function initEvent() {},
};
},
createElement: function createElement() {
return {
children: [],
childNodes: [],
style: {},
setAttribute: function setAttribute() {},
getElementsByTagName: function getElementsByTagName() {
return [];
},
};
},
location: { hash: '' },
} : document; // eslint-disable-line
var win$1 = (typeof window === 'undefined') ? {
document: doc$1,
navigator: {
userAgent: '',
},
location: {},
history: {},
CustomEvent: function CustomEvent() {
return this;
},
addEventListener: function addEventListener() {},
removeEventListener: function removeEventListener() {},
getComputedStyle: function getComputedStyle() {
return {
getPropertyValue: function getPropertyValue() {
return '';
},
};
},
Image: function Image() {},
Date: function Date() {},
screen: {},
setTimeout: function setTimeout() {},
clearTimeout: function clearTimeout() {},
} : window; // eslint-disable-line
/**
* Swiper 4.5.1
* Most modern mobile touch slider and framework with hardware accelerated transitions
@@ -847,16 +977,16 @@ const Utils = {
let curTransform;
let transformMatrix;
const curStyle = win.getComputedStyle(el, null);
const curStyle = win$1.getComputedStyle(el, null);
if (win.WebKitCSSMatrix) {
if (win$1.WebKitCSSMatrix) {
curTransform = curStyle.transform || curStyle.webkitTransform;
if (curTransform.split(',').length > 6) {
curTransform = curTransform.split(', ').map((a) => a.replace(',', '.')).join(', ');
}
// Some old versions of Webkit choke when 'none' is passed; pass
// empty string instead in this case
transformMatrix = new win.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
transformMatrix = new win$1.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
} else {
transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
matrix = transformMatrix.toString().split(',');
@@ -864,7 +994,7 @@ const Utils = {
if (axis === 'x') {
// Latest Chrome and webkits Fix
if (win.WebKitCSSMatrix) curTransform = transformMatrix.m41;
if (win$1.WebKitCSSMatrix) curTransform = transformMatrix.m41;
// Crazy IE10 Matrix
else if (matrix.length === 16) curTransform = parseFloat(matrix[12]);
// Normal Browsers
@@ -872,7 +1002,7 @@ const Utils = {
}
if (axis === 'y') {
// Latest Chrome and webkits Fix
if (win.WebKitCSSMatrix) curTransform = transformMatrix.m42;
if (win$1.WebKitCSSMatrix) curTransform = transformMatrix.m42;
// Crazy IE10 Matrix
else if (matrix.length === 16) curTransform = parseFloat(matrix[13]);
// Normal Browsers
@@ -882,7 +1012,7 @@ const Utils = {
},
parseUrlQuery(url) {
const query = {};
let urlToParse = url || win.location.href;
let urlToParse = url || win$1.location.href;
let i;
let params;
let param;
@@ -929,20 +1059,20 @@ const Utils = {
};
const Support = (function Support() {
const testDiv = doc.createElement('div');
const testDiv = doc$1.createElement('div');
return {
touch: (win.Modernizr && win.Modernizr.touch === true) || (function checkTouch() {
return !!((win.navigator.maxTouchPoints > 0) || ('ontouchstart' in win) || (win.DocumentTouch && doc instanceof win.DocumentTouch));
touch: (win$1.Modernizr && win$1.Modernizr.touch === true) || (function checkTouch() {
return !!((win$1.navigator.maxTouchPoints > 0) || ('ontouchstart' in win$1) || (win$1.DocumentTouch && doc$1 instanceof win$1.DocumentTouch));
}()),
pointerEvents: !!(win.navigator.pointerEnabled || win.PointerEvent || ('maxTouchPoints' in win.navigator && win.navigator.maxTouchPoints > 0)),
prefixedPointerEvents: !!win.navigator.msPointerEnabled,
pointerEvents: !!(win$1.navigator.pointerEnabled || win$1.PointerEvent || ('maxTouchPoints' in win$1.navigator && win$1.navigator.maxTouchPoints > 0)),
prefixedPointerEvents: !!win$1.navigator.msPointerEnabled,
transition: (function checkTransition() {
const style = testDiv.style;
return ('transition' in style || 'webkitTransition' in style || 'MozTransition' in style);
}()),
transforms3d: (win.Modernizr && win.Modernizr.csstransforms3d === true) || (function checkTransforms3d() {
transforms3d: (win$1.Modernizr && win$1.Modernizr.csstransforms3d === true) || (function checkTransforms3d() {
const style = testDiv.style;
return ('webkitPerspective' in style || 'MozPerspective' in style || 'OPerspective' in style || 'MsPerspective' in style || 'perspective' in style);
}()),
@@ -957,7 +1087,7 @@ const Support = (function Support() {
}()),
observer: (function checkObserver() {
return ('MutationObserver' in win || 'WebkitMutationObserver' in win);
return ('MutationObserver' in win$1 || 'WebkitMutationObserver' in win$1);
}()),
passiveListener: (function checkPassiveListener() {
@@ -969,7 +1099,7 @@ const Support = (function Support() {
supportsPassive = true;
},
});
win.addEventListener('testPassiveListener', null, opts);
win$1.addEventListener('testPassiveListener', null, opts);
} catch (e) {
// No support
}
@@ -977,21 +1107,21 @@ const Support = (function Support() {
}()),
gestures: (function checkGestures() {
return 'ongesturestart' in win;
return 'ongesturestart' in win$1;
}()),
};
}());
const Browser = (function Browser() {
function isSafari() {
const ua = win.navigator.userAgent.toLowerCase();
const ua = win$1.navigator.userAgent.toLowerCase();
return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0);
}
return {
isIE: !!win.navigator.userAgent.match(/Trident/g) || !!win.navigator.userAgent.match(/MSIE/g),
isEdge: !!win.navigator.userAgent.match(/Edge/g),
isIE: !!win$1.navigator.userAgent.match(/Trident/g) || !!win$1.navigator.userAgent.match(/MSIE/g),
isEdge: !!win$1.navigator.userAgent.match(/Edge/g),
isSafari: isSafari(),
isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(win.navigator.userAgent),
isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(win$1.navigator.userAgent),
};
}());
@@ -1308,7 +1438,7 @@ function updateSlides () {
if (slide.css('display') === 'none') continue; // eslint-disable-line
if (params.slidesPerView === 'auto') {
const slideStyles = win.getComputedStyle(slide[0], null);
const slideStyles = win$1.getComputedStyle(slide[0], null);
const currentTransform = slide[0].style.transform;
const currentWebKitTransform = slide[0].style.webkitTransform;
if (currentTransform) {
@@ -2169,7 +2299,7 @@ function loopCreate () {
const blankSlidesNum = params.slidesPerGroup - (slides.length % params.slidesPerGroup);
if (blankSlidesNum !== params.slidesPerGroup) {
for (let i = 0; i < blankSlidesNum; i += 1) {
const blankNode = $(doc.createElement('div')).addClass(`${params.slideClass} ${params.slideBlankClass}`);
const blankNode = $(doc$1.createElement('div')).addClass(`${params.slideClass} ${params.slideBlankClass}`);
$wrapperEl.append(blankNode);
}
slides = $wrapperEl.children(`.${params.slideClass}`);
@@ -2427,7 +2557,7 @@ var manipulation = {
};
const Device = (function Device() {
const ua = win.navigator.userAgent;
const ua = win$1.navigator.userAgent;
const device = {
ios: false,
@@ -2438,8 +2568,8 @@ const Device = (function Device() {
iphone: false,
ipod: false,
ipad: false,
cordova: win.cordova || win.phonegap,
phonegap: win.cordova || win.phonegap,
cordova: win$1.cordova || win$1.phonegap,
phonegap: win$1.cordova || win$1.phonegap,
};
const windows = ua.match(/(Windows Phone);?[\s\/]+([\d.]+)?/); // eslint-disable-line
@@ -2495,7 +2625,7 @@ const Device = (function Device() {
// Minimal UI
if (device.os && device.os === 'ios') {
const osVersionArr = device.osVersion.split('.');
const metaViewport = doc.querySelector('meta[name="viewport"]');
const metaViewport = doc$1.querySelector('meta[name="viewport"]');
device.minimalUi = !device.webView
&& (ipod || iphone)
&& (osVersionArr[0] * 1 === 7 ? osVersionArr[1] * 1 >= 1 : osVersionArr[0] * 1 > 7)
@@ -2503,7 +2633,7 @@ const Device = (function Device() {
}
// Pixel Ratio
device.pixelRatio = win.devicePixelRatio || 1;
device.pixelRatio = win$1.devicePixelRatio || 1;
// Export object
return device;
@@ -2542,7 +2672,7 @@ function onTouchStart (event) {
if (
edgeSwipeDetection
&& ((startX <= edgeSwipeThreshold)
|| (startX >= win.screen.width - edgeSwipeThreshold))
|| (startX >= win$1.screen.width - edgeSwipeThreshold))
) {
return;
}
@@ -2566,11 +2696,11 @@ function onTouchStart (event) {
let preventDefault = true;
if ($(e.target).is(data.formElements)) preventDefault = false;
if (
doc.activeElement
&& $(doc.activeElement).is(data.formElements)
&& doc.activeElement !== e.target
doc$1.activeElement
&& $(doc$1.activeElement).is(data.formElements)
&& doc$1.activeElement !== e.target
) {
doc.activeElement.blur();
doc$1.activeElement.blur();
}
const shouldPreventDefault = preventDefault && swiper.allowTouchMove && params.touchStartPreventDefault;
@@ -2633,8 +2763,8 @@ function onTouchMove (event) {
return;
}
}
if (data.isTouchEvent && doc.activeElement) {
if (e.target === doc.activeElement && $(e.target).is(data.formElements)) {
if (data.isTouchEvent && doc$1.activeElement) {
if (e.target === doc$1.activeElement && $(e.target).is(data.formElements)) {
data.isMoved = true;
swiper.allowClick = false;
return;
@@ -3133,8 +3263,8 @@ function attachEvents() {
{
if (!Support.touch && (Support.pointerEvents || Support.prefixedPointerEvents)) {
target.addEventListener(touchEvents.start, swiper.onTouchStart, false);
doc.addEventListener(touchEvents.move, swiper.onTouchMove, capture);
doc.addEventListener(touchEvents.end, swiper.onTouchEnd, false);
doc$1.addEventListener(touchEvents.move, swiper.onTouchMove, capture);
doc$1.addEventListener(touchEvents.end, swiper.onTouchEnd, false);
} else {
if (Support.touch) {
const passiveListener = touchEvents.start === 'touchstart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
@@ -3144,8 +3274,8 @@ function attachEvents() {
}
if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
target.addEventListener('mousedown', swiper.onTouchStart, false);
doc.addEventListener('mousemove', swiper.onTouchMove, capture);
doc.addEventListener('mouseup', swiper.onTouchEnd, false);
doc$1.addEventListener('mousemove', swiper.onTouchMove, capture);
doc$1.addEventListener('mouseup', swiper.onTouchEnd, false);
}
}
// Prevent Links Clicks
@@ -3172,8 +3302,8 @@ function detachEvents() {
{
if (!Support.touch && (Support.pointerEvents || Support.prefixedPointerEvents)) {
target.removeEventListener(touchEvents.start, swiper.onTouchStart, false);
doc.removeEventListener(touchEvents.move, swiper.onTouchMove, capture);
doc.removeEventListener(touchEvents.end, swiper.onTouchEnd, false);
doc$1.removeEventListener(touchEvents.move, swiper.onTouchMove, capture);
doc$1.removeEventListener(touchEvents.end, swiper.onTouchEnd, false);
} else {
if (Support.touch) {
const passiveListener = touchEvents.start === 'onTouchStart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
@@ -3183,8 +3313,8 @@ function detachEvents() {
}
if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
target.removeEventListener('mousedown', swiper.onTouchStart, false);
doc.removeEventListener('mousemove', swiper.onTouchMove, capture);
doc.removeEventListener('mouseup', swiper.onTouchEnd, false);
doc$1.removeEventListener('mousemove', swiper.onTouchMove, capture);
doc$1.removeEventListener('mouseup', swiper.onTouchEnd, false);
}
}
// Prevent Links Clicks
@@ -3271,10 +3401,10 @@ function getBreakpoint (breakpoints) {
for (let i = 0; i < points.length; i += 1) {
const point = points[i];
if (swiper.params.breakpointsInverse) {
if (point <= win.innerWidth) {
if (point <= win$1.innerWidth) {
breakpoint = point;
}
} else if (point >= win.innerWidth && !breakpoint) {
} else if (point >= win$1.innerWidth && !breakpoint) {
breakpoint = point;
}
}
@@ -3342,7 +3472,7 @@ function loadImage (imageEl, src, srcset, sizes, checkForComplete, callback) {
}
if (!imageEl.complete || !checkForComplete) {
if (src) {
image = new win.Image();
image = new win$1.Image();
image.onload = onReady;
image.onerror = onReady;
if (sizes) {
@@ -4064,21 +4194,21 @@ var Resize = {
init() {
const swiper = this;
// Emit resize
win.addEventListener('resize', swiper.resize.resizeHandler);
win$1.addEventListener('resize', swiper.resize.resizeHandler);
// Emit orientationchange
win.addEventListener('orientationchange', swiper.resize.orientationChangeHandler);
win$1.addEventListener('orientationchange', swiper.resize.orientationChangeHandler);
},
destroy() {
const swiper = this;
win.removeEventListener('resize', swiper.resize.resizeHandler);
win.removeEventListener('orientationchange', swiper.resize.orientationChangeHandler);
win$1.removeEventListener('resize', swiper.resize.resizeHandler);
win$1.removeEventListener('orientationchange', swiper.resize.orientationChangeHandler);
},
},
};
const Observer = {
func: win.MutationObserver || win.WebkitMutationObserver,
func: win$1.MutationObserver || win$1.WebkitMutationObserver,
attach(target, options = {}) {
const swiper = this;
@@ -4095,10 +4225,10 @@ const Observer = {
swiper.emit('observerUpdate', mutations[0]);
};
if (win.requestAnimationFrame) {
win.requestAnimationFrame(observerUpdate);
if (win$1.requestAnimationFrame) {
win$1.requestAnimationFrame(observerUpdate);
} else {
win.setTimeout(observerUpdate, 0);
win$1.setTimeout(observerUpdate, 0);
}
});
@@ -4166,23 +4296,23 @@ var Observer$1 = {
function isEventSupported() {
const eventName = 'onwheel';
let isSupported = eventName in doc;
let isSupported = eventName in doc$1;
if (!isSupported) {
const element = doc.createElement('div');
const element = doc$1.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof element[eventName] === 'function';
}
if (!isSupported
&& doc.implementation
&& doc.implementation.hasFeature
&& doc$1.implementation
&& doc$1.implementation.hasFeature
// always returns true in newer browsers as per the standard.
// @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
&& doc.implementation.hasFeature('', '') !== true
&& doc$1.implementation.hasFeature('', '') !== true
) {
// This is the only way to test support for the `wheel` event in IE9+.
isSupported = doc.implementation.hasFeature('Events.wheel', '3.0');
isSupported = doc$1.implementation.hasFeature('Events.wheel', '3.0');
}
return isSupported;
@@ -4190,7 +4320,7 @@ function isEventSupported() {
const Mousewheel = {
lastScrollTime: Utils.now(),
event: (function getEvent() {
if (win.navigator.userAgent.indexOf('firefox') > -1) return 'DOMMouseScroll';
if (win$1.navigator.userAgent.indexOf('firefox') > -1) return 'DOMMouseScroll';
return isEventSupported() ? 'wheel' : 'mousewheel';
}()),
normalize(e) {
@@ -4306,7 +4436,7 @@ const Mousewheel = {
swiper.emit('scroll', e);
} else if (params.releaseOnEdges) return true;
}
swiper.mousewheel.lastScrollTime = (new win.Date()).getTime();
swiper.mousewheel.lastScrollTime = (new win$1.Date()).getTime();
} else {
// Freemode or scrollContainer:
if (swiper.params.loop) {
@@ -4912,8 +5042,8 @@ const Scrollbar = {
const passiveListener = Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
if (!Support.touch) {
target.addEventListener(touchEventsDesktop.start, swiper.scrollbar.onDragStart, activeListener);
doc.addEventListener(touchEventsDesktop.move, swiper.scrollbar.onDragMove, activeListener);
doc.addEventListener(touchEventsDesktop.end, swiper.scrollbar.onDragEnd, passiveListener);
doc$1.addEventListener(touchEventsDesktop.move, swiper.scrollbar.onDragMove, activeListener);
doc$1.addEventListener(touchEventsDesktop.end, swiper.scrollbar.onDragEnd, passiveListener);
} else {
target.addEventListener(touchEventsTouch.start, swiper.scrollbar.onDragStart, activeListener);
target.addEventListener(touchEventsTouch.move, swiper.scrollbar.onDragMove, activeListener);
@@ -4932,8 +5062,8 @@ const Scrollbar = {
const passiveListener = Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
if (!Support.touch) {
target.removeEventListener(touchEventsDesktop.start, swiper.scrollbar.onDragStart, activeListener);
doc.removeEventListener(touchEventsDesktop.move, swiper.scrollbar.onDragMove, activeListener);
doc.removeEventListener(touchEventsDesktop.end, swiper.scrollbar.onDragEnd, passiveListener);
doc$1.removeEventListener(touchEventsDesktop.move, swiper.scrollbar.onDragMove, activeListener);
doc$1.removeEventListener(touchEventsDesktop.end, swiper.scrollbar.onDragEnd, passiveListener);
} else {
target.removeEventListener(touchEventsTouch.start, swiper.scrollbar.onDragStart, activeListener);
target.removeEventListener(touchEventsTouch.move, swiper.scrollbar.onDragMove, activeListener);

View File

@@ -68,6 +68,7 @@
box-shadow: $toggle-ios-handle-box-shadow;
will-change: transform;
contain: strict;
}

View File

@@ -69,6 +69,7 @@
box-shadow: $toggle-md-handle-box-shadow;
will-change: transform, background-color;
contain: strict;
}

View File

@@ -16,7 +16,7 @@ ion-virtual-scroll > .virtual-loading {
}
ion-virtual-scroll > .virtual-item {
/* stylelint-disable declaration-no-important, property-blacklist */
/* stylelint-disable declaration-no-important, property-disallowed-list */
position: absolute !important;
top: 0 !important;

View File

@@ -128,8 +128,10 @@ export const createAnimation = (): Animation => {
webAnimations.length = 0;
} else {
elements.forEach(element => {
raf(() => {
const elementsArray = elements.slice();
raf(() => {
elementsArray.forEach(element => {
removeStyleProperty(element, 'animation-name');
removeStyleProperty(element, 'animation-duration');
removeStyleProperty(element, 'animation-timing-function');

View File

@@ -17,6 +17,9 @@ 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 NaN.
*/
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);

View File

@@ -243,6 +243,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: IonicAnimation | Animation | undefined) => void);
baseEl: any;

View File

@@ -1,6 +1,6 @@
import { IonicAnimation } 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 => {
@@ -376,6 +376,13 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio
.beforeClearStyles([OPACITY])
.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)'));
const leavingPage = getIonPageElement(leavingEl) as HTMLElement;
rootAnimation.afterAddWrite(() => {
if (rootAnimation.getDirection() === 'normal') {
leavingPage.style.setProperty('display', 'none');
}
});
} else {
// leaving content, forward direction
leavingContent

View File

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

View File

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

1
packages/react-router/empty-module.js vendored Normal file
View File

@@ -0,0 +1 @@
module.exports = ''

View File

@@ -1,7 +0,0 @@
window.matchMedia = window.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
};
};

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "4.11.0",
"version": "4.11.13",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -36,54 +36,64 @@
"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": "4.11.13",
"@ionic/react": "4.11.13",
"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",
"@types/jest": "^23.3.9",
"@types/node": "12.6.9",
"@ionic/core": "4.11.13",
"@ionic/react": "4.11.13",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.23",
"@types/node": "^12.12.14",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@types/react-router": "^5.0.3",
"@types/react-router-dom": "^4.3.1",
"jest": "^24.8.0",
"jest-dom": "^3.4.0",
"jest": "^24.9.0",
"jest-dom": "^4.0.0",
"np": "^5.0.1",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react-testing-library": "^7.0.0",
"rollup": "^1.18.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.4.2",
"ts-jest": "^24.0.2",
"typescript": "3.5.3"
"tslint": "^5.20.0",
"tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.1.0",
"typescript": "^3.7.2"
},
"jest": {
"preset": "ts-jest",
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.js"
"<rootDir>/setupTests.ts"
],
"testPathIgnorePatterns": [
"node_modules",
"dist-transpiled",
"dist"
],
"globals": {
"ts-jest": {
"diagnostics": false
}
},
"modulePaths": [
"<rootDir>"
]
],
"moduleNameMapper": {
"\\.(css|jpg|png|svg)$": "<rootDir>/empty-module.js"
}
}
}

View File

@@ -0,0 +1,9 @@
import '@testing-library/jest-dom/extend-expect';
window.matchMedia = window.matchMedia || function() {
return {
matches: false,
addListener() { },
removeListener() { }
};
} as any;

View File

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

View File

@@ -0,0 +1,21 @@
import { MemoryHistory } from 'history';
import React from 'react';
import { MemoryRouter, MemoryRouterProps, matchPath } from 'react-router';
import { LocationState, RouteManager } from './Router';
interface IonReactMemoryRouterProps extends MemoryRouterProps {
history: MemoryHistory<LocationState>;
}
export class IonReactMemoryRouter extends React.Component<IonReactMemoryRouterProps> {
render() {
const { children, history, ...props } = this.props;
const match = matchPath(history.location.pathname, this.props);
return (
<MemoryRouter {...props}>
<RouteManager history={history} location={history.location} match={match!}>{children}</RouteManager>
</MemoryRouter>
);
}
}

View File

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

View File

@@ -0,0 +1 @@
export type IonRouteAction = 'push' | 'replace' | 'pop';

View File

@@ -1,6 +1,6 @@
import { RouteProps, match } from 'react-router-dom';
export interface IonRouteData {
match: match<{ tab: string }> | null;
match: match | null;
childProps: RouteProps;
}

View File

@@ -4,31 +4,23 @@ 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 { IonRouteAction } from './IonRouteAction';
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: (ionRouteAction: IonRouteAction, 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),
@@ -40,16 +32,15 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
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,11 @@ 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' });
}
}
this.props.onNavigateBack(defaultHref);
}
getHistory() {
return this.props.history as any;
}
getLocation() {
return this.props.location as any;
}
navigate(path: string, direction?: RouterDirection | 'none') {
this.props.history.push(path, { direction });
navigate(path: string, direction?: RouterDirection | 'none', ionRouteAction: IonRouteAction = 'push') {
this.props.onNavigate(ionRouteAction, path, direction);
}
getPageManager() {

View File

@@ -1,24 +1,25 @@
import { NavDirection } from '@ionic/core';
import React, { ReactNode } from 'react';
import { ViewStacks } from './ViewStacks';
export interface RouteManagerContextState {
syncView: (page: HTMLElement, viewId: string) => void;
syncRoute: (id: string, route: any) => void;
hideView: (viewId: string) => void;
viewStacks: ViewStacks;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => Promise<void>;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void;
removeViewStack: (stack: string) => void;
transitionView: (enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction: NavDirection) => void;
getRoute: (id: string) => any;
}
export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManagerContextState>({
viewStacks: new ViewStacks(),
syncView: () => { navContextNotFoundError(); },
syncRoute: () => { navContextNotFoundError(); },
hideView: () => { navContextNotFoundError(); },
setupIonRouter: () => Promise.reject(navContextNotFoundError()),
removeViewStack: () => { navContextNotFoundError(); },
transitionView: () => { navContextNotFoundError(); }
getRoute: () => { navContextNotFoundError(); }
});
function navContextNotFoundError() {

View File

@@ -1,109 +1,176 @@
import { NavDirection } from '@ionic/core';
import { RouterDirection } from '@ionic/react';
import { RouterDirection, getConfig } from '@ionic/react';
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react';
import { BrowserRouter, BrowserRouterProps, RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { generateId } from '../utils';
import { generateId, isDevMode } from '../utils';
import { LocationHistory } from '../utils/LocationHistory';
import { IonRouteAction } from './IonRouteAction';
import { IonRouteData } from './IonRouteData';
import { NavManager } from './NavManager';
import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext';
import { ViewItem } from './ViewItem';
import { ViewStack, ViewStacks } from './ViewStacks';
interface RouteManagerState extends RouteManagerContextState {
location?: HistoryLocation;
action?: HistoryAction;
export interface LocationState {
direction?: RouterDirection;
action?: IonRouteAction;
}
class RouteManager extends React.Component<RouteComponentProps, RouteManagerState> {
interface RouteManagerProps extends RouteComponentProps<{}, {}, LocationState> {
location: HistoryLocation<LocationState>;
}
interface RouteManagerState extends RouteManagerContextState {
location?: HistoryLocation<LocationState>;
action?: IonRouteAction;
}
export class RouteManager extends React.Component<RouteManagerProps, RouteManagerState> {
listenUnregisterCallback: UnregisterCallback | undefined;
activeIonPageId?: string;
currentIonRouteAction?: IonRouteAction;
currentRouteDirection?: RouterDirection;
locationHistory = new LocationHistory();
routes: { [key: string]: React.ReactElement<any>; } = {};
ionPageElements: { [key: string]: HTMLElement; } = {};
routerOutlets: { [key: string]: HTMLIonRouterOutletElement; } = {};
firstRender = true;
constructor(props: RouteComponentProps) {
constructor(props: RouteManagerProps) {
super(props);
this.listenUnregisterCallback = this.props.history.listen(this.historyChange.bind(this));
this.handleNavigate = this.handleNavigate.bind(this);
this.navigateBack = this.navigateBack.bind(this);
this.state = {
viewStacks: new ViewStacks(),
hideView: this.hideView.bind(this),
setupIonRouter: this.setupIonRouter.bind(this),
removeViewStack: this.removeViewStack.bind(this),
syncView: this.syncView.bind(this),
transitionView: this.transitionView.bind(this),
syncRoute: this.syncRoute.bind(this),
getRoute: this.getRoute.bind(this)
};
this.locationHistory.add({
hash: window.location.hash,
key: generateId(),
pathname: window.location.pathname,
search: window.location.search,
state: {}
});
}
componentDidUpdate(_prevProps: RouteComponentProps, prevState: RouteManagerState) {
// Trigger a page change if the location or action is different
if (this.state.location && prevState.location !== this.state.location || prevState.action !== this.state.action) {
this.setActiveView(this.state.location!, this.state.action!);
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
this.setActiveView(this.state.location!, this.state.action!, viewStacks);
}
}
componentWillUnmount() {
if (this.listenUnregisterCallback) {
this.listenUnregisterCallback();
}
}
getRoute(id: string) {
return this.routes[id];
}
hideView(viewId: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId);
if (view) {
view.show = false;
view.ionPageElement = undefined;
view.isIonRoute = false;
view.prevId = undefined;
view.key = generateId();
delete this.ionPageElements[view.id];
this.setState({
viewStacks
});
}
}
historyChange(location: HistoryLocation, action: HistoryAction) {
historyChange(location: HistoryLocation<LocationState>, action: HistoryAction) {
const ionRouteAction = this.currentIonRouteAction === 'pop' ? 'pop' : action.toLowerCase() as IonRouteAction;
let direction = this.currentRouteDirection;
if (ionRouteAction === 'push') {
this.locationHistory.add(location);
} else if (ionRouteAction === 'pop') {
this.locationHistory.pop();
direction = direction || 'back';
} else if (ionRouteAction === 'replace') {
this.locationHistory.replace(location);
direction = 'none';
}
if (direction === 'root') {
this.locationHistory.clear();
this.locationHistory.add(location);
}
location.state = location.state || { direction };
this.setState({
location,
action
action: ionRouteAction as IonRouteAction
});
this.currentRouteDirection = undefined;
this.currentIonRouteAction = undefined;
}
setActiveView(location: HistoryLocation, action: HistoryAction) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
let direction: RouterDirection = location.state && location.state.direction || 'forward';
setActiveView(location: HistoryLocation<LocationState>, action: IonRouteAction, viewStacks: ViewStacks) {
let direction: RouterDirection | undefined = (location.state && location.state.direction) || 'forward';
let leavingView: ViewItem | undefined;
const viewStackKeys = viewStacks.getKeys();
let shouldTransitionPage = false;
let leavingViewHtml: string | undefined;
viewStackKeys.forEach(key => {
const { view: enteringView, viewStack: enteringViewStack, match } = viewStacks.findViewInfoByLocation(location, key);
if (!enteringView || !enteringViewStack) {
return;
}
leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view;
if (enteringView.isIonRoute) {
enteringView.show = true;
enteringView.mount = true;
enteringView.routeData.match = match!;
shouldTransitionPage = true;
this.activeIonPageId = enteringView.id;
if (leavingView) {
if (direction === 'forward') {
if (action === 'PUSH') {
/**
* If the page is being pushed into the stack by another view,
* record the view that originally directed to the new view for back button purposes.
*/
enteringView.prevId = enteringView.prevId || leavingView.id;
} else {
direction = direction || 'back';
leavingView.mount = false;
}
} else if (action === 'REPLACE') {
if (action === 'push' && direction === 'forward') {
/**
* If the page is being pushed into the stack by another view,
* record the view that originally directed to the new view for back button purposes.
*/
enteringView.prevId = leavingView.id;
} else {
leavingView.mount = false;
this.removeOrphanedViews(enteringView, enteringViewStack);
}
leavingViewHtml = enteringView.id === leavingView.id ? this.ionPageElements[leavingView.id].outerHTML : undefined;
} else {
// If there is not a leavingView, then we shouldn't provide a direction
direction = undefined;
}
} else {
enteringView.show = true;
enteringView.mount = true;
enteringView.routeData.match = match!;
}
});
if (leavingView) {
@@ -116,47 +183,102 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
this.setState({
viewStacks
}, () => {
const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (enteringView && viewStack) {
const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined;
const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined;
if (shouldTransitionPage) {
const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (enteringView && viewStack) {
const enteringEl = this.ionPageElements[enteringView.id];
const leavingEl = leavingView && this.ionPageElements[leavingView.id];
if (enteringEl) {
let navDirection: NavDirection | undefined;
if (leavingEl && leavingEl.innerHTML === '') {
// Don't animate from an empty view
navDirection = undefined;
} else if (direction === 'none' || direction === 'root') {
navDirection = undefined;
} else {
navDirection = direction;
}
const shouldGoBack = !!enteringView.prevId;
const routerOutlet = this.routerOutlets[viewStack.id];
this.commitView(
enteringEl!,
leavingEl!,
routerOutlet,
navDirection,
shouldGoBack,
leavingViewHtml);
} else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
}
}
if (enteringEl) {
// Don't animate from an empty view
const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction;
this.transitionView(
enteringEl!,
leavingEl!,
viewStack.routerOutlet,
navDirection);
} else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
// Warn if an IonPage was not eventually rendered in Dev Mode
if (isDevMode()) {
if (enteringView && enteringView.routeData.match!.url !== location.pathname) {
setTimeout(() => {
const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (view!.routeData.match!.url !== location.pathname) {
console.warn('No IonPage was found to render. Make sure you wrap your page with an IonPage component.');
}
}, 100);
}
}
}
});
}
componentWillUnmount() {
if (this.listenUnregisterCallback) {
this.listenUnregisterCallback();
}
removeOrphanedViews(view: ViewItem, viewStack: ViewStack) {
// Note: This technique is a bit wonky for views that reference each other and get into a circular loop.
// It can still remove a view that probably shouldn't be.
const viewsToRemove = viewStack.views.filter(v => v.prevId === view.id);
viewsToRemove.forEach(v => {
// Don't remove if view is currently active
if (v.id !== this.activeIonPageId) {
this.removeOrphanedViews(v, viewStack);
// If view is not currently visible, go ahead and remove it from DOM
const page = this.ionPageElements[v.id];
if (page.classList.contains('ion-page-hidden')) {
v.show = false;
v.isIonRoute = false;
v.prevId = undefined;
v.key = generateId();
delete this.ionPageElements[v.id];
}
v.mount = false;
}
});
}
async setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
const views: ViewItem[] = [];
let activeId: string | undefined;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
let foundMatch = false;
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
views.push(createViewItem(child, this.props.history.location));
const routeId = generateId();
this.routes[routeId] = child;
views.push(createViewItem(child, routeId, this.props.history.location));
});
await this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
if (!foundMatch) {
const notFoundRoute = views.find(r => {
// try to find a route that doesn't have a path or from prop, that will be our not found route
return !r.routeData.childProps.path && !r.routeData.childProps.from;
});
if (notFoundRoute) {
notFoundRoute.show = true;
}
}
function createViewItem(child: React.ReactElement<any>, location: HistoryLocation) {
this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
function createViewItem(child: React.ReactElement<any>, routeId: string, location: HistoryLocation) {
const viewId = generateId();
const key = generateId();
const route = child;
// const route = child;
const matchProps = {
exact: child.props.exact,
path: child.props.path || child.props.from,
@@ -170,7 +292,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
match,
childProps: child.props
},
route,
routeId,
mount: true,
show: !!match,
isIonRoute: false
@@ -178,33 +300,56 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
if (match && view.isIonRoute) {
activeId = viewId;
}
if (!foundMatch && match) {
foundMatch = true;
}
return view;
}
}
async registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) {
return new Promise(resolve => {
this.setState(prevState => {
const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = {
id: stack,
views: stackItems,
routerOutlet
};
if (activeId) {
this.activeIonPageId = activeId;
}
prevViewStacks.set(stack, newStack);
return {
viewStacks: prevViewStacks
};
}, () => {
resolve();
});
registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) {
this.setState(prevState => {
const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = {
id: stack,
views: stackItems
};
this.routerOutlets[stack] = routerOutlet;
if (activeId) {
this.activeIonPageId = activeId;
}
prevViewStacks.set(stack, newStack);
return {
viewStacks: prevViewStacks
};
}, () => {
this.setupRouterOutlet(routerOutlet);
});
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
if (swipeEnabled) {
const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
return !!(view && view.prevId);
} else {
return false;
}
};
const onStart = () => {
this.navigateBack();
};
routerOutlet.swipeHandler = {
canStart,
onStart,
onEnd: _shouldContinue => true
};
}
removeViewStack(stack: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
viewStacks.delete(stack);
@@ -214,55 +359,124 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
syncView(page: HTMLElement, viewId: string) {
this.setState(state => {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId);
if (view) {
view.isIonRoute = true;
this.ionPageElements[view.id] = page;
this.setActiveView(this.state.location || this.props.location, this.state.action!, viewStacks);
}
}
const viewStacks = Object.assign(new ViewStacks(), state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId);
syncRoute(_id: string, routerOutlet: any) {
const ionRouterOutlet = React.Children.only(routerOutlet) as React.ReactElement;
view!.ionPageElement = page;
view!.isIonRoute = true;
return {
viewStacks
};
}, () => {
this.setActiveView(this.state.location || this.props.location, this.state.action!);
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
for (const routeKey in this.routes) {
const route = this.routes[routeKey];
if (typeof route.props.path !== 'undefined' && route.props.path === (child.props.path || child.props.from)) {
this.routes[routeKey] = child;
}
}
});
}
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet?: HTMLIonRouterOutletElement, direction?: NavDirection) {
/**
* Super hacky workaround to make sure ionRouterOutlet is available
* since transitionView might be called before IonRouterOutlet is fully mounted
*/
if (ionRouterOutlet && ionRouterOutlet.componentOnReady) {
this.commitView(enteringEl, leavingEl, ionRouterOutlet, direction);
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean, leavingViewHtml?: string) {
if (!this.firstRender) {
if (!('componentOnReady' in ionRouterOutlet)) {
await waitUntilRouterOutletReady(ionRouterOutlet);
}
if ((enteringEl === leavingEl) && direction && leavingViewHtml) {
// If a page is transitioning to another version of itself
// we clone it so we can have an animation to show
const newLeavingElement = clonePageElement(leavingViewHtml);
ionRouterOutlet.appendChild(newLeavingElement);
await ionRouterOutlet.commit(enteringEl, newLeavingElement, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation: false
});
ionRouterOutlet.removeChild(newLeavingElement);
} else {
await ionRouterOutlet.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation: false
});
}
if (leavingEl && (enteringEl !== leavingEl)) {
/** add hidden attributes */
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
}
} else {
setTimeout(() => {
this.transitionView(enteringEl, leavingEl, ionRouterOutlet, direction);
}, 10);
enteringEl.classList.remove('ion-page-invisible');
enteringEl.style.zIndex = '101';
enteringEl.dispatchEvent(new Event('ionViewWillEnter'));
enteringEl.dispatchEvent(new Event('ionViewDidEnter'));
this.firstRender = false;
}
}
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection) {
if (enteringEl === leavingEl) {
return;
handleNavigate(ionRouteAction: IonRouteAction, path: string, direction?: RouterDirection) {
this.currentIonRouteAction = ionRouteAction;
switch (ionRouteAction) {
case 'push':
this.currentRouteDirection = direction;
this.props.history.push(path);
break;
case 'pop':
this.currentRouteDirection = direction || 'back';
this.props.history.replace(path);
break;
case 'replace':
this.currentRouteDirection = 'none';
this.props.history.replace(path);
break;
}
}
await ionRouterOuter.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack: direction === 'forward',
progressAnimation: false
});
if (leavingEl && (enteringEl !== leavingEl)) {
/** add hidden attributes */
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
navigateBack(defaultHref?: string) {
const { view: leavingView } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (leavingView) {
if (leavingView.id === leavingView.prevId) {
const previousLocation = this.locationHistory.previous();
if (previousLocation) {
this.handleNavigate('pop', previousLocation.pathname + previousLocation.search);
} else {
defaultHref && this.handleNavigate('pop', defaultHref);
}
} else {
const { view: enteringView } = this.state.viewStacks.findViewInfoById(leavingView.prevId);
if (enteringView) {
const lastLocation = this.locationHistory.findLastLocationByUrl(enteringView.routeData.match!.url);
if (lastLocation) {
this.handleNavigate('pop', lastLocation.pathname + lastLocation.search);
} else {
this.handleNavigate('pop', enteringView.routeData.match!.url);
}
} else {
const currentLocation = this.locationHistory.previous();
if (currentLocation) {
this.handleNavigate('pop', currentLocation.pathname + currentLocation.search);
} else {
if (defaultHref) {
this.handleNavigate('pop', defaultHref);
}
}
}
}
} else {
if (defaultHref) {
this.handleNavigate('replace', defaultHref, 'back');
}
}
}
@@ -271,9 +485,8 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
<RouteManagerContext.Provider value={this.state}>
<NavManager
{...this.props}
findViewInfoById={(id: string) => this.state.viewStacks.findViewInfoById(id)}
findViewInfoByLocation={(location: HistoryLocation) => this.state.viewStacks.findViewInfoByLocation(location)}
getActiveIonPage={() => this.state.viewStacks.findViewInfoById(this.activeIonPageId)}
onNavigateBack={this.navigateBack}
onNavigate={this.handleNavigate}
>
{this.props.children}
</NavManager>
@@ -282,16 +495,28 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
const RouteManagerWithRouter = withRouter(RouteManager);
RouteManagerWithRouter.displayName = 'RouteManager';
function clonePageElement(leavingViewHtml: string) {
const newEl = document.createElement('div');
newEl.innerHTML = leavingViewHtml;
newEl.classList.add('ion-page-hidden');
newEl.style.zIndex = '';
// Remove an existing back button so the new element doesn't get two of them
const ionBackButton = newEl.getElementsByTagName('ion-back-button');
if (ionBackButton[0]) {
ionBackButton[0].innerHTML = '';
}
return newEl.firstChild as HTMLElement;
}
export class IonReactRouter extends React.Component<BrowserRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<BrowserRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</BrowserRouter>
);
async function waitUntilRouterOutletReady(ionRouterOutlet: HTMLIonRouterElement) {
if ('componentOnReady' in ionRouterOutlet) {
return;
} else {
setTimeout(() => {
waitUntilRouterOutletReady(ionRouterOutlet);
}, 0);
}
}
export const RouteManagerWithRouter = withRouter(RouteManager);
RouteManagerWithRouter.displayName = 'RouteManager';

View File

@@ -2,18 +2,21 @@ import React from 'react';
import { generateId, isDevMode } from '../utils';
import { RouteManagerContext } from './RouteManagerContext';
import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext';
import { View } from './View';
import { ViewItem } from './ViewItem';
import { ViewTransitionManager } from './ViewTransitionManager';
interface StackManagerProps {
id?: string;
routeManager: RouteManagerContextState;
children?: React.ReactNode;
}
export class StackManager extends React.Component<StackManagerProps, {}> {
interface StackManagerState { }
class StackManagerInner extends React.Component<StackManagerProps, StackManagerState> {
routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
context!: React.ContextType<typeof RouteManagerContext>;
id: string;
constructor(props: StackManagerProps) {
@@ -21,38 +24,44 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
this.id = this.props.id || generateId();
this.handleViewSync = this.handleViewSync.bind(this);
this.handleHideView = this.handleHideView.bind(this);
this.state = {};
}
componentDidMount() {
this.context.setupIonRouter(this.id, this.props.children, this.routerOutletEl.current!);
this.props.routeManager.setupIonRouter(this.id, this.props.children, this.routerOutletEl.current!);
}
static getDerivedStateFromProps(props: StackManagerProps, state: StackManagerState) {
props.routeManager.syncRoute('', props.children);
return state;
}
componentWillUnmount() {
this.context.removeViewStack(this.id);
this.props.routeManager.removeViewStack(this.id);
}
handleViewSync(page: HTMLElement, viewId: string) {
this.context.syncView(page, viewId);
this.props.routeManager.syncView(page, viewId);
}
handleHideView(viewId: string) {
this.context.hideView(viewId);
this.props.routeManager.hideView(viewId);
}
renderChild(item: ViewItem) {
const component = React.cloneElement(item.route, {
renderChild(item: ViewItem, route: any) {
const component = React.cloneElement(route, {
computedMatch: item.routeData.match
});
return component;
}
render() {
const context = this.context;
const viewStack = context.viewStacks.get(this.id);
const routeManager = this.props.routeManager;
const viewStack = routeManager.viewStacks.get(this.id);
const views = (viewStack || { views: [] }).views.filter(x => x.show);
const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement;
const childElements = views.map(view => {
const route = routeManager.getRoute(view.routeId);
return (
<ViewTransitionManager
id={view.id}
@@ -63,8 +72,9 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
onViewSync={this.handleViewSync}
onHideView={this.handleHideView}
view={view}
route={route}
>
{this.renderChild(view)}
{this.renderChild(view, route)}
</View>
</ViewTransitionManager>
);
@@ -74,6 +84,10 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
ref: this.routerOutletEl
};
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = this.routerOutletEl;
}
if (isDevMode()) {
elementProps['data-stack-id'] = this.id;
}
@@ -82,8 +96,14 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
return routerOutletChild;
}
static get contextType() {
return RouteManagerContext;
}
}
const withContext = (Component: any) => {
return (props: any) => (
<RouteManagerContext.Consumer>
{context => <Component {...props} routeManager={context} />}
</RouteManagerContext.Consumer>
);
};
export const StackManager = withContext(StackManagerInner);

View File

@@ -1,6 +1,5 @@
import { IonLifeCycleContext, NavContext } from '@ionic/react';
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { isDevMode } from '../utils';
@@ -10,6 +9,7 @@ interface ViewProps extends React.HTMLAttributes<HTMLElement> {
onViewSync: (page: HTMLElement, viewId: string) => void;
onHideView: (viewId: string) => void;
view: ViewItem;
route: any;
}
/**
@@ -19,20 +19,6 @@ export class View extends React.Component<ViewProps, {}> {
context!: React.ContextType<typeof IonLifeCycleContext>;
ionPage?: HTMLElement;
componentDidMount() {
/**
* If we can tell if view is a redirect, hide it so it will work again in future
*/
const { view } = this.props;
if (view.route.type === Redirect) {
this.props.onHideView(view.id);
} else if (view.route.type === Route && view.route.props.render) {
if (view.route.props.render().type === Redirect) {
this.props.onHideView(view.id);
}
}
}
componentWillUnmount() {
if (this.ionPage) {
this.ionPage.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
@@ -79,6 +65,7 @@ export class View extends React.Component<ViewProps, {}> {
...value,
registerIonPage: this.registerIonPage.bind(this)
};
return (
<NavContext.Provider value={newProvider}>
{this.props.children}

View File

@@ -3,10 +3,9 @@ export interface ViewItem<RouteData = any> {
id: string;
/** The key used by React. A new key is generated each time the view comes into the DOM so React thinks its a completely new element. */
key: string;
/** The <Route /> or <Redirect /> component associated with the view */
route: React.ReactElement<any>;
/** The reference to the <IonPage /> element. */
ionPageElement?: HTMLElement;
routeId: string;
/** The routeData for the view. */
routeData: RouteData;
/** Used to track which page pushed the page into view. Used for back button purposes. */
@@ -23,4 +22,9 @@ export interface ViewItem<RouteData = any> {
* An IonRoute is a Route that contains an IonPage. Only IonPages participate in transition and lifecycle events.
*/
isIonRoute: boolean;
/**
* location of the view
*/
location?: string;
}

View File

@@ -6,7 +6,6 @@ import { ViewItem } from './ViewItem';
export interface ViewStack {
id: string;
routerOutlet: HTMLIonRouterOutletElement;
views: ViewItem[];
}
@@ -14,7 +13,7 @@ export interface ViewStack {
* The holistic view of all the Routes configured for an application inside of an IonRouterOutlet.
*/
export class ViewStacks {
private viewStacks: { [key: string]: ViewStack | undefined } = {};
private viewStacks: { [key: string]: ViewStack | undefined; } = {};
get(key: string) {
return this.viewStacks[key];
@@ -32,25 +31,34 @@ export class ViewStacks {
delete this.viewStacks[key];
}
findViewInfoByLocation(location: HistoryLocation, viewKey?: string) {
findViewInfoByLocation(location: HistoryLocation, viewKey: string) {
let view: ViewItem<IonRouteData> | undefined;
let match: IonRouteData['match'] | null | undefined;
let viewStack: ViewStack | undefined;
if (viewKey) {
viewStack = this.viewStacks[viewKey];
if (viewStack) {
viewStack.views.some(matchView);
viewStack = this.viewStacks[viewKey];
if (viewStack) {
viewStack.views.some(matchView);
if (!view) {
viewStack.views.some(r => {
// try to find a route that doesn't have a path or from prop, that will be our not found route
if (!r.routeData.childProps.path && !r.routeData.childProps.from) {
match = {
path: location.pathname,
url: location.pathname,
isExact: true,
params: {}
};
view = r;
return true;
}
return false;
});
}
} else {
const keys = this.getKeys();
keys.some(key => {
viewStack = this.viewStacks[key];
return viewStack!.views.some(matchView);
});
}
const result = { view, viewStack, match };
return result;
return { view, viewStack, match };
function matchView(v: ViewItem) {
const matchProps = {
@@ -58,9 +66,10 @@ export class ViewStacks {
path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component
};
match = matchPath(location.pathname, matchProps);
if (match) {
const myMatch: IonRouteData['match'] | null | undefined = matchPath(location.pathname, matchProps);
if (myMatch) {
view = v;
match = myMatch;
return true;
}
return false;
@@ -85,16 +94,4 @@ export class ViewStacks {
return { view, viewStack };
}
setHiddenViews() {
const keys = this.getKeys();
keys.forEach(key => {
const viewStack = this.viewStacks[key];
viewStack!.views.forEach(view => {
if (!view.routeData.match && !view.isIonRoute) {
view.show = false;
view.mount = false;
}
});
});
}
}

View File

@@ -0,0 +1,75 @@
import React, { useRef, useEffect } from 'react';
import { IonApp, IonRouterOutlet, IonPage } from '@ionic/react';
import { IonReactRouter } from '../IonReactRouter';
import { render } from '@testing-library/react';
import { Route } from 'react-router';
// import {Router} from '../Router';
describe('Router', () => {
describe('on first page render', () => {
let IonTestApp: React.ComponentType<any>;
beforeEach(() => {
IonTestApp = ({ Page }) => {
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/" component={Page}></Route>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);
};
});
it('should be visible', () => {
const MyPage = () => {
return (
<IonPage className="ion-page-invisible">
<div>hello</div>
</IonPage>
);
};
const { container } = render(<IonTestApp Page={MyPage} />);
const page = container.getElementsByClassName('ion-page')[0];
expect(page).not.toHaveClass('ion-page-invisible');
expect(page).toHaveStyle('z-index: 101')
});
it('should fire initial lifecycle events', () => {
const ionViewWillEnterListener = jest.fn();
const ionViewDidEnterListener = jest.fn();
const MyPage = () => {
const ref = useRef<HTMLDivElement>();
useEffect(() => {
ref.current.addEventListener('ionViewWillEnter', ionViewWillEnterListener);
ref.current.addEventListener('ionViewDidEnter', ionViewDidEnterListener);
}, []);
return (
<IonPage ref={ref}>
<div>hello</div>
</IonPage>
);
};
render(<IonTestApp Page={MyPage} />);
expect(ionViewWillEnterListener).toHaveBeenCalledTimes(1);
expect(ionViewDidEnterListener).toHaveBeenCalledTimes(1);
});
});
});;

View File

@@ -1,2 +1,3 @@
export { IonReactRouter } from './Router';
export { IonReactRouter } from './IonReactRouter';
export { IonReactHashRouter } from './IonReactHashRouter';
export { IonReactMemoryRouter } from './IonReactMemoryRouter';

View File

@@ -3,7 +3,7 @@ import { Location as HistoryLocation } from 'history';
const RESTRICT_SIZE = 25;
export class LocationHistory {
locationHistory: HistoryLocation[] = [];
private locationHistory: HistoryLocation[] = [];
add(location: HistoryLocation) {
this.locationHistory.push(location);
@@ -12,9 +12,34 @@ export class LocationHistory {
}
}
findLastLocation(url: string) {
const reversedLocations = [...this.locationHistory].reverse();
const last = reversedLocations.find(x => x.pathname.toLowerCase() === url.toLowerCase());
return last;
pop() {
this.locationHistory.pop();
}
replace(location: HistoryLocation) {
this.locationHistory.pop();
this.locationHistory.push(location);
}
clear() {
this.locationHistory = [];
}
findLastLocationByUrl(url: string) {
for (let i = this.locationHistory.length - 1; i >= 0; i--) {
const location = this.locationHistory[i];
if (location.pathname.toLocaleLowerCase() === url.toLocaleLowerCase()) {
return location;
}
}
return undefined;
}
previous() {
return this.locationHistory[this.locationHistory.length - 2];
}
current() {
return this.locationHistory[this.locationHistory.length - 1];
}
}

View File

@@ -19,7 +19,7 @@
"no-invalid-template-strings": true,
"ban-export-const-enum": true,
"only-arrow-functions": false,
"strict-boolean-conditions": [true, "allow-null-union", "allow-undefined-union", "allow-boolean-or-undefined", "allow-string"],
"strict-boolean-conditions": [false],
"jsx-key": false,
"jsx-self-close": false,
"jsx-curly-spacing": [true, "never"],
@@ -27,6 +27,11 @@
"jsx-no-bind": false,
"jsx-no-lambda": false,
"jsx-no-multiline-js": false,
"jsx-wrap-multiline": false
"jsx-wrap-multiline": false,
"forin": false,
"strict-type-predicates": false,
"no-unused-expression": false,
"no-constant-condition": false,
"no-empty-interface": false
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "4.11.0",
"version": "4.11.13",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -39,7 +39,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "4.11.0",
"@ionic/core": "4.11.13",
"tslib": "*"
},
"peerDependencies": {
@@ -48,7 +48,7 @@
},
"devDependencies": {
"@types/jest": "^23.3.9",
"@types/node": "10.12.9",
"@types/node": "^12.12.14",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"fs-extra": "^8.1.0",
@@ -66,7 +66,7 @@
"tslint": "^5.18.0",
"tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.0.0",
"typescript": "3.5.3"
"typescript": "^3.7.2"
},
"jest": {
"preset": "ts-jest",

View File

@@ -1,5 +1,22 @@
import { ActionSheetOptions, actionSheetController } from '@ionic/core';
import { ActionSheetButton as ActionSheetButtonCore, ActionSheetOptions as ActionSheetOptionsCore, actionSheetController as actionSheetControllerCore } from '@ionic/core';
import { createOverlayComponent } from './createOverlayComponent';
export interface ActionSheetButton extends Omit<ActionSheetButtonCore, 'icon'> {
icon?: {
ios: string;
md: string;
} | string;
}
export interface ActionSheetOptions extends Omit<ActionSheetOptionsCore, 'buttons'> {
buttons?: (ActionSheetButton | string)[];
}
const actionSheetController = {
create: (options: ActionSheetOptions) => actionSheetControllerCore.create(options as any),
dismiss: (data?: any, role?: string | undefined, id?: string | undefined) => actionSheetControllerCore.dismiss(data, role, id),
getTop: () => actionSheetControllerCore.getTop()
};
export const IonActionSheet = /*@__PURE__*/createOverlayComponent<ActionSheetOptions, HTMLIonActionSheetElement>('IonActionSheet', actionSheetController);

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { NavContext } from '../contexts/NavContext';
import { IonicReactProps } from './IonicReactProps';
import { IonIconInner } from './inner-proxies';
import { createForwardRef, isPlatform } from './utils';
import { deprecationWarning } from './utils/dev';
interface IonIconProps {
ariaLabel?: string;
color?: string;
flipRtl?: boolean;
icon?: { ios: string; md: string; } | string;
ios?: { ios: string; md: string; } | string;
lazy?: boolean;
md?: { ios: string; md: string; } | string;
mode?: 'ios' | 'md';
name?: string;
size?: string;
src?: string;
}
type InternalProps = IonIconProps & {
forwardedRef?: React.RefObject<HTMLIonIconElement>;
};
class IonIconContainer extends React.PureComponent<InternalProps> {
constructor(props: InternalProps) {
super(props);
if (this.props.name) {
deprecationWarning('icon-name', 'In Ionic React, you import icons from "ionicons/icons" and set the icon you imported to the "icon" property. Setting the "name" property has no effect.');
}
}
setIcon() {
const { icon, ios, md } = this.props;
if (ios || md) {
if (isPlatform('ios')) {
this.setState({
icon: ios ?? md ?? icon
});
} else if (isPlatform('android')) {
this.setState({
icon: md ?? ios ?? icon
});
}
} else {
this.setState({
icon
});
}
}
render() {
const { icon, ios, md, ...rest } = this.props;
let iconToUse: typeof icon;
if (ios || md) {
if (isPlatform('ios')) {
iconToUse = ios ?? md ?? icon;
} else if (isPlatform('android')) {
iconToUse = md ?? ios ?? icon;
}
} else {
iconToUse = icon;
}
return (
<IonIconInner ref={this.props.forwardedRef} icon={iconToUse} {...rest}>
{this.props.children}
</IonIconInner>
);
}
static get contextType() {
return NavContext;
}
}
export const IonIcon = createForwardRef<IonIconProps & IonicReactProps, HTMLIonIconElement>(IonIconContainer, 'IonIcon');

View File

@@ -3,13 +3,26 @@ import React from 'react';
import { NavContext } from '../contexts/NavContext';
import { IonicReactProps } from './IonicReactProps';
import { createForwardRef } from './utils';
export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.Component<React.HTMLAttributes<HTMLElement> & IonicReactProps> {
interface IonPageProps extends IonicReactProps {
}
interface IonPageInternalProps extends IonPageProps {
forwardedRef?: React.RefObject<HTMLDivElement>;
}
class IonPageInternal extends React.Component<IonPageInternalProps> {
context!: React.ContextType<typeof NavContext>;
ref = React.createRef<HTMLDivElement>();
ref: React.RefObject<HTMLDivElement>;
constructor(props: IonPageInternalProps) {
super(props);
this.ref = this.props.forwardedRef || React.createRef();
}
componentDidMount() {
if (this.context && this.ref.current) {
if (this.context && this.ref && this.ref.current) {
if (this.context.hasIonicRouter()) {
this.context.registerIonPage(this.ref.current);
}
@@ -17,7 +30,7 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C
}
render() {
const { className, children, ...props } = this.props;
const { className, children, forwardedRef, ...props } = this.props;
return (
<div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}>
@@ -33,4 +46,6 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C
static get contextType() {
return NavContext;
}
})();
}
export const IonPage = createForwardRef(IonPageInternal, 'IonPage');

View File

@@ -0,0 +1,5 @@
import { PickerOptions, pickerController } from '@ionic/core';
import { createControllerComponent } from './createControllerComponent';
export const IonPicker = /*@__PURE__*/createControllerComponent<PickerOptions, HTMLIonPickerElement>('IonPicker', pickerController);

View File

@@ -13,7 +13,7 @@ type Props = LocalJSX.IonRouterOutlet & {
};
type InternalProps = Props & {
forwardedRef: any;
forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
};
const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> {

View File

@@ -1,5 +1,22 @@
import { ToastOptions, toastController } from '@ionic/core';
import { ToastButton as ToastButtonCore, ToastOptions as ToastOptionsCore, toastController as toastControllerCore } from '@ionic/core';
import { createControllerComponent } from './createControllerComponent';
export interface ToastButton extends Omit<ToastButtonCore, 'icon'> {
icon?: {
ios: string;
md: string;
} | string;
}
export interface ToastOptions extends Omit<ToastOptionsCore, 'buttons'> {
buttons?: (ToastButton | string)[];
}
const toastController = {
create: (options: ToastOptions) => toastControllerCore.create(options as any),
dismiss: (data?: any, role?: string | undefined, id?: string | undefined) => toastControllerCore.dismiss(data, role, id),
getTop: () => toastControllerCore.getTop()
};
export const IonToast = /*@__PURE__*/createControllerComponent<ToastOptions, HTMLIonToastElement>('IonToast', toastController);

View File

@@ -1,5 +1,6 @@
export interface IonicReactProps {
class?: string;
className?: string;
style?: {[key: string]: any };
}

View File

@@ -10,7 +10,7 @@ describe('IonTabs', () => {
const { container } = render(
<IonTabs>
<IonRouterOutlet></IonRouterOutlet>
<IonTabBar slot="bottom" currentPath={'/'} navigate={() => {}}>
<IonTabBar slot="bottom" currentPath={'/'}>
<IonTabButton tab="schedule">
<IonLabel>Schedule</IonLabel>
<IonIcon name="schedule"></IonIcon>
@@ -44,7 +44,7 @@ describe('IonTabs', () => {
const { container } = render(
<IonTabs>
<IonRouterOutlet></IonRouterOutlet>
<IonTabBar slot="bottom" currentPath={'/'} navigate={() => {}}>
<IonTabBar slot="bottom" currentPath={'/'}>
{false &&
<IonTabButton tab="schedule">
<IonLabel>Schedule</IonLabel>

View File

@@ -11,21 +11,31 @@ interface OverlayBase extends HTMLElement {
export interface ReactControllerProps {
isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
}
export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>(
displayName: string,
controller: { create: (options: OptionsType) => Promise<OverlayType> }
controller: { create: (options: OptionsType) => Promise<OverlayType>; }
) => {
const dismissEventName = `on${displayName}DidDismiss`;
const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = OptionsType & ReactControllerProps;
type Props = OptionsType & ReactControllerProps & {
forwardedRef?: React.RefObject<OverlayType>;
};
return class extends React.Component<Props> {
class Overlay extends React.Component<Props> {
overlay?: OverlayType;
isUnmounted = false;
constructor(props: Props) {
super(props);
this.handleDismiss = this.handleDismiss.bind(this);
}
static get displayName() {
@@ -40,6 +50,7 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
}
componentWillUnmount() {
this.isUnmounted = true;
if (this.overlay) { this.overlay.dismiss(); }
}
@@ -52,19 +63,42 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
}
}
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props;
const overlay = this.overlay = await controller.create({
const { isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
this.overlay = await controller.create({
...cProps as any
});
attachProps(overlay, {
[dismissEventName]: onDidDismiss
attachProps(this.overlay, {
[didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e)
}, prevProps);
await overlay.present();
// Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.isUnmounted === false) {
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
await this.overlay.present();
}
}
render(): null {
return null;
}
};
}
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
};

View File

@@ -13,23 +13,32 @@ export interface ReactOverlayProps {
children?: React.ReactNode;
isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
}
export const createOverlayComponent = <T extends object, OverlayType extends OverlayElement>(
export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
displayName: string,
controller: { create: (options: any) => Promise<OverlayType> }
controller: { create: (options: any) => Promise<OverlayType>; }
) => {
const dismissEventName = `on${displayName}DidDismiss`;
const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;
type Props = T & ReactOverlayProps;
type Props = OverlayComponent & ReactOverlayProps & {
forwardedRef?: React.RefObject<OverlayType>;
};
return class extends React.Component<Props> {
class Overlay extends React.Component<Props> {
overlay?: OverlayType;
el: HTMLDivElement;
constructor(props: Props) {
super(props);
this.el = document.createElement('div');
this.handleDismiss = this.handleDismiss.bind(this);
}
static get displayName() {
@@ -37,7 +46,7 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
}
componentDidMount() {
if (this.props.isOpen as boolean) {
if (this.props.isOpen) {
this.present();
}
}
@@ -46,7 +55,20 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
if (this.overlay) { this.overlay.dismiss(); }
}
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async componentDidUpdate(prevProps: Props) {
if (this.overlay) {
attachProps(this.overlay, this.props, prevProps);
}
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
@@ -56,28 +78,40 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
}
async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss = () => { return; }, ...cProps } = this.props;
const { children, isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
const elementProps = {
...cProps,
[dismissEventName]: onDidDismiss
ref: this.props.forwardedRef,
[didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e)
};
const overlay = this.overlay = await controller.create({
this.overlay = await controller.create({
...elementProps,
component: this.el,
componentProps: {}
});
attachProps(overlay, elementProps, prevProps);
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
await overlay.present();
attachProps(this.overlay, elementProps, prevProps);
await this.overlay.present();
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
this.props.isOpen ? this.props.children : null,
this.el
);
}
};
}
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
};

View File

@@ -1,4 +1,4 @@
export declare type RouterDirection = 'forward' | 'back' | 'none';
export declare type RouterDirection = 'forward' | 'back' | 'root' | 'none';
export type HrefProps<T> = Omit<T, 'routerDirection'> & {
routerLink?: string;

View File

@@ -8,10 +8,11 @@ export * from './proxies';
// createControllerComponent
export { IonAlert } from './IonAlert';
export { IonLoading } from './IonLoading';
export { IonToast } from './IonToast';
export * from './IonToast';
export { IonPicker } from './IonPicker';
// createOverlayComponent
export { IonActionSheet } from './IonActionSheet';
export * from './IonActionSheet';
export { IonModal } from './IonModal';
export { IonPopover } from './IonPopover';
@@ -21,9 +22,10 @@ export { IonTabs } from './navigation/IonTabs';
export { IonTabBar } from './navigation/IonTabBar';
export { IonBackButton } from './navigation/IonBackButton';
export { IonRouterOutlet } from './IonRouterOutlet';
export { IonIcon } from './IonIcon';
// Utils
export { isPlatform, getPlatforms } from './utils';
export { isPlatform, getPlatforms, getConfig } from './utils';
export { RouterDirection } from './hrefprops';
// Icons that are used by internal components

View File

@@ -1,7 +1,11 @@
import { JSX } from '@ionic/core';
import { JSX as IoniconsJSX } from 'ionicons';
import { /*@__PURE__*/ createReactComponent } from './createComponent';
export const IonTabBarInner = /*@__PURE__*/createReactComponent<JSX.IonTabBar, HTMLIonTabBarElement>('ion-tab-bar');
export const IonBackButtonInner = /*@__PURE__*/createReactComponent<Omit<JSX.IonBackButton, 'icon'>, HTMLIonBackButtonElement>('ion-back-button');
export const IonRouterOutletInner = /*@__PURE__*/createReactComponent<JSX.IonRouterOutlet, HTMLIonRouterOutletElement>('ion-router-outlet');
// ionicons
export const IonIconInner = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon, HTMLIonIconElement>('ion-icon');

View File

@@ -9,7 +9,7 @@ type Props = Omit<LocalJSX.IonBackButton, 'icon'> & IonicReactProps & {
icon?: {
ios: string;
md: string;
};
} | string;
ref?: React.RefObject<HTMLIonBackButtonElement>;
};

View File

@@ -6,7 +6,8 @@ import { IonTabBarInner } from '../inner-proxies';
import { IonTabButton } from '../proxies';
type Props = LocalJSX.IonTabBar & {
navigate?: (path: string, direction: 'back' | 'none') => void;
onIonTabsDidChange?: (event: CustomEvent<{ tab: string }>) => void;
onIonTabsWillChange?: (event: CustomEvent<{ tab: string }>) => void;
currentPath?: string;
slot?: 'bottom' | 'top';
};
@@ -22,6 +23,7 @@ interface State {
}
const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Props, State> {
context!: React.ContextType<typeof NavContext>;
constructor(props: Props) {
super(props);
@@ -67,13 +69,22 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
}
private onTabButtonClick = (e: CustomEvent<{ href: string, selected: boolean, tab: string }>) => {
const { navigate } = this.props;
if (navigate) {
if (this.state.activeTab === e.detail.tab) {
navigate(this.state.tabs[e.detail.tab].originalHref, 'back');
const originalHref = this.state.tabs[e.detail.tab].originalHref;
const currentHref = this.state.tabs[e.detail.tab].currentHref;
if (this.state.activeTab === e.detail.tab) {
if (originalHref === currentHref) {
this.context.navigate(originalHref, 'none');
} else {
navigate(this.state.tabs[e.detail.tab].currentHref, 'none');
this.context.navigate(originalHref, 'back', 'pop');
}
} else {
if (this.props.onIonTabsWillChange) {
this.props.onIonTabsWillChange(new CustomEvent('ionTabWillChange', { detail: { tab: e.detail.tab } }));
}
if (this.props.onIonTabsDidChange) {
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
}
this.context.navigate(currentHref, 'none');
}
}
@@ -96,6 +107,10 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
</IonTabBarInner>
);
}
static get contextType() {
return NavContext;
}
})();
export const IonTabBar: React.FC<Props> = props => {
@@ -103,9 +118,6 @@ export const IonTabBar: React.FC<Props> = props => {
return (
<IonTabBarUnwrapped
{...props as any}
navigate={props.navigate || ((path: string, direction: 'back' | 'none') => {
context.navigate(path, direction);
})}
currentPath={props.currentPath || context.currentPath}
>
{props.children}

View File

@@ -1,3 +1,4 @@
import { JSX as LocalJSX } from '@ionic/core';
import React from 'react';
import { NavContext } from '../../contexts/NavContext';
@@ -5,7 +6,7 @@ import { IonRouterOutlet } from '../IonRouterOutlet';
import { IonTabBar } from './IonTabBar';
interface Props {
interface Props extends LocalJSX.IonTabs {
children: React.ReactNode;
}
@@ -28,7 +29,7 @@ const tabsInner: React.CSSProperties = {
contain: 'layout size style'
};
export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props> {
export class IonTabs extends React.Component<Props> {
context!: React.ContextType<typeof NavContext>;
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
@@ -38,7 +39,7 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
render() {
let outlet: React.ReactElement<{}> | undefined;
let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }> | undefined;
let tabBar: React.ReactElement | undefined;
React.Children.forEach(this.props.children, (child: any) => {
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
@@ -48,7 +49,8 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
outlet = child;
}
if (child.type === IonTabBar) {
tabBar = child;
const { onIonTabsDidChange, onIonTabsWillChange } = this.props;
tabBar = React.cloneElement(child, { onIonTabsDidChange, onIonTabsWillChange });
}
});
@@ -71,11 +73,7 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
);
}
static get displayName() {
return 'IonTabs';
}
static get contextType() {
return NavContext;
}
})();
}

View File

@@ -1,12 +1,8 @@
import { JSX } from '@ionic/core';
import { JSX as IoniconsJSX } from 'ionicons';
import { createReactComponent } from './createComponent';
import { HrefProps } from './hrefprops';
// ionicons
export const IonIcon = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon, HTMLIonIconElement>('ion-icon');
// ionic/core
export const IonApp = /*@__PURE__*/createReactComponent<JSX.IonApp, HTMLIonAppElement>('ion-app');
export const IonTab = /*@__PURE__*/createReactComponent<JSX.IonTab, HTMLIonTabElement>('ion-tab');
@@ -50,7 +46,6 @@ export const IonMenu = /*@__PURE__*/createReactComponent<JSX.IonMenu, HTMLIonMen
export const IonMenuButton = /*@__PURE__*/createReactComponent<JSX.IonMenuButton, HTMLIonMenuButtonElement>('ion-menu-button');
export const IonMenuToggle = /*@__PURE__*/createReactComponent<JSX.IonMenuToggle, HTMLIonMenuToggleElement>('ion-menu-toggle');
export const IonNote = /*@__PURE__*/createReactComponent<JSX.IonNote, HTMLIonNoteElement>('ion-note');
export const IonPicker = /*@__PURE__*/createReactComponent<JSX.IonPicker, HTMLIonPickerElement>('ion-picker');
export const IonPickerColumn = /*@__PURE__*/createReactComponent<JSX.IonPickerColumn, HTMLIonPickerColumnElement>('ion-picker-column');
export const IonNav = /*@__PURE__*/createReactComponent<JSX.IonNav, HTMLIonNavElement>('ion-nav');
export const IonProgressBar = /*@__PURE__*/createReactComponent<JSX.IonProgressBar, HTMLIonProgressBarElement>('ion-progress-bar');

View File

@@ -1,33 +1,36 @@
import { camelToDashCase } from './case';
export const attachProps = (node: HTMLElement, newProps: any, oldProps: any = {}) => {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'class' || name === 'className' || name === 'forwardedRef') {
return;
// some test frameworks don't render DOM elements, so we test here to make sure we are dealing with DOM first
if (node instanceof Element) {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'class' || name === 'className' || name === 'forwardedRef') {
return;
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
} else {
(node as any)[name] = newProps[name];
}
}
}
});
});
}
};
export const getClassName = (classList: DOMTokenList, newProps: any, oldProps: any) => {

View File

@@ -1,4 +1,4 @@
import { Platforms, getPlatforms as getPlatformsCore, isPlatform as isPlatformCore } from '@ionic/core';
import { Config as CoreConfig, Platforms, getPlatforms as getPlatformsCore, isPlatform as isPlatformCore } from '@ionic/core';
import React from 'react';
import { IonicReactProps } from '../IonicReactProps';
@@ -24,3 +24,13 @@ export const isPlatform = (platform: Platforms) => {
export const getPlatforms = () => {
return getPlatformsCore(window);
};
export const getConfig = (): CoreConfig | null => {
if (typeof (window as any) !== 'undefined') {
const Ionic = (window as any).Ionic;
if (Ionic && Ionic.config) {
return Ionic.config;
}
}
return null;
};

View File

@@ -22,52 +22,82 @@ export const IonLifeCycleContext = /*@__PURE__*/React.createContext<IonLifeCycle
ionViewDidLeave: () => { return; },
});
export interface LifeCycleCallback { (): void; id?: number; }
export const DefaultIonLifeCycleContext = class implements IonLifeCycleContextInterface {
ionViewWillEnterCallback?: () => void;
ionViewDidEnterCallback?: () => void;
ionViewWillLeaveCallback?: () => void;
ionViewDidLeaveCallback?: () => void;
ionViewWillEnterCallbacks: LifeCycleCallback[] = [];
ionViewDidEnterCallbacks: LifeCycleCallback[] = [];
ionViewWillLeaveCallbacks: LifeCycleCallback[] = [];
ionViewDidLeaveCallbacks: LifeCycleCallback[] = [];
componentCanBeDestroyedCallback?: () => void;
onIonViewWillEnter(callback: () => void) {
this.ionViewWillEnterCallback = callback;
onIonViewWillEnter(callback: LifeCycleCallback) {
if (callback.id) {
const index = this.ionViewWillEnterCallbacks.findIndex(x => x.id === callback.id);
if (index > -1) {
this.ionViewWillEnterCallbacks[index] = callback;
} else {
this.ionViewWillEnterCallbacks.push(callback);
}
} else {
this.ionViewWillEnterCallbacks.push(callback);
}
}
ionViewWillEnter() {
if (this.ionViewWillEnterCallback) {
this.ionViewWillEnterCallback();
}
this.ionViewWillEnterCallbacks.forEach(cb => cb());
}
onIonViewDidEnter(callback: () => void) {
this.ionViewDidEnterCallback = callback;
onIonViewDidEnter(callback: LifeCycleCallback) {
if (callback.id) {
const index = this.ionViewDidEnterCallbacks.findIndex(x => x.id === callback.id);
if (index > -1) {
this.ionViewDidEnterCallbacks[index] = callback;
} else {
this.ionViewDidEnterCallbacks.push(callback);
}
} else {
this.ionViewDidEnterCallbacks.push(callback);
}
}
ionViewDidEnter() {
if (this.ionViewDidEnterCallback) {
this.ionViewDidEnterCallback();
}
this.ionViewDidEnterCallbacks.forEach(cb => cb());
}
onIonViewWillLeave(callback: () => void) {
this.ionViewWillLeaveCallback = callback;
onIonViewWillLeave(callback: LifeCycleCallback) {
if (callback.id) {
const index = this.ionViewWillLeaveCallbacks.findIndex(x => x.id === callback.id);
if (index > -1) {
this.ionViewWillLeaveCallbacks[index] = callback;
} else {
this.ionViewWillLeaveCallbacks.push(callback);
}
} else {
this.ionViewWillLeaveCallbacks.push(callback);
}
}
ionViewWillLeave() {
if (this.ionViewWillLeaveCallback) {
this.ionViewWillLeaveCallback();
}
this.ionViewWillLeaveCallbacks.forEach(cb => cb());
}
onIonViewDidLeave(callback: () => void) {
this.ionViewDidLeaveCallback = callback;
onIonViewDidLeave(callback: LifeCycleCallback) {
if (callback.id) {
const index = this.ionViewDidLeaveCallbacks.findIndex(x => x.id === callback.id);
if (index > -1) {
this.ionViewDidLeaveCallbacks[index] = callback;
} else {
this.ionViewDidLeaveCallbacks.push(callback);
}
} else {
this.ionViewDidLeaveCallbacks.push(callback);
}
}
ionViewDidLeave() {
if (this.ionViewDidLeaveCallback) {
this.ionViewDidLeaveCallback();
}
this.ionViewDidLeaveCallbacks.forEach(cb => cb());
this.componentCanBeDestroyed();
}

View File

@@ -2,20 +2,16 @@ import { RouterDirection } from '@ionic/core';
import React from 'react';
export interface NavContextState {
getHistory: () => History;
getLocation: () => Location;
getPageManager: () => any;
getStackManager: () => any;
goBack: (defaultHref?: string) => void;
navigate: (path: string, direction?: RouterDirection | 'none') => void;
navigate: (path: string, direction?: RouterDirection | 'none', ionRouteAction?: 'push' | 'replace' | 'pop') => void;
hasIonicRouter: () => boolean;
registerIonPage: (page: HTMLElement) => void;
currentPath: string | undefined;
}
export const NavContext = /*@__PURE__*/React.createContext<NavContextState>({
getHistory: () => window.history,
getLocation: () => window.location,
getPageManager: () => undefined,
getStackManager: () => undefined,
goBack: (defaultHref?: string) => {

View File

@@ -1,23 +1,43 @@
import { useContext } from 'react';
import { useContext, useEffect, useRef } from 'react';
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
import { IonLifeCycleContext, LifeCycleCallback } from '../contexts/IonLifeCycleContext';
export const useIonViewWillEnter = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewWillEnter(callback);
export const useIonViewWillEnter = (callback: LifeCycleCallback, deps: any[] = []) => {
const context = useContext(IonLifeCycleContext);
const id = useRef<number | undefined>();
id.current = id.current || Math.floor(Math.random() * 1000000);
useEffect(() => {
callback.id = id.current!;
context.onIonViewWillEnter(callback);
}, deps);
};
export const useIonViewDidEnter = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewDidEnter(callback);
export const useIonViewDidEnter = (callback: LifeCycleCallback, deps: any[] = []) => {
const context = useContext(IonLifeCycleContext);
const id = useRef<number | undefined>();
id.current = id.current || Math.floor(Math.random() * 1000000);
useEffect(() => {
callback.id = id.current!;
context.onIonViewDidEnter(callback);
}, deps);
};
export const useIonViewWillLeave = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewWillLeave(callback);
export const useIonViewWillLeave = (callback: LifeCycleCallback, deps: any[] = []) => {
const context = useContext(IonLifeCycleContext);
const id = useRef<number | undefined>();
id.current = id.current || Math.floor(Math.random() * 1000000);
useEffect(() => {
callback.id = id.current!;
context.onIonViewWillLeave(callback);
}, deps);
};
export const useIonViewDidLeave = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewDidLeave(callback);
export const useIonViewDidLeave = (callback: LifeCycleCallback, deps: any[] = []) => {
const context = useContext(IonLifeCycleContext);
const id = useRef<number | undefined>();
id.current = id.current || Math.floor(Math.random() * 1000000);
useEffect(() => {
callback.id = id.current!;
context.onIonViewDidLeave(callback);
}, deps);
};

View File

@@ -14,12 +14,11 @@
"trailing-comma": false,
"no-null-keyword": false,
"no-console": false,
"no-unbound-method": true,
"no-floating-promises": false,
"no-invalid-template-strings": true,
"ban-export-const-enum": true,
"only-arrow-functions": true,
"strict-boolean-conditions": [true, "allow-null-union", "allow-undefined-union", "allow-boolean-or-undefined", "allow-string"],
"strict-boolean-conditions": [false],
"jsx-key": false,
"jsx-self-close": false,
"jsx-curly-spacing": [true, "never"],
@@ -27,6 +26,8 @@
"jsx-no-bind": false,
"jsx-no-lambda": false,
"jsx-no-multiline-js": false,
"jsx-wrap-multiline": false
"jsx-wrap-multiline": false,
"no-empty-interface": false,
"no-unbound-method": false
}
}