From bd27846b28d909c580cf07933d9c880a37b1d0e6 Mon Sep 17 00:00:00 2001
From: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Date: Tue, 28 Nov 2023 19:41:00 -0500
Subject: [PATCH 01/11] chore(playwright): resolve axe violations with
setContent (#28592)
Issue number: N/A
---------
## What is the current behavior?
When using `.setContent`, the initial page template is lacking key
metadata that will result in AXE violations.
## What is the new behavior?
- Adds a default `lang` to the `html` document root
- Adds a default `title` tag
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
## Other information
---
core/src/utils/test/playwright/page/utils/set-content.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/core/src/utils/test/playwright/page/utils/set-content.ts b/core/src/utils/test/playwright/page/utils/set-content.ts
index 3087789cdf..16fd1cc3b3 100644
--- a/core/src/utils/test/playwright/page/utils/set-content.ts
+++ b/core/src/utils/test/playwright/page/utils/set-content.ts
@@ -32,8 +32,9 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
const output = `
-
+
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
core/package-lock.json | 14 +++++++-------
core/package.json | 2 +-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/core/package-lock.json b/core/package-lock.json
index 3706a97c00..08c47b5014 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -9,7 +9,7 @@
"version": "7.5.7",
"license": "MIT",
"dependencies": {
- "@stencil/core": "^4.7.2",
+ "@stencil/core": "^4.8.0",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},
@@ -1825,9 +1825,9 @@
}
},
"node_modules/@stencil/core": {
- "version": "4.7.2",
- "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.2.tgz",
- "integrity": "sha512-sPPDYrXiTbfeUF5CCyfqysXK/yfTHC4xYR1+nHzGkS2vhRSBOLp0oPuB+xkJLKA+K2ZqDJUxpOnDxy1CLWwBXA==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.0.tgz",
+ "integrity": "sha512-iNEaMiEt9oFZXSjZ7qkdlBpPTzWzBgWM7go8nI8a7V6ZOkmdVhhT0xGQD7OY13v5ZuDoIw5IsGvbIAGefhIhhQ==",
"bin": {
"stencil": "bin/stencil"
},
@@ -12184,9 +12184,9 @@
"requires": {}
},
"@stencil/core": {
- "version": "4.7.2",
- "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.7.2.tgz",
- "integrity": "sha512-sPPDYrXiTbfeUF5CCyfqysXK/yfTHC4xYR1+nHzGkS2vhRSBOLp0oPuB+xkJLKA+K2ZqDJUxpOnDxy1CLWwBXA=="
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.0.tgz",
+ "integrity": "sha512-iNEaMiEt9oFZXSjZ7qkdlBpPTzWzBgWM7go8nI8a7V6ZOkmdVhhT0xGQD7OY13v5ZuDoIw5IsGvbIAGefhIhhQ=="
},
"@stencil/react-output-target": {
"version": "0.5.3",
diff --git a/core/package.json b/core/package.json
index 925ec866bc..df64eb1499 100644
--- a/core/package.json
+++ b/core/package.json
@@ -31,7 +31,7 @@
"loader/"
],
"dependencies": {
- "@stencil/core": "^4.7.2",
+ "@stencil/core": "^4.8.0",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},
From 5c2a73b262b9549902db606490f125674f988569 Mon Sep 17 00:00:00 2001
From: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Date: Wed, 29 Nov 2023 15:48:16 -0500
Subject: [PATCH 05/11] chore(playwright): dark mode support (#28593)
Issue number: N/A
---------
## What is the current behavior?
Testing dark mode is manual per test in Playwright. Ionic developer
needs to setup the variables and assign them to a selector that applies
in the class.
## What is the new behavior?
- The `.setContent` API will now work with a new config option to test
dark mode automatically without additional configuration/test set-up.
- Default theme is no theme (fallback theme)
- Screenshot names and test titles remain the same for all existing
tests. Only tests that opt into a theme will be pre-pended with `-dark`
or `-light` (as an example for current themes).
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
## Other information
---
core/scripts/testing/themes/dark.css | 151 ++++++++++++++++++
core/src/utils/test/playwright/generator.ts | 51 ++++--
.../test/playwright/page/utils/set-content.ts | 11 +-
3 files changed, 203 insertions(+), 10 deletions(-)
create mode 100644 core/scripts/testing/themes/dark.css
diff --git a/core/scripts/testing/themes/dark.css b/core/scripts/testing/themes/dark.css
new file mode 100644
index 0000000000..e263803d56
--- /dev/null
+++ b/core/scripts/testing/themes/dark.css
@@ -0,0 +1,151 @@
+/*
+ * Dark Colors
+ * -------------------------------------------
+ */
+
+:root {
+ --ion-color-primary: #428cff;
+ --ion-color-primary-rgb: 66, 140, 255;
+ --ion-color-primary-contrast: #ffffff;
+ --ion-color-primary-contrast-rgb: 255, 255, 255;
+ --ion-color-primary-shade: #3a7be0;
+ --ion-color-primary-tint: #5598ff;
+
+ --ion-color-secondary: #50c8ff;
+ --ion-color-secondary-rgb: 80, 200, 255;
+ --ion-color-secondary-contrast: #ffffff;
+ --ion-color-secondary-contrast-rgb: 255, 255, 255;
+ --ion-color-secondary-shade: #46b0e0;
+ --ion-color-secondary-tint: #62ceff;
+
+ --ion-color-tertiary: #6a64ff;
+ --ion-color-tertiary-rgb: 106, 100, 255;
+ --ion-color-tertiary-contrast: #ffffff;
+ --ion-color-tertiary-contrast-rgb: 255, 255, 255;
+ --ion-color-tertiary-shade: #5d58e0;
+ --ion-color-tertiary-tint: #7974ff;
+
+ --ion-color-success: #2fdf75;
+ --ion-color-success-rgb: 47, 223, 117;
+ --ion-color-success-contrast: #000000;
+ --ion-color-success-contrast-rgb: 0, 0, 0;
+ --ion-color-success-shade: #29c467;
+ --ion-color-success-tint: #44e283;
+
+ --ion-color-warning: #ffd534;
+ --ion-color-warning-rgb: 255, 213, 52;
+ --ion-color-warning-contrast: #000000;
+ --ion-color-warning-contrast-rgb: 0, 0, 0;
+ --ion-color-warning-shade: #e0bb2e;
+ --ion-color-warning-tint: #ffd948;
+
+ --ion-color-danger: #ff4961;
+ --ion-color-danger-rgb: 255, 73, 97;
+ --ion-color-danger-contrast: #ffffff;
+ --ion-color-danger-contrast-rgb: 255, 255, 255;
+ --ion-color-danger-shade: #e04055;
+ --ion-color-danger-tint: #ff5b71;
+
+ --ion-color-dark: #f4f5f8;
+ --ion-color-dark-rgb: 244, 245, 248;
+ --ion-color-dark-contrast: #000000;
+ --ion-color-dark-contrast-rgb: 0, 0, 0;
+ --ion-color-dark-shade: #d7d8da;
+ --ion-color-dark-tint: #f5f6f9;
+
+ --ion-color-medium: #989aa2;
+ --ion-color-medium-rgb: 152, 154, 162;
+ --ion-color-medium-contrast: #000000;
+ --ion-color-medium-contrast-rgb: 0, 0, 0;
+ --ion-color-medium-shade: #86888f;
+ --ion-color-medium-tint: #a2a4ab;
+
+ --ion-color-light: #222428;
+ --ion-color-light-rgb: 34, 36, 40;
+ --ion-color-light-contrast: #ffffff;
+ --ion-color-light-contrast-rgb: 255, 255, 255;
+ --ion-color-light-shade: #1e2023;
+ --ion-color-light-tint: #383a3e;
+}
+
+/*
+ * iOS Dark Theme
+ * -------------------------------------------
+ */
+
+.ios body {
+ --ion-background-color: #000000;
+ --ion-background-color-rgb: 0, 0, 0;
+
+ --ion-text-color: #ffffff;
+ --ion-text-color-rgb: 255, 255, 255;
+
+ --ion-color-step-50: #0d0d0d;
+ --ion-color-step-100: #1a1a1a;
+ --ion-color-step-150: #262626;
+ --ion-color-step-200: #333333;
+ --ion-color-step-250: #404040;
+ --ion-color-step-300: #4d4d4d;
+ --ion-color-step-350: #595959;
+ --ion-color-step-400: #666666;
+ --ion-color-step-450: #737373;
+ --ion-color-step-500: #808080;
+ --ion-color-step-550: #8c8c8c;
+ --ion-color-step-600: #999999;
+ --ion-color-step-650: #a6a6a6;
+ --ion-color-step-700: #b3b3b3;
+ --ion-color-step-750: #bfbfbf;
+ --ion-color-step-800: #cccccc;
+ --ion-color-step-850: #d9d9d9;
+ --ion-color-step-900: #e6e6e6;
+ --ion-color-step-950: #f2f2f2;
+
+ --ion-toolbar-background: #0d0d0d;
+
+ --ion-item-background: #000000;
+
+ --ion-card-background: #1c1c1d;
+}
+
+/*
+ * Material Design Dark Theme
+ * -------------------------------------------
+ */
+
+.md body {
+ --ion-background-color: #121212;
+ --ion-background-color-rgb: 18, 18, 18;
+
+ --ion-text-color: #ffffff;
+ --ion-text-color-rgb: 255, 255, 255;
+
+ --ion-border-color: #222222;
+
+ --ion-color-step-50: #1e1e1e;
+ --ion-color-step-100: #2a2a2a;
+ --ion-color-step-150: #363636;
+ --ion-color-step-200: #414141;
+ --ion-color-step-250: #4d4d4d;
+ --ion-color-step-300: #595959;
+ --ion-color-step-350: #656565;
+ --ion-color-step-400: #717171;
+ --ion-color-step-450: #7d7d7d;
+ --ion-color-step-500: #898989;
+ --ion-color-step-550: #949494;
+ --ion-color-step-600: #a0a0a0;
+ --ion-color-step-650: #acacac;
+ --ion-color-step-700: #b8b8b8;
+ --ion-color-step-750: #c4c4c4;
+ --ion-color-step-800: #d0d0d0;
+ --ion-color-step-850: #dbdbdb;
+ --ion-color-step-900: #e7e7e7;
+ --ion-color-step-950: #f3f3f3;
+
+ --ion-item-background: #1e1e1e;
+
+ --ion-toolbar-background: #1f1f1f;
+
+ --ion-tab-bar-background: #1f1f1f;
+
+ --ion-card-background: #1e1e1e;
+}
diff --git a/core/src/utils/test/playwright/generator.ts b/core/src/utils/test/playwright/generator.ts
index e2cdd940f9..41b2abc183 100644
--- a/core/src/utils/test/playwright/generator.ts
+++ b/core/src/utils/test/playwright/generator.ts
@@ -1,5 +1,12 @@
export type Mode = 'ios' | 'md';
export type Direction = 'ltr' | 'rtl';
+/**
+ * The theme to use for the playwright test.
+ *
+ * - `light`: The fallback theme values. Theme stylesheet will not be included.
+ * - `dark`: The dark theme values.
+ */
+export type Theme = 'light' | 'dark';
export type TitleFn = (title: string) => string;
export type ScreenshotFn = (fileName: string) => string;
@@ -7,6 +14,7 @@ export type ScreenshotFn = (fileName: string) => string;
export interface TestConfig {
mode: Mode;
direction: Direction;
+ theme: Theme;
}
interface TestUtilities {
@@ -18,6 +26,7 @@ interface TestUtilities {
interface TestConfigOption {
modes?: Mode[];
directions?: Direction[];
+ themes?: Theme[];
}
/**
@@ -27,9 +36,19 @@ interface TestConfigOption {
* each test title is unique.
*/
const generateTitle = (title: string, config: TestConfig): string => {
- const { mode, direction } = config;
+ const { mode, direction, theme } = config;
- return `${title} - ${mode}/${direction}`;
+ if (theme === 'light') {
+ /**
+ * Ionic has many existing tests that existed prior to
+ * the introduction of theme testing. To maintain backwards
+ * compatibility, we will not include the theme in the test
+ * title if the theme is set to light.
+ */
+ return `${title} - ${mode}/${direction}`;
+ }
+
+ return `${title} - ${mode}/${direction}/${theme}`;
};
/**
@@ -37,9 +56,19 @@ const generateTitle = (title: string, config: TestConfig): string => {
* and a test config.
*/
const generateScreenshotName = (fileName: string, config: TestConfig): string => {
- const { mode, direction } = config;
+ const { mode, direction, theme } = config;
- return `${fileName}-${mode}-${direction}.png`;
+ if (theme === 'light') {
+ /**
+ * Ionic has many existing tests that existed prior to
+ * the introduction of theme testing. To maintain backwards
+ * compatibility, we will not include the theme in the screenshot
+ * name if the theme is set to light.
+ */
+ return `${fileName}-${mode}-${direction}.png`;
+ }
+
+ return `${fileName}-${mode}-${direction}-${theme}.png`;
};
/**
@@ -54,12 +83,15 @@ export const configs = (testConfig: TestConfigOption = DEFAULT_TEST_CONFIG_OPTIO
* If certain options are not provided,
* fall back to the defaults.
*/
- const processedMode: Mode[] = modes ?? DEFAULT_MODES;
- const processedDirection: Direction[] = directions ?? DEFAULT_DIRECTIONS;
+ const processedMode = modes ?? DEFAULT_MODES;
+ const processedDirection = directions ?? DEFAULT_DIRECTIONS;
+ const processedTheme = testConfig.themes ?? DEFAULT_THEMES;
- processedMode.forEach((mode: Mode) => {
- processedDirection.forEach((direction: Direction) => {
- configs.push({ mode, direction });
+ processedMode.forEach((mode) => {
+ processedDirection.forEach((direction) => {
+ processedTheme.forEach((theme) => {
+ configs.push({ mode, direction, theme });
+ });
});
});
@@ -74,6 +106,7 @@ export const configs = (testConfig: TestConfigOption = DEFAULT_TEST_CONFIG_OPTIO
const DEFAULT_MODES: Mode[] = ['ios', 'md'];
const DEFAULT_DIRECTIONS: Direction[] = ['ltr', 'rtl'];
+const DEFAULT_THEMES: Theme[] = ['light'];
const DEFAULT_TEST_CONFIG_OPTION = {
modes: DEFAULT_MODES,
diff --git a/core/src/utils/test/playwright/page/utils/set-content.ts b/core/src/utils/test/playwright/page/utils/set-content.ts
index 16fd1cc3b3..1f0e6b2a4a 100644
--- a/core/src/utils/test/playwright/page/utils/set-content.ts
+++ b/core/src/utils/test/playwright/page/utils/set-content.ts
@@ -1,5 +1,5 @@
import type { Page, TestInfo } from '@playwright/test';
-import type { E2EPageOptions, Mode, Direction } from '@utils/test/playwright';
+import type { E2EPageOptions, Mode, Direction, Theme } from '@utils/test/playwright';
/**
* Overwrites the default Playwright page.setContent method.
@@ -19,13 +19,16 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
let mode: Mode;
let direction: Direction;
+ let theme: Theme;
if (options == undefined) {
mode = testInfo.project.metadata.mode;
direction = testInfo.project.metadata.rtl ? 'rtl' : 'ltr';
+ theme = testInfo.project.metadata.theme;
} else {
mode = options.mode;
direction = options.direction;
+ theme = options.theme;
}
const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL;
@@ -39,6 +42,7 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
+ ${theme !== 'light' ? `` : ''}
diff --git a/packages/vue/test/base/src/components/NavRoot.vue b/packages/vue/test/base/src/components/NavRoot.vue
index ec2fe1c513..ffd18e6765 100644
--- a/packages/vue/test/base/src/components/NavRoot.vue
+++ b/packages/vue/test/base/src/components/NavRoot.vue
@@ -8,11 +8,14 @@
- Go to Nav Child
+
Message: {{ message }}
+ Go to Nav Child
-
diff --git a/packages/vue/test/base/tests/e2e/specs/navigation.cy.js b/packages/vue/test/base/tests/e2e/specs/navigation.cy.js
index d8f75e7163..25788bf759 100644
--- a/packages/vue/test/base/tests/e2e/specs/navigation.cy.js
+++ b/packages/vue/test/base/tests/e2e/specs/navigation.cy.js
@@ -10,4 +10,10 @@ describe('Navigation', () => {
cy.get('#nav-child-content').should('have.text', 'Custom Title');
});
+
+ it('nav should support kebab-case root-params', () => {
+ cy.get('#open-nav-modal').click();
+
+ cy.get('#nav-root-params').should('have.text', 'Message: Hello World!');
+ });
});
From 1705d064cc041e99f432a27207f3aab7fa62c778 Mon Sep 17 00:00:00 2001
From: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Date: Fri, 1 Dec 2023 17:11:41 -0500
Subject: [PATCH 08/11] fix(react): router creates new view instances of
parameterized routes (#28616)
Issue number: Resolves #26524
---------
## Definitions
**Parameterized routes**: A route that includes one or more variables in
the path segments, such as `/form/:index`.
## What is the current behavior?
When an application routes from a parameterized route, to an
intermediary route, to the same parameterized route, but with a
different value/url, Ionic's routing logic is incorrectly reusing the
view item from the first instance of the parameterized route instead of
calculating that the matched path is different. This results in the
wrong view item being recycled and rendered.
Another way of representing it:
- User navigates to `/form/0` which resolves `FormPage`
- User enters `0` into the form and submits the form
- User navigates to `/link`, which resolves `LinkPage`
- User navigates to `/form/1`, which resolves `FormPage`
- However, instead of creating a new instance of `FormPage` it is
reusing the instance of `FormPage` from `/form/0` which includes the
form having `0` in the input.
- The user now sees a "new view", but with cached data in the form.
This is not expected or desired.
## What is the new behavior?
- Ionic's routing logic will validate if the entering view item matches
the match route data before reusing it. This results in new instances of
the view item being constructed when using parameterized routes.
https://github.com/ionic-team/ionic-framework/assets/13732623/e7e3d03f-2848-4429-9f60-9074d0761e45
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
## Other information
Dev-build: `7.5.8-dev.11701383555.17254408`
---
.../src/ReactRouter/ReactRouterViewStack.tsx | 65 ++++++++++---------
.../src/ReactRouter/StackManager.tsx | 27 ++++----
.../src/ReactRouter/utils/matchPath.ts | 47 ++++++++++++++
.../test/base/src/pages/routing/Details.tsx | 7 +-
.../test/base/tests/e2e/specs/routing.cy.js | 35 ++++++++++
5 files changed, 131 insertions(+), 50 deletions(-)
create mode 100644 packages/react-router/src/ReactRouter/utils/matchPath.ts
diff --git a/packages/react-router/src/ReactRouter/ReactRouterViewStack.tsx b/packages/react-router/src/ReactRouter/ReactRouterViewStack.tsx
index fa9e002102..9203679020 100644
--- a/packages/react-router/src/ReactRouter/ReactRouterViewStack.tsx
+++ b/packages/react-router/src/ReactRouter/ReactRouterViewStack.tsx
@@ -1,7 +1,8 @@
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';
export class ReactRouterViewStack extends ViewStacks {
constructor() {
@@ -23,21 +24,16 @@ export class ReactRouterViewStack extends ViewStacks {
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,
+ match: matchPath({
+ pathname: routeInfo.pathname,
+ componentProps: reactElement.props,
+ }),
childProps: reactElement.props,
};
@@ -106,7 +102,7 @@ export class ReactRouterViewStack extends ViewStacks {
}
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
- const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, mustBeIonRoute);
+ const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, mustBeIonRoute);
return viewItem;
}
@@ -115,7 +111,10 @@ export class ReactRouterViewStack extends ViewStacks {
return viewItem;
}
- private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
+ /**
+ * Returns the matching view item and the match result for a given pathname.
+ */
+ private findViewItemByPath(pathname: string, outletId?: string, mustBeIonRoute?: boolean) {
let viewItem: ViewItem | undefined;
let match: ReturnType | undefined;
let viewStack: ViewItem[];
@@ -140,16 +139,24 @@ export class ReactRouterViewStack extends ViewStacks {
if (mustBeIonRoute && !v.ionRoute) {
return false;
}
- const matchProps = {
- exact: forceExact ? true : v.routeData.childProps.exact,
- path: v.routeData.childProps.path || v.routeData.childProps.from,
- component: v.routeData.childProps.component,
- };
- const myMatch = matchPath(pathname, matchProps);
- if (myMatch) {
- viewItem = v;
- match = myMatch;
- return true;
+
+ match = matchPath({
+ pathname,
+ componentProps: v.routeData.childProps,
+ });
+
+ if (match) {
+ /**
+ * Even though we have a match from react-router, we do not know if the match
+ * is for this specific view item.
+ *
+ * To validate this, we need to check if the path and url match the view item's route data.
+ */
+ const hasParameter = match.path.includes(':');
+ if (!hasParameter || (hasParameter && match.url === v.routeData?.match?.url)) {
+ viewItem = v;
+ return true;
+ }
}
return false;
}
@@ -171,13 +178,9 @@ export class ReactRouterViewStack extends ViewStacks {
}
}
-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;
+function matchComponent(node: React.ReactElement, pathname: string) {
+ return matchPath({
+ pathname,
+ componentProps: node.props,
+ });
}
diff --git a/packages/react-router/src/ReactRouter/StackManager.tsx b/packages/react-router/src/ReactRouter/StackManager.tsx
index 1acfd48468..708de65139 100644
--- a/packages/react-router/src/ReactRouter/StackManager.tsx
+++ b/packages/react-router/src/ReactRouter/StackManager.tsx
@@ -1,9 +1,9 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import React from 'react';
-import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
+import { matchPath } from './utils/matchPath';
// TODO(FW-2959): types
@@ -433,12 +433,10 @@ export default StackManager;
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
let matchedNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
- const matchProps = {
- exact: child.props.exact,
- path: child.props.path || child.props.from,
- component: child.props.component,
- };
- const match = matchPath(routeInfo.pathname, matchProps);
+ const match = matchPath({
+ pathname: routeInfo.pathname,
+ componentProps: child.props,
+ });
if (match) {
matchedNode = child;
}
@@ -459,12 +457,11 @@ function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
}
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 matchPath({
+ pathname,
+ componentProps: {
+ ...node.props,
+ exact: forceExact,
+ },
+ });
}
diff --git a/packages/react-router/src/ReactRouter/utils/matchPath.ts b/packages/react-router/src/ReactRouter/utils/matchPath.ts
new file mode 100644
index 0000000000..891eda08bb
--- /dev/null
+++ b/packages/react-router/src/ReactRouter/utils/matchPath.ts
@@ -0,0 +1,47 @@
+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 => {
+ 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;
+ }
+
+ return match;
+};
diff --git a/packages/react-router/test/base/src/pages/routing/Details.tsx b/packages/react-router/test/base/src/pages/routing/Details.tsx
index 17905911a4..94aeeb6dba 100644
--- a/packages/react-router/test/base/src/pages/routing/Details.tsx
+++ b/packages/react-router/test/base/src/pages/routing/Details.tsx
@@ -24,10 +24,6 @@ const Details: React.FC = () => {
return () => console.log('Home Details unmount');
}, []);
- // useIonViewWillEnter(() => {
- // console.log('IVWE Details')
- // })
-
const nextId = parseInt(id, 10) + 1;
return (
@@ -58,6 +54,9 @@ const Details: React.FC = () => {
Go to Settings Details 1
+
+
+
);
diff --git a/packages/react-router/test/base/tests/e2e/specs/routing.cy.js b/packages/react-router/test/base/tests/e2e/specs/routing.cy.js
index 950a27d157..1289bb3065 100644
--- a/packages/react-router/test/base/tests/e2e/specs/routing.cy.js
+++ b/packages/react-router/test/base/tests/e2e/specs/routing.cy.js
@@ -309,6 +309,41 @@ describe('Routing Tests', () => {
cy.ionPageDoesNotExist('home-details-page-1');
cy.ionPageVisible('home-page');
});
+
+ it('should mount new view item instances of parameterized routes', () => {
+ cy.visit(`http://localhost:${port}/routing/tabs/home/details/1`);
+
+ cy.get('div.ion-page[data-pageid=home-details-page-1]')
+ .get('[data-testid="details-input"]')
+ .should('have.value', '');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-1] [data-testid="details-input"]').type('1');
+
+ cy.ionNav('ion-button', 'Go to Details 2');
+ cy.ionPageVisible('home-details-page-2');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-2] [data-testid="details-input"]').should('have.value', '');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-2] [data-testid="details-input"]').type('2');
+
+ cy.ionNav('ion-button', 'Go to Details 3');
+ cy.ionPageVisible('home-details-page-3');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-3] [data-testid="details-input"]').should('have.value', '');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-3] [data-testid="details-input"]').type('3');
+
+ cy.ionBackClick('home-details-page-3');
+ cy.ionPageVisible('home-details-page-2');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-2] [data-testid="details-input"]').should('have.value', '2');
+
+ cy.ionBackClick('home-details-page-2');
+ cy.ionPageVisible('home-details-page-1');
+
+ cy.get('div.ion-page[data-pageid=home-details-page-1] [data-testid="details-input"]').should('have.value', '1');
+ });
+
/*
Tests to add:
Test that lifecycle events fire
From fe3c3d500a2afd716ebde81a75b357b7c79d4920 Mon Sep 17 00:00:00 2001
From: Shawn Taylor
Date: Mon, 4 Dec 2023 08:24:34 -0500
Subject: [PATCH 09/11] docs(input, searchbar, textarea): improve docs for
managing focus (#28614)
Issue number: Related to #18132
---------
## What is the current behavior?
The documentation about the `autofocus` prop is unclear and does not
accurately reflect how it actually works across browsers and devices.
## What is the new behavior?
- The documentation for `autofocus` and `setFocus` are more detailed.
- The documentation links to the relevant page in the docs.
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
## Other information
---
core/src/components.d.ts | 14 +++++++-------
core/src/components/input/input.tsx | 6 +++++-
core/src/components/searchbar/searchbar.tsx | 2 ++
core/src/components/textarea/textarea.tsx | 6 +++++-
4 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 14d14454de..885ff656c6 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -1162,7 +1162,7 @@ export namespace Components {
*/
"autocorrect": 'on' | 'off';
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus": boolean;
/**
@@ -1274,7 +1274,7 @@ export namespace Components {
*/
"required": boolean;
/**
- * Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
+ * Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise;
/**
@@ -2601,7 +2601,7 @@ export namespace Components {
*/
"searchIcon"?: string;
/**
- * Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
+ * Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise;
/**
@@ -2950,7 +2950,7 @@ export namespace Components {
*/
"autocapitalize": string;
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus": boolean;
/**
@@ -3050,7 +3050,7 @@ export namespace Components {
*/
"rows"?: number;
/**
- * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`.
+ * Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"setFocus": () => Promise;
/**
@@ -5854,7 +5854,7 @@ declare namespace LocalJSX {
*/
"autocorrect"?: 'on' | 'off';
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus"?: boolean;
/**
@@ -7699,7 +7699,7 @@ declare namespace LocalJSX {
*/
"autocapitalize"?: string;
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
"autofocus"?: boolean;
/**
diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx
index 1c54046718..92bf5fdeff 100644
--- a/core/src/components/input/input.tsx
+++ b/core/src/components/input/input.tsx
@@ -95,7 +95,9 @@ export class Input implements ComponentInterface {
@Prop() autocorrect: 'on' | 'off' = 'off';
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.
+ *
+ * This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Prop() autofocus = false;
@@ -424,6 +426,8 @@ export class Input implements ComponentInterface {
*
* Developers who wish to focus an input when an overlay is presented
* should call `setFocus` after `didPresent` has resolved.
+ *
+ * See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Method()
async setFocus() {
diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx
index 84ae9b2acc..21fed733d2 100644
--- a/core/src/components/searchbar/searchbar.tsx
+++ b/core/src/components/searchbar/searchbar.tsx
@@ -257,6 +257,8 @@ export class Searchbar implements ComponentInterface {
*
* Developers who wish to focus an input when an overlay is presented
* should call `setFocus` after `didPresent` has resolved.
+ *
+ * See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Method()
async setFocus() {
diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx
index 6159f64790..31ec5eb616 100644
--- a/core/src/components/textarea/textarea.tsx
+++ b/core/src/components/textarea/textarea.tsx
@@ -93,7 +93,9 @@ export class Textarea implements ComponentInterface {
@Prop() autocapitalize = 'none';
/**
- * This Boolean attribute lets you specify that a form control should have input focus when the page loads.
+ * Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.
+ *
+ * This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Prop() autofocus = false;
@@ -372,6 +374,8 @@ export class Textarea implements ComponentInterface {
/**
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global
* `textarea.focus()`.
+ *
+ * See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Method()
async setFocus() {
From 48474880903774d4e3efcb332a95d4c5ea470ea9 Mon Sep 17 00:00:00 2001
From: Liam DeBeasi
Date: Mon, 4 Dec 2023 16:31:12 -0500
Subject: [PATCH 10/11] chore(ci): update labeler workflow for labeler@v5
(#28627)
Issue number: N/A
---------
## What is the current behavior?
The labeler action is currently failing:
https://github.com/ionic-team/ionic-framework/actions/runs/7090913880/job/19298918578?pr=28622.
This is happening due to a breaking change in v5 of the action.
We currently pull from `main` for this action so we are now receiving v5
of the action:
https://github.com/ionic-team/ionic-framework/blob/fe3c3d500a2afd716ebde81a75b357b7c79d4920/.github/workflows/label.yml#L16
## What is the new behavior?
- This PR updates the action explicitly to v5 so we don't unexpectedly
take on breaking changes
- This PR also updates the labeler yaml file to account for the v5
breaking changes
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
## Other information
---
.github/labeler.yml | 15 ++++++++-------
.github/workflows/label.yml | 2 +-
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 545025db58..7ea70aaf7f 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -6,16 +6,17 @@
# https://github.com/actions/labeler
'package: core':
- - core/**/*
+- changed-files:
+ - any-glob-to-any-file: ['core/**/*']
'package: angular':
- - packages/angular/**/*
- - packages/angular-*/**/*
+- changed-files:
+ - any-glob-to-any-file: ['packages/angular/**/*', 'packages/angular-*/**/*']
'package: react':
- - packages/react/**/*
- - packages/react-*/**/*
+- changed-files:
+ - any-glob-to-any-file: ['packages/react/**/*', 'packages/react-*/**/*']
'package: vue':
- - packages/vue/**/*
- - packages/vue-*/**/*
+- changed-files:
+ - any-glob-to-any-file: ['packages/vue/**/*', 'packages/vue-*/**/*']
diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml
index 0241c7fa5b..a4e35060df 100644
--- a/.github/workflows/label.yml
+++ b/.github/workflows/label.yml
@@ -13,7 +13,7 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@main
+ - uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
From e2cbc9c15fbc1c24993267cb0acb56db69cac8ac Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Dec 2023 19:58:11 -0500
Subject: [PATCH 11/11] chore(deps): Bump @stencil/core from 4.8.0 to 4.8.1 in
/core (#28632)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [@stencil/core](https://github.com/ionic-team/stencil) from 4.8.0
to 4.8.1.
Release notes
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
core/package-lock.json | 14 +++++++-------
core/package.json | 2 +-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/core/package-lock.json b/core/package-lock.json
index 08c47b5014..256d5876d6 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -9,7 +9,7 @@
"version": "7.5.7",
"license": "MIT",
"dependencies": {
- "@stencil/core": "^4.8.0",
+ "@stencil/core": "^4.8.1",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},
@@ -1825,9 +1825,9 @@
}
},
"node_modules/@stencil/core": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.0.tgz",
- "integrity": "sha512-iNEaMiEt9oFZXSjZ7qkdlBpPTzWzBgWM7go8nI8a7V6ZOkmdVhhT0xGQD7OY13v5ZuDoIw5IsGvbIAGefhIhhQ==",
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.1.tgz",
+ "integrity": "sha512-KG1H10j24rlyxIqOI4CG8/h9T7ObTv7giW2H3u1qXV4KKrLykDOpMcLzpqNXqL2Fki3s1QvHyl/oaRvi5waWVw==",
"bin": {
"stencil": "bin/stencil"
},
@@ -12184,9 +12184,9 @@
"requires": {}
},
"@stencil/core": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.0.tgz",
- "integrity": "sha512-iNEaMiEt9oFZXSjZ7qkdlBpPTzWzBgWM7go8nI8a7V6ZOkmdVhhT0xGQD7OY13v5ZuDoIw5IsGvbIAGefhIhhQ=="
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.8.1.tgz",
+ "integrity": "sha512-KG1H10j24rlyxIqOI4CG8/h9T7ObTv7giW2H3u1qXV4KKrLykDOpMcLzpqNXqL2Fki3s1QvHyl/oaRvi5waWVw=="
},
"@stencil/react-output-target": {
"version": "0.5.3",
diff --git a/core/package.json b/core/package.json
index df64eb1499..79e803f515 100644
--- a/core/package.json
+++ b/core/package.json
@@ -31,7 +31,7 @@
"loader/"
],
"dependencies": {
- "@stencil/core": "^4.8.0",
+ "@stencil/core": "^4.8.1",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},