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 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" }, 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/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index 813e38c7f9..984e24cdaa 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -337,6 +337,17 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { if (this.isOpen === true) { raf(() => this.present()); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } render() { diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 4568e45a38..90a1c281bb 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -376,6 +376,17 @@ export class Alert implements ComponentInterface, OverlayInterface { if (this.isOpen === true) { raf(() => this.present()); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } /** 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/loading/loading.tsx b/core/src/components/loading/loading.tsx index 0892b082d1..05e40669d9 100644 --- a/core/src/components/loading/loading.tsx +++ b/core/src/components/loading/loading.tsx @@ -225,6 +225,17 @@ export class Loading implements ComponentInterface, OverlayInterface { if (this.isOpen === true) { raf(() => this.present()); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } disconnectedCallback() { diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 3c8a091901..ef6114b18b 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -368,6 +368,17 @@ export class Modal implements ComponentInterface, OverlayInterface { raf(() => this.present()); } this.breakpointsChanged(this.breakpoints); + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } /** diff --git a/core/src/components/picker/picker.tsx b/core/src/components/picker/picker.tsx index 19d03d2f8b..4232288919 100644 --- a/core/src/components/picker/picker.tsx +++ b/core/src/components/picker/picker.tsx @@ -209,6 +209,17 @@ export class Picker implements ComponentInterface, OverlayInterface { if (this.isOpen === true) { raf(() => this.present()); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } /** diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 75e16ad70d..21dce20a5d 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -370,6 +370,17 @@ export class Popover implements ComponentInterface, PopoverInterface { this.dismiss(undefined, undefined, false); }); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.configureTriggerInteraction(); } /** 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() { diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index b7945400b5..0a4fc420fc 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -288,6 +288,17 @@ export class Toast implements ComponentInterface, OverlayInterface { if (this.isOpen === true) { raf(() => this.present()); } + + /** + * When binding values in frameworks such as Angular + * it is possible for the value to be set after the Web Component + * initializes but before the value watcher is set up in Stencil. + * As a result, the watcher callback may not be fired. + * We work around this by manually calling the watcher + * callback when the component has loaded and the watcher + * is configured. + */ + this.triggerChanged(); } /** 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 diff --git a/packages/vue/src/components/IonNav.ts b/packages/vue/src/components/IonNav.ts index 437734d159..47124a19ff 100644 --- a/packages/vue/src/components/IonNav.ts +++ b/packages/vue/src/components/IonNav.ts @@ -4,7 +4,7 @@ import { defineComponent, h, shallowRef } from "vue"; import { VueDelegate } from "../framework-delegate"; -export const IonNav = /*@__PURE__*/ defineComponent(() => { +export const IonNav = /*@__PURE__*/ defineComponent((props) => { defineCustomElement(); const views = shallowRef([]); @@ -19,8 +19,39 @@ export const IonNav = /*@__PURE__*/ defineComponent(() => { const delegate = VueDelegate(addView, removeView); return () => { - return h("ion-nav", { delegate }, views.value); + return h("ion-nav", { ...props, delegate }, views.value); }; }); IonNav.name = "IonNav"; + +/** + * The default values follow what is defined at + * https://ionicframework.com/docs/api/nav#properties + * otherwise the default values on the Web Component + * may be overridden. For example, if the default animated value + * is not `true` below, then Vue would default the prop to `false` + * which would override the Web Component default of `true`. + */ +IonNav.props = { + animated: { + type: Boolean, + default: true, + }, + animation: { + type: Function, + default: undefined, + }, + root: { + type: [Function, Object, String], + default: undefined, + }, + rootParams: { + type: Object, + default: undefined, + }, + swipeGesture: { + type: Boolean, + default: undefined, + }, +}; diff --git a/packages/vue/test/base/src/components/Nav.vue b/packages/vue/test/base/src/components/Nav.vue index f4ca025094..69dc6d7175 100644 --- a/packages/vue/test/base/src/components/Nav.vue +++ b/packages/vue/test/base/src/components/Nav.vue @@ -1,16 +1,12 @@ - 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 + + 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!'); + }); });