Compare commits

..

7 Commits

Author SHA1 Message Date
ionitron
f9211e5434 v7.3.2 2023-08-30 12:47:53 +00:00
Shawn Taylor
d4875df644 chore(router-outlet): rename files (#28074)
Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->
Router outlet files can be hard to find when searching for them.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Router outlet files are named what you'd expect.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-08-29 17:49:24 +00:00
Liam DeBeasi
01fc9b4511 fix(datetime): gracefully handle invalid min/max (#28054)
Issue number: resolves #28041

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

`parseDate` returns `undefined` when given an invalid value. However,
our min/max processing functions did not account for this. As a result,
we would attempt to destructure an undefined value which resulted in an
error.

Note regarding linked issue: The developer is calling
`setMin(undefined)`. However, this is triggering a React quirk with
Custom Elements where `undefined` is being set to `null` inside of
React. The type signature on min/max is `string | undefined`, so `null`
is being treated as an invalid date value.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Min/Max processing functions now return `undefined` if the input was
invalid.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Dev build: `7.3.2-dev.11692887667.1614d10a`
2023-08-28 13:39:19 +00:00
Shawn Taylor
9eef62e4f2 chore(CODEOWNERS): add shawn to CODEOWNERS (#28064) 2023-08-25 17:19:12 +00:00
Brandy Carney
04b9b31622 chore(CODEOWNERS): add brandy to CODEOWNERS (#28063) 2023-08-25 16:17:31 +00:00
Maria Hutt
a7ed0a347a chore(CODEOWNERS): add Maria to Angular and Vue (#28061)
Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Maria isn't part of the codeowners.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Maria has been added to the Angular and Vue folders.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

N/A
2023-08-25 16:14:06 +00:00
Amanda Johnston
d1ce8e2f8d chore(codeowners): add Amanda to checkbox and radio (#28062)
Issue number: #

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [ ] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-08-25 16:00:40 +00:00
44 changed files with 483 additions and 801 deletions

38
.github/CODEOWNERS vendored
View File

@@ -15,9 +15,9 @@
## Angular
/packages/angular/ @sean-perkins
/packages/angular-server @sean-perkins
/packages/angular/test
/packages/angular/ @sean-perkins @thetaPC
/packages/angular-server @sean-perkins @thetaPC
/packages/angular/test @thetaPC
## React
@@ -28,19 +28,23 @@
## Vue
/packages/vue/ @liamdebeasi
/packages/vue-router/ @liamdebeasi
/packages/vue/test/
/packages/vue-router/__tests__
/packages/vue/ @liamdebeasi @thetaPC
/packages/vue-router/ @liamdebeasi @thetaPC
/packages/vue/test/ @thetaPC
/packages/vue-router/__tests__ @thetaPC
# Components
/core/src/components/accordion/ @liamdebeasi
/core/src/components/accordion-group/ @liamdebeasi
/core/src/components/checkbox/ @amandaejohnston
/core/src/components/datetime/ @liamdebeasi @amandaejohnston @sean-perkins
/core/src/components/datetime-button/ @liamdebeasi
/core/src/components/item/ @brandyscarney
/core/src/components/menu/ @amandaejohnston
/core/src/components/menu-toggle/ @amandaejohnston
@@ -50,9 +54,19 @@
/core/src/components/picker-internal/ @liamdebeasi
/core/src/components/picker-column-internal/ @liamdebeasi
/core/src/components/radio/ @amandaejohnston
/core/src/components/radio-group/ @amandaejohnston
/core/src/components/refresher/ @liamdebeasi
/core/src/components/refresher-content/ @liamdebeasi
/core/src/components/searchbar/ @brandyscarney
/core/src/components/segment/ @brandyscarney
/core/src/components/segment-button/ @brandyscarney
/core/src/components/skeleton-text/ @brandyscarney
# Utilities
/core/src/utils/animation/ @liamdebeasi
@@ -64,3 +78,13 @@
/core/src/utils/sanitization/ @liamdebeasi
/core/src/utils/tap-click/ @liamdebeasi
/core/src/utils/transition/ @liamdebeasi
/core/src/css/ @brandyscarney
/core/src/themes/ @brandyscarney
# Tests
**/datetime.e2e.ts @mapsandapps
**/datetime-button.e2e.ts @mapsandapps
**/icon.e2e.ts @mapsandapps
**/loading.e2e.ts @mapsandapps

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
### Bug Fixes
* **datetime:** gracefully handle invalid min/max ([#28054](https://github.com/ionic-team/ionic-framework/issues/28054)) ([01fc9b4](https://github.com/ionic-team/ionic-framework/commit/01fc9b45116f7ad6ddc56c7fb1535dec798c2b3a)), closes [#28041](https://github.com/ionic-team/ionic-framework/issues/28041)
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
### Bug Fixes
* **datetime:** gracefully handle invalid min/max ([#28054](https://github.com/ionic-team/ionic-framework/issues/28054)) ([01fc9b4](https://github.com/ionic-team/ionic-framework/commit/01fc9b45116f7ad6ddc56c7fb1535dec798c2b3a)), closes [#28041](https://github.com/ionic-team/ionic-framework/issues/28041)
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.3.1",
"version": "7.3.2",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@@ -154,6 +154,18 @@ describe('parseMinParts()', () => {
minute: 30,
});
});
it('should return undefined when given invalid info', () => {
const today = {
day: 14,
month: 3,
year: 2022,
minute: 4,
hour: 2,
};
expect(parseMinParts(undefined, today)).toEqual(undefined);
expect(parseMinParts(null, today)).toEqual(undefined);
expect(parseMinParts('foo', today)).toEqual(undefined);
});
});
describe('parseMaxParts()', () => {
@@ -205,4 +217,16 @@ describe('parseMaxParts()', () => {
minute: 59,
});
});
it('should return undefined when given invalid info', () => {
const today = {
day: 14,
month: 3,
year: 2022,
minute: 4,
hour: 2,
};
expect(parseMaxParts(undefined, today)).toEqual(undefined);
expect(parseMaxParts(null, today)).toEqual(undefined);
expect(parseMaxParts('foo', today)).toEqual(undefined);
});
});

View File

@@ -132,8 +132,17 @@ export const parseAmPm = (hour: number) => {
* For example, max="2012" would fill in the missing
* month, day, hour, and minute information.
*/
export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeParts => {
const { month, day, year, hour, minute } = parseDate(max);
export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeParts | undefined => {
const result = parseDate(max);
/**
* If min was not a valid date then return undefined.
*/
if (result === undefined) {
return;
}
const { month, day, year, hour, minute } = result;
/**
* When passing in `max` or `min`, developers
@@ -168,8 +177,17 @@ export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeP
* For example, min="2012" would fill in the missing
* month, day, hour, and minute information.
*/
export const parseMinParts = (min: string, todayParts: DatetimeParts): DatetimeParts => {
const { month, day, year, hour, minute } = parseDate(min);
export const parseMinParts = (min: string, todayParts: DatetimeParts): DatetimeParts | undefined => {
const result = parseDate(min);
/**
* If min was not a valid date then return undefined.
*/
if (result === undefined) {
return;
}
const { month, day, year, hour, minute } = result;
/**
* When passing in `max` or `min`, developers

View File

@@ -20,7 +20,7 @@ import type { RouteID, RouterDirection, RouteWrite, NavOutlet } from '../router/
@Component({
tag: 'ion-router-outlet',
styleUrl: 'route-outlet.scss',
styleUrl: 'router-outlet.scss',
shadow: true,
})
export class RouterOutlet implements ComponentInterface, NavOutlet {

View File

@@ -1,7 +1,7 @@
import { newSpecPage } from '@stencil/core/testing';
import { Nav } from '../../../components/nav/nav';
import { RouterOutlet } from '../../../components/router-outlet/route-outlet';
import { RouterOutlet } from '../../../components/router-outlet/router-outlet';
import { setRootAriaHidden } from '../../overlays';
describe('setRootAriaHidden()', () => {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/docs
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT"
}
}

View File

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

View File

@@ -4,5 +4,5 @@
"docs",
"packages/*"
],
"version": "7.3.1"
"version": "7.3.2"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/angular-server
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.3.1"
"@ionic/core": "^7.3.2"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^14.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "7.3.1",
"version": "7.3.2",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -61,6 +61,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^7.3.1"
"@ionic/core": "^7.3.2"
}
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/angular
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "7.3.1",
"version": "7.3.2",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -47,7 +47,7 @@
}
},
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/react-router
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
**Note:** Version bump only for package @ionic/react-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/react": "^7.3.1",
"@ionic/react": "^7.3.2",
"tslib": "*"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "7.3.1",
"version": "7.3.2",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -37,7 +37,7 @@
"dist/"
],
"dependencies": {
"@ionic/react": "^7.3.1",
"@ionic/react": "^7.3.2",
"tslib": "*"
},
"peerDependencies": {

View File

@@ -46,10 +46,6 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
addViewItem: this.viewStack.add,
unMountViewItem: this.viewStack.remove,
registerIonPage: this.viewStack.registerIonPage,
unmountLeavingViews: this.viewStack.unmountLeavingViews,
mountIntermediaryViews: this.viewStack.mountIntermediaryViews,
size: this.viewStack.size,
};
constructor(props: IonRouteProps) {

View File

@@ -1,9 +1,7 @@
import type { RouteInfo, ViewItem } from '@ionic/react';
import { IonRoute, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
import React from 'react';
// import { matchPath } from 'react-router';
import { matchPath } from './utils/matchPath';
import { matchPath } from 'react-router';
export class ReactRouterViewStack extends ViewStacks {
constructor() {
@@ -15,30 +13,35 @@ export class ReactRouterViewStack extends ViewStacks {
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
}
createViewItem(
outletId: string,
reactElement: React.ReactElement,
routeInfo: RouteInfo,
ionPage?: HTMLElement
): ViewItem {
const ionRoute = reactElement.type === IonRoute;
return {
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
const viewItem: ViewItem = {
id: generateId('viewItem'),
outletId,
ionPageElement: ionPage,
ionPageElement: page,
reactElement,
ionRoute,
disableIonPageManagement: ionRoute && reactElement.props.disableIonPageManagement,
mount: false,
routeData: {
match: matchPath({
pathname: routeInfo.pathname,
componentProps: reactElement.props,
}),
childProps: reactElement.props,
},
mount: true,
ionRoute: false,
};
const matchProps = {
exact: reactElement.props.exact,
path: reactElement.props.path || reactElement.props.from,
component: reactElement.props.component,
};
const match = matchPath(routeInfo.pathname, matchProps);
if (reactElement.type === IonRoute) {
viewItem.ionRoute = true;
viewItem.disableIonPageManagement = reactElement.props.disableIonPageManagement;
}
viewItem.routeData = {
match,
childProps: reactElement.props,
};
return viewItem;
}
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
@@ -55,7 +58,7 @@ export class ReactRouterViewStack extends ViewStacks {
});
const children = viewItems.map((viewItem) => {
let clonedChild: React.ReactNode;
let clonedChild;
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
clonedChild = (
<ViewLifeCycleManager
@@ -93,20 +96,8 @@ export class ReactRouterViewStack extends ViewStacks {
return children;
}
/**
* Registers the `<IonPage>` element reference to
* the view item with the matching route info.
*/
registerIonPage(viewItem: ViewItem, ionPage: HTMLElement) {
if (viewItem) {
// TODO view doesn't check if it exists
viewItem.ionPageElement = ionPage;
viewItem.ionRoute = true;
}
}
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId, false);
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
if (shouldUpdateMatch && viewItem && match) {
viewItem.routeData.match = match;
@@ -115,30 +106,31 @@ export class ReactRouterViewStack extends ViewStacks {
}
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, mustBeIonRoute);
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, mustBeIonRoute);
return viewItem;
}
findViewItemByPathname(pathname: string, outletId?: string) {
const { viewItem } = this.findViewItemByPath(pathname, outletId, false);
const { viewItem } = this.findViewItemByPath(pathname, outletId);
return viewItem;
}
private findViewItemByPath(pathname: string, outletId?: string, mustBeIonRoute = false) {
private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
let viewItem: ViewItem | undefined;
let match: ReturnType<typeof matchPath> | undefined;
let viewStack: ViewItem[];
if (outletId) {
const viewStack = this.getViewItemsForOutlet(outletId);
viewStack = this.getViewItemsForOutlet(outletId);
viewStack.some(matchView);
if (!viewItem) {
viewStack.some(matchDefaultRoute);
}
} else {
const viewStack = this.getAllViewItems();
viewStack.some(matchView);
const viewItems = this.getAllViewItems();
viewItems.some(matchView);
if (!viewItem) {
viewStack.some(matchDefaultRoute);
viewItems.some(matchDefaultRoute);
}
}
@@ -148,14 +140,15 @@ export class ReactRouterViewStack extends ViewStacks {
if (mustBeIonRoute && !v.ionRoute) {
return false;
}
match = matchPath({
pathname,
componentProps: v.routeData.childProps,
});
if (match) {
const matchProps = {
exact: forceExact ? true : v.routeData.childProps.exact,
path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component,
};
const myMatch = matchPath(pathname, matchProps);
if (myMatch) {
viewItem = v;
match = myMatch;
return true;
}
return false;
@@ -178,16 +171,13 @@ export class ReactRouterViewStack extends ViewStacks {
}
}
function matchComponent(node: React.ReactElement, pathname: string) {
// const matchProps = {
// exact: forceExact ? true : node.props.exact,
// path: node.props.path || node.props.from,
// component: node.props.component,
// };
// const match = matchPath(pathname, matchProps);
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
const matchProps = {
exact: forceExact ? true : node.props.exact,
path: node.props.path || node.props.from,
component: node.props.component,
};
const match = matchPath(pathname, matchProps);
return matchPath({
pathname,
componentProps: node.props,
});
return match;
}

View File

@@ -1,20 +1,21 @@
import type { AnimationBuilder, RouteInfo, RouterDirection, StackContextState } from '@ionic/react';
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import type { ReactNode } from 'react';
import React from 'react';
import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
// TODO(FW-2959): types
interface StackManagerProps {
routeInfo: RouteInfo;
}
interface StackManagerState {
components: ReactNode[];
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StackManagerState {}
const isViewVisible = (enteringEl: HTMLElement) => {
return !enteringEl.classList.contains('ion-page-invisible') && !enteringEl.classList.contains('ion-page-hidden');
};
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;
@@ -30,18 +31,16 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
};
private clearOutletTimeout: any;
private pendingPageTransition = false;
constructor(props: StackManagerProps) {
super(props);
this.registerIonPage = this.registerIonPage.bind(this);
this.transition = this.transition.bind(this);
this.transitionPage = this.transitionPage.bind(this);
this.handlePageTransition = this.handlePageTransition.bind(this);
this.id = generateId('ion-router-outlet');
this.id = generateId('routerOutlet');
this.prevProps = undefined;
this.skipTransition = false;
this.state = {
components: [],
};
}
componentDidMount() {
@@ -59,7 +58,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
}
if (this.routerOutletElement) {
this.setupRouterOutlet(this.routerOutletElement);
this.setupViewItem();
this.handlePageTransition(this.props.routeInfo);
}
}
@@ -69,362 +68,145 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
if (pathname !== prevPathname) {
this.prevProps = prevProps;
console.log('pathname changed... setup view item?!');
this.setupViewItem();
this.handlePageTransition(this.props.routeInfo);
} else if (this.pendingPageTransition) {
this.handlePageTransition(this.props.routeInfo);
this.pendingPageTransition = false;
}
// if (pathname !== prevPathname) {
// this.prevProps = prevProps;
// console.log('calling page transition because path is different', {
// pathname,
// prevPathname,
// });
// this.handlePageTransition(this.props.routeInfo);
// } else if (this.pendingPageTransition) {
// console.log('calling page transition because pending transition');
// this.handlePageTransition(this.props.routeInfo);
// this.pendingPageTransition = false;
// }
}
componentWillUnmount() {
/**
* Remove stack data for this outlet
* when outlet is destroyed otherwise
* we will see cached view data.
*/
this.clearOutletTimeout = this.context.clearOutlet(this.id);
}
setupViewItem() {
// TODO additional checks
const { id, props } = this;
const { routeInfo } = props;
/**
* This function is responsible for creating the entering view item
* and adding it to the view stack.
*/
const currentRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, id);
if (!enteringViewItem) {
async handlePageTransition(routeInfo: RouteInfo) {
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
/**
* If the entering view item does not exist, this is
* the first time we are rendering this route.
* The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page.
*
* We need to create a view item instance and add it to the view stack.
* Later, we will mount the view item and transition the page.
* Set a flag to indicate that we should transition the page after
* the component has updated.
*/
enteringViewItem = this.context.createViewItem(id, currentRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
if (!enteringViewItem.mount) {
/**
* If the entering view item is not mounted,
* that means it has not been rendered yet.
* We need to mount it and then finish the transition
* after the React component has mounted.
*/
enteringViewItem.mount = true;
enteringViewItem.registerCallback = () => {
this.handlePageTransition();
if (enteringViewItem) {
enteringViewItem.registerCallback = undefined;
}
};
this.pendingPageTransition = true;
} else {
/**
* If the entering view item has already mounted,
* that means the page reference should already
* exist and we can transition immediately.
*/
this.handlePageTransition();
}
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
this.setState({
components: this.context.getChildrenToRender(id, this.ionRouterOutlet!, routeInfo),
});
}
async handlePageTransition() {
const { id, props } = this;
const { routeInfo } = props;
const { prevRouteLastPathname, routeDirection, pushedByRoute, routeAnimation, routeAction, delta } = routeInfo;
const enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, id)!;
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, id);
const enteringEl = enteringViewItem?.ionPageElement;
/**
* All views that can be transitioned to must have
* an `<ion-page>` element for transitions and lifecycle
* methods to work properly.
*/
if (enteringEl === undefined) {
console.warn(`[@ionic/react Warning]: The view you are trying to render for path ${routeInfo.pathname} does not have the required <IonPage> component. Transitions and lifecycle methods may not work as expected.
See https://ionicframework.com/docs/react/navigation#ionpage for more information.`);
}
if (enteringViewItem === leavingViewItem) return;
if (!leavingViewItem && prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(prevRouteLastPathname, id);
}
/**
* If the entering view is already
* visible, then no transition is needed.
* This is most common when navigating
* from a tabs page to a non-tabs page
* and then back to the tabs page.
* Even when the tabs context navigated away,
* the inner tabs page was still active.
* This also avoids an issue where
* the previous tabs page is incorrectly
* unmounted since it would automatically
* unmount the previous view.
*
* This should also only apply to entering and
* leaving items in the same router outlet (i.e.
* Tab1 and Tab2), otherwise this will
* return early for swipe to go back when
* going from a non-tabs page to a tabs page.
*/
if (
enteringEl !== undefined &&
isViewVisible(enteringEl) &&
leavingViewItem?.ionPageElement !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement)
) {
return;
}
// Vue performs a fireLifeCycle for willEnter here on the enteringViewItem
if (leavingViewItem?.ionPageElement && enteringViewItem !== leavingViewItem) {
const enteringEl = enteringViewItem.ionPageElement;
const leavingEl = leavingViewItem.ionPageElement;
let animationBuilder = routeAnimation;
/**
* If we are going back from a page that
* was presented using a custom animation
* we should default to using that
* unless the developer explicitly
* provided another animation.
*/
const customAnimation = enteringViewItem!.routerAnimation;
if (animationBuilder === undefined && routeDirection === 'back' && customAnimation !== undefined) {
animationBuilder = customAnimation;
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
}
leavingViewItem.routerAnimation = animationBuilder;
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transition(enteringEl!, leavingEl, routeDirection!, !!pushedByRoute, false, animationBuilder);
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
const usingLinearNavigation = this.context.size() === 1;
if (routeAction === 'replace') {
leavingViewItem.mount = false;
leavingViewItem.ionPageElement = undefined;
leavingViewItem.ionRoute = false;
} else if (!(routeAction === 'push' && routeDirection === 'forward')) {
const shouldLeavingViewBeRemoved =
routeDirection !== 'none' && leavingViewItem && enteringViewItem !== leavingViewItem;
if (shouldLeavingViewBeRemoved) {
// Check if leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
leavingViewItem.ionPageElement = undefined;
leavingViewItem.ionRoute = false;
}
if (usingLinearNavigation) {
this.context.unmountLeavingViews(id, enteringViewItem, delta);
}
} else if (usingLinearNavigation) {
this.context.mountIntermediaryViews(id, leavingViewItem, delta);
}
} else {
/**
* If there is no leaving element, just show
* the entering element. Wrap it in an raf
* in case IonContent's fullscreen callback
* is running. Otherwise we'd have a flicker.
*/
requestAnimationFrame(() => enteringEl?.classList.remove('ion-page-invisible'));
}
// this.forceUpdate();
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
if (enteringViewItem) {
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
if (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* If the entering view item is the same as the leaving view item,
* then we don't need to transition.
*/
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.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/
if (enteringViewItem.routeData.match.url !== 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 (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
return;
}
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
} 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 (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
// }, 250);
}
this.forceUpdate();
}
}
// 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.
// *
// * Set a flag to indicate that we should transition the page after
// * the component has updated.
// */
// this.pendingPageTransition = true;
// } else {
// console.log('handling page transition...');
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
// let enteringViewItem = this.context.findViewItemByPathname(routeInfo.pathname, this.id);
// let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
// if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
// leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
// }
// // Check if leavingViewItem should be unmounted
// if (leavingViewItem) {
// if (routeInfo.routeAction === 'replace') {
// leavingViewItem.mount = false;
// } else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
// if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
// leavingViewItem.mount = false;
// }
// } else if (routeInfo.routeOptions?.unmount) {
// leavingViewItem.mount = false;
// }
// }
// const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
// if (enteringViewItem) {
// enteringViewItem.reactElement = enteringRoute;
// } else if (enteringRoute) {
// enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
// this.context.addViewItem(enteringViewItem);
// }
// if (enteringViewItem && enteringViewItem.ionPageElement) {
// /**
// * If the entering view item is the same as the leaving view item,
// * then we don't need to transition.
// */
// 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.
// *
// * If the route data does not match the current path, the parent router outlet
// * is attempting to transition and we cancel the operation.
// */
// if (enteringViewItem.routeData.match.url !== 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 (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
// leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
// }
// /**
// * If the entering view is already visible and the leaving view is not, the transition does not need to occur.
// */
// if (
// isViewVisible(enteringViewItem.ionPageElement) &&
// leavingViewItem !== undefined &&
// !isViewVisible(leavingViewItem.ionPageElement!)
// ) {
// return;
// }
// /**
// * The view should only be transitioned in the following cases:
// * 1. Performing a replace or pop action, such as a swipe to go back gesture
// * to animation the leaving view off the screen.
// *
// * 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
// * or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
// *
// * 3. The entering view is an ion-router-outlet containing a page
// * matching the current route and that hasn't already transitioned in.
// *
// * This should only happen when navigating directly to a nested router outlet
// * route or on an initial page load (i.e. refreshing). In cases when loading
// * /tabs/tab-1, we need to transition the /tabs page element into the view.
// */
// this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
// } 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 (leavingViewItem.ionPageElement) {
// leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
// leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
// }
// // }, 250);
// }
// this.forceUpdate();
// }
// }
registerIonPage(routeInfo: RouteInfo, ionPageEl: HTMLElement) {
const { id } = this;
const viewItem = this.context.findViewItemByRouteInfo(routeInfo, id)!;
const oldIonPageEl = viewItem.ionPageElement;
this.context.registerIonPage(viewItem, ionPageEl);
/**
* If there is a registerCallback,
* then this component is being registered
* as a result of a navigation change.
*/
if (viewItem.registerCallback) {
/**
* Page should be hidden initially
* to avoid flickering.
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
ionPageEl.classList.add('ion-page-invisible');
viewItem.registerCallback();
} else if (oldIonPageEl && !oldIonPageEl.classList.contains('ion-page-invisible')) {
/**
* If there is no registerCallback, then
* this component is likely being re-registered
* as a result of a hot module replacement.
* We need to see if the oldIonPageEl has
* .ion-page-invisible. If it does not then we
* need to remove it from the new ionPageEl otherwise
* the page will be hidden when it is replaced.
*/
ionPageEl.classList.remove('ion-page-invisible');
if (oldPageElement === page) {
return;
}
}
this.handlePageTransition(routeInfo);
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
@@ -465,52 +247,24 @@ See https://ionicframework.com/docs/react/navigation#ionpage for more informatio
};
const onStart = async () => {
const { id } = this;
const { routeInfo } = this.props;
let { routeAnimation: animationBuilder } = routeInfo;
// const propsToUse =
// this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
// ? this.prevProps.routeInfo
// : ({ pathname: routeInfo.pushedByRoute || '' } as any);
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
const enteringViewItem = this.context.findViewItemByRouteInfo(
{ pathname: routeInfo.pushedByRoute || '' } as RouteInfo,
id,
false
);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, id, false);
if (leavingViewItem) {
const enteringEl = enteringViewItem?.ionPageElement;
const leavingEl = leavingViewItem?.ionPageElement;
/**
* If we are going back from a page that
* was presented using a custom animation
* we should default to using that
* unless the developer explicitly
* provided another animation.
*/
const customAnimation = enteringViewItem!.routerAnimation;
if (animationBuilder === undefined && customAnimation !== undefined) {
animationBuilder = customAnimation;
}
leavingViewItem.routerAnimation = animationBuilder;
await this.transition(enteringEl!, leavingEl!, 'back', this.context.canGoBack(), true, animationBuilder);
/**
* When the gesture starts, kick off
* a transition that is controlled
* via a swipe gesture.
*/
if (enteringViewItem && leavingViewItem) {
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
}
// /**
// * When the gesture starts, kick off
// * a transition that is controlled
// * via a swipe gesture.
// */
// if (enteringViewItem && leavingViewItem) {
// await this.transition(enteringViewItem.ionPageElement!, leavingViewItem.ionPageElement!, 'back', true, true);
// }
return Promise.resolve();
};
const onEnd = (shouldContinue: boolean) => {
@@ -557,103 +311,82 @@ See https://ionicframework.com/docs/react/navigation#ionpage for more informatio
};
}
async transition(
enteringEl: HTMLElement,
leavingEl: HTMLElement,
direction: RouterDirection,
showGoBack: boolean,
progressAnimation: boolean,
animationBuilder?: AnimationBuilder
async transitionPage(
routeInfo: RouteInfo,
enteringViewItem: ViewItem,
leavingViewItem?: ViewItem,
direction?: 'forward' | 'back',
progressAnimation = false
) {
console.log('transition', {
enteringEl,
leavingEl,
direction,
});
const { skipTransition, routerOutletElement } = this;
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = this.skipTransition;
/**
* If the transition was handled
* via the swipe to go back gesture,
* then we do not want to perform
* another transition.
*
* We skip adding ion-page or ion-page-invisible
* because the entering view already exists in the DOM.
* If we added the classes, there would be a flicker where
* the view would be briefly hidden.
*/
if (skipTransition) {
/**
* We need to reset skipTransition before
* we call routerOutlet.commit otherwise
* the transition triggered by the swipe
* to go back gesture would reset it. In
* that case you would see a duplicate
* transition triggered by handlePageTransition
* in componentDidUpdate.
* If the transition was handled
* via the swipe to go back gesture,
* then we do not want to perform
* another transition.
*
* We skip adding ion-page or ion-page-invisible
* because the entering view already exists in the DOM.
* If we added the classes, there would be a flicker where
* the view would be briefly hidden.
*/
this.skipTransition = false;
if (skipTransition) {
/**
* We need to reset skipTransition before
* we call routerOutlet.commit otherwise
* the transition triggered by the swipe
* to go back gesture would reset it. In
* that case you would see a duplicate
* transition triggered by handlePageTransition
* in componentDidUpdate.
*/
this.skipTransition = false;
} else {
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
}
return Promise.resolve(false);
await routerOutlet.commit(enteringEl, leavingEl, {
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
};
const routerOutlet = this.routerOutletElement!;
const routeInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routeInfoFallbackDirection;
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
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);
if (match) {
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
if (newLeavingElement) {
this.routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement);
}
} else {
await runCommit(enteringViewItem.ionPageElement, undefined);
}
} else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
}
if (enteringEl === leavingEl) {
return Promise.resolve(false);
}
enteringEl.classList.add('ion-page-invisible');
const hasRootDirection = direction === undefined || direction === 'root' || direction === 'none';
const result = await routerOutletElement!.commit(enteringEl, leavingEl, {
/**
* replace operations result in a direction of none.
* These typically do not have need animations, so we set
* the duration to 0. However, if a developer explicitly
* passes an animationBuilder, we should assume that
* they want an animation to be played even
* though it is a replace operation.
*/
duration: hasRootDirection && animationBuilder === undefined ? 0 : undefined,
direction: direction as any, // TODO none isn't a valid direction, investigate
showGoBack,
progressAnimation,
animationBuilder,
});
return result;
// };
// const routerOutlet = this.routerOutletElement!;
// const routeInfoFallbackDirection =
// routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
// const directionToUse = direction ?? routeInfoFallbackDirection;
// if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
// 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);
// if (match) {
// const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
// if (newLeavingElement) {
// this.routerOutletElement.appendChild(newLeavingElement);
// await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
// this.routerOutletElement.removeChild(newLeavingElement);
// }
// } else {
// await runCommit(enteringViewItem.ionPageElement, undefined);
// }
// } else {
// await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
// if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
// leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
// leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
// }
// }
// }
}
render() {
@@ -661,6 +394,10 @@ See https://ionicframework.com/docs/react/navigation#ionpage for more informatio
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
this.ionRouterOutlet = ionRouterOutlet;
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
this.forceUpdate();
});
return (
<StackContext.Provider value={this.stackContextValue}>
{React.cloneElement(
@@ -673,17 +410,14 @@ See https://ionicframework.com/docs/react/navigation#ionpage for more informatio
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
if (node) {
this.routerOutletElement = node;
console.log('assigned router outlet element node...', node);
}
this.routerOutletElement = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
},
this.state.components
components
)}
</StackContext.Provider>
);
@@ -724,13 +458,13 @@ function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
return matchedNode;
}
// function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
// const matchProps = {
// exact: forceExact ? true : node.props.exact,
// path: node.props.path || node.props.from,
// component: node.props.component,
// };
// const match = matchPath(pathname, matchProps);
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
const matchProps = {
exact: forceExact ? true : node.props.exact,
path: node.props.path || node.props.from,
component: node.props.component,
};
const match = matchPath(pathname, matchProps);
// return match;
// }
return match;
}

View File

@@ -1,65 +0,0 @@
import { matchPath as reactRouterMatchPath } from 'react-router';
interface MatchPathOptions {
/**
* The pathname to match against.
*/
pathname: string;
/**
* The props to match against, they are identical to the matching props `Route` accepts.
*/
componentProps: {
path?: string;
from?: string;
component?: any;
exact?: boolean;
};
}
/**
* @see https://v5.reactrouter.com/web/api/matchPath
*/
export const matchPath = ({ pathname, componentProps }: MatchPathOptions): false | ReturnType<typeof reactRouterMatchPath> => {
const { exact, component } = 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
};
const match = reactRouterMatchPath(pathname, matchProps);
if (!match) {
return false;
}
const hasParameter = match.path.includes(':');
if (hasParameter) {
console.log('the match path has a parameter!!', {
pathname,
url: match.url,
match
})
}
if (hasParameter && match.url.includes(':')) {
return false;
}
if (hasParameter && pathname !== match.url) {
console.log('discarding the match because it has a path parameter', {
pathname,
path,
match
})
return false;
}
return match;
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/react
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0",
"tslib": "*"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "7.3.1",
"version": "7.3.2",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -41,7 +41,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0",
"tslib": "*"
},

View File

@@ -8,11 +8,7 @@ export interface RouteInfo<TOptions = any> {
lastPathname?: string;
prevRouteLastPathname?: string;
routeAction?: RouteAction;
// In Ionic Vue this is called routerDirection
// TODO we should align the naming
routeDirection?: RouterDirection;
// In Ionic Vue this is called routerAnimation
// TODO we should align the naming
routeAnimation?: AnimationBuilder;
routeOptions?: TOptions;
params?: { [key: string]: string | string[] };
@@ -20,5 +16,4 @@ export interface RouteInfo<TOptions = any> {
pathname: string;
search: string;
tab?: string;
delta?: number;
}

View File

@@ -35,9 +35,7 @@ export class OutletPageManager extends React.Component<OutletPageManagerProps> {
if (!this.outletIsReady) {
componentOnReady(this.ionRouterOutlet, () => {
this.outletIsReady = true;
console.log('registerIonPage from OutletPageManager');
this.context.registerIonPage(this.props.routeInfo!, this.ionRouterOutlet!);
// this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
});
}

View File

@@ -30,11 +30,7 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
if (this.context.isInOutlet()) {
this.ionPageElementRef.current.classList.add('ion-page-invisible');
}
console.log('registerIonPage from PageManager');
this.context.registerIonPage(this.props.routeInfo!, this.ionPageElementRef.current);
// this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));

View File

@@ -14,52 +14,17 @@ export interface RouteManagerContextState {
routeInfo: RouteInfo,
page?: HTMLElement
) => ViewItem;
findViewItemByPathname(pathname: string, outletId?: string, forceExact?: boolean): ViewItem | undefined;
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => ViewItem | undefined;
getChildrenToRender: (
outletId: string,
ionRouterOutlet: React.ReactElement,
routeInfo: RouteInfo
routeInfo: RouteInfo,
reRender: () => void
) => React.ReactNode[];
goBack: () => void;
/**
* Returns the number of active stacks.
* This is useful for determining if an app
* is using linear navigation only or non-linear
* navigation. Multiple stacks indicate an app
* is using non-linear navigation.
*/
size: () => number;
/**
* When navigating backwards, we need to clean up and
* leaving pages so that they are re-created if
* we ever navigate back to them. This is especially
* important when using router.go and stepping back
* multiple pages at a time.
*/
unmountLeavingViews: (outletId: string, viewItem: ViewItem, delta?: number) => void;
/**
* When navigating forward it is possible for
* developers to step forward over multiple views.
* The intermediary views need to be remounted so that
* swipe to go back works properly.
* We need to account for the delta value here too because
* we do not want to remount an unrelated view.
* Example:
* /home --> /page2 --> router.back() --> /page3
* Going to /page3 would remount /page2 since we do
* not prune /page2 from the stack. However, /page2
* needs to remain in the stack.
* Example:
* /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2)
* We would end up on /page3, but users need to be able to swipe
* to go back to /page2 and /home, so we need both pages mounted
* in the DOM.
*/
mountIntermediaryViews: (outletId: string, viewItem: ViewItem, delta?: number) => void;
unMountViewItem: (viewItem: ViewItem) => void;
registerIonPage: (viewItem: ViewItem, ionPage: HTMLElement) => void;
}
// TODO(FW-2959): types
@@ -71,11 +36,7 @@ export const RouteManagerContext = /*@__PURE__*/ React.createContext<RouteManage
findViewItemByPathname: () => undefined,
findLeavingViewItemByRouteInfo: () => undefined,
findViewItemByRouteInfo: () => undefined,
getChildrenToRender: () => [],
getChildrenToRender: () => undefined as any,
goBack: () => undefined,
size: () => 0,
unmountLeavingViews: () => undefined,
mountIntermediaryViews: () => undefined,
unMountViewItem: () => undefined,
registerIonPage: () => undefined
});

View File

@@ -3,7 +3,7 @@ import React from 'react';
import type { RouteInfo } from '../models/RouteInfo';
export interface StackContextState {
registerIonPage: (routeInfo: RouteInfo, page: HTMLElement) => void;
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => void;
isInOutlet: () => boolean;
}

View File

@@ -1,4 +1,3 @@
import type { AnimationBuilder } from '@ionic/core';
import type { ReactElement } from 'react';
export interface ViewItem<T = any> {
@@ -11,9 +10,4 @@ export interface ViewItem<T = any> {
transitionHtml?: string;
outletId: string;
disableIonPageManagement?: boolean;
routerAnimation?: AnimationBuilder;
/**
* Callback function when the view item is registered.
*/
registerCallback?: () => void;
}

View File

@@ -44,69 +44,6 @@ export abstract class ViewStacks {
}
}
/**
* When navigating backwards, we need to clean up and
* leaving pages so that they are re-created if
* we ever navigate back to them. This is especially
* important when using router.go and stepping back
* multiple pages at a time.
*/
unmountLeavingViews(outletId: string, viewItem: ViewItem, delta = 1) {
const viewStack = this.viewStacks[outletId];
if (!viewStack) return;
const startIndex = viewStack.findIndex(v => v === viewItem);
for (let i = startIndex + 1; i < startIndex - delta; i++) {
const viewItem = viewStack[1];
viewItem.mount = false;
viewItem.ionPageElement = undefined;
viewItem.ionRoute = false;
// This is Vue specific - TODO find if there is a React equivalent needed
// viewItem.matchedRoute.instances = {};
}
}
/**
* When navigating forward it is possible for
* developers to step forward over multiple views.
* The intermediary views need to be remounted so that
* swipe to go back works properly.
* We need to account for the delta value here too because
* we do not want to remount an unrelated view.
* Example:
* /home --> /page2 --> router.back() --> /page3
* Going to /page3 would remount /page2 since we do
* not prune /page2 from the stack. However, /page2
* needs to remain in the stack.
* Example:
* /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2)
* We would end up on /page3, but users need to be able to swipe
* to go back to /page2 and /home, so we need both pages mounted
* in the DOM.
*/
mountIntermediaryViews(outletId: string, viewItem: ViewItem, delta = 1) {
const viewStack = this.viewStacks[outletId];
if (!viewStack) return;
const startIndex = viewStack.findIndex(v => v === viewItem);
for (let i = startIndex + 1; i < startIndex + delta; i++) {
viewStack[i].mount = true;
}
}
/**
* Returns the number of active stacks.
* This is useful for determining if an app
* is using linear navigation only or non-linear
* navigation. Multiple stacks indicate an app
* is using non-linear navigation.
*/
size() {
return Object.keys(this.viewStacks || {}).length;
}
protected getStackIds() {
return Object.keys(this.viewStacks);
}
@@ -126,7 +63,7 @@ export abstract class ViewStacks {
routeInfo: RouteInfo,
page?: HTMLElement
): ViewItem;
abstract findViewItemByPathname(pathname: string, outletId?: string, forceExact?: boolean): ViewItem | undefined;
abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
abstract findViewItemByRouteInfo(
routeInfo: RouteInfo,
outletId?: string,
@@ -137,5 +74,7 @@ export abstract class ViewStacks {
outletId: string,
ionRouterOutlet: React.ReactElement,
routeInfo: RouteInfo,
reRender: () => void,
setInTransition: () => void
): React.ReactNode[];
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/vue-router
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
**Note:** Version bump only for package @ionic/vue-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^7.3.1"
"@ionic/vue": "^7.3.2"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "7.3.1",
"version": "7.3.2",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"test.spec": "jest",
@@ -45,7 +45,7 @@
},
"homepage": "https://github.com/ionic-team/ionic#readme",
"dependencies": {
"@ionic/vue": "^7.3.1"
"@ionic/vue": "^7.3.2"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -11,7 +11,7 @@ export const createViewStacks = (router: Router) => {
* Returns the number of active stacks.
* This is useful for determining if an app
* is using linear navigation only or non-linear
* navigation. Multiple stacks indicate an app
* navigation. Multiple stacks indiciate an app
* is using non-linear navigation.
*/
const size = () => Object.keys(viewStacks).length;

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.3.2](https://github.com/ionic-team/ionic-framework/compare/v7.3.1...v7.3.2) (2023-08-30)
**Note:** Version bump only for package @ionic/vue
## [7.3.1](https://github.com/ionic-team/ionic-framework/compare/v7.3.0...v7.3.1) (2023-08-23)
**Note:** Version bump only for package @ionic/vue

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "7.3.1",
"version": "7.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "7.3.1",
"version": "7.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "7.3.1",
"version": "7.3.2",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"eslint": "eslint src",
@@ -66,7 +66,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
"@ionic/core": "^7.3.1",
"@ionic/core": "^7.3.2",
"ionicons": "^7.0.0"
},
"vetur": {