diff --git a/CHANGELOG.md b/CHANGELOG.md index 49895d7f11..875825ea57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + + +### Bug Fixes + +* **tabs, tab-bar:** use standalone tab bar in Vue, React ([#29940](https://github.com/ionic-team/ionic-framework/issues/29940)) ([b7b383b](https://github.com/ionic-team/ionic-framework/commit/b7b383bee080b72de2e6307ff9a9a051314c69ed)), closes [#29885](https://github.com/ionic-team/ionic-framework/issues/29885) [#29924](https://github.com/ionic-team/ionic-framework/issues/29924) + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + + +### Bug Fixes + +* **segment:** prevent flickering for scrollable on iOS ([#29884](https://github.com/ionic-team/ionic-framework/issues/29884)) ([078ed0b](https://github.com/ionic-team/ionic-framework/commit/078ed0b86a0d8e9f8457481cb739ea214195adce)), closes [#29523](https://github.com/ionic-team/ionic-framework/issues/29523) + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 72ef2d42d3..5cf7ee82a7 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/core + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + + +### Bug Fixes + +* **segment:** prevent flickering for scrollable on iOS ([#29884](https://github.com/ionic-team/ionic-framework/issues/29884)) ([078ed0b](https://github.com/ionic-team/ionic-framework/commit/078ed0b86a0d8e9f8457481cb739ea214195adce)), closes [#29523](https://github.com/ionic-team/ionic-framework/issues/29523) + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) diff --git a/core/api.txt b/core/api.txt index ea4944a2be..2f199c8533 100644 --- a/core/api.txt +++ b/core/api.txt @@ -181,6 +181,7 @@ ion-alert,css-prop,--width,md ion-app,none ion-app,prop,mode,"ios" | "md",undefined,false,false ion-app,prop,theme,"ios" | "md" | "ionic",undefined,false,false +ion-app,method,setFocus,setFocus(elements: HTMLElement[]) => Promise ion-avatar,shadow ion-avatar,prop,mode,"ios" | "md",undefined,false,false diff --git a/core/package-lock.json b/core/package-lock.json index 4df3be208e..cb9cbf4f5e 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.3.1", - "lockfileVersion": 3, + "version": "8.3.3", + "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { "@phosphor-icons/core": "^2.1.1", diff --git a/core/package.json b/core/package.json index 252f2f03e4..486d8662e0 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.3.1", + "version": "8.3.3", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/core/scripts/testing/styles.css b/core/scripts/testing/styles.css index 5ffd01e566..58f6f6e363 100644 --- a/core/scripts/testing/styles.css +++ b/core/scripts/testing/styles.css @@ -53,8 +53,19 @@ html.ios.ios { --ionic-global-background-color: var(--background); } -ion-content button, -main button { +/** + * Button styles should only be applied + * to native buttons that are not part of the + * Ionic framework. + * Otherwise, the styles may not appear correctly + * when comparing between testing and production. + * This issue occurs only with `scoped` components, + * which is why `sc-ion-` is used as a filter, + * since this class is specifically added to `scoped` + * components. + */ +ion-content button:not([class*="sc-ion-"]), +main button:not([class*="sc-ion-"]) { display: inline-block; width: auto; clear: both; @@ -67,8 +78,19 @@ main button { margin: 8px 0; } -ion-content button.expand, -main button.expand { +/** + * Button styles should only be applied + * to native buttons that are not part of the + * Ionic framework. + * Otherwise, the styles may not appear correctly + * when comparing between testing and production. + * This issue occurs only with `scoped` components, + * which is why `sc-ion-` is used as a filter, + * since this class is specifically added to `scoped` + * components. + */ +ion-content button.expand:not([class*="sc-ion-"]), +main button.expand:not([class*="sc-ion-"]) { display: block; width: 100%; } diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 77444d293f..b0f4ab5dc5 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -324,6 +324,9 @@ export namespace Components { * The mode determines the platform behaviors of the component. */ "mode"?: "ios" | "md"; + /** + * Used to set focus on an element that uses `ion-focusable`. Do not use this if focusing the element as a result of a keyboard event as the focus utility should handle this for us. This method should be used when we want to programmatically focus an element as a result of another user action. (Ex: We focus the first element inside of a popover when the user presents it, but the popover is not always presented as a result of keyboard action.) + */ "setFocus": (elements: HTMLElement[]) => Promise; /** * The theme determines the visual appearance of the component. diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index dfbbfc45a9..756f010bdc 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -17,7 +17,6 @@ export class App implements ComponentInterface { @Element() el!: HTMLElement; /** - * @internal * Used to set focus on an element that uses `ion-focusable`. * Do not use this if focusing the element as a result of a keyboard * event as the focus utility should handle this for us. This method diff --git a/core/src/components/popover/test/size/index.html b/core/src/components/popover/test/size/index.html index 9d473b130c..1ce48a75cb 100644 --- a/core/src/components/popover/test/size/index.html +++ b/core/src/components/popover/test/size/index.html @@ -16,7 +16,7 @@ ion-app > ion-content { --background: #dddddd; } - ion-content button { + ion-content button.trigger { padding: 12px 16px; } .grid { @@ -57,26 +57,30 @@

Cover

- + My really really really really long content

With Event

- +

Auto

- + My really really really really long content

No Event

- +
diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index 35f74efe71..14e854953c 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -344,21 +344,35 @@ export class Segment implements ComponentInterface { const centeredX = activeButtonLeft - scrollContainerBox.width / 2 + activeButtonBox.width / 2; /** - * We intentionally use scrollBy here instead of scrollIntoView + * newScrollPosition is the absolute scroll position that the + * container needs to move to in order to center the active button. + * It is calculated by adding the current scroll position + * (scrollLeft) to the offset needed to center the button + * (centeredX). + */ + const newScrollPosition = el.scrollLeft + centeredX; + + /** + * We intentionally use scrollTo here instead of scrollIntoView * to avoid a WebKit bug where accelerated animations break * when using scrollIntoView. Using scrollIntoView will cause the * segment container to jump during the transition and then snap into place. * This is because scrollIntoView can potentially cause parent element - * containers to also scroll. scrollBy does not have this same behavior, so + * containers to also scroll. scrollTo does not have this same behavior, so * we use this API instead. * + * scrollTo is used instead of scrollBy because there is a + * Webkit bug that causes scrollBy to not work smoothly when + * the active button is near the edge of the scroll container. + * This leads to the buttons to jump around during the transition. + * * Note that if there is not enough scrolling space to center the element * within the scroll container, the browser will attempt * to center by as much as it can. */ - el.scrollBy({ + el.scrollTo({ top: 0, - left: centeredX, + left: newScrollPosition, behavior: smoothScroll ? 'smooth' : 'instant', }); } diff --git a/lerna.json b/lerna.json index ba40926c95..aaecbf98e0 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.3.1" + "version": "8.3.3" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index bc377c703c..87f068694a 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/angular-server + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index 5021c63f68..cec18a9842 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular-server", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.1" + "@ionic/core": "^8.3.3" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -1031,9 +1031,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -7188,9 +7188,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 81048e1d7c..4b84cac006 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.3.1", + "version": "8.3.3", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.3.1" + "@ionic/core": "^8.3.3" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 60acb6f8ff..517488505d 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/angular + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/angular + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index 1119059fe1..7f71a33aa6 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -1398,9 +1398,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -9820,9 +9820,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/angular/package.json b/packages/angular/package.json index f4f0cca8b0..f2c927b098 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.3.1", + "version": "8.3.3", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -47,7 +47,7 @@ } }, "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index e38fa10e0e..42d1cdd624 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -188,7 +188,8 @@ Shorthand for ionAlertDidDismiss. @ProxyCmp({ - inputs: ['mode', 'theme'] + inputs: ['mode', 'theme'], + methods: ['setFocus'] }) @Component({ selector: 'ion-app', diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index 0f97609f21..842f7879f7 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -267,7 +267,8 @@ Shorthand for ionAlertDidDismiss. @ProxyCmp({ defineCustomElementFn: defineIonApp, - inputs: ['mode', 'theme'] + inputs: ['mode', 'theme'], + methods: ['setFocus'] }) @Component({ selector: 'ion-app', diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index 5a4a4ef748..bf807426dd 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/docs + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index 33cd632156..a1d2d61aae 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/docs", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index 3078b88d31..9faa6071e6 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.3.1", + "version": "8.3.3", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 2dbfce6426..8969ab83e6 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/react-router + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 6557363294..53ec60cc26 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/react": "^8.3.1", + "@ionic/react": "^8.3.3", "tslib": "*" }, "devDependencies": { @@ -238,9 +238,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -414,11 +414,11 @@ } }, "node_modules/@ionic/react": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.1.tgz", - "integrity": "sha512-5P/EFtJsgXFi505TmhIVIASomLWxUFf1KilCiH9/AZlLvEXFIT5x9o6L061+j+An4j0uR1MbDh/DwnrTYiO0NA==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.3.tgz", + "integrity": "sha512-BQVke+4QF1viPmwYFV/Bfseh4AhLnA0svP8UvKTP45plJ2KDXF/IbFVNn+FWtjByrqYU4PldUgF01+O4yPGiRw==", "dependencies": { - "@ionic/core": "8.3.1", + "@ionic/core": "8.3.3", "ionicons": "^7.0.0", "tslib": "*" }, @@ -4057,9 +4057,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -4163,11 +4163,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.1.tgz", - "integrity": "sha512-5P/EFtJsgXFi505TmhIVIASomLWxUFf1KilCiH9/AZlLvEXFIT5x9o6L061+j+An4j0uR1MbDh/DwnrTYiO0NA==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.3.tgz", + "integrity": "sha512-BQVke+4QF1viPmwYFV/Bfseh4AhLnA0svP8UvKTP45plJ2KDXF/IbFVNn+FWtjByrqYU4PldUgF01+O4yPGiRw==", "requires": { - "@ionic/core": "8.3.1", + "@ionic/core": "8.3.3", "ionicons": "^7.0.0", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 436f770395..4e14633138 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.3.1", + "version": "8.3.3", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.3.1", + "@ionic/react": "^8.3.3", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 75cf831b7c..dc28f6a1d9 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + + +### Bug Fixes + +* **tabs, tab-bar:** use standalone tab bar in Vue, React ([#29940](https://github.com/ionic-team/ionic-framework/issues/29940)) ([b7b383b](https://github.com/ionic-team/ionic-framework/commit/b7b383bee080b72de2e6307ff9a9a051314c69ed)), closes [#29885](https://github.com/ionic-team/ionic-framework/issues/29885) [#29924](https://github.com/ionic-team/ionic-framework/issues/29924) + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/react + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 36b8551ffc..6e176a5e27 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0", "tslib": "*" }, @@ -736,9 +736,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -12315,9 +12315,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/react/package.json b/packages/react/package.json index 28be56e310..72ce6e4adc 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.3.1", + "version": "8.3.3", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -39,7 +39,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0", "tslib": "*" }, diff --git a/packages/react/src/components/navigation/IonTabBar.tsx b/packages/react/src/components/navigation/IonTabBar.tsx index f1a66440fc..92fde774dd 100644 --- a/packages/react/src/components/navigation/IonTabBar.tsx +++ b/packages/react/src/components/navigation/IonTabBar.tsx @@ -8,6 +8,8 @@ import { IonTabBarInner } from '../inner-proxies'; import { createForwardRef } from '../utils'; import { IonTabButton } from './IonTabButton'; +import { IonTabsContext } from './IonTabsContext'; +import type { IonTabsContextState } from './IonTabsContext'; type IonTabBarProps = LocalJSX.IonTabBar & IonicReactProps & { @@ -21,7 +23,7 @@ interface InternalProps extends IonTabBarProps { forwardedRef?: React.ForwardedRef; onSetCurrentTab: (tab: string, routeInfo: RouteInfo) => void; routeInfo: RouteInfo; - routerOutletRef?: React.RefObject | undefined; + tabsContext?: IonTabsContextState; } interface TabUrls { @@ -183,12 +185,14 @@ class IonTabBarUnwrapped extends React.PureComponent = React.memo(({ forwardedRef, ...props }) => { const context = useContext(NavContext); + const tabsContext = useContext(IonTabsContext); + const tabBarRef = forwardedRef || tabsContext.tabBarProps.ref; + const updatedTabBarProps = { + ...tabsContext.tabBarProps, + ref: tabBarRef, + }; + return ( {props.children} diff --git a/packages/react/src/components/navigation/IonTabs.tsx b/packages/react/src/components/navigation/IonTabs.tsx index e80e09ac15..a7a8a250bf 100644 --- a/packages/react/src/components/navigation/IonTabs.tsx +++ b/packages/react/src/components/navigation/IonTabs.tsx @@ -8,7 +8,6 @@ import { IonRouterOutlet } from '../IonRouterOutlet'; import { IonTabsInner } from '../inner-proxies'; import { IonTab } from '../proxies'; -import { IonTabBar } from './IonTabBar'; import type { IonTabsContextState } from './IonTabsContext'; import { IonTabsContext } from './IonTabsContext'; @@ -43,28 +42,15 @@ interface Props extends LocalJSX.IonTabs { children: ChildFunction | React.ReactNode; } -const hostStyles: React.CSSProperties = { - display: 'flex', - position: 'absolute', - top: '0', - left: '0', - right: '0', - bottom: '0', - flexDirection: 'column', - width: '100%', - height: '100%', - contain: 'layout size style', -}; - -const tabsInner: React.CSSProperties = { - position: 'relative', - flex: 1, - contain: 'layout size style', -}; - export const IonTabs = /*@__PURE__*/ (() => class extends React.Component { context!: React.ContextType; + /** + * `routerOutletRef` allows users to add a `ref` to `IonRouterOutlet`. + * Without this, `ref.current` will be `undefined` in the user's app, + * breaking their ability to access the `IonRouterOutlet` instance. + * Do not remove this ref. + */ routerOutletRef: React.Ref = React.createRef(); selectTabHandler?: (tag: string) => boolean; tabBarRef = React.createRef(); @@ -72,6 +58,14 @@ export const IonTabs = /*@__PURE__*/ (() => ionTabContextState: IonTabsContextState = { activeTab: undefined, selectTab: () => false, + hasRouterOutlet: false, + /** + * Tab bar can be used as a standalone component, + * so the props can not be passed directly to the + * tab bar component. Instead, props will be + * passed through the context. + */ + tabBarProps: { ref: this.tabBarRef }, }; constructor(props: Props) { @@ -90,9 +84,32 @@ export const IonTabs = /*@__PURE__*/ (() => } } + renderTabsInner(children: React.ReactNode, outlet: React.ReactElement<{}> | undefined) { + return ( + + {React.Children.map(children, (child: React.ReactNode) => { + if (React.isValidElement(child)) { + const isRouterOutlet = + child.type === IonRouterOutlet || + (child.type as any).isRouterOutlet || + (child.type === Fragment && child.props.children[0].type === IonRouterOutlet); + + if (isRouterOutlet) { + /** + * The modified outlet needs to be returned to include + * the ref. + */ + return outlet; + } + } + return child; + })} + + ); + } + render() { let outlet: React.ReactElement<{}> | undefined; - let tabBar: React.ReactElement | undefined; // Check if IonTabs has any IonTab children let hasTab = false; const { className, onIonTabsDidChange, onIonTabsWillChange, ...props } = this.props; @@ -102,19 +119,15 @@ export const IonTabs = /*@__PURE__*/ (() => ? (this.props.children as ChildFunction)(this.ionTabContextState) : this.props.children; - const outletProps = { - ref: this.routerOutletRef, - }; - React.Children.forEach(children, (child: any) => { // eslint-disable-next-line no-prototype-builtins if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) { return; } if (child.type === IonRouterOutlet || child.type.isRouterOutlet) { - outlet = React.cloneElement(child, outletProps); + outlet = React.cloneElement(child); } else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) { - outlet = React.cloneElement(child.props.children[0], outletProps); + outlet = React.cloneElement(child.props.children[0]); } else if (child.type === IonTab) { /** * This indicates that IonTabs will be using a basic tab-based navigation @@ -123,9 +136,10 @@ export const IonTabs = /*@__PURE__*/ (() => hasTab = true; } + this.ionTabContextState.hasRouterOutlet = !!outlet; + let childProps: any = { - ref: this.tabBarRef, - routerOutletRef: this.routerOutletRef, + ...this.ionTabContextState.tabBarProps, }; /** @@ -149,14 +163,7 @@ export const IonTabs = /*@__PURE__*/ (() => }; } - if (child.type === IonTabBar || child.type.isTabBar) { - tabBar = React.cloneElement(child, childProps); - } else if ( - child.type === Fragment && - (child.props.children[1].type === IonTabBar || child.props.children[1].type.isTabBar) - ) { - tabBar = React.cloneElement(child.props.children[1], childProps); - } + this.ionTabContextState.tabBarProps = childProps; }); if (!outlet && !hasTab) { @@ -186,46 +193,10 @@ export const IonTabs = /*@__PURE__*/ (() => {this.context.hasIonicRouter() ? ( - - {React.Children.map(children, (child: React.ReactNode) => { - if (React.isValidElement(child)) { - const isTabBar = - child.type === IonTabBar || - (child.type as any).isTabBar || - (child.type === Fragment && - (child.props.children[1].type === IonTabBar || child.props.children[1].type.isTabBar)); - const isRouterOutlet = - child.type === IonRouterOutlet || - (child.type as any).isRouterOutlet || - (child.type === Fragment && child.props.children[0].type === IonRouterOutlet); - - if (isTabBar) { - /** - * The modified tabBar needs to be returned to include - * the context and the overridden methods. - */ - return tabBar; - } - if (isRouterOutlet) { - /** - * The modified outlet needs to be returned to include - * the ref. - */ - return outlet; - } - } - return child; - })} - + {this.renderTabsInner(children, outlet)} ) : ( -
- {tabBar?.props.slot === 'top' ? tabBar : null} -
- {outlet} -
- {tabBar?.props.slot === 'bottom' ? tabBar : null} -
+ this.renderTabsInner(children, outlet) )}
); diff --git a/packages/react/src/components/navigation/IonTabsContext.tsx b/packages/react/src/components/navigation/IonTabsContext.tsx index e7f9ba2b10..12686bd2ad 100644 --- a/packages/react/src/components/navigation/IonTabsContext.tsx +++ b/packages/react/src/components/navigation/IonTabsContext.tsx @@ -3,9 +3,25 @@ import React from 'react'; export interface IonTabsContextState { activeTab: string | undefined; selectTab: (tab: string) => boolean; + hasRouterOutlet: boolean; + tabBarProps: TabBarProps; } +/** + * Tab bar can be used as a standalone component, + * so the props can not be passed directly to the + * tab bar component. Instead, props will be + * passed through the context. + */ +type TabBarProps = { + ref: React.RefObject; + onIonTabsWillChange?: (e: CustomEvent) => void; + onIonTabsDidChange?: (e: CustomEvent) => void; +}; + export const IonTabsContext = React.createContext({ activeTab: undefined, selectTab: () => false, + hasRouterOutlet: false, + tabBarProps: { ref: React.createRef() }, }); diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index 258c04cbb4..fc02c3c19a 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + +**Note:** Version bump only for package @ionic/vue-router + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 9784d0cf70..2a45b55ebf 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.3.1" + "@ionic/vue": "^8.3.3" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -661,9 +661,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -852,11 +852,11 @@ } }, "node_modules/@ionic/vue": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.1.tgz", - "integrity": "sha512-UWOVuibeHY4xjWl2Sh93FYiXLBZgAVXoh8ObskV93plm36iS611gpHqw47lBeR29uHwZAohfQVT0WLN6GWQ3JQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.3.tgz", + "integrity": "sha512-6EAPWdmQDvazP4ZsCRjWlG91Kx+1vYxX1gHx02Xvcc7Dn8hRrMW58IAAQ0BqSYsrHNFTKVtwD3cJbmnut8Ghyg==", "dependencies": { - "@ionic/core": "8.3.1", + "@ionic/core": "8.3.3", "ionicons": "^7.0.0" } }, @@ -7878,9 +7878,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -7993,11 +7993,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.1.tgz", - "integrity": "sha512-UWOVuibeHY4xjWl2Sh93FYiXLBZgAVXoh8ObskV93plm36iS611gpHqw47lBeR29uHwZAohfQVT0WLN6GWQ3JQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.3.tgz", + "integrity": "sha512-6EAPWdmQDvazP4ZsCRjWlG91Kx+1vYxX1gHx02Xvcc7Dn8hRrMW58IAAQ0BqSYsrHNFTKVtwD3cJbmnut8Ghyg==", "requires": { - "@ionic/core": "8.3.1", + "@ionic/core": "8.3.3", "ionicons": "^7.0.0" } }, diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index a015e83d7e..a8fe48ac36 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.3.1", + "version": "8.3.3", "description": "Vue Router integration for @ionic/vue", "scripts": { "test.spec": "jest", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic-framework#readme", "dependencies": { - "@ionic/vue": "^8.3.1" + "@ionic/vue": "^8.3.3" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 255993a026..ae7db1feb0 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16) + + +### Bug Fixes + +* **tabs, tab-bar:** use standalone tab bar in Vue, React ([#29940](https://github.com/ionic-team/ionic-framework/issues/29940)) ([b7b383b](https://github.com/ionic-team/ionic-framework/commit/b7b383bee080b72de2e6307ff9a9a051314c69ed)), closes [#29885](https://github.com/ionic-team/ionic-framework/issues/29885) [#29924](https://github.com/ionic-team/ionic-framework/issues/29924) + + + + + +## [8.3.2](https://github.com/ionic-team/ionic-framework/compare/v8.3.1...v8.3.2) (2024-10-02) + +**Note:** Version bump only for package @ionic/vue + + + + + ## [8.3.1](https://github.com/ionic-team/ionic-framework/compare/v8.3.0...v8.3.1) (2024-09-17) diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index 7f87e9f282..9a5b0bfcef 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.3.1", + "version": "8.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.3.1", + "version": "8.3.3", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0" }, "devDependencies": { @@ -208,9 +208,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -3970,9 +3970,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.1.tgz", - "integrity": "sha512-md4JFwKYLgN/YP+uzoTE5H7ah0W5SQQNZ1cJOQtxhv0ytCCHHaXJrfRVzefdy8iy8NdzL9s6EV5ZTKYH98E+ZQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/vue/package.json b/packages/vue/package.json index e6b675a486..d5a6d14983 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.3.1", + "version": "8.3.3", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -66,7 +66,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.3.1", + "@ionic/core": "^8.3.3", "ionicons": "^7.0.0" }, "vetur": { diff --git a/packages/vue/src/components/IonTabBar.ts b/packages/vue/src/components/IonTabBar.ts index 24e8134540..714891c188 100644 --- a/packages/vue/src/components/IonTabBar.ts +++ b/packages/vue/src/components/IonTabBar.ts @@ -1,5 +1,5 @@ import { defineCustomElement } from "@ionic/core/components/ion-tab-bar.js"; -import type { VNode } from "vue"; +import type { VNode, Ref } from "vue"; import { h, defineComponent, getCurrentInstance, inject } from "vue"; // TODO(FW-2969): types @@ -16,6 +16,12 @@ interface Tab { ref: VNode; } +interface TabBarData { + hasRouterOutlet: boolean; + _tabsWillChange: Function; + _tabsDidChange: Function; +} + const isTabButton = (child: any) => child.type?.name === "IonTabButton"; const getTabs = (nodes: VNode[]) => { @@ -34,20 +40,23 @@ const getTabs = (nodes: VNode[]) => { export const IonTabBar = defineComponent({ name: "IonTabBar", - props: { - /* eslint-disable @typescript-eslint/no-empty-function */ - _tabsWillChange: { type: Function, default: () => {} }, - _tabsDidChange: { type: Function, default: () => {} }, - _hasRouterOutlet: { type: Boolean, default: false }, - /* eslint-enable @typescript-eslint/no-empty-function */ - }, data() { return { tabState: { activeTab: undefined, tabs: {}, + /** + * Passing this prop to each tab button + * lets it be aware of the presence of + * the router outlet. + */ + hasRouterOutlet: false, }, tabVnodes: [], + /* eslint-disable @typescript-eslint/no-empty-function */ + _tabsWillChange: { type: Function, default: () => {} }, + _tabsDidChange: { type: Function, default: () => {} }, + /* eslint-enable @typescript-eslint/no-empty-function */ }; }, updated() { @@ -55,7 +64,7 @@ export const IonTabBar = defineComponent({ }, methods: { setupTabState(ionRouter: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; /** * For each tab, we need to keep track of its * base href as well as any child page that @@ -75,13 +84,6 @@ export const IonTabBar = defineComponent({ ref: child, }; - /** - * Passing this prop to each tab button - * lets it be aware of the presence of - * the router outlet. - */ - tabState.hasRouterOutlet = hasRouterOutlet; - /** * Passing this prop to each tab button * lets it be aware of the state that @@ -126,7 +128,7 @@ export const IonTabBar = defineComponent({ * @param ionRouter */ checkActiveTab(ionRouter: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; const currentRoute = ionRouter?.getCurrentRouteInfo(); const childNodes = this.$data.tabVnodes; const { tabs, activeTab: prevActiveTab } = this.$data.tabState; @@ -216,7 +218,7 @@ export const IonTabBar = defineComponent({ this.tabSwitch(activeTab); }, tabSwitch(activeTab: string, ionRouter?: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; const childNodes = this.$data.tabVnodes; const { activeTab: prevActiveTab } = this.$data.tabState; const tabState = this.$data.tabState; @@ -227,7 +229,7 @@ export const IonTabBar = defineComponent({ const tabDidChange = activeTab !== prevActiveTab; if (tabBar) { if (activeChild) { - tabDidChange && this.$props._tabsWillChange(activeTab); + tabDidChange && this.$data._tabsWillChange(activeTab); if (hasRouterOutlet && ionRouter !== null) { ionRouter.handleSetCurrentTab(activeTab); @@ -235,7 +237,7 @@ export const IonTabBar = defineComponent({ tabBar.selectedTab = tabState.activeTab = activeTab; - tabDidChange && this.$props._tabsDidChange(activeTab); + tabDidChange && this.$data._tabsDidChange(activeTab); } else { /** * When going to a tab that does @@ -250,6 +252,17 @@ export const IonTabBar = defineComponent({ }, mounted() { const ionRouter: any = inject("navManager", null); + /** + * Tab bar can be used as a standalone component, + * so it cannot be modified directly through + * IonTabs. Instead, data will be passed through + * the provide/inject. + */ + const tabBarData = inject>("tabBarData"); + + this.$data.tabState.hasRouterOutlet = tabBarData.value.hasRouterOutlet; + this.$data._tabsWillChange = tabBarData.value._tabsWillChange; + this.$data._tabsDidChange = tabBarData.value._tabsDidChange; this.setupTabState(ionRouter); diff --git a/packages/vue/src/components/IonTabs.ts b/packages/vue/src/components/IonTabs.ts index 4fde919bc8..5adde3a331 100644 --- a/packages/vue/src/components/IonTabs.ts +++ b/packages/vue/src/components/IonTabs.ts @@ -1,6 +1,13 @@ import { defineCustomElement } from "@ionic/core/components/ion-tabs.js"; import type { VNode } from "vue"; -import { h, defineComponent, Fragment, isVNode } from "vue"; +import { + h, + defineComponent, + Fragment, + isVNode, + provide, + shallowRef, +} from "vue"; import { IonTab } from "../proxies"; @@ -9,6 +16,12 @@ const DID_CHANGE = "ionTabsDidChange"; // TODO(FW-2969): types +interface TabBarData { + hasRouterOutlet: boolean; + _tabsWillChange: Function; + _tabsDidChange: Function; +} + /** * Vue 3.2.38 fixed an issue where Web Component * names are respected using kebab case instead of pascal case. @@ -24,13 +37,6 @@ const isRouterOutlet = (node: VNode) => { ); }; -const isTabBar = (node: VNode) => { - return ( - node.type && - ((node.type as any).name === "IonTabBar" || node.type === "ion-tab-bar") - ); -}; - const isTab = (node: VNode): boolean => { // The `ion-tab` component was created with the `v-for` directive. if (node.type === Fragment) { @@ -49,7 +55,43 @@ const isTab = (node: VNode): boolean => { export const IonTabs = /*@__PURE__*/ defineComponent({ name: "IonTabs", emits: [WILL_CHANGE, DID_CHANGE], + data() { + return { + hasRouterOutlet: false, + }; + }, setup(props, { slots, emit }) { + const slottedContent: VNode[] | undefined = + slots.default && slots.default(); + let routerOutlet: VNode | undefined = undefined; + + if (slottedContent && slottedContent.length > 0) { + /** + * Developers must pass an ion-router-outlet + * inside of ion-tabs if they want to use + * the history stack or URL updates associated + * with the router. + */ + routerOutlet = slottedContent.find((child: VNode) => + isRouterOutlet(child) + ); + } + + /** + * Tab bar can be used as a standalone component, + * so it cannot be modified directly through + * IonTabs. Instead, data will be passed through + * the provide/inject. + */ + provide( + "tabBarData", + shallowRef({ + hasRouterOutlet: !!routerOutlet, + _tabsWillChange: (tab: string) => emit(WILL_CHANGE, { tab }), + _tabsDidChange: (tab: string) => emit(DID_CHANGE, { tab }), + }) + ); + return { props, slots, @@ -68,9 +110,10 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ defineCustomElement(); }, render() { - const { slots, emit, props } = this; - const slottedContent = slots.default && slots.default(); - let routerOutlet; + const { slots, props } = this; + const slottedContent: VNode[] | undefined = + slots.default && slots.default(); + let routerOutlet: VNode | undefined = undefined; let hasTab = false; if (slottedContent && slottedContent.length > 0) { @@ -78,7 +121,7 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ * Developers must pass an ion-router-outlet * inside of ion-tabs if they want to use * the history stack or URL updates associated - * wit the router. + * with the router. */ routerOutlet = slottedContent.find((child: VNode) => isRouterOutlet(child) @@ -103,30 +146,6 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ ); } - if (slottedContent && slottedContent.length > 0) { - const slottedTabBar = slottedContent.find((child: VNode) => - isTabBar(child) - ); - - if (slottedTabBar) { - if (!slottedTabBar.props) { - slottedTabBar.props = {}; - } - /** - * ionTabsWillChange and ionTabsDidChange are - * fired from `ion-tabs`, so we need to pass these down - * as props so they can fire when the active tab changes. - * TODO: We may want to move logic from the tab bar into here - * so we do not have code split across two components. - */ - slottedTabBar.props._tabsWillChange = (tab: string) => - emit(WILL_CHANGE, { tab }); - slottedTabBar.props._tabsDidChange = (tab: string) => - emit(DID_CHANGE, { tab }); - slottedTabBar.props._hasRouterOutlet = !!routerOutlet; - } - } - if (hasTab) { return h( "ion-tabs",