mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
34 Commits
ionic-modu
...
mh/react-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a65886ba37 | ||
|
|
a926134ba5 | ||
|
|
49cbc46d4b | ||
|
|
9c5f55adae | ||
|
|
9d69a69d08 | ||
|
|
7a20697849 | ||
|
|
10bb889dc3 | ||
|
|
2656e98490 | ||
|
|
e6e17eb435 | ||
|
|
5cccf0b297 | ||
|
|
366004e3f8 | ||
|
|
c6f8dd4c54 | ||
|
|
71fb8cb8f9 | ||
|
|
12c49f5c51 | ||
|
|
c45c0f9bfa | ||
|
|
7ff8994d12 | ||
|
|
205a7056d7 | ||
|
|
331b39427b | ||
|
|
0a0dcb6d7d | ||
|
|
0008059f8c | ||
|
|
93202c070e | ||
|
|
a78f6b3151 | ||
|
|
37f76c72ec | ||
|
|
ca27ed618b | ||
|
|
2436ba383e | ||
|
|
e76c1e86f9 | ||
|
|
fd6baaceda | ||
|
|
4aad76a93a | ||
|
|
6a42e6959d | ||
|
|
e364fea46b | ||
|
|
af7710b5a3 | ||
|
|
cccf290670 | ||
|
|
f0127bd874 | ||
|
|
426232456b |
287
packages/react-router/package-lock.json
generated
287
packages/react-router/package-lock.json
generated
@@ -27,8 +27,8 @@
|
||||
"prettier": "^2.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-router": "^6.30.0",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^4.2.0",
|
||||
"typescript": "^4.0.5"
|
||||
@@ -36,8 +36,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.30.0",
|
||||
"react-router-dom": "^6.30.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -152,18 +152,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
@@ -464,6 +452,15 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-typescript": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz",
|
||||
@@ -2454,29 +2451,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"dev": true,
|
||||
"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==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
@@ -2782,12 +2756,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -3112,15 +3080,6 @@
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"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",
|
||||
@@ -3175,17 +3134,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -3240,56 +3188,38 @@
|
||||
"react": "17.0.2"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"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.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
|
||||
"integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==",
|
||||
"dev": true,
|
||||
"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.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"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.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz",
|
||||
"integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==",
|
||||
"dev": true,
|
||||
"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.23.0",
|
||||
"react-router": "6.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||
@@ -3354,12 +3284,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
@@ -3754,18 +3678,6 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -3948,12 +3860,6 @@
|
||||
"integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -4108,15 +4014,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
}
|
||||
},
|
||||
"@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
@@ -4316,6 +4213,12 @@
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"dev": true
|
||||
},
|
||||
"@rollup/plugin-typescript": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz",
|
||||
@@ -5689,29 +5592,6 @@
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
@@ -5920,12 +5800,6 @@
|
||||
"call-bind": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -6184,15 +6058,6 @@
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"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",
|
||||
@@ -6223,17 +6088,6 @@
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -6265,50 +6119,25 @@
|
||||
"scheduler": "^0.20.2"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"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.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz",
|
||||
"integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==",
|
||||
"dev": true,
|
||||
"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"
|
||||
"@remix-run/router": "1.23.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.30.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz",
|
||||
"integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==",
|
||||
"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.3.4",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.0"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||
"dev": true
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||
@@ -6349,12 +6178,6 @@
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
@@ -6640,18 +6463,6 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -6790,12 +6601,6 @@
|
||||
"integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -42,8 +42,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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
@@ -60,8 +60,8 @@
|
||||
"prettier": "^2.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-router": "^6.30.0",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^4.2.0",
|
||||
"typescript": "^4.0.5"
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Copy ionic react dist
|
||||
rm -rf node_modules/@ionic/react/dist node_modules/@ionic/react/css
|
||||
cp -a ../react/dist node_modules/@ionic/react/dist
|
||||
cp -a ../react/css node_modules/@ionic/react/css
|
||||
cp -a ../react/package.json node_modules/@ionic/react/package.json
|
||||
# Delete old packages
|
||||
rm -f *.tgz
|
||||
|
||||
# Copy core dist
|
||||
rm -rf node_modules/@ionic/core/dist node_modules/@ionic/core/components
|
||||
cp -a ../../core/dist node_modules/@ionic/core/dist
|
||||
cp -a ../../core/components node_modules/@ionic/core/components
|
||||
cp -a ../../core/package.json node_modules/@ionic/core/package.json
|
||||
# Pack @ionic/react
|
||||
npm pack ../react
|
||||
|
||||
# Pack @ionic/core
|
||||
npm pack ../../core
|
||||
|
||||
# Install Dependencies
|
||||
npm install *.tgz --no-save
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
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';
|
||||
/**
|
||||
* `IonReactHashRouter` provides a way to use hash-based routing in Ionic
|
||||
* React applications.
|
||||
*/
|
||||
|
||||
import type { Action as HistoryAction, Location as HistoryLocation } from 'history';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { HashRouterProps } from 'react-router-dom';
|
||||
import { HashRouter, useLocation, useNavigationType } from 'react-router-dom';
|
||||
|
||||
import { IonRouter } from './IonRouter';
|
||||
|
||||
interface IonReactHashRouterProps extends BrowserRouterProps {
|
||||
history?: History;
|
||||
}
|
||||
export const IonReactHashRouter = ({ children }: PropsWithChildren<HashRouterProps>) => {
|
||||
const location = useLocation();
|
||||
const navigationType = useNavigationType();
|
||||
|
||||
export class IonReactHashRouter extends React.Component<IonReactHashRouterProps> {
|
||||
history: History;
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
const historyListenHandler = useRef<(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);
|
||||
}
|
||||
const registerHistoryListener = (cb: (location: HistoryLocation, action: HistoryAction) => void) => {
|
||||
historyListenHandler.current = cb;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Processes navigation changes within the application.
|
||||
*
|
||||
* Its purpose is to relay the current `location` and the associated
|
||||
* `action` ('PUSH', 'POP', or 'REPLACE') to any registered listeners,
|
||||
* primarily for `IonRouter` to manage Ionic-specific UI updates and
|
||||
* navigation stack behavior.
|
||||
*
|
||||
* @param location The current browser history location object.
|
||||
* @param action The type of navigation action ('PUSH', 'POP', or
|
||||
* 'REPLACE').
|
||||
*/
|
||||
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);
|
||||
const handleHistoryChange = (location: HistoryLocation, action: HistoryAction) => {
|
||||
if (historyListenHandler.current) {
|
||||
historyListenHandler.current(location, action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
|
||||
this.historyListenHandler = cb;
|
||||
}
|
||||
useEffect(() => {
|
||||
handleHistoryChange(location, navigationType);
|
||||
}, [location, navigationType]);
|
||||
|
||||
render() {
|
||||
const { children, ...props } = this.props;
|
||||
return (
|
||||
<Router history={this.history} {...props}>
|
||||
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<HashRouter>
|
||||
<IonRouter registerHistoryListener={registerHistoryListener}>{children}</IonRouter>
|
||||
</HashRouter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,51 +1,53 @@
|
||||
import type { Action as HistoryAction, Location as HistoryLocation, MemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
/**
|
||||
* `IonReactMemoryRouter` provides a way to use `react-router` in
|
||||
* environments where a traditional browser history (like `BrowserRouter`)
|
||||
* isn't available or desirable.
|
||||
*/
|
||||
|
||||
import type { Action as HistoryAction, Location as HistoryLocation } from 'history';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { MemoryRouterProps } from 'react-router';
|
||||
import { Router } from 'react-router';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { useLocation, useNavigationType } from 'react-router-dom';
|
||||
|
||||
import { IonRouter } from './IonRouter';
|
||||
|
||||
interface IonReactMemoryRouterProps extends MemoryRouterProps {
|
||||
history: MemoryHistory;
|
||||
}
|
||||
export const IonReactMemoryRouter = ({ children }: PropsWithChildren<MemoryRouterProps>) => {
|
||||
const location = useLocation();
|
||||
const navigationType = useNavigationType();
|
||||
|
||||
export class IonReactMemoryRouter extends React.Component<IonReactMemoryRouterProps> {
|
||||
history: MemoryHistory;
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
const historyListenHandler = useRef<(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);
|
||||
}
|
||||
const registerHistoryListener = (cb: (location: HistoryLocation, action: HistoryAction) => void) => {
|
||||
historyListenHandler.current = cb;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Processes navigation changes within the application.
|
||||
*
|
||||
* Its purpose is to relay the current `location` and the associated
|
||||
* `action` ('PUSH', 'POP', or 'REPLACE') to any registered listeners,
|
||||
* primarily for `IonRouter` to manage Ionic-specific UI updates and
|
||||
* navigation stack behavior.
|
||||
*
|
||||
* @param location The current browser history location object.
|
||||
* @param action The type of navigation action ('PUSH', 'POP', or
|
||||
* 'REPLACE').
|
||||
*/
|
||||
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);
|
||||
const handleHistoryChange = (location: HistoryLocation, action: HistoryAction) => {
|
||||
if (historyListenHandler.current) {
|
||||
historyListenHandler.current(location, action);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
|
||||
this.historyListenHandler = cb;
|
||||
}
|
||||
useEffect(() => {
|
||||
handleHistoryChange(location, navigationType);
|
||||
}, [location, navigationType]);
|
||||
|
||||
render() {
|
||||
const { children, ...props } = this.props;
|
||||
return (
|
||||
<Router {...props}>
|
||||
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<IonRouter registerHistoryListener={registerHistoryListener}>{children}</IonRouter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,53 +1,65 @@
|
||||
import type { Action as HistoryAction, History, Location as HistoryLocation } from 'history';
|
||||
import { createBrowserHistory as createHistory } from 'history';
|
||||
import React from 'react';
|
||||
/**
|
||||
* `IonReactRouter` facilitates the integration of Ionic's specific
|
||||
* navigation and UI management with the standard React Router mechanisms,
|
||||
* allowing an inner Ionic-specific router (`IonRouter`) to react to
|
||||
* navigation events.
|
||||
*/
|
||||
|
||||
import type { Action as HistoryAction, Location as HistoryLocation } from 'history';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import type { BrowserRouterProps } from 'react-router-dom';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { BrowserRouter, useLocation, useNavigationType } from 'react-router-dom';
|
||||
|
||||
import { IonRouter } from './IonRouter';
|
||||
|
||||
interface IonReactRouterProps extends BrowserRouterProps {
|
||||
history?: History;
|
||||
}
|
||||
/**
|
||||
* This component acts as a bridge to ensure React Router hooks like
|
||||
* `useLocation` and `useNavigationType` are called within the valid
|
||||
* context of a `<BrowserRouter>`.
|
||||
*
|
||||
* It was split from `IonReactRouter` because these hooks must be
|
||||
* descendants of a `<Router>` component, which `BrowserRouter` provides.
|
||||
*/
|
||||
const RouterContent = ({ children }: PropsWithChildren<{}>) => {
|
||||
const location = useLocation();
|
||||
const navigationType = useNavigationType();
|
||||
|
||||
export class IonReactRouter extends React.Component<IonReactRouterProps> {
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
history: History;
|
||||
const historyListenHandler = useRef<(location: HistoryLocation, action: HistoryAction) => void>();
|
||||
|
||||
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);
|
||||
}
|
||||
const registerHistoryListener = useCallback((cb: (location: HistoryLocation, action: HistoryAction) => void) => {
|
||||
historyListenHandler.current = cb;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Processes navigation changes within the application.
|
||||
*
|
||||
* Its purpose is to relay the current `location` and the associated
|
||||
* `action` ('PUSH', 'POP', or 'REPLACE') to any registered listeners,
|
||||
* primarily for `IonRouter` to manage Ionic-specific UI updates and
|
||||
* navigation stack behavior.
|
||||
*
|
||||
* @param loc The current browser history location object.
|
||||
* @param act The type of navigation action ('PUSH', 'POP', or
|
||||
* 'REPLACE').
|
||||
*/
|
||||
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);
|
||||
const handleHistoryChange = useCallback((loc: HistoryLocation, act: HistoryAction) => {
|
||||
if (historyListenHandler.current) {
|
||||
historyListenHandler.current(loc, act);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
registerHistoryListener(cb: (location: HistoryLocation, action: HistoryAction) => void) {
|
||||
this.historyListenHandler = cb;
|
||||
}
|
||||
useEffect(() => {
|
||||
handleHistoryChange(location, navigationType);
|
||||
}, [location, navigationType, handleHistoryChange]);
|
||||
|
||||
render() {
|
||||
const { children, ...props } = this.props;
|
||||
return (
|
||||
<Router history={this.history} {...props}>
|
||||
<IonRouter registerHistoryListener={this.registerHistoryListener}>{children}</IonRouter>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
return <IonRouter registerHistoryListener={registerHistoryListener}>{children}</IonRouter>;
|
||||
};
|
||||
|
||||
export const IonReactRouter = ({ children, ...browserRouterProps }: PropsWithChildren<BrowserRouterProps>) => {
|
||||
return (
|
||||
<BrowserRouter {...browserRouterProps}>
|
||||
<RouterContent>{children}</RouterContent>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 const IonRouteInner = ({ path, element }: IonRouteProps) => {
|
||||
return <Route path={path} element={element} />;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type {
|
||||
AnimationBuilder,
|
||||
RouteAction,
|
||||
RouteInfo,
|
||||
RouteManagerContextState,
|
||||
RouterDirection,
|
||||
ViewItem,
|
||||
} from '@ionic/react';
|
||||
/**
|
||||
* `IonRouter` is responsible for managing the application's navigation
|
||||
* state, tracking the history of visited routes, and coordinating
|
||||
* transitions between different views. It intercepts route changes from
|
||||
* React Router and translates them into actions that Ionic can understand
|
||||
* and animate.
|
||||
*/
|
||||
|
||||
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 { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { IonRouteInner } from './IonRouteInner';
|
||||
import { ReactRouterViewStack } from './ReactRouterViewStack';
|
||||
@@ -21,162 +22,178 @@ export interface LocationState {
|
||||
routerOptions?: { as?: string; unmount?: boolean };
|
||||
}
|
||||
|
||||
interface IonRouteProps extends RouteComponentProps<{}, {}, LocationState> {
|
||||
interface IonRouterProps {
|
||||
registerHistoryListener: (cb: (location: HistoryLocation<any>, action: HistoryAction) => void) => void;
|
||||
}
|
||||
|
||||
interface IonRouteState {
|
||||
routeInfo: RouteInfo;
|
||||
}
|
||||
export const IonRouter = ({ children, registerHistoryListener }: PropsWithChildren<IonRouterProps>) => {
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
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 didMountRef = useRef(false);
|
||||
const locationHistory = useRef(new LocationHistory());
|
||||
const currentTab = useRef<string | undefined>(undefined);
|
||||
const viewStack = useRef(new ReactRouterViewStack());
|
||||
const incomingRouteParams = useRef<Partial<RouteInfo> | null>(null);
|
||||
|
||||
constructor(props: IonRouteProps) {
|
||||
super(props);
|
||||
const [routeInfo, setRouteInfo] = useState({
|
||||
id: generateId('routeInfo'),
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
});
|
||||
|
||||
const routeInfo = {
|
||||
id: generateId('routeInfo'),
|
||||
pathname: this.props.location.pathname,
|
||||
search: this.props.location.search,
|
||||
};
|
||||
useEffect(() => {
|
||||
didMountRef.current = true;
|
||||
}, []);
|
||||
|
||||
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) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routeInfo = this.locationHistory.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) {
|
||||
/**
|
||||
* Triggered whenever the history changes, either through user navigation
|
||||
* or programmatic changes. It transforms the raw browser history changes
|
||||
* into `RouteInfo` objects, which are needed Ionic's animations and
|
||||
* navigation patterns.
|
||||
*
|
||||
* @param location The current location object from the history.
|
||||
* @param action The action that triggered the history change.
|
||||
*/
|
||||
const handleHistoryChange = (location: HistoryLocation<LocationState>, action: HistoryAction) => {
|
||||
let leavingLocationInfo: RouteInfo;
|
||||
if (this.incomingRouteParams) {
|
||||
if (this.incomingRouteParams.routeAction === 'replace') {
|
||||
leavingLocationInfo = this.locationHistory.previous();
|
||||
/**
|
||||
* A programmatic navigation was triggered.
|
||||
* e.g., `<Redirect />`, `history.push()`, or `handleNavigate()`
|
||||
*/
|
||||
if (incomingRouteParams) {
|
||||
/**
|
||||
* The current history entry is overwritten, so the previous entry
|
||||
* is the one we are leaving.
|
||||
*/
|
||||
if (incomingRouteParams.current?.routeAction === 'replace') {
|
||||
leavingLocationInfo = locationHistory.current.previous();
|
||||
} else {
|
||||
leavingLocationInfo = this.locationHistory.current();
|
||||
// If the action is 'push' or 'pop', we want to use the current route.
|
||||
leavingLocationInfo = locationHistory.current.current();
|
||||
}
|
||||
} else {
|
||||
leavingLocationInfo = this.locationHistory.current();
|
||||
/**
|
||||
* An external navigation was triggered
|
||||
* e.g., browser back/forward button or direct link
|
||||
*
|
||||
* The leaving location is the current route.
|
||||
*/
|
||||
leavingLocationInfo = locationHistory.current.current();
|
||||
}
|
||||
|
||||
const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
|
||||
// Check if the URL has changed.
|
||||
if (leavingUrl !== location.pathname) {
|
||||
if (!this.incomingRouteParams) {
|
||||
// An external navigation was triggered.
|
||||
if (!incomingRouteParams.current) {
|
||||
/**
|
||||
* A `REPLACE` action can be triggered by React Router's
|
||||
* `<Redirect />` component.
|
||||
*/
|
||||
if (action === 'REPLACE') {
|
||||
this.incomingRouteParams = {
|
||||
incomingRouteParams.current = {
|
||||
routeAction: 'replace',
|
||||
routeDirection: 'none',
|
||||
tab: this.currentTab,
|
||||
tab: currentTab.current,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A `POP` action can be triggered by the browser's back/forward
|
||||
* button.
|
||||
*/
|
||||
if (action === 'POP') {
|
||||
const currentRoute = this.locationHistory.current();
|
||||
const currentRoute = locationHistory.current.current();
|
||||
/**
|
||||
* Check if the current route was "pushed" by a previous route
|
||||
* (indicates a linear history path).
|
||||
*/
|
||||
if (currentRoute && currentRoute.pushedByRoute) {
|
||||
const prevInfo = this.locationHistory.findLastLocation(currentRoute);
|
||||
this.incomingRouteParams = { ...prevInfo, routeAction: 'pop', routeDirection: 'back' };
|
||||
const prevInfo = locationHistory.current.findLastLocation(currentRoute);
|
||||
incomingRouteParams.current = { ...prevInfo, routeAction: 'pop', routeDirection: 'back' };
|
||||
// It's a non-linear history path like a direct link.
|
||||
} else {
|
||||
this.incomingRouteParams = {
|
||||
incomingRouteParams.current = {
|
||||
routeAction: 'pop',
|
||||
routeDirection: 'none',
|
||||
tab: this.currentTab,
|
||||
tab: currentTab.current,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!this.incomingRouteParams) {
|
||||
this.incomingRouteParams = {
|
||||
// Still found no params, set it to a default state of forward.
|
||||
if (!incomingRouteParams.current) {
|
||||
incomingRouteParams.current = {
|
||||
routeAction: 'push',
|
||||
routeDirection: location.state?.direction || 'forward',
|
||||
routeOptions: location.state?.routerOptions,
|
||||
tab: this.currentTab,
|
||||
tab: currentTab.current,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let routeInfo: RouteInfo;
|
||||
|
||||
if (this.incomingRouteParams?.id) {
|
||||
/**
|
||||
* An existing id indicates that it's re-activating an existing route.
|
||||
* e.g., tab switching or navigating back to a previous route
|
||||
*/
|
||||
if (incomingRouteParams.current?.id) {
|
||||
routeInfo = {
|
||||
...(this.incomingRouteParams as RouteInfo),
|
||||
...(incomingRouteParams.current as RouteInfo),
|
||||
lastPathname: leavingLocationInfo.pathname,
|
||||
};
|
||||
this.locationHistory.add(routeInfo);
|
||||
locationHistory.current.add(routeInfo);
|
||||
/**
|
||||
* A new route is being created since it's not re-activating
|
||||
* an existing route.
|
||||
*/
|
||||
} else {
|
||||
const isPushed =
|
||||
this.incomingRouteParams.routeAction === 'push' && this.incomingRouteParams.routeDirection === 'forward';
|
||||
incomingRouteParams.current?.routeAction === 'push' &&
|
||||
incomingRouteParams.current.routeDirection === 'forward';
|
||||
routeInfo = {
|
||||
id: generateId('routeInfo'),
|
||||
...this.incomingRouteParams,
|
||||
lastPathname: leavingLocationInfo.pathname,
|
||||
pathname: location.pathname,
|
||||
...incomingRouteParams,
|
||||
lastPathname: leavingLocationInfo.pathname, // The URL we just came from
|
||||
pathname: location.pathname, // The current (destination) URL
|
||||
search: location.search,
|
||||
params: this.props.match.params,
|
||||
prevRouteLastPathname: leavingLocationInfo.lastPathname,
|
||||
params: params as { [key: string]: string | string[] },
|
||||
prevRouteLastPathname: leavingLocationInfo.lastPathname, // The lastPathname of the route we are leaving
|
||||
};
|
||||
// It's a linear navigation.
|
||||
if (isPushed) {
|
||||
routeInfo.tab = leavingLocationInfo.tab;
|
||||
routeInfo.pushedByRoute = leavingLocationInfo.pathname;
|
||||
// Triggered by a browser back button or handleNavigateBack.
|
||||
} else if (routeInfo.routeAction === 'pop') {
|
||||
const r = this.locationHistory.findLastLocation(routeInfo);
|
||||
// Find the route that pushed this one.
|
||||
const r = locationHistory.current.findLastLocation(routeInfo);
|
||||
routeInfo.pushedByRoute = r?.pushedByRoute;
|
||||
// Navigating to a new tab.
|
||||
} 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);
|
||||
/**
|
||||
* If we are switching tabs grab the last route info for the
|
||||
* tab and use its `pushedByRoute`.
|
||||
*/
|
||||
const lastRoute = locationHistory.current.getCurrentRouteInfoForTab(routeInfo.tab);
|
||||
// This helps maintain correct back stack behavior within tabs.
|
||||
routeInfo.pushedByRoute = lastRoute?.pushedByRoute;
|
||||
// Triggered by `history.replace()` or a `<Redirect />` component, etc.
|
||||
} 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();
|
||||
/**
|
||||
* Make sure to set the `lastPathname`, etc.. to the current route
|
||||
* so the page transitions out.
|
||||
*/
|
||||
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
|
||||
* say that /home was pushed by /home which is not correct.
|
||||
* Special handling for `replace` to ensure correct `pushedByRoute`
|
||||
* and `lastPathname`.
|
||||
*
|
||||
* If going from `/home` to `/child`, then replacing from
|
||||
* `/child` to `/home`, we don't want the route info to
|
||||
* say that `/home` was pushed by `/home` which is not correct.
|
||||
*/
|
||||
const currentPushedBy = currentRouteInfo?.pushedByRoute;
|
||||
const pushedByRoute =
|
||||
@@ -198,58 +215,127 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
routeInfo.routeAnimation = routeInfo.routeAnimation || currentRouteInfo?.routeAnimation;
|
||||
}
|
||||
|
||||
this.locationHistory.add(routeInfo);
|
||||
locationHistory.current.add(routeInfo);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
routeInfo,
|
||||
});
|
||||
setRouteInfo(routeInfo);
|
||||
}
|
||||
|
||||
this.incomingRouteParams = undefined;
|
||||
}
|
||||
// Reset for the next navigation.
|
||||
incomingRouteParams.current = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Resets the specified tab to its initial, root route.
|
||||
*
|
||||
* @param tab The tab to reset.
|
||||
* @param originalHref The original href for the tab.
|
||||
* @param originalRouteOptions The original route options for the tab.
|
||||
*/
|
||||
handleNativeBack() {
|
||||
const history = this.props.history as any;
|
||||
const goBack = history.goBack || history.back;
|
||||
goBack();
|
||||
}
|
||||
|
||||
handleNavigate(
|
||||
path: string,
|
||||
routeAction: RouteAction,
|
||||
routeDirection?: RouterDirection,
|
||||
routeAnimation?: AnimationBuilder,
|
||||
routeOptions?: any,
|
||||
tab?: string
|
||||
) {
|
||||
this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, {
|
||||
routeAction,
|
||||
routeDirection,
|
||||
routeOptions,
|
||||
routeAnimation,
|
||||
tab,
|
||||
});
|
||||
|
||||
if (routeAction === 'push') {
|
||||
this.props.history.push(path);
|
||||
} else {
|
||||
this.props.history.replace(path);
|
||||
const handleResetTab = (tab: string, originalHref: string, originalRouteOptions: any) => {
|
||||
const routeInfo = locationHistory.current.getFirstRouteInfoForTab(tab);
|
||||
if (routeInfo) {
|
||||
const newRouteInfo = { ...routeInfo };
|
||||
newRouteInfo.pathname = originalHref;
|
||||
newRouteInfo.routeOptions = originalRouteOptions;
|
||||
incomingRouteParams.current = { ...newRouteInfo, routeAction: 'pop', routeDirection: 'back' };
|
||||
navigate(newRouteInfo.pathname + (newRouteInfo.search || ''));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleNavigateBack(defaultHref: string | RouteInfo = '/', routeAnimation?: AnimationBuilder) {
|
||||
/**
|
||||
* Handles tab changes.
|
||||
*
|
||||
* @param tab The tab to switch to.
|
||||
* @param path The new path for the tab.
|
||||
* @param routeOptions Additional route options.
|
||||
*/
|
||||
const handleChangeTab = (tab: string, path?: string, routeOptions?: any) => {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routeInfo = locationHistory.current.getCurrentRouteInfoForTab(tab);
|
||||
const [pathname, search] = path.split('?');
|
||||
// User has navigated to the current tab before.
|
||||
if (routeInfo) {
|
||||
const routeParams = {
|
||||
...routeInfo,
|
||||
routeAction: 'push' as RouteAction,
|
||||
routeDirection: 'none' as RouterDirection,
|
||||
};
|
||||
/**
|
||||
* User is navigating to the same tab.
|
||||
* e.g., `/tabs/home` → `/tabs/home`
|
||||
*/
|
||||
if (routeInfo.pathname === pathname) {
|
||||
incomingRouteParams.current = {
|
||||
...routeParams,
|
||||
routeOptions,
|
||||
};
|
||||
|
||||
navigate(routeInfo.pathname + (routeInfo.search || ''));
|
||||
/**
|
||||
* User is navigating to a different tab.
|
||||
* e.g., `/tabs/home` → `/tabs/settings`
|
||||
*/
|
||||
} else {
|
||||
incomingRouteParams.current = {
|
||||
...routeParams,
|
||||
pathname,
|
||||
search: search ? '?' + search : undefined,
|
||||
routeOptions,
|
||||
};
|
||||
|
||||
navigate(pathname + (search ? '?' + search : ''));
|
||||
}
|
||||
// User has not navigated to this tab before.
|
||||
} else {
|
||||
handleNavigate(pathname, 'push', 'none', undefined, routeOptions, tab);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current active tab in `locationHistory`.
|
||||
* This is crucial for maintaining tab history since each tab has
|
||||
* its own navigation stack.
|
||||
*
|
||||
* @param tab The tab to set as active.
|
||||
*/
|
||||
const handleSetCurrentTab = (tab: string) => {
|
||||
currentTab.current = tab;
|
||||
const ri = { ...locationHistory.current.current() };
|
||||
if (ri.tab !== tab) {
|
||||
ri.tab = tab;
|
||||
locationHistory.current.update(ri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the native back button press.
|
||||
* It's usually called when a user presses the platform-native back action.
|
||||
*/
|
||||
const handleNativeBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to manage the back navigation within the Ionic React's routing
|
||||
* system. It's deeply integrated with Ionic's view lifecycle, animations,
|
||||
* and its custom history tracking (`locationHistory`) to provide a
|
||||
* native-like transition and maintain correct application state.
|
||||
*
|
||||
* @param defaultHref The fallback URL to navigate to if there's no
|
||||
* previous entry in the `locationHistory` stack.
|
||||
* @param routeAnimation A custom animation builder to override the
|
||||
* default "back" animation.
|
||||
*/
|
||||
const handleNavigateBack = (defaultHref: string | RouteInfo = '/', routeAnimation?: AnimationBuilder) => {
|
||||
const config = getConfig();
|
||||
defaultHref = defaultHref ? defaultHref : config && config.get('backButtonDefaultHref' as any);
|
||||
const routeInfo = this.locationHistory.current();
|
||||
const routeInfo = locationHistory.current.current();
|
||||
// It's a linear navigation.
|
||||
if (routeInfo && routeInfo.pushedByRoute) {
|
||||
const prevInfo = this.locationHistory.findLastLocation(routeInfo);
|
||||
const prevInfo = locationHistory.current.findLastLocation(routeInfo);
|
||||
if (prevInfo) {
|
||||
/**
|
||||
* This needs to be passed to handleNavigate
|
||||
@@ -257,12 +343,16 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
* will be overridden.
|
||||
*/
|
||||
const incomingAnimation = routeAnimation || routeInfo.routeAnimation;
|
||||
this.incomingRouteParams = {
|
||||
incomingRouteParams.current = {
|
||||
...prevInfo,
|
||||
routeAction: 'pop',
|
||||
routeDirection: 'back',
|
||||
routeAnimation: incomingAnimation,
|
||||
};
|
||||
/**
|
||||
* Check if it's a simple linear back navigation (not tabbed).
|
||||
* e.g., `/home` → `/settings` → back to `/home`
|
||||
*/
|
||||
if (
|
||||
routeInfo.lastPathname === routeInfo.pushedByRoute ||
|
||||
/**
|
||||
@@ -273,68 +363,98 @@ 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', incomingAnimation);
|
||||
/**
|
||||
* It's a non-linear back navigation.
|
||||
* e.g., direct link or tab switch or nested navigation with redirects
|
||||
*/
|
||||
handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back', incomingAnimation);
|
||||
}
|
||||
/**
|
||||
* `pushedByRoute` exists, but no corresponding previous entry in
|
||||
* the history stack.
|
||||
*/
|
||||
} else {
|
||||
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
|
||||
handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
|
||||
}
|
||||
/**
|
||||
* No `pushedByRoute`
|
||||
* e.g., initial page load
|
||||
*/
|
||||
} else {
|
||||
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
|
||||
handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to programmatically navigate through the app.
|
||||
*
|
||||
* @param path The path to navigate to.
|
||||
* @param routeAction The action to take (push, replace, etc.).
|
||||
* @param routeDirection The direction of the navigation (forward,
|
||||
* back, etc.).
|
||||
* @param routeAnimation The animation to use for the transition.
|
||||
* @param routeOptions Additional options for the route.
|
||||
* @param tab The tab to navigate to, if applicable.
|
||||
*/
|
||||
const handleNavigate = (
|
||||
path: string,
|
||||
routeAction: RouteAction,
|
||||
routeDirection?: RouterDirection,
|
||||
routeAnimation?: AnimationBuilder,
|
||||
routeOptions?: any,
|
||||
tab?: string
|
||||
) => {
|
||||
incomingRouteParams.current = Object.assign(incomingRouteParams.current || {}, {
|
||||
routeAction,
|
||||
routeDirection,
|
||||
routeOptions,
|
||||
routeAnimation,
|
||||
tab,
|
||||
});
|
||||
|
||||
navigate(path, { replace: routeAction !== 'push' });
|
||||
};
|
||||
|
||||
if (!didMountRef.current) {
|
||||
locationHistory.current.add(routeInfo);
|
||||
|
||||
registerHistoryListener(handleHistoryChange);
|
||||
}
|
||||
|
||||
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 routeMangerContextValue: 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,
|
||||
};
|
||||
|
||||
handleSetCurrentTab(tab: string) {
|
||||
this.currentTab = tab;
|
||||
const ri = { ...this.locationHistory.current() };
|
||||
if (ri.tab !== tab) {
|
||||
ri.tab = tab;
|
||||
this.locationHistory.update(ri);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<RouteManagerContext.Provider value={routeMangerContextValue}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const IonRouter = withRouter(IonRouterInner);
|
||||
IonRouter.displayName = 'IonRouter';
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
/**
|
||||
* `ReactRouterViewStack` is a custom navigation manager used in Ionic React
|
||||
* apps to map React Router route elements (such as `<IonRoute>`) to "view
|
||||
* items" that Ionic can manage in a view stack. This is critical to maintain
|
||||
* Ionic’s animation, lifecycle, and history behavior across views.
|
||||
*/
|
||||
|
||||
import type { RouteInfo, ViewItem } from '@ionic/react';
|
||||
import { IonRoute, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import type { PathMatch } from 'react-router';
|
||||
import { Routes } from 'react-router';
|
||||
|
||||
import { matchPath } from './utils/matchPath';
|
||||
|
||||
export class ReactRouterViewStack extends ViewStacks {
|
||||
constructor() {
|
||||
super();
|
||||
this.createViewItem = this.createViewItem.bind(this);
|
||||
this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this);
|
||||
this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
|
||||
this.getChildrenToRender = this.getChildrenToRender.bind(this);
|
||||
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
|
||||
}
|
||||
|
||||
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
|
||||
/**
|
||||
* Creates a new view item for the given outlet and react route element.
|
||||
* Associates route props with the matched route path for further lookups.
|
||||
*/
|
||||
createViewItem = (outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) => {
|
||||
const viewItem: ViewItem = {
|
||||
id: generateId('viewItem'),
|
||||
outletId,
|
||||
@@ -38,149 +46,196 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
};
|
||||
|
||||
return viewItem;
|
||||
}
|
||||
};
|
||||
|
||||
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
|
||||
/**
|
||||
* Renders a ViewLifeCycleManager for the given view item.
|
||||
* Handles cleanup if the view no longer matches.
|
||||
*
|
||||
* - Deactivates view if it no longer matches the current route
|
||||
* - Wraps the route element in <Routes> to support nested routing and ensure remounting
|
||||
* - Adds a unique key to <Routes> so React Router remounts routes when switching
|
||||
*/
|
||||
private renderViewItem = (viewItem: ViewItem, routeInfo: RouteInfo) => {
|
||||
const match = matchComponent(viewItem.reactElement, routeInfo.pathname);
|
||||
|
||||
if (!match && viewItem.routeData.match) {
|
||||
this.deactivateView(viewItem);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewLifeCycleManager key={`view-${viewItem.id}`} mount={viewItem.mount} removeView={() => this.remove(viewItem)}>
|
||||
{/**
|
||||
* Wrapped in <Routes> to ensure React Router v6 correctly processes nested route elements
|
||||
* `key` is provided to enforce remounting of Routes when switching between view items.
|
||||
*/}
|
||||
<Routes key={`routes-${viewItem.id}`}>{React.cloneElement(viewItem.reactElement)}</Routes>
|
||||
</ViewLifeCycleManager>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-renders all active view items for the specified outlet.
|
||||
* Ensures React elements are updated with the latest match.
|
||||
*
|
||||
* 1. Iterates through children of IonRouterOutlet
|
||||
* 2. Updates each matching viewItem with the current child React element
|
||||
* (important for updating props or changes to elements)
|
||||
* 3. Returns a list of React components that will be rendered inside the outlet
|
||||
* Each view is wrapped in <ViewLifeCycleManager> to manage lifecycle and rendering
|
||||
*/
|
||||
getChildrenToRender = (outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) => {
|
||||
const viewItems = this.getViewItemsForOutlet(outletId);
|
||||
|
||||
// Sync latest routes with viewItems
|
||||
// Sync child elements with stored viewItems (e.g. to reflect new props)
|
||||
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
|
||||
const viewItem = viewItems.find((v) => {
|
||||
return matchComponent(child, v.routeData.childProps.path || v.routeData.childProps.from);
|
||||
});
|
||||
if (viewItem) {
|
||||
viewItem.reactElement = child;
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
// Ensure the child is a valid React element sincewe
|
||||
// might have whitespace strings or other non-element children
|
||||
if (React.isValidElement(child)) {
|
||||
const viewItem = viewItems.find((v) =>
|
||||
matchComponent(child, v.routeData.childProps.path || routeInfo.pathname)
|
||||
);
|
||||
} else {
|
||||
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;
|
||||
viewItem.mount = false;
|
||||
if (viewItem) {
|
||||
viewItem.reactElement = child;
|
||||
}
|
||||
}
|
||||
|
||||
return clonedChild;
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
|
||||
// Render all view items using renderViewItem
|
||||
return viewItems.map((viewItem) => this.renderViewItem(viewItem, routeInfo));
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a view item matching the current route, optionally updating its match state.
|
||||
*/
|
||||
findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => {
|
||||
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
|
||||
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
|
||||
if (shouldUpdateMatch && viewItem && match) {
|
||||
viewItem.routeData.match = match;
|
||||
}
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
|
||||
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, mustBeIonRoute);
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
findViewItemByPathname(pathname: string, outletId?: string) {
|
||||
const { viewItem } = this.findViewItemByPath(pathname, outletId);
|
||||
return viewItem;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the matching view item and the match result for a given pathname.
|
||||
* Finds the view item that was previously active before a route change.
|
||||
*/
|
||||
findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) => {
|
||||
// If the lastPathname is not set, we cannot find a leaving view item
|
||||
if (!routeInfo.lastPathname) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[ReactRouterViewStack] No matching leaving view item found for: ${routeInfo.pathname}`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute);
|
||||
return viewItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a view item by pathname only, used in simpler queries.
|
||||
*/
|
||||
findViewItemByPathname = (pathname: string, outletId?: string) => {
|
||||
const { viewItem } = this.findViewItemByPath(pathname, outletId);
|
||||
return viewItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Core function that matches a given pathname against all view items.
|
||||
* Returns both the matched view item and match metadata.
|
||||
*/
|
||||
private findViewItemByPath(pathname: string, outletId?: string, mustBeIonRoute?: boolean) {
|
||||
let viewItem: ViewItem | undefined;
|
||||
let match: ReturnType<typeof matchPath> | undefined;
|
||||
let match: PathMatch<string> | null = null;
|
||||
let viewStack: ViewItem[];
|
||||
|
||||
if (outletId) {
|
||||
viewStack = this.getViewItemsForOutlet(outletId);
|
||||
viewStack.some(matchView);
|
||||
if (!viewItem) {
|
||||
viewStack.some(matchDefaultRoute);
|
||||
}
|
||||
if (!viewItem) viewStack.some(matchDefaultRoute);
|
||||
} else {
|
||||
const viewItems = this.getAllViewItems();
|
||||
viewItems.some(matchView);
|
||||
if (!viewItem) {
|
||||
viewItems.some(matchDefaultRoute);
|
||||
}
|
||||
if (!viewItem) viewItems.some(matchDefaultRoute);
|
||||
}
|
||||
|
||||
if (!viewItem && process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[ReactRouterViewStack] No matching view item found for: ${pathname}`);
|
||||
}
|
||||
|
||||
return { viewItem, match };
|
||||
|
||||
/**
|
||||
* Matches a route path with dynamic parameters (e.g. /tabs/:id)
|
||||
*/
|
||||
function matchView(v: ViewItem) {
|
||||
if (mustBeIonRoute && !v.ionRoute) {
|
||||
return false;
|
||||
}
|
||||
if (mustBeIonRoute && !v.ionRoute) return false;
|
||||
|
||||
match = matchPath({
|
||||
const result = matchPath({
|
||||
pathname,
|
||||
componentProps: v.routeData.childProps,
|
||||
});
|
||||
|
||||
if (match) {
|
||||
/**
|
||||
* Even though we have a match from react-router, we do not know if the match
|
||||
* is for this specific view item.
|
||||
*
|
||||
* To validate this, we need to check if the path and url match the view item's route data.
|
||||
*/
|
||||
const hasParameter = match.path.includes(':');
|
||||
if (!hasParameter || (hasParameter && match.url === v.routeData?.match?.url)) {
|
||||
if (result) {
|
||||
const hasParams = result.params && Object.keys(result.params).length > 0;
|
||||
const previousMatch = v.routeData?.match;
|
||||
const isSamePath = result.pathname === previousMatch?.pathname;
|
||||
|
||||
if (!hasParams || isSamePath) {
|
||||
match = result;
|
||||
viewItem = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a view with no path prop (default fallback route).
|
||||
*/
|
||||
function matchDefaultRoute(v: ViewItem) {
|
||||
// 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,
|
||||
params: {},
|
||||
};
|
||||
if (!v.routeData.childProps.path) {
|
||||
match = createDefaultMatch(pathname);
|
||||
viewItem = v;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts a view by clearing its match and setting mount to false.
|
||||
*/
|
||||
private deactivateView = (viewItem: ViewItem) => {
|
||||
viewItem.routeData.match = undefined; // clear it so it's no longer active
|
||||
viewItem.mount = false; // do not display the view anymore
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to apply matchPath to a React element and return its match state.
|
||||
*/
|
||||
function matchComponent(node: React.ReactElement, pathname: string) {
|
||||
return matchPath({
|
||||
pathname,
|
||||
componentProps: node.props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default match object for a fallback route.
|
||||
*/
|
||||
function createDefaultMatch(pathname: string): PathMatch<string> {
|
||||
return {
|
||||
params: {},
|
||||
pathname,
|
||||
pathnameBase: pathname,
|
||||
pattern: {
|
||||
path: pathname,
|
||||
caseSensitive: false,
|
||||
end: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
/**
|
||||
* `StackManager` is responsible for managing page transitions, keeping track
|
||||
* of views (pages), and ensuring that navigation behaves like native apps —
|
||||
* particularly with animations and swipe gestures.
|
||||
*/
|
||||
|
||||
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
|
||||
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
import { clonePageElement } from './clonePageElement';
|
||||
import { findRoutesNode } from './utils/findRoutesNode';
|
||||
import { matchPath } from './utils/matchPath';
|
||||
|
||||
// TODO(FW-2959): types
|
||||
@@ -18,7 +26,7 @@ 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;
|
||||
id: string; // Unique id for the router outlet aka outletId
|
||||
context!: React.ContextType<typeof RouteManagerContext>;
|
||||
ionRouterOutlet?: React.ReactElement;
|
||||
routerOutletElement: HTMLIonRouterOutletElement | undefined;
|
||||
@@ -79,25 +87,49 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
this.clearOutletTimeout = this.context.clearOutlet(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition between pages within this router outlet.
|
||||
* This function determines the entering and leaving views based on the
|
||||
* provided route information and triggers the appropriate animation.
|
||||
* It also handles scenarios like initial loads, back navigation, and
|
||||
* navigation to the same view with different parameters.
|
||||
*
|
||||
* @param routeInfo It contains info about the current route,
|
||||
* the previous route, and the action taken (e.g., push, replace).
|
||||
*
|
||||
* @returns A promise that resolves when the transition is complete.
|
||||
* If no transition is needed or if the router outlet isn't ready,
|
||||
* the Promise may resolve immediately.
|
||||
*/
|
||||
async handlePageTransition(routeInfo: RouteInfo) {
|
||||
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
|
||||
/**
|
||||
* The route outlet has not mounted yet. We need to wait for it to render
|
||||
* before we can transition the page.
|
||||
* The route outlet has not mounted yet (i.e., not in the DOM yet).
|
||||
* We need to wait for it to render before we can transition the page.
|
||||
*
|
||||
* Set a flag to indicate that we should transition the page after
|
||||
* the component has updated.
|
||||
* the component has updated (i.e., in `componentDidUpdate`).
|
||||
*/
|
||||
this.pendingPageTransition = true;
|
||||
} else {
|
||||
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
||||
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
||||
|
||||
/**
|
||||
* If we don't have a leaving view item, but the route info indicates
|
||||
* that the user has routed from a previous path, then the leaving view
|
||||
* can be found by the last known pathname.
|
||||
*/
|
||||
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
||||
}
|
||||
|
||||
// Check if leavingViewItem should be unmounted
|
||||
/**
|
||||
* The leaving view item should be unmounted in the following cases:
|
||||
* - Navigating with `replace`
|
||||
* - Navigating forward but not pushing a new view (e.g., back navigation or non-animated transition) and the leaving view is not the same as the entering view
|
||||
* - The routeOptions explicitly says unmount
|
||||
*/
|
||||
if (leavingViewItem) {
|
||||
if (routeInfo.routeAction === 'replace') {
|
||||
leavingViewItem.mount = false;
|
||||
@@ -110,8 +142,13 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
}
|
||||
}
|
||||
|
||||
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||
// Match the route element to render
|
||||
const enteringRoute = findRouteByRouteInfo(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||
|
||||
/**
|
||||
* If we already have a view item for this route, update its element.
|
||||
* Otherwise, create a new view item for the route.
|
||||
*/
|
||||
if (enteringViewItem) {
|
||||
enteringViewItem.reactElement = enteringRoute;
|
||||
} else if (enteringRoute) {
|
||||
@@ -119,6 +156,9 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
this.context.addViewItem(enteringViewItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin transition only if we have an ionPageElement (i.e., the page has rendered).
|
||||
*/
|
||||
if (enteringViewItem && enteringViewItem.ionPageElement) {
|
||||
/**
|
||||
* If the entering view item is the same as the leaving view item,
|
||||
@@ -127,21 +167,27 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
if (enteringViewItem === leavingViewItem) {
|
||||
/**
|
||||
* If the entering view item is the same as the leaving view item,
|
||||
* we are either transitioning using parameterized routes to the same view
|
||||
* or a parent router outlet is re-rendering as a result of React props changing.
|
||||
* we are either transitioning using parameterized routes to the same
|
||||
* view (e.g., `/user/1` → `/user/2`)
|
||||
* or a parent router outlet is re-rendering as a result of React props
|
||||
* changing (e.g., tab navigation).
|
||||
*
|
||||
* If the route data does not match the current path, the parent router outlet
|
||||
* is attempting to transition and we cancel the operation.
|
||||
* If the route data does not match the current path, it indicates a
|
||||
* situation where the view within this nested outlet might already be
|
||||
* visible due to the parent's re-render. In such cases
|
||||
* (like tab navigation), we prevent a redundant transition in this
|
||||
* outlet to avoid flickering.
|
||||
*/
|
||||
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
|
||||
if (enteringViewItem.routeData.match.pathname !== routeInfo.pathname) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there isn't a leaving view item, but the route info indicates
|
||||
* that the user has routed from a previous path, then we need
|
||||
* to find the leaving view item to transition between.
|
||||
* If the leaving view is still not found, especially during a
|
||||
* 'pop' (back navigation) operation, try to retrieve it using the
|
||||
* previous route information that was available as a prop on the
|
||||
* component.
|
||||
*/
|
||||
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
||||
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
||||
@@ -175,21 +221,29 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
*/
|
||||
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
|
||||
} 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(() => {
|
||||
/**
|
||||
* If we have a leavingView but no entering view/route, we are probably
|
||||
* leaving to another outlet, so hide this leavingView.
|
||||
* (e.g., /tabs/tab1 → /settings)
|
||||
*/
|
||||
if (leavingViewItem.ionPageElement) {
|
||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
// }, 250);
|
||||
}
|
||||
|
||||
// Force re-render so views update according to their new mount/visible status
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an `<IonPage>` DOM element with the `StackManager`.
|
||||
* This is called when `<IonPage>` has been mounted.
|
||||
*
|
||||
* @param page The element of the rendered `<IonPage>`.
|
||||
* @param routeInfo The route information that associates with `<IonPage>`.
|
||||
*/
|
||||
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
|
||||
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
||||
if (foundView) {
|
||||
@@ -209,9 +263,15 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
this.handlePageTransition(routeInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the router outlet for the swipe-to-go-back gesture.
|
||||
*
|
||||
* @param routerOutlet The Ionic router outlet component: `<IonRouterOutlet>`.
|
||||
*/
|
||||
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
|
||||
const canStart = () => {
|
||||
const config = getConfig();
|
||||
// Check if swipe back is enabled in config (default to true for iOS mode)
|
||||
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
|
||||
if (!swipeEnabled) {
|
||||
return false;
|
||||
@@ -219,10 +279,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
|
||||
const { routeInfo } = this.props;
|
||||
|
||||
// Determine the route to use for finding the view we would be navigating back to
|
||||
const propsToUse =
|
||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
||||
? this.prevProps.routeInfo
|
||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||
// Find the view item for the route we are going back to
|
||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||
|
||||
return (
|
||||
@@ -242,18 +304,21 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
* Make sure that we are not swiping back to the same
|
||||
* instances of a view.
|
||||
*/
|
||||
enteringViewItem.routeData.match.path !== routeInfo.pathname
|
||||
enteringViewItem.routeData.match.pattern.path !== routeInfo.pathname
|
||||
);
|
||||
};
|
||||
|
||||
const onStart = async () => {
|
||||
const { routeInfo } = this.props;
|
||||
|
||||
// Determine the route to use for finding the view we would be navigating back to
|
||||
const propsToUse =
|
||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
||||
? this.prevProps.routeInfo
|
||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||
// Find the view item for the route we are going back to
|
||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||
// Find the view item for the route we are going back from
|
||||
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
||||
|
||||
/**
|
||||
@@ -267,8 +332,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const onEnd = (shouldContinue: boolean) => {
|
||||
if (shouldContinue) {
|
||||
// User finished the swipe gesture, so complete the back navigation
|
||||
this.skipTransition = true;
|
||||
|
||||
this.context.goBack();
|
||||
@@ -280,11 +347,14 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
*/
|
||||
const { routeInfo } = this.props;
|
||||
|
||||
// Determine the route to use for finding the view we would be navigating back to
|
||||
const propsToUse =
|
||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
||||
? this.prevProps.routeInfo
|
||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||
// Find the view item for the route we are going back to
|
||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||
// Find the view item for the route we are going back from
|
||||
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
||||
|
||||
/**
|
||||
@@ -311,6 +381,18 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the transition between the entering and leaving pages within the
|
||||
* router outlet.
|
||||
*
|
||||
* @param routeInfo Info about the current route.
|
||||
* @param enteringViewItem The view item that is entering.
|
||||
* @param leavingViewItem The view item that is leaving.
|
||||
* @param direction The direction of the transition.
|
||||
* @param progressAnimation Indicates if the transition is part of a
|
||||
* gesture controlled animation (e.g., swipe to go back).
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
async transitionPage(
|
||||
routeInfo: RouteInfo,
|
||||
enteringViewItem: ViewItem,
|
||||
@@ -367,8 +449,8 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
if (leavingViewItem && 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);
|
||||
// (e.g., `/user/1` → `/user/2`)
|
||||
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname);
|
||||
if (match) {
|
||||
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
|
||||
if (newLeavingElement) {
|
||||
@@ -377,11 +459,23 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
this.routerOutletElement.removeChild(newLeavingElement);
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* The route no longer matches the component type of the leaving view.
|
||||
* (e.g., `/user/1` → `/settings`)
|
||||
*
|
||||
* This can also occur in edge cases like rapid navigation
|
||||
* or during parent component re-renders that briefly cause
|
||||
* the view items to be the same instance before the final
|
||||
* route component is determined.
|
||||
*/
|
||||
await runCommit(enteringViewItem.ionPageElement, undefined);
|
||||
}
|
||||
} else {
|
||||
// The leaving view is not the same as the entering view
|
||||
// (e.g., `/home` → `/settings` or initial load `/`)
|
||||
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
||||
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
|
||||
// An initiial load will not have a leaving view.
|
||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
@@ -392,10 +486,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
|
||||
this.ionRouterOutlet = ionRouterOutlet;
|
||||
this.ionRouterOutlet = ionRouterOutlet; // TODO: check if we can use a ref instead of storing this in the class
|
||||
|
||||
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
|
||||
this.forceUpdate();
|
||||
this.forceUpdate(); // TODO: investigate why this is needed
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -405,13 +499,16 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
{
|
||||
ref: (node: HTMLIonRouterOutletElement) => {
|
||||
if (ionRouterOutlet.props.setRef) {
|
||||
// Needed to handle external refs from devs.
|
||||
ionRouterOutlet.props.setRef(node);
|
||||
}
|
||||
if (ionRouterOutlet.props.forwardedRef) {
|
||||
// Needed to handle external refs from devs.
|
||||
ionRouterOutlet.props.forwardedRef.current = node;
|
||||
}
|
||||
this.routerOutletElement = node;
|
||||
const { ref } = ionRouterOutlet as any;
|
||||
// Check for legacy refs.
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
}
|
||||
@@ -430,30 +527,51 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
|
||||
export default StackManager;
|
||||
|
||||
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
|
||||
/**
|
||||
* Finds the `<Route />` node matching the current route info.
|
||||
* If no `<Route />` can be matched, a fallback node is returned.
|
||||
*
|
||||
* @param node The root node to search for `<Route />` nodes.
|
||||
* @param routeInfo The route information to match against.
|
||||
*/
|
||||
function findRouteByRouteInfo(node: React.ReactNode, routeInfo: RouteInfo) {
|
||||
let matchedNode: React.ReactNode;
|
||||
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
|
||||
const match = matchPath({
|
||||
pathname: routeInfo.pathname,
|
||||
componentProps: child.props,
|
||||
});
|
||||
if (match) {
|
||||
matchedNode = child;
|
||||
let fallbackNode: React.ReactNode;
|
||||
|
||||
// `<Route />` nodes are rendered inside of a <Routes /> node
|
||||
const routesNode = findRoutesNode(node) ?? node;
|
||||
|
||||
for (const child of React.Children.toArray(routesNode) as React.ReactElement[]) {
|
||||
// Check if the child is a `<Route />` node
|
||||
if (child.type === Route) {
|
||||
const match = matchPath({
|
||||
pathname: routeInfo.pathname,
|
||||
componentProps: child.props,
|
||||
});
|
||||
|
||||
if (match) {
|
||||
matchedNode = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (matchedNode) {
|
||||
return matchedNode;
|
||||
}
|
||||
// If we haven't found a node
|
||||
// try to find one that doesn't have a path or from prop, that will be our not found route
|
||||
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
|
||||
if (!(child.props.path || child.props.from)) {
|
||||
matchedNode = child;
|
||||
}
|
||||
});
|
||||
|
||||
return matchedNode;
|
||||
// If we haven't found a node
|
||||
// try to find one that doesn't have a path prop, that will be our not found route
|
||||
for (const child of React.Children.toArray(routesNode) as React.ReactElement[]) {
|
||||
if (child.type === Route) {
|
||||
if (!child.props.path) {
|
||||
fallbackNode = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchedNode ?? fallbackNode;
|
||||
}
|
||||
|
||||
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
|
||||
@@ -461,7 +579,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
|
||||
pathname,
|
||||
componentProps: {
|
||||
...node.props,
|
||||
exact: forceExact,
|
||||
end: forceExact,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Routes } from 'react-router';
|
||||
|
||||
export const findRoutesNode = (node: React.ReactNode) => {
|
||||
// The use of `<Routes />` is encouraged with React Router v6.
|
||||
let routesNode: React.ReactNode;
|
||||
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
|
||||
if (child.type === Routes) {
|
||||
routesNode = child;
|
||||
}
|
||||
});
|
||||
|
||||
if (routesNode) {
|
||||
// The childern of the `<Routes />` component are most likely
|
||||
// (and should be) the `<Route />` components.
|
||||
return (routesNode as React.ReactElement).props.children;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { matchPath as reactRouterMatchPath } from 'react-router';
|
||||
import type { PathMatch } from 'react-router';
|
||||
import { matchPath as reactRouterMatchPath } from 'react-router-dom';
|
||||
|
||||
interface MatchPathOptions {
|
||||
/**
|
||||
@@ -10,37 +11,30 @@ interface MatchPathOptions {
|
||||
*/
|
||||
componentProps: {
|
||||
path?: string;
|
||||
from?: string;
|
||||
component?: any;
|
||||
exact?: boolean;
|
||||
caseSensitive?: boolean;
|
||||
end?: boolean;
|
||||
index?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://v5.reactrouter.com/web/api/matchPath
|
||||
* The matchPath function is used only for matching paths, not rendering components or elements.
|
||||
* @see https://reactrouter.com/v6/utils/match-path
|
||||
*/
|
||||
export const matchPath = ({
|
||||
pathname,
|
||||
componentProps,
|
||||
}: MatchPathOptions): false | ReturnType<typeof reactRouterMatchPath> => {
|
||||
const { exact, component } = componentProps;
|
||||
export const matchPath = ({ pathname, componentProps }: MatchPathOptions): PathMatch<string> | null => {
|
||||
const { path, ...restProps } = componentProps;
|
||||
|
||||
const path = componentProps.path || componentProps.from;
|
||||
/***
|
||||
* The props to match against, they are identical
|
||||
* to the matching props `Route` accepts. It could also be a string
|
||||
* or an array of strings as shortcut for `{ path }`.
|
||||
*/
|
||||
const matchProps = {
|
||||
exact,
|
||||
path,
|
||||
component,
|
||||
};
|
||||
if (!path) {
|
||||
console.warn('[Ionic] matchPath: No path prop provided. This will always return null.', {
|
||||
componentProps,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = reactRouterMatchPath(pathname, matchProps);
|
||||
const match = reactRouterMatchPath({ path, ...restProps }, pathname);
|
||||
|
||||
if (!match) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return match;
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
"name": "react-router-new",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@ionic/react": "^6.6.1",
|
||||
"@ionic/react-router": "^6.6.1",
|
||||
"@ionic/react": "^8.6.1",
|
||||
"@ionic/react-router": "^8.6.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^18.2.67",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@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",
|
||||
@@ -2295,22 +2295,52 @@
|
||||
"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": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^2.18.0",
|
||||
"ionicons": "^6.1.3",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"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": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/core/node_modules/ionicons": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz",
|
||||
"integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==",
|
||||
"dependencies": {
|
||||
"@ionic/core": "6.6.1",
|
||||
"ionicons": "^6.1.3",
|
||||
"@stencil/core": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/react": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.2.tgz",
|
||||
"integrity": "sha512-SXE1RnzGqj0MGKGs6D4UCk4rOghbLYI5qwANdZJuBxlIcrcBJuAySjneuTGt+Y3UHS8W3YZHFujRv2Gvb+zvqQ==",
|
||||
"dependencies": {
|
||||
"@ionic/core": "8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -2319,11 +2349,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": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.6.2.tgz",
|
||||
"integrity": "sha512-wNVYZHEHkRkNimiK24bJ8KsWjuQyug7C+J/rNER7BKtZDzU3kWKVjvzD3P7kaiOf/DtVo+OrZNvYQJOuoIEhWg==",
|
||||
"dependencies": {
|
||||
"@ionic/react": "6.6.1",
|
||||
"@ionic/react": "8.6.2",
|
||||
"tslib": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -2333,6 +2363,36 @@
|
||||
"react-router-dom": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/react/node_modules/@stencil/core": {
|
||||
"version": "4.35.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.1.tgz",
|
||||
"integrity": "sha512-u65m3TbzOtpn679gUV4Yvi8YpInhRJ62js30a7YtXief9Ej/vzrhwDE22U0w4DMWJOYwAsJl133BUaZkWwnmzg==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/react/node_modules/ionicons": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz",
|
||||
"integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -3775,6 +3835,102 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
|
||||
@@ -23882,31 +24038,81 @@
|
||||
"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": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.2.tgz",
|
||||
"integrity": "sha512-CGZ9CDp/XHtm9WrK3wt0ZtR2f2B76qEvJIaF/juCqmpza9Al6u2L9R/NTEwInDRCWfbkAIF22nHNH54/VvN78Q==",
|
||||
"requires": {
|
||||
"@stencil/core": "^2.18.0",
|
||||
"ionicons": "^6.1.3",
|
||||
"@stencil/core": "4.33.1",
|
||||
"ionicons": "^7.2.2",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stencil/core": {
|
||||
"version": "4.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||
}
|
||||
},
|
||||
"ionicons": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz",
|
||||
"integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==",
|
||||
"requires": {
|
||||
"@stencil/core": "^4.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@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": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.2.tgz",
|
||||
"integrity": "sha512-SXE1RnzGqj0MGKGs6D4UCk4rOghbLYI5qwANdZJuBxlIcrcBJuAySjneuTGt+Y3UHS8W3YZHFujRv2Gvb+zvqQ==",
|
||||
"requires": {
|
||||
"@ionic/core": "6.6.1",
|
||||
"ionicons": "^6.1.3",
|
||||
"@ionic/core": "8.6.2",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stencil/core": {
|
||||
"version": "4.35.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.1.tgz",
|
||||
"integrity": "sha512-u65m3TbzOtpn679gUV4Yvi8YpInhRJ62js30a7YtXief9Ej/vzrhwDE22U0w4DMWJOYwAsJl133BUaZkWwnmzg==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||
}
|
||||
},
|
||||
"ionicons": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz",
|
||||
"integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==",
|
||||
"requires": {
|
||||
"@stencil/core": "^4.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@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": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.6.2.tgz",
|
||||
"integrity": "sha512-wNVYZHEHkRkNimiK24bJ8KsWjuQyug7C+J/rNER7BKtZDzU3kWKVjvzD3P7kaiOf/DtVo+OrZNvYQJOuoIEhWg==",
|
||||
"requires": {
|
||||
"@ionic/react": "6.6.1",
|
||||
"@ionic/react": "8.6.2",
|
||||
"tslib": "*"
|
||||
}
|
||||
},
|
||||
@@ -24962,6 +25168,54 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||
"optional": true
|
||||
},
|
||||
"@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||
"optional": true
|
||||
},
|
||||
"@rushstack/eslint-patch": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ionic/react": "^6.6.1",
|
||||
"@ionic/react-router": "^6.6.1",
|
||||
"@ionic/react": "^8.6.1",
|
||||
"@ionic/react-router": "^8.6.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
|
||||
72
packages/react-router/test/apps/reactrouter5/src/App.tsx
Normal file
72
packages/react-router/test/apps/reactrouter5/src/App.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { IonApp, setupIonicReact, IonRouterOutlet } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import '@ionic/react/css/normalize.css';
|
||||
import '@ionic/react/css/structure.css';
|
||||
import '@ionic/react/css/typography.css';
|
||||
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import '@ionic/react/css/display.css';
|
||||
import '@ionic/react/css/flex-utils.css';
|
||||
import '@ionic/react/css/float-elements.css';
|
||||
import '@ionic/react/css/padding.css';
|
||||
import '@ionic/react/css/text-alignment.css';
|
||||
import '@ionic/react/css/text-transformation.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
import Main from './pages/Main';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import DynamicRoutes from './pages/dynamic-routes/DynamicRoutes';
|
||||
import Routing from './pages/routing/Routing';
|
||||
import MultipleTabs from './pages/muiltiple-tabs/MultipleTabs';
|
||||
import DynamicTabs from './pages/dynamic-tabs/DynamicTabs';
|
||||
import NestedOutlet from './pages/nested-outlet/NestedOutlet';
|
||||
import NestedOutlet2 from './pages/nested-outlet/NestedOutlet2';
|
||||
import ReplaceAction from './pages/replace-action/Replace';
|
||||
import TabsContext from './pages/tab-context/TabContext';
|
||||
import { OutletRef } from './pages/outlet-ref/OutletRef';
|
||||
import { SwipeToGoBack } from './pages/swipe-to-go-back/SwipToGoBack';
|
||||
import Refs from './pages/refs/Refs';
|
||||
import DynamicIonpageClassnames from './pages/dynamic-ionpage-classnames/DynamicIonpageClassnames';
|
||||
import Tabs from './pages/tabs/Tabs';
|
||||
import TabsSecondary from './pages/tabs/TabsSecondary';
|
||||
import Params from './pages/params/Params';
|
||||
import Overlays from './pages/overlays/Overlays';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonRouterOutlet>
|
||||
<Route path="/" component={Main} exact />
|
||||
<Route path="/routing" component={Routing} />
|
||||
<Route path="/dynamic-routes" component={DynamicRoutes} />
|
||||
<Route path="/multiple-tabs" component={MultipleTabs} />
|
||||
<Route path="/dynamic-tabs" component={DynamicTabs} />
|
||||
<Route path="/nested-outlet" component={NestedOutlet} />
|
||||
<Route path="/nested-outlet2" component={NestedOutlet2} />
|
||||
<Route path="/replace-action" component={ReplaceAction} />
|
||||
<Route path="/tab-context" component={TabsContext} />
|
||||
<Route path="/outlet-ref" component={OutletRef} />
|
||||
<Route path="/swipe-to-go-back" component={SwipeToGoBack} />
|
||||
<Route path="/dynamic-ionpage-classnames" component={DynamicIonpageClassnames} />
|
||||
<Route path="/tabs" component={Tabs} />
|
||||
<Route path="/tabs-secondary" component={TabsSecondary} />
|
||||
<Route path="/refs" component={Refs} />
|
||||
<Route path="/overlays" component={Overlays} />
|
||||
<Route path="/params/:id" component={Params} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
interface DynamicIonpageClassnamesProps {}
|
||||
|
||||
const DynamicIonpageClassnames: React.FC<DynamicIonpageClassnamesProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/dynamic-ionpage-classnames" component={Page} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicIonpageClassnames;
|
||||
|
||||
const Page: React.FC = (props) => {
|
||||
const [styleClass, setStyleClass] = useState('initial-class');
|
||||
const [divClasses, setDivClasses] = useState<string>();
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
useEffect(() => {
|
||||
if(ref.current) {
|
||||
var observer = new MutationObserver(function (event) {
|
||||
setDivClasses(ref.current?.className)
|
||||
})
|
||||
|
||||
observer.observe(ref.current, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
childList: false,
|
||||
characterData: false
|
||||
})
|
||||
}
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<IonPage className={styleClass} ref={ref} data-pageid="dynamic-ionpage-classnames">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Dynamic Ionpage Classnames</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton onClick={() => setStyleClass('other-class')}>Add Class</IonButton>
|
||||
<br />
|
||||
Div classes: {divClasses}
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
import React, { useState, ReactElement } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonRouterOutlet,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const DynamicRoutes: React.FC = () => {
|
||||
const [routes, setRoutes] = useState<ReactElement[]>([
|
||||
<Route
|
||||
key="sldjflsdj"
|
||||
path="/dynamic-routes/home"
|
||||
render={() => <Home update={addRoute} />}
|
||||
exact={true}
|
||||
/>,
|
||||
]);
|
||||
|
||||
const addRoute = () => {
|
||||
const newRoute = (
|
||||
<Route key="lsdjldj" path="/dynamic-routes/newRoute" component={NewRoute} exact={true} />
|
||||
);
|
||||
setRoutes([...routes, newRoute]);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
{routes}
|
||||
{/* <Route exact path="/home" render={() => <Home update={addRoute} />} /> */}
|
||||
<Route exact path="/dynamic-routes" render={() => <Redirect to="/dynamic-routes/home" />} />
|
||||
<Route render={() => <Failed />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicRoutes;
|
||||
|
||||
const Home: React.FC<{
|
||||
update: Function;
|
||||
}> = (props) => {
|
||||
const updateRoute = () => {
|
||||
props.update();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="dynamic-routes-home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>HOME</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">HOME</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<div className="container">
|
||||
<strong>Click Add Route Button</strong>
|
||||
<br />
|
||||
<button className="" onClick={() => updateRoute()}>
|
||||
Add Route
|
||||
</button>
|
||||
<br />
|
||||
<Link to="/dynamic-routes/newRoute">Take me to the newRoute</Link>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const NewRoute: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="dynamic-routes-newroute">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>New Route</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">New Route</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Failed: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="dynamic-routes-failed">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>New Route Failed</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">New Route Failed</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonApp,
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
const DynamicTabs: React.FC = () => {
|
||||
const [display2ndTab, setDisplayThirdTab] = useState<boolean>(false);
|
||||
|
||||
const renderFirstTab = useCallback(() => {
|
||||
return <Tab1 setDisplayThirdTab={() => setDisplayThirdTab(!display2ndTab)} />;
|
||||
}, [display2ndTab]);
|
||||
|
||||
const render2ndTabRoute = useCallback(() => {
|
||||
if (display2ndTab) {
|
||||
return <Route path="/dynamic-tabs/tab2" component={Tab2} />;
|
||||
} else {
|
||||
// This is weird, if I return null or undefined then I get all sorts of errors, seemingly
|
||||
// because the router is mad about a child not being a route.
|
||||
return <Route path="/dynamic-tabs/tab200" component={Tab1} />;
|
||||
}
|
||||
}, [display2ndTab]);
|
||||
|
||||
return (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route path="/dynamic-tabs/tab1" render={renderFirstTab} exact={true} />
|
||||
{render2ndTabRoute()}
|
||||
<Route
|
||||
path="/dynamic-tabs/"
|
||||
render={() => <Redirect to="/dynamic-tabs/tab1" />}
|
||||
exact={true}
|
||||
/>
|
||||
<Route render={() => <Redirect to="/dynamic-tabs/tab1" />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/dynamic-tabs/tab1">
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Tab 1</IonLabel>
|
||||
</IonTabButton>
|
||||
{display2ndTab && (
|
||||
<IonTabButton tab="tab2" href="/dynamic-tabs/tab2">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab 2</IonLabel>
|
||||
</IonTabButton>
|
||||
)}
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicTabs;
|
||||
|
||||
const Tab1: React.FC<{
|
||||
setDisplayThirdTab: (value: boolean) => void;
|
||||
}> = ({ setDisplayThirdTab }) => {
|
||||
const doIt = useCallback(() => {
|
||||
setDisplayThirdTab(true);
|
||||
}, [setDisplayThirdTab]);
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="Tab1">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>Tab 1 Page</div>
|
||||
<IonButton onClick={doIt}>Add Tab 2</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="Tab2">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>Tab 2 Page</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonSplitPane,
|
||||
IonRouterOutlet,
|
||||
IonTabs,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonMenuButton,
|
||||
IonButtons,
|
||||
IonIcon,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Menu } from './Menu';
|
||||
import { triangle, ellipse, square, rocket } from 'ionicons/icons';
|
||||
|
||||
const MultipleTabs: React.FC = () => {
|
||||
return (
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route
|
||||
path="/multiple-tabs/tab1"
|
||||
render={() => {
|
||||
return <Tab1 />;
|
||||
}}
|
||||
exact={false}
|
||||
/>
|
||||
<Route
|
||||
path="/multiple-tabs/tab2"
|
||||
render={() => {
|
||||
return <Tab2 />;
|
||||
}}
|
||||
exact={false}
|
||||
/>
|
||||
<Route
|
||||
path="/multiple-tabs"
|
||||
render={() => <Redirect to="/multiple-tabs/tab1" />}
|
||||
exact={true}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultipleTabs;
|
||||
|
||||
const Tab1: React.FC = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="pagea" href="/multiple-tabs/tab1/pagea">
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Page A</IonLabel>
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="pageb" href="/multiple-tabs/tab1/pageb">
|
||||
<IonIcon icon={ellipse} />
|
||||
<IonLabel>Page B</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
<IonRouterOutlet id="tab1">
|
||||
<Route
|
||||
path="/multiple-tabs/tab1"
|
||||
render={() => <Redirect to="/multiple-tabs/tab1/pagea" />}
|
||||
exact={true}
|
||||
/>
|
||||
{/* <Redirect path="/multiple-tabs/event" to="/multiple-tabs/tab1/pagea" exact={true} /> */}
|
||||
<Route path="/multiple-tabs/tab1/pagea" render={() => <Page name="PageA" />} exact={true} />
|
||||
<Route path="/multiple-tabs/tab1/pageb" render={() => <Page name="PageB" />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2: React.FC = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="pagec" href="/multiple-tabs/tab2/pagec">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Page C</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="paged" href="/multiple-tabs/tab2/paged">
|
||||
<IonIcon icon={rocket} />
|
||||
<IonLabel>Page D</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
<IonRouterOutlet id="tab2">
|
||||
<Route
|
||||
path="/multiple-tabs/tab2"
|
||||
render={() => <Redirect to="/multiple-tabs/tab2/pagec" />}
|
||||
exact={true}
|
||||
/>
|
||||
{/* <Redirect path="/multiple-tabs/tab2" to="/multiple-tabs/tab2/pagec" exact={true} /> */}
|
||||
<Route path="/multiple-tabs/tab2/pagec" render={() => <Page name="PageC" />} exact={true} />
|
||||
<Route path="/multiple-tabs/tab2/paged" render={() => <Page name="PageD" />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const Page: React.FC<{ name: string }> = ({ name }) => {
|
||||
return (
|
||||
<IonPage data-pageid={name}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<IonMenuButton />
|
||||
<IonTitle>{name}</IonTitle>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>{name}</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
useEffect(() => {
|
||||
console.log('mount MySubPage');
|
||||
return () => {
|
||||
console.log('unmount MySubPage');
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<IonPage data-pageid="secondpage">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Second Page</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton routerLink="/nested-outlet" routerDirection="root">
|
||||
Back with direction "root"
|
||||
</IonButton>
|
||||
<IonButton routerLink="/nested-outlet" routerDirection="back">
|
||||
Back with direction "back"
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const SecondPage: React.FC = () => {
|
||||
useEffect(() => {
|
||||
console.log('mount secondpage');
|
||||
return () => {
|
||||
console.log('unmount secondpage'); // Never called.
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<IonRouterOutlet ionPage>
|
||||
<Route
|
||||
path="/nested-outlet/secondpage"
|
||||
exact={true}
|
||||
render={() => <Redirect to="/nested-outlet/secondpage/page" />}
|
||||
/>
|
||||
<Route path="/nested-outlet/secondpage/page" component={Page} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const FirstPage: React.FC = () => {
|
||||
useEffect(() => {
|
||||
console.log('mount FirstPage');
|
||||
return () => {
|
||||
console.log('unmount FirstPage');
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<IonPage data-pageid="firstpage">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>FirstPage</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton routerLink="/nested-outlet/secondpage/page" routerDirection="forward">
|
||||
Go to second page
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const NestedOutlet: React.FC = () => (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/nested-outlet" component={FirstPage} exact={true} />
|
||||
<Route path="/nested-outlet/secondpage" component={SecondPage} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
export default NestedOutlet;
|
||||
@@ -0,0 +1,135 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, RouteComponentProps } from 'react-router-dom';
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
|
||||
const ListPage: React.FC<RouteComponentProps> = ({ match }) => {
|
||||
return (
|
||||
<IonRouterOutlet ionPage id="listpage">
|
||||
<Route exact path="/nested-outlet2/list" component={List} />
|
||||
<Route path={`${match.url}/:id`} component={Item} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const List: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="list">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref={'/nested-outlet2/home'} text={'Home'} />
|
||||
</IonButtons>
|
||||
<IonTitle>List</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonList>
|
||||
<IonItem detail routerLink="/nested-outlet2/list/1">
|
||||
Item #1
|
||||
</IonItem>
|
||||
<IonItem detail routerLink="/nested-outlet2/list/2">
|
||||
Item #2
|
||||
</IonItem>
|
||||
<IonItem detail routerLink="/nested-outlet2/list/3">
|
||||
Item #3
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Item: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
return (
|
||||
<IonPage data-pageid="item">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Item</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>Detail of item #{match.params.id}</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const HomePage: React.FC<RouteComponentProps> = ({ match }) => {
|
||||
return (
|
||||
<IonRouterOutlet ionPage id="homepage">
|
||||
<Route exact path="/nested-outlet2/home" component={Home} />
|
||||
<Route path="/nested-outlet2/home/welcome" component={Welcome} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const Welcome: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="welcome">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Welcome</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonList>
|
||||
<IonItem routerLink="/nested-outlet2/list" detail>
|
||||
<IonLabel>Go to list from Welcome</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/nested-outlet2/list/1" detail>
|
||||
<IonLabel>Go to first item</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Home: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
return (
|
||||
<IonPage data-pageid="home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonList>
|
||||
<IonItem routerLink="/nested-outlet2/home/welcome" target="_blank" detail>
|
||||
<IonLabel>Go to Welcome</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/nested-outlet2/list" detail>
|
||||
<IonLabel>Go to list from Home</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const NestedOutlet2: React.FC = () => (
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/nested-outlet2/list" component={ListPage} />
|
||||
<Route path="/nested-outlet2/home" component={HomePage} />
|
||||
<Route
|
||||
path="/nested-outlet2"
|
||||
render={() => <Redirect to="/nested-outlet2/home" />}
|
||||
exact={true}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
export default NestedOutlet2;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import {
|
||||
IonRouterOutlet,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
} from '@ionic/react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
interface OutletRefProps {}
|
||||
|
||||
export const OutletRef: React.FC<OutletRefProps> = () => {
|
||||
const ref = useRef<HTMLIonRouterOutletElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(ref);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonRouterOutlet id="main-outlet" ref={ref}>
|
||||
<Route
|
||||
path="/outlet-ref"
|
||||
render={() => {
|
||||
return <Main outletId={ref.current?.id} />;
|
||||
}}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const Main: React.FC<{ outletId?: string }> = ({ outletId }) => {
|
||||
return (
|
||||
<IonPage data-pageid="main">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Main</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>{outletId}</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { IonButton, IonContent, IonModal } from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
const Overlays: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const goBack = () => history.goBack();
|
||||
const replace = () => history.replace('/');
|
||||
const push = () => history.push('/');
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonButton id="openModal" onClick={() => setIsOpen(true)}>
|
||||
Open Modal
|
||||
</IonButton>
|
||||
<IonModal
|
||||
isOpen={isOpen}
|
||||
onDidDismiss={() => {
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<IonContent>
|
||||
<IonButton id="goBack" onClick={goBack}>
|
||||
Go Back
|
||||
</IonButton>
|
||||
<IonButton id="replace" onClick={replace}>
|
||||
Replace
|
||||
</IonButton>
|
||||
<IonButton id="push" onClick={push}>
|
||||
Push
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Overlays;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface PageProps
|
||||
extends RouteComponentProps<{
|
||||
id: string;
|
||||
}> {}
|
||||
|
||||
|
||||
const Page: React.FC<PageProps> = ({ match }) => {
|
||||
const parseID = parseInt(match.params.id);
|
||||
return (
|
||||
<IonPage data-pageid={'params-' + match.params.id }>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Params { match.params.id }</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton id="next-page" routerLink={'/params/' + (parseID + 1) } >Go to next param</IonButton>
|
||||
<br />
|
||||
Page ID: { match.params.id }
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from "@ionic/react";
|
||||
import { Route } from "react-router";
|
||||
|
||||
interface RefsProps {}
|
||||
|
||||
const Refs: React.FC = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
{/* <Route exact path="/home" render={() => <Home update={addRoute} />} /> */}
|
||||
<Route exact path="/refs" component={RefsFC} />
|
||||
<Route exact path="/refs/class" component={RefsClass} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const RefsFC: React.FC<RefsProps> = () => {
|
||||
const contentRef = useRef<HTMLIonContentElement>(null);
|
||||
return (
|
||||
<IonPage data-pageid="refs-fc">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Refs FC</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent ref={contentRef} className="ref-test"></IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
class RefsClass extends React.Component {
|
||||
ref = React.createRef<HTMLIonContentElement>();
|
||||
render() {
|
||||
return (
|
||||
<IonPage data-pageid="refs-class">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Refs Class</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent ref={this.ref} className="ref-test"></IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Refs;
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButton,
|
||||
IonRouterOutlet,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect, useHistory } from 'react-router';
|
||||
|
||||
interface TopPageProps {}
|
||||
|
||||
const ReplaceAction: React.FC<TopPageProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/replace-action/page1" component={Page1} exact />
|
||||
<Route path="/replace-action/page2" component={Page2} exact />
|
||||
<Route path="/replace-action/page3" component={Page3} exact />
|
||||
<Route exact path="/replace-action" render={() => <Redirect to="/replace-action/page1" />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const Page1: React.FC = () => (
|
||||
<IonPage data-pageid="page1">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Page one</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton routerLink={'/replace-action/page2'}>Goto Page2</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const Page2: React.FC = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const clickButton = () => {
|
||||
history.replace('/replace-action/page3');
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="page2">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Page two</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton onClick={() => clickButton()}>Goto Page3</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Page3: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="page3">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/replace-action/page1" />
|
||||
</IonButtons>
|
||||
<IonTitle>Page three</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<p>Page 3</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplaceAction;
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { useParams, useLocation } from 'react-router';
|
||||
|
||||
interface DetailsProps {}
|
||||
|
||||
const Details: React.FC<DetailsProps> = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Home Details mount');
|
||||
return () => console.log('Home Details unmount');
|
||||
}, []);
|
||||
|
||||
const nextId = parseInt(id, 10) + 1;
|
||||
|
||||
return (
|
||||
<IonPage data-pageid={`home-details-page-${id}`}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<IonBackButton></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Details</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonLabel data-testid="details-label">Details {id}</IonLabel>
|
||||
<br />
|
||||
<br />
|
||||
{location.search && (
|
||||
<>
|
||||
<IonLabel data-testid="query-label">Query Params: {location.search}</IonLabel>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<IonButton routerLink={`/routing/tabs/home/details/${nextId}`}>
|
||||
<IonLabel>Go to Details {nextId}</IonLabel>
|
||||
</IonButton>
|
||||
<br />
|
||||
<IonButton routerLink={`/routing/tabs/settings/details/1`}>
|
||||
<IonLabel>Go to Settings Details 1</IonLabel>
|
||||
</IonButton>
|
||||
<br />
|
||||
<br />
|
||||
<input data-testid="details-input" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
useIonViewWillEnter,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
|
||||
interface OtherPageProps {}
|
||||
|
||||
const OtherPage: React.FC<OtherPageProps> = () => {
|
||||
useIonViewWillEnter(() => {
|
||||
console.log('IVWE on otherpage');
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Other Page mount');
|
||||
return () => console.log('Other Page unmount');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
// <IonRouterOutlet id="other" ionPageContainer>
|
||||
// <Route path="/otherpage" render={() => (
|
||||
<IonPage data-pageid="other-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>OtherPage</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton routerLink="/routing/tabs/tab3">Go to tab3</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
// )}></Route>
|
||||
// </IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
export default OtherPage;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButton,
|
||||
IonRouterOutlet,
|
||||
} from '@ionic/react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
interface PropsTestProps {}
|
||||
|
||||
const PropsTest: React.FC<PropsTestProps> = () => {
|
||||
const [count, setCount] = useState(1);
|
||||
useEffect(() => {
|
||||
console.log(count);
|
||||
}, [count]);
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route
|
||||
path="/routing/propstest"
|
||||
render={() => <InnerPropsTest count={count} setCount={setCount} />}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const InnerPropsTest: React.FC<{ count: number; setCount: any }> = ({ count, setCount }) => {
|
||||
return (
|
||||
<IonPage data-pageid="props-test">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>PropsTest</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="count-label">Count: {count}</div>
|
||||
<IonButton onClick={() => setCount(count + 1)}>Increment</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default PropsTest;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonSplitPane,
|
||||
} from '@ionic/react';
|
||||
import Menu from './Menu';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import Tabs from './Tabs';
|
||||
import Favorites from './Favorites';
|
||||
import OtherPage from './OtherPage';
|
||||
import PropsTest from './PropsTest';
|
||||
import RedirectRouting from './RedirectRouting';
|
||||
|
||||
interface RoutingProps {}
|
||||
|
||||
const Routing: React.FC<RoutingProps> = () => {
|
||||
return (
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/routing/tabs" render={() => <Tabs />} />
|
||||
{/* <Route path="/routing/tabs" component={Tabs} /> */}
|
||||
<Route path="/routing/" render={() => <Redirect to="/routing/tabs" />} exact />
|
||||
<Route path="/routing/favorites" component={Favorites} />
|
||||
{/* <Route path="/routing/favorites" render={() => {
|
||||
return (
|
||||
<IonRouterOutlet id="favorites">
|
||||
<Route path="/routing/favorites" component={Favorites} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}} /> */}
|
||||
{/* <Route path="/routing/otherpage" render={() => {
|
||||
return (
|
||||
<IonRouterOutlet id="otherpage">
|
||||
<Route path="/routing/otherpage" component={OtherPage} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}} /> */}
|
||||
<Route path="/routing/otherpage" component={OtherPage} />
|
||||
<Route path="/routing/propstest" component={PropsTest} />
|
||||
<Route path="/routing/redirect" render={() => <Redirect to="/routing/tabs" />} />
|
||||
<Route path="/routing/redirect-routing" render={() => <RedirectRouting />} />
|
||||
<Route
|
||||
render={() => (
|
||||
<IonPage data-pageid="not-found">
|
||||
<IonContent>
|
||||
<div>Not found</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
)}
|
||||
/>
|
||||
{/* <Route render={() => <Redirect to="/tabs" />} /> */}
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
);
|
||||
};
|
||||
|
||||
export default Routing;
|
||||
@@ -0,0 +1,50 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
interface DetailsProps {}
|
||||
|
||||
const SettingsDetails: React.FC<DetailsProps> = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Settings Details mount');
|
||||
return () => console.log('Settings Details unmount');
|
||||
}, []);
|
||||
|
||||
const nextId = parseInt(id, 10) + 1;
|
||||
// LEFT OFF - why is back button not working for multiple entries?
|
||||
|
||||
return (
|
||||
<IonPage data-pageid={`settings-details-page-${id}`}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<IonBackButton defaultHref="/routing/tabs/settings"></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Settings Details</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonLabel data-testid="details-label">Details {id}</IonLabel>
|
||||
<br />
|
||||
<br />
|
||||
<IonButton routerLink={`/routing/tabs/settings/details/${nextId}`}>
|
||||
<IonLabel>Go to Settings Details {nextId}</IonLabel>
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsDetails;
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonList,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonButtons,
|
||||
IonMenuButton,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import './Tab2.css';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
const Tab2: React.FC = () => {
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Settings mount');
|
||||
return () => console.log('Settings unmount');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="settings-page">
|
||||
<IonHeader translucent={true}>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Settings</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Settings</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonList>
|
||||
<IonItem routerLink="/routing/tabs/settings/details/1">
|
||||
<IonLabel>Settings Details 1</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/routing/tabs/settings/details/2">
|
||||
<IonLabel>Settings Details 2</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<br />
|
||||
<br />
|
||||
<IonButton
|
||||
onClick={() => {
|
||||
history.push('/routing/tabs/settings/details/1', { routerOptions: { unmount: true } });
|
||||
}}
|
||||
>
|
||||
Details with Unmount via history.push
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab2;
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import Tab1 from './Tab1';
|
||||
import Details from './Details';
|
||||
import Tab2 from './Tab2';
|
||||
import Tab3 from './Tab3';
|
||||
import { triangle, ellipse, square } from 'ionicons/icons';
|
||||
import SettingsDetails from './SettingsDetails';
|
||||
|
||||
interface TabsProps {}
|
||||
|
||||
const Tabs: React.FC<TabsProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/routing/tabs/home" component={Tab1} exact />
|
||||
<Route path="/routing/tabs/home/details/:id" component={Details} exact={true} />
|
||||
{/* <Route path="/routing/tabs/home/details/:id" render={(props) => {
|
||||
return <Details />
|
||||
}} exact={true} /> */}
|
||||
<Route path="/routing/tabs/settings" component={Tab2} exact={true} />
|
||||
<Route path="/routing/tabs/settings/details/:id" component={SettingsDetails} exact={true} />
|
||||
<Route path="/routing/tabs/tab3" component={Tab3} />
|
||||
<Route
|
||||
path="/routing/tabs"
|
||||
render={() => <Redirect to="/routing/tabs/home" />}
|
||||
exact={true}
|
||||
/>
|
||||
<Route
|
||||
path="/routing/tabs/redirect"
|
||||
render={() => <Redirect to="/routing/tabs/settings" />}
|
||||
exact={true}
|
||||
/>
|
||||
{/* <Route path="/routing/tabs" render={() => <Route render={() => <Redirect to="/tabs/home" />} />} /> */}
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="home" href="/routing/tabs/home" routerOptions={{ unmount: true }}>
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Home</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="settings" href="/routing/tabs/settings">
|
||||
<IonIcon icon={ellipse} />
|
||||
<IonLabel>Settings</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab3" href="/routing/tabs/tab3">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab 3</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonRouterOutlet,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonItem,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
} from '@ionic/react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
interface SwipeToGoBackProps {}
|
||||
|
||||
export const SwipeToGoBack: React.FC<SwipeToGoBackProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet id="swipe-to-go-back">
|
||||
<Route path="/swipe-to-go-back" component={Main} exact />
|
||||
<Route path="/swipe-to-go-back/details" component={Details} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
const Main: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="main">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Main</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonItem routerLink="/swipe-to-go-back/details">Details</IonItem>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Details: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="details">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<IonBackButton></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Details</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>Details</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { useContext } from 'react';
|
||||
import {
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonMenuButton,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonTabsContext,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsContextProps {}
|
||||
|
||||
const TabsContext: React.FC<TabsContextProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/tab-context/tab1" component={Tab1} exact />
|
||||
<Route path="/tab-context/tab2" component={Tab2} exact />
|
||||
<Redirect from="/tab-context" to="/tab-context/tab1" exact />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/tab-context/tab1" routerOptions={{ unmount: true }}>
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Tab1</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/tab-context/tab2">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab2</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1 = () => {
|
||||
const tabContext = useContext(IonTabsContext);
|
||||
|
||||
return (
|
||||
<IonPage id="home" data-pageid="tab1">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>Page: {tabContext.activeTab}</div>
|
||||
<IonButton onClick={() => tabContext.selectTab('tab2')}>Go to tab2</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2 = () => {
|
||||
const tabContext = useContext(IonTabsContext);
|
||||
|
||||
return (
|
||||
<IonPage id="home" data-pageid="tab2">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div>Page: {tabContext.activeTab}</div>
|
||||
<IonButton onClick={() => tabContext.selectTab('tab1')}>Go to tab1</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabsContext;
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsProps {}
|
||||
|
||||
const Tabs: React.FC<TabsProps> = () => {
|
||||
return (
|
||||
<IonTabs data-pageid="tabs">
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/tabs/tab1" component={Tab1} exact />
|
||||
<Route path="/tabs/tab2" component={Tab2} exact />
|
||||
<Route path="/tabs/tab1/child" component={Tab1Child1} exact />
|
||||
<Route path="/tabs/tab1/child2" component={Tab1Child2} exact />
|
||||
<Redirect from="/tabs" to="/tabs/tab1" exact />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/tabs/tab1">
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Tab1</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2" href="/tabs/tab2">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab2</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab1">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton routerLink="/tabs/tab1/child" id="child-one">Go to Tab1Child1</IonButton>
|
||||
<IonButton routerLink="/tabs-secondary/tab1" id="tabs-secondary">Go to Secondary Tabs</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1Child1 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab1child1">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
Tab 1 Child 1
|
||||
<IonButton routerLink="/tabs/tab1/child2" id="child-two">Go to Tab1Child2</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1Child2 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab1child2">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Tab1</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
Tab 1 Child 2
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab2">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
Tab 2
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsSecondaryProps {}
|
||||
|
||||
const TabsSecondary: React.FC<TabsSecondaryProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs-secondary">
|
||||
<Route path="/tabs-secondary/tab1" component={Tab1} exact />
|
||||
<Route path="/tabs-secondary/tab2" component={Tab2} exact />
|
||||
<Redirect from="/tabs-secondary" to="/tabs-secondary/tab1" exact />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1-secondary" href="/tabs-secondary/tab1">
|
||||
<IonIcon icon={triangle} />
|
||||
<IonLabel>Tab1</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="tab2-secondary" href="/tabs-secondary/tab2">
|
||||
<IonIcon icon={square} />
|
||||
<IonLabel>Tab2</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab1 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab1-secondary">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab1</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
Tab 1
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const Tab2 = () => {
|
||||
return (
|
||||
<IonPage data-pageid="tab2-secondary">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Tab2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
Tab 2
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabsSecondary;
|
||||
38620
packages/react-router/test/apps/reactrouter6/package-lock.json
generated
Normal file
38620
packages/react-router/test/apps/reactrouter6/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
63
packages/react-router/test/apps/reactrouter6/package.json
Normal file
63
packages/react-router/test/apps/reactrouter6/package.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "react-router-new",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ionic/react": "^8.6.1",
|
||||
"@ionic/react-router": "^8.6.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@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": "^6.0.0",
|
||||
"react-router-dom": "^6.0.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"sass-loader": "8.0.2",
|
||||
"typescript": "^4.4.2",
|
||||
"wait-on": "^5.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "cypress open",
|
||||
"cypress": "node_modules/.bin/cypress run --headless --browser chrome",
|
||||
"cypress.open": "cypress open",
|
||||
"e2e": "concurrently \"serve -s build -l 3000\" \"wait-on http-get://localhost:3000 && npm run cypress\" --kill-others --success first",
|
||||
"sync": "sh ./scripts/sync.sh"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^6.3.0",
|
||||
"cypress": "^13.2.0",
|
||||
"serve": "^14.0.1",
|
||||
"wait-on": "^6.0.0",
|
||||
"webpack-cli": "^4.9.1"
|
||||
},
|
||||
"description": "An Ionic project",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
}
|
||||
@@ -46,23 +46,23 @@ const App: React.FC = () => {
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonRouterOutlet>
|
||||
<Route path="/" component={Main} exact />
|
||||
<Route path="/routing" component={Routing} />
|
||||
<Route path="/dynamic-routes" component={DynamicRoutes} />
|
||||
<Route path="/multiple-tabs" component={MultipleTabs} />
|
||||
<Route path="/dynamic-tabs" component={DynamicTabs} />
|
||||
<Route path="/nested-outlet" component={NestedOutlet} />
|
||||
<Route path="/nested-outlet2" component={NestedOutlet2} />
|
||||
<Route path="/replace-action" component={ReplaceAction} />
|
||||
<Route path="/tab-context" component={TabsContext} />
|
||||
<Route path="/outlet-ref" component={OutletRef} />
|
||||
<Route path="/swipe-to-go-back" component={SwipeToGoBack} />
|
||||
<Route path="/dynamic-ionpage-classnames" component={DynamicIonpageClassnames} />
|
||||
<Route path="/tabs" component={Tabs} />
|
||||
<Route path="/tabs-secondary" component={TabsSecondary} />
|
||||
<Route path="/refs" component={Refs} />
|
||||
<Route path="/overlays" component={Overlays} />
|
||||
<Route path="/params/:id" component={Params} />
|
||||
<Route path="/" element={<Main />} />
|
||||
<Route path="/routing/*" element={<Routing />} />
|
||||
<Route path="/dynamic-routes" element={<DynamicRoutes />} />
|
||||
<Route path="/multiple-tabs" element={<MultipleTabs />} />
|
||||
<Route path="/dynamic-tabs" element={<DynamicTabs />} />
|
||||
<Route path="/nested-outlet" element={<NestedOutlet />} />
|
||||
<Route path="/nested-outlet2" element={<NestedOutlet2 />} />
|
||||
<Route path="/replace-action" element={<ReplaceAction />} />
|
||||
<Route path="/tab-context" element={<TabsContext />} />
|
||||
<Route path="/outlet-ref" element={<OutletRef />} />
|
||||
<Route path="/swipe-to-go-back" element={<SwipeToGoBack />} />
|
||||
<Route path="/dynamic-ionpage-classnames" element={<DynamicIonpageClassnames/ >} />
|
||||
<Route path="/tabs" element={<Tabs />} />
|
||||
<Route path="/tabs-secondary" element={<TabsSecondary />} />
|
||||
<Route path="/refs" element={<Refs />} />
|
||||
<Route path="/overlays" element={<Overlays />} />
|
||||
<Route path="/params/:id" element={<Params />} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
|
||||
@@ -10,14 +10,20 @@ import {
|
||||
IonLabel,
|
||||
} from '@ionic/react';
|
||||
|
||||
import packageJson from '../../package.json';
|
||||
|
||||
interface MainProps {}
|
||||
|
||||
const Main: React.FC<MainProps> = () => {
|
||||
const majorVersion = packageJson.dependencies['react-router'].match(
|
||||
/(\d+)\.(\d+)\.(\d+)/
|
||||
)?.[1];
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="home">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Main</IonTitle>
|
||||
<IonTitle>Test App - React Router v{majorVersion}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface DynamicIonpageClassnamesProps {}
|
||||
const DynamicIonpageClassnames: React.FC<DynamicIonpageClassnamesProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/dynamic-ionpage-classnames" component={Page} />
|
||||
<Route path="/dynamic-ionpage-classnames" element={<Page />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,32 +7,31 @@ import {
|
||||
IonToolbar,
|
||||
IonRouterOutlet,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const DynamicRoutes: React.FC = () => {
|
||||
const [routes, setRoutes] = useState<ReactElement[]>([
|
||||
<Route
|
||||
key="sldjflsdj"
|
||||
path="/dynamic-routes/home"
|
||||
render={() => <Home update={addRoute} />}
|
||||
exact={true}
|
||||
/>,
|
||||
]);
|
||||
|
||||
const addRoute = () => {
|
||||
const newRoute = (
|
||||
<Route key="lsdjldj" path="/dynamic-routes/newRoute" component={NewRoute} exact={true} />
|
||||
<Route key="lsdjldj" path="/dynamic-routes/newRoute" element={<NewRoute />} />
|
||||
);
|
||||
setRoutes([...routes, newRoute]);
|
||||
};
|
||||
|
||||
const [routes, setRoutes] = useState<ReactElement[]>([
|
||||
<Route
|
||||
key="sldjflsdj"
|
||||
path="/dynamic-routes/home"
|
||||
element={<Home update={addRoute} />}
|
||||
/>,
|
||||
]);
|
||||
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
{routes}
|
||||
{/* <Route exact path="/home" render={() => <Home update={addRoute} />} /> */}
|
||||
<Route exact path="/dynamic-routes" render={() => <Redirect to="/dynamic-routes/home" />} />
|
||||
<Route render={() => <Failed />} />
|
||||
{/* <Route path="/home" render={() => <Home update={addRoute} />} /> */}
|
||||
<Route path="/dynamic-routes" element={<Navigate to="/dynamic-routes/home" replace />} />
|
||||
<Route element={<Failed />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
IonLabel,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
@@ -22,16 +22,16 @@ const DynamicTabs: React.FC = () => {
|
||||
const [display2ndTab, setDisplayThirdTab] = useState<boolean>(false);
|
||||
|
||||
const renderFirstTab = useCallback(() => {
|
||||
return <Tab1 setDisplayThirdTab={() => setDisplayThirdTab(!display2ndTab)} />;
|
||||
}, [display2ndTab]);
|
||||
return <Tab1 setDisplayThirdTab={() => setDisplayThirdTab(true)} />;
|
||||
}, []);
|
||||
|
||||
const render2ndTabRoute = useCallback(() => {
|
||||
if (display2ndTab) {
|
||||
return <Route path="/dynamic-tabs/tab2" component={Tab2} />;
|
||||
return <Route path="/dynamic-tabs/tab2" element={<Tab2 />} />;
|
||||
} else {
|
||||
// This is weird, if I return null or undefined then I get all sorts of errors, seemingly
|
||||
// because the router is mad about a child not being a route.
|
||||
return <Route path="/dynamic-tabs/tab200" component={Tab1} />;
|
||||
return <Route path="/dynamic-tabs/tab200" element={<Tab1 setDisplayThirdTab={setDisplayThirdTab} />} />;
|
||||
}
|
||||
}, [display2ndTab]);
|
||||
|
||||
@@ -40,14 +40,10 @@ const DynamicTabs: React.FC = () => {
|
||||
<IonReactRouter>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Route path="/dynamic-tabs/tab1" render={renderFirstTab} exact={true} />
|
||||
<Route path="/dynamic-tabs/tab1" element={renderFirstTab()} />
|
||||
{render2ndTabRoute()}
|
||||
<Route
|
||||
path="/dynamic-tabs/"
|
||||
render={() => <Redirect to="/dynamic-tabs/tab1" />}
|
||||
exact={true}
|
||||
/>
|
||||
<Route render={() => <Redirect to="/dynamic-tabs/tab1" />} />
|
||||
<Route path="/dynamic-tabs" element={<Navigate to="/dynamic-tabs/tab1" replace />} />
|
||||
<Route path="*" element={<Navigate to="/dynamic-tabs/tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/dynamic-tabs/tab1">
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { Menu } from './Menu';
|
||||
import { triangle, ellipse, square, rocket } from 'ionicons/icons';
|
||||
|
||||
@@ -26,24 +26,14 @@ const MultipleTabs: React.FC = () => {
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route
|
||||
path="/multiple-tabs/tab1"
|
||||
render={() => {
|
||||
return <Tab1 />;
|
||||
}}
|
||||
exact={false}
|
||||
path="/multiple-tabs/tab1/*"
|
||||
element={<Tab1 />}
|
||||
/>
|
||||
<Route
|
||||
path="/multiple-tabs/tab2"
|
||||
render={() => {
|
||||
return <Tab2 />;
|
||||
}}
|
||||
exact={false}
|
||||
/>
|
||||
<Route
|
||||
path="/multiple-tabs"
|
||||
render={() => <Redirect to="/multiple-tabs/tab1" />}
|
||||
exact={true}
|
||||
path="/multiple-tabs/tab2/*"
|
||||
element={<Tab2 />}
|
||||
/>
|
||||
<Route path="/multiple-tabs" element={<Navigate to="/multiple-tabs/tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
);
|
||||
@@ -68,12 +58,11 @@ const Tab1: React.FC = () => {
|
||||
<IonRouterOutlet id="tab1">
|
||||
<Route
|
||||
path="/multiple-tabs/tab1"
|
||||
render={() => <Redirect to="/multiple-tabs/tab1/pagea" />}
|
||||
exact={true}
|
||||
element={<Navigate to="/multiple-tabs/tab1/pagea" replace />}
|
||||
/>
|
||||
{/* <Redirect path="/multiple-tabs/event" to="/multiple-tabs/tab1/pagea" exact={true} /> */}
|
||||
<Route path="/multiple-tabs/tab1/pagea" render={() => <Page name="PageA" />} exact={true} />
|
||||
<Route path="/multiple-tabs/tab1/pageb" render={() => <Page name="PageB" />} exact={true} />
|
||||
{/* <Route path="/multiple-tabs/event" element={<Navigate to="/multiple-tabs/tab1/pagea" replace />} /> */}
|
||||
<Route path="/multiple-tabs/tab1/pagea" element={<Page name="PageA" />} />
|
||||
<Route path="/multiple-tabs/tab1/pageb" element={<Page name="PageB" />} />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
@@ -95,12 +84,11 @@ const Tab2: React.FC = () => {
|
||||
<IonRouterOutlet id="tab2">
|
||||
<Route
|
||||
path="/multiple-tabs/tab2"
|
||||
render={() => <Redirect to="/multiple-tabs/tab2/pagec" />}
|
||||
exact={true}
|
||||
element={<Navigate to="/multiple-tabs/tab2/pagec" replace />}
|
||||
/>
|
||||
{/* <Redirect path="/multiple-tabs/tab2" to="/multiple-tabs/tab2/pagec" exact={true} /> */}
|
||||
<Route path="/multiple-tabs/tab2/pagec" render={() => <Page name="PageC" />} exact={true} />
|
||||
<Route path="/multiple-tabs/tab2/paged" render={() => <Page name="PageD" />} exact={true} />
|
||||
{/* <Route path="/multiple-tabs/tab2" element={<Navigate to="/multiple-tabs/tab2/pagec" replace />} /> */}
|
||||
<Route path="/multiple-tabs/tab2/pagec" element={<Page name="PageC" />} />
|
||||
<Route path="/multiple-tabs/tab2/paged" element={<Page name="PageD" />} />
|
||||
</IonRouterOutlet>
|
||||
</IonTabs>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@ionic/react';
|
||||
import { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
useEffect(() => {
|
||||
@@ -48,10 +48,9 @@ const SecondPage: React.FC = () => {
|
||||
<IonRouterOutlet ionPage>
|
||||
<Route
|
||||
path="/nested-outlet/secondpage"
|
||||
exact={true}
|
||||
render={() => <Redirect to="/nested-outlet/secondpage/page" />}
|
||||
element={<Navigate to="/nested-outlet/secondpage/page" replace />}
|
||||
/>
|
||||
<Route path="/nested-outlet/secondpage/page" component={Page} exact={true} />
|
||||
<Route path="/nested-outlet/secondpage/page" element={<Page />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
@@ -81,8 +80,8 @@ const FirstPage: React.FC = () => {
|
||||
|
||||
const NestedOutlet: React.FC = () => (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/nested-outlet" component={FirstPage} exact={true} />
|
||||
<Route path="/nested-outlet/secondpage" component={SecondPage} />
|
||||
<Route path="/nested-outlet" element={<FirstPage />} />
|
||||
<Route path="/nested-outlet/secondpage" element={<SecondPage />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, RouteComponentProps } from 'react-router-dom';
|
||||
import { Navigate, Route, useParams } from 'react-router-dom';
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
|
||||
const ListPage: React.FC<RouteComponentProps> = ({ match }) => {
|
||||
const ListPage: React.FC = () => {
|
||||
return (
|
||||
<IonRouterOutlet ionPage id="listpage">
|
||||
<Route exact path="/nested-outlet2/list" component={List} />
|
||||
<Route path={`${match.url}/:id`} component={Item} />
|
||||
<Route path="/nested-outlet2/list" element={<List />} />
|
||||
<Route path="/nested-outlet2/list/:id" element={<Item />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
@@ -51,7 +51,9 @@ const List: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Item: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
const Item: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
return (
|
||||
<IonPage data-pageid="item">
|
||||
<IonHeader>
|
||||
@@ -62,16 +64,16 @@ const Item: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
<IonTitle>Item</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>Detail of item #{match.params.id}</IonContent>
|
||||
<IonContent>Detail of item #{id}</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
const HomePage: React.FC<RouteComponentProps> = ({ match }) => {
|
||||
const HomePage: React.FC = () => {
|
||||
return (
|
||||
<IonRouterOutlet ionPage id="homepage">
|
||||
<Route exact path="/nested-outlet2/home" component={Home} />
|
||||
<Route path="/nested-outlet2/home/welcome" component={Welcome} />
|
||||
<Route path="/nested-outlet2/home" element={<Home />} />
|
||||
<Route path="/nested-outlet2/home/welcome" element={<Welcome />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
@@ -98,7 +100,7 @@ const Welcome: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Home: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<IonPage data-pageid="home">
|
||||
<IonHeader>
|
||||
@@ -122,12 +124,11 @@ const Home: React.FC<RouteComponentProps<{ id: string }>> = ({ match }) => {
|
||||
|
||||
const NestedOutlet2: React.FC = () => (
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/nested-outlet2/list" component={ListPage} />
|
||||
<Route path="/nested-outlet2/home" component={HomePage} />
|
||||
<Route path="/nested-outlet2/list" element={<ListPage />} />
|
||||
<Route path="/nested-outlet2/home" element={<HomePage />} />
|
||||
<Route
|
||||
path="/nested-outlet2"
|
||||
render={() => <Redirect to="/nested-outlet2/home" />}
|
||||
exact={true}
|
||||
element={<Navigate to="/nested-outlet2/home" replace />}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
@@ -22,9 +22,7 @@ export const OutletRef: React.FC<OutletRefProps> = () => {
|
||||
<IonRouterOutlet id="main-outlet" ref={ref}>
|
||||
<Route
|
||||
path="/outlet-ref"
|
||||
render={() => {
|
||||
return <Main outletId={ref.current?.id} />;
|
||||
}}
|
||||
element={<Main outletId={ref.current?.id} />}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { IonButton, IonContent, IonModal } from '@ionic/react';
|
||||
import { useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
const Overlays: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goBack = () => history.goBack();
|
||||
const replace = () => history.replace('/');
|
||||
const push = () => history.push('/');
|
||||
const goBack = () => navigate(-1);
|
||||
const replace = () => navigate('/', { replace: true });
|
||||
const push = () => navigate('/');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -9,30 +9,28 @@ import {
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
interface PageProps
|
||||
extends RouteComponentProps<{
|
||||
id: string;
|
||||
}> {}
|
||||
const Page: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const parseID = id ? parseInt(id) : NaN;
|
||||
const displayId = id || 'N/A';
|
||||
const nextParamLink = !isNaN(parseID) ? `/params/${parseID + 1}` : '/params/1';
|
||||
|
||||
|
||||
const Page: React.FC<PageProps> = ({ match }) => {
|
||||
const parseID = parseInt(match.params.id);
|
||||
return (
|
||||
<IonPage data-pageid={'params-' + match.params.id }>
|
||||
<IonPage data-pageid={'params-' + displayId }>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Params { match.params.id }</IonTitle>
|
||||
<IonTitle>Params { displayId }</IonTitle>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonButton id="next-page" routerLink={'/params/' + (parseID + 1) } >Go to next param</IonButton>
|
||||
<IonButton id="next-page" routerLink={nextParamLink} >Go to next param</IonButton>
|
||||
<br />
|
||||
Page ID: { match.params.id }
|
||||
Page ID: { displayId }
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,9 @@ interface RefsProps {}
|
||||
const Refs: React.FC = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
{/* <Route exact path="/home" render={() => <Home update={addRoute} />} /> */}
|
||||
<Route exact path="/refs" component={RefsFC} />
|
||||
<Route exact path="/refs/class" component={RefsClass} />
|
||||
{/* <Route path="/home" element={<Home update={addRoute} />} /> */}
|
||||
<Route path="/refs" element={<RefsFC />} />
|
||||
<Route path="/refs/class" element={<RefsClass />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,17 +10,17 @@ import {
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect, useHistory } from 'react-router';
|
||||
import { Route, Navigate, useNavigate } from 'react-router';
|
||||
|
||||
interface TopPageProps {}
|
||||
|
||||
const ReplaceAction: React.FC<TopPageProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/replace-action/page1" component={Page1} exact />
|
||||
<Route path="/replace-action/page2" component={Page2} exact />
|
||||
<Route path="/replace-action/page3" component={Page3} exact />
|
||||
<Route exact path="/replace-action" render={() => <Redirect to="/replace-action/page1" />} />
|
||||
<Route path="/replace-action/page1" element={<Page1 />} />
|
||||
<Route path="/replace-action/page2" element={<Page2 />} />
|
||||
<Route path="/replace-action/page3" element={<Page3 />} />
|
||||
<Route path="/replace-action" element={<Navigate to="/replace-action/page1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
@@ -42,10 +42,10 @@ const Page1: React.FC = () => (
|
||||
);
|
||||
|
||||
const Page2: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const clickButton = () => {
|
||||
history.replace('/replace-action/page3');
|
||||
navigate('/replace-action/page3', { replace: true });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -24,7 +24,7 @@ const Details: React.FC<DetailsProps> = () => {
|
||||
return () => console.log('Home Details unmount');
|
||||
}, []);
|
||||
|
||||
const nextId = parseInt(id, 10) + 1;
|
||||
const nextId = parseInt(id ?? '0', 10) + 1;
|
||||
|
||||
return (
|
||||
<IonPage data-pageid={`home-details-page-${id}`}>
|
||||
|
||||
@@ -25,7 +25,7 @@ const OtherPage: React.FC<OtherPageProps> = () => {
|
||||
|
||||
return (
|
||||
// <IonRouterOutlet id="other" ionPageContainer>
|
||||
// <Route path="/otherpage" render={() => (
|
||||
// <Route path="/otherpage" element={
|
||||
<IonPage data-pageid="other-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
@@ -39,7 +39,7 @@ const OtherPage: React.FC<OtherPageProps> = () => {
|
||||
<IonButton routerLink="/routing/tabs/tab3">Go to tab3</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
// )}></Route>
|
||||
// }></Route>
|
||||
// </IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ const PropsTest: React.FC<PropsTestProps> = () => {
|
||||
<IonRouterOutlet>
|
||||
<Route
|
||||
path="/routing/propstest"
|
||||
render={() => <InnerPropsTest count={count} setCount={setCount} />}
|
||||
element={<InnerPropsTest count={count} setCount={setCount} />}
|
||||
/>
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
IonSplitPane,
|
||||
} from '@ionic/react';
|
||||
import Menu from './Menu';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import Tabs from './Tabs';
|
||||
import Favorites from './Favorites';
|
||||
import OtherPage from './OtherPage';
|
||||
@@ -20,38 +20,25 @@ const Routing: React.FC<RoutingProps> = () => {
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/routing/tabs" render={() => <Tabs />} />
|
||||
{/* <Route path="/routing/tabs" component={Tabs} /> */}
|
||||
<Route path="/routing/" render={() => <Redirect to="/routing/tabs" />} exact />
|
||||
<Route path="/routing/favorites" component={Favorites} />
|
||||
{/* <Route path="/routing/favorites" render={() => {
|
||||
return (
|
||||
<IonRouterOutlet id="favorites">
|
||||
<Route path="/routing/favorites" component={Favorites} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}} /> */}
|
||||
{/* <Route path="/routing/otherpage" render={() => {
|
||||
return (
|
||||
<IonRouterOutlet id="otherpage">
|
||||
<Route path="/routing/otherpage" component={OtherPage} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
}} /> */}
|
||||
<Route path="/routing/otherpage" component={OtherPage} />
|
||||
<Route path="/routing/propstest" component={PropsTest} />
|
||||
<Route path="/routing/redirect" render={() => <Redirect to="/routing/tabs" />} />
|
||||
<Route path="/routing/redirect-routing" render={() => <RedirectRouting />} />
|
||||
<Route index element={<Navigate to="/routing/tabs" replace />} />
|
||||
|
||||
<Route path="tabs" element={<Tabs />} />
|
||||
<Route path="favorites" element={<Favorites />} />
|
||||
<Route path="otherpage" element={<OtherPage />} />
|
||||
<Route path="propstest" element={<PropsTest />} />
|
||||
<Route path="redirect" element={<Navigate to="/routing/tabs" replace />} />
|
||||
<Route path="redirect-routing" element={<RedirectRouting />} />
|
||||
|
||||
<Route
|
||||
render={() => (
|
||||
path="*"
|
||||
element={
|
||||
<IonPage data-pageid="not-found">
|
||||
<IonContent>
|
||||
<div>Not found</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
{/* <Route render={() => <Redirect to="/tabs" />} /> */}
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ const SettingsDetails: React.FC<DetailsProps> = () => {
|
||||
return () => console.log('Settings Details unmount');
|
||||
}, []);
|
||||
|
||||
const nextId = parseInt(id, 10) + 1;
|
||||
const nextId = parseInt(id ?? '0', 10) + 1;
|
||||
// LEFT OFF - why is back button not working for multiple entries?
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import './Tab2.css';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
const Tab2: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Settings mount');
|
||||
@@ -51,10 +51,10 @@ const Tab2: React.FC = () => {
|
||||
<br />
|
||||
<IonButton
|
||||
onClick={() => {
|
||||
history.push('/routing/tabs/settings/details/1', { routerOptions: { unmount: true } });
|
||||
navigate('/routing/tabs/settings/details/1');
|
||||
}}
|
||||
>
|
||||
Details with Unmount via history.push
|
||||
Details with Unmount via navigate
|
||||
</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import Tab1 from './Tab1';
|
||||
import Details from './Details';
|
||||
import Tab2 from './Tab2';
|
||||
@@ -14,25 +14,21 @@ const Tabs: React.FC<TabsProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/routing/tabs/home" component={Tab1} exact />
|
||||
<Route path="/routing/tabs/home/details/:id" component={Details} exact={true} />
|
||||
{/* <Route path="/routing/tabs/home/details/:id" render={(props) => {
|
||||
return <Details />
|
||||
}} exact={true} /> */}
|
||||
<Route path="/routing/tabs/settings" component={Tab2} exact={true} />
|
||||
<Route path="/routing/tabs/settings/details/:id" component={SettingsDetails} exact={true} />
|
||||
<Route path="/routing/tabs/tab3" component={Tab3} />
|
||||
<Route path="/routing/tabs/home" element={<Tab1 />} />
|
||||
<Route path="/routing/tabs/home/details/:id" element={<Details />} />
|
||||
{/* <Route path="/routing/tabs/home/details/:id" element={<Details />} /> */}
|
||||
<Route path="/routing/tabs/settings" element={<Tab2 />} />
|
||||
<Route path="/routing/tabs/settings/details/:id" element={<SettingsDetails />} />
|
||||
<Route path="/routing/tabs/tab3" element={<Tab3 />} />
|
||||
<Route
|
||||
path="/routing/tabs"
|
||||
render={() => <Redirect to="/routing/tabs/home" />}
|
||||
exact={true}
|
||||
element={<Navigate to="/routing/tabs/home" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="/routing/tabs/redirect"
|
||||
render={() => <Redirect to="/routing/tabs/settings" />}
|
||||
exact={true}
|
||||
element={<Navigate to="/routing/tabs/settings" replace />}
|
||||
/>
|
||||
{/* <Route path="/routing/tabs" render={() => <Route render={() => <Redirect to="/tabs/home" />} />} /> */}
|
||||
{/* <Route path="/routing/tabs" element={<Navigate to="/tabs/home" replace />} /> */}
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="home" href="/routing/tabs/home" routerOptions={{ unmount: true }}>
|
||||
|
||||
@@ -17,8 +17,8 @@ interface SwipeToGoBackProps {}
|
||||
export const SwipeToGoBack: React.FC<SwipeToGoBackProps> = () => {
|
||||
return (
|
||||
<IonRouterOutlet id="swipe-to-go-back">
|
||||
<Route path="/swipe-to-go-back" component={Main} exact />
|
||||
<Route path="/swipe-to-go-back/details" component={Details} />
|
||||
<Route path="/swipe-to-go-back" element={<Main />} />
|
||||
<Route path="/swipe-to-go-back/details" element={<Details />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
IonTabsContext,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsContextProps {}
|
||||
@@ -25,9 +25,9 @@ const TabsContext: React.FC<TabsContextProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/tab-context/tab1" component={Tab1} exact />
|
||||
<Route path="/tab-context/tab2" component={Tab2} exact />
|
||||
<Redirect from="/tab-context" to="/tab-context/tab1" exact />
|
||||
<Route path="/tab-context/tab1" element={<Tab1 />} />
|
||||
<Route path="/tab-context/tab2" element={<Tab2 />} />
|
||||
<Route path="/tab-context" element={<Navigate to="/tab-context/tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/tab-context/tab1" routerOptions={{ unmount: true }}>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
IonContent,
|
||||
IonButton,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsProps {}
|
||||
@@ -24,11 +24,11 @@ const Tabs: React.FC<TabsProps> = () => {
|
||||
return (
|
||||
<IonTabs data-pageid="tabs">
|
||||
<IonRouterOutlet id="tabs">
|
||||
<Route path="/tabs/tab1" component={Tab1} exact />
|
||||
<Route path="/tabs/tab2" component={Tab2} exact />
|
||||
<Route path="/tabs/tab1/child" component={Tab1Child1} exact />
|
||||
<Route path="/tabs/tab1/child2" component={Tab1Child2} exact />
|
||||
<Redirect from="/tabs" to="/tabs/tab1" exact />
|
||||
<Route path="/tabs/tab1" element={<Tab1 />} />
|
||||
<Route path="/tabs/tab2" element={<Tab2 />} />
|
||||
<Route path="/tabs/tab1/child" element={<Tab1Child1 />} />
|
||||
<Route path="/tabs/tab1/child2" element={<Tab1Child2 />} />
|
||||
<Route path="/tabs" element={<Navigate to="/tabs/tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1" href="/tabs/tab1">
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
IonTitle,
|
||||
IonContent,
|
||||
} from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
import { triangle, square } from 'ionicons/icons';
|
||||
|
||||
interface TabsSecondaryProps {}
|
||||
@@ -23,9 +23,9 @@ const TabsSecondary: React.FC<TabsSecondaryProps> = () => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet id="tabs-secondary">
|
||||
<Route path="/tabs-secondary/tab1" component={Tab1} exact />
|
||||
<Route path="/tabs-secondary/tab2" component={Tab2} exact />
|
||||
<Redirect from="/tabs-secondary" to="/tabs-secondary/tab1" exact />
|
||||
<Route path="/tabs-secondary/tab1" element={<Tab1 />} />
|
||||
<Route path="/tabs-secondary/tab2" element={<Tab2 />} />
|
||||
<Route path="/tabs-secondary" element={<Navigate to="/tabs-secondary/tab1" replace />} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="tab1-secondary" href="/tabs-secondary/tab1">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Location as HistoryLocation } from 'history';
|
||||
import type { Location as HistoryLocation } from 'history';
|
||||
|
||||
const RESTRICT_SIZE = 25;
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import { NavContext } from '../contexts/NavContext';
|
||||
|
||||
export interface IonRouteProps {
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
show?: boolean;
|
||||
render: (props?: any) => JSX.Element; // TODO(FW-2959): type
|
||||
element: React.ReactElement;
|
||||
disableIonPageManagement?: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user