Compare commits

...

19 Commits

Author SHA1 Message Date
Maria Hutt
80e9ee2314 test(chip): reduce file space 2026-01-22 08:12:25 -08:00
Maria Hutt
25a7adf7cc test(chip): add to the states 2026-01-21 16:55:44 -08:00
Brandy Smith
442e3e9831 fix(toast): keep icon on the same line as long message in stacked layout (#30923)
Issue number: resolves #30908

---------

## What is the current behavior?
Toast with an icon and long message using a stacked layout will wrap the
message below the icon.

## What is the new behavior?
- Apply `flex: 1` to `.toast-content` regardless of layout, which makes
sure the content does not get wrapped under the icon
- Adds an e2e test for a stacked toast with a long message to
`toast/test/layout`

## Does this introduce a breaking change?
- [ ] Yes
- [x] No

## Other information

- Dev build: `8.7.18-dev.11768592717.14a59d2f`
- Preview:
[Layout](https://ionic-framework-git-fw-7035-ionic1.vercel.app/src/components/toast/test/layout/)

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2026-01-19 21:48:13 +00:00
renovate[bot]
62d880d620 chore(deps): update dependency @capacitor/core to v8.0.1 (#30914)
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.0.0`
→
`8.0.1`](https://renovatebot.com/diffs/npm/@capacitor%2fcore/8.0.0/8.0.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fcore/8.0.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fcore/8.0.0/8.0.1?slim=true)
|

---

### Release Notes

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

###
[`v8.0.1`](https://redirect.github.com/ionic-team/capacitor/blob/HEAD/CHANGELOG.md#801-2026-01-13)

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

##### Bug Fixes

- **android:** Remove calculated bottom inset if keyboard is visible
([#&#8203;8280](https://redirect.github.com/ionic-team/capacitor/issues/8280))
([196b642](196b642236))
- **cli:** Support wireless iOS devices in `cap run`
([#&#8203;8301](https://redirect.github.com/ionic-team/capacitor/issues/8301))
([dcb368c](dcb368c335))
- **cli:** use latest native-run
([#&#8203;8296](https://redirect.github.com/ionic-team/capacitor/issues/8296))
([121d830](121d83013f))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekday before 11am" (UTC),
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:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NC41IiwidXBkYXRlZEluVmVyIjoiNDIuNzQuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 14:56:28 +00:00
renovate[bot]
4eca8d39d8 chore(deps): update actions/setup-node action to v6.2.0 (#30918)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-node](https://redirect.github.com/actions/setup-node) |
action | minor | `v6.1.0` → `v6.2.0` |

---

### Release Notes

<details>
<summary>actions/setup-node (actions/setup-node)</summary>

###
[`v6.2.0`](https://redirect.github.com/actions/setup-node/compare/v6.1.0...v6.2.0)

[Compare
Source](https://redirect.github.com/actions/setup-node/compare/v6.1.0...v6.2.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "every weekday before 11am" (UTC),
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:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NC41IiwidXBkYXRlZEluVmVyIjoiNDIuNzQuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 14:54:52 +00:00
Brandy Smith
040bdf78c5 merge release-8.7.17 (#30916)
v8.7.17
2026-01-14 14:43:10 -05:00
Brandy Smith
1bccf76d35 chore(changelog): remove duplicate fixes in wrong version 2026-01-14 14:22:32 -05:00
ionitron
dd1c1e8fa3 chore(): update package lock files 2026-01-14 18:59:30 +00:00
ionitron
d7b4d0690b v8.7.17 2026-01-14 18:58:56 +00:00
Brandy Smith
95b87020d6 chore(github): do not close issues as stale when they are external bugs (#30915)
Ionitron keeps closing issues with `bug: external` as stale:
https://github.com/ionic-team/ionic-framework/issues/27052#event-21879561018

This PRs adds `bug: external` as an exempt label when closing issues as
stale.

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2026-01-14 16:18:00 +00:00
Shane
ab733b71dd fix(input): prevent Android TalkBack from focusing label separately (#30895)
Issue number: resolves internal

---------

<!-- 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?
When using `ion-input` with a label on Android, TalkBack treats the
visual label text as a separate focusable element. This causes the
initial focus to land on the label instead of the input field, creating
a confusing experience for screen reader users.

## What is the new behavior?
The label text wrapper is now hidden from the accessibility tree via
`aria-hidden="true"`, while the native input maintains proper labeling
through `aria-labelledby`. This ensures Android TalkBack focuses
directly on the input field while still announcing the label correctly.

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

Current dev build:
```
8.7.16-dev.11767032989.1ae720d0
```
2026-01-14 16:17:33 +00:00
Shane
f99d0007a8 fix(tab-bar): prevent keyboard controller memory leak on rapid mount/unmount (#30906)
Issue number: resolves internal

---------

## What is the current behavior?
When `ion-tab-bar` is rapidly mounted and unmounted, a race condition in
connectedCallback can cause the keyboard controller to be created after
the component has been disconnected. This results in orphaned event
listeners (`keyboardWillShow`, `keyboardWillHide`) on the window object
that are never cleaned up, causing a memory leak.

## What is the new behavior?
The keyboard controller is now properly destroyed in all scenarios:
- If the component is disconnected while createKeyboardController is
pending, the promise is tracked and destroyed when it resolves
- If a new connectedCallback runs before the previous async completes,
the stale controller is destroyed

The promise tracking pattern ensures only the most recent async
operation assigns its result

## 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. -->
Current dev build:
```
8.7.17-dev.11767895575.16ea7cef
```

I was unable to find a way to create tests that accurately identified if
this problem was occurring. Memory leaks are notoriously difficult to
created automated tests for. I ultimately removed my previous attempts
because I didn't want to give a false sense of security.
2026-01-14 15:59:14 +00:00
Shane
3b3318da51 fix(input): prevent placeholder from overlapping start slot during scroll assist (#30896)
Issue number: resolves internal

---------

<!-- 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?
On iOS, when focusing an `ion-input` or `ion-textarea` that requires
scrolling into view (scroll assist), the placeholder text shifts to the
left and overlaps any content in the start slot (e.g., icons). This
occurs because the cloned input used during scroll assist is positioned
at the container's left edge rather than at the native input's actual
position. Additionally, when quickly switching between inputs before
scroll assist completes, focus jumps back to the original input.

## What is the new behavior?
The cloned input is now positioned at the same offset as the native
input, preventing the placeholder from shifting or overlapping start
slot content during scroll assist. This works correctly for both LTR and
RTL layouts. Also, scroll assist no longer steals focus back if the user
has moved focus to another element while scrolling was in progress.

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

Current dev build:
```
8.7.16-dev.11767042721.11309185
```
2026-01-13 18:42:50 +00:00
Shane
07b46d745a release-8.7.16 (#30903)
v8.7.16

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-12-31 13:32:10 -08:00
ionitron
37f87b39c4 chore(): update package lock files 2025-12-31 13:20:48 -08:00
ionitron
f71f4bf454 v8.7.16 2025-12-31 13:20:48 -08:00
Shane
36f4b4d600 fix(modal): prevent card modal animation on viewport resize when modal is closed (#30894)
Issue number: resolves #30679

---------

<!-- 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?
When a page contains a card modal with a `presentingElement`, resizing
the viewport (e.g., rotating from portrait to landscape) triggers the
card modal's "lean back" animation on the presenting element, even when
the modal has never been opened.

## What is the new behavior?
Viewport resize events no longer trigger the presenting element
animation when the modal is not presented. The animation only runs when
the modal is actually open.

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

Current dev build:
```
8.7.16-dev.11767028735.16932cea
```
2025-12-31 13:20:48 -08:00
Shane
e5634d45ee fix(modal): prevent card modal animation on viewport resize when modal is closed (#30894)
Issue number: resolves #30679

---------

<!-- 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?
When a page contains a card modal with a `presentingElement`, resizing
the viewport (e.g., rotating from portrait to landscape) triggers the
card modal's "lean back" animation on the presenting element, even when
the modal has never been opened.

## What is the new behavior?
Viewport resize events no longer trigger the presenting element
animation when the modal is not presented. The animation only runs when
the modal is actually open.

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

Current dev build:
```
8.7.16-dev.11767028735.16932cea
```
2025-12-30 10:33:41 -08:00
Shane
7d6430738e merge release-8.7.15 (#30887)
v8.7.15
2025-12-23 11:36:36 -08:00
77 changed files with 1007 additions and 360 deletions

View File

@@ -22,7 +22,7 @@ runs:
using: 'composite'
steps:
- name: 🟢 Configure Node for Publish
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'

View File

@@ -40,7 +40,7 @@ comment:
If the requested feature is something you would find useful for your applications, please react to the original post with 👍 (`+1`). If you would like to provide an additional use case for the feature, please post a comment.
The team will review this feedback and make a final decision. Any decision will be posted on this thread, but please note that we may ultimately decide not to pursue this feature.
@@ -83,6 +83,7 @@ stale:
exemptLabels:
- "good first issue"
- "triage"
- "bug: external"
- "type: bug"
- "type: feature request"
- "needs: investigation"

View File

@@ -3,7 +3,7 @@ description: 'Build Ionic Angular Server'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -9,7 +9,7 @@ runs:
using: 'composite'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x

View File

@@ -9,7 +9,7 @@ runs:
using: 'composite'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies

View File

@@ -3,7 +3,7 @@ description: 'Build Ionic React Router'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -3,7 +3,7 @@ description: 'Build Ionic React'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -3,7 +3,7 @@ description: 'Builds Ionic Vue Router'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -3,7 +3,7 @@ description: 'Build Ionic Vue'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -3,7 +3,7 @@ description: 'Test Core Clean Build'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x

View File

@@ -3,7 +3,7 @@ description: 'Test Core Lint'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies

View File

@@ -13,7 +13,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- name: 🕸️ Install Dependencies

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -7,7 +7,7 @@ on:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: actions/download-artifact@v7

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
### Bug Fixes
* **input:** prevent Android TalkBack from focusing label separately ([#30895](https://github.com/ionic-team/ionic-framework/issues/30895)) ([ab733b7](https://github.com/ionic-team/ionic-framework/commit/ab733b71dd355d9486757f219fe09acaefeeefcc))
* **input:** prevent placeholder from overlapping start slot during scroll assist ([#30896](https://github.com/ionic-team/ionic-framework/issues/30896)) ([3b3318d](https://github.com/ionic-team/ionic-framework/commit/3b3318da513b199128f3822bd8226797cd118b0f))
* **tab-bar:** prevent keyboard controller memory leak on rapid mount/unmount ([#30906](https://github.com/ionic-team/ionic-framework/issues/30906)) ([f99d000](https://github.com/ionic-team/ionic-framework/commit/f99d0007a8ffc9c7d3d2636e912c37c12112b21d))
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
### Bug Fixes
* **modal:** prevent card modal animation on viewport resize when modal is closed ([#30894](https://github.com/ionic-team/ionic-framework/issues/30894)) ([e5634d4](https://github.com/ionic-team/ionic-framework/commit/e5634d45ee5fd32715f6e6b75e0448f74ee1f8f2)), closes [#30679](https://github.com/ionic-team/ionic-framework/issues/30679)
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
### Bug Fixes
* **input:** prevent Android TalkBack from focusing label separately ([#30895](https://github.com/ionic-team/ionic-framework/issues/30895)) ([ab733b7](https://github.com/ionic-team/ionic-framework/commit/ab733b71dd355d9486757f219fe09acaefeeefcc))
* **input:** prevent placeholder from overlapping start slot during scroll assist ([#30896](https://github.com/ionic-team/ionic-framework/issues/30896)) ([3b3318d](https://github.com/ionic-team/ionic-framework/commit/3b3318da513b199128f3822bd8226797cd118b0f))
* **tab-bar:** prevent keyboard controller memory leak on rapid mount/unmount ([#30906](https://github.com/ionic-team/ionic-framework/issues/30906)) ([f99d000](https://github.com/ionic-team/ionic-framework/commit/f99d0007a8ffc9c7d3d2636e912c37c12112b21d))
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
### Bug Fixes
* **modal:** prevent card modal animation on viewport resize when modal is closed ([#30894](https://github.com/ionic-team/ionic-framework/issues/30894)) ([e5634d4](https://github.com/ionic-team/ionic-framework/commit/e5634d45ee5fd32715f6e6b75e0448f74ee1f8f2)), closes [#30679](https://github.com/ionic-team/ionic-framework/issues/30679)
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)

47
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",
@@ -94,6 +94,7 @@
"version": "7.16.12",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.16.8",
@@ -628,11 +629,12 @@
"license": "MIT"
},
"node_modules/@capacitor/core": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.0.0.tgz",
"integrity": "sha512-250HTVd/W/KdMygoqaedisvNbHbpbQTN2Hy/8ZYGm1nAqE0Fx7sGss4l0nDg33STxEdDhtVRoL2fIaaiukKseA==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.0.1.tgz",
"integrity": "sha512-5UqSWxGMp/B8KhYu7rAijqNtYslhcLh+TrbfU48PfdMDsPfaU/VY48sMNzC22xL8BmoFoql/3SKyP+pavTOvOA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -870,6 +872,7 @@
"version": "4.33.0",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "4.33.0",
"@typescript-eslint/types": "4.33.0",
@@ -1807,6 +1810,7 @@
"node_modules/@stencil/core": {
"version": "4.38.0",
"license": "MIT",
"peer": true,
"bin": {
"stencil": "bin/stencil"
},
@@ -2231,6 +2235,7 @@
"version": "6.7.2",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.7.2",
"@typescript-eslint/types": "6.7.2",
@@ -2456,7 +2461,6 @@
"integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.25",
@@ -2471,7 +2475,6 @@
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"engines": {
"node": ">=0.12"
},
@@ -2484,8 +2487,7 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.25",
@@ -2493,7 +2495,6 @@
"integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-core": "3.5.25",
"@vue/shared": "3.5.25"
@@ -2505,7 +2506,6 @@
"integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.25",
@@ -2523,8 +2523,7 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@vue/compiler-sfc/node_modules/postcss": {
"version": "8.5.6",
@@ -2546,7 +2545,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -2562,7 +2560,6 @@
"integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/shared": "3.5.25"
@@ -2574,7 +2571,6 @@
"integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/shared": "3.5.25"
}
@@ -2585,7 +2581,6 @@
"integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/shared": "3.5.25"
@@ -2597,7 +2592,6 @@
"integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.25",
"@vue/runtime-core": "3.5.25",
@@ -2611,7 +2605,6 @@
"integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.25",
"@vue/shared": "3.5.25"
@@ -2625,8 +2618,7 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz",
"integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@zeit/schemas": {
"version": "2.21.0",
@@ -2649,6 +2641,7 @@
"version": "7.4.0",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3809,8 +3802,7 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
@@ -4104,6 +4096,7 @@
"version": "7.32.0",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.3",
@@ -7299,7 +7292,6 @@
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
@@ -7621,7 +7613,6 @@
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -7976,6 +7967,7 @@
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"playwright-core": "cli.js"
},
@@ -7987,6 +7979,7 @@
"version": "7.0.35",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
@@ -8092,6 +8085,7 @@
"version": "0.36.2",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"postcss": ">=5.0.0"
}
@@ -8140,6 +8134,7 @@
"version": "2.6.1",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
@@ -8497,6 +8492,7 @@
"version": "2.35.1",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -8718,7 +8714,6 @@
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "8.7.15",
"version": "8.7.17",
"description": "Base components for Ionic",
"engines": {
"node": ">= 16"

View File

@@ -1,23 +1,27 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
/**
* This behavior does not vary across modes/directions.
*/
test.describe(title('chip: states'), () => {
test('should render disabled state', async ({ page }) => {
await page.setContent(
`<ion-chip disabled="true">
<ion-label>Disabled</ion-label>
</ion-chip>`,
config
);
await page.goto(`/src/components/chip/test/states`, config);
const chip = page.locator('ion-chip');
const container = page.locator('#disabled');
await expect(chip).toHaveScreenshot(screenshot(`chip-disabled`));
await expect(container).toHaveScreenshot(screenshot(`chip-disabled`));
});
test('should render activated state', async ({ page }) => {
await page.goto(`/src/components/chip/test/states`, config);
const container = page.locator('#activated');
await expect(container).toHaveScreenshot(screenshot(`chip-activated`));
});
test('should custom chip', async ({ page }) => {
await page.setContent(
`

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -19,211 +19,285 @@
<ion-content>
<h3>Default</h3>
<p>
<ion-chip>
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip style="border-radius: 4px">
<ion-label>Border Radius</ion-label>
</ion-chip>
<ion-chip>
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>With Icon</ion-label>
</ion-chip>
<ion-chip>
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>With Avatar</ion-label>
</ion-chip>
<ion-chip>
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>With Icon and Avatar</ion-label>
<ion-icon name="close-circle"></ion-icon>
</ion-chip>
</p>
<p>
<ion-chip class="ion-focused">
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip style="border-radius: 4px" class="ion-focused">
<ion-label>Border Radius</ion-label>
</ion-chip>
<ion-chip class="ion-focused">
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>With Icon</ion-label>
</ion-chip>
<ion-chip class="ion-focused">
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>With Avatar</ion-label>
</ion-chip>
<ion-chip class="ion-focused">
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>With Icon and Avatar</ion-label>
<ion-icon name="close-circle"></ion-icon>
</ion-chip>
</p>
<div id="default">
<p>
<ion-chip>
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip color="primary">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip color="success">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip color="warning">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip color="danger">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<h3>Colors</h3>
<p>
<ion-chip color="primary">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary">
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>Tertiary with Icon</ion-label>
</ion-chip>
<ion-chip color="success">
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>Success with Avatar</ion-label>
</ion-chip>
<ion-chip color="warning">
<ion-label>Warning with Icon</ion-label>
<ion-icon name="calendar"></ion-icon>
</ion-chip>
<ion-chip color="danger">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<p>
<ion-chip color="primary" class="ion-focused">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary" class="ion-focused">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary" class="ion-focused">
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>Tertiary with Icon</ion-label>
</ion-chip>
<ion-chip color="success" class="ion-focused">
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
<ion-label>Success with Avatar</ion-label>
</ion-chip>
<ion-chip color="warning" class="ion-focused">
<ion-label>Warning with Icon</ion-label>
<ion-icon name="calendar"></ion-icon>
</ion-chip>
<ion-chip color="danger" class="ion-focused">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light" class="ion-focused">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium" class="ion-focused">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark" class="ion-focused">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<h3>Outline</h3>
<p>
<ion-chip outline>
<ion-label>Outline</ion-label>
</ion-chip>
<ion-chip outline color="danger">
<ion-label>Danger Outline</ion-label>
</ion-chip>
<ion-chip outline color="secondary">
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>Secondary Outline with Icon</ion-label>
</ion-chip>
<ion-chip outline color="primary">
<ion-label>Primary Outline with Avatar</ion-label>
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
</ion-chip>
<ion-chip outline>
<ion-icon name="git-pull-request"></ion-icon>
<ion-label>Outline with Icon and Avatar</ion-label>
<ion-icon name="close-circle"></ion-icon>
</ion-chip>
</p>
<p>
<ion-chip outline class="ion-focused">
<ion-label>Outline</ion-label>
</ion-chip>
<ion-chip outline color="danger" class="ion-focused">
<ion-label>Danger Outline</ion-label>
</ion-chip>
<ion-chip outline color="secondary" class="ion-focused">
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>Secondary Outline with Icon</ion-label>
</ion-chip>
<ion-chip outline color="primary" class="ion-focused">
<ion-label>Primary Outline with Avatar</ion-label>
<ion-avatar>
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjYzVkYmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz48cGF0aCBkPSJNMjU2IDMwNGM2MS42IDAgMTEyLTUwLjQgMTEyLTExMlMzMTcuNiA4MCAyNTYgODBzLTExMiA1MC40LTExMiAxMTIgNTAuNCAxMTIgMTEyIDExMnptMCA0MGMtNzQuMiAwLTIyNCAzNy44LTIyNCAxMTJ2NTZoNDQ4di01NmMwLTc0LjItMTQ5LjgtMTEyLTIyNC0xMTJ6IiBmaWxsPSIjODJhZWZmIi8+PC9zdmc+"
/>
</ion-avatar>
</ion-chip>
<ion-chip outline class="ion-focused">
<ion-icon name="git-pull-request"></ion-icon>
<ion-label>Outline with Icon and Avatar</ion-label>
<ion-icon name="close-circle"></ion-icon>
</ion-chip>
</p>
<p>
<ion-chip outline>
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip outline color="primary">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip outline color="secondary">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip outline color="tertiary">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip outline color="success">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip outline color="warning">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip outline color="danger">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip outline color="light">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip outline color="medium">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip outline color="dark">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
</div>
<h3>Disabled</h3>
<div id="disabled">
<p>
<ion-chip disabled>
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip color="primary" disabled>
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary" disabled>
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary" disabled>
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip color="success" disabled>
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip color="warning" disabled>
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip color="danger" disabled>
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light" disabled>
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium" disabled>
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark" disabled>
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<p>
<ion-chip outline disabled>
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip outline color="primary" disabled>
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip outline color="secondary" disabled>
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip outline color="tertiary" disabled>
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip outline color="success" disabled>
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip outline color="warning" disabled>
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip outline color="danger" disabled>
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip outline color="light" disabled>
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip outline color="medium" disabled>
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip outline color="dark" disabled>
<ion-label>Dark</ion-label>
</ion-chip>
</p>
</div>
<h3>Activated</h3>
<div id="activated">
<p>
<ion-chip class="ion-activated">
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip color="primary" class="ion-activated">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary" class="ion-activated">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary" class="ion-activated">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip color="success" class="ion-activated">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip color="warning" class="ion-activated">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip color="danger" class="ion-activated">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light" class="ion-activated">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium" class="ion-activated">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark" class="ion-activated">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<p>
<ion-chip outline class="ion-activated">
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip outline color="primary" class="ion-activated">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip outline color="secondary" class="ion-activated">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip outline color="tertiary" class="ion-activated">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip outline color="success" class="ion-activated">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip outline color="warning" class="ion-activated">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip outline color="danger" class="ion-activated">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip outline color="light" class="ion-activated">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip outline color="medium" class="ion-activated">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip outline color="dark" class="ion-activated">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
</div>
<h3>Focused</h3>
<p>
<ion-chip disabled>
<ion-label>Disabled</ion-label>
</ion-chip>
<ion-chip outline color="danger" class="ion-focused" disabled>
<ion-label>Disabled Outline</ion-label>
</ion-chip>
<ion-chip color="secondary" class="ion-focused" disabled>
<ion-icon name="checkmark-circle"></ion-icon>
<ion-label>Disabled Secondary with Icon</ion-label>
</ion-chip>
<ion-chip outline class="ion-focused" disabled>
<ion-icon name="git-pull-request"></ion-icon>
<ion-label>Disabled Outline with Icon and Avatar</ion-label>
<ion-icon name="close-circle"></ion-icon>
</ion-chip>
`ion-chip` is not focusable by default, meaning it doesn't use `ion-focusable`. But it does have focus styles
that target the `:focus` pseudo-class. In order to see the focus styles, you need to add tabindex="0" and tab
into the chip.
</p>
<div id="focused">
<p>
<ion-chip tabindex="0">
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip color="primary" tabindex="0">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip color="secondary" tabindex="0">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip color="tertiary" tabindex="0">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip color="success" tabindex="0">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip color="warning" tabindex="0">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip color="danger" tabindex="0">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip color="light" tabindex="0">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip color="medium" tabindex="0">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip color="dark" tabindex="0">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
<p>
<ion-chip outline tabindex="0">
<ion-label>Default</ion-label>
</ion-chip>
<ion-chip outline color="primary" tabindex="0">
<ion-label>Primary</ion-label>
</ion-chip>
<ion-chip outline color="secondary" tabindex="0">
<ion-label>Secondary</ion-label>
</ion-chip>
<ion-chip outline color="tertiary" tabindex="0">
<ion-label>Tertiary</ion-label>
</ion-chip>
<ion-chip outline color="success" tabindex="0">
<ion-label>Success</ion-label>
</ion-chip>
<ion-chip outline color="warning" tabindex="0">
<ion-label>Warning</ion-label>
</ion-chip>
<ion-chip outline color="danger" tabindex="0">
<ion-label>Danger</ion-label>
</ion-chip>
<ion-chip outline color="light" tabindex="0">
<ion-label>Light</ion-label>
</ion-chip>
<ion-chip outline color="medium" tabindex="0">
<ion-label>Medium</ion-label>
</ion-chip>
<ion-chip outline color="dark" tabindex="0">
<ion-label>Dark</ion-label>
</ion-chip>
</p>
</div>
<h3>Custom</h3>
@@ -232,9 +306,6 @@
<ion-chip class="wide" text="wide">
<ion-icon name="add"></ion-icon>
</ion-chip>
<ion-chip class="wide ion-focused" text="wide">
<ion-icon name="add"></ion-icon>
</ion-chip>
</p>
<!-- Custom Colors -->
@@ -245,25 +316,19 @@
<ion-chip color="secondary" class="custom">
<ion-icon name="add"></ion-icon>
</ion-chip>
<ion-chip class="custom ion-focused">
<ion-icon name="add"></ion-icon>
</ion-chip>
<ion-chip color="secondary" class="custom ion-focused">
<ion-icon name="add"></ion-icon>
</ion-chip>
</p>
</ion-content>
</ion-app>
<script>
var buttons = document.querySelectorAll('ion-chip');
var chips = document.querySelectorAll('ion-chip');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', (event) => onClick(event));
for (var i = 0; i < chips.length; i++) {
chips[i].addEventListener('click', (event) => onClick(event));
}
function onClick(ev) {
console.log('clicked the button', ev.target.innerText);
console.log('clicked the chip', ev.target.innerText);
}
</script>

View File

@@ -22,6 +22,7 @@ export class Footer implements ComponentInterface {
private scrollEl?: HTMLElement;
private contentScrollCallback?: () => void;
private keyboardCtrl: KeyboardController | null = null;
private keyboardCtrlPromise: Promise<KeyboardController> | null = null;
@State() private keyboardVisible = false;
@@ -52,7 +53,7 @@ export class Footer implements ComponentInterface {
}
async connectedCallback() {
this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
/**
* If the keyboard is hiding, then we need to wait
* for the webview to resize. Otherwise, the footer
@@ -64,11 +65,32 @@ export class Footer implements ComponentInterface {
this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
});
this.keyboardCtrlPromise = promise;
const keyboardCtrl = await promise;
/**
* Only assign if this is still the current promise.
* Otherwise, a new connectedCallback has started or
* disconnectedCallback was called, so destroy this instance.
*/
if (this.keyboardCtrlPromise === promise) {
this.keyboardCtrl = keyboardCtrl;
this.keyboardCtrlPromise = null;
} else {
keyboardCtrl.destroy();
}
}
disconnectedCallback() {
if (this.keyboardCtrlPromise) {
this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
this.keyboardCtrlPromise = null;
}
if (this.keyboardCtrl) {
this.keyboardCtrl.destroy();
this.keyboardCtrl = null;
}
}

View File

@@ -165,9 +165,13 @@
// otherwise the .input-cover will not be rendered at all
// The input cover is not clickable when the input is disabled
.cloned-input {
@include position(0, null, 0, 0);
position: absolute;
top: 0;
bottom: 0;
// Reset height since absolute positioning with top/bottom handles sizing
height: auto;
max-height: none;
pointer-events: none;
}

View File

@@ -48,6 +48,7 @@ export class Input implements ComponentInterface {
private inputId = `ion-input-${inputIds++}`;
private helperTextId = `${this.inputId}-helper-text`;
private errorTextId = `${this.inputId}-error-text`;
private labelTextId = `${this.inputId}-label`;
private inheritedAttributes: Attributes = {};
private isComposing = false;
private slotMutationController?: SlotMutationController;
@@ -406,7 +407,12 @@ export class Input implements ComponentInterface {
connectedCallback() {
const { el } = this;
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
this.setSlottedLabelId();
forceUpdate(this);
});
this.setSlottedLabelId();
this.notchController = createNotchController(
el,
() => this.notchSpacerEl,
@@ -721,7 +727,7 @@ export class Input implements ComponentInterface {
}
private renderLabel() {
const { label } = this;
const { label, labelTextId } = this;
return (
<div
@@ -729,8 +735,17 @@ export class Input implements ComponentInterface {
'label-text-wrapper': true,
'label-text-wrapper-hidden': !this.hasLabel,
}}
// Prevents Android TalkBack from focusing the label separately.
// The input remains labelled via aria-labelledby.
aria-hidden={this.hasLabel ? 'true' : null}
>
{label === undefined ? <slot name="label"></slot> : <div class="label-text">{label}</div>}
{label === undefined ? (
<slot name="label"></slot>
) : (
<div class="label-text" id={labelTextId}>
{label}
</div>
)}
</div>
);
}
@@ -743,6 +758,33 @@ export class Input implements ComponentInterface {
return this.el.querySelector('[slot="label"]');
}
/**
* Ensures the slotted label element has an ID for aria-labelledby.
* If no ID exists, we assign one using our generated labelTextId.
*/
private setSlottedLabelId() {
const slottedLabel = this.labelSlot;
if (slottedLabel && !slottedLabel.id) {
slottedLabel.id = this.labelTextId;
}
}
/**
* Returns the ID to use for aria-labelledby on the native input,
* or undefined if aria-label is explicitly set (to avoid conflicts).
*/
private getLabelledById(): string | undefined {
if (this.inheritedAttributes['aria-label']) {
return undefined;
}
if (this.label !== undefined) {
return this.labelTextId;
}
return this.labelSlot?.id || undefined;
}
/**
* Returns `true` if label content is provided
* either by a prop or a content. If you want
@@ -898,6 +940,7 @@ export class Input implements ComponentInterface {
onCompositionend={this.onCompositionEnd}
aria-describedby={this.getHintTextID()}
aria-invalid={this.isInvalid ? 'true' : undefined}
aria-labelledby={this.getLabelledById()}
{...this.inheritedAttributes}
/>
{this.clearInput && !readonly && !disabled && (

View File

@@ -57,6 +57,104 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title,
});
});
configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => {
test.describe(title('input: label a11y for Android TalkBack'), () => {
/**
* Android TalkBack treats visible text elements as separate focusable items.
* These tests verify that the label is hidden from a11y tree (aria-hidden)
* while remaining associated with the input via aria-labelledby.
*/
test('label text wrapper should be hidden from accessibility tree when using label prop', async ({ page }) => {
await page.setContent(
`
<ion-input label="Email" value="test@example.com"></ion-input>
`,
config
);
const labelTextWrapper = page.locator('ion-input .label-text-wrapper');
await expect(labelTextWrapper).toHaveAttribute('aria-hidden', 'true');
});
test('label text wrapper should be hidden from accessibility tree when using label slot', async ({ page }) => {
await page.setContent(
`
<ion-input value="test@example.com">
<div slot="label">Email</div>
</ion-input>
`,
config
);
const labelTextWrapper = page.locator('ion-input .label-text-wrapper');
await expect(labelTextWrapper).toHaveAttribute('aria-hidden', 'true');
});
test('native input should have aria-labelledby pointing to label text when using label prop', async ({ page }) => {
await page.setContent(
`
<ion-input label="Email" value="test@example.com"></ion-input>
`,
config
);
const nativeInput = page.locator('ion-input input');
const labelText = page.locator('ion-input .label-text');
const labelTextId = await labelText.getAttribute('id');
expect(labelTextId).not.toBeNull();
await expect(nativeInput).toHaveAttribute('aria-labelledby', labelTextId!);
});
test('native input should have aria-labelledby pointing to slotted label when using label slot', async ({
page,
}) => {
await page.setContent(
`
<ion-input value="test@example.com">
<div slot="label">Email</div>
</ion-input>
`,
config
);
const nativeInput = page.locator('ion-input input');
const slottedLabel = page.locator('ion-input [slot="label"]');
const slottedLabelId = await slottedLabel.getAttribute('id');
expect(slottedLabelId).not.toBeNull();
await expect(nativeInput).toHaveAttribute('aria-labelledby', slottedLabelId!);
});
test('should not add aria-labelledby when aria-label is provided on host', async ({ page }) => {
await page.setContent(
`
<ion-input aria-label="Custom Label" value="test@example.com"></ion-input>
`,
config
);
const nativeInput = page.locator('ion-input input');
await expect(nativeInput).toHaveAttribute('aria-label', 'Custom Label');
await expect(nativeInput).not.toHaveAttribute('aria-labelledby');
});
test('should not add aria-hidden to label wrapper when no label is present', async ({ page }) => {
await page.setContent(
`
<ion-input aria-label="Hidden Label" value="test@example.com"></ion-input>
`,
config
);
const labelTextWrapper = page.locator('ion-input .label-text-wrapper');
await expect(labelTextWrapper).not.toHaveAttribute('aria-hidden', 'true');
});
});
});
configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('input: font scaling'), () => {
test('should scale text on larger font sizes', async ({ page }) => {

View File

@@ -1116,6 +1116,11 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
private handleViewTransition() {
// Only run view transitions when the modal is presented
if (!this.presented) {
return;
}
const isPortrait = window.innerWidth < 768;
// Only transition if view state actually changed

View File

@@ -0,0 +1,176 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('card modal: viewport resize'), () => {
test.beforeEach(async ({ page }) => {
// Start in portrait mode (mobile)
await page.setViewportSize({ width: 375, height: 667 });
await page.setContent(
`
<ion-app>
<div class="ion-page" id="main-page">
<ion-header>
<ion-toolbar>
<ion-title>Card Viewport Resize Test</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<p>This page tests that viewport resize does not trigger card modal animation when modal is closed.</p>
<ion-button id="open-modal">Open Card Modal</ion-button>
<ion-modal id="card-modal">
<ion-header>
<ion-toolbar>
<ion-title>Card Modal</ion-title>
<ion-buttons slot="end">
<ion-button id="close-modal">Close</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<p>Modal content</p>
</ion-content>
</ion-modal>
</ion-content>
</div>
</ion-app>
<script>
const modal = document.querySelector('#card-modal');
const mainPage = document.querySelector('#main-page');
modal.presentingElement = mainPage;
document.querySelector('#open-modal').addEventListener('click', () => {
modal.present();
});
document.querySelector('#close-modal').addEventListener('click', () => {
modal.dismiss();
});
</script>
`,
config
);
});
test('should not animate presenting element when viewport resizes and modal is closed', async ({
page,
}, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30679',
});
const mainPage = page.locator('#main-page');
// Verify the presenting element has no transform initially
const initialTransform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(initialTransform).toBe('none');
// Resize from portrait to landscape (crossing the 768px threshold)
await page.setViewportSize({ width: 900, height: 375 });
// Wait for the debounced resize handler (50ms) plus some buffer
await page.waitForTimeout(150);
// The presenting element should still have no transform
// If the bug exists, it would have scale(0.93) or similar applied
const afterResizeTransform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(afterResizeTransform).toBe('none');
});
test('should not animate presenting element when resizing multiple times with modal closed', async ({ page }) => {
const mainPage = page.locator('#main-page');
// Multiple resize cycles should not trigger the animation
for (let i = 0; i < 3; i++) {
// Portrait to landscape
await page.setViewportSize({ width: 900, height: 375 });
await page.waitForTimeout(150);
let transform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(transform).toBe('none');
// Landscape to portrait
await page.setViewportSize({ width: 375, height: 667 });
await page.waitForTimeout(150);
transform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(transform).toBe('none');
}
});
test('should still animate presenting element correctly when modal is open and viewport resizes', async ({
page,
}) => {
const mainPage = page.locator('#main-page');
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
// Open the modal
await page.click('#open-modal');
await ionModalDidPresent.next();
// When modal is open in portrait, presenting element should be transformed
let transform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
// The presenting element should have a scale transform when modal is open
expect(transform).not.toBe('none');
// Resize to landscape while modal is open
await page.setViewportSize({ width: 900, height: 375 });
await page.waitForTimeout(150);
// The modal transitions correctly - in landscape mode the presenting element
// should have different (or no) transform than portrait
transform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
// Note: The exact transform depends on the landscape handling
// The main point is that when modal IS open, the transition should work
// This test just ensures we don't break existing functionality
});
test('presenting element should return to normal after modal is dismissed', async ({ page }) => {
const mainPage = page.locator('#main-page');
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
// Open the modal
await page.click('#open-modal');
await ionModalDidPresent.next();
// Close the modal
await page.click('#close-modal');
await ionModalDidDismiss.next();
// Wait for animations to complete
await page.waitForTimeout(500);
// The presenting element should be back to normal
const transform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(transform).toBe('none');
// Now resize the viewport - should not trigger animation
await page.setViewportSize({ width: 900, height: 375 });
await page.waitForTimeout(150);
const afterResizeTransform = await mainPage.evaluate((el) => {
return window.getComputedStyle(el).transform;
});
expect(afterResizeTransform).toBe('none');
});
});
});

View File

@@ -22,6 +22,7 @@ import type { TabBarChangedEventDetail } from './tab-bar-interface';
})
export class TabBar implements ComponentInterface {
private keyboardCtrl: KeyboardController | null = null;
private keyboardCtrlPromise: Promise<KeyboardController> | null = null;
private didLoad = false;
@Element() el!: HTMLElement;
@@ -88,7 +89,7 @@ export class TabBar implements ComponentInterface {
}
async connectedCallback() {
this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
/**
* If the keyboard is hiding, then we need to wait
* for the webview to resize. Otherwise, the tab bar
@@ -100,11 +101,32 @@ export class TabBar implements ComponentInterface {
this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
});
this.keyboardCtrlPromise = promise;
const keyboardCtrl = await promise;
/**
* Only assign if this is still the current promise.
* Otherwise, a new connectedCallback has started or
* disconnectedCallback was called, so destroy this instance.
*/
if (this.keyboardCtrlPromise === promise) {
this.keyboardCtrl = keyboardCtrl;
this.keyboardCtrlPromise = null;
} else {
keyboardCtrl.destroy();
}
}
disconnectedCallback() {
if (this.keyboardCtrlPromise) {
this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
this.keyboardCtrlPromise = null;
}
if (this.keyboardCtrl) {
this.keyboardCtrl.destroy();
this.keyboardCtrl = null;
}
}

View File

@@ -205,9 +205,13 @@
// otherwise the .input-cover will not be rendered at all
// The input cover is not clickable when the input is disabled
.cloned-input {
@include position(0, null, 0, 0);
position: absolute;
top: 0;
bottom: 0;
// Reset height since absolute positioning with top/bottom handles sizing
height: auto;
max-height: none;
pointer-events: none;
}

View File

@@ -29,6 +29,9 @@
<ion-content class="ion-padding">
<button id="baseline" onclick="openToast(baselineConfig)">Open Baseline Layout Toast</button>
<button id="stacked" onclick="openToast(stackedConfig)">Open Stacked Layout Toast</button>
<button id="stacked-long-message" onclick="openToast(stackedLongMessageConfig)">
Open Stacked Layout Toast with Long Message
</button>
</ion-content>
<script>
@@ -52,6 +55,13 @@
message: 'This is a stacked layout toast.',
layout: 'stacked',
};
const stackedLongMessageConfig = {
...baselineConfig,
message:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum in elit varius, maximus sem in, bibendum justo.',
layout: 'stacked',
};
</script>
</ion-app>
</body>

View File

@@ -3,7 +3,7 @@ import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('toast: stacked layout'), () => {
test('should render stacked buttons', async ({ page }) => {
test('should render stacked toast', async ({ page }) => {
await page.goto('/src/components/toast/test/layout', config);
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
@@ -13,5 +13,21 @@ configs().forEach(({ title, screenshot, config }) => {
const toastWrapper = page.locator('ion-toast .toast-wrapper');
await expect(toastWrapper).toHaveScreenshot(screenshot(`toast-stacked`));
});
test('should render stacked toast with long message', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30908',
});
await page.goto('/src/components/toast/test/layout', config);
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
await page.click('#stacked-long-message');
await ionToastDidPresent.next();
const toastWrapper = page.locator('ion-toast .toast-wrapper');
await expect(toastWrapper).toHaveScreenshot(screenshot(`toast-stacked-long-message`));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -132,7 +132,6 @@
.toast-layout-baseline .toast-content {
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
}
@@ -142,6 +141,8 @@
}
.toast-content {
flex: 1;
min-width: 0;
}

View File

@@ -68,11 +68,26 @@ const addClone = (
if (disabledClonedInput) {
clonedEl.disabled = true;
}
/**
* Position the clone at the same horizontal offset as the native input
* to prevent the placeholder from overlapping start slot content (e.g., icons).
*/
const doc = componentEl.ownerDocument!;
const isRTL = doc.dir === 'rtl';
if (isRTL) {
const parentWidth = (parentEl as HTMLElement).offsetWidth;
const startOffset = parentWidth - inputEl.offsetLeft - inputEl.offsetWidth;
clonedEl.style.insetInlineStart = `${startOffset}px`;
} else {
clonedEl.style.insetInlineStart = `${inputEl.offsetLeft}px`;
}
parentEl.appendChild(clonedEl);
cloneMap.set(componentEl, clonedEl);
const doc = componentEl.ownerDocument!;
const tx = doc.dir === 'rtl' ? 9999 : -9999;
const tx = isRTL ? 9999 : -9999;
componentEl.style.pointerEvents = 'none';
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0) scale(0)`;
};

View File

@@ -291,8 +291,14 @@ const jsSetFocus = async (
// give the native text input focus
relocateInput(componentEl, inputEl, false, scrollData.inputSafeY);
// ensure this is the focused input
setManualFocus(inputEl);
/**
* If focus has moved to another element while scroll assist was running,
* don't steal focus back. This prevents focus jumping when users
* quickly switch between inputs or tap other elements.
*/
if (document.activeElement === inputEl) {
setManualFocus(inputEl);
}
/**
* When the input is about to be blurred

View File

@@ -3,5 +3,5 @@
"core",
"packages/*"
],
"version": "8.7.15"
"version": "8.7.17"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.15"
"@ionic/core": "^8.7.17"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^16.0.0",
@@ -1031,9 +1031,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",
@@ -7309,9 +7309,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"requires": {
"@stencil/core": "4.38.0",
"ionicons": "^8.0.13",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "8.7.15",
"version": "8.7.17",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -62,6 +62,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^8.7.15"
"@ionic/core": "^8.7.17"
}
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/angular
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/angular
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/angular

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -1398,9 +1398,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "8.7.15",
"version": "8.7.17",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -48,7 +48,7 @@
}
},
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/docs
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/docs
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "8.7.15",
"version": "8.7.17",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/react-router
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/react-router
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/react-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/react": "^8.7.15",
"@ionic/react": "^8.7.17",
"tslib": "*"
},
"devDependencies": {
@@ -238,9 +238,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",
@@ -418,12 +418,12 @@
}
},
"node_modules/@ionic/react": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.15.tgz",
"integrity": "sha512-3W5DAN3OMe2w32AGlW8zvKZHNY/dLCWhVLL9485VKtsYvgUc9nkG8dYDvQGc6c17PEDVrMy+IAl4H9hLgJhYnQ==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.17.tgz",
"integrity": "sha512-t/ApHBEigSTvovM/hKtNAMrddoOQ5l2GlyjOzASUq7sJLvDS4ewDMk5pRahjGqmFSYSN8TIBlF9QAHswp6XTRg==",
"license": "MIT",
"dependencies": {
"@ionic/core": "8.7.15",
"@ionic/core": "8.7.17",
"ionicons": "^8.0.13",
"tslib": "*"
},
@@ -4178,9 +4178,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"requires": {
"@stencil/core": "4.38.0",
"ionicons": "^8.0.13",
@@ -4284,11 +4284,11 @@
"requires": {}
},
"@ionic/react": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.15.tgz",
"integrity": "sha512-3W5DAN3OMe2w32AGlW8zvKZHNY/dLCWhVLL9485VKtsYvgUc9nkG8dYDvQGc6c17PEDVrMy+IAl4H9hLgJhYnQ==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.17.tgz",
"integrity": "sha512-t/ApHBEigSTvovM/hKtNAMrddoOQ5l2GlyjOzASUq7sJLvDS4ewDMk5pRahjGqmFSYSN8TIBlF9QAHswp6XTRg==",
"requires": {
"@ionic/core": "8.7.15",
"@ionic/core": "8.7.17",
"ionicons": "^8.0.13",
"tslib": "*"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "8.7.15",
"version": "8.7.17",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -36,7 +36,7 @@
"dist/"
],
"dependencies": {
"@ionic/react": "^8.7.15",
"@ionic/react": "^8.7.17",
"tslib": "*"
},
"peerDependencies": {

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/react
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/react
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/react

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"tslib": "*"
},
@@ -736,9 +736,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "8.7.15",
"version": "8.7.17",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -40,7 +40,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"tslib": "*"
},

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/vue-router
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/vue-router
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/vue-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^8.7.15"
"@ionic/vue": "^8.7.17"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
@@ -673,9 +673,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",
@@ -868,12 +868,12 @@
}
},
"node_modules/@ionic/vue": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.15.tgz",
"integrity": "sha512-F94GoGBsLoFFkJLe+W401SynIfUzEL1Z+Y4YUQ2EGpulnFpY4tXKzDhRNrisPnM0k3BWQRh8AA8fwTtMqEiISw==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.17.tgz",
"integrity": "sha512-S/UQc/ytauWxcAtM6cLz1mbpqwv8xuSdTzF8aslPi8zGELdXJSg48QBP6ehhFj+uv8qsblrRd4TjCQYyskyMCQ==",
"license": "MIT",
"dependencies": {
"@ionic/core": "8.7.15",
"@ionic/core": "8.7.17",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}
@@ -8044,9 +8044,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"requires": {
"@stencil/core": "4.38.0",
"ionicons": "^8.0.13",
@@ -8159,11 +8159,11 @@
"requires": {}
},
"@ionic/vue": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.15.tgz",
"integrity": "sha512-F94GoGBsLoFFkJLe+W401SynIfUzEL1Z+Y4YUQ2EGpulnFpY4tXKzDhRNrisPnM0k3BWQRh8AA8fwTtMqEiISw==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.17.tgz",
"integrity": "sha512-S/UQc/ytauWxcAtM6cLz1mbpqwv8xuSdTzF8aslPi8zGELdXJSg48QBP6ehhFj+uv8qsblrRd4TjCQYyskyMCQ==",
"requires": {
"@ionic/core": "8.7.15",
"@ionic/core": "8.7.17",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "8.7.15",
"version": "8.7.17",
"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.7.15"
"@ionic/vue": "^8.7.17"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14)
**Note:** Version bump only for package @ionic/vue
## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31)
**Note:** Version bump only for package @ionic/vue
## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23)
**Note:** Version bump only for package @ionic/vue

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "8.7.15",
"version": "8.7.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "8.7.15",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
},
@@ -222,9 +222,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.15",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.15.tgz",
"integrity": "sha512-u1w9c6dx2iuatXIW5X1JY0ighDhQPjBwOHZsrOcnpm891pktuEjJDdyhDulWFa6kKVkXw1q7khwxXBEurvKc2g==",
"version": "8.7.17",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.17.tgz",
"integrity": "sha512-gp7PIEJX27NX/FkjiUlpjQUtJiFFE5W1lofRC5CfORQ8p4PrLh9wJO9EJH0YryCr2qZS0k47sYgRQP5FwiXlpg==",
"license": "MIT",
"dependencies": {
"@stencil/core": "4.38.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "8.7.15",
"version": "8.7.17",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"eslint": "eslint src",
@@ -68,7 +68,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
"@ionic/core": "^8.7.15",
"@ionic/core": "^8.7.17",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
},