Compare commits

...

22 Commits

Author SHA1 Message Date
Sean Perkins
1f8643cade chore: memoize stack context value in StackManager 2023-07-03 15:26:03 -04:00
Sean Perkins
f83f4c03d5 chore: migrate PageManager to a functional component 2023-07-03 15:25:38 -04:00
Sean Perkins
ec69d340c9 chore: migrate NavManager to a functional component 2023-07-03 13:16:24 -04:00
Sean Perkins
02a2b393b8 chore: comment why <Routes /> is necessary 2023-07-03 12:39:21 -04:00
Sean Perkins
6bbab525d0 chore: remove previous IonRouterOutlet implementation 2023-07-03 12:33:24 -04:00
Sean Perkins
bf1debb947 chore: migrate ViewLifeCycleManager to be a functional component 2023-06-30 16:13:27 -04:00
Sean Perkins
caf0e3fae1 chore: migrate OutletPageManager to be a functional component 2023-06-30 16:13:10 -04:00
Sean Perkins
0f00d63648 chore: migrate IonTabs to be a functional component 2023-06-30 16:12:51 -04:00
Sean Perkins
6a44a70372 chore: migrate StackManager to be a functional component 2023-06-30 16:12:30 -04:00
Sean Perkins
9bef17d9c8 refactor: IonRouter defaultHref assignment 2023-06-30 16:12:09 -04:00
Sean Perkins
7d8204a0a7 chore: document IonReactRouter usage with BrowserRouter 2023-06-30 16:11:43 -04:00
Sean Perkins
7236dabdce chore: this is a trashy commit
This commit does a lot to try and meld the v6 migration to work against the test app. The introduction of `<Routes>` component from react-router means much of the logic around querying the `<Route>` as the direct child of the outlet and also cloning the element, leads to a broken experience. This commit tries to handle that requirement.

Additional discovery will try to uncover if we can move the `<Routes>` inside of the outlet so the developer doesn't need to specify it when upgrading (and perhaps can simplify how we query the nodes).
2023-06-29 15:56:01 -04:00
Sean Perkins
faa553faf9 fix: invisible pages should not be clickable
Discovered this while working through errors during the migration. When a page is invisible, the elements are still clickable. This is not expected behavior.
2023-06-29 13:30:32 -04:00
Sean Perkins
eca89a38bf chore: add location history when IonRouter first renders 2023-06-29 11:22:08 -04:00
Sean Perkins
b907726bbd chore: migrate IonReactRouter to react-router v6 2023-06-29 10:20:14 -04:00
Sean Perkins
bcfad4e6c6 chore: remove IonReactMemoryRouter
React Router v6 exposes hooks to listen for history/location change. This implementation doesn't provide anything extra and should be removed completely AFAIK.
2023-06-29 10:17:33 -04:00
Sean Perkins
b418632450 chore: remove IonReactHashRouter
React Router v6 exposes hooks to listen for history/location change. This implementation doesn't provide anything extra and should be removed completely AFAIK.
2023-06-29 10:17:07 -04:00
Sean Perkins
0936639f81 chore: migrate IonRouteInner to react-router v6 2023-06-29 01:26:02 -04:00
Sean Perkins
86a3d353d6 chore: migrate matchPath param order in ReactRouterViewStack 2023-06-29 01:21:25 -04:00
Sean Perkins
492921b649 chore: migrate matchPath param order in StackManager 2023-06-29 01:16:55 -04:00
Sean Perkins
1db7c5322c chore: migrate IonRouter to react-router v6 2023-06-29 01:14:17 -04:00
Sean Perkins
16c373f853 chore: update peer and dev deps for react router v6 2023-06-29 01:14:02 -04:00
24 changed files with 1095 additions and 1651 deletions

View File

@@ -200,6 +200,8 @@ ion-toast-controller,
.ion-page-invisible {
opacity: 0;
pointer-events: none;
}
.can-go-back > ion-header ion-back-button {

View File

@@ -17,18 +17,16 @@
"@ionic/prettier-config": "^2.0.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"@types/node": "^14.0.14",
"@types/react": "16.14.0",
"@types/react-dom": "^16.9.0",
"@types/react-router": "^5.0.3",
"@types/react-router-dom": "^5.1.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"eslint": "^7.32.0",
"prettier": "^2.8.3",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.26.4",
"rollup-plugin-sourcemaps": "^0.6.2",
@@ -37,8 +35,8 @@
"peerDependencies": {
"react": ">=16.8.6",
"react-dom": ">=16.8.6",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1"
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -144,17 +142,6 @@
"node": ">=4"
}
},
"node_modules/@babel/runtime": {
"version": "7.16.3",
"dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -449,6 +436,15 @@
"node": ">= 8"
}
},
"node_modules/@remix-run/router": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "8.4.0",
"dev": true,
@@ -502,11 +498,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/history": {
"version": "4.7.9",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -530,39 +521,23 @@
"license": "MIT"
},
"node_modules/@types/react": {
"version": "16.14.0",
"version": "17.0.44",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.44.tgz",
"integrity": "sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "16.9.14",
"version": "17.0.20",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz",
"integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "^16"
}
},
"node_modules/@types/react-router": {
"version": "5.1.17",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/history": "*",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "5.3.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
"@types/react": "^17"
}
},
"node_modules/@types/resolve": {
@@ -573,6 +548,12 @@
"@types/node": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -2097,27 +2078,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/history": {
"version": "4.10.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -2441,11 +2401,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "0.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2562,19 +2517,6 @@
"node": ">=8.6"
}
},
"node_modules/mini-create-react-context": {
"version": "0.4.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2733,14 +2675,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/path-to-regexp": {
"version": "1.8.0",
"dev": true,
"license": "MIT",
"dependencies": {
"isarray": "0.0.1"
}
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -2795,15 +2729,6 @@
"node": ">=0.4.0"
}
},
"node_modules/prop-types": {
"version": "15.7.2",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"node_modules/punycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz",
@@ -2834,76 +2759,62 @@
]
},
"node_modules/react": {
"version": "16.14.0",
"license": "MIT",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "16.14.0",
"license": "MIT",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
"scheduler": "^0.20.2"
},
"peerDependencies": {
"react": "^16.14.0"
"react": "17.0.2"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"license": "MIT"
},
"node_modules/react-router": {
"version": "5.2.1",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "5.3.0",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.1",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0",
"react-router": "6.14.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"dev": true,
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
@@ -2968,11 +2879,6 @@
"node": ">=4"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -3070,8 +2976,9 @@
}
},
"node_modules/scheduler": {
"version": "0.19.1",
"license": "MIT",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -3311,16 +3218,6 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"node_modules/tiny-invariant": {
"version": "1.2.0",
"dev": true,
"license": "MIT"
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"dev": true,
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -3451,11 +3348,6 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"node_modules/value-equal": {
"version": "1.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3613,13 +3505,6 @@
}
}
},
"@babel/runtime": {
"version": "7.16.3",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -3821,6 +3706,12 @@
"fastq": "^1.6.0"
}
},
"@remix-run/router": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
"dev": true
},
"@rollup/plugin-node-resolve": {
"version": "8.4.0",
"dev": true,
@@ -3852,10 +3743,6 @@
"version": "0.0.39",
"dev": true
},
"@types/history": {
"version": "4.7.9",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -3877,35 +3764,23 @@
"dev": true
},
"@types/react": {
"version": "16.14.0",
"version": "17.0.44",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.44.tgz",
"integrity": "sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"@types/react-dom": {
"version": "16.9.14",
"version": "17.0.20",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz",
"integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==",
"dev": true,
"requires": {
"@types/react": "^16"
}
},
"@types/react-router": {
"version": "5.1.17",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.3.2",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
"@types/react": "^17"
}
},
"@types/resolve": {
@@ -3915,6 +3790,12 @@
"@types/node": "*"
}
},
"@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"dev": true
},
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -4974,25 +4855,6 @@
"has-symbols": "^1.0.2"
}
},
"history": {
"version": "4.10.1",
"dev": true,
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"dev": true,
"requires": {
"react-is": "^16.7.0"
}
},
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -5212,10 +5074,6 @@
"call-bind": "^1.0.2"
}
},
"isarray": {
"version": "0.0.1",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -5309,14 +5167,6 @@
"picomatch": "^2.3.1"
}
},
"mini-create-react-context": {
"version": "0.4.1",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
}
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5432,13 +5282,6 @@
"version": "1.0.7",
"dev": true
},
"path-to-regexp": {
"version": "1.8.0",
"dev": true,
"requires": {
"isarray": "0.0.1"
}
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -5469,14 +5312,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"prop-types": {
"version": "15.7.2",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"punycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz",
@@ -5490,58 +5325,43 @@
"dev": true
},
"react": {
"version": "16.14.0",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
"object-assign": "^4.1.1"
}
},
"react-dom": {
"version": "16.14.0",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
"scheduler": "^0.20.2"
}
},
"react-is": {
"version": "16.13.1"
},
"react-router": {
"version": "5.2.1",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0"
}
},
"react-router-dom": {
"version": "5.3.0",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.1",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0",
"react-router": "6.14.0"
}
},
"regenerator-runtime": {
"version": "0.13.9",
"dev": true
},
"regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
@@ -5582,10 +5402,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"resolve-pathname": {
"version": "3.0.0",
"dev": true
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -5635,7 +5451,9 @@
}
},
"scheduler": {
"version": "0.19.1",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -5815,14 +5633,6 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"tiny-invariant": {
"version": "1.2.0",
"dev": true
},
"tiny-warning": {
"version": "1.0.3",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5923,10 +5733,6 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"value-equal": {
"version": "1.0.1",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -43,26 +43,24 @@
"peerDependencies": {
"react": ">=16.8.6",
"react-dom": ">=16.8.6",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1"
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"@types/node": "^14.0.14",
"@types/react": "16.14.0",
"@types/react-dom": "^16.9.0",
"@types/react-router": "^5.0.3",
"@types/react-router-dom": "^5.1.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"eslint": "^7.32.0",
"prettier": "^2.8.3",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.26.4",
"rollup-plugin-sourcemaps": "^0.6.2",

View File

@@ -1,53 +0,0 @@
import type { Action as HistoryAction, History, Location as HistoryLocation } from 'history';
import { createHashHistory as createHistory } from 'history';
import React from 'react';
import type { BrowserRouterProps } from 'react-router-dom';
import { Router } from 'react-router-dom';
import { IonRouter } from './IonRouter';
interface IonReactHashRouterProps extends BrowserRouterProps {
history?: History;
}
export class IonReactHashRouter extends React.Component<IonReactHashRouterProps> {
history: History;
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
constructor(props: IonReactHashRouterProps) {
super(props);
const { history, ...rest } = props;
this.history = history || createHistory(rest);
this.history.listen(this.handleHistoryChange.bind(this));
this.registerHistoryListener = this.registerHistoryListener.bind(this);
}
/**
* history@4.x passes separate location and action
* params. history@5.x passes location and action
* together as a single object.
* TODO: If support for React Router <=5 is dropped
* this logic is no longer needed. We can just assume
* a single object with both location and action.
*/
handleHistoryChange(location: HistoryLocation, action: HistoryAction) {
const locationValue = (location as any).location || location;
const actionValue = (location as any).action || action;
if (this.historyListenHandler) {
this.historyListenHandler(locationValue, actionValue);
}
}
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
this.historyListenHandler = cb;
}
render() {
const { children, ...props } = this.props;
return (
<Router history={this.history} {...props}>
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
</Router>
);
}
}

View File

@@ -1,51 +0,0 @@
import type { Action as HistoryAction, Location as HistoryLocation, MemoryHistory } from 'history';
import React from 'react';
import type { MemoryRouterProps } from 'react-router';
import { Router } from 'react-router';
import { IonRouter } from './IonRouter';
interface IonReactMemoryRouterProps extends MemoryRouterProps {
history: MemoryHistory;
}
export class IonReactMemoryRouter extends React.Component<IonReactMemoryRouterProps> {
history: MemoryHistory;
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
constructor(props: IonReactMemoryRouterProps) {
super(props);
this.history = props.history;
this.history.listen(this.handleHistoryChange.bind(this));
this.registerHistoryListener = this.registerHistoryListener.bind(this);
}
/**
* history@4.x passes separate location and action
* params. history@5.x passes location and action
* together as a single object.
* TODO: If support for React Router <=5 is dropped
* this logic is no longer needed. We can just assume
* a single object with both location and action.
*/
handleHistoryChange(location: HistoryLocation, action: HistoryAction) {
const locationValue = (location as any).location || location;
const actionValue = (location as any).action || action;
if (this.historyListenHandler) {
this.historyListenHandler(locationValue, actionValue);
}
}
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
this.historyListenHandler = cb;
}
render() {
const { children, ...props } = this.props;
return (
<Router {...props}>
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
</Router>
);
}
}

View File

@@ -1,53 +1,20 @@
import type { Action as HistoryAction, History, Location as HistoryLocation } from 'history';
import { createBrowserHistory as createHistory } from 'history';
import type { PropsWithChildren } from 'react';
import React from 'react';
import type { BrowserRouterProps } from 'react-router-dom';
import { Router } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
import { IonRouter } from './IonRouter';
import IonRouter from './IonRouter';
interface IonReactRouterProps extends BrowserRouterProps {
history?: History;
}
export class IonReactRouter extends React.Component<IonReactRouterProps> {
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
history: History;
constructor(props: IonReactRouterProps) {
super(props);
const { history, ...rest } = props;
this.history = history || createHistory(rest);
this.history.listen(this.handleHistoryChange.bind(this));
this.registerHistoryListener = this.registerHistoryListener.bind(this);
}
/**
* history@4.x passes separate location and action
* params. history@5.x passes location and action
* together as a single object.
* TODO: If support for React Router <=5 is dropped
* this logic is no longer needed. We can just assume
* a single object with both location and action.
*/
handleHistoryChange(location: HistoryLocation, action: HistoryAction) {
const locationValue = (location as any).location || location;
const actionValue = (location as any).action || action;
if (this.historyListenHandler) {
this.historyListenHandler(locationValue, actionValue);
}
}
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
this.historyListenHandler = cb;
}
render() {
const { children, ...props } = this.props;
return (
<Router history={this.history} {...props}>
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
</Router>
);
}
/**
* Wrapper around react-router-dom's BrowserRouter that provides a context for IonRouterOutlet.
* Ionic developers should use IonReactRouter instead of BrowserRouter when using React Router.
*/
export function IonReactRouter({ children }: PropsWithChildren<BrowserRouterProps>) {
// BrowserRouter is used so that the route state is kept in sync with the browser history.
// This reflects the current route in the URL.
return (
<BrowserRouter>
<IonRouter>{children}</IonRouter>
</BrowserRouter>
);
}

View File

@@ -2,29 +2,6 @@ import type { IonRouteProps } from '@ionic/react';
import React from 'react';
import { Route } from 'react-router';
export class IonRouteInner extends React.PureComponent<IonRouteProps> {
render() {
return (
<Route
path={this.props.path}
exact={this.props.exact}
render={this.props.render}
{
/**
* `computedMatch` is a private API in react-router v5 that
* has been removed in v6.
*
* This needs to be removed when we support v6.
*
* TODO: FW-647
*/
...((this.props as any).computedMatch !== undefined
? {
computedMatch: (this.props as any).computedMatch,
}
: {})
}
/>
);
}
export function IonRouteInner(props: IonRouteProps) {
return <Route path={props.path} children={props.render} />;
}

View File

@@ -1,16 +1,9 @@
import type {
AnimationBuilder,
RouteAction,
RouteInfo,
RouteManagerContextState,
RouterDirection,
ViewItem,
} from '@ionic/react';
import type { AnimationBuilder, RouteAction, RouteInfo, RouteManagerContextState, RouterDirection } from '@ionic/react';
import { LocationHistory, NavManager, RouteManagerContext, generateId, getConfig } from '@ionic/react';
import type { Action as HistoryAction, Location as HistoryLocation } from 'history';
import React from 'react';
import type { RouteComponentProps } from 'react-router-dom';
import { withRouter } from 'react-router-dom';
import type { PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import type { Location, NavigationType } from 'react-router-dom';
import { useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
import { IonRouteInner } from './IonRouteInner';
import { ReactRouterViewStack } from './ReactRouterViewStack';
@@ -21,158 +14,149 @@ export interface LocationState {
routerOptions?: { as?: string; unmount?: boolean };
}
interface IonRouteProps extends RouteComponentProps<{}, {}, LocationState> {
registerHistoryListener: (cb: (location: HistoryLocation<any>, action: HistoryAction) => void) => void;
}
const IonRouter = ({ children }: PropsWithChildren<{}>) => {
const location = useLocation();
const params = useParams();
const navigate = useNavigate();
const navigationType = useNavigationType();
interface IonRouteState {
routeInfo: RouteInfo;
}
const [currentTab, setCurrentTab] = useState<string>();
const [incomingRouteParams, setIncomingRouteParams] = useState<Partial<RouteInfo>>({});
const [routeInfo, setRouteInfo] = useState({
id: generateId('routeInfo'),
pathname: location.pathname,
search: location.search,
});
const locationHistory = useRef(new LocationHistory());
class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
currentTab?: string;
exitViewFromOtherOutletHandlers: ((pathname: string) => ViewItem | undefined)[] = [];
incomingRouteParams?: Partial<RouteInfo>;
locationHistory = new LocationHistory();
viewStack = new ReactRouterViewStack();
routeMangerContextState: RouteManagerContextState = {
canGoBack: () => this.locationHistory.canGoBack(),
clearOutlet: this.viewStack.clear,
findViewItemByPathname: this.viewStack.findViewItemByPathname,
getChildrenToRender: this.viewStack.getChildrenToRender,
goBack: () => this.handleNavigateBack(),
createViewItem: this.viewStack.createViewItem,
findViewItemByRouteInfo: this.viewStack.findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
addViewItem: this.viewStack.add,
unMountViewItem: this.viewStack.remove,
};
const viewStack = useRef(new ReactRouterViewStack());
constructor(props: IonRouteProps) {
super(props);
useEffect(() => {
locationHistory.current.add(routeInfo);
}, []);
const routeInfo = {
id: generateId('routeInfo'),
pathname: this.props.location.pathname,
search: this.props.location.search,
};
useEffect(() => {
handleHistoryChange(location, navigationType);
}, [location, navigationType]);
this.locationHistory.add(routeInfo);
this.handleChangeTab = this.handleChangeTab.bind(this);
this.handleResetTab = this.handleResetTab.bind(this);
this.handleNativeBack = this.handleNativeBack.bind(this);
this.handleNavigate = this.handleNavigate.bind(this);
this.handleNavigateBack = this.handleNavigateBack.bind(this);
this.props.registerHistoryListener(this.handleHistoryChange.bind(this));
this.handleSetCurrentTab = this.handleSetCurrentTab.bind(this);
this.state = {
routeInfo,
};
}
handleChangeTab(tab: string, path?: string, routeOptions?: any) {
const handleChangeTab = (tab: string, path?: string, routeOptions?: any) => {
if (!path) {
return;
}
const routeInfo = this.locationHistory.getCurrentRouteInfoForTab(tab);
const routeInfo = locationHistory.current.getCurrentRouteInfoForTab(tab);
const [pathname, search] = path.split('?');
if (routeInfo) {
this.incomingRouteParams = { ...routeInfo, routeAction: 'push', routeDirection: 'none' };
if (routeInfo.pathname === pathname) {
this.incomingRouteParams.routeOptions = routeOptions;
this.props.history.push(routeInfo.pathname + (routeInfo.search || ''));
} else {
this.incomingRouteParams.pathname = pathname;
this.incomingRouteParams.search = search ? '?' + search : undefined;
this.incomingRouteParams.routeOptions = routeOptions;
this.props.history.push(pathname + (search ? '?' + search : ''));
}
} else {
this.handleNavigate(pathname, 'push', 'none', undefined, routeOptions, tab);
}
}
handleHistoryChange(location: HistoryLocation<LocationState>, action: HistoryAction) {
let leavingLocationInfo: RouteInfo;
if (this.incomingRouteParams) {
if (this.incomingRouteParams.routeAction === 'replace') {
leavingLocationInfo = this.locationHistory.previous();
if (routeInfo) {
const routeParams = {
...incomingRouteParams,
routeAction: 'push' as RouteAction,
routeDirection: 'none' as RouterDirection,
};
if (routeInfo.pathname === pathname) {
routeParams.routeOptions = routeOptions;
setIncomingRouteParams({
...routeParams,
routeOptions,
});
navigate(routeInfo.pathname + (routeInfo.search || ''));
} else {
leavingLocationInfo = this.locationHistory.current();
setIncomingRouteParams({
...routeParams,
pathname,
search: search ? '?' + search : undefined,
routeOptions,
});
navigate(pathname + (search ? '?' + search : ''));
}
} else {
leavingLocationInfo = this.locationHistory.current();
handleNavigate(pathname, 'push', 'none', undefined, routeOptions, tab);
}
};
const handleHistoryChange = (location: Location, action: NavigationType) => {
let leavingLocationInfo: RouteInfo;
if (incomingRouteParams) {
if (incomingRouteParams.routeAction === 'replace') {
leavingLocationInfo = locationHistory.current.previous();
} else {
leavingLocationInfo = locationHistory.current.current();
}
} else {
leavingLocationInfo = locationHistory.current.current();
}
const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
if (leavingUrl !== location.pathname) {
if (!this.incomingRouteParams) {
if (!incomingRouteParams) {
if (action === 'REPLACE') {
this.incomingRouteParams = {
routeAction: 'replace',
routeDirection: 'none',
tab: this.currentTab,
};
setIncomingRouteParams({
routeAction: 'replace' as RouteAction,
routeDirection: 'none' as RouterDirection,
tab: currentTab,
});
}
if (action === 'POP') {
const currentRoute = this.locationHistory.current();
const currentRoute = locationHistory.current.current();
if (currentRoute && currentRoute.pushedByRoute) {
const prevInfo = this.locationHistory.findLastLocation(currentRoute);
this.incomingRouteParams = { ...prevInfo, routeAction: 'pop', routeDirection: 'back' };
const prevInfo = locationHistory.current.findLastLocation(currentRoute);
setIncomingRouteParams({
...prevInfo,
routeAction: 'pop' as RouteAction,
routeDirection: 'back' as RouterDirection,
});
} else {
this.incomingRouteParams = {
routeAction: 'pop',
routeDirection: 'none',
tab: this.currentTab,
};
setIncomingRouteParams({
routeAction: 'pop' as RouteAction,
routeDirection: 'back' as RouterDirection,
tab: currentTab,
});
}
}
if (!this.incomingRouteParams) {
this.incomingRouteParams = {
routeAction: 'push',
if (!incomingRouteParams) {
setIncomingRouteParams({
routeAction: 'push' as RouteAction,
routeDirection: location.state?.direction || 'forward',
routeOptions: location.state?.routerOptions,
tab: this.currentTab,
};
routeOptions: location.state?.routerOptions, // todo @sean review this routeOptions vs. routerOptions
tab: currentTab,
});
}
}
let routeInfo: RouteInfo;
if (this.incomingRouteParams?.id) {
if (incomingRouteParams?.id) {
routeInfo = {
...(this.incomingRouteParams as RouteInfo),
...(incomingRouteParams as RouteInfo),
lastPathname: leavingLocationInfo.pathname,
};
this.locationHistory.add(routeInfo);
locationHistory.current.add(routeInfo);
} else {
const isPushed =
this.incomingRouteParams.routeAction === 'push' && this.incomingRouteParams.routeDirection === 'forward';
const isPushed = incomingRouteParams.routeAction === 'push' && incomingRouteParams.routeDirection === 'forward';
routeInfo = {
id: generateId('routeInfo'),
...this.incomingRouteParams,
...incomingRouteParams,
lastPathname: leavingLocationInfo.pathname,
pathname: location.pathname,
search: location.search,
params: this.props.match.params,
params: params as any, // TODO @sean fix type of route info for params
prevRouteLastPathname: leavingLocationInfo.lastPathname,
};
if (isPushed) {
routeInfo.tab = leavingLocationInfo.tab;
routeInfo.pushedByRoute = leavingLocationInfo.pathname;
} else if (routeInfo.routeAction === 'pop') {
const r = this.locationHistory.findLastLocation(routeInfo);
const r = locationHistory.current.findLastLocation(routeInfo);
routeInfo.pushedByRoute = r?.pushedByRoute;
} else if (routeInfo.routeAction === 'push' && routeInfo.tab !== leavingLocationInfo.tab) {
// If we are switching tabs grab the last route info for the tab and use its pushedByRoute
const lastRoute = this.locationHistory.getCurrentRouteInfoForTab(routeInfo.tab);
const lastRoute = locationHistory.current.getCurrentRouteInfoForTab(routeInfo.tab);
routeInfo.pushedByRoute = lastRoute?.pushedByRoute;
} else if (routeInfo.routeAction === 'replace') {
// Make sure to set the lastPathname, etc.. to the current route so the page transitions out
const currentRouteInfo = this.locationHistory.current();
const currentRouteInfo = locationHistory.current.current();
/**
* If going from /home to /child, then replacing from
* /child to /home, we don't want the route info to
@@ -190,39 +174,28 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
routeInfo.routeDirection = currentRouteInfo?.routeDirection || routeInfo.routeDirection;
routeInfo.routeAnimation = currentRouteInfo?.routeAnimation || routeInfo.routeAnimation;
}
this.locationHistory.add(routeInfo);
locationHistory.current.add(routeInfo);
}
this.setState({
routeInfo,
});
setRouteInfo(routeInfo);
}
this.incomingRouteParams = undefined;
}
setIncomingRouteParams({});
};
/**
* history@4.x uses goBack(), history@5.x uses back()
* TODO: If support for React Router <=5 is dropped
* this logic is no longer needed. We can just
* assume back() is available.
*/
handleNativeBack() {
const history = this.props.history as any;
const goBack = history.goBack || history.back;
goBack();
}
const handleNativeBack = () => {
navigate(-1);
};
handleNavigate(
const handleNavigate = (
path: string,
routeAction: RouteAction,
routeDirection?: RouterDirection,
routeAnimation?: AnimationBuilder,
routeOptions?: any,
tab?: string
) {
this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, {
) => {
setIncomingRouteParams({
...incomingRouteParams,
routeAction,
routeDirection,
routeOptions,
@@ -230,26 +203,28 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
tab,
});
if (routeAction === 'push') {
this.props.history.push(path);
} else {
this.props.history.replace(path);
}
}
navigate(path, { replace: routeAction !== 'push' });
};
handleNavigateBack(defaultHref: string | RouteInfo = '/', routeAnimation?: AnimationBuilder) {
const handleNavigateBack = (defaultHref: string | RouteInfo = '/', routeAnimation?: AnimationBuilder) => {
const config = getConfig();
defaultHref = defaultHref ? defaultHref : config && config.get('backButtonDefaultHref' as any);
const routeInfo = this.locationHistory.current();
if (!defaultHref && config) {
// If the defaultHref wasn't passed in, then we should use the configured defaultHref.
// Developers can set this on their IonicConfig.
defaultHref = config.get('backButtonDefaultHref');
}
const routeInfo = locationHistory.current.current();
if (routeInfo && routeInfo.pushedByRoute) {
const prevInfo = this.locationHistory.findLastLocation(routeInfo);
const prevInfo = locationHistory.current.findLastLocation(routeInfo);
if (prevInfo) {
this.incomingRouteParams = {
setIncomingRouteParams({
...prevInfo,
routeAction: 'pop',
routeDirection: 'back',
routeAction: 'pop' as RouteAction,
routeDirection: 'back' as RouterDirection,
routeAnimation: routeAnimation || routeInfo.routeAnimation,
};
});
if (
routeInfo.lastPathname === routeInfo.pushedByRoute ||
/**
@@ -260,68 +235,74 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
*/
(prevInfo.pathname === routeInfo.pushedByRoute && routeInfo.tab === '' && prevInfo.tab === '')
) {
/**
* history@4.x uses goBack(), history@5.x uses back()
* TODO: If support for React Router <=5 is dropped
* this logic is no longer needed. We can just
* assume back() is available.
*/
const history = this.props.history as any;
const goBack = history.goBack || history.back;
goBack();
navigate(-1);
} else {
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');
handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back');
handleNavigate(defaultHref as string, 'pop', 'back');
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back');
handleNavigate(defaultHref as string, 'pop', 'back');
}
}
};
handleResetTab(tab: string, originalHref: string, originalRouteOptions: any) {
const routeInfo = this.locationHistory.getFirstRouteInfoForTab(tab);
if (routeInfo) {
const newRouteInfo = { ...routeInfo };
newRouteInfo.pathname = originalHref;
newRouteInfo.routeOptions = originalRouteOptions;
this.incomingRouteParams = { ...newRouteInfo, routeAction: 'pop', routeDirection: 'back' };
this.props.history.push(newRouteInfo.pathname + (newRouteInfo.search || ''));
const handleResetTab = (tab: string, originalHref: string, originalRouteOptions: any) => {
const firstRouteForTab = locationHistory.current.getFirstRouteInfoForTab(tab);
if (firstRouteForTab) {
const routeInfo = {
...firstRouteForTab,
pathName: originalHref,
routeOptions: originalRouteOptions,
routeAction: 'pop' as RouteAction,
routeDirection: 'back' as RouterDirection,
};
setIncomingRouteParams(routeInfo);
navigate(routeInfo.pathname + (routeInfo.search || ''));
}
}
};
handleSetCurrentTab(tab: string) {
this.currentTab = tab;
const ri = { ...this.locationHistory.current() };
if (ri.tab !== tab) {
ri.tab = tab;
this.locationHistory.update(ri);
const handleSetCurrentTab = (tab: string) => {
setCurrentTab(tab);
const currentRoute = locationHistory.current.current();
if (currentRoute && currentRoute.tab !== tab) {
const updatedRoute = { ...currentRoute, tab };
locationHistory.current.update(updatedRoute);
}
}
};
render() {
return (
<RouteManagerContext.Provider value={this.routeMangerContextState}>
<NavManager
ionRoute={IonRouteInner}
ionRedirect={{}}
stackManager={StackManager}
routeInfo={this.state.routeInfo!}
onNativeBack={this.handleNativeBack}
onNavigateBack={this.handleNavigateBack}
onNavigate={this.handleNavigate}
onSetCurrentTab={this.handleSetCurrentTab}
onChangeTab={this.handleChangeTab}
onResetTab={this.handleResetTab}
locationHistory={this.locationHistory}
>
{this.props.children}
</NavManager>
</RouteManagerContext.Provider>
);
}
}
const routeManagerContextValue: RouteManagerContextState = {
canGoBack: () => locationHistory.current.canGoBack(),
clearOutlet: viewStack.current.clear,
findViewItemByPathname: viewStack.current.findViewItemByPathname,
getChildrenToRender: viewStack.current.getChildrenToRender,
goBack: () => handleNavigateBack(),
createViewItem: viewStack.current.createViewItem,
findViewItemByRouteInfo: viewStack.current.findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo: viewStack.current.findLeavingViewItemByRouteInfo,
addViewItem: viewStack.current.add,
unMountViewItem: viewStack.current.remove,
};
export const IonRouter = withRouter(IonRouterInner);
IonRouter.displayName = 'IonRouter';
return (
<RouteManagerContext.Provider value={routeManagerContextValue}>
<NavManager
ionRoute={IonRouteInner}
ionRedirect={{}}
stackManager={StackManager}
routeInfo={routeInfo}
onNativeBack={handleNativeBack}
onNavigateBack={handleNavigateBack}
onNavigate={handleNavigate}
onSetCurrentTab={handleSetCurrentTab}
onChangeTab={handleChangeTab}
onResetTab={handleResetTab}
locationHistory={locationHistory.current}
>
{children}
</NavManager>
</RouteManagerContext.Provider>
);
};
export default IonRouter;

View File

@@ -1,7 +1,7 @@
import type { RouteInfo, ViewItem } from '@ionic/react';
import { IonRoute, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
import React from 'react';
import { matchPath } from 'react-router';
import { Route, Routes, matchPath } from 'react-router';
export class ReactRouterViewStack extends ViewStacks {
constructor() {
@@ -23,13 +23,11 @@ export class ReactRouterViewStack extends ViewStacks {
ionRoute: false,
};
const matchProps = {
exact: reactElement.props.exact,
path: reactElement.props.path || reactElement.props.from,
component: reactElement.props.component,
};
if (reactElement.type !== Route) {
console.warn('something is wrong, reactElement is not a Route', reactElement);
}
const match = matchPath(routeInfo.pathname, matchProps);
const match = matchPath(reactElement.props, routeInfo.pathname);
if (reactElement.type === IonRoute) {
viewItem.ionRoute = true;
@@ -47,8 +45,10 @@ export class ReactRouterViewStack extends ViewStacks {
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
const viewItems = this.getViewItemsForOutlet(outletId);
const routesNode = findRoutesNode(ionRouterOutlet.props.children);
// Sync latest routes with viewItems
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
React.Children.forEach(routesNode, (child: React.ReactElement) => {
const viewItem = viewItems.find((v) => {
return matchComponent(child, v.routeData.childProps.path || v.routeData.childProps.from);
});
@@ -58,32 +58,8 @@ export class ReactRouterViewStack extends ViewStacks {
});
const children = viewItems.map((viewItem) => {
let clonedChild;
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
clonedChild = (
<ViewLifeCycleManager
key={`view-${viewItem.id}`}
mount={viewItem.mount}
removeView={() => this.remove(viewItem)}
>
{React.cloneElement(viewItem.reactElement, {
computedMatch: viewItem.routeData.match,
})}
</ViewLifeCycleManager>
);
} else {
if (!viewItem.ionRoute || viewItem.disableIonPageManagement) {
const match = matchComponent(viewItem.reactElement, routeInfo.pathname);
clonedChild = (
<ViewLifeCycleManager
key={`view-${viewItem.id}`}
mount={viewItem.mount}
removeView={() => this.remove(viewItem)}
>
{React.cloneElement(viewItem.reactElement, {
computedMatch: viewItem.routeData.match,
})}
</ViewLifeCycleManager>
);
if (!match && viewItem.routeData.match) {
viewItem.routeData.match = undefined;
@@ -91,6 +67,20 @@ export class ReactRouterViewStack extends ViewStacks {
}
}
const clonedChild = (
<ViewLifeCycleManager
key={`view-${viewItem.id}`}
mount={viewItem.mount}
removeView={() => this.remove(viewItem)}
>
{/*
TODO @sean it is currently required to render a <Routes /> or you will get:
Uncaught Error: A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
*/}
<Routes>{React.cloneElement(viewItem.reactElement)}</Routes>
</ViewLifeCycleManager>
);
return clonedChild;
});
return children;
@@ -140,12 +130,20 @@ export class ReactRouterViewStack extends ViewStacks {
if (mustBeIonRoute && !v.ionRoute) {
return false;
}
if (pathname === undefined) {
// This wasn't needed in react router v5
// it is possible we are re-rendering or calling something too early.
return false;
}
const matchProps = {
exact: forceExact ? true : v.routeData.childProps.exact,
path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component,
// component: v.routeData.childProps.component, // TODO removed in v6
element: v.routeData.childProps.element,
};
const myMatch = matchPath(pathname, matchProps);
const myMatch = matchPath(matchProps, pathname);
if (myMatch) {
viewItem = v;
match = myMatch;
@@ -158,11 +156,12 @@ export class ReactRouterViewStack extends ViewStacks {
// try to find a route that doesn't have a path or from prop, that will be our default route
if (!v.routeData.childProps.path && !v.routeData.childProps.from) {
match = {
path: pathname,
url: pathname,
isExact: true,
pathname,
params: {},
};
// url: pathname,
// isExact: true,
// params: {},
} as any; // TODO @sean review what this is doing
viewItem = v;
return true;
}
@@ -171,13 +170,27 @@ export class ReactRouterViewStack extends ViewStacks {
}
}
const findRoutesNode = (node: React.ReactNode) => {
// Finds the <Routes /> component node
let routesNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
if (child.type === Routes) {
routesNode = child;
}
});
if (routesNode) {
return (routesNode as React.ReactElement).props.children;
}
return undefined;
};
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
const matchProps = {
exact: forceExact ? true : node.props.exact,
path: node.props.path || node.props.from,
component: node.props.component,
element: node.props.element,
};
const match = matchPath(pathname, matchProps);
const match = matchPath(matchProps, pathname);
return match;
}

View File

@@ -1,7 +1,8 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import React from 'react';
import { matchPath } from 'react-router-dom';
import type { PropsWithChildren } from 'react';
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { Route, Routes, matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
@@ -11,76 +12,91 @@ interface StackManagerProps {
routeInfo: RouteInfo;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StackManagerState {}
const isViewVisible = (el: HTMLElement) =>
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
id: string;
context!: React.ContextType<typeof RouteManagerContext>;
ionRouterOutlet?: React.ReactElement;
routerOutletElement: HTMLIonRouterOutletElement | undefined;
prevProps?: StackManagerProps;
skipTransition: boolean;
export const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
const { routeInfo } = props;
const {
findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo,
findViewItemByPathname,
createViewItem,
addViewItem,
goBack,
getChildrenToRender,
clearOutlet,
} = useContext(RouteManagerContext);
stackContextValue: StackContextState = {
registerIonPage: this.registerIonPage.bind(this),
isInOutlet: () => true,
};
const routerOutletRef = useRef<HTMLIonRouterOutletElement>();
const routerOutletElement = routerOutletRef.current;
const ionRouterOutletRef = useRef<React.ReactElement>();
const skipTransitionRef = useRef(false);
const clearOutletTimeout = useRef(null);
const pendingPageTransitionRef = useRef(false);
const prevProps = useRef<{ routeInfo: RouteInfo }>();
private clearOutletTimeout: any;
private pendingPageTransition = false;
const forceUpdate = useReducer((x) => x + 1, 0)[1];
constructor(props: StackManagerProps) {
super(props);
this.registerIonPage = this.registerIonPage.bind(this);
this.transitionPage = this.transitionPage.bind(this);
this.handlePageTransition = this.handlePageTransition.bind(this);
this.id = generateId('routerOutlet');
this.prevProps = undefined;
this.skipTransition = false;
}
const [id] = useState(generateId('routerOutlet'));
componentDidMount() {
if (this.clearOutletTimeout) {
/**
* The clearOutlet integration with React Router is a bit hacky.
* It uses a timeout to clear the outlet after a transition.
* In React v18, components are mounted and unmounted in development mode
* to check for side effects.
*
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
* which should only happen in development mode and as a result of a hot reload.
*/
clearTimeout(this.clearOutletTimeout);
const stackContextValue: StackContextState = useMemo(
() => ({
isInOutlet: () => true,
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => {
const foundView = findViewItemByRouteInfo(routeInfo, id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (oldPageElement === page) {
return;
}
}
handlePageTransition(routeInfo);
},
}),
[routerOutletElement]
);
useEffect(() => {
if (routerOutletElement) {
// Mount behavior for the initial route
setupRouterOutlet(routerOutletElement);
handlePageTransition(routeInfo);
}
if (this.routerOutletElement) {
this.setupRouterOutlet(this.routerOutletElement);
this.handlePageTransition(this.props.routeInfo);
}, [routerOutletElement]);
useEffect(() => {
const { pathname } = routeInfo;
if (pathname !== prevProps.current?.routeInfo.pathname) {
prevProps.current = props;
handlePageTransition(routeInfo);
} else if (pendingPageTransitionRef.current) {
handlePageTransition(routeInfo);
pendingPageTransitionRef.current = false;
}
}
}, [routeInfo]);
componentDidUpdate(prevProps: StackManagerProps) {
const { pathname } = this.props.routeInfo;
const { pathname: prevPathname } = prevProps.routeInfo;
useEffect(() => {
return () => {
clearOutlet(id);
if (clearOutletTimeout.current) {
clearTimeout(clearOutletTimeout.current);
clearOutletTimeout.current = null;
}
};
}, []);
if (pathname !== prevPathname) {
this.prevProps = prevProps;
this.handlePageTransition(this.props.routeInfo);
} else if (this.pendingPageTransition) {
this.handlePageTransition(this.props.routeInfo);
this.pendingPageTransition = false;
}
}
componentWillUnmount() {
this.clearOutletTimeout = this.context.clearOutlet(this.id);
}
async handlePageTransition(routeInfo: RouteInfo) {
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
const handlePageTransition = (routeInfo: RouteInfo) => {
if (!routerOutletElement || !routerOutletElement.commit) {
/**
* The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page.
@@ -88,16 +104,16 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* Set a flag to indicate that we should transition the page after
* the component has updated.
*/
this.pendingPageTransition = true;
pendingPageTransitionRef.current = true;
} else {
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
// Check if leavingViewItem should be unmounted
// Check if the leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
@@ -110,13 +126,15 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
}
}
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement; // TODO @sean can we return React.ReactElement so we don't need to cast?
if (enteringViewItem) {
// If the entering view is already in the stack, then we need to clone it
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
// Otherwise we need to create a new view item
enteringViewItem = createViewItem(id, enteringRoute, routeInfo);
addViewItem(enteringViewItem);
}
if (enteringViewItem && enteringViewItem.ionPageElement) {
@@ -143,8 +161,8 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
/**
@@ -173,57 +191,39 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
transitionPage(routeInfo, enteringViewItem, leavingViewItem!); // TODO @sean why do we need to use ! on the leavingViewItem here? Didn't need to originally.
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
// transition to finish.
// setTimeout(() => {
// another outlet, so hide this leavingView.
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
// }, 250);
}
this.forceUpdate();
// This causes the router outlet to re-render with the updated view items.
// Without it, a push navigation will remove the previous route's view item,
// but will not render the new route's view item.
// this.forceUpdate();
forceUpdate();
}
}
};
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (oldPageElement === page) {
return;
}
}
this.handlePageTransition(routeInfo);
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
const setupRouterOutlet = (routerOutlet: HTMLIonRouterOutletElement) => {
const canStart = (): boolean => {
const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) {
return false;
}
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); // TODO figure out what this does
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
return (
!!enteringViewItem &&
@@ -247,14 +247,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
};
const onStart = async () => {
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); // TODO figure out what this does
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/**
* When the gesture starts, kick off
@@ -262,30 +260,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* via a swipe gesture.
*/
if (enteringViewItem && leavingViewItem) {
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
}
return Promise.resolve();
};
const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) {
this.skipTransition = true;
this.context.goBack();
skipTransitionRef.current = true;
goBack();
} else {
/**
* In the event that the swipe
* gesture was aborted, we should
* re-hide the page that was going to enter.
*/
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); // TODO figure out what this does
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/**
* Ionic React has a design defect where it
@@ -309,17 +305,17 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
onStart,
onEnd,
};
}
};
async transitionPage(
const transitionPage = async (
routeInfo: RouteInfo,
enteringViewItem: ViewItem,
leavingViewItem?: ViewItem,
leavingViewItem: ViewItem,
direction?: 'forward' | 'back',
progressAnimation = false
) {
) => {
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = this.skipTransition;
const skipTransition = skipTransitionRef.current;
/**
* If the transition was handled
@@ -342,105 +338,112 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* transition triggered by handlePageTransition
* in componentDidUpdate.
*/
this.skipTransition = false;
skipTransitionRef.current = false;
} else {
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
enteringEl.classList.add('ion-page', 'ion-page-invisible');
}
await routerOutlet.commit(enteringEl, leavingEl, {
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
if (routerOutletElement) {
await routerOutletElement.commit(enteringEl, leavingEl, {
duration: skipTransitionRef.current || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
}
};
const routerOutlet = this.routerOutletElement!;
const routeInfoFallbackDirection =
const routerInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routeInfoFallbackDirection;
const directionToUse = direction ?? routerInfoFallbackDirection;
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
if (enteringViewItem?.ionPageElement && routerOutletElement) {
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
// If a page is transitioning to another version of itself
// we clone it so we can have an animation to show
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
if (match) {
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
if (newLeavingElement) {
this.routerOutletElement.appendChild(newLeavingElement);
routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement);
routerOutletElement.removeChild(newLeavingElement);
}
} else {
await runCommit(enteringViewItem.ionPageElement, undefined);
}
} else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
if (leavingViewItem?.ionPageElement && !progressAnimation) {
const { ionPageElement } = leavingViewItem;
ionPageElement.setAttribute('aria-hidden', 'true');
ionPageElement.classList.add('ion-page-hidden');
}
}
}
}
};
render() {
const { children } = this.props;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
this.ionRouterOutlet = ionRouterOutlet;
const renderComponents = () => {
const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
ionRouterOutletRef.current = ionRouterOutlet;
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
this.forceUpdate();
const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
forceUpdate();
});
return (
<StackContext.Provider value={this.stackContextValue}>
{React.cloneElement(
ionRouterOutlet as any,
{
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
this.routerOutletElement = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
},
components
)}
</StackContext.Provider>
);
}
return cloneElement(ionRouterOutlet, {
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
routerOutletRef.current = node;
const { ref } = ionRouterOutlet as any; // TODO @sean why do we need to cast any here? Is there a better type?
if (typeof ref === 'function') {
ref(node);
}
},
children: components,
});
};
static get contextType() {
return RouteManagerContext;
}
}
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
};
export default StackManager;
const findRoutesNode = (node: React.ReactNode) => {
// Finds the <Routes /> component node
let routesNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
if (child.type === Routes) {
routesNode = child;
}
});
if (routesNode) {
return (routesNode as React.ReactElement).props.children;
}
return undefined;
};
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
let matchedNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
const matchProps = {
exact: child.props.exact,
path: child.props.path || child.props.from,
component: child.props.component,
};
const match = matchPath(routeInfo.pathname, matchProps);
if (match) {
matchedNode = child;
const routesNode = findRoutesNode(node);
if (!routesNode) {
console.error('No <Routes /> component found in the stack');
}
React.Children.forEach(routesNode as React.ReactElement, (child: React.ReactElement) => {
if (child.type === Route) {
const match = matchPath(child.props, routeInfo.pathname);
if (match) {
matchedNode = child;
}
}
});
@@ -464,7 +467,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
path: node.props.path || node.props.from,
component: node.props.component,
};
const match = matchPath(pathname, matchProps);
const match = matchPath(matchProps, pathname);
return match;
}

View File

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

View File

@@ -1,5 +1,6 @@
import type { JSX as LocalJSX } from '@ionic/core/components';
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { useContext } from 'react';
import { NavContext } from '../contexts/NavContext';
import OutletPageManager from '../routing/OutletPageManager';
@@ -18,43 +19,35 @@ interface InternalProps extends Props {
forwardedRef?: React.ForwardedRef<HTMLIonRouterOutletElement>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface InternalState {}
const IonRouterOutletContainer = (props: PropsWithChildren<InternalProps>) => {
const { hasIonicRouter, routeInfo, getStackManager } = useContext(NavContext);
const { children, forwardedRef, ...restProps } = props;
class IonRouterOutletContainer extends React.Component<InternalProps, InternalState> {
context!: React.ContextType<typeof NavContext>;
if (hasIonicRouter()) {
const StackManager = getStackManager();
constructor(props: InternalProps) {
super(props);
}
render() {
const StackManager = this.context.getStackManager();
const { children, forwardedRef, ...props } = this.props;
return this.context.hasIonicRouter() ? (
props.ionPage ? (
<OutletPageManager StackManager={StackManager} routeInfo={this.context.routeInfo} {...props}>
if (restProps.ionPage) {
return (
<OutletPageManager StackManager={StackManager} routeInfo={routeInfo} {...restProps}>
{children}
</OutletPageManager>
) : (
<StackManager routeInfo={this.context.routeInfo}>
<IonRouterOutletInner {...props} forwardedRef={forwardedRef}>
{children}
</IonRouterOutletInner>
</StackManager>
)
) : (
<IonRouterOutletInner ref={forwardedRef} {...this.props}>
{this.props.children}
</IonRouterOutletInner>
);
}
return (
<StackManager routeInfo={routeInfo}>
<IonRouterOutletInner {...restProps} forwardedRef={forwardedRef}>
{children}
</IonRouterOutletInner>
</StackManager>
);
}
static get contextType() {
return NavContext;
}
}
return (
<IonRouterOutletInner ref={forwardedRef} {...restProps}>
{children}
</IonRouterOutletInner>
);
};
export const IonRouterOutlet = createForwardRef<Props & IonicReactProps, HTMLIonRouterOutletElement>(
IonRouterOutletContainer,

View File

@@ -1,5 +1,5 @@
import type { JSX as LocalJSX } from '@ionic/core/components';
import React, { Fragment } from 'react';
import React, { Fragment, cloneElement, useContext, useEffect, useRef } from 'react';
import { NavContext } from '../../contexts/NavContext';
import PageManager from '../../routing/PageManager';
@@ -60,123 +60,102 @@ const tabsInner: React.CSSProperties = {
contain: 'layout size style',
};
export const IonTabs = /*@__PURE__*/ (() =>
class extends React.Component<Props> {
context!: React.ContextType<typeof NavContext>;
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
selectTabHandler?: (tag: string) => boolean;
tabBarRef = React.createRef<any>();
export const IonTabs = (props: Props) => {
const { hasIonicRouter, routeInfo } = useContext(NavContext);
const tabBarRef = useRef<any>(null); // TODO @sean find the type for this
ionTabContextState: IonTabsContextState = {
activeTab: undefined,
selectTab: () => false,
const ionTabContextState: IonTabsContextState = {
activeTab: undefined,
selectTab: () => false,
};
useEffect(() => {
if (tabBarRef.current) {
ionTabContextState.activeTab = tabBarRef.current.state.activeTab;
// Overrides the web component implementation of this method
tabBarRef.current.setActiveTabOnContext = (tab: string) => {
ionTabContextState.activeTab = tab;
};
ionTabContextState.selectTab = tabBarRef.current.selectTab;
}
}, []);
let outlet: React.ReactElement | undefined;
let tabBar: React.ReactElement | undefined;
const { className, onIonTabsDidChange, onIonTabsWillChange, ...restProps } = props;
const children =
typeof props.children === 'function' ? (props.children as ChildFunction)(ionTabContextState) : props.children;
React.Children.forEach(children, (child: any) => {
// eslint-disable-next-line no-prototype-builtins
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
return;
}
if (child.type === IonRouterOutlet || child.type.isRouterOutlet) {
outlet = cloneElement(child);
} else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
outlet = child.props.children[0]; // TODO @sean why do we cloneElement everywhere but here?
}
let childProps: any = {
ref: tabBarRef,
};
constructor(props: Props) {
super(props);
/**
* Only pass these props down from IonTabs to
* IonTabsBar if they are defined. Otherwise if you have
* a handler set on IonTabBar it will be overridden.
*/
if (onIonTabsDidChange !== undefined) {
childProps = {
...childProps,
onIonTabsDidChange,
};
}
componentDidMount() {
if (this.tabBarRef.current) {
// Grab initial value
this.ionTabContextState.activeTab = this.tabBarRef.current.state.activeTab;
// Override method
this.tabBarRef.current.setActiveTabOnContext = (tab: string) => {
this.ionTabContextState.activeTab = tab;
};
this.ionTabContextState.selectTab = this.tabBarRef.current.selectTab;
}
if (onIonTabsWillChange !== undefined) {
childProps = {
...childProps,
onIonTabsWillChange,
};
}
render() {
let outlet: React.ReactElement<{}> | undefined;
let tabBar: React.ReactElement | undefined;
const { className, onIonTabsDidChange, onIonTabsWillChange, ...props } = this.props;
if (child.type === IonTabBar || child.type.isTabBar) {
tabBar = cloneElement(child, childProps);
} else if (child.type === Fragment && child.props.children[1].type === IonTabBar) {
tabBar = cloneElement(child.props.children[1], childProps);
}
});
const children =
typeof this.props.children === 'function'
? (this.props.children as ChildFunction)(this.ionTabContextState)
: this.props.children;
if (!outlet) {
throw new Error('[Ionic Error]: IonTabs must contain an <IonRouterOutlet />');
}
React.Children.forEach(children, (child: any) => {
// eslint-disable-next-line no-prototype-builtins
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
return;
}
if (child.type === IonRouterOutlet || child.type.isRouterOutlet) {
outlet = React.cloneElement(child);
} else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
outlet = child.props.children[0];
}
let childProps: any = {
ref: this.tabBarRef,
};
/**
* Only pass these props
* down from IonTabs to IonTabBar
* if they are defined, otherwise
* if you have a handler set on
* IonTabBar it will be overridden.
*/
if (onIonTabsDidChange !== undefined) {
childProps = {
...childProps,
onIonTabsDidChange,
};
}
if (onIonTabsWillChange !== undefined) {
childProps = {
...childProps,
onIonTabsWillChange,
};
}
if (child.type === IonTabBar || child.type.isTabBar) {
tabBar = React.cloneElement(child, childProps);
} else if (
child.type === Fragment &&
(child.props.children[1].type === IonTabBar || child.props.children[1].type.isTabBar)
) {
tabBar = React.cloneElement(child.props.children[1], childProps);
}
});
if (!outlet) {
throw new Error('IonTabs must contain an IonRouterOutlet');
}
if (!tabBar) {
throw new Error('IonTabs needs a IonTabBar');
}
return (
<IonTabsContext.Provider value={this.ionTabContextState}>
{this.context.hasIonicRouter() ? (
<PageManager className={className ? `${className}` : ''} routeInfo={this.context.routeInfo} {...props}>
<ion-tabs className="ion-tabs" style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{outlet}
</div>
{tabBar.props.slot === 'bottom' ? tabBar : null}
</ion-tabs>
</PageManager>
) : (
<div className={className ? `${className}` : 'ion-tabs'} {...props} style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{outlet}
</div>
{tabBar.props.slot === 'bottom' ? tabBar : null}
if (!tabBar) {
throw new Error('[Ionic Error]: IonTabs must contain an <IonTabBar />');
}
return (
<IonTabsContext.Provider value={ionTabContextState}>
{hasIonicRouter() ? (
<PageManager className={className ? `${className}` : ''} routeInfo={routeInfo} {...restProps}>
<ion-tabs className="ion-tabs" style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{outlet}
</div>
)}
</IonTabsContext.Provider>
);
}
static get contextType() {
return NavContext;
}
})();
{tabBar.props.slot === 'bottom' ? tabBar : null}
</ion-tabs>
</PageManager>
) : (
<div className={className ? `${className}` : 'ion-tabs'} {...restProps} style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{outlet}
</div>
{tabBar.props.slot === 'bottom' ? tabBar : null}
</div>
)}
</IonTabsContext.Provider>
);
};

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { createContext } from 'react';
export interface IonTabsContextState {
activeTab: string | undefined;
selectTab: (tab: string) => boolean;
}
export const IonTabsContext = React.createContext<IonTabsContextState>({
export const IonTabsContext = createContext<IonTabsContextState>({
activeTab: undefined,
selectTab: () => false,
});

View File

@@ -1,9 +1,9 @@
import type { AnimationBuilder } from '@ionic/core/components';
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { useCallback, useEffect } from 'react';
import type { IonRouterContextState } from '../components/IonRouterContext';
import { IonRouterContext } from '../components/IonRouterContext';
import type { NavContextState } from '../contexts/NavContext';
import { NavContext } from '../contexts/NavContext';
import type { RouteAction } from '../models/RouteAction';
import type { RouteInfo } from '../models/RouteInfo';
@@ -36,8 +36,27 @@ interface NavManagerProps {
locationHistory: LocationHistory;
}
export class NavManager extends React.PureComponent<NavManagerProps, NavContextState> {
ionRouterContextValue: IonRouterContextState = {
export const NavManager = ({ children, ...props }: PropsWithChildren<NavManagerProps>) => {
const {
routeInfo,
onNativeBack,
onNavigateBack,
onNavigate,
onSetCurrentTab,
onChangeTab,
onResetTab,
stackManager,
ionRedirect,
ionRoute,
locationHistory,
} = props;
const getPageManager = useCallback(() => PageManager, []);
const getIonRedirect = useCallback(() => ionRedirect, []);
const getIonRoute = useCallback(() => ionRoute, []);
const getStackManager = useCallback(() => stackManager, []);
const ionRouterContextValue: IonRouterContextState = {
push: (
pathname: string,
routerDirection?: RouterDirection,
@@ -45,95 +64,67 @@ export class NavManager extends React.PureComponent<NavManagerProps, NavContextS
routerOptions?: RouterOptions,
animationBuilder?: AnimationBuilder
) => {
this.navigate(pathname, routerDirection, routeAction, animationBuilder, routerOptions);
navigate(pathname, routerDirection, routeAction, animationBuilder, routerOptions);
},
back: (animationBuilder?: AnimationBuilder) => {
this.goBack(undefined, animationBuilder);
goBack(undefined, animationBuilder);
},
canGoBack: () => this.props.locationHistory.canGoBack(),
nativeBack: () => this.props.onNativeBack(),
routeInfo: this.props.routeInfo,
canGoBack: () => locationHistory.canGoBack(),
nativeBack: () => onNativeBack(),
routeInfo,
};
constructor(props: NavManagerProps) {
super(props);
this.state = {
goBack: this.goBack.bind(this),
hasIonicRouter: () => true,
navigate: this.navigate.bind(this),
getIonRedirect: this.getIonRedirect.bind(this),
getIonRoute: this.getIonRoute.bind(this),
getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this),
routeInfo: this.props.routeInfo,
setCurrentTab: this.props.onSetCurrentTab,
changeTab: this.props.onChangeTab,
resetTab: this.props.onResetTab,
useEffect(() => {
const handleHardwareBackButton = (e: any) => {
e.detail.register(0, (processNextHandler: () => void) => {
onNativeBack();
processNextHandler();
});
};
}
componentDidMount() {
if (typeof document !== 'undefined') {
this.handleHardwareBackButton = this.handleHardwareBackButton.bind(this);
document.addEventListener('ionBackButton', this.handleHardwareBackButton);
document.addEventListener('ionBackButton', handleHardwareBackButton);
}
}
componentWillUnmount() {
if (typeof document !== 'undefined') {
document.removeEventListener('ionBackButton', this.handleHardwareBackButton);
}
}
return () => {
if (typeof document !== 'undefined') {
document.removeEventListener('ionBackButton', handleHardwareBackButton);
}
};
}, []);
handleHardwareBackButton(e: any) {
e.detail.register(0, (processNextHandler: () => void) => {
this.nativeGoBack();
processNextHandler();
});
}
const goBack = (route?: string | RouteInfo, animationBuilder?: AnimationBuilder) => {
onNavigateBack(route, animationBuilder);
};
goBack(route?: string | RouteInfo, animationBuilder?: AnimationBuilder) {
this.props.onNavigateBack(route, animationBuilder);
}
nativeGoBack() {
this.props.onNativeBack();
}
navigate(
const navigate = (
path: string,
direction: RouterDirection = 'forward',
action: RouteAction = 'push',
animationBuilder?: AnimationBuilder,
options?: any,
tab?: string
) {
this.props.onNavigate(path, action, direction, animationBuilder, options, tab);
}
) => {
onNavigate(path, action, direction, animationBuilder, options, tab);
};
getPageManager() {
return PageManager;
}
getIonRedirect() {
return this.props.ionRedirect;
}
getIonRoute() {
return this.props.ionRoute;
}
getStackManager() {
return this.props.stackManager;
}
render() {
return (
<NavContext.Provider value={{ ...this.state, routeInfo: this.props.routeInfo }}>
<IonRouterContext.Provider value={{ ...this.ionRouterContextValue, routeInfo: this.props.routeInfo }}>
{this.props.children}
</IonRouterContext.Provider>
</NavContext.Provider>
);
}
}
return (
<NavContext.Provider
value={{
goBack,
hasIonicRouter: () => true,
navigate,
getIonRedirect,
getIonRoute,
getStackManager,
getPageManager,
routeInfo,
setCurrentTab: onSetCurrentTab,
changeTab: onChangeTab,
resetTab: onResetTab,
}}
>
<IonRouterContext.Provider value={{ ...ionRouterContextValue, routeInfo }}>{children}</IonRouterContext.Provider>
</NavContext.Provider>
);
};

View File

@@ -1,7 +1,9 @@
import { componentOnReady } from '@ionic/core/components';
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { IonRouterOutletInner } from '../components/inner-proxies';
import type { IonLifeCycleContextInterface } from '../contexts/IonLifeCycleContext';
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
import type { RouteInfo } from '../models';
@@ -11,89 +13,81 @@ interface OutletPageManagerProps {
className?: string;
forwardedRef?: React.ForwardedRef<HTMLIonRouterOutletElement>;
routeInfo?: RouteInfo;
// TODO @sean I don't think we can get the type, it comes from @ionic/react, but we don't have a dep against it for react-router
StackManager: any; // TODO(FW-2959): type
}
export class OutletPageManager extends React.Component<OutletPageManagerProps> {
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
context!: React.ContextType<typeof StackContext>;
ionRouterOutlet: HTMLIonRouterOutletElement | undefined;
outletIsReady: boolean;
const OutletPageManager = ({ children, ...props }: PropsWithChildren<OutletPageManagerProps>) => {
const { registerIonPage } = useContext(StackContext);
constructor(props: OutletPageManagerProps) {
super(props);
const ionRouterOutlet = useRef<HTMLIonRouterOutletElement>(null);
const ionLifeCycleContext = useRef<IonLifeCycleContextInterface>();
this.outletIsReady = false;
}
const [outletIsReady, setOutletIsReady] = useState(false);
componentDidMount() {
if (this.ionRouterOutlet) {
const { routeInfo, StackManager } = props;
useEffect(() => {
const routerOutlet = ionRouterOutlet.current;
if (routerOutlet) {
/**
* This avoids multiple raf calls
* when React unmounts + remounts components.
*/
if (!this.outletIsReady) {
componentOnReady(this.ionRouterOutlet, () => {
this.outletIsReady = true;
this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
if (!outletIsReady) {
componentOnReady(routerOutlet, () => {
registerIonPage(routerOutlet!, routeInfo!);
setOutletIsReady(true);
});
}
this.ionRouterOutlet.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
this.ionRouterOutlet.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
this.ionRouterOutlet.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
this.ionRouterOutlet.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
routerOutlet.addEventListener('ionViewWillEnter', ionViewWillEnterHandler);
routerOutlet.addEventListener('ionViewDidEnter', ionViewDidEnterHandler);
routerOutlet.addEventListener('ionViewWillLeave', ionViewWillLeaveHandler);
routerOutlet.addEventListener('ionViewDidLeave', ionViewDidLeaveHandler);
}
}
componentWillUnmount() {
if (this.ionRouterOutlet) {
this.ionRouterOutlet.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
this.ionRouterOutlet.removeEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
this.ionRouterOutlet.removeEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
this.ionRouterOutlet.removeEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
}
}
return () => {
if (routerOutlet) {
routerOutlet.removeEventListener('ionViewWillEnter', ionViewWillEnterHandler);
routerOutlet.removeEventListener('ionViewDidEnter', ionViewDidEnterHandler);
routerOutlet.removeEventListener('ionViewWillLeave', ionViewWillLeaveHandler);
routerOutlet.removeEventListener('ionViewDidLeave', ionViewDidLeaveHandler);
}
};
}, [ionRouterOutlet, outletIsReady, routeInfo]);
ionViewWillEnterHandler() {
this.ionLifeCycleContext.ionViewWillEnter();
}
const ionViewWillEnterHandler = () => {
ionLifeCycleContext.current!.ionViewWillEnter();
};
ionViewDidEnterHandler() {
this.ionLifeCycleContext.ionViewDidEnter();
}
const ionViewDidEnterHandler = () => {
ionLifeCycleContext.current!.ionViewDidEnter();
};
ionViewWillLeaveHandler() {
this.ionLifeCycleContext.ionViewWillLeave();
}
const ionViewWillLeaveHandler = () => {
ionLifeCycleContext.current!.ionViewWillLeave();
};
ionViewDidLeaveHandler() {
this.ionLifeCycleContext.ionViewDidLeave();
}
const ionViewDidLeaveHandler = () => {
ionLifeCycleContext.current!.ionViewDidLeave();
};
render() {
const { StackManager, children, routeInfo, ...props } = this.props;
return (
<IonLifeCycleContext.Consumer>
{(context) => {
this.ionLifeCycleContext = context;
return (
<StackManager routeInfo={routeInfo}>
<IonRouterOutletInner
setRef={(val: HTMLIonRouterOutletElement) => (this.ionRouterOutlet = val)}
{...props}
>
{children}
</IonRouterOutletInner>
</StackManager>
);
}}
</IonLifeCycleContext.Consumer>
);
}
return (
<IonLifeCycleContext.Consumer>
{(context) => {
ionLifeCycleContext.current = context;
return (
<StackManager routeInfo={routeInfo}>
<IonRouterOutletInner ref={ionRouterOutlet} {...props}>
{children}
</IonRouterOutletInner>
</StackManager>
);
}}
</IonLifeCycleContext.Consumer>
);
};
static get contextType() {
return StackContext;
}
}
export default OutletPageManager;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { useContext, useEffect, useRef } from 'react';
import { mergeRefs } from '../components/react-component-lib/utils';
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
@@ -12,77 +13,68 @@ interface PageManagerProps {
routeInfo?: RouteInfo;
}
export class PageManager extends React.PureComponent<PageManagerProps> {
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
context!: React.ContextType<typeof StackContext>;
ionPageElementRef: React.RefObject<HTMLDivElement>;
stableMergedRefs: React.RefCallback<HTMLDivElement>;
const PageManager = ({ children, ...props }: PropsWithChildren<PageManagerProps>) => {
const { className, forwardedRef, routeInfo, ...restProps } = props;
constructor(props: PageManagerProps) {
super(props);
this.ionPageElementRef = React.createRef();
// React refs must be stable (not created inline).
this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
}
const ionLifeCycleContext = useContext(IonLifeCycleContext);
const context = useContext(StackContext);
componentDidMount() {
if (this.ionPageElementRef.current) {
if (this.context.isInOutlet()) {
this.ionPageElementRef.current.classList.add('ion-page-invisible');
const ionPageElementRef = useRef<HTMLDivElement>(null);
const stableMergedRefs = mergeRefs(ionPageElementRef, forwardedRef);
useEffect(() => {
const ionPageElement = ionPageElementRef.current;
if (ionPageElement) {
if (context.isInOutlet()) {
ionPageElement.classList.add('ion-page-invisible');
}
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
this.ionPageElementRef.current.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
context.registerIonPage(ionPageElement, routeInfo!);
ionPageElement.addEventListener('ionViewWillEnter', ionViewWillEnterHandler);
ionPageElement.addEventListener('ionViewDidEnter', ionViewDidEnterHandler);
ionPageElement.addEventListener('ionViewWillLeave', ionViewWillLeaveHandler);
ionPageElement.addEventListener('ionViewDidLeave', ionViewDidLeaveHandler);
}
}
componentWillUnmount() {
if (this.ionPageElementRef.current) {
this.ionPageElementRef.current.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
this.ionPageElementRef.current.removeEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
this.ionPageElementRef.current.removeEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
this.ionPageElementRef.current.removeEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
}
}
return () => {
if (ionPageElement) {
ionPageElement.removeEventListener('ionViewWillEnter', ionViewWillEnterHandler);
ionPageElement.removeEventListener('ionViewDidEnter', ionViewDidEnterHandler);
ionPageElement.removeEventListener('ionViewWillLeave', ionViewWillLeaveHandler);
ionPageElement.removeEventListener('ionViewDidLeave', ionViewDidLeaveHandler);
}
};
}, []);
ionViewWillEnterHandler() {
this.ionLifeCycleContext.ionViewWillEnter();
}
const ionViewWillEnterHandler = () => {
ionLifeCycleContext.ionViewWillEnter();
};
ionViewDidEnterHandler() {
this.ionLifeCycleContext.ionViewDidEnter();
}
const ionViewDidEnterHandler = () => {
ionLifeCycleContext.ionViewDidEnter();
};
ionViewWillLeaveHandler() {
this.ionLifeCycleContext.ionViewWillLeave();
}
const ionViewWillLeaveHandler = () => {
ionLifeCycleContext.ionViewWillLeave();
};
ionViewDidLeaveHandler() {
this.ionLifeCycleContext.ionViewDidLeave();
}
const ionViewDidLeaveHandler = () => {
ionLifeCycleContext.ionViewDidLeave();
};
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { className, children, routeInfo, forwardedRef, ...props } = this.props;
return (
<IonLifeCycleContext.Consumer>
{() => {
return (
<div className={className ? `${className} ion-page` : 'ion-page'} ref={stableMergedRefs} {...restProps}>
{children}
</div>
);
}}
</IonLifeCycleContext.Consumer>
);
};
return (
<IonLifeCycleContext.Consumer>
{(context) => {
this.ionLifeCycleContext = context;
return (
<div className={className ? `${className} ion-page` : `ion-page`} ref={this.stableMergedRefs} {...props}>
{children}
</div>
);
}}
</IonLifeCycleContext.Consumer>
);
}
static get contextType() {
return StackContext;
}
}
export default PageManager;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { DefaultIonLifeCycleContext, IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
@@ -7,49 +8,32 @@ interface ViewTransitionManagerProps {
mount: boolean;
}
interface ViewTransitionManagerState {
show: boolean;
}
export const ViewLifeCycleManager = ({ children, ...props }: PropsWithChildren<ViewTransitionManagerProps>) => {
const ionLifeCycleContext = useRef(new DefaultIonLifeCycleContext());
export class ViewLifeCycleManager extends React.Component<ViewTransitionManagerProps, ViewTransitionManagerState> {
ionLifeCycleContext = new DefaultIonLifeCycleContext();
private _isMounted = false;
const [isMounted, setIsMounted] = useState(false);
const [show, setShow] = useState(true);
constructor(props: ViewTransitionManagerProps) {
super(props);
const { mount, removeView } = props;
this.ionLifeCycleContext.onComponentCanBeDestroyed(() => {
if (!this.props.mount) {
if (this._isMounted) {
this.setState(
{
show: false,
},
() => this.props.removeView()
);
}
useEffect(() => {
setIsMounted(true);
return () => {
setIsMounted(false);
};
}, []);
useEffect(() => {
ionLifeCycleContext.current.onComponentCanBeDestroyed(() => {
if (!mount && isMounted) {
setShow(false);
removeView();
}
});
}, [mount, isMounted, removeView]);
this.state = {
show: true,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { show } = this.state;
return (
<IonLifeCycleContext.Provider value={this.ionLifeCycleContext}>
{show && this.props.children}
</IonLifeCycleContext.Provider>
);
}
}
return (
<IonLifeCycleContext.Provider value={ionLifeCycleContext.current}>{show && children}</IonLifeCycleContext.Provider>
);
};

View File

@@ -8,17 +8,15 @@
"name": "test-app",
"version": "0.0.1",
"dependencies": {
"@ionic/react": "^6.6.1",
"@ionic/react-router": "^6.6.1",
"@ionic/react": "^7.0.0",
"@ionic/react-router": "^7.0.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"ionicons": "^6.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0",
"react-scripts": "^5.0.0",
"typescript": "^4.1.3"
},
@@ -33,6 +31,9 @@
"serve": "^14.0.1",
"wait-on": "^6.0.0",
"webpack-cli": "^4.9.1"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/@adobe/css-tools": {
@@ -2367,22 +2368,54 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
},
"node_modules/@ionic/core": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.6.1.tgz",
"integrity": "sha512-+LMBk7kUX55rvYQ35AiAXPNzbNm3zNx9ginvuCzByguMjl+N63lpdPzIEfeRURkmq7NByD1VqpodMj5c6Oq2KQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.1.1.tgz",
"integrity": "sha512-5tdOSUiAkAfCtaP94ADugRskVxdwjRl+lRmOQnInkHnbUmA9eo2sCLCAO2UHfSL6VCP8BOw41NGlz4ad+Ivijw==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.1.3",
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
"tslib": "^2.1.0"
}
},
"node_modules/@ionic/react": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.6.1.tgz",
"integrity": "sha512-gq8FzC0CAPt6MpOFethe9+zIU7jg1JyWPWRANJ/UudlF05f2eFOzLgqe/EH0uIIsuDjeoM50hrqfuvg6x2j3UQ==",
"node_modules/@ionic/core/node_modules/@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g==",
"bin": {
"stencil": "bin/stencil"
},
"engines": {
"node": ">=14.10.0",
"npm": ">=6.0.0"
}
},
"node_modules/@ionic/core/node_modules/ionicons": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.0.tgz",
"integrity": "sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==",
"dependencies": {
"@ionic/core": "6.6.1",
"ionicons": "^6.1.3",
"@stencil/core": "^2.18.0"
}
},
"node_modules/@ionic/core/node_modules/ionicons/node_modules/@stencil/core": {
"version": "2.22.3",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz",
"integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==",
"bin": {
"stencil": "bin/stencil"
},
"engines": {
"node": ">=12.10.0",
"npm": ">=6.0.0"
}
},
"node_modules/@ionic/react": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.1.1.tgz",
"integrity": "sha512-XMuXtC6HZXf5dBHkfbH0qzTAbvT/cbC+A69NbtOWI9BzxJOUUM3QLhmO7DMfYEdKjAEAfJxQ1vi+iOrz9wQ5Pw==",
"dependencies": {
"@ionic/core": "7.1.1",
"ionicons": "^7.0.0",
"tslib": "*"
},
"peerDependencies": {
@@ -2391,11 +2424,11 @@
}
},
"node_modules/@ionic/react-router": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-6.6.1.tgz",
"integrity": "sha512-9bHlz3MdzvkUyZ9QfxzcAGDtbRhZ7R5uMjm3UHvGhYS1Rdx4KIc8E5q31IQf7H6j2ULU9YcB7UeyW5ORxBX18Q==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-7.1.1.tgz",
"integrity": "sha512-ncu5nEmCDEvVf++4MpfrZKf+XDTBLa9IJyrPYpDYi/U3iaCdNLUwGYqKbY5Q78O69T/mjYh94fZg7gTbYM+/ZA==",
"dependencies": {
"@ionic/react": "6.6.1",
"@ionic/react": "7.1.1",
"tslib": "*"
},
"peerDependencies": {
@@ -2405,6 +2438,14 @@
"react-router-dom": "^5.0.1"
}
},
"node_modules/@ionic/react/node_modules/ionicons": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz",
"integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==",
"dependencies": {
"@stencil/core": "^2.18.0"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -2957,6 +2998,14 @@
"node": ">= 8"
}
},
"node_modules/@remix-run/router": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
"engines": {
"node": ">=14"
}
},
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3541,11 +3590,6 @@
"@types/node": "*"
}
},
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -3658,25 +3702,6 @@
"@types/react": "*"
}
},
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -8977,32 +9002,6 @@
"he": "bin/he"
}
},
"node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -9816,11 +9815,6 @@
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -12349,14 +12343,6 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"dependencies": {
"isarray": "0.0.1"
}
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -14054,46 +14040,35 @@
}
},
"node_modules/react-router": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
"integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.3.4",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0",
"react-router": "6.14.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-router/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -14466,11 +14441,6 @@
"node": ">=8"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"node_modules/resolve-url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@@ -15893,16 +15863,6 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
"node_modules/tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -16328,11 +16288,6 @@
"node": ">= 8"
}
},
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -18924,31 +18879,63 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
},
"@ionic/core": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.6.1.tgz",
"integrity": "sha512-+LMBk7kUX55rvYQ35AiAXPNzbNm3zNx9ginvuCzByguMjl+N63lpdPzIEfeRURkmq7NByD1VqpodMj5c6Oq2KQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.1.1.tgz",
"integrity": "sha512-5tdOSUiAkAfCtaP94ADugRskVxdwjRl+lRmOQnInkHnbUmA9eo2sCLCAO2UHfSL6VCP8BOw41NGlz4ad+Ivijw==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.1.3",
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
"tslib": "^2.1.0"
},
"dependencies": {
"@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g=="
},
"ionicons": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.0.tgz",
"integrity": "sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==",
"requires": {
"@stencil/core": "^2.18.0"
},
"dependencies": {
"@stencil/core": {
"version": "2.22.3",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz",
"integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng=="
}
}
}
}
},
"@ionic/react": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.6.1.tgz",
"integrity": "sha512-gq8FzC0CAPt6MpOFethe9+zIU7jg1JyWPWRANJ/UudlF05f2eFOzLgqe/EH0uIIsuDjeoM50hrqfuvg6x2j3UQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.1.1.tgz",
"integrity": "sha512-XMuXtC6HZXf5dBHkfbH0qzTAbvT/cbC+A69NbtOWI9BzxJOUUM3QLhmO7DMfYEdKjAEAfJxQ1vi+iOrz9wQ5Pw==",
"requires": {
"@ionic/core": "6.6.1",
"ionicons": "^6.1.3",
"@ionic/core": "7.1.1",
"ionicons": "^7.0.0",
"tslib": "*"
},
"dependencies": {
"ionicons": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.1.2.tgz",
"integrity": "sha512-zZ4njAqSP39H8RRvZhJvkHsv7cBjYE/VfInH218Osf2UVxJITSOutTTd25MW+tAXKN5fheYzclUXUsF55JHUDg==",
"requires": {
"@stencil/core": "^2.18.0"
}
}
}
},
"@ionic/react-router": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-6.6.1.tgz",
"integrity": "sha512-9bHlz3MdzvkUyZ9QfxzcAGDtbRhZ7R5uMjm3UHvGhYS1Rdx4KIc8E5q31IQf7H6j2ULU9YcB7UeyW5ORxBX18Q==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-7.1.1.tgz",
"integrity": "sha512-ncu5nEmCDEvVf++4MpfrZKf+XDTBLa9IJyrPYpDYi/U3iaCdNLUwGYqKbY5Q78O69T/mjYh94fZg7gTbYM+/ZA==",
"requires": {
"@ionic/react": "6.6.1",
"@ionic/react": "7.1.1",
"tslib": "*"
}
},
@@ -19357,6 +19344,11 @@
}
}
},
"@remix-run/router": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw=="
},
"@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -19779,11 +19771,6 @@
"@types/node": "*"
}
},
"@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
},
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -19896,25 +19883,6 @@
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"requires": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"requires": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -23784,34 +23752,6 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -24364,11 +24304,6 @@
"is-docker": "^2.0.0"
}
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -26251,14 +26186,6 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -27341,40 +27268,20 @@
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
"react-router": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
"@remix-run/router": "1.7.0"
}
},
"react-router-dom": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
"integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.3.4",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.7.0",
"react-router": "6.14.0"
}
},
"react-scripts": {
@@ -27669,11 +27576,6 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
},
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@@ -28734,16 +28636,6 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
"tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -29068,11 +28960,6 @@
}
}
},
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -3,17 +3,15 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"@ionic/react": "^6.6.1",
"@ionic/react-router": "^6.6.1",
"@ionic/react": "^7.0.0",
"@ionic/react-router": "^7.0.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"ionicons": "^6.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0",
"react-scripts": "^5.0.0",
"typescript": "^4.1.3"
},

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Route } from 'react-router-dom';
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import React from 'react';
import { Route, Routes } from 'react-router-dom';
/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';
@@ -21,18 +21,18 @@ import '@ionic/react/css/display.css';
/* Theme variables */
import './theme/variables.css';
import Main from './pages/Main';
import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
import OverlayComponents from './pages/overlay-components/OverlayComponents';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import Tabs from './pages/Tabs';
import Icons from './pages/Icons';
import Main from './pages/Main';
import Tabs from './pages/Tabs';
import NavComponent from './pages/navigation/NavComponent';
import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling';
import IonModalConditional from './pages/overlay-components/IonModalConditional';
import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling';
import IonModalDatetimeButton from './pages/overlay-components/IonModalDatetimeButton';
import IonPopoverNested from './pages/overlay-components/IonPopoverNested';
import IonModalMultipleChildren from './pages/overlay-components/IonModalMultipleChildren';
import IonPopoverNested from './pages/overlay-components/IonPopoverNested';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import OverlayComponents from './pages/overlay-components/OverlayComponents';
import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
setupIonicReact();
@@ -40,27 +40,20 @@ const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} />
<Route path="/overlay-components/nested-popover" component={IonPopoverNested} />
<Route
path="/overlay-components/modal-conditional-sibling"
component={IonModalConditionalSibling}
/>
<Route path="/overlay-components/modal-conditional" component={IonModalConditional} />
<Route
path="/overlay-components/modal-datetime-button"
component={IonModalDatetimeButton}
/>
<Route
path="/overlay-components/modal-multiple-children"
component={IonModalMultipleChildren}
/>
<Route path="/keep-contents-mounted" component={KeepContentsMounted} />
<Route path="/navigation" component={NavComponent} />
<Route path="/tabs" component={Tabs} />
<Route path="/icons" component={Icons} />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/overlay-hooks" element={<OverlayHooks />} />
<Route path="/overlay-components" element={<OverlayComponents />} />
<Route path="/overlay-components/nested-popover" element={<IonPopoverNested />} />
<Route path="/overlay-components/modal-conditional-sibling" element={<IonModalConditionalSibling />} />
<Route path="/overlay-components/modal-conditional" element={<IonModalConditional />} />
<Route path="/overlay-components/modal-datetime-button" element={<IonModalDatetimeButton />} />
<Route path="/overlay-components/modal-multiple-children" element={<IonModalMultipleChildren />} />
<Route path="/keep-contents-mounted" element={<KeepContentsMounted />} />
<Route path="/navigation" element={<NavComponent />} />
<Route path="/tabs" element={<Tabs />} />
<Route path="/icons" element={<Icons />} />
</Routes>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import React from 'react';
import { Route, Navigate } from 'react-router';
interface TabsProps {}
@@ -8,8 +8,10 @@ const Tabs: React.FC<TabsProps> = () => {
return (
<IonTabs>
<IonRouterOutlet>
<Redirect from="/tabs" to="/tabs/tab1" exact />
<Route path="/tabs/tab1" render={() => <IonLabel>Tab 1</IonLabel>} />
<Route path="/tabs/tab1">
<IonLabel>Tab 1</IonLabel>
</Route>
<Route path="/tabs" element={<Navigate to="/tabs/tab1" />} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" onClick={() => window.alert('Tab was clicked')}>

View File

@@ -1,15 +1,8 @@
import React from 'react';
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import {
addCircleOutline,
alarm,
alertCircle,
logoGoogle,
logoIonic,
newspaper,
star,
} from 'ionicons/icons';
import { addCircleOutline, alarm, alertCircle, logoGoogle, logoIonic, newspaper, star } from 'ionicons/icons';
import React from 'react';
import { Route, Routes, Navigate } from 'react-router';
import ActionSheetComponent from './ActionSheetComponent';
import AlertComponent from './AlertComponent';
import LoadingComponent from './LoadingComponent';
@@ -24,14 +17,16 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
return (
<IonTabs>
<IonRouterOutlet>
<Redirect from="/overlay-components" to="/overlay-components/actionsheet" exact />
<Route path="/overlay-components/actionsheet" component={ActionSheetComponent} />
<Route path="/overlay-components/alert" component={AlertComponent} />
<Route path="/overlay-components/loading" component={LoadingComponent} />
<Route path="/overlay-components/modal" component={ModalComponent} />
<Route path="/overlay-components/picker" component={PickerComponent} />
<Route path="/overlay-components/popover" component={PopoverComponent} />
<Route path="/overlay-components/toast" component={ToastComponent} />
<Routes>
<Route path="/overlay-components/actionsheet" element={<ActionSheetComponent />} />
<Route path="/overlay-components/alert" element={<AlertComponent />} />
<Route path="/overlay-components/loading" element={<LoadingComponent />} />
<Route path="/overlay-components/modal" element={<ModalComponent />} />
<Route path="/overlay-components/picker" element={<PickerComponent />} />
<Route path="/overlay-components/popover" element={<PopoverComponent />} />
<Route path="/overlay-components/toast" element={<ToastComponent />} />
<Route path="/overlay-components/*" element={<Navigate to="/overlay-components/actionsheet" />} />
</Routes>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="actionsheet" href="/overlay-components/actionsheet">

View File

@@ -1,16 +1,9 @@
import React from 'react';
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { addCircleOutline, alarm, alertCircle, logoGoogle, logoIonic, newspaper, star } from 'ionicons/icons';
import React from 'react';
import { Route, Navigate, Routes } from 'react-router';
import ActionSheetHook from './ActionSheetHook';
import {
addCircleOutline,
alarm,
alertCircle,
logoGoogle,
logoIonic,
newspaper,
star,
} from 'ionicons/icons';
import AlertHook from './AlertHook';
import LoadingHook from './LoadingHook';
import ModalHook from './ModalHook';
@@ -24,14 +17,16 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
return (
<IonTabs>
<IonRouterOutlet>
<Redirect from="/overlay-hooks" to="/overlay-hooks/actionsheet" exact />
<Route path="/overlay-hooks/actionsheet" component={ActionSheetHook} />
<Route path="/overlay-hooks/alert" component={AlertHook} />
<Route path="/overlay-hooks/loading" component={LoadingHook} />
<Route path="/overlay-hooks/modal" component={ModalHook} />
<Route path="/overlay-hooks/picker" component={PickerHook} />
<Route path="/overlay-hooks/popover" component={PopoverHook} />
<Route path="/overlay-hooks/toast" component={ToastHook} />
<Routes>
<Route path="/overlay-hooks/actionsheet" element={<ActionSheetHook />} />
<Route path="/overlay-hooks/alert" element={<AlertHook />} />
<Route path="/overlay-hooks/loading" element={<LoadingHook />} />
<Route path="/overlay-hooks/modal" element={<ModalHook />} />
<Route path="/overlay-hooks/picker" element={<PickerHook />} />
<Route path="/overlay-hooks/popover" element={<PopoverHook />} />
<Route path="/overlay-hooks/toast" element={<ToastHook />} />
<Route path="/overlay-hooks/*" element={<Navigate to="/overlay-hooks/actionsheet" />} />
</Routes>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="actionsheet" href="/overlay-hooks/actionsheet">