@ionic/react-router 7.5.3-dev.11698858201.14165f23 → 7.5.3-dev.11698859252.15ac163e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,911 @@
1
- "use strict";
2
- export { IonReactRouter } from "./ReactRouter/IonReactRouter";
3
- export { IonReactMemoryRouter } from "./ReactRouter/IonReactMemoryRouter";
4
- export { IonReactHashRouter } from "./ReactRouter/IonReactHashRouter";
1
+ import { __rest } from 'tslib';
2
+ import { createBrowserHistory, createHashHistory } from 'history';
3
+ import React from 'react';
4
+ import { matchPath as matchPath$1, withRouter, Router } from 'react-router-dom';
5
+ import { ViewStacks, generateId, IonRoute, ViewLifeCycleManager, StackContext, RouteManagerContext, getConfig, LocationHistory, NavManager } from '@ionic/react';
6
+ import { Route, matchPath, Router as Router$1 } from 'react-router';
7
+
8
+ class IonRouteInner extends React.PureComponent {
9
+ render() {
10
+ return (React.createElement(Route, Object.assign({ path: this.props.path, exact: this.props.exact, render: this.props.render }, (this.props.computedMatch !== undefined
11
+ ? {
12
+ computedMatch: this.props.computedMatch,
13
+ }
14
+ : {}))));
15
+ }
16
+ }
17
+
18
+ class ReactRouterViewStack extends ViewStacks {
19
+ constructor() {
20
+ super();
21
+ this.createViewItem = this.createViewItem.bind(this);
22
+ this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this);
23
+ this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
24
+ this.getChildrenToRender = this.getChildrenToRender.bind(this);
25
+ this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
26
+ }
27
+ createViewItem(outletId, reactElement, routeInfo, page) {
28
+ const viewItem = {
29
+ id: generateId('viewItem'),
30
+ outletId,
31
+ ionPageElement: page,
32
+ reactElement,
33
+ mount: true,
34
+ ionRoute: false,
35
+ };
36
+ const matchProps = {
37
+ exact: reactElement.props.exact,
38
+ path: reactElement.props.path || reactElement.props.from,
39
+ component: reactElement.props.component,
40
+ };
41
+ const match = matchPath(routeInfo.pathname, matchProps);
42
+ if (reactElement.type === IonRoute) {
43
+ viewItem.ionRoute = true;
44
+ viewItem.disableIonPageManagement = reactElement.props.disableIonPageManagement;
45
+ }
46
+ viewItem.routeData = {
47
+ match,
48
+ childProps: reactElement.props,
49
+ };
50
+ return viewItem;
51
+ }
52
+ getChildrenToRender(outletId, ionRouterOutlet, routeInfo) {
53
+ const viewItems = this.getViewItemsForOutlet(outletId);
54
+ // Sync latest routes with viewItems
55
+ React.Children.forEach(ionRouterOutlet.props.children, (child) => {
56
+ const viewItem = viewItems.find((v) => {
57
+ return matchComponent$1(child, v.routeData.childProps.path || v.routeData.childProps.from);
58
+ });
59
+ if (viewItem) {
60
+ viewItem.reactElement = child;
61
+ }
62
+ });
63
+ const children = viewItems.map((viewItem) => {
64
+ let clonedChild;
65
+ if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
66
+ clonedChild = (React.createElement(ViewLifeCycleManager, { key: `view-${viewItem.id}`, mount: viewItem.mount, removeView: () => this.remove(viewItem) }, React.cloneElement(viewItem.reactElement, {
67
+ computedMatch: viewItem.routeData.match,
68
+ })));
69
+ }
70
+ else {
71
+ const match = matchComponent$1(viewItem.reactElement, routeInfo.pathname);
72
+ clonedChild = (React.createElement(ViewLifeCycleManager, { key: `view-${viewItem.id}`, mount: viewItem.mount, removeView: () => this.remove(viewItem) }, React.cloneElement(viewItem.reactElement, {
73
+ computedMatch: viewItem.routeData.match,
74
+ })));
75
+ if (!match && viewItem.routeData.match) {
76
+ viewItem.routeData.match = undefined;
77
+ viewItem.mount = false;
78
+ }
79
+ }
80
+ return clonedChild;
81
+ });
82
+ return children;
83
+ }
84
+ findViewItemByRouteInfo(routeInfo, outletId, updateMatch) {
85
+ const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
86
+ const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
87
+ if (shouldUpdateMatch && viewItem && match) {
88
+ viewItem.routeData.match = match;
89
+ }
90
+ return viewItem;
91
+ }
92
+ findLeavingViewItemByRouteInfo(routeInfo, outletId, mustBeIonRoute = true) {
93
+ const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname, outletId, false, mustBeIonRoute);
94
+ return viewItem;
95
+ }
96
+ findViewItemByPathname(pathname, outletId) {
97
+ const { viewItem } = this.findViewItemByPath(pathname, outletId);
98
+ return viewItem;
99
+ }
100
+ findViewItemByPath(pathname, outletId, forceExact, mustBeIonRoute) {
101
+ let viewItem;
102
+ let match;
103
+ let viewStack;
104
+ if (outletId) {
105
+ viewStack = this.getViewItemsForOutlet(outletId);
106
+ viewStack.some(matchView);
107
+ if (!viewItem) {
108
+ viewStack.some(matchDefaultRoute);
109
+ }
110
+ }
111
+ else {
112
+ const viewItems = this.getAllViewItems();
113
+ viewItems.some(matchView);
114
+ if (!viewItem) {
115
+ viewItems.some(matchDefaultRoute);
116
+ }
117
+ }
118
+ return { viewItem, match };
119
+ function matchView(v) {
120
+ if (mustBeIonRoute && !v.ionRoute) {
121
+ return false;
122
+ }
123
+ const matchProps = {
124
+ exact: forceExact ? true : v.routeData.childProps.exact,
125
+ path: v.routeData.childProps.path || v.routeData.childProps.from,
126
+ component: v.routeData.childProps.component,
127
+ };
128
+ const myMatch = matchPath(pathname, matchProps);
129
+ if (myMatch) {
130
+ viewItem = v;
131
+ match = myMatch;
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ function matchDefaultRoute(v) {
137
+ // try to find a route that doesn't have a path or from prop, that will be our default route
138
+ if (!v.routeData.childProps.path && !v.routeData.childProps.from) {
139
+ match = {
140
+ path: pathname,
141
+ url: pathname,
142
+ isExact: true,
143
+ params: {},
144
+ };
145
+ viewItem = v;
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ }
151
+ }
152
+ function matchComponent$1(node, pathname, forceExact) {
153
+ const matchProps = {
154
+ exact: forceExact ? true : node.props.exact,
155
+ path: node.props.path || node.props.from,
156
+ component: node.props.component,
157
+ };
158
+ const match = matchPath(pathname, matchProps);
159
+ return match;
160
+ }
161
+
162
+ function clonePageElement(leavingViewHtml) {
163
+ let html;
164
+ if (typeof leavingViewHtml === 'string') {
165
+ html = leavingViewHtml;
166
+ }
167
+ else {
168
+ html = leavingViewHtml.outerHTML;
169
+ }
170
+ if (document) {
171
+ const newEl = document.createElement('div');
172
+ newEl.innerHTML = html;
173
+ newEl.style.zIndex = '';
174
+ // Remove an existing back button so the new element doesn't get two of them
175
+ const ionBackButton = newEl.getElementsByTagName('ion-back-button');
176
+ if (ionBackButton[0]) {
177
+ ionBackButton[0].remove();
178
+ }
179
+ return newEl.firstChild;
180
+ }
181
+ return undefined;
182
+ }
183
+
184
+ const isViewVisible = (el) => !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
185
+ class StackManager extends React.PureComponent {
186
+ constructor(props) {
187
+ super(props);
188
+ this.stackContextValue = {
189
+ registerIonPage: this.registerIonPage.bind(this),
190
+ isInOutlet: () => true,
191
+ };
192
+ this.pendingPageTransition = false;
193
+ this.registerIonPage = this.registerIonPage.bind(this);
194
+ this.transitionPage = this.transitionPage.bind(this);
195
+ this.handlePageTransition = this.handlePageTransition.bind(this);
196
+ this.id = generateId('routerOutlet');
197
+ this.prevProps = undefined;
198
+ this.skipTransition = false;
199
+ }
200
+ componentDidMount() {
201
+ if (this.clearOutletTimeout) {
202
+ /**
203
+ * The clearOutlet integration with React Router is a bit hacky.
204
+ * It uses a timeout to clear the outlet after a transition.
205
+ * In React v18, components are mounted and unmounted in development mode
206
+ * to check for side effects.
207
+ *
208
+ * This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
209
+ * which should only happen in development mode and as a result of a hot reload.
210
+ */
211
+ clearTimeout(this.clearOutletTimeout);
212
+ }
213
+ if (this.routerOutletElement) {
214
+ this.setupRouterOutlet(this.routerOutletElement);
215
+ this.handlePageTransition(this.props.routeInfo);
216
+ }
217
+ }
218
+ componentDidUpdate(prevProps) {
219
+ const { pathname } = this.props.routeInfo;
220
+ const { pathname: prevPathname } = prevProps.routeInfo;
221
+ if (pathname !== prevPathname) {
222
+ this.prevProps = prevProps;
223
+ this.handlePageTransition(this.props.routeInfo);
224
+ }
225
+ else if (this.pendingPageTransition) {
226
+ this.handlePageTransition(this.props.routeInfo);
227
+ this.pendingPageTransition = false;
228
+ }
229
+ }
230
+ componentWillUnmount() {
231
+ this.clearOutletTimeout = this.context.clearOutlet(this.id);
232
+ }
233
+ async handlePageTransition(routeInfo) {
234
+ var _a, _b;
235
+ if (!this.routerOutletElement || !this.routerOutletElement.commit) {
236
+ /**
237
+ * The route outlet has not mounted yet. We need to wait for it to render
238
+ * before we can transition the page.
239
+ *
240
+ * Set a flag to indicate that we should transition the page after
241
+ * the component has updated.
242
+ */
243
+ this.pendingPageTransition = true;
244
+ }
245
+ else {
246
+ let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
247
+ let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
248
+ if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
249
+ leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
250
+ }
251
+ // Check if leavingViewItem should be unmounted
252
+ if (leavingViewItem) {
253
+ if (routeInfo.routeAction === 'replace') {
254
+ leavingViewItem.mount = false;
255
+ }
256
+ else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
257
+ if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
258
+ leavingViewItem.mount = false;
259
+ }
260
+ }
261
+ else if ((_a = routeInfo.routeOptions) === null || _a === void 0 ? void 0 : _a.unmount) {
262
+ leavingViewItem.mount = false;
263
+ }
264
+ }
265
+ const enteringRoute = matchRoute((_b = this.ionRouterOutlet) === null || _b === void 0 ? void 0 : _b.props.children, routeInfo);
266
+ if (enteringViewItem) {
267
+ enteringViewItem.reactElement = enteringRoute;
268
+ }
269
+ else if (enteringRoute) {
270
+ enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
271
+ this.context.addViewItem(enteringViewItem);
272
+ }
273
+ if (enteringViewItem && enteringViewItem.ionPageElement) {
274
+ /**
275
+ * If the entering view item is the same as the leaving view item,
276
+ * then we don't need to transition.
277
+ */
278
+ if (enteringViewItem === leavingViewItem) {
279
+ /**
280
+ * If the entering view item is the same as the leaving view item,
281
+ * we are either transitioning using parameterized routes to the same view
282
+ * or a parent router outlet is re-rendering as a result of React props changing.
283
+ *
284
+ * If the route data does not match the current path, the parent router outlet
285
+ * is attempting to transition and we cancel the operation.
286
+ */
287
+ if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
288
+ return;
289
+ }
290
+ }
291
+ /**
292
+ * If there isn't a leaving view item, but the route info indicates
293
+ * that the user has routed from a previous path, then we need
294
+ * to find the leaving view item to transition between.
295
+ */
296
+ if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
297
+ leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
298
+ }
299
+ /**
300
+ * If the entering view is already visible and the leaving view is not, the transition does not need to occur.
301
+ */
302
+ if (isViewVisible(enteringViewItem.ionPageElement) &&
303
+ leavingViewItem !== undefined &&
304
+ !isViewVisible(leavingViewItem.ionPageElement)) {
305
+ return;
306
+ }
307
+ /**
308
+ * The view should only be transitioned in the following cases:
309
+ * 1. Performing a replace or pop action, such as a swipe to go back gesture
310
+ * to animation the leaving view off the screen.
311
+ *
312
+ * 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
313
+ * or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
314
+ *
315
+ * 3. The entering view is an ion-router-outlet containing a page
316
+ * matching the current route and that hasn't already transitioned in.
317
+ *
318
+ * This should only happen when navigating directly to a nested router outlet
319
+ * route or on an initial page load (i.e. refreshing). In cases when loading
320
+ * /tabs/tab-1, we need to transition the /tabs page element into the view.
321
+ */
322
+ this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
323
+ }
324
+ else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
325
+ // If we have a leavingView but no entering view/route, we are probably leaving to
326
+ // another outlet, so hide this leavingView. We do it in a timeout to give time for a
327
+ // transition to finish.
328
+ // setTimeout(() => {
329
+ if (leavingViewItem.ionPageElement) {
330
+ leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
331
+ leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
332
+ }
333
+ // }, 250);
334
+ }
335
+ this.forceUpdate();
336
+ }
337
+ }
338
+ registerIonPage(page, routeInfo) {
339
+ const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
340
+ if (foundView) {
341
+ const oldPageElement = foundView.ionPageElement;
342
+ foundView.ionPageElement = page;
343
+ foundView.ionRoute = true;
344
+ /**
345
+ * React 18 will unmount and remount IonPage
346
+ * elements in development mode when using createRoot.
347
+ * This can cause duplicate page transitions to occur.
348
+ */
349
+ if (oldPageElement === page) {
350
+ return;
351
+ }
352
+ }
353
+ this.handlePageTransition(routeInfo);
354
+ }
355
+ async setupRouterOutlet(routerOutlet) {
356
+ const canStart = () => {
357
+ const config = getConfig();
358
+ const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
359
+ if (!swipeEnabled) {
360
+ return false;
361
+ }
362
+ const { routeInfo } = this.props;
363
+ const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
364
+ ? this.prevProps.routeInfo
365
+ : { pathname: routeInfo.pushedByRoute || '' };
366
+ const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
367
+ return (!!enteringViewItem &&
368
+ /**
369
+ * The root url '/' is treated as
370
+ * the first view item (but is never mounted),
371
+ * so we do not want to swipe back to the
372
+ * root url.
373
+ */
374
+ enteringViewItem.mount &&
375
+ /**
376
+ * When on the first page (whatever view
377
+ * you land on after the root url) it
378
+ * is possible for findViewItemByRouteInfo to
379
+ * return the exact same view you are currently on.
380
+ * Make sure that we are not swiping back to the same
381
+ * instances of a view.
382
+ */
383
+ enteringViewItem.routeData.match.path !== routeInfo.pathname);
384
+ };
385
+ const onStart = async () => {
386
+ const { routeInfo } = this.props;
387
+ const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
388
+ ? this.prevProps.routeInfo
389
+ : { pathname: routeInfo.pushedByRoute || '' };
390
+ const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
391
+ const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
392
+ /**
393
+ * When the gesture starts, kick off
394
+ * a transition that is controlled
395
+ * via a swipe gesture.
396
+ */
397
+ if (enteringViewItem && leavingViewItem) {
398
+ await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
399
+ }
400
+ return Promise.resolve();
401
+ };
402
+ const onEnd = (shouldContinue) => {
403
+ if (shouldContinue) {
404
+ this.skipTransition = true;
405
+ this.context.goBack();
406
+ }
407
+ else {
408
+ /**
409
+ * In the event that the swipe
410
+ * gesture was aborted, we should
411
+ * re-hide the page that was going to enter.
412
+ */
413
+ const { routeInfo } = this.props;
414
+ const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
415
+ ? this.prevProps.routeInfo
416
+ : { pathname: routeInfo.pushedByRoute || '' };
417
+ const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
418
+ const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
419
+ /**
420
+ * Ionic React has a design defect where it
421
+ * a) Unmounts the leaving view item when using parameterized routes
422
+ * b) Considers the current view to be the entering view when using
423
+ * parameterized routes
424
+ *
425
+ * As a result, we should not hide the view item here
426
+ * as it will cause the current view to be hidden.
427
+ */
428
+ if (enteringViewItem !== leavingViewItem && (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) !== undefined) {
429
+ const { ionPageElement } = enteringViewItem;
430
+ ionPageElement.setAttribute('aria-hidden', 'true');
431
+ ionPageElement.classList.add('ion-page-hidden');
432
+ }
433
+ }
434
+ };
435
+ routerOutlet.swipeHandler = {
436
+ canStart,
437
+ onStart,
438
+ onEnd,
439
+ };
440
+ }
441
+ async transitionPage(routeInfo, enteringViewItem, leavingViewItem, direction, progressAnimation = false) {
442
+ const runCommit = async (enteringEl, leavingEl) => {
443
+ const skipTransition = this.skipTransition;
444
+ /**
445
+ * If the transition was handled
446
+ * via the swipe to go back gesture,
447
+ * then we do not want to perform
448
+ * another transition.
449
+ *
450
+ * We skip adding ion-page or ion-page-invisible
451
+ * because the entering view already exists in the DOM.
452
+ * If we added the classes, there would be a flicker where
453
+ * the view would be briefly hidden.
454
+ */
455
+ if (skipTransition) {
456
+ /**
457
+ * We need to reset skipTransition before
458
+ * we call routerOutlet.commit otherwise
459
+ * the transition triggered by the swipe
460
+ * to go back gesture would reset it. In
461
+ * that case you would see a duplicate
462
+ * transition triggered by handlePageTransition
463
+ * in componentDidUpdate.
464
+ */
465
+ this.skipTransition = false;
466
+ }
467
+ else {
468
+ enteringEl.classList.add('ion-page');
469
+ enteringEl.classList.add('ion-page-invisible');
470
+ }
471
+ await routerOutlet.commit(enteringEl, leavingEl, {
472
+ duration: skipTransition || directionToUse === undefined ? 0 : undefined,
473
+ direction: directionToUse,
474
+ showGoBack: !!routeInfo.pushedByRoute,
475
+ progressAnimation,
476
+ animationBuilder: routeInfo.routeAnimation,
477
+ });
478
+ };
479
+ const routerOutlet = this.routerOutletElement;
480
+ const routeInfoFallbackDirection = routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
481
+ const directionToUse = direction !== null && direction !== void 0 ? direction : routeInfoFallbackDirection;
482
+ if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
483
+ if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
484
+ // If a page is transitioning to another version of itself
485
+ // we clone it so we can have an animation to show
486
+ const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
487
+ if (match) {
488
+ const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
489
+ if (newLeavingElement) {
490
+ this.routerOutletElement.appendChild(newLeavingElement);
491
+ await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
492
+ this.routerOutletElement.removeChild(newLeavingElement);
493
+ }
494
+ }
495
+ else {
496
+ await runCommit(enteringViewItem.ionPageElement, undefined);
497
+ }
498
+ }
499
+ else {
500
+ await runCommit(enteringViewItem.ionPageElement, leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.ionPageElement);
501
+ if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
502
+ leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
503
+ leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
504
+ }
505
+ }
506
+ }
507
+ }
508
+ render() {
509
+ const { children } = this.props;
510
+ const ionRouterOutlet = React.Children.only(children);
511
+ this.ionRouterOutlet = ionRouterOutlet;
512
+ const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
513
+ this.forceUpdate();
514
+ });
515
+ return (React.createElement(StackContext.Provider, { value: this.stackContextValue }, React.cloneElement(ionRouterOutlet, {
516
+ ref: (node) => {
517
+ if (ionRouterOutlet.props.setRef) {
518
+ ionRouterOutlet.props.setRef(node);
519
+ }
520
+ if (ionRouterOutlet.props.forwardedRef) {
521
+ ionRouterOutlet.props.forwardedRef.current = node;
522
+ }
523
+ this.routerOutletElement = node;
524
+ const { ref } = ionRouterOutlet;
525
+ if (typeof ref === 'function') {
526
+ ref(node);
527
+ }
528
+ },
529
+ }, components)));
530
+ }
531
+ static get contextType() {
532
+ return RouteManagerContext;
533
+ }
534
+ }
535
+ function matchRoute(node, routeInfo) {
536
+ let matchedNode;
537
+ React.Children.forEach(node, (child) => {
538
+ const matchProps = {
539
+ exact: child.props.exact,
540
+ path: child.props.path || child.props.from,
541
+ component: child.props.component,
542
+ };
543
+ const match = matchPath$1(routeInfo.pathname, matchProps);
544
+ if (match) {
545
+ matchedNode = child;
546
+ }
547
+ });
548
+ if (matchedNode) {
549
+ return matchedNode;
550
+ }
551
+ // If we haven't found a node
552
+ // try to find one that doesn't have a path or from prop, that will be our not found route
553
+ React.Children.forEach(node, (child) => {
554
+ if (!(child.props.path || child.props.from)) {
555
+ matchedNode = child;
556
+ }
557
+ });
558
+ return matchedNode;
559
+ }
560
+ function matchComponent(node, pathname, forceExact) {
561
+ const matchProps = {
562
+ exact: forceExact ? true : node.props.exact,
563
+ path: node.props.path || node.props.from,
564
+ component: node.props.component,
565
+ };
566
+ const match = matchPath$1(pathname, matchProps);
567
+ return match;
568
+ }
569
+
570
+ class IonRouterInner extends React.PureComponent {
571
+ constructor(props) {
572
+ super(props);
573
+ this.exitViewFromOtherOutletHandlers = [];
574
+ this.locationHistory = new LocationHistory();
575
+ this.viewStack = new ReactRouterViewStack();
576
+ this.routeMangerContextState = {
577
+ canGoBack: () => this.locationHistory.canGoBack(),
578
+ clearOutlet: this.viewStack.clear,
579
+ findViewItemByPathname: this.viewStack.findViewItemByPathname,
580
+ getChildrenToRender: this.viewStack.getChildrenToRender,
581
+ goBack: () => this.handleNavigateBack(),
582
+ createViewItem: this.viewStack.createViewItem,
583
+ findViewItemByRouteInfo: this.viewStack.findViewItemByRouteInfo,
584
+ findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
585
+ addViewItem: this.viewStack.add,
586
+ unMountViewItem: this.viewStack.remove,
587
+ };
588
+ const routeInfo = {
589
+ id: generateId('routeInfo'),
590
+ pathname: this.props.location.pathname,
591
+ search: this.props.location.search,
592
+ };
593
+ this.locationHistory.add(routeInfo);
594
+ this.handleChangeTab = this.handleChangeTab.bind(this);
595
+ this.handleResetTab = this.handleResetTab.bind(this);
596
+ this.handleNativeBack = this.handleNativeBack.bind(this);
597
+ this.handleNavigate = this.handleNavigate.bind(this);
598
+ this.handleNavigateBack = this.handleNavigateBack.bind(this);
599
+ this.props.registerHistoryListener(this.handleHistoryChange.bind(this));
600
+ this.handleSetCurrentTab = this.handleSetCurrentTab.bind(this);
601
+ this.state = {
602
+ routeInfo,
603
+ };
604
+ }
605
+ handleChangeTab(tab, path, routeOptions) {
606
+ if (!path) {
607
+ return;
608
+ }
609
+ const routeInfo = this.locationHistory.getCurrentRouteInfoForTab(tab);
610
+ const [pathname, search] = path.split('?');
611
+ if (routeInfo) {
612
+ this.incomingRouteParams = Object.assign(Object.assign({}, routeInfo), { routeAction: 'push', routeDirection: 'none' });
613
+ if (routeInfo.pathname === pathname) {
614
+ this.incomingRouteParams.routeOptions = routeOptions;
615
+ this.props.history.push(routeInfo.pathname + (routeInfo.search || ''));
616
+ }
617
+ else {
618
+ this.incomingRouteParams.pathname = pathname;
619
+ this.incomingRouteParams.search = search ? '?' + search : undefined;
620
+ this.incomingRouteParams.routeOptions = routeOptions;
621
+ this.props.history.push(pathname + (search ? '?' + search : ''));
622
+ }
623
+ }
624
+ else {
625
+ this.handleNavigate(pathname, 'push', 'none', undefined, routeOptions, tab);
626
+ }
627
+ }
628
+ handleHistoryChange(location, action) {
629
+ var _a, _b, _c;
630
+ let leavingLocationInfo;
631
+ if (this.incomingRouteParams) {
632
+ if (this.incomingRouteParams.routeAction === 'replace') {
633
+ leavingLocationInfo = this.locationHistory.previous();
634
+ }
635
+ else {
636
+ leavingLocationInfo = this.locationHistory.current();
637
+ }
638
+ }
639
+ else {
640
+ leavingLocationInfo = this.locationHistory.current();
641
+ }
642
+ const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
643
+ if (leavingUrl !== location.pathname) {
644
+ if (!this.incomingRouteParams) {
645
+ if (action === 'REPLACE') {
646
+ this.incomingRouteParams = {
647
+ routeAction: 'replace',
648
+ routeDirection: 'none',
649
+ tab: this.currentTab,
650
+ };
651
+ }
652
+ if (action === 'POP') {
653
+ const currentRoute = this.locationHistory.current();
654
+ if (currentRoute && currentRoute.pushedByRoute) {
655
+ const prevInfo = this.locationHistory.findLastLocation(currentRoute);
656
+ this.incomingRouteParams = Object.assign(Object.assign({}, prevInfo), { routeAction: 'pop', routeDirection: 'back' });
657
+ }
658
+ else {
659
+ this.incomingRouteParams = {
660
+ routeAction: 'pop',
661
+ routeDirection: 'none',
662
+ tab: this.currentTab,
663
+ };
664
+ }
665
+ }
666
+ if (!this.incomingRouteParams) {
667
+ this.incomingRouteParams = {
668
+ routeAction: 'push',
669
+ routeDirection: ((_a = location.state) === null || _a === void 0 ? void 0 : _a.direction) || 'forward',
670
+ routeOptions: (_b = location.state) === null || _b === void 0 ? void 0 : _b.routerOptions,
671
+ tab: this.currentTab,
672
+ };
673
+ }
674
+ }
675
+ let routeInfo;
676
+ if ((_c = this.incomingRouteParams) === null || _c === void 0 ? void 0 : _c.id) {
677
+ routeInfo = Object.assign(Object.assign({}, this.incomingRouteParams), { lastPathname: leavingLocationInfo.pathname });
678
+ this.locationHistory.add(routeInfo);
679
+ }
680
+ else {
681
+ const isPushed = this.incomingRouteParams.routeAction === 'push' && this.incomingRouteParams.routeDirection === 'forward';
682
+ routeInfo = Object.assign(Object.assign({ id: generateId('routeInfo') }, this.incomingRouteParams), { lastPathname: leavingLocationInfo.pathname, pathname: location.pathname, search: location.search, params: this.props.match.params, prevRouteLastPathname: leavingLocationInfo.lastPathname });
683
+ if (isPushed) {
684
+ routeInfo.tab = leavingLocationInfo.tab;
685
+ routeInfo.pushedByRoute = leavingLocationInfo.pathname;
686
+ }
687
+ else if (routeInfo.routeAction === 'pop') {
688
+ const r = this.locationHistory.findLastLocation(routeInfo);
689
+ routeInfo.pushedByRoute = r === null || r === void 0 ? void 0 : r.pushedByRoute;
690
+ }
691
+ else if (routeInfo.routeAction === 'push' && routeInfo.tab !== leavingLocationInfo.tab) {
692
+ // If we are switching tabs grab the last route info for the tab and use its pushedByRoute
693
+ const lastRoute = this.locationHistory.getCurrentRouteInfoForTab(routeInfo.tab);
694
+ routeInfo.pushedByRoute = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute;
695
+ }
696
+ else if (routeInfo.routeAction === 'replace') {
697
+ // Make sure to set the lastPathname, etc.. to the current route so the page transitions out
698
+ const currentRouteInfo = this.locationHistory.current();
699
+ /**
700
+ * If going from /home to /child, then replacing from
701
+ * /child to /home, we don't want the route info to
702
+ * say that /home was pushed by /home which is not correct.
703
+ */
704
+ const currentPushedBy = currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.pushedByRoute;
705
+ const pushedByRoute = currentPushedBy !== undefined && currentPushedBy !== routeInfo.pathname
706
+ ? currentPushedBy
707
+ : routeInfo.pushedByRoute;
708
+ routeInfo.lastPathname = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.pathname) || routeInfo.lastPathname;
709
+ routeInfo.prevRouteLastPathname = currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.lastPathname;
710
+ routeInfo.pushedByRoute = pushedByRoute;
711
+ routeInfo.routeDirection = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routeDirection) || routeInfo.routeDirection;
712
+ routeInfo.routeAnimation = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routeAnimation) || routeInfo.routeAnimation;
713
+ }
714
+ this.locationHistory.add(routeInfo);
715
+ }
716
+ this.setState({
717
+ routeInfo,
718
+ });
719
+ }
720
+ this.incomingRouteParams = undefined;
721
+ }
722
+ /**
723
+ * history@4.x uses goBack(), history@5.x uses back()
724
+ * TODO: If support for React Router <=5 is dropped
725
+ * this logic is no longer needed. We can just
726
+ * assume back() is available.
727
+ */
728
+ handleNativeBack() {
729
+ const history = this.props.history;
730
+ const goBack = history.goBack || history.back;
731
+ goBack();
732
+ }
733
+ handleNavigate(path, routeAction, routeDirection, routeAnimation, routeOptions, tab) {
734
+ this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, {
735
+ routeAction,
736
+ routeDirection,
737
+ routeOptions,
738
+ routeAnimation,
739
+ tab,
740
+ });
741
+ if (routeAction === 'push') {
742
+ this.props.history.push(path);
743
+ }
744
+ else {
745
+ this.props.history.replace(path);
746
+ }
747
+ }
748
+ handleNavigateBack(defaultHref = '/', routeAnimation) {
749
+ const config = getConfig();
750
+ defaultHref = defaultHref ? defaultHref : config && config.get('backButtonDefaultHref');
751
+ const routeInfo = this.locationHistory.current();
752
+ if (routeInfo && routeInfo.pushedByRoute) {
753
+ const prevInfo = this.locationHistory.findLastLocation(routeInfo);
754
+ if (prevInfo) {
755
+ this.incomingRouteParams = Object.assign(Object.assign({}, prevInfo), { routeAction: 'pop', routeDirection: 'back', routeAnimation: routeAnimation || routeInfo.routeAnimation });
756
+ if (routeInfo.lastPathname === routeInfo.pushedByRoute ||
757
+ /**
758
+ * We need to exclude tab switches/tab
759
+ * context changes here because tabbed
760
+ * navigation is not linear, but router.back()
761
+ * will go back in a linear fashion.
762
+ */
763
+ (prevInfo.pathname === routeInfo.pushedByRoute && routeInfo.tab === '' && prevInfo.tab === '')) {
764
+ /**
765
+ * history@4.x uses goBack(), history@5.x uses back()
766
+ * TODO: If support for React Router <=5 is dropped
767
+ * this logic is no longer needed. We can just
768
+ * assume back() is available.
769
+ */
770
+ const history = this.props.history;
771
+ const goBack = history.goBack || history.back;
772
+ goBack();
773
+ }
774
+ else {
775
+ this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back', routeAnimation);
776
+ }
777
+ }
778
+ else {
779
+ this.handleNavigate(defaultHref, 'pop', 'back', routeAnimation);
780
+ }
781
+ }
782
+ else {
783
+ this.handleNavigate(defaultHref, 'pop', 'back', routeAnimation);
784
+ }
785
+ }
786
+ handleResetTab(tab, originalHref, originalRouteOptions) {
787
+ const routeInfo = this.locationHistory.getFirstRouteInfoForTab(tab);
788
+ if (routeInfo) {
789
+ const newRouteInfo = Object.assign({}, routeInfo);
790
+ newRouteInfo.pathname = originalHref;
791
+ newRouteInfo.routeOptions = originalRouteOptions;
792
+ this.incomingRouteParams = Object.assign(Object.assign({}, newRouteInfo), { routeAction: 'pop', routeDirection: 'back' });
793
+ this.props.history.push(newRouteInfo.pathname + (newRouteInfo.search || ''));
794
+ }
795
+ }
796
+ handleSetCurrentTab(tab) {
797
+ this.currentTab = tab;
798
+ const ri = Object.assign({}, this.locationHistory.current());
799
+ if (ri.tab !== tab) {
800
+ ri.tab = tab;
801
+ this.locationHistory.update(ri);
802
+ }
803
+ }
804
+ render() {
805
+ return (React.createElement(RouteManagerContext.Provider, { value: this.routeMangerContextState },
806
+ React.createElement(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)));
807
+ }
808
+ }
809
+ const IonRouter = withRouter(IonRouterInner);
810
+ IonRouter.displayName = 'IonRouter';
811
+
812
+ class IonReactRouter extends React.Component {
813
+ constructor(props) {
814
+ super(props);
815
+ const { history } = props, rest = __rest(props, ["history"]);
816
+ this.history = history || createBrowserHistory(rest);
817
+ this.history.listen(this.handleHistoryChange.bind(this));
818
+ this.registerHistoryListener = this.registerHistoryListener.bind(this);
819
+ }
820
+ /**
821
+ * history@4.x passes separate location and action
822
+ * params. history@5.x passes location and action
823
+ * together as a single object.
824
+ * TODO: If support for React Router <=5 is dropped
825
+ * this logic is no longer needed. We can just assume
826
+ * a single object with both location and action.
827
+ */
828
+ handleHistoryChange(location, action) {
829
+ const locationValue = location.location || location;
830
+ const actionValue = location.action || action;
831
+ if (this.historyListenHandler) {
832
+ this.historyListenHandler(locationValue, actionValue);
833
+ }
834
+ }
835
+ registerHistoryListener(cb) {
836
+ this.historyListenHandler = cb;
837
+ }
838
+ render() {
839
+ const _a = this.props, { children } = _a, props = __rest(_a, ["children"]);
840
+ return (React.createElement(Router, Object.assign({ history: this.history }, props),
841
+ React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children)));
842
+ }
843
+ }
844
+
845
+ class IonReactMemoryRouter extends React.Component {
846
+ constructor(props) {
847
+ super(props);
848
+ this.history = props.history;
849
+ this.history.listen(this.handleHistoryChange.bind(this));
850
+ this.registerHistoryListener = this.registerHistoryListener.bind(this);
851
+ }
852
+ /**
853
+ * history@4.x passes separate location and action
854
+ * params. history@5.x passes location and action
855
+ * together as a single object.
856
+ * TODO: If support for React Router <=5 is dropped
857
+ * this logic is no longer needed. We can just assume
858
+ * a single object with both location and action.
859
+ */
860
+ handleHistoryChange(location, action) {
861
+ const locationValue = location.location || location;
862
+ const actionValue = location.action || action;
863
+ if (this.historyListenHandler) {
864
+ this.historyListenHandler(locationValue, actionValue);
865
+ }
866
+ }
867
+ registerHistoryListener(cb) {
868
+ this.historyListenHandler = cb;
869
+ }
870
+ render() {
871
+ const _a = this.props, { children } = _a, props = __rest(_a, ["children"]);
872
+ return (React.createElement(Router$1, Object.assign({}, props),
873
+ React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children)));
874
+ }
875
+ }
876
+
877
+ class IonReactHashRouter extends React.Component {
878
+ constructor(props) {
879
+ super(props);
880
+ const { history } = props, rest = __rest(props, ["history"]);
881
+ this.history = history || createHashHistory(rest);
882
+ this.history.listen(this.handleHistoryChange.bind(this));
883
+ this.registerHistoryListener = this.registerHistoryListener.bind(this);
884
+ }
885
+ /**
886
+ * history@4.x passes separate location and action
887
+ * params. history@5.x passes location and action
888
+ * together as a single object.
889
+ * TODO: If support for React Router <=5 is dropped
890
+ * this logic is no longer needed. We can just assume
891
+ * a single object with both location and action.
892
+ */
893
+ handleHistoryChange(location, action) {
894
+ const locationValue = location.location || location;
895
+ const actionValue = location.action || action;
896
+ if (this.historyListenHandler) {
897
+ this.historyListenHandler(locationValue, actionValue);
898
+ }
899
+ }
900
+ registerHistoryListener(cb) {
901
+ this.historyListenHandler = cb;
902
+ }
903
+ render() {
904
+ const _a = this.props, { children } = _a, props = __rest(_a, ["children"]);
905
+ return (React.createElement(Router, Object.assign({ history: this.history }, props),
906
+ React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children)));
907
+ }
908
+ }
909
+
910
+ export { IonReactHashRouter, IonReactMemoryRouter, IonReactRouter };
5
911
  //# sourceMappingURL=index.js.map