From f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9 Mon Sep 17 00:00:00 2001 From: OS-jacobbell <228905018+OS-jacobbell@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:27:28 -0600 Subject: [PATCH 01/11] fix(checkbox): show labels after page navigation (#31062) Issue number: resolves #31052 --------- ## What is the current behavior? After a page navigation, ion-checkbox's `onslotchange` event fires before the element's `textContent` has been updated. It is called again after `textContent` becomes readable on Safari, but is not called again after the `textContent` becomes readable on Chrome and Firefox. ## What is the new behavior? - Uses `MutationObserver` instead of `onslotchange` and fires specifically on character data changes. This ensures `hasLabelContent` is up to date. - `MutationObserver` does not fire on load, so `hasLabelContent` is initialized in `connectedCallback` ## Does this introduce a breaking change? - [ ] Yes - [X] No --- core/src/components/checkbox/checkbox.tsx | 68 ++++++++++++----------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index 8a25b9200d5..99cb13c474a 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -151,44 +151,54 @@ export class Checkbox implements ComponentInterface { connectedCallback() { const { el } = this; - // Watch for class changes to update validation state. if (Build.isBrowser && typeof MutationObserver !== 'undefined') { - this.validationObserver = new MutationObserver(() => { - const newIsInvalid = checkInvalidState(el); - if (this.isInvalid !== newIsInvalid) { - this.isInvalid = newIsInvalid; - /** - * Screen readers tend to announce changes - * to `aria-describedby` when the attribute - * is changed during a blur event for a - * native form control. - * However, the announcement can be spotty - * when using a non-native form control - * and `forceUpdate()`. - * This is due to `forceUpdate()` internally - * rescheduling the DOM update to a lower - * priority queue regardless if it's called - * inside a Promise or not, thus causing - * the screen reader to potentially miss the - * change. - * By using a State variable inside a Promise, - * it guarantees a re-render immediately at - * a higher priority. - */ - Promise.resolve().then(() => { - this.hintTextId = this.getHintTextId(); - }); + this.validationObserver = new MutationObserver((mutations) => { + // Watch for label content changes + if (mutations.some((mutation) => mutation.type === 'characterData' || mutation.type === 'childList')) { + this.hasLabelContent = this.el.textContent !== ''; + } + // Watch for class changes to update validation state. + if (mutations.some((mutation) => mutation.type === 'attributes' && mutation.target === el)) { + const newIsInvalid = checkInvalidState(el); + if (this.isInvalid !== newIsInvalid) { + this.isInvalid = newIsInvalid; + /** + * Screen readers tend to announce changes + * to `aria-describedby` when the attribute + * is changed during a blur event for a + * native form control. + * However, the announcement can be spotty + * when using a non-native form control + * and `forceUpdate()`. + * This is due to `forceUpdate()` internally + * rescheduling the DOM update to a lower + * priority queue regardless if it's called + * inside a Promise or not, thus causing + * the screen reader to potentially miss the + * change. + * By using a State variable inside a Promise, + * it guarantees a re-render immediately at + * a higher priority. + */ + Promise.resolve().then(() => { + this.hintTextId = this.getHintTextId(); + }); + } } }); this.validationObserver.observe(el, { attributes: true, attributeFilter: ['class'], + characterData: true, + childList: true, + subtree: true, }); } // Always set initial state this.isInvalid = checkInvalidState(el); + this.hasLabelContent = this.el.textContent !== ''; } componentWillLoad() { @@ -267,10 +277,6 @@ export class Checkbox implements ComponentInterface { ev.stopPropagation(); }; - private onSlotChange = () => { - this.hasLabelContent = this.el.textContent !== ''; - }; - private getHintTextId(): string | undefined { const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this; @@ -387,7 +393,7 @@ export class Checkbox implements ComponentInterface { id={this.inputLabelId} onClick={this.onDivLabelClick} > - + {this.renderHintText()}
From b431b4c94450b1176fa4e97792861b66da88741c Mon Sep 17 00:00:00 2001 From: ionitron Date: Wed, 15 Apr 2026 18:12:24 +0000 Subject: [PATCH 02/11] v8.8.4 --- CHANGELOG.md | 13 +++++++++++++ core/CHANGELOG.md | 13 +++++++++++++ core/package-lock.json | 6 +++--- core/package.json | 2 +- lerna.json | 2 +- packages/angular-server/CHANGELOG.md | 8 ++++++++ packages/angular-server/package-lock.json | 8 ++++---- packages/angular-server/package.json | 4 ++-- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package-lock.json | 8 ++++---- packages/angular/package.json | 4 ++-- packages/docs/CHANGELOG.md | 8 ++++++++ packages/docs/package-lock.json | 6 +++--- packages/docs/package.json | 2 +- packages/react-router/CHANGELOG.md | 8 ++++++++ packages/react-router/package-lock.json | 8 ++++---- packages/react-router/package.json | 4 ++-- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package-lock.json | 8 ++++---- packages/react/package.json | 4 ++-- packages/vue-router/CHANGELOG.md | 8 ++++++++ packages/vue-router/package-lock.json | 8 ++++---- packages/vue-router/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package-lock.json | 8 ++++---- packages/vue/package.json | 4 ++-- 26 files changed, 127 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fae4854106..84abbbee409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + + +### Bug Fixes + +* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052) +* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d)) +* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac)) + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 1fd2212efdf..ee45ffd629a 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + + +### Bug Fixes + +* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052) +* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d)) +* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac)) + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) diff --git a/core/package-lock.json b/core/package-lock.json index 01a06435345..8bef7f2f1f7 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.8.3", + "version": "8.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -9829,4 +9829,4 @@ } } } -} +} \ No newline at end of file diff --git a/core/package.json b/core/package.json index 47566399094..4321a90686f 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.8.3", + "version": "8.8.4", "description": "Base components for Ionic", "engines": { "node": ">= 16" diff --git a/lerna.json b/lerna.json index e79ac7702c2..ba3e4cc2dcf 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.8.3" + "version": "8.8.4" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 68ecf941df3..e92a2268e2e 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **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 eaa09666b5b..62015e76228 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.8.3", + "version": "8.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/core": "^8.8.3" + "@ionic/core": "^8.8.4" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -11289,4 +11289,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 6e7325b33e3..2af2f292161 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.8.3", + "version": "8.8.4", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.8.3" + "@ionic/core": "^8.8.4" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 66721281c95..0c73bb9366f 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/angular + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index 90b33a3e6b3..b9c31ace5a7 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.8.3", + "version": "8.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -9095,4 +9095,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/angular/package.json b/packages/angular/package.json index 2211afd24b3..3785f542d0a 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.8.3", + "version": "8.8.4", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -48,7 +48,7 @@ } }, "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index 588ab851d3d..2a483528e35 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index 6e6bd26dc7a..158656a20f7 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,13 +1,13 @@ { "name": "@ionic/docs", - "version": "8.8.3", + "version": "8.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT" } } -} +} \ No newline at end of file diff --git a/packages/docs/package.json b/packages/docs/package.json index 91c1147613d..71eabacc599 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.8.3", + "version": "8.8.4", "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 d7185dd9b1d..4354d774ac4 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **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 dc82458d0e3..94f944650a9 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.8.3", + "version": "8.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/react": "^8.8.3", + "@ionic/react": "^8.8.4", "tslib": "*" }, "devDependencies": { @@ -6847,4 +6847,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/packages/react-router/package.json b/packages/react-router/package.json index e77fef9f281..ed337f5b494 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.8.3", + "version": "8.8.4", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.8.3", + "@ionic/react": "^8.8.4", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 0f8400c8511..dc8997adad5 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/react + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 3395ac5f3dc..821b953f957 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.8.3", + "version": "8.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "ionicons": "^8.0.13", "tslib": "*" }, @@ -11916,4 +11916,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json index a08514a5476..d7253110c7e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.8.3", + "version": "8.8.4", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -40,7 +40,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "ionicons": "^8.0.13", "tslib": "*" }, diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index 00d1ab61982..ea8af11eccb 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **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 9c7b1d57853..05b1320c660 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.8.3", + "version": "8.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.8.3" + "@ionic/vue": "^8.8.4" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -12994,4 +12994,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index 96fbba44ad2..b1ae2a0db94 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.8.3", + "version": "8.8.4", "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.8.3" + "@ionic/vue": "^8.8.4" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 425c7e49817..30c774c6c81 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + +**Note:** Version bump only for package @ionic/vue + + + + + ## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index a5c15bc6024..d1aba690bf3 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.8.3", + "version": "8.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.8.3", + "version": "8.8.4", "license": "MIT", "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" }, @@ -4022,4 +4022,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/packages/vue/package.json b/packages/vue/package.json index 372305c4b78..277719e7c3b 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.8.3", + "version": "8.8.4", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -68,7 +68,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.8.3", + "@ionic/core": "^8.8.4", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" }, From 7053da1a5e803f9095c28e6f8c3c4215bd0aad20 Mon Sep 17 00:00:00 2001 From: ionitron Date: Wed, 15 Apr 2026 18:13:11 +0000 Subject: [PATCH 03/11] chore(): update package lock files --- core/package-lock.json | 2 +- packages/angular-server/package-lock.json | 14 +++++------ packages/angular/package-lock.json | 8 +++--- packages/docs/package-lock.json | 2 +- packages/react-router/package-lock.json | 30 +++++++++++------------ packages/react/package-lock.json | 8 +++--- packages/vue-router/package-lock.json | 30 +++++++++++------------ packages/vue/package-lock.json | 8 +++--- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/core/package-lock.json b/core/package-lock.json index 8bef7f2f1f7..007fed4a238 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -9829,4 +9829,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index 62015e76228..798964509f8 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1031,9 +1031,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -7309,9 +7309,9 @@ "dev": true }, "@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "requires": { "@stencil/core": "4.43.0", "ionicons": "^8.0.13", @@ -11289,4 +11289,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index b9c31ace5a7..1178f97c7da 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1398,9 +1398,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -9095,4 +9095,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index 158656a20f7..5110fe60ab6 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -10,4 +10,4 @@ "license": "MIT" } } -} \ No newline at end of file +} diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 94f944650a9..37b215ff62c 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -238,9 +238,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -418,12 +418,12 @@ } }, "node_modules/@ionic/react": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.3.tgz", - "integrity": "sha512-mqUftIoYROKSiqD9rOyF0XwR1/5dsGpAW/JD5bcPJqbNKx2bopb0/uiddeQ3Vbhi37tz5nsxzX0NB1+3cdx7hQ==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.4.tgz", + "integrity": "sha512-qlQNV6sRVIsq+vs33COKfzvMbNKRpPBGWYATv6vsNWmX7OhOm9tk7QSZl91LECvZRHMIe6Sqn9xMB+Aw/IBRfA==", "license": "MIT", "dependencies": { - "@ionic/core": "8.8.3", + "@ionic/core": "8.8.4", "ionicons": "^8.0.13", "tslib": "*" }, @@ -4178,9 +4178,9 @@ "dev": true }, "@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "requires": { "@stencil/core": "4.43.0", "ionicons": "^8.0.13", @@ -4284,11 +4284,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.3.tgz", - "integrity": "sha512-mqUftIoYROKSiqD9rOyF0XwR1/5dsGpAW/JD5bcPJqbNKx2bopb0/uiddeQ3Vbhi37tz5nsxzX0NB1+3cdx7hQ==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.4.tgz", + "integrity": "sha512-qlQNV6sRVIsq+vs33COKfzvMbNKRpPBGWYATv6vsNWmX7OhOm9tk7QSZl91LECvZRHMIe6Sqn9xMB+Aw/IBRfA==", "requires": { - "@ionic/core": "8.8.3", + "@ionic/core": "8.8.4", "ionicons": "^8.0.13", "tslib": "*" } @@ -6847,4 +6847,4 @@ "dev": true } } -} \ No newline at end of file +} diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 821b953f957..487b165f5b4 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -736,9 +736,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -11916,4 +11916,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 05b1320c660..0dffc9612f6 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -673,9 +673,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -868,12 +868,12 @@ } }, "node_modules/@ionic/vue": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.3.tgz", - "integrity": "sha512-6bwi2RodoxECt2Ef6C6km4ZkuH4TweyhF800/XpunOKjM2Iya3pD1j230KSRd35cJpAenGc1uplLdIZhAyMsWg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.4.tgz", + "integrity": "sha512-FZftTMul3zw7vjZHAxznheSu0sy0h6GSLtKNeU5U4XtziOaMYZ9dLP5TWsBjRVahAEtqn+n6493fYW4EPE+dvw==", "license": "MIT", "dependencies": { - "@ionic/core": "8.8.3", + "@ionic/core": "8.8.4", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } @@ -8044,9 +8044,9 @@ "dev": true }, "@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "requires": { "@stencil/core": "4.43.0", "ionicons": "^8.0.13", @@ -8159,11 +8159,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.3.tgz", - "integrity": "sha512-6bwi2RodoxECt2Ef6C6km4ZkuH4TweyhF800/XpunOKjM2Iya3pD1j230KSRd35cJpAenGc1uplLdIZhAyMsWg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.4.tgz", + "integrity": "sha512-FZftTMul3zw7vjZHAxznheSu0sy0h6GSLtKNeU5U4XtziOaMYZ9dLP5TWsBjRVahAEtqn+n6493fYW4EPE+dvw==", "requires": { - "@ionic/core": "8.8.3", + "@ionic/core": "8.8.4", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } @@ -12994,4 +12994,4 @@ "dev": true } } -} \ No newline at end of file +} diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index d1aba690bf3..b21f4c5a4ba 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -222,9 +222,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.3.tgz", - "integrity": "sha512-qvl+bRgZRvAJ35eW2iW0Vlo11T/EQsPazMU6z45QxJvcLukGJ59MwubjDTx6dPKteful4/FBzVt9etCvcNp8Gg==", + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.4.tgz", + "integrity": "sha512-RWts/72xtNNJDZQtntMRxB68KDctq76INV5OmLWWc0rlgcxOlNQDNH+lTBH7kV9vQ78JDVGSgi10ax3oQ3dIIQ==", "license": "MIT", "dependencies": { "@stencil/core": "4.43.0", @@ -4022,4 +4022,4 @@ "dev": true } } -} \ No newline at end of file +} From 12800cabfd22f785fcadb892fbbc91b5eff92dcd Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 15 Apr 2026 13:30:10 -0700 Subject: [PATCH 04/11] test(datetime): skip failing test --- core/src/components/datetime/test/basic/datetime.e2e.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/components/datetime/test/basic/datetime.e2e.ts b/core/src/components/datetime/test/basic/datetime.e2e.ts index bbc6033374f..4865e243092 100644 --- a/core/src/components/datetime/test/basic/datetime.e2e.ts +++ b/core/src/components/datetime/test/basic/datetime.e2e.ts @@ -440,7 +440,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => */ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('datetime: IO fallback'), () => { - test('should become ready even if IntersectionObserver never reports visible', async ({ page }, testInfo) => { + test('should become ready even if IntersectionObserver never reports visible', async ({ page, skip }, testInfo) => { + // TODO(FW-7284): Re-enable on WebKit after determining why it fails + skip.browser('webkit', 'Wheel is not available in WebKit'); + testInfo.annotations.push({ type: 'issue', description: 'https://github.com/ionic-team/ionic-framework/issues/30706', From 0c178ef7b3513744a461bbabd8dbcfcae008ba3f Mon Sep 17 00:00:00 2001 From: Shane Date: Thu, 16 Apr 2026 08:32:36 -0700 Subject: [PATCH 05/11] chore(build): add Vercel preview builds for framework test apps (#31073) Issue number: resolves internal --------- ## What is the current behavior? Vercel preview deployments only build and serve core Stencil component test pages. Framework-specific test apps (Angular, React, Vue, React Router) are not included, requiring developers to pull and build locally to validate them. ## What is the new behavior? Vercel preview deployments build and serve framework test apps alongside core component tests under a single preview URL. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information --- core/scripts/vercel-build.sh | 354 ++++++++++++++++++ core/vercel.json | 12 + packages/react/test/base/src/App.tsx | 3 +- .../react/test/base/src/react-app-env.d.ts | 8 + 4 files changed, 376 insertions(+), 1 deletion(-) create mode 100755 core/scripts/vercel-build.sh create mode 100644 core/vercel.json diff --git a/core/scripts/vercel-build.sh b/core/scripts/vercel-build.sh new file mode 100755 index 00000000000..daf44142dce --- /dev/null +++ b/core/scripts/vercel-build.sh @@ -0,0 +1,354 @@ +#!/bin/bash +# +# Vercel preview build script +# +# Builds core component tests (same as before) plus framework test apps +# (Angular, React, Vue) so they're all accessible from a single preview URL. +# +# Core tests: /src/components/{name}/test/{scenario} +# Angular test app: /angular/ +# React test app: /react/ +# Vue test app: /vue/ +# +set -e + +# Vercel places core/ at /vercel/path1 (bind mount). The full repo clone +# lives at /vercel/path0. We can't rely on `..` to reach it, so we search. +CORE_DIR=$(pwd) +OUTPUT_DIR="${CORE_DIR}/../_vercel_output" + +# Find the actual repo root (the directory containing packages/) +REPO_ROOT="" +for candidate in /vercel/path0 /vercel/path1 "${CORE_DIR}/.."; do + if [ -d "${candidate}/packages" ]; then + REPO_ROOT="${candidate}" + break + fi +done + +echo "=== Ionic Framework Preview Build ===" +echo "Core dir: ${CORE_DIR}" +echo "Repo root: ${REPO_ROOT:-NOT FOUND}" +if [ -z "${REPO_ROOT}" ]; then + echo "(This is expected in some Vercel configs -- framework test apps will be skipped)" +fi + +rm -rf "${OUTPUT_DIR}" +mkdir -p "${OUTPUT_DIR}" + +# Step 1 - Build Core (dependencies already installed by Vercel installCommand) +echo "" +echo "--- Step 1: Building Core ---" +npm run build + +# Copy core files to output. The test HTML files use relative paths like +# ../../../../../dist/ionic/ionic.esm.js so the directory structure must +# be preserved exactly. +echo "Copying core output..." +cp -r "${CORE_DIR}/src" "${OUTPUT_DIR}/src" +cp -r "${CORE_DIR}/dist" "${OUTPUT_DIR}/dist" +cp -r "${CORE_DIR}/css" "${OUTPUT_DIR}/css" +mkdir -p "${OUTPUT_DIR}/scripts" +cp -r "${CORE_DIR}/scripts/testing" "${OUTPUT_DIR}/scripts/testing" + +# Generate directory index pages so users can browse core test pages. +# Creates an index.html in every directory under src/components/ that +# doesn't already have one. Only includes child directories that eventually +# lead to a test page (an index.html). Prunes snapshot dirs and dead ends. +echo "Generating directory indexes for core tests..." +generate_dir_index() { + local dir="$1" + local url_path="$2" + # Skip if an index.html already exists (it's an actual test page) + [ -f "${dir}/index.html" ] && return + + local entries="" + for child in "${dir}"/*/; do + [ -d "${child}" ] || continue + local name=$(basename "${child}") + # Skip snapshot directories and hidden dirs + case "${name}" in *-snapshots|.*) continue ;; esac + # Only include if there's at least one index.html somewhere underneath + find "${child}" -name "index.html" -print -quit | grep -q . || continue + entries="${entries}${name}/\n" + done + + [ -z "${entries}" ] && return + + cat > "${dir}/index.html" << IDXEOF + + + + + + Index of ${url_path} + + + +

Index of ${url_path}

+ ../ +$(echo -e "${entries}") + + +IDXEOF +} + +# Walk all directories under src/ (bottom-up so parent indexes reflect pruned children) +find "${OUTPUT_DIR}/src" -depth -type d | while IFS= read -r dir; do + url_path="${dir#${OUTPUT_DIR}}" + generate_dir_index "${dir}" "${url_path}/" +done + +# Vercel mounts core/ at a separate path (path1) from the repo clone (path0). +# Framework packages reference core via relative paths (../../core/css etc.), +# which resolve to path0/core/ -- not path1/ where we just built. +# Symlink path0/core -> path1 so those references find the build outputs. +if [ -n "${REPO_ROOT}" ] && [ "${CORE_DIR}" != "${REPO_ROOT}/core" ] && [ -d "${REPO_ROOT}/core" ]; then + echo "Linking ${REPO_ROOT}/core -> ${CORE_DIR} (so framework builds find core outputs)" + rm -rf "${REPO_ROOT}/core" + ln -s "${CORE_DIR}" "${REPO_ROOT}/core" +fi + +# Check if the full repo is available +if [ -z "${REPO_ROOT}" ]; then + echo "" + echo "WARNING: Could not find repo root (no directory with packages/ found)" + echo "Only core tests will be deployed (framework test apps require the full repo)." + + # Generate landing page and exit -- core tests are still useful + cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF' + +Ionic Framework - Preview +

Ionic Framework Preview

Core tests only. Browse to /src/components/{name}/test/{scenario}/

+ +LANDING_EOF + + echo "=== Preview build complete (core only) ===" + exit 0 +fi + +# Step 2 - Build Framework Packages (parallel) +echo "" +echo "--- Step 2: Building Framework Packages ---" + +build_angular_pkgs() { + (cd "${REPO_ROOT}/packages/angular" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/angular-server" && npm install && npm run build) || return 1 +} + +build_react_pkgs() { + (cd "${REPO_ROOT}/packages/react" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/react-router" && npm install && npm run build) || return 1 +} + +build_vue_pkgs() { + (cd "${REPO_ROOT}/packages/vue" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/vue-router" && npm install && npm run build) || return 1 +} + +build_angular_pkgs > /tmp/vercel-angular-pkg.log 2>&1 & +PID_ANG=$! +build_react_pkgs > /tmp/vercel-react-pkg.log 2>&1 & +PID_REACT=$! +build_vue_pkgs > /tmp/vercel-vue-pkg.log 2>&1 & +PID_VUE=$! + +ANG_PKG_OK=true; REACT_PKG_OK=true; VUE_PKG_OK=true +wait $PID_ANG || { echo "Angular packages failed:"; tail -30 /tmp/vercel-angular-pkg.log; ANG_PKG_OK=false; } +wait $PID_REACT || { echo "React packages failed:"; tail -30 /tmp/vercel-react-pkg.log; REACT_PKG_OK=false; } +wait $PID_VUE || { echo "Vue packages failed:"; tail -30 /tmp/vercel-vue-pkg.log; VUE_PKG_OK=false; } + +if ! $ANG_PKG_OK || ! $REACT_PKG_OK || ! $VUE_PKG_OK; then + echo "ERROR: Some framework package builds failed." + echo "Core tests will still be deployed. Skipping failed framework test apps." +else + echo "All framework packages built." +fi + +# Step 3 - Build Framework Test Apps (parallel) +echo "" +echo "--- Step 3: Building Framework Test Apps ---" + +# Find the best available app version for a given package. +# Scans the apps/ directory and picks the newest version (reverse version sort). +pick_app() { + local apps_dir="$1/apps" + [ -d "${apps_dir}" ] || return 1 + local app + app=$(ls -1d "${apps_dir}"/*/ 2>/dev/null | xargs -n1 basename | sort -V -r | head -1) + [ -n "${app}" ] && echo "${app}" && return 0 + return 1 +} + +build_angular_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/angular/test") || { + echo "[angular] No test app found, skipping." + return 0 + } + echo "[angular] Building ${APP}..." + + cd "${REPO_ROOT}/packages/angular/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # --base-href sets so Angular Router works under the sub-path + npm run build -- --base-href /angular/ + + # Output path assumes the 'browser' builder. If migrated to 'application' builder, update this. + if [ ! -d "dist/test-app/browser" ]; then + echo "[angular] ERROR: Expected output at dist/test-app/browser/ not found." + return 1 + fi + mkdir -p "${OUTPUT_DIR}/angular" + cp -r dist/test-app/browser/* "${OUTPUT_DIR}/angular/" + echo "[angular] Done." +} + +build_react_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/react/test") || { + echo "[react] No test app found, skipping." + return 0 + } + echo "[react] Building ${APP}..." + + cd "${REPO_ROOT}/packages/react/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # --base sets Vite's base URL; import.meta.env.BASE_URL is read by IonReactRouter basename + npx vite build --base /react/ + + mkdir -p "${OUTPUT_DIR}/react" + cp -r dist/* "${OUTPUT_DIR}/react/" + echo "[react] Done." +} + +build_vue_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/vue/test") || { + echo "[vue] No test app found, skipping." + return 0 + } + echo "[vue] Building ${APP}..." + + cd "${REPO_ROOT}/packages/vue/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # Vue Router already reads import.meta.env.BASE_URL which Vite sets from --base + npx vite build --base /vue/ + + mkdir -p "${OUTPUT_DIR}/vue" + cp -r dist/* "${OUTPUT_DIR}/vue/" + echo "[vue] Done." +} + +# TODO: Add build_react_router_test() when reactrouter6-* apps are added to +# packages/react-router/test/apps/ + +TEST_FAILED="" + +if $ANG_PKG_OK; then + build_angular_test > /tmp/vercel-angular-test.log 2>&1 & + PID_ANG_TEST=$! +fi +if $REACT_PKG_OK; then + build_react_test > /tmp/vercel-react-test.log 2>&1 & + PID_REACT_TEST=$! +fi +if $VUE_PKG_OK; then + build_vue_test > /tmp/vercel-vue-test.log 2>&1 & + PID_VUE_TEST=$! +fi + +if $ANG_PKG_OK; then + wait $PID_ANG_TEST || { echo "Angular test app failed:"; tail -30 /tmp/vercel-angular-test.log; TEST_FAILED="${TEST_FAILED} angular"; } +fi +if $REACT_PKG_OK; then + wait $PID_REACT_TEST || { echo "React test app failed:"; tail -30 /tmp/vercel-react-test.log; TEST_FAILED="${TEST_FAILED} react"; } +fi +if $VUE_PKG_OK; then + wait $PID_VUE_TEST || { echo "Vue test app failed:"; tail -30 /tmp/vercel-vue-test.log; TEST_FAILED="${TEST_FAILED} vue"; } +fi + +if [ -n "${TEST_FAILED}" ]; then + echo "" + echo "WARNING: Some test app builds failed:${TEST_FAILED}" + echo "Core tests and successful framework apps will still be deployed." +fi + +# Step 4 - Landing Page +echo "" +echo "--- Step 4: Generating landing page ---" + +cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF' + + + + + + Ionic Framework - Preview + + + + + + +LANDING_EOF + +echo "" +echo "=== Preview build complete ===" +ls -la "${OUTPUT_DIR}" diff --git a/core/vercel.json b/core/vercel.json new file mode 100644 index 00000000000..64b88751822 --- /dev/null +++ b/core/vercel.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": null, + "installCommand": "npm install", + "buildCommand": "bash scripts/vercel-build.sh", + "outputDirectory": "../_vercel_output", + "rewrites": [ + { "source": "/angular/:path*", "destination": "/angular/index.html" }, + { "source": "/react/:path*", "destination": "/react/index.html" }, + { "source": "/vue/:path*", "destination": "/vue/index.html" } + ] +} diff --git a/packages/react/test/base/src/App.tsx b/packages/react/test/base/src/App.tsx index c8ea117f60e..5878f0214f6 100644 --- a/packages/react/test/base/src/App.tsx +++ b/packages/react/test/base/src/App.tsx @@ -44,7 +44,8 @@ setupIonicReact(); const App: React.FC = () => ( - + {/* Vercel previews serve this app under /react/, so derive basename from Vite's base URL */} + diff --git a/packages/react/test/base/src/react-app-env.d.ts b/packages/react/test/base/src/react-app-env.d.ts index 6431bc5fc6b..b4ef8effc8f 100644 --- a/packages/react/test/base/src/react-app-env.d.ts +++ b/packages/react/test/base/src/react-app-env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly BASE_URL?: string; +} + +interface ImportMeta { + readonly env?: ImportMetaEnv; +} From 164af7ae3e516e375571c5d3589e0648a3a8ec5f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:40:18 -0400 Subject: [PATCH 06/11] chore(deps): update capacitor to v8.3.1 (#31075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@capacitor/core](https://capacitorjs.com) ([source](https://redirect.github.com/ionic-team/capacitor)) | [`8.3.0` β†’ `8.3.1`](https://renovatebot.com/diffs/npm/@capacitor%2fcore/8.3.0/8.3.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fcore/8.3.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fcore/8.3.0/8.3.1?slim=true) | | [@capacitor/keyboard](https://redirect.github.com/ionic-team/capacitor-keyboard) | [`8.0.2` β†’ `8.0.3`](https://renovatebot.com/diffs/npm/@capacitor%2fkeyboard/8.0.2/8.0.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fkeyboard/8.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fkeyboard/8.0.2/8.0.3?slim=true) | --- ### Release Notes
ionic-team/capacitor (@​capacitor/core) ### [`v8.3.1`](https://redirect.github.com/ionic-team/capacitor/blob/HEAD/CHANGELOG.md#831-2026-04-16) [Compare Source](https://redirect.github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) ##### Bug Fixes - **android:** handle null versionName in isNewBinary() ([#​8397](https://redirect.github.com/ionic-team/capacitor/issues/8397)) ([aa1a660](https://redirect.github.com/ionic-team/capacitor/commit/aa1a660f364f9b5f5a1e350e279c8864b04dd13b)) - boundary value extraction for form-data requests ([#​7518](https://redirect.github.com/ionic-team/capacitor/issues/7518)) ([64ab854](https://redirect.github.com/ionic-team/capacitor/commit/64ab854c12330804c24275d88d3a9f7c8e52a73d)) - **cli:** check CAPACITOR\_COCOAPODS\_PATH in determinePackageManager ([#​8407](https://redirect.github.com/ionic-team/capacitor/issues/8407)) ([acb64ab](https://redirect.github.com/ionic-team/capacitor/commit/acb64ab92a37ff53701cde453558e272e2e11eb6)) - **system-bars:** use separate current styles ([#​8409](https://redirect.github.com/ionic-team/capacitor/issues/8409)) ([3d1f8d1](https://redirect.github.com/ionic-team/capacitor/commit/3d1f8d1b61480187375f5cd4de7ba999db007542))
ionic-team/capacitor-keyboard (@​capacitor/keyboard) ### [`v8.0.3`](https://redirect.github.com/ionic-team/capacitor-keyboard/blob/HEAD/CHANGELOG.md#803-2026-04-10) [Compare Source](https://redirect.github.com/ionic-team/capacitor-keyboard/compare/v8.0.2...v8.0.3) ##### Bug Fixes - **android:** fixing Keyboard interaction with SystemBars ([#​62](https://redirect.github.com/ionic-team/capacitor-keyboard/issues/62)) ([4afd89b](https://redirect.github.com/ionic-team/capacitor-keyboard/commit/4afd89b4af63609f40e970e7775fd03c5f6b407c))
--- ### Configuration πŸ“… **Schedule**: (UTC) - Branch creation - "every weekday before 11am" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Never, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/ionic-team/ionic-framework). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- core/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/package-lock.json b/core/package-lock.json index 007fed4a238..70157701050 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -629,9 +629,9 @@ "license": "MIT" }, "node_modules/@capacitor/core": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.0.tgz", - "integrity": "sha512-S4ajn4G/fS3VJj8salxqH/3LO5PPWv1VxGKQ27OCajnDcLJjEg9VXwgMPnlypgkIOqCJ2fmQLtk8GT+BlI9/rw==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.1.tgz", + "integrity": "sha512-UF8ItlHguU1Z6GXfPTeT2gakf+ctNI8pAS1kwSBQlsJMlfD4OPoto/SmKnOxKCQvnF4WRcdWeg6C0zREUNaAQg==", "dev": true, "license": "MIT", "dependencies": { @@ -649,9 +649,9 @@ } }, "node_modules/@capacitor/keyboard": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.2.tgz", - "integrity": "sha512-he6xKmTBp5AhVrWJeEi6RYkJ25FjLLdNruBU2wafpITk3Nb7UdzOj96x3K6etFuEj8/rtn9WXBTs1o2XA86A1A==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.3.tgz", + "integrity": "sha512-27Bv5/2w1Ss2njguBgTS98O0Bb8DRJhAARyzXYib0JlT/n6BrJw/EZ0CokM4C8GFUjFDjJnEKF1Ie01buTMEXQ==", "dev": true, "license": "MIT", "peerDependencies": { From 0db5b4032778cc8dfb8378865a623b58d5692989 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:11:26 -0400 Subject: [PATCH 07/11] chore(deps): update actions/setup-node action to v6.4.0 (#31091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [actions/setup-node](https://redirect.github.com/actions/setup-node) | action | minor | `v6.3.0` β†’ `v6.4.0` | --- ### Release Notes
actions/setup-node (actions/setup-node) ### [`v6.4.0`](https://redirect.github.com/actions/setup-node/compare/v6.3.0...v6.4.0) [Compare Source](https://redirect.github.com/actions/setup-node/compare/v6.3.0...v6.4.0)
--- ### Configuration πŸ“… **Schedule**: (UTC) - Branch creation - "every weekday before 11am" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. β™» **Rebasing**: Never, or you tick the rebase/retry checkbox. πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/ionic-team/ionic-framework). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/actions/publish-npm/action.yml | 2 +- .github/workflows/actions/build-angular-server/action.yml | 2 +- .../workflows/actions/build-core-stencil-prerelease/action.yml | 2 +- .github/workflows/actions/build-core/action.yml | 2 +- .github/workflows/actions/build-react-router/action.yml | 2 +- .github/workflows/actions/build-react/action.yml | 2 +- .github/workflows/actions/build-vue-router/action.yml | 2 +- .github/workflows/actions/build-vue/action.yml | 2 +- .github/workflows/actions/test-angular-e2e/action.yml | 2 +- .github/workflows/actions/test-core-clean-build/action.yml | 2 +- .github/workflows/actions/test-core-lint/action.yml | 2 +- .github/workflows/actions/test-core-screenshot/action.yml | 2 +- .github/workflows/actions/test-core-spec/action.yml | 2 +- .github/workflows/actions/test-react-e2e/action.yml | 2 +- .github/workflows/actions/test-react-router-e2e/action.yml | 2 +- .github/workflows/actions/test-vue-e2e/action.yml | 2 +- .../workflows/actions/update-reference-screenshots/action.yml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml index 132b57f75b3..d3e06d5fb01 100644 --- a/.github/actions/publish-npm/action.yml +++ b/.github/actions/publish-npm/action.yml @@ -22,7 +22,7 @@ runs: using: 'composite' steps: - name: 🟒 Configure Node for Publish - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ inputs.node-version }} registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/actions/build-angular-server/action.yml b/.github/workflows/actions/build-angular-server/action.yml index 3cab52b650a..b5d37c5a9ac 100644 --- a/.github/workflows/actions/build-angular-server/action.yml +++ b/.github/workflows/actions/build-angular-server/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic Angular Server' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-core-stencil-prerelease/action.yml b/.github/workflows/actions/build-core-stencil-prerelease/action.yml index 913e8f494ff..e23d9119831 100644 --- a/.github/workflows/actions/build-core-stencil-prerelease/action.yml +++ b/.github/workflows/actions/build-core-stencil-prerelease/action.yml @@ -9,7 +9,7 @@ runs: using: 'composite' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x diff --git a/.github/workflows/actions/build-core/action.yml b/.github/workflows/actions/build-core/action.yml index 2b5117cf7af..7524c8a97b3 100644 --- a/.github/workflows/actions/build-core/action.yml +++ b/.github/workflows/actions/build-core/action.yml @@ -9,7 +9,7 @@ runs: using: 'composite' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies diff --git a/.github/workflows/actions/build-react-router/action.yml b/.github/workflows/actions/build-react-router/action.yml index 568c835c42f..c8083494b0a 100644 --- a/.github/workflows/actions/build-react-router/action.yml +++ b/.github/workflows/actions/build-react-router/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic React Router' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-react/action.yml b/.github/workflows/actions/build-react/action.yml index 9b4a5995e9e..5899335ad3e 100644 --- a/.github/workflows/actions/build-react/action.yml +++ b/.github/workflows/actions/build-react/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic React' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-vue-router/action.yml b/.github/workflows/actions/build-vue-router/action.yml index efd4579f565..9b07ce64973 100644 --- a/.github/workflows/actions/build-vue-router/action.yml +++ b/.github/workflows/actions/build-vue-router/action.yml @@ -3,7 +3,7 @@ description: 'Builds Ionic Vue Router' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-vue/action.yml b/.github/workflows/actions/build-vue/action.yml index 170e889f968..5c7497ec359 100644 --- a/.github/workflows/actions/build-vue/action.yml +++ b/.github/workflows/actions/build-vue/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic Vue' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-angular-e2e/action.yml b/.github/workflows/actions/test-angular-e2e/action.yml index 11aa8eb789c..a4835a0210a 100644 --- a/.github/workflows/actions/test-angular-e2e/action.yml +++ b/.github/workflows/actions/test-angular-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-core-clean-build/action.yml b/.github/workflows/actions/test-core-clean-build/action.yml index 92e3fed394b..96abc90121c 100644 --- a/.github/workflows/actions/test-core-clean-build/action.yml +++ b/.github/workflows/actions/test-core-clean-build/action.yml @@ -3,7 +3,7 @@ description: 'Test Core Clean Build' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x diff --git a/.github/workflows/actions/test-core-lint/action.yml b/.github/workflows/actions/test-core-lint/action.yml index 321a2d26304..f9f0011719a 100644 --- a/.github/workflows/actions/test-core-lint/action.yml +++ b/.github/workflows/actions/test-core-lint/action.yml @@ -3,7 +3,7 @@ description: 'Test Core Lint' runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml index 7ffa40faf5c..1f8699e66d4 100644 --- a/.github/workflows/actions/test-core-screenshot/action.yml +++ b/.github/workflows/actions/test-core-screenshot/action.yml @@ -13,7 +13,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-core-spec/action.yml b/.github/workflows/actions/test-core-spec/action.yml index f25207f6a49..2aab4b1be94 100644 --- a/.github/workflows/actions/test-core-spec/action.yml +++ b/.github/workflows/actions/test-core-spec/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies diff --git a/.github/workflows/actions/test-react-e2e/action.yml b/.github/workflows/actions/test-react-e2e/action.yml index a6f1d42ba72..a1bcbf7a4db 100644 --- a/.github/workflows/actions/test-react-e2e/action.yml +++ b/.github/workflows/actions/test-react-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-react-router-e2e/action.yml b/.github/workflows/actions/test-react-router-e2e/action.yml index 70dff8db874..034cfdce747 100644 --- a/.github/workflows/actions/test-react-router-e2e/action.yml +++ b/.github/workflows/actions/test-react-router-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-vue-e2e/action.yml b/.github/workflows/actions/test-vue-e2e/action.yml index 060e923bdf4..191cd193c8a 100644 --- a/.github/workflows/actions/test-vue-e2e/action.yml +++ b/.github/workflows/actions/test-vue-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml index 51d7bdce508..6ee56689b10 100644 --- a/.github/workflows/actions/update-reference-screenshots/action.yml +++ b/.github/workflows/actions/update-reference-screenshots/action.yml @@ -7,7 +7,7 @@ on: runs: using: 'composite' steps: - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: actions/download-artifact@v8 From f3cd39b7fb291286374285c4a326ec6b9a8ea237 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 27 Apr 2026 11:15:49 -0700 Subject: [PATCH 08/11] fix(modal): remove safe-area gap and flash in fullscreen modals (#31092) Issue number: resolves #31015 --------- ## What is the current behavior? Fullscreen modals without an `ion-footer` set `height` and `padding-bottom` on `.modal-wrapper` to reserve space for the bottom safe-area. Because `ion-content`'s background does not extend into that wrapper padding, a visible gap appears between the content and the bottom edge of the modal. Separately, modals that declare custom `--width` and `--height` on phone viewports flash inherited safe-area values while animating in, then snap to `0px` once the post-animation position correction runs. ## What is the new behavior? Bottom safe-area compensation now happens inside `ion-content` instead of on the wrapper, so the modal background stays edge-to-edge. - New internal `--ion-content-safe-area-padding-bottom` CSS property is added to `.inner-scroll`'s `padding-bottom` calc. - `modal.tsx` sets that property on `ion-content` for fullscreen modals without a footer, and clears it on resize and dismiss. - Wrapper no longer has `height` or `padding-bottom` inline styles written to it. - New `hasCustomModalDimensions()` helper detects modals that override both `--width` and `--height` to non-fullscreen values. These are zeroed from the initial safe-area prediction so there is no flash before the post-animation correction. - Extracted `findContentAndFooter()` and `applyFullscreenSafeAreaTo()` helpers so the resize handler and initial apply share the same lookup. - E2E tests cover scroll-padding placement, wrapper content area remaining full viewport height, `.inner-scroll` padding including safe-area, and custom-dimension modals starting at zero. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information `--ion-content-safe-area-padding-bottom` is an internal property. It is additive with the existing `--padding-bottom` consumers can set, and it only takes effect when `modal.tsx` writes it on fullscreen modals without a footer. Example pages: - iOS: https://ionic-framework-git-fw-7136-ionic1.vercel.app/src/components/modal/test/basic?ionic:mode=ios - MD: https://ionic-framework-git-fw-7136-ionic1.vercel.app/src/components/modal/test/basic?ionic:mode=md Example iOS card modal safe-area: - iOS: https://ionic-framework-git-fw-7136-ionic1.vercel.app/src/components/modal/test/safe-area?ionic:mode=ios ## Dev Build: ``` 8.8.5-dev.11776879142.101200b9 ``` --- core/src/components/content/content.scss | 2 +- core/src/components/modal/modal.tsx | 112 ++++++++++------ core/src/components/modal/safe-area-utils.ts | 36 +++++- .../modal/test/safe-area/modal.e2e.ts | 122 ++++++++++++++++-- 4 files changed, 218 insertions(+), 54 deletions(-) diff --git a/core/src/components/content/content.scss b/core/src/components/content/content.scss index 5f8b2afa831..89dae0aff94 100644 --- a/core/src/components/content/content.scss +++ b/core/src/components/content/content.scss @@ -64,7 +64,7 @@ .inner-scroll { @include position(calc(var(--offset-top) * -1), 0px,calc(var(--offset-bottom) * -1), 0px); - @include padding(calc(var(--padding-top) + var(--offset-top)), var(--padding-end), calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom)), var(--padding-start)); + @include padding(calc(var(--padding-top) + var(--offset-top)), var(--padding-end), calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom) + var(--ion-content-safe-area-padding-bottom, 0px)), var(--padding-start)); position: absolute; diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 9ba3ecbf009..5bbb67d3d61 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -50,6 +50,7 @@ import { applySafeAreaOverrides, clearSafeAreaOverrides, getRootSafeAreaTop, + hasCustomModalDimensions, type ModalSafeAreaContext, } from './safe-area-utils'; import { setCardStatusBarDark, setCardStatusBarDefault } from './utils'; @@ -311,12 +312,10 @@ export class Modal implements ComponentInterface, OverlayInterface { if (!context.isSheetModal && !context.isCardModal) { this.updateSafeAreaOverrides(); - // Re-evaluate fullscreen safe-area padding: clear first, then re-apply - if (this.wrapperEl) { - this.wrapperEl.style.removeProperty('height'); - this.wrapperEl.style.removeProperty('padding-bottom'); - } - this.applyFullscreenSafeArea(); + // Re-evaluate fullscreen safe-area padding: clear first, then re-apply. + const { contentEl, hasFooter } = this.findContentAndFooter(); + this.clearContentSafeAreaPadding(contentEl); + this.applyFullscreenSafeAreaTo(contentEl, hasFooter); } }, 50); // Debounce to avoid excessive calls during active resizing } @@ -1429,6 +1428,11 @@ export class Modal implements ComponentInterface, OverlayInterface { /** * Creates the context object for safe-area utilities. + * + * `hasCustomDimensions` is only set by `setInitialSafeAreaOverrides()` + * because it is only read by `getInitialSafeAreaConfig()`. Other callers + * (resize handler, post-animation update, fullscreen-padding apply) would + * pay a `getComputedStyle()` cost for a value they never consult. */ private getSafeAreaContext(): ModalSafeAreaContext { return { @@ -1451,7 +1455,10 @@ export class Modal implements ComponentInterface, OverlayInterface { * sheets to prevent header content from getting double-offset padding). */ private setInitialSafeAreaOverrides(): void { - const context = this.getSafeAreaContext(); + const context: ModalSafeAreaContext = { + ...this.getSafeAreaContext(), + hasCustomDimensions: hasCustomModalDimensions(this.el), + }; const safeAreaConfig = getInitialSafeAreaConfig(context); applySafeAreaOverrides(this.el, safeAreaConfig); @@ -1496,48 +1503,77 @@ export class Modal implements ComponentInterface, OverlayInterface { } /** - * Applies padding-bottom to fullscreen modal wrapper to prevent - * content from overlapping system navigation bar. + * Applies safe-area-bottom scroll padding to ion-content inside + * fullscreen modals that have no ion-footer. This prevents content + * from being hidden behind the system navigation bar while keeping + * the modal background edge-to-edge (no visible gap). */ private applyFullscreenSafeArea(): void { - const { wrapperEl, el } = this; - if (!wrapperEl) return; - const context = this.getSafeAreaContext(); if (context.isSheetModal || context.isCardModal) return; - // Check for standard Ionic layout children (ion-content, ion-footer), - // searching one level deep for wrapped components (e.g., - // ...). - // Note: uses a manual loop instead of querySelector(':scope > ...') because - // Stencil's mock-doc (used in spec tests) does not support :scope. - let hasContent = false; + const { contentEl, hasFooter } = this.findContentAndFooter(); + this.applyFullscreenSafeAreaTo(contentEl, hasFooter); + } + + /** + * Sets --ion-content-safe-area-padding-bottom on the given ion-content + * when no footer is present, so ion-content's .inner-scroll includes + * safe-area-bottom in its scroll padding. This keeps the modal background + * edge-to-edge while ensuring content scrolls clear of the system nav bar. + * + * --ion-content-safe-area-padding-bottom is an internal CSS property used + * only by this code path. It is not part of ion-content's public API and + * should not be set by consumers. The default of 0px makes it a no-op + * when unset, which is the expected state for ion-content used outside of + * a fullscreen modal without a footer. + */ + private applyFullscreenSafeAreaTo(contentEl: HTMLElement | null, hasFooter: boolean): void { + // Only apply for standard Ionic layouts (has ion-content but no + // ion-footer). When a footer is present it handles its own safe-area + // padding. Custom modals with raw HTML are developer-controlled. + if (!contentEl || hasFooter) return; + + contentEl.style.setProperty('--ion-content-safe-area-padding-bottom', 'var(--ion-safe-area-bottom, 0px)'); + } + + /** + * Removes the internal --ion-content-safe-area-padding-bottom property + * from an already-located ion-content. Callers do their own + * findContentAndFooter() so they can also read hasFooter if needed. + */ + private clearContentSafeAreaPadding(contentEl: HTMLElement | null): void { + if (!contentEl) return; + contentEl.style.removeProperty('--ion-content-safe-area-padding-bottom'); + } + + /** + * Finds ion-content and ion-footer among direct children and one level of + * grandchildren (for wrapped components like ). + * + * Intentionally does NOT use findIonContent() or querySelector() because + * those search the full subtree and would match ion-content inside nested + * routes/pages. We only want direct slot children (+ one wrapper level). + * + * Uses a manual loop instead of querySelector(':scope > ...') because + * Stencil's mock-doc (used in spec tests) does not support :scope. + */ + private findContentAndFooter(): { contentEl: HTMLElement | null; hasFooter: boolean } { + let contentEl: HTMLElement | null = null; let hasFooter = false; - for (const child of Array.from(el.children)) { - if (child.tagName === 'ION-CONTENT') hasContent = true; + for (const child of Array.from(this.el.children)) { + if (child.tagName === 'ION-CONTENT') contentEl = child as HTMLElement; if (child.tagName === 'ION-FOOTER') hasFooter = true; for (const grandchild of Array.from(child.children)) { - if (grandchild.tagName === 'ION-CONTENT') hasContent = true; + if (grandchild.tagName === 'ION-CONTENT' && !contentEl) contentEl = grandchild as HTMLElement; if (grandchild.tagName === 'ION-FOOTER') hasFooter = true; } } - - // Only apply wrapper padding for standard Ionic layouts (has ion-content - // but no ion-footer). Custom modals with raw HTML are fully - // developer-controlled and should not be modified. - if (!hasContent || hasFooter) return; - - // Reduce wrapper height by safe-area and add equivalent padding so the - // total visual size stays the same but the flex content area shrinks. - // Using height + padding instead of box-sizing: border-box avoids - // breaking custom modals that set --border-width (border-box would - // include the border inside the height, changing the layout). - wrapperEl.style.setProperty('height', 'calc(var(--height) - var(--ion-safe-area-bottom, 0px))'); - wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)'); + return { contentEl, hasFooter }; } /** - * Clears all safe-area overrides and padding from wrapper. + * Clears all safe-area overrides and padding. */ private cleanupSafeAreaOverrides(): void { clearSafeAreaOverrides(this.el); @@ -1545,10 +1581,8 @@ export class Modal implements ComponentInterface, OverlayInterface { // Remove internal sheet offset property this.el.style.removeProperty('--ion-modal-offset-top'); - if (this.wrapperEl) { - this.wrapperEl.style.removeProperty('height'); - this.wrapperEl.style.removeProperty('padding-bottom'); - } + const { contentEl } = this.findContentAndFooter(); + this.clearContentSafeAreaPadding(contentEl); } render() { diff --git a/core/src/components/modal/safe-area-utils.ts b/core/src/components/modal/safe-area-utils.ts index a13bf1770a0..59ae3a4fdb5 100644 --- a/core/src/components/modal/safe-area-utils.ts +++ b/core/src/components/modal/safe-area-utils.ts @@ -23,6 +23,11 @@ export interface ModalSafeAreaContext { presentingElement?: HTMLElement; breakpoints?: number[]; currentBreakpoint?: number; + /** + * Only consulted by `getInitialSafeAreaConfig()`. Callers that only use the + * context for non-initial paths can omit this. See `hasCustomModalDimensions()`. + */ + hasCustomDimensions?: boolean; } /** @@ -38,6 +43,13 @@ const MODAL_INSET_MIN_WIDTH = 768; const MODAL_INSET_MIN_HEIGHT = 600; const EDGE_THRESHOLD = 5; +/** + * CSS values for `--width` / `--height` that are treated as fullscreen + * (modal touches the corresponding screen edges). Empty string means the + * property was not overridden. See `hasCustomModalDimensions()`. + */ +const FULLSCREEN_SIZE_VALUES = new Set(['', '100%', '100vw', '100vh', '100dvw', '100dvh', '100svw', '100svh']); + /** * Cache for resolved root safe-area-top value, invalidated once per frame. */ @@ -92,6 +104,23 @@ export const getRootSafeAreaTop = (): number => { return value; }; +/** + * True when the modal host declares BOTH a non-fullscreen `--width` AND a + * non-fullscreen `--height` (i.e. a centered-dialog-like modal that doesn't + * touch any screen edge). + * + * The conservative "both axes" check avoids mis-zeroing safe-area for + * partial-custom modals where the modal still touches top/bottom edges + * (e.g. only `--width` overridden). Partial cases fall through to the + * existing position-based post-animation correction. + */ +export const hasCustomModalDimensions = (hostEl: HTMLElement): boolean => { + const styles = getComputedStyle(hostEl); + const width = styles.getPropertyValue('--width').trim(); + const height = styles.getPropertyValue('--height').trim(); + return !FULLSCREEN_SIZE_VALUES.has(width) && !FULLSCREEN_SIZE_VALUES.has(height); +}; + /** * Returns the initial safe-area configuration based on modal type. * This is called before animation starts and uses configuration-based prediction. @@ -129,8 +158,11 @@ export const getInitialSafeAreaConfig = (context: ModalSafeAreaContext): SafeAre // On viewports that meet the centered dialog media query breakpoints, // regular modals render as centered dialogs (not fullscreen), so they - // don't touch any screen edges and don't need safe-area insets. - if (isCenteredDialogViewport()) { + // don't touch any screen edges and don't need safe-area insets. Also + // applies to phone viewports when the modal declares custom --width and + // --height; these don't touch screen edges either, so the initial + // prediction must be zero to avoid a post-animation correction flash. + if (isCenteredDialogViewport() || context.hasCustomDimensions) { return { top: '0px', bottom: '0px', diff --git a/core/src/components/modal/test/safe-area/modal.e2e.ts b/core/src/components/modal/test/safe-area/modal.e2e.ts index af2a58c7699..a65b6f3fee8 100644 --- a/core/src/components/modal/test/safe-area/modal.e2e.ts +++ b/core/src/components/modal/test/safe-area/modal.e2e.ts @@ -13,7 +13,11 @@ import { configs, test, Viewports } from '@utils/test/playwright'; * The test page (index.html) sets these root safe-area values. * Keep in sync with the :root block in test/safe-area/index.html. */ -const TEST_SAFE_AREA_TOP = '47px'; +const TEST_SAFE_AREA_TOP = 47; +const TEST_SAFE_AREA_BOTTOM = 34; +/** Default value of --ion-padding (16px), applied via the .ion-padding class on ion-content in the test modal. */ +const TEST_ION_PADDING = 16; + configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('modal: safe-area handling'), () => { test.beforeEach(async ({ page }) => { @@ -100,10 +104,12 @@ configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config expect(safeAreaBottom).toBe('inherit'); }); - test('fullscreen modal without footer should have wrapper padding-bottom', async ({ page }, testInfo) => { + test('fullscreen modal without footer should set safe-area scroll padding on ion-content', async ({ + page, + }, testInfo) => { testInfo.annotations.push({ type: 'issue', - description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', }); const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); @@ -113,20 +119,83 @@ configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config const modal = page.locator('ion-modal'); - // When no footer is present, the wrapper should have reduced height - // and padding-bottom to prevent content from overlapping the system - // navigation bar, without changing box-sizing (which would break - // custom modals with --border-width). + // The wrapper should NOT have reduced height or padding-bottom. + // Safe-area compensation is handled by ion-content's scroll padding. const wrapper = modal.locator('.modal-wrapper'); - const paddingBottom = await wrapper.evaluate((el: HTMLElement) => { + const wrapperPaddingBottom = await wrapper.evaluate((el: HTMLElement) => { return el.style.getPropertyValue('padding-bottom'); }); - const height = await wrapper.evaluate((el: HTMLElement) => { + const wrapperHeight = await wrapper.evaluate((el: HTMLElement) => { return el.style.getPropertyValue('height'); }); - expect(paddingBottom).toBe('var(--ion-safe-area-bottom, 0px)'); - expect(height).toBe('calc(var(--height) - var(--ion-safe-area-bottom, 0px))'); + expect(wrapperPaddingBottom).toBe(''); + expect(wrapperHeight).toBe(''); + + // ion-content should have --ion-content-safe-area-padding-bottom set so its + // .inner-scroll element includes safe-area in its bottom padding. + const content = modal.locator('ion-content'); + const safeAreaPadding = await content.evaluate((el: HTMLElement) => { + return el.style.getPropertyValue('--ion-content-safe-area-padding-bottom'); + }); + expect(safeAreaPadding).toBe('var(--ion-safe-area-bottom, 0px)'); + }); + + test('fullscreen modal with ion-content and no footer should not reduce wrapper content area', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal-no-footer'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const wrapper = modal.locator('.modal-wrapper'); + + // The wrapper's content area should equal the full viewport height. + // Safe-area compensation is handled by ion-content's scroll padding, + // not by reducing the wrapper. This prevents the visible white gap + // reported in #31015. + const { contentHeight, paddingBottom } = await wrapper.evaluate((el: HTMLElement) => { + const computed = getComputedStyle(el); + return { + contentHeight: parseFloat(computed.height), + paddingBottom: parseFloat(computed.paddingBottom), + }; + }); + const viewportHeight = await page.evaluate(() => window.innerHeight); + + expect(paddingBottom).toBeCloseTo(0, 0); + expect(contentHeight).toBeCloseTo(viewportHeight, 0); + }); + + test('fullscreen modal ion-content scroll padding should include safe-area-bottom', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal-no-footer'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const content = modal.locator('ion-content'); + + // The .inner-scroll element inside ion-content's shadow DOM should + // have padding-bottom that includes the safe-area-bottom value. + const innerScroll = content.locator('.inner-scroll'); + const scrollPaddingBottom = await innerScroll.evaluate((el: Element) => { + return parseFloat(getComputedStyle(el).paddingBottom); + }); + + expect(scrollPaddingBottom).toBe(TEST_ION_PADDING + TEST_SAFE_AREA_BOTTOM); }); test('sheet modal at breakpoint 1 should keep top safe-area zeroed', async ({ page }, testInfo) => { @@ -185,7 +254,7 @@ configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config const offsetTop = await modal.evaluate((el: HTMLIonModalElement) => { return el.style.getPropertyValue('--ion-modal-offset-top'); }); - expect(offsetTop).toBe(TEST_SAFE_AREA_TOP); + expect(offsetTop).toBe(`${TEST_SAFE_AREA_TOP}px`); }); test('fullscreen modal safe-area should update on resize from phone to tablet', async ({ page }, testInfo) => { @@ -251,6 +320,35 @@ configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config expect(safeAreaBottom).toBe('0px'); }); + test('centered dialog with custom dimensions on phone should zero safe-area from initial prediction', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + // Stay on phone viewport. This is the path where the centered-dialog + // media query does NOT match but the modal still doesn't touch screen + // edges because cssClass sets --width/--height. Without the initial + // prediction catching this, safe-area flashes inherited values and + // then snaps to 0px after animation. + const ionModalWillPresent = await page.spyOnEvent('ionModalWillPresent'); + await page.click('#centered-dialog'); + await ionModalWillPresent.next(); + + // Read inline style IMMEDIATELY after will-present fires, before the + // animation finishes. This captures the initial prediction value. + const modal = page.locator('ion-modal'); + const initial = await modal.evaluate((el: HTMLIonModalElement) => ({ + top: el.style.getPropertyValue('--ion-safe-area-top'), + bottom: el.style.getPropertyValue('--ion-safe-area-bottom'), + })); + + expect(initial.top).toBe('0px'); + expect(initial.bottom).toBe('0px'); + }); + test('safe-area overrides should be cleared on dismiss', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'issue', From fd79771e5be77c9f38379a3a7b9ab44bb11ff325 Mon Sep 17 00:00:00 2001 From: Shane Date: Wed, 29 Apr 2026 06:51:21 -0700 Subject: [PATCH 09/11] fix(select): select focused option on Enter in popover and modal interfaces (#31093) Issue number: resolves #30561 --------- ## What is the current behavior? When an `ion-select` with `interface="popover"` or `interface="modal"` is opened with the keyboard, pressing Enter on a focused option doesn't commit the value or dismiss the overlay. Only Space works. The `onKeyUp` handlers in `select-popover.tsx` and `select-modal.tsx` only checked for `' '`, despite the comment claiming Enter was supported, and `ion-radio-group`'s arrow-key handler never committed the focused value on Enter the way native `` behavior. `radio-group.tsx` gains an Enter branch inside its select-interface arrow-key handler that sets `value` to the focused radio and emits `ionChange` when it differs from the previous value. `select-popover.tsx` and `select-modal.tsx` track the `onKeyDown` target so `onKeyUp` only dismisses when the Enter press started on the same option. That guard stops a held Enter on the triggering `ion-select` from re-firing inside the just-opened overlay and auto-dismissing it. The `!ev.repeat` check on keydown covers the same case for the radio-group commit. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information The keydown/keyup target-matching pattern is the same trick the browser uses to avoid firing a click when keydown and keyup happen on different elements. It's needed here because Enter on the `ion-select` trigger opens the overlay on keydown, and without the guard the corresponding keyup (now inside the overlay) would immediately commit and dismiss. Preview page: - [select / basic](https://ionic-framework-git-fw-6754-ionic1.vercel.app/src/components/select/test/basic?ionic) --- .../components/radio-group/radio-group.tsx | 13 + .../radio-group/test/basic/radio-group.e2e.ts | 26 ++ .../components/radio-group/test/fixtures.ts | 4 +- .../components/select-modal/select-modal.tsx | 22 +- .../test/basic/select-modal.e2e.ts | 29 ++ .../components/select-modal/test/fixtures.ts | 5 + .../select-popover/select-popover.tsx | 23 +- .../test/basic/select-popover.e2e.ts | 29 ++ .../select-popover/test/fixtures.ts | 5 + .../select/test/basic/select.e2e.ts | 441 ++++++++++++++++++ 10 files changed, 585 insertions(+), 12 deletions(-) diff --git a/core/src/components/radio-group/radio-group.tsx b/core/src/components/radio-group/radio-group.tsx index 88ff48e2c4c..37e0f0da0c9 100644 --- a/core/src/components/radio-group/radio-group.tsx +++ b/core/src/components/radio-group/radio-group.tsx @@ -290,6 +290,19 @@ export class RadioGroup implements ComponentInterface { // to the bottom of the screen ev.preventDefault(); } + + // Inside a select interface, Enter commits the focused radio + // value (matching native