Skip to content

merge release-8.8.17 - #31329

Merged
thetaPC merged 15 commits into
8.8.xfrom
release-8.8.17
Aug 5, 2026
Merged

merge release-8.8.17#31329
thetaPC merged 15 commits into
8.8.xfrom
release-8.8.17

Conversation

@thetaPC

@thetaPC thetaPC commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Release 8.8.17

brandyscarney and others added 15 commits July 29, 2026 16:59
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[github/codeql-action](https://redirect.github.com/github/codeql-action)
| action | patch | `v4.37.3` → `v4.37.4` |

---

### Release Notes

<details>
<summary>github/codeql-action (github/codeql-action)</summary>

###
[`v4.37.4`](https://redirect.github.com/github/codeql-action/compare/v4.37.3...v4.37.4)

[Compare
Source](https://redirect.github.com/github/codeql-action/compare/v4.37.3...v4.37.4)

</details>

---

### 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.

---

- [ ] <!-- rebase-check -->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).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Updates Docker instructions to include the required Virtual Machine
settings for Rancher Desktop.

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/labeler](https://redirect.github.com/actions/labeler) |
action | major | `v6.2.0` → `v7.0.0` |

---

### Release Notes

<details>
<summary>actions/labeler (actions/labeler)</summary>

###
[`v7.0.0`](https://redirect.github.com/actions/labeler/compare/v6.2.0...v7.0.0)

[Compare
Source](https://redirect.github.com/actions/labeler/compare/v7.0.0...v7.0.0)

###
[`v7`](https://redirect.github.com/actions/labeler/compare/v6.2.0...v7.0.0)

[Compare
Source](https://redirect.github.com/actions/labeler/compare/v6.2.0...v7.0.0)

</details>

---

### 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.

---

- [ ] <!-- rebase-check -->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).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzIuNCIsInVwZGF0ZWRJblZlciI6IjQ0LjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | patch | `v7.0.0` → `v7.0.1` |

---

### Release Notes

<details>
<summary>actions/checkout (actions/checkout)</summary>

###
[`v7.0.1`](https://redirect.github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v701)

[Compare
Source](https://redirect.github.com/actions/checkout/compare/v7.0.0...v7.0.1)

- Bump github/codeql-action from 3 to 4 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2475](https://redirect.github.com/actions/checkout/pull/2475)
- Bump actions/setup-node from 4 to 6 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2477](https://redirect.github.com/actions/checkout/pull/2477)
- Bump docker/build-push-action from 6.5.0 to 7.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2478](https://redirect.github.com/actions/checkout/pull/2478)
- Bump docker/login-action from 3.3.0 to 4.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2479](https://redirect.github.com/actions/checkout/pull/2479)
- Bump actions/checkout from 6 to 7 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2488](https://redirect.github.com/actions/checkout/pull/2488)
- Bump actions/upload-artifact from 4 to 7 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2476](https://redirect.github.com/actions/checkout/pull/2476)
- eslint 9 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2474](https://redirect.github.com/actions/checkout/pull/2474)
- Bump the minor-actions-dependencies group with 2 updates by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;2499](https://redirect.github.com/actions/checkout/pull/2499)
- skip running unsafe pr check if input is default by
[@&#8203;aiqiaoy](https://redirect.github.com/aiqiaoy) in
[#&#8203;2518](https://redirect.github.com/actions/checkout/pull/2518)
- trim only ascii whitespace for branch by
[@&#8203;aiqiaoy](https://redirect.github.com/aiqiaoy) in
[#&#8203;2521](https://redirect.github.com/actions/checkout/pull/2521)
- escape values passed to --unset by
[@&#8203;aiqiaoy](https://redirect.github.com/aiqiaoy) in
[#&#8203;2530](https://redirect.github.com/actions/checkout/pull/2530)

</details>

---

### 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.

---

- [ ] <!-- rebase-check -->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).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzIuNCIsInVwZGF0ZWRJblZlciI6IjQ0LjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) | Type |
Update |
|---|---|---|---|---|---|
| [@playwright/test](https://playwright.dev)
([source](https://redirect.github.com/microsoft/playwright)) |
[`^1.62.0` →
`^1.62.1`](https://renovatebot.com/diffs/npm/@playwright%2ftest/1.62.0/1.62.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@playwright%2ftest/1.62.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@playwright%2ftest/1.62.0/1.62.1?slim=true)
| devDependencies | patch |
| mcr.microsoft.com/playwright | `v1.62.0` → `v1.62.1` |
![age](https://developer.mend.io/api/mc/badges/age/docker/mcr.microsoft.com%2fplaywright/v1.62.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/docker/mcr.microsoft.com%2fplaywright/v1.62.0/v1.62.1?slim=true)
| final | patch |
| [playwright-core](https://playwright.dev)
([source](https://redirect.github.com/microsoft/playwright)) |
[`^1.62.0` →
`^1.62.1`](https://renovatebot.com/diffs/npm/playwright-core/1.62.0/1.62.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/playwright-core/1.62.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/playwright-core/1.62.0/1.62.1?slim=true)
| devDependencies | patch |

---

### Release Notes

<details>
<summary>microsoft/playwright (@&#8203;playwright/test)</summary>

###
[`v1.62.1`](https://redirect.github.com/microsoft/playwright/compare/v1.62.0...26a9e470a7b3c7822084b09fb7f13902c5f37b51)

[Compare
Source](https://redirect.github.com/microsoft/playwright/compare/v1.62.0...v1.62.1)

</details>

---

### 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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->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).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…1295)

Issue number: internal

---------

## What is the current behavior?

Currently, `ion-checkbox` and `ion-toggle` never show a keyboard focus
indicator. `ion-checkbox` doesn't set the `ion-focusable` class and has
no focus styles in the `ios` or `md` themes, so it can't receive the
`ion-focused` class or render a ring. `ion-toggle`'s only focus rule
(`:host(.ion-focused) input`) targets the native `input`, which is
`display: none`, so it never renders.

Separately, `ion-checkbox`, `ion-radio`, and `ion-toggle` show no focus
indicator inside an `ion-item` that holds more than one input.
`ion-checkbox` and `ion-radio` disable their own indicator in an item on
the assumption the item draws one, but a multi-input item has no cover
to highlight, so nothing is focused.

## What is the new behavior?

`ion-checkbox` now sets `ion-focusable` when it isn't deferring to an
item, with matching `ios` and `md` focus styles, so a standalone
checkbox shows the same indicator `ion-radio` already did. Both
`ion-checkbox` and `ion-radio` also become focusable inside a
multi-input item, detected through the item's `item-multiple-inputs`
class, so each control shows its own indicator when the item can't.

`ion-toggle` always shows its own indicator. Unlike checkbox and radio,
it's excluded from the item's input cover, so an item never highlights
on its behalf, in or out of a multi-input item. Its dead `input` focus
rule is replaced with a ring on the visible track in both themes.

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

## Other information

Preview checkbox:
- iOS:
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/checkbox/test/basic?ionic:mode=ios
- MD:
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/checkbox/test/basic?ionic:mode=md

Preview toggle:
-
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/toggle/test/basic?ionic:mode=ios
-
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/toggle/test/basic?ionic:mode=md

Preview radio:
-
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/radio/test/basic?ionic:mode=ios
-
https://ionic-framework-git-fw-7585-fw-7586-ionic1.vercel.app/src/components/radio/test/basic?ionic:mode=md

---------

Co-authored-by: ionitron <hi@ionicframework.com>
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.4.2`
→
`8.5.0`](https://renovatebot.com/diffs/npm/@capacitor%2fcore/8.4.2/8.5.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fcore/8.5.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fcore/8.4.2/8.5.0?slim=true)
|

---

### Release Notes

<details>
<summary>ionic-team/capacitor (@&#8203;capacitor/core)</summary>

###
[`v8.5.0`](https://redirect.github.com/ionic-team/capacitor/blob/HEAD/CHANGELOG.md#850-2026-07-31)

[Compare
Source](https://redirect.github.com/ionic-team/capacitor/compare/8.4.2...8.5.0)

##### Bug Fixes

- **cli:** support TypeScript 7 when loading capacitor.config.ts
([#&#8203;8534](https://redirect.github.com/ionic-team/capacitor/issues/8534))
([4c1c870](https://redirect.github.com/ionic-team/capacitor/commit/4c1c8709413b9c19b008c99122ca330cc3c90e6f))

##### Features

- **cli:** add migrator functionality for adopting UIScene
([#&#8203;8544](https://redirect.github.com/ionic-team/capacitor/issues/8544))
([984fa85](https://redirect.github.com/ionic-team/capacitor/commit/984fa85ba0adab0aacf895aed6323bf4b503dccb))
- **ios:** UIScene Support
([#&#8203;8536](https://redirect.github.com/ionic-team/capacitor/issues/8536))
([3fa04a3](https://redirect.github.com/ionic-team/capacitor/commit/3fa04a357c92af34cd6fccb8124791963804a9dc))

####
[8.4.2](https://redirect.github.com/ionic-team/capacitor/compare/8.4.1...8.4.2)
(2026-07-14)

##### Bug Fixes

- **android:** explicitly grant URI permissions for image capture intent
([#&#8203;8526](https://redirect.github.com/ionic-team/capacitor/issues/8526))
([6f2d328](https://redirect.github.com/ionic-team/capacitor/commit/6f2d3283897a375d09ca9ec8784a9f0d65f0a530))

####
[8.4.1](https://redirect.github.com/ionic-team/capacitor/compare/8.4.0...8.4.1)
(2026-06-19)

##### Bug Fixes

- **cli:** make SPM dependency patch work on prereleases
([#&#8203;8508](https://redirect.github.com/ionic-team/capacitor/issues/8508))
([6048e90](https://redirect.github.com/ionic-team/capacitor/commit/6048e90171afa0229a3c25b52a23c377c6bb804c))
- **cli:** patch Capacitor SPM dependency version in plugins
([#&#8203;8492](https://redirect.github.com/ionic-team/capacitor/issues/8492))
([28bb2c6](https://redirect.github.com/ionic-team/capacitor/commit/28bb2c687069dfdd6aa7abc866004a1c6388d103))

</details>

---

### 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.

---

- [ ] <!-- rebase-check -->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).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Issue number: resolves #31315 
---------

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

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

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

When an `ion-refresher`'s host page is unmounted while the native
refresher is still being set up, Ionic throws an uncaught `TypeError`
from the gesture utility:

```
TypeError: Cannot read properties of undefined (reading '__zone_symbol__addEventListener')
    at addEventListener (core/src/utils/gesture/listener.ts:21)
    at Object.enable (core/src/utils/gesture/index.ts)
    at Refresher.disabledChanged (core/src/components/refresher/refresher.tsx:125)
    at Refresher.setupMDNativeRefresher (core/src/components/refresher/refresher.tsx:372)
```

`setupMDNativeRefresher()` and `setupiOSNativeRefresher()` both do:

```ts
this.gesture = (await import('../../utils/gesture')).createGesture({
  el: this.scrollEl!,
  ...
});
```

The dynamic import yields to the event loop. If the refresher is
disconnected while it resolves, `disconnectedCallback()` (line 547-549)
has already run `this.scrollEl = undefined`, so `createGesture` receives
`el: undefined`, and the subsequent `disabledChanged()` →
`gesture.enable(true)` calls `addEventListener(undefined, ...)`, which
throws.

Because this happens inside an async method that nothing awaits, it
surfaces as an **unhandled promise rejection** — it cannot be caught by
a framework error boundary; this crashes my app in playwright sometimes
when components are mounted very quickly.

The `!` non-null assertions on `this.scrollEl` are what hide this from
TypeScript.

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

If the refresher is disconnected while the gesture module is being
imported, setup aborts quietly. No gesture is created, and no error is
thrown.

## Does this introduce a breaking change?

- [ ] Yes
- [ X] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

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

---------

Co-authored-by: Shane <shane@shanessite.net>
…ken contributing link (#31250)

Issue number: resolves #31251

---------

## What is the current behavior?

While working on e2e tests in the angular directory, I encountered a few
documentation issues that made the local development workflow more
difficult to follow.

- The **"See Ionic's E2E testing guide"** link in `CONTRIBUTING.md`
resolves to a 404 when viewed from GitHub's **Contributing** tab because
the relative path is not resolved correctly.
- Broken link:
https://github.com/ionic-team/ionic-framework/blob/core/src/utils/test/playwright/docs/README.md
- Intended destination:
https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/test/playwright/docs/README.md

- The Angular testing guide (`docs/angular/testing.md`) omits several
steps that are necessary to successfully test local framework changes,
including:
- Removing existing `npm link` relationships before starting a new sync
cycle.
  - Building `packages/angular-server`.
- Installing Playwright browser dependencies before running Playwright
tests for the first time.

These omissions made it difficult to reproduce and validate additions to
Angular tests I was adding as part of a separate PR.

## What is the new behavior?

This PR does the following:

- Fixes the broken link to the Ionic E2E testing guide in
`CONTRIBUTING.md` by using a repository-root-relative path.
- Updating `docs/angular/testing.md` to document the complete workflow
for syncing local Angular framework changes.
- Documents that `packages/angular-server` should be built as part of
the local testing workflow.
- Documents that contributors should remove previous `npm link`
relationships before beginning a new sync cycle.
- Clarifies the recommended workflow for syncing local package changes.
- Documents the Playwright browser installation step required before
running Playwright tests for the first time.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

---------

Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
Issue number: resolves #31152

## What is the current behavior?

In `IonRouter.handleHistoryChange`, the URL-change guard compares
mismatched operands:

```ts
const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
if (leavingUrl !== location.pathname) { ... }
```

The left side includes `search`, the right side does not. For any route
with a non-empty query string, the comparison is **always** unequal, so
the transition block runs on every history event — including no-op
popstates over same-URL entries pushed via `window.history.pushState`.

Concretely: when a same-URL history entry on a search-bearing route is
popped, `action === 'POP'` is processed and the IRO transitions to
`currentRoute.pushedByRoute`. The browser URL doesn't change but the
rendered view does — the user gets silently teleported to a different
page in the stack.

Minimal repro: on any search-bearing route, run in the console:
```js
window.history.pushState({}, '', window.location.href);
window.history.back();
```
The current behavior swaps the rendered page to the previous entry in
`locationHistory` while the URL stays put. Expected: no visible change.

Full repro and analysis in #31152. This is also the root cause behind
the symptom reported in #25534 (framed there as a transition-rerender
flash).

## What is the new behavior?

The right side of the comparison now also includes `search`, so the
block only runs when the URL actually changed:

```ts
const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
const currentUrl = location.pathname + (location.search || '');
if (leavingUrl !== currentUrl) { ... }
```

Behavior matrix:
- Search-only navigations (e.g. `routerPush(samePath + newSearch)`):
**unchanged** — pathname matches but search differs, block still runs.
- Pathname changes: **unchanged** — pathnames differ, block still runs.
- No-op popstates on search-bearing routes: **fixed** — pathname +
search both match, block correctly skipped.

A new Cypress regression test is included
(`packages/react-router/test/base/tests/e2e/specs/routing.cy.js`) that
pushes a same-URL state on a search-bearing route, calls
`history.back()`, and asserts the page does not teleport.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

The comparison becomes stricter (skips the block in more cases than
before), but only for the cases where the URL didn't actually change.
All cases where the URL *did* change are still routed through the
existing transition logic unchanged.

## Other information

Happy to iterate on the fix shape if there's a preferred alternative —
e.g. gating behind a flag for backward compat, or restructuring the
guard differently. The one-line change above is the smallest possible
fix that keeps existing behavior for every "real URL change" case.

---------

Co-authored-by: ShaneK <shane@shanessite.net>
…ty (#31249)

## Summary

Fixes #18550 — `ion-fab-button` with `type="submit"` does not submit its
parent form.

**Root cause:** `ion-fab-button` uses Shadow DOM, so its internal
`<button>` element is not part of the form-associated elements in the
light DOM. Clicking it (or pressing Enter in a text field) never
triggers the form's `submit` event.

**Fix:** Port the hidden-button pattern already used by `ion-button`:
- On render (when `type !== 'button'`), inject a hidden `<button>` into
the associated form in the light DOM.
- On click, call `ev.preventDefault()` to stop the shadow button from
acting, then `.click()` the hidden button — which is a proper form
participant and triggers submission.
- Add a `form` prop (`string | HTMLFormElement`) so the button can
target a form outside its DOM subtree (same API as `ion-button`).
- Keep the `disabled` watcher in sync with the hidden button so a
disabled fab-button cannot submit.

## Changes

- `core/src/components/fab-button/fab-button.tsx` — core fix
- `core/src/components/fab-button/test/form/fab-button.e2e.ts` —
Playwright e2e tests (closest form, form by id, form by reference, Enter
key, disabled, reset)
- `core/src/components/fab-button/test/form/fab-button.spec.ts` — unit
test asserting the hidden button is not duplicated on re-renders
- `core/src/components/fab-button/test/form/index.html` — visual test
page

## Test plan

- [ ] `npm run test.spec` passes (unit test for hidden-button dedup)
- [ ] `npm run test.e2e` passes for
`fab-button/test/form/fab-button.e2e.ts`
- [ ] Manual: `ion-fab-button type="submit"` inside a `<form>` submits
on click
- [ ] Manual: pressing Enter in a form input triggers submit
- [ ] Manual: `ion-fab-button type="reset"` resets form fields
- [ ] Manual: disabled fab-button does not submit
- [ ] Manual: `form="id"` targets an external form

---------

Co-authored-by: ShaneK <shane@shanessite.net>
Issue number: internal

---------

## What is the current behavior?

Currently, `prepareLazyLoaded` in `ion-tab` sets `loaded = true` before
it attempts the attach, and the `try/catch` wrapped around
`attachComponent` is synchronous while `attachComponent` itself is
`async`. So when the first attach rejects, the rejection skips the
`catch` entirely and `loaded` is already `true`, which means the `if
(!this.loaded && ...)` guard blocks every later attempt. The tab renders
as an empty page for the rest of the session, even if whatever caused
the failure is fixed.

`ion-tabs` activates a tab by setting `active` rather than by awaiting
the tab's `setActive()`, so the failure goes through the
`@Watch('active')` path. That call isn't awaited, so on rejection the
error surfaces as an unhandled promise rejection and `printIonError`
never runs. `select()` resolves successfully while the tab is blank, so
there's no signal to the caller either.

## What is the new behavior?

With this change, `prepareLazyLoaded` caches the in-flight attach
promise instead of flipping a boolean up front. Concurrent activations
still share a single attempt, which is what the old `loaded = true`
ordering was providing, but the cache is only cleared when the attach
rejects, so the next activation retries. The unawaited call in the
`active` watcher now has a `.catch` that routes the error to
`printIonError`, which is what the unreachable `catch` block was there
for.

`setActive()` still rejects on a failed attach and still leaves `active`
as `false`, so that part is unchanged.

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

## Other information

There's no stock test page that reproduces the failure, since it needs a
delegate that rejects, but the tabs pages confirm normal lazy loading
still works:

- [Tabs:
Basic](https://ionic-framework-git-fw-7296-ionic1.vercel.app/src/components/tabs/test/basic)
@thetaPC
thetaPC requested a review from a team as a code owner August 5, 2026 17:19
@thetaPC
thetaPC requested review from brandyscarney and removed request for a team August 5, 2026 17:19
@github-actions github-actions Bot added package: core @ionic/core package package: angular @ionic/angular package package: vue @ionic/vue package package: react @ionic/react package labels Aug 5, 2026
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ionic-framework Ready Ready Preview Aug 5, 2026 5:24pm

Request Review

@thetaPC
thetaPC merged commit 35fc04d into 8.8.x Aug 5, 2026
98 checks passed
@thetaPC
thetaPC deleted the release-8.8.17 branch August 5, 2026 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: angular @ionic/angular package package: core @ionic/core package package: react @ionic/react package package: vue @ionic/vue package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants