Compare commits

...

72 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
Shane
72826edf9a merge release-8.7.15 (#30886)
v8.7.15

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-12-23 11:14:57 -08:00
ionitron
4360e39a58 chore(): update package lock files 2025-12-23 18:55:54 +00:00
ionitron
622d62a3f4 v8.7.15 2025-12-23 18:55:19 +00:00
Shane
12ede4b79c fix(input-password-toggle): improve screen reader announcements (#30885)
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?

The `ion-input-password-toggle` button uses `role="switch"` with
`aria-checked`, causing screen readers like VoiceOver to announce both a
state ("On/Off") and an action ("Show/Hide password"). This results in
confusing, redundant output such as "On, Hide Password" or "Off, Show
Password".

## What is the new behavior?

The password toggle button now uses `aria-pressed` instead of
`role="switch"` with `aria-checked`. Screen readers announce the
action-based label ("Show password" or "Hide password") along with the
pressed state, and properly announce state changes when the button is
activated.

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

[Old
Preview](https://ionic-framework-git-main-ionic1.vercel.app/src/components/input-password-toggle/test/basic)

[New
Preview](https://ionic-framework-git-fw-6920-ionic1.vercel.app/src/components/input-password-toggle/test/basic)

Current dev build:
```
8.7.15-dev.11766421552.180757ca
```
2025-12-22 17:24:32 +00:00
Israel de la Barrera
f83b000530 fix(header): show iOS condense header when app is in MD mode (#30690)
Issue number: resolves #29929

---------

## What is the current behavior?
When forcing `mode=ios` in a collapsible header,
`.header-collapse-condense` would still be applied from the
`header.md.scss` file, leaving the collapsible header always hidden.

## What is the new behavior?
When forcing `mode=ios` in a collapsible header, the
`.header-collapse-condense` styles from the `header.md.scss` file won't
be applied, and the collapsible header will be visible.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No


## Other information
Something worth mentioning is that this behavior only appears after
initial load: if the route is loaded refreshing the page, the header
will appear and work correctly, but navigating forth and back will apply
both the .ios and .md style files.

I showcase this with a modal because It'll always display the broken
hehavior.

| Before | After |
|--------|-------|
| <video
src="https://github.com/user-attachments/assets/1307ee9f-452a-4b00-877d-0b8e360d3bf7">
| <video
src="https://github.com/user-attachments/assets/f9ee3851-ce94-4a27-9947-37aa1f5433b9">
|

---------

Co-authored-by: ShaneK <shane@shanessite.net>
2025-12-22 17:21:16 +00:00
Brandy Smith
3b60a1d68a fix(modal): dismiss top-most overlay when multiple IDs match (#30883)
Issue number: resolves #30030

---------

## What is the current behavior?
When modals are presented one after another with matching IDs and then
dismissed by ID it will dismiss the first presented modal.

## What is the new behavior?
- When modals are presented one after another with matching IDs and then
dismissed by ID it will dismiss the last (top-most) presented modal.
- Added e2e tests to verify this behavior works the same as the default
dismiss (not passing an ID).

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

[Modal: Dismiss
Behavior](https://ionic-framework-git-fw-7016-ionic1.vercel.app/src/components/modal/test/dismiss-behavior)

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-19 19:03:04 +00:00
Maria Hutt
8573bf8083 fix(core): use Capacitor safe-area CSS variables on older WebViews (#30865)
Issue number: 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?
<!-- Please describe the current behavior that you are modifying. -->

The safe area variables are only reliant on `env` variables that are
provided by devices.

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

Capacitor 8 has released [safe area variable
fallbacks](https://capacitorjs.com/docs/apis/system-bars#android-note)
to provide consistent behaviors with older Android devices:

> Due to a [bug](https://issues.chromium.org/issues/40699457) in some
older versions of Android WebView (< 140), correct safe area values are
not available via the safe-area-inset-x CSS env variables. This plugin
will inject the correct inset values into a new CSS variable(s) named
--safe-area-inset-x that you can use as a fallback in your frontend
styles.

- Updated safe area variables to use the fallbacks provided by
Capacitor.

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

Dev build: `8.7.13-dev.11765920447.1a01ab8b`

---------

Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-12-18 18:16:56 +00:00
Brandy Smith
2c6fac9060 merge release-8.7.14 (#30879)
v8.7.14

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-12-17 13:22:41 -05:00
ionitron
b9fdfab667 chore(): update package lock files 2025-12-17 17:51:54 +00:00
ionitron
f7af5d3ca5 v8.7.14 2025-12-17 17:50:44 +00:00
Shane
03fb422bfa fix(tabs): select correct tab when routes have similar prefixes (#30863)
Issue number: resolves #30448 

---------

<!-- 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-tabs with routes that share a common prefix (e.g.,
`/home`, `/home2`, `/home3`), navigating to `/home2` incorrectly
highlights the `/home` tab. This occurs because the tab matching logic
uses `pathname.startsWith(href)`, which causes `/home2` to match `/home`
since `/home2` starts with `/home`.

## What is the new behavior?

Tab selection now uses path segment matching instead of simple prefix
matching. A tab's href will only match if the pathname is an exact match
OR starts with the href followed by a / (for nested routes). This
ensures /home2 no longer incorrectly matches /home, while still allowing
/home/details to correctly match the /home tab.

## 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.13-dev.11765486444.14025098
```

---------

Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-12-17 00:11:10 +00:00
renovate[bot]
82de33b96e chore(deps): update download + upload artifacts (major) (#30872)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/download-artifact](https://redirect.github.com/actions/download-artifact)
| action | major | `v6` -> `v7` |
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | major | `v5` -> `v6` |

---

### Release Notes

<details>
<summary>actions/download-artifact (actions/download-artifact)</summary>

###
[`v7`](https://redirect.github.com/actions/download-artifact/compare/v6...v7)

[Compare
Source](https://redirect.github.com/actions/download-artifact/compare/v6...v7)

</details>

<details>
<summary>actions/upload-artifact (actions/upload-artifact)</summary>

###
[`v6`](https://redirect.github.com/actions/upload-artifact/compare/v5...v6)

[Compare
Source](https://redirect.github.com/actions/upload-artifact/compare/v5...v6)

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

👻 **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:eyJjcmVhdGVkSW5WZXIiOiI0Mi40Mi4yIiwidXBkYXRlZEluVmVyIjoiNDIuNDIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 21:31:47 +00:00
Shane
76b715874a release-8.7.13 (#30870)
v8.7.13

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-12-13 08:16:52 -08:00
ionitron
6205338620 chore(): update package lock files 2025-12-13 15:52:33 +00:00
ionitron
f775815a13 v8.7.13 2025-12-13 15:51:51 +00:00
Shane
cf3caa287e chore(core): aligning core engine requirement with main ionic-framework requirement (#30869)
Issue number: resolves #30868

---------

<!-- 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?

Currently, users are unable to use ionic-framework with node < 24. This
was an accidental change, not something we actually require.

## What is the new behavior?

This change aligns the core file with the [top level
package.json](https://github.com/ionic-team/ionic-framework/blob/main/package.json#L9)
requirement. We may want to look into upping this at some point in the
future, but right now this should be fine.

## 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. -->
2025-12-13 15:00:20 +00:00
renovate[bot]
2ee52d77c8 chore(deps): update capacitor to v8 (major) (#30847)
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)) | [`^7.0.0`
->
`^8.0.0`](https://renovatebot.com/diffs/npm/@capacitor%2fcore/7.4.4/8.0.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fcore/8.0.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fcore/7.4.4/8.0.0?slim=true)
|
|
[@capacitor/haptics](https://redirect.github.com/ionic-team/capacitor-haptics)
| [`^7.0.0` ->
`^8.0.0`](https://renovatebot.com/diffs/npm/@capacitor%2fhaptics/7.0.3/8.0.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fhaptics/8.0.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fhaptics/7.0.3/8.0.0?slim=true)
|
|
[@capacitor/keyboard](https://redirect.github.com/ionic-team/capacitor-keyboard)
| [`^7.0.0` ->
`^8.0.0`](https://renovatebot.com/diffs/npm/@capacitor%2fkeyboard/7.0.4/8.0.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fkeyboard/8.0.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fkeyboard/7.0.4/8.0.0?slim=true)
|
|
[@capacitor/status-bar](https://redirect.github.com/ionic-team/capacitor-plugins)
| [`^7.0.0` ->
`^8.0.0`](https://renovatebot.com/diffs/npm/@capacitor%2fstatus-bar/7.0.4/8.0.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fstatus-bar/8.0.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fstatus-bar/7.0.4/8.0.0?slim=true)
|

---

### Release Notes

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

###
[`v8.0.0`](https://redirect.github.com/ionic-team/capacitor/blob/HEAD/CHANGELOG.md#800-2025-12-08)

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

##### Bug Fixes

- **cli:** Android apk name multi flavor dimensions parsing
([#&#8203;7598](https://redirect.github.com/ionic-team/capacitor/issues/7598))
([2dc20ee](2dc20ee894))
- **cli:** make migrate update to 8.0.0
([#&#8203;8250](https://redirect.github.com/ionic-team/capacitor/issues/8250))
([ee8ba7b](ee8ba7bbee))
- **ios:** move PrivacyInfo.xcprivacy to resource\_bundles to fix build…
([#&#8203;8264](https://redirect.github.com/ionic-team/capacitor/issues/8264))
([e6f50b8](e6f50b8c0c))

##### Features

- **android:** Improving SystemBars inset handling
([#&#8203;8268](https://redirect.github.com/ionic-team/capacitor/issues/8268))
([81ae30a](81ae30a503))

</details>

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

###
[`v8.0.0`](https://redirect.github.com/ionic-team/capacitor-haptics/blob/HEAD/CHANGELOG.md#800-2025-12-08)

[Compare
Source](e492876ac8...v8.0.0)

##### Bug Fixes

- **android:** use 'propName = value' assignment syntax in build.gradle
files
([e0d3987](e0d3987eea))

##### feature

- Capacitor 8 support
([7d840c7](7d840c7825))

##### BREAKING CHANGES

- Capacitor 8 requires major update

</details>

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

###
[`v8.0.0`](https://redirect.github.com/ionic-team/capacitor-keyboard/blob/HEAD/CHANGELOG.md#800-2025-12-08)

[Compare
Source](e492876ac8...v8.0.0)

##### Bug Fixes

- **android:** Adjust WebView sizing during keyboard size changes
([ae75247](ae752478ff))
- **android:** use 'propName = value' assignment syntax in build.gradle
files
([37b39b0](37b39b0c64))

##### feature

- Capacitor 8 support
([f8c21ef](f8c21ef521))

##### BREAKING CHANGES

- Capacitor 8 requires major update

</details>

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

###
[`v8.0.0`](https://redirect.github.com/ionic-team/capacitor-plugins/releases/tag/%40capacitor/app%408.0.0)

[Compare
Source](https://redirect.github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.4...@capacitor/status-bar@8.0.0)

**Note:** Version bump only for package
[@&#8203;capacitor/app](https://redirect.github.com/capacitor/app)

</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 these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuNDIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 19:26:50 +00:00
renovate[bot]
0e110de5e3 chore(deps): update capacitor to v7.0.3 (#30846)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[@capacitor/haptics](https://redirect.github.com/ionic-team/capacitor-haptics)
| [`7.0.2` ->
`7.0.3`](https://renovatebot.com/diffs/npm/@capacitor%2fhaptics/7.0.2/7.0.3)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fhaptics/7.0.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fhaptics/7.0.2/7.0.3?slim=true)
|
|
[@capacitor/keyboard](https://redirect.github.com/ionic-team/capacitor-keyboard)
| [`7.0.3` ->
`7.0.4`](https://renovatebot.com/diffs/npm/@capacitor%2fkeyboard/7.0.3/7.0.4)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fkeyboard/7.0.4?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fkeyboard/7.0.3/7.0.4?slim=true)
|
|
[@capacitor/status-bar](https://redirect.github.com/ionic-team/capacitor-plugins)
| [`7.0.3` ->
`7.0.4`](https://renovatebot.com/diffs/npm/@capacitor%2fstatus-bar/7.0.3/7.0.4)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@capacitor%2fstatus-bar/7.0.4?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@capacitor%2fstatus-bar/7.0.3/7.0.4?slim=true)
|

---

### Release Notes

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

###
[`v7.0.3`](https://redirect.github.com/ionic-team/capacitor-haptics/compare/v7.0.2...e492876ac85661078e39664652b01ac9e0ab08c7)

[Compare
Source](https://redirect.github.com/ionic-team/capacitor-haptics/compare/v7.0.2...e492876ac85661078e39664652b01ac9e0ab08c7)

</details>

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

###
[`v7.0.4`](https://redirect.github.com/ionic-team/capacitor-keyboard/compare/v7.0.3...e492876ac85661078e39664652b01ac9e0ab08c7)

[Compare
Source](https://redirect.github.com/ionic-team/capacitor-keyboard/compare/v7.0.3...e492876ac85661078e39664652b01ac9e0ab08c7)

</details>

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

###
[`v7.0.4`](https://redirect.github.com/ionic-team/capacitor-plugins/releases/tag/%40capacitor/splash-screen%407.0.4)

[Compare
Source](https://redirect.github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.3...@capacitor/status-bar@7.0.4)

**Note:** Version bump only for package
[@&#8203;capacitor/splash-screen](https://redirect.github.com/capacitor/splash-screen)

</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 these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zMi4yIiwidXBkYXRlZEluVmVyIjoiNDIuNDIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 18:57:45 +00:00
Shane
f50994a6ef merge release-8.7.12 (#30856)
v8.7.12

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-12-10 13:52:10 -08:00
ionitron
5bf6f6e825 chore(): update package lock files 2025-12-10 21:34:28 +00:00
ionitron
afa15d23d2 v8.7.12 2025-12-10 21:33:15 +00:00
Shane
b1645168a7 fix(modal): prevent browser hang when using ModalController in Angular (#30845)
Issue number: resolves internal

---------

## What is the current behavior?

When using ModalController to present a modal in Angular applications,
the browser becomes non-responsive and hangs in some circumstances. This
regression was introduced in #30544 with the addition of a
MutationObserver that watches document.body with subtree: true to detect
when a modal's parent element is removed from the DOM. For
controller-based modals, this observer fires on every DOM mutation in
the document, causing severe performance issues during Angular's change
detection cycles.

## What is the new behavior?

The MutationObserver for parent removal detection is now skipped for
controller-based modals and when the cached parent is the app root
(document.body or ion-app). These parents are never removed from the
DOM, so observing them is unnecessary. This prevents the performance
issues while still maintaining the parent removal detection behavior for
inline modals with meaningful parent elements.

## 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.12-dev.11765231260.1def96ab
```

---------

Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
2025-12-10 21:11:49 +00:00
Shane
b9e3cf0f5a fix(modal): allow interaction with parent content through sheet modals in child routes (#30839)
Issue number: resolves #30700

---------

## What is the current behavior?

When a sheet modal with showBackdrop=false is rendered in a child route
(nested ion-router-outlet), the parent content becomes non-interactive.
Clicks on buttons or other interactive elements in the parent component
are blocked, even though showBackdrop=false should allow background
interaction.

Two separate issues contributed to this bug:
1. **Root locking with `backdropBreakpoint`**: The `shouldLockRoot`
logic in `overlays.ts` didn't account for `backdropBreakpoint`. Modals
with `backdropBreakpoint > 0` were still locking the root with
`aria-hidden`, even though developers expect background interaction when
the modal is below the backdrop breakpoint.
2. **Child route wrapper blocking**: When a modal is in a child route,
the child route's page wrapper (`ion-page`) and its parent
`ion-router-outlet` remain in the DOM with `position: absolute` covering
the viewport. Even after the modal is moved to `ion-app` and has
`pointer-events: none`, these wrapper elements block clicks to the
parent page's content.

This issue stems from
[#30563](https://github.com/ionic-team/ionic-framework/pull/30563),
which added root-locking behavior that didn't account for modals that
allow background interaction. A partial fix in
[#30689](https://github.com/ionic-team/ionic-framework/pull/30689)
partially addressed `showBackdrop=false` and `focusTrap=false`, but
missed `backdropBreakpoint`.

## What is the new behavior?

Sheet modals with showBackdrop=false or focusTrap=false now correctly
allow interaction with parent content when the modal is in a child
route.
Improvements:
- Recalculates isSheetModal in present() to handle Angular binding
timing
- Sets pointer-events: none on the modal element and its original parent
elements when background interaction should be allowed
- Cleans up pointer-events on dismiss
- Adds regression tests

## Does this introduce a breaking change?

- [ ] Yes
- [X] No


## Other information

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

Dev build:
```
8.7.12-dev.11765060985.14ad27fb
```
2025-12-10 21:08:48 +00:00
Brandy Smith
99dcf3810a fix(popover): recalculate the content dimensions after the header has fully loaded (#30853)
Issue number: internal

---------

## What is the current behavior?
A translucent header in a popover does not consistently render as
translucent upon presenting due to the `offset-top` of the content being
set to `0`.

## What is the new behavior?
Watch the header for height changes using `ResizeObserver` and
recalculate the content dimensions when the header height is greater
than `0`.

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

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-10 20:56:59 +00:00
Brandy Smith
6643f6a115 chore(angular): add @types/node dep to ng17 test app (#30855)
Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-10 20:34:08 +00:00
Maria Hutt
1c89cf06ac fix(select, action-sheet): use radio role for options (#30769)
Issue number: 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?
<!-- Please describe the current behavior that you are modifying. -->

The screen reader does not announce when an option is selected within
the action sheet interface. This is because the action sheet uses
standard buttons, which do not support a detectable selected state via
native properties or ARIA attributes like `aria-checked` or
`aria-selected`, creating an inconsistent user experience across
different interface types.

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

- Updated the action sheet buttons to accept `role="radio"`
- Added keyboard navigation to follow the pattern for radio group
- Added test

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


[Basic](https://ionic-framework-git-fw-6818-ionic1.vercel.app/src/components/select/test/basic/)

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Co-authored-by: Shane <shane@shanessite.net>
Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-12-08 20:50:46 +00:00
Maria Hutt
3129565e4e test(scripts): update palette query (#30842)
Issue number: N/A

---------

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

`high-contrast` and `high-contrast-dark` palettes were not working when
requested through a URL query or hash for a test page. This was due to
the `match` not accepting hyphens so it would only save `high` which is
not a valid palette.

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

- Updated `match` to accept hyphens
- Added an error if an invalid palette is provided
- Added a palette fallback if an invalid palette is provided
- Added a class check for high contrast and high contrast dark

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

How to test:
1. Verify that `?palette=high-contrast`, `?palette=high-contrast-dark`,
`#palette=high-contrast`, and `#palette=high-contrast-dark` render
correctly (I recommend using [button basic
page](https://ionic-framework-git-scripts-ionic1.vercel.app/src/components/button/test/basic/))

---------

Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-12-08 20:23:53 +00:00
Brandy Smith
39a0be848c docs(testing): add a note on flaky tests to usage instructions (#30843)
Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-08 18:56:29 +00:00
Maria Hutt
57687623aa chore(renovate): configure node version consistency (#30816)
Issue number: N/A

---------

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

Core does not indicate what node version we should be running, which can
lead to inconsistent versions between local and workflows. These
inconsistencies can lead to workflow failures.

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

- Provides the node version within package
- Updates the node version of workflows and package at the same time

## 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. -->
2025-12-08 17:16:10 +00:00
renovate[bot]
3709bba41e chore(deps): update actions/checkout action to v6.0.1 (#30832)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v6.0.1`](https://redirect.github.com/actions/checkout/compare/v6.0.0...v6.0.1)

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

</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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMzIuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 20:50:17 +00:00
renovate[bot]
76e4901189 chore(deps): update actions/setup-node action to v6.1.0 (#30834)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

#### What's Changed

##### Enhancement:

- Remove always-auth configuration handling by
[@&#8203;priyagupta108](https://redirect.github.com/priyagupta108) in
[#&#8203;1436](https://redirect.github.com/actions/setup-node/pull/1436)

##### Dependency updates:

- Upgrade
[@&#8203;actions/cache](https://redirect.github.com/actions/cache) from
4.0.3 to 4.1.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;1384](https://redirect.github.com/actions/setup-node/pull/1384)
- Upgrade actions/checkout from 5 to 6 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;1439](https://redirect.github.com/actions/setup-node/pull/1439)
- Upgrade js-yaml from 3.14.1 to 3.14.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;1435](https://redirect.github.com/actions/setup-node/pull/1435)

##### Documentation update:

- Add example for restore-only cache in documentation by
[@&#8203;aparnajyothi-y](https://redirect.github.com/aparnajyothi-y) in
[#&#8203;1419](https://redirect.github.com/actions/setup-node/pull/1419)

**Full Changelog**:
<https://github.com/actions/setup-node/compare/v6...v6.1.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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 14:43:34 +00:00
Brandy Smith
22288319ed chore(deps): add playwright-core as a dev dependency (#30827)
This updates the `package-lock` file to match the structure `next` uses
so we won't have a mismatch with errors on each branch. It installs
`playwright-core` as a dev dependency to align with `next`.

How to test this:

1. Switch to this branch `chore/deps-playwright`
1. Remove `node_modules`: `rm -rf node_modules`
1. Run `npx lerna@5 bootstrap --include-dependencies --scope @ionic/core
--ignore-scripts -- --legacy-peer-deps && npm run build`
1.  Verify: build is successful
1. Run `git revert 76cf4bda6a`
1. Run `npx lerna@5 bootstrap --include-dependencies --scope @ionic/core
--ignore-scripts -- --legacy-peer-deps && npm run build`
1. 🛑 Verify: the following error occurs:
    ```bash
[ ERROR ] TypeScript:
node_modules/@axe-core/playwright/dist/index.d.ts:2:22
              Cannot find module 'playwright-core' or its corresponding
              type declarations.

L1: import { SerialFrameSelector, RunOptions, AxeResults } from
'axe-core';
          L2:  import { Page } from 'playwright-core';

    [00:58.9]  build failed in 6.36 s
    ```

⚠️ Please do not push the reverted commit, it is just to show that the
new dev dependency is required.

This shows that without `playwright-core` installed the lerna command in
the release process will fail.

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-01 22:21:22 +00:00
Brandy Smith
c65b76e727 chore(workflows): fix release workflow permissions (#30823)
The workflow permissions update did not work due to it being nested in a
workflow that only had `read` permissions.

You can see a failing run here (scroll down to annotations):
[19828029833](https://github.com/ionic-team/ionic-framework/actions/runs/19828029833)

```
Invalid workflow file: .github/workflows/release-orchestrator.yml#L71
The workflow is not valid. .github/workflows/release-orchestrator.yml (Line: 71, Col: 3): Error calling workflow 'ionic-team/ionic-framework/.github/workflows/release.yml@b4e540decc484bd22eb84484a8eb94f19b1790c1'. The nested job 'finalize-release' is requesting 'contents: write', but is only allowed 'contents: read'. .github/workflows/release-orchestrator.yml (Line: 71, Col: 3): Error calling workflow 'ionic-team/ionic-framework/.github/workflows/release.yml@b4e540decc484bd22eb84484a8eb94f19b1790c1'. The nested job 'update-package-lock' is requesting 'contents: write', but is only allowed 'contents: read'.
```

This updates the parent workflow to have `write` permissions. You can
see a passing run here:
[19828895682](https://github.com/ionic-team/ionic-framework/actions/runs/19828895682)

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-01 16:25:26 +00:00
Brandy Smith
b4e540decc chore(workflows): update release workflow permissions for finalizing (#30814)
Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
2025-12-01 15:10:00 +00:00
Maria Hutt
87e50ad9da merge release-8.7.11 (#30812)
v8.7.11

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2025-11-26 12:03:03 -08:00
Maria Hutt
595643fd14 chore(many): update lock files 2025-11-26 11:33:54 -08:00
ionitron
3249e1dce8 v8.7.11 2025-11-26 18:47:18 +00:00
Maria Hutt
e9bd3f819d test(scripts): update to handle hash params (#30807)
Issue number: N/A

---------

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

Playwright's `setContent` cannot handle query params which causes the
`scripts.js` to not run effectively.

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

- Update `scripts.js` to accept hash params as well
- Update `scripts.js` to accept the dark class to set dark mode if dark
query or hash was not passed

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

How to test:
1. Verify that tests are passing

---------

Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-11-26 18:19:31 +00:00
renovate[bot]
bf0f1e36e4 chore(deps): update actions/checkout action to v6 (#30802)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | major | `v5.0.1` -> `v6.0.0` |

---

### Release Notes

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

###
[`v6.0.0`](https://redirect.github.com/actions/checkout/compare/v5.0.1...v6.0.0)

[Compare
Source](https://redirect.github.com/actions/checkout/compare/v5.0.1...v6.0.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:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xNi4xIiwidXBkYXRlZEluVmVyIjoiNDIuMTYuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 17:23:59 +00:00
Shane
9d781db662 fix(datetime): ensure datetime is shown when intersection observer fails to report visibility (#30793)
Issue number: resolves #30706

---------

<!-- 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?

Due to some recent unknown changes, the intersection observer for date
time no longer reliably fires, especially in mobile views.

## What is the new behavior?

In this PR, we're adding a visibility check after everything has had a
chance to render to make sure we're setting up properly even if the
intersection observer has failed to trigger for some reason.

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

## Other information

Since the intersection observer is being set up after a `raf`, it's
possible something got introduced to make the initial setup slower for
some reason, causing timing issues. I think we should do a more thorough
investigation into the cause of this problem when we have more time.

This PR also adds tests to verify the new fallback works properly.

Current dev build:
```
8.7.10-dev.11763478209.1d9c4cd8
```

---------

Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
2025-11-20 20:16:09 +00:00
Maria Hutt
9ae41efddb merge release-8.7.10 (#30797)
v8.7.10

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Co-authored-by: Maria Hutt <maria.hutt@outsystems.com>
2025-11-19 11:11:45 -08:00
Maria Hutt
7379d34f38 chore(angular): add missing new line to package-lock 2025-11-19 10:36:45 -08:00
Maria Hutt
53f3bea244 chore(angular): correct package-lock 2025-11-19 10:35:57 -08:00
Maria Hutt
5c86b87fe3 chore(angular): add package-lock back 2025-11-19 10:28:23 -08:00
Brandy Smith
d8e6756ac3 chore(): update package lock files 2025-11-19 13:07:46 -05:00
Brandy Smith
627416b9d7 v8.7.10 2025-11-19 12:59:00 -05:00
OS-jacobbell
eeb15c3c5c chore(ci): change condition for success messages (#30796)
For Stencil Nightly alert messages, instead of checking if anything
succeeded, check if nothing failed.

---------

<!-- 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. -->
Both a success and failure message was sent when some jobs passed and
some failed.
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Success message should only be sent if nothing failed.

## 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. -->
2025-11-19 15:24:41 +00:00
renovate[bot]
2bebbd7a3e chore(deps): update actions/checkout action to v5.0.1 (#30790)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v5.0.1`](https://redirect.github.com/actions/checkout/compare/v5.0.0...v5.0.1)

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

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 16:59:49 +00:00
Gonçalo M.
b3826febe0 chore(github-actions): fine tune workflow orchestrator defaults (#30794)
Issue number: resolves #

---------

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

- "nightly" was the default choice and was available for manual trigger
when running `release-orchestrator`

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

- Set "dev" as the default choice (changed from "nightly")
- Removed "nightly" from manual workflow_dispatch options (only "dev"
and "production" remain)
- Simplified the `run-nightly` condition to only run on schedule
(`github.event_name == 'schedule'`)


## Does this introduce a breaking change?

- [ ] Yes
- [-] 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. -->

- Manual triggers (workflow_dispatch): Only "dev" (default) and
"production" are available
- Scheduled triggers: The nightly build continues to run automatically
every Monday–Friday at 6:00 UTC via the schedule trigger
- Trusted Publishers compliance: Maintained — all releases still go
through the single orchestrator workflow, ensuring the OIDC token is
issued from the whitelisted workflow
2025-11-18 16:20:27 +00:00
Gonçalo M.
1ff26b796b chore(github-actions): fine tune workflow permissions (#30791)
Issue number: resolves #

---------

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

- Permissions not set as expected

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

- Permissions are properly set throughout the hierarchy

## 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. -->
2025-11-18 14:08:47 +00:00
Gonçalo M.
99bfdee4cd chore(github-actions): Review workflow structure and fine tune permissions (#30789)
Issue number: resolves #

---------

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

- Permissions not set as expected

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

- Permissions are properly set throughout the hierarchy
- Workflow structure prevents unintended standalone executions

## 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. -->
2025-11-17 19:19:32 +00:00
OS-jacobbell
d03e88179b chore(deps): update @types/node (#30783)
Update node types for compatibility with typescript 5.8 now used in
Stencil.

---------

<!-- 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. -->
- Ionic fails to build with Stencil Nightly due to typed array changes
in typescript 5.7.
- Angular package fails to build with Stencil Nightly due to type errors
with Mixins.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
- Ionic can build with Stencil Nightly.
- Angular package can build with Stencil Nightly.

## 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. -->
2025-11-17 18:23:36 +00:00
Gonçalo M.
ecc291138e chore(npm): attempt to fix issue with Trusted Publishers when using reusable workflows (#30787)
Issue number: resolves #

---------

<!-- 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. -->
- Publishing to npm is failing due to the changes to move to Trusted
Publishers, since it seems that they still don't support reusable
workflows, as mentioned
[here](https://github.com/orgs/community/discussions/174507)
- The action to which we grant permissions on npm needs to follow a
strict path location `.github/workflows/` in your repository.

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

- Fixed permissions mismatch by applying the orchestrator method for npm
publish:
release-orchestrator.yml (contents: read, id-token: write)

  ├─→ nightly.yml (contents: read, id-token: write)
  │   └─→ release-ionic.yml (contents: read, id-token: write)
  │       └─→ publish-npm.yml (contents: read, id-token: write) 

  ├─→ dev-build.yml (contents: read, id-token: write)
  │   └─→ release-ionic.yml (contents: read, id-token: write)
  │       └─→ publish-npm.yml (contents: read, id-token: write) 

  └─→ release.yml (contents: read, id-token: write)
      └─→ release-ionic.yml (contents: read, id-token: write)
          └─→ publish-npm.yml (contents: read, id-token: write) 

- `release-orchestrator.yml` calls three workflows: `nightly.yml`,
`dev-build.yml`, and `release.yml`.
- All three call `release-ionic.yml`, which handles publishing multiple
packages.
- `release-ionic.yml` calls `publish-npm.yml` multiple times (once per
package).
- All workflows have `contents: read` and `id-token: write` permissions.
- `publish-npm.yml` is in `.github/workflows/`, which satisfies npm
Trusted Publishers requirements.
- This shows that `publish-npm.yml` is reachable through all three
release paths, and moving it to `.github/workflows/` ensures npm Trusted
Publishers can authenticate it 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.
-->

- Run pipelines after merge


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
- The workflow `release-orchestrator.yml` needs to be the one set up in
the npm package settings for the Trusted Publishers
2025-11-17 18:09:29 +00:00
139 changed files with 4502 additions and 10123 deletions

View File

@@ -22,10 +22,11 @@ runs:
using: 'composite'
steps:
- name: 🟢 Configure Node for Publish
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'
scope: '@ionic'
# Provenance requires npm 9.5.0+
- name: 📦 Install latest npm
run: npm install -g npm@latest
@@ -56,3 +57,4 @@ runs:
run: npm publish ${{ inputs.folder }} --tag ${{ inputs.tag }} --provenance
shell: bash
working-directory: ${{ inputs.working-directory }}

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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -8,8 +8,8 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x

View File

@@ -8,8 +8,8 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- 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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -10,7 +10,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive
@@ -62,7 +62,7 @@ runs:
working-directory: ./core
- name: 📦 Archive Updated Screenshots
if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }}
path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip

View File

@@ -6,7 +6,7 @@ inputs:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: ./.github/workflows/actions/download-archive

View File

@@ -7,10 +7,10 @@ on:
runs:
using: 'composite'
steps:
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.x
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
path: ./artifacts
- name: 🔎 Extract Archives

View File

@@ -13,7 +13,7 @@ runs:
- name: 🗄️ Create Archive
run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }}
shell: bash
- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@v6
with:
name: ${{ inputs.name }}
path: ${{ inputs.output }}

View File

@@ -22,7 +22,7 @@ jobs:
build-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-core
with:
ionicons-version: ${{ inputs.ionicons_npm_release_tag }}
@@ -31,21 +31,21 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-clean-build
test-core-lint:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-lint
test-core-spec:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-spec
test-core-screenshot:
@@ -62,7 +62,7 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-screenshot
with:
shard: ${{ matrix.shard }}
@@ -90,14 +90,14 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-vue
build-vue-router:
needs: [build-vue]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-vue-router
test-vue-e2e:
@@ -108,7 +108,7 @@ jobs:
needs: [build-vue, build-vue-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-vue-e2e
with:
app: ${{ matrix.apps }}
@@ -126,14 +126,14 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-angular
build-angular-server:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-angular-server
test-angular-e2e:
@@ -144,7 +144,7 @@ jobs:
needs: [build-angular, build-angular-server]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-angular-e2e
with:
app: ${{ matrix.apps }}
@@ -162,14 +162,14 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-react
build-react-router:
needs: [build-react]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-react-router
test-react-router-e2e:
@@ -180,7 +180,7 @@ jobs:
needs: [build-react, build-react-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-react-router-e2e
with:
app: ${{ matrix.apps }}
@@ -202,7 +202,7 @@ jobs:
needs: [build-react, build-react-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-react-e2e
with:
app: ${{ matrix.apps }}

View File

@@ -14,7 +14,7 @@ jobs:
permissions:
security-events: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: github/codeql-action/init@v4
with:
languages: javascript

View File

@@ -1,7 +1,11 @@
name: 'Ionic Dev Build'
on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
id-token: write
jobs:
create-dev-hash:
@@ -9,7 +13,7 @@ jobs:
outputs:
dev-hash: ${{ steps.create-dev-hash.outputs.DEV_HASH }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
# A 1 is required before the timestamp
# as lerna will fail when there is a leading 0
# See https://github.com/lerna/lerna/issues/2840
@@ -25,6 +29,7 @@ jobs:
release-ionic:
needs: [create-dev-hash]
permissions:
contents: read
id-token: write
uses: ./.github/workflows/release-ionic.yml
with:

View File

@@ -1,10 +1,11 @@
name: 'Ionic Nightly Build'
on:
schedule:
# Run every Monday-Friday
# at 6:00 UTC (6:00 am UTC)
- cron: '00 06 * * 1-5'
workflow_call:
permissions:
contents: read
id-token: write
jobs:
create-nightly-hash:
@@ -12,7 +13,7 @@ jobs:
outputs:
nightly-hash: ${{ steps.create-nightly-hash.outputs.NIGHTLY_HASH }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
# A 1 is required before the timestamp
# as lerna will fail when there is a leading 0
# See https://github.com/lerna/lerna/issues/2840
@@ -30,8 +31,10 @@ jobs:
release-ionic:
needs: [create-nightly-hash]
permissions:
contents: read
id-token: write
uses: ./.github/workflows/release-ionic.yml
secrets: inherit
with:
tag: nightly
version: ${{ needs.create-nightly-hash.outputs.nightly-hash }}

View File

@@ -23,8 +23,8 @@ jobs:
release-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: ./.github/workflows/actions/publish-npm
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/core'
tag: ${{ inputs.tag }}
@@ -48,14 +48,14 @@ jobs:
needs: [release-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/docs built cache
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-docs
path: ./packages/docs
filename: DocsBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/docs'
tag: ${{ inputs.tag }}
@@ -67,14 +67,14 @@ jobs:
needs: [release-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core
path: ./core
filename: CoreBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/angular'
tag: ${{ inputs.tag }}
@@ -93,14 +93,14 @@ jobs:
needs: [release-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core
path: ./core
filename: CoreBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/react'
tag: ${{ inputs.tag }}
@@ -118,14 +118,14 @@ jobs:
needs: [release-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core
path: ./core
filename: CoreBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/vue'
tag: ${{ inputs.tag }}
@@ -143,14 +143,14 @@ jobs:
needs: [release-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core
path: ./core
filename: CoreBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/angular-server'
tag: ${{ inputs.tag }}
@@ -163,7 +163,7 @@ jobs:
needs: [release-react]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
@@ -176,7 +176,7 @@ jobs:
name: ionic-react
path: ./packages/react
filename: ReactBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/react-router'
tag: ${{ inputs.tag }}
@@ -188,7 +188,7 @@ jobs:
needs: [release-vue]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Restore @ionic/core built cache
uses: ./.github/workflows/actions/download-archive
with:
@@ -201,7 +201,7 @@ jobs:
name: ionic-vue
path: ./packages/vue
filename: VueBuild.zip
- uses: ./.github/workflows/actions/publish-npm
- uses: ./.github/actions/publish-npm
with:
scope: '@ionic/vue-router'
tag: ${{ inputs.tag }}

View File

@@ -0,0 +1,81 @@
name: 'Release - Ionic Framework'
on:
schedule:
# Run every Monday-Friday
# at 6:00 UTC (6:00 am UTC)
- cron: '00 06 * * 1-5'
workflow_dispatch:
inputs:
release-type:
description: 'Which Ionic release workflow should run?'
required: true
type: choice
default: dev
options:
- dev
- production
version:
description: 'Which version should be published? (Only for production releases)'
required: false
type: choice
options:
- patch
- minor
- major
- prepatch
- preminor
- premajor
- prerelease
tag:
description: 'Which npm tag should this be published to? (Only for production releases)'
required: false
type: choice
default: latest
options:
- latest
- next
preid:
description: 'Which prerelease identifier should be used? (Only for production releases)'
required: false
type: choice
default: ''
options:
- ''
- alpha
- beta
- rc
- next
permissions:
contents: read
id-token: write
jobs:
run-nightly:
if: ${{ github.event_name == 'schedule' }}
permissions:
contents: read
id-token: write
uses: ./.github/workflows/nightly.yml
secrets: inherit
run-dev:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'dev' }}
permissions:
contents: read
id-token: write
uses: ./.github/workflows/dev-build.yml
secrets: inherit
run-production:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'production' }}
permissions:
contents: write
id-token: write
uses: ./.github/workflows/release.yml
secrets: inherit
with:
version: ${{ inputs.version }}
tag: ${{ inputs.tag }}
preid: ${{ inputs.preid }}

View File

@@ -1,40 +1,47 @@
name: 'Ionic Production Release'
on:
workflow_dispatch:
workflow_call:
inputs:
version:
description: 'Which version should be published?'
required: true
type: choice
description: Which version should be published?
options:
- patch
- minor
- major
- prepatch
- preminor
- premajor
- prerelease
type: string
tag:
description: 'Which npm tag should this be published to?'
required: true
type: choice
description: Which npm tag should this be published to?
options:
- latest
- next
type: string
preid:
type: choice
description: Which prerelease identifier should be used? This is only needed when version is "prepatch", "preminor", "premajor", or "prerelease".
default: ''
options:
- ''
- alpha
- beta
- rc
- next
description: 'Which prerelease identifier should be used? This is only needed when version is "prepatch", "preminor", "premajor", or "prerelease".'
required: false
type: string
permissions:
contents: read
id-token: write
jobs:
validate_version:
name: ✅ Validate Version Input
runs-on: ubuntu-latest
steps:
- name: 🔎 Ensure version is allowed
env:
VERSION: ${{ inputs.version }}
run: |
case "$VERSION" in
patch|minor|major|prepatch|preminor|premajor|prerelease)
exit 0
;;
*)
echo "::error::Invalid version input: '$VERSION'. Allowed values: patch, minor, major, prepatch, preminor, premajor, prerelease."
exit 1
;;
esac
shell: bash
release-ionic:
needs: [validate_version]
permissions:
contents: read
id-token: write
@@ -47,8 +54,11 @@ jobs:
finalize-release:
needs: [release-ionic]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
token: ${{ secrets.IONITRON_TOKEN }}
fetch-depth: 0
@@ -75,8 +85,11 @@ jobs:
# possible for them to push at the same time.
needs: [finalize-release]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
# Pull the latest version of the reference
# branch instead of the revision that triggered
# the workflow otherwise we won't get the commit

View File

@@ -26,7 +26,7 @@ jobs:
build-core-with-stencil-nightly:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-core-stencil-prerelease
with:
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
@@ -35,21 +35,21 @@ jobs:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-clean-build
test-core-lint:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-lint
test-core-spec:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-spec
with:
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
@@ -72,7 +72,7 @@ jobs:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-screenshot
with:
shard: ${{ matrix.shard }}
@@ -100,14 +100,14 @@ jobs:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-vue
build-vue-router:
needs: [build-vue]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-vue-router
test-vue-e2e:
@@ -118,7 +118,7 @@ jobs:
needs: [build-vue, build-vue-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-vue-e2e
with:
app: ${{ matrix.apps }}
@@ -136,14 +136,14 @@ jobs:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-angular
build-angular-server:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-angular-server
test-angular-e2e:
@@ -154,7 +154,7 @@ jobs:
needs: [build-angular, build-angular-server]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-angular-e2e
with:
app: ${{ matrix.apps }}
@@ -172,14 +172,14 @@ jobs:
needs: [build-core-with-stencil-nightly]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-react
build-react-router:
needs: [build-react]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-react-router
test-react-router-e2e:
@@ -190,7 +190,7 @@ jobs:
needs: [build-react, build-react-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-react-router-e2e
with:
app: ${{ matrix.apps }}
@@ -212,7 +212,7 @@ jobs:
needs: [build-react, build-react-router]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-react-e2e
with:
app: ${{ matrix.apps }}
@@ -229,7 +229,7 @@ jobs:
send-success-messages:
needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e]
runs-on: ubuntu-latest
if: ${{ !cancelled() && contains(needs.*.result, 'success') }}
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') }}
steps:
- name: Notify success on Discord
run: |

View File

@@ -26,7 +26,7 @@ jobs:
build-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/build-core
test-core-screenshot:
@@ -47,7 +47,7 @@ jobs:
needs: [build-core]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/workflows/actions/test-core-screenshot
with:
shard: ${{ matrix.shard }}
@@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
needs: [test-core-screenshot]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
# Normally, we could just push with the
# default GITHUB_TOKEN, but that will
# not cause the build workflow

View File

@@ -3,6 +3,99 @@
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)
### Bug Fixes
* **core:** use Capacitor safe-area CSS variables on older WebViews ([#30865](https://github.com/ionic-team/ionic-framework/issues/30865)) ([8573bf8](https://github.com/ionic-team/ionic-framework/commit/8573bf8083f75eda13c954a56731a6aac8ca5724))
* **header:** show iOS condense header when app is in MD mode ([#30690](https://github.com/ionic-team/ionic-framework/issues/30690)) ([f83b000](https://github.com/ionic-team/ionic-framework/commit/f83b0005309400d674e43c497bdffbcb9d2c4d94)), closes [#29929](https://github.com/ionic-team/ionic-framework/issues/29929)
* **input-password-toggle:** improve screen reader announcements ([#30885](https://github.com/ionic-team/ionic-framework/issues/30885)) ([12ede4b](https://github.com/ionic-team/ionic-framework/commit/12ede4b79c8d5cffc2b014c7c8a0d2ef1d3bf90d))
* **modal:** dismiss top-most overlay when multiple IDs match ([#30883](https://github.com/ionic-team/ionic-framework/issues/30883)) ([3b60a1d](https://github.com/ionic-team/ionic-framework/commit/3b60a1d68a1df1606ffee0bde7db7a206bac404a)), closes [#30030](https://github.com/ionic-team/ionic-framework/issues/30030)
## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17)
### Bug Fixes
* **tabs:** select correct tab when routes have similar prefixes ([#30863](https://github.com/ionic-team/ionic-framework/issues/30863)) ([03fb422](https://github.com/ionic-team/ionic-framework/commit/03fb422bfa775e3e9dd695ea1857fa88d4245ecd)), closes [#30448](https://github.com/ionic-team/ionic-framework/issues/30448)
## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13)
**Note:** Version bump only for package ionic-framework
## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10)
### Bug Fixes
* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700)
* **modal:** prevent browser hang when using ModalController in Angular ([#30845](https://github.com/ionic-team/ionic-framework/issues/30845)) ([b164516](https://github.com/ionic-team/ionic-framework/commit/b1645168a7fb9378dc39a081c207b2de0e180089))
* **popover:** recalculate the content dimensions after the header has fully loaded ([#30853](https://github.com/ionic-team/ionic-framework/issues/30853)) ([99dcf38](https://github.com/ionic-team/ionic-framework/commit/99dcf3810a0c32416996d1e992ddf63359965cfc))
* **select, action-sheet:** use radio role for options ([#30769](https://github.com/ionic-team/ionic-framework/issues/30769)) ([1c89cf0](https://github.com/ionic-team/ionic-framework/commit/1c89cf06ac959f9c9a35a66f811227c244d3198b))
## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26)
### Bug Fixes
* **datetime:** ensure datetime is shown when intersection observer fails to report visibility ([#30793](https://github.com/ionic-team/ionic-framework/issues/30793)) ([9d781db](https://github.com/ionic-team/ionic-framework/commit/9d781db662d213090d0b7198d0cdc5abb16fed1b)), closes [#30706](https://github.com/ionic-team/ionic-framework/issues/30706)
## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19)
### Bug Fixes
* **checkbox, toggle, radio-group:** improve screen reader announcement timing for validation errors ([#30714](https://github.com/ionic-team/ionic-framework/issues/30714)) ([92db364](https://github.com/ionic-team/ionic-framework/commit/92db36489cca944caf1593dbd518a1f025a171a2))
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)

View File

@@ -3,6 +3,96 @@
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)
### Bug Fixes
* **core:** use Capacitor safe-area CSS variables on older WebViews ([#30865](https://github.com/ionic-team/ionic-framework/issues/30865)) ([8573bf8](https://github.com/ionic-team/ionic-framework/commit/8573bf8083f75eda13c954a56731a6aac8ca5724))
* **header:** show iOS condense header when app is in MD mode ([#30690](https://github.com/ionic-team/ionic-framework/issues/30690)) ([f83b000](https://github.com/ionic-team/ionic-framework/commit/f83b0005309400d674e43c497bdffbcb9d2c4d94)), closes [#29929](https://github.com/ionic-team/ionic-framework/issues/29929)
* **input-password-toggle:** improve screen reader announcements ([#30885](https://github.com/ionic-team/ionic-framework/issues/30885)) ([12ede4b](https://github.com/ionic-team/ionic-framework/commit/12ede4b79c8d5cffc2b014c7c8a0d2ef1d3bf90d))
* **modal:** dismiss top-most overlay when multiple IDs match ([#30883](https://github.com/ionic-team/ionic-framework/issues/30883)) ([3b60a1d](https://github.com/ionic-team/ionic-framework/commit/3b60a1d68a1df1606ffee0bde7db7a206bac404a)), closes [#30030](https://github.com/ionic-team/ionic-framework/issues/30030)
## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17)
**Note:** Version bump only for package @ionic/core
## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13)
**Note:** Version bump only for package @ionic/core
## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10)
### Bug Fixes
* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700)
* **modal:** prevent browser hang when using ModalController in Angular ([#30845](https://github.com/ionic-team/ionic-framework/issues/30845)) ([b164516](https://github.com/ionic-team/ionic-framework/commit/b1645168a7fb9378dc39a081c207b2de0e180089))
* **popover:** recalculate the content dimensions after the header has fully loaded ([#30853](https://github.com/ionic-team/ionic-framework/issues/30853)) ([99dcf38](https://github.com/ionic-team/ionic-framework/commit/99dcf3810a0c32416996d1e992ddf63359965cfc))
* **select, action-sheet:** use radio role for options ([#30769](https://github.com/ionic-team/ionic-framework/issues/30769)) ([1c89cf0](https://github.com/ionic-team/ionic-framework/commit/1c89cf06ac959f9c9a35a66f811227c244d3198b))
## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26)
### Bug Fixes
* **datetime:** ensure datetime is shown when intersection observer fails to report visibility ([#30793](https://github.com/ionic-team/ionic-framework/issues/30793)) ([9d781db](https://github.com/ionic-team/ionic-framework/commit/9d781db662d213090d0b7198d0cdc5abb16fed1b)), closes [#30706](https://github.com/ionic-team/ionic-framework/issues/30706)
## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19)
### Bug Fixes
* **checkbox, toggle, radio-group:** improve screen reader announcement timing for validation errors ([#30714](https://github.com/ionic-team/ionic-framework/issues/30714)) ([92db364](https://github.com/ionic-team/ionic-framework/commit/92db36489cca944caf1593dbd518a1f025a171a2))
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)

10679
core/package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,10 @@
{
"name": "@ionic/core",
"version": "8.7.9",
"version": "8.7.17",
"description": "Base components for Ionic",
"engines": {
"node": ">= 16"
},
"keywords": [
"ionic",
"framework",
@@ -37,10 +40,10 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.11.0",
"@capacitor/core": "^7.0.0",
"@capacitor/haptics": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
"@capacitor/core": "^8.0.0",
"@capacitor/haptics": "^8.0.0",
"@capacitor/keyboard": "^8.0.0",
"@capacitor/status-bar": "^8.0.0",
"@clack/prompts": "^0.11.0",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
@@ -52,7 +55,7 @@
"@stencil/sass": "^3.0.9",
"@stencil/vue-output-target": "0.10.8",
"@types/jest": "^29.5.6",
"@types/node": "^14.6.0",
"@types/node": "^16.18.126",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"chalk": "^5.3.0",
@@ -65,6 +68,7 @@
"fs-extra": "^9.0.1",
"jest": "^29.7.0",
"jest-cli": "^29.7.0",
"playwright-core": "^1.56.1",
"prettier": "^2.6.1",
"rollup": "^2.26.4",
"sass": "^1.33.0",

View File

@@ -1,30 +1,81 @@
/**
* This script is loaded in testing environments to set up the
* document based on URL parameters.
*
* Test pages (e.g., `chip/test/basic/index.html`) are set to use
* URL query parameters.
*
* Playwright test environments (e.g., `chip/test/basic/chip.e2e.ts`)
* are set based on whether `setContent` or `goto` has been used:
* - `setContent` uses URL hash parameters. Tests will break if
* query parameters are used.
* - `goto` uses URL query parameters.
*
* The following URL parameters are supported:
* - `rtl`: Set to `true` to enable right-to-left directionality.
* - `ionic:_testing`: Set to `true` to identify testing environments.
* - `ionic:mode`: Set to `ios` or `md` to load a specific mode.
* Defaults to `md`.
* - `palette`: Set to `light`, `dark`, `high-contrast`, or
* `high-contrast-dark` to load a specific palette. Defaults to `light`.
*/
(function() {
if (window.location.search.indexOf('rtl=true') > -1) {
/**
* The `rtl` param is used to set the directionality of the
* document. This can be `true` or `false`.
*/
const isRTL = window.location.search.indexOf('rtl=true') > -1 || window.location.hash.indexOf('rtl=true') > -1;
if (isRTL) {
document.documentElement.setAttribute('dir', 'rtl');
}
if (window.location.search.indexOf('ionic:_testing=true') > -1) {
/**
* The `ionic:_testing` param is used to identify testing
* environments.
*/
const isTestEnv = window.location.search.indexOf('ionic:_testing=true') > -1 || window.location.hash.indexOf('ionic:_testing=true') > -1;
if (isTestEnv) {
const style = document.createElement('style');
style.innerHTML = `
* {
caret-color: transparent !important;
}`;
* {
caret-color: transparent !important;
}
`;
document.head.appendChild(style);
}
/**
* The term `palette` is used to as a param to match the
* Ionic docs, plus here is already a `ionic:theme` query being
* used for `md`, `ios`, and `ionic` themes.
* The `palette` param is used to load a specific palette
* for the theme.
* The dark class will load the dark palette automatically
* if no palette is specified through the URL.
*
* Values can be `light`, `dark`, `high-contrast`,
* or `high-contrast-dark`. Default to `light` for tests.
*/
const palette = window.location.search.match(/palette=([a-z]+)/);
if (palette && palette[1] !== 'light') {
const validPalettes = ['light', 'dark', 'high-contrast', 'high-contrast-dark'];
const paletteQuery = window.location.search.match(/palette=([a-z-]+)/);
const paletteHash = window.location.hash.match(/palette=([a-z-]+)/);
const darkClass = document.body?.classList.contains('ion-palette-dark') ? 'dark' : null;
const highContrastClass = document.body?.classList.contains('ion-palette-high-contrast') ? 'high-contrast' : null;
const highContrastDarkClass = darkClass && highContrastClass ? 'high-contrast-dark' : null;
let paletteName = paletteQuery?.[1] || paletteHash?.[1] || highContrastDarkClass || darkClass || highContrastClass || 'light';
if (!validPalettes.includes(paletteName)) {
console.warn(`Invalid palette name: '${paletteName}'. Falling back to 'light' palette.`);
paletteName = 'light';
}
if (paletteName !== 'light') {
const linkTag = document.createElement('link');
linkTag.setAttribute('rel', 'stylesheet');
linkTag.setAttribute('type', 'text/css');
linkTag.setAttribute('href', `/css/palettes/${palette[1]}.always.css`);
linkTag.setAttribute('href', `/css/palettes/${paletteName}.always.css`);
document.head.appendChild(linkTag);
}

View File

@@ -868,6 +868,10 @@ export namespace Components {
* Get the element where the actual scrolling takes place. This element can be used to subscribe to `scroll` events or manually modify `scrollTop`. However, it's recommended to use the API provided by `ion-content`: i.e. Using `ionScroll`, `ionScrollStart`, `ionScrollEnd` for scrolling events and `scrollToPoint()` to scroll the content into a certain point.
*/
"getScrollElement": () => Promise<HTMLElement>;
/**
* Recalculate content dimensions. Called by overlays (e.g., popover) when sibling elements like headers or footers have finished rendering and their heights are available, ensuring accurate offset-top calculations.
*/
"recalculateDimensions": () => Promise<void>;
/**
* Scroll by a specified X/Y distance in the component.
* @param x The amount to scroll by on the horizontal axis.

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Watch, Component, Element, Event, Host, Method, Prop, h, readTask } from '@stencil/core';
import { Watch, Component, Element, Event, Host, Listen, Method, Prop, State, h, readTask } from '@stencil/core';
import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
@@ -46,11 +46,18 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
private wrapperEl?: HTMLElement;
private groupEl?: HTMLElement;
private gesture?: Gesture;
private hasRadioButtons = false;
presented = false;
lastFocus?: HTMLElement;
animation?: any;
/**
* The ID of the currently active/selected radio button.
* Used for keyboard navigation and ARIA attributes.
*/
@State() activeRadioId?: string;
@Element() el!: HTMLIonActionSheetElement;
/** @internal */
@@ -81,6 +88,22 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
* An array of buttons for the action sheet.
*/
@Prop() buttons: (ActionSheetButton | string)[] = [];
@Watch('buttons')
buttonsChanged() {
const radioButtons = this.getRadioButtons();
this.hasRadioButtons = radioButtons.length > 0;
// Initialize activeRadioId when buttons change
if (this.hasRadioButtons) {
const checkedButton = radioButtons.find((b) => b.htmlAttributes?.['aria-checked'] === 'true');
if (checkedButton) {
const allButtons = this.getButtons();
const checkedIndex = allButtons.indexOf(checkedButton);
this.activeRadioId = this.getButtonId(checkedButton, checkedIndex);
}
}
}
/**
* Additional classes to apply for custom CSS. If multiple classes are
@@ -277,12 +300,53 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
return true;
}
/**
* Get all buttons regardless of role.
*/
private getButtons(): ActionSheetButton[] {
return this.buttons.map((b) => {
return typeof b === 'string' ? { text: b } : b;
});
}
/**
* Get all radio buttons (buttons with role="radio").
*/
private getRadioButtons(): ActionSheetButton[] {
return this.getButtons().filter((b) => {
const role = b.htmlAttributes?.role;
return role === 'radio' && !isCancel(role);
});
}
/**
* Handle radio button selection and update aria-checked state.
*
* @param button The radio button that was selected.
*/
private selectRadioButton(button: ActionSheetButton) {
const buttonId = this.getButtonId(button);
// Set the active radio ID (this will trigger a re-render and update aria-checked)
this.activeRadioId = buttonId;
}
/**
* Get or generate an ID for a button.
*
* @param button The button for which to get the ID.
* @param index Optional index of the button in the buttons array.
* @returns The ID of the button.
*/
private getButtonId(button: ActionSheetButton, index?: number): string {
if (button.id) {
return button.id;
}
const allButtons = this.getButtons();
const buttonIndex = index !== undefined ? index : allButtons.indexOf(button);
return `action-sheet-button-${this.overlayIndex}-${buttonIndex}`;
}
private onBackdropTap = () => {
this.dismiss(undefined, BACKDROP);
};
@@ -295,6 +359,96 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
}
};
/**
* When the action sheet has radio buttons, we want to follow the
* keyboard navigation pattern for radio groups:
* - Arrow Down/Right: Move to the next radio button (wrap to first if at end)
* - Arrow Up/Left: Move to the previous radio button (wrap to last if at start)
* - Space/Enter: Select the focused radio button and trigger its handler
*/
@Listen('keydown')
onKeydown(ev: KeyboardEvent) {
// Only handle keyboard navigation if we have radio buttons
if (!this.hasRadioButtons || !this.presented) {
return;
}
const target = ev.target as HTMLElement;
// Ignore if the target element is not within the action sheet or not a radio button
if (
!this.el.contains(target) ||
!target.classList.contains('action-sheet-button') ||
target.getAttribute('role') !== 'radio'
) {
return;
}
// Get all radio button elements and filter out disabled ones
const radios = Array.from(this.el.querySelectorAll('.action-sheet-button[role="radio"]')).filter(
(el) => !(el as HTMLButtonElement).disabled
) as HTMLButtonElement[];
const currentIndex = radios.findIndex((radio) => radio.id === target.id);
if (currentIndex === -1) {
return;
}
const allButtons = this.getButtons();
const radioButtons = this.getRadioButtons();
/**
* Build a map of button element IDs to their ActionSheetButton
* config objects.
* This allows us to quickly look up which button config corresponds
* to a DOM element when handling keyboard navigation
* (e.g., whenuser presses Space/Enter or arrow keys).
* The key is the ID that was set on the DOM element during render,
* and the value is the ActionSheetButton config that contains the
* handler and other properties.
*/
const buttonIdMap = new Map<string, ActionSheetButton>();
radioButtons.forEach((b) => {
const allIndex = allButtons.indexOf(b);
const buttonId = this.getButtonId(b, allIndex);
buttonIdMap.set(buttonId, b);
});
let nextEl: HTMLButtonElement | undefined;
if (['ArrowDown', 'ArrowRight'].includes(ev.key)) {
ev.preventDefault();
ev.stopPropagation();
nextEl = currentIndex === radios.length - 1 ? radios[0] : radios[currentIndex + 1];
} else if (['ArrowUp', 'ArrowLeft'].includes(ev.key)) {
ev.preventDefault();
ev.stopPropagation();
nextEl = currentIndex === 0 ? radios[radios.length - 1] : radios[currentIndex - 1];
} else if (ev.key === ' ' || ev.key === 'Enter') {
ev.preventDefault();
ev.stopPropagation();
const button = buttonIdMap.get(target.id);
if (button) {
this.selectRadioButton(button);
this.buttonClick(button);
}
return;
}
// Focus the next radio button
if (nextEl) {
const button = buttonIdMap.get(nextEl.id);
if (button) {
this.selectRadioButton(button);
nextEl.focus();
}
}
}
connectedCallback() {
prepareOverlay(this.el);
this.triggerChanged();
@@ -312,6 +466,8 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
if (!this.htmlAttributes?.id) {
setOverlayId(this.el);
}
// Initialize activeRadioId for radio buttons
this.buttonsChanged();
}
componentDidLoad() {
@@ -355,8 +511,82 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
this.triggerChanged();
}
private renderActionSheetButtons(filteredButtons: ActionSheetButton[]) {
const mode = getIonMode(this);
const { activeRadioId } = this;
return filteredButtons.map((b, index) => {
const isRadio = b.htmlAttributes?.role === 'radio';
const buttonId = this.getButtonId(b, index);
const radioButtons = this.getRadioButtons();
const isActiveRadio = isRadio && buttonId === activeRadioId;
const isFirstRadio = isRadio && b === radioButtons[0];
// For radio buttons, set tabindex: 0 for the active one, -1 for others
// For non-radio buttons, use default tabindex (undefined, which means 0)
/**
* For radio buttons, set tabindex based on activeRadioId
* - If the button is the active radio, tabindex is 0
* - If no radio is active, the first radio button should have tabindex 0
* - All other radio buttons have tabindex -1
* For non-radio buttons, use default tabindex (undefined, which means 0)
*/
let tabIndex: number | undefined;
if (isRadio) {
// Focus on the active radio button
if (isActiveRadio) {
tabIndex = 0;
} else if (!activeRadioId && isFirstRadio) {
// No active radio, first radio gets focus
tabIndex = 0;
} else {
// All other radios are not focusable
tabIndex = -1;
}
} else {
tabIndex = undefined;
}
// For radio buttons, set aria-checked based on activeRadioId
// Otherwise, use the value from htmlAttributes if provided
const htmlAttrs = { ...b.htmlAttributes };
if (isRadio) {
htmlAttrs['aria-checked'] = isActiveRadio ? 'true' : 'false';
}
return (
<button
{...htmlAttrs}
role={isRadio ? 'radio' : undefined}
type="button"
id={buttonId}
class={{
...buttonClass(b),
'action-sheet-selected': isActiveRadio,
}}
onClick={() => {
if (isRadio) {
this.selectRadioButton(b);
}
this.buttonClick(b);
}}
disabled={b.disabled}
tabIndex={tabIndex}
>
<span class="action-sheet-button-inner">
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
{b.text}
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
);
});
}
render() {
const { header, htmlAttributes, overlayIndex } = this;
const { header, htmlAttributes, overlayIndex, hasRadioButtons } = this;
const mode = getIonMode(this);
const allButtons = this.getButtons();
const cancelButton = allButtons.find((b) => b.role === 'cancel');
@@ -388,7 +618,11 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
<div class="action-sheet-wrapper ion-overlay-wrapper" ref={(el) => (this.wrapperEl = el)}>
<div class="action-sheet-container">
<div class="action-sheet-group" ref={(el) => (this.groupEl = el)}>
<div
class="action-sheet-group"
ref={(el) => (this.groupEl = el)}
role={hasRadioButtons ? 'radiogroup' : undefined}
>
{header !== undefined && (
<div
id={headerID}
@@ -401,22 +635,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
{this.subHeader && <div class="action-sheet-sub-title">{this.subHeader}</div>}
</div>
)}
{buttons.map((b) => (
<button
{...b.htmlAttributes}
type="button"
id={b.id}
class={buttonClass(b)}
onClick={() => this.buttonClick(b)}
disabled={b.disabled}
>
<span class="action-sheet-button-inner">
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
{b.text}
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
))}
{this.renderActionSheetButtons(buttons)}
</div>
{cancelButton && (

View File

@@ -134,3 +134,58 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
});
});
});
/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('action-sheet: radio buttons'), () => {
test('should render action sheet with radio buttons correctly', async ({ page }) => {
await page.goto(`/src/components/action-sheet/test/a11y`, config);
const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
const button = page.locator('#radioButtons');
await button.click();
await ionActionSheetDidPresent.next();
const actionSheet = page.locator('ion-action-sheet');
const radioButtons = actionSheet.locator('.action-sheet-button[role="radio"]');
await expect(radioButtons).toHaveCount(2);
});
test('should navigate radio buttons with keyboard', async ({ page, pageUtils }) => {
await page.goto(`/src/components/action-sheet/test/a11y`, config);
const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
const button = page.locator('#radioButtons');
await button.click();
await ionActionSheetDidPresent.next();
// Focus on the radios
await pageUtils.pressKeys('Tab');
// Verify the first focusable radio button is focused
let focusedElement = await page.evaluate(() => document.activeElement?.textContent?.trim());
expect(focusedElement).toBe('Option 2');
// Navigate to the next radio button
await page.keyboard.press('ArrowDown');
// Verify the first radio button is focused again (wrap around)
focusedElement = await page.evaluate(() => document.activeElement?.textContent?.trim());
expect(focusedElement).toBe('Option 1');
// Navigate to the next radio button
await page.keyboard.press('ArrowDown');
// Navigate to the cancel button
await pageUtils.pressKeys('Tab');
focusedElement = await page.evaluate(() => document.activeElement?.textContent?.trim());
expect(focusedElement).toBe('Cancel');
});
});
});

View File

@@ -27,6 +27,7 @@
<button class="expand" id="ariaLabelCancelButton" onclick="presentAriaLabelCancelButton()">
Aria Label Cancel Button
</button>
<button class="expand" id="radioButtons" onclick="presentRadioButtons()">Radio Buttons</button>
</main>
<script>
@@ -100,6 +101,32 @@
],
});
}
function presentRadioButtons() {
openActionSheet({
header: 'Select an option',
buttons: [
{
text: 'Option 1',
htmlAttributes: {
role: 'radio',
'aria-checked': 'false',
},
},
{
text: 'Option 2',
htmlAttributes: {
role: 'radio',
'aria-checked': 'true',
},
},
{
text: 'Cancel',
role: 'cancel',
},
],
});
}
</script>
</body>
</html>

View File

@@ -18,20 +18,66 @@ configs({ directions: ['ltr'] }).forEach(({ config, title, screenshot }) => {
await expect(page).toHaveScreenshot(screenshot(`app-${screenshotModifier}-diff`));
};
test.beforeEach(async ({ page }) => {
await page.goto(`/src/components/app/test/safe-area`, config);
});
test('should not have visual regressions with action sheet', async ({ page }) => {
await testOverlay(page, '#show-action-sheet', 'ionActionSheetDidPresent', 'action-sheet');
test.describe(title('Ionic safe area variables'), () => {
test.beforeEach(async ({ page }) => {
const htmlTag = page.locator('html');
const hasSafeAreaClass = await htmlTag.evaluate((el) => el.classList.contains('safe-area'));
expect(hasSafeAreaClass).toBe(true);
});
test('should not have visual regressions with action sheet', async ({ page }) => {
await testOverlay(page, '#show-action-sheet', 'ionActionSheetDidPresent', 'action-sheet');
});
test('should not have visual regressions with menu', async ({ page }) => {
await testOverlay(page, '#show-menu', 'ionDidOpen', 'menu');
});
test('should not have visual regressions with picker', async ({ page }) => {
await testOverlay(page, '#show-picker', 'ionPickerDidPresent', 'picker');
});
test('should not have visual regressions with toast', async ({ page }) => {
await testOverlay(page, '#show-toast', 'ionToastDidPresent', 'toast');
});
});
test('should not have visual regressions with menu', async ({ page }) => {
await testOverlay(page, '#show-menu', 'ionDidOpen', 'menu');
});
test('should not have visual regressions with picker', async ({ page }) => {
await testOverlay(page, '#show-picker', 'ionPickerDidPresent', 'picker');
});
test('should not have visual regressions with toast', async ({ page }) => {
await testOverlay(page, '#show-toast', 'ionToastDidPresent', 'toast');
test.describe(title('Capacitor safe area variables'), () => {
test('should use safe-area-inset vars when safe-area class is not defined', async ({ page }) => {
await page.evaluate(() => {
const html = document.documentElement;
// Remove the safe area class
html.classList.remove('safe-area');
// Set the safe area inset variables
html.style.setProperty('--safe-area-inset-top', '10px');
html.style.setProperty('--safe-area-inset-bottom', '20px');
html.style.setProperty('--safe-area-inset-left', '30px');
html.style.setProperty('--safe-area-inset-right', '40px');
});
const top = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--ion-safe-area-top').trim()
);
const bottom = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--ion-safe-area-bottom').trim()
);
const left = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--ion-safe-area-left').trim()
);
const right = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--ion-safe-area-right').trim()
);
expect(top).toBe('10px');
expect(bottom).toBe('20px');
expect(left).toBe('30px');
expect(right).toBe('40px');
});
});
});
});

View File

@@ -170,6 +170,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
*/
@Watch('aria-checked')
@Watch('aria-label')
@Watch('aria-pressed')
onAriaChanged(newValue: string, _oldValue: string, propName: string) {
this.inheritedAttributes = {
...this.inheritedAttributes,

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

@@ -254,6 +254,17 @@ export class Content implements ComponentInterface {
}
}
/**
* Recalculate content dimensions. Called by overlays (e.g., popover) when
* sibling elements like headers or footers have finished rendering and their
* heights are available, ensuring accurate offset-top calculations.
* @internal
*/
@Method()
async recalculateDimensions(): Promise<void> {
readTask(() => this.readDimensions());
}
private readDimensions() {
const page = getPageElement(this.el);
const top = Math.max(this.el.offsetTop, 0);

View File

@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import { expect } from '@playwright/test';
import type { EventSpy } from '@utils/test/playwright';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions.
@@ -176,5 +176,34 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await ionModalDidPresent.next();
await expect(datetime).toBeVisible();
});
test('should set datetime ready state and keep calendar interactive when reopening modal', async ({
page,
}, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30706',
});
const openAndInteract = async () => {
await page.click('#date-button');
await ionModalDidPresent.next();
await page.locator('ion-datetime.datetime-ready').waitFor();
const calendarBody = datetime.locator('.calendar-body');
await expect(calendarBody).toBeVisible();
};
await openAndInteract();
const firstEnabledDay = datetime.locator('.calendar-day:not([disabled])').first();
await firstEnabledDay.click();
await page.waitForChanges();
await modal.evaluate((el: HTMLIonModalElement) => el.dismiss());
await ionModalDidDismiss.next();
await openAndInteract();
});
});
});

View File

@@ -1101,6 +1101,32 @@ export class Datetime implements ComponentInterface {
this.initializeKeyboardListeners();
}
/**
* TODO(FW-6931): Remove this fallback upon solving the root cause
* Fallback to ensure the datetime becomes ready even if
* IntersectionObserver never reports it as intersecting.
*
* This is primarily used in environments where the observer
* might not fire as expected, such as when running under
* synthetic tests that stub IntersectionObserver.
*/
private ensureReadyIfVisible = () => {
if (this.el.classList.contains('datetime-ready')) {
return;
}
const rect = this.el.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return;
}
this.initializeListeners();
writeTask(() => {
this.el.classList.add('datetime-ready');
});
};
componentDidLoad() {
const { el, intersectionTrackerRef } = this;
@@ -1141,6 +1167,18 @@ export class Datetime implements ComponentInterface {
*/
raf(() => visibleIO?.observe(intersectionTrackerRef!));
/**
* TODO(FW-6931): Remove this fallback upon solving the root cause
* Fallback: If IntersectionObserver never reports that the
* datetime is visible but the host clearly has layout, ensure
* we still initialize listeners and mark the component as ready.
*
* We schedule this after everything has had a chance to run.
*/
setTimeout(() => {
this.ensureReadyIfVisible();
}, 100);
/**
* We need to clean up listeners when the datetime is hidden
* in a popover/modal so that we can properly scroll containers

View File

@@ -394,6 +394,61 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
/**
* Synthetic IntersectionObserver fallback behavior.
*
* This test stubs IntersectionObserver so that the callback
* never reports an intersecting entry. The datetime should
* still become ready via its internal fallback logic.
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: IO fallback'), () => {
test('should become ready even if IntersectionObserver never reports visible', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30706',
});
await page.addInitScript(() => {
const OriginalIO = window.IntersectionObserver;
(window as any).IntersectionObserver = function (callback: any, options: any) {
const instance = new OriginalIO(() => {}, options);
const originalObserve = instance.observe.bind(instance);
instance.observe = (target: Element) => {
originalObserve(target);
callback([
{
isIntersecting: false,
target,
} as IntersectionObserverEntry,
]);
};
return instance;
} as any;
});
await page.setContent(
`
<ion-datetime value="2022-05-03"></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
// Give the fallback a short amount of time to run
await page.waitForTimeout(100);
await expect(datetime).toHaveClass(/datetime-ready/);
const calendarBody = datetime.locator('.calendar-body');
await expect(calendarBody).toBeVisible();
});
});
});
/**
* We are setting RTL on the component
* instead, so we don't need to test

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

@@ -8,7 +8,7 @@
box-shadow: $header-md-box-shadow;
}
.header-collapse-condense {
.header-md.header-collapse-condense {
display: none;
}

View File

@@ -0,0 +1,71 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This test verifies that collapsible headers with mode="ios" work correctly
* when both iOS and MD stylesheets are loaded. The bug occurred because
* `.header-collapse-condense { display: none }` in the MD stylesheet was not
* scoped to `.header-md`, causing it to hide iOS condense headers when both
* stylesheets were present.
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('header: condense with iOS mode override'), () => {
test('should show iOS condense header when both MD and iOS styles are loaded', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/29929',
});
// Include both an MD header and an iOS modal to force both stylesheets to load
await page.setContent(
`
<!-- MD header to force MD stylesheet to load -->
<ion-header mode="md" id="mdHeader">
<ion-toolbar>
<ion-title>MD Header</ion-title>
</ion-toolbar>
</ion-header>
<!-- Modal with iOS condense header -->
<ion-modal>
<ion-header mode="ios" id="smallTitleHeader">
<ion-toolbar>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true">
<ion-header collapse="condense" mode="ios" id="largeTitleHeader">
<ion-toolbar>
<ion-title size="large">Large Header</ion-title>
</ion-toolbar>
</ion-header>
<p>Content</p>
</ion-content>
</ion-modal>
`,
config
);
const modal = page.locator('ion-modal');
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await modal.evaluate((el: HTMLIonModalElement) => el.present());
await ionModalDidPresent.next();
const largeTitleHeader = modal.locator('#largeTitleHeader');
// The large title header should be visible, not hidden by MD styles
await expect(largeTitleHeader).toBeVisible();
// Verify it has the iOS mode class
await expect(largeTitleHeader).toHaveClass(/header-ios/);
// Verify it does NOT have display: none applied
// This would fail if the MD stylesheet's unscoped .header-collapse-condense rule applies
const display = await largeTitleHeader.evaluate((el) => {
return window.getComputedStyle(el).display;
});
expect(display).not.toBe('none');
});
});
});

View File

@@ -126,9 +126,8 @@ export class InputPasswordToggle implements ComponentInterface {
color={color}
fill="clear"
shape="round"
aria-checked={isPasswordVisible ? 'true' : 'false'}
aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
role="switch"
aria-pressed={isPasswordVisible ? 'true' : 'false'}
type="button"
onPointerDown={(ev) => {
/**

View File

@@ -22,7 +22,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
});
test.describe(title('input password toggle: aria attributes'), () => {
test('should inherit aria attributes to inner button on load', async ({ page }) => {
test('should have correct aria attributes on load', async ({ page }) => {
await page.setContent(
`
<ion-input label="input" type="password">
@@ -35,9 +35,9 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
const nativeButton = page.locator('ion-input-password-toggle button');
await expect(nativeButton).toHaveAttribute('aria-label', 'Show password');
await expect(nativeButton).toHaveAttribute('aria-checked', 'false');
await expect(nativeButton).toHaveAttribute('aria-pressed', 'false');
});
test('should inherit aria attributes to inner button after toggle', async ({ page }) => {
test('should update aria attributes after toggle', async ({ page }) => {
await page.setContent(
`
<ion-input label="input" type="password">
@@ -51,7 +51,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
await nativeButton.click();
await expect(nativeButton).toHaveAttribute('aria-label', 'Hide password');
await expect(nativeButton).toHaveAttribute('aria-checked', 'true');
await expect(nativeButton).toHaveAttribute('aria-pressed', 'true');
});
});
});

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

@@ -71,7 +71,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
private gesture?: Gesture;
private coreDelegate: FrameworkDelegate = CoreDelegate();
private sheetTransition?: Promise<any>;
private isSheetModal = false;
@State() private isSheetModal = false;
private currentBreakpoint?: number;
private wrapperEl?: HTMLElement;
private backdropEl?: HTMLIonBackdropElement;
@@ -100,6 +100,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
private parentRemovalObserver?: MutationObserver;
// Cached original parent from before modal is moved to body during presentation
private cachedOriginalParent?: HTMLElement;
// Cached ion-page ancestor for child route passthrough
private cachedPageParent?: HTMLElement | null;
lastFocus?: HTMLElement;
animation?: Animation;
@@ -644,7 +646,14 @@ export class Modal implements ComponentInterface, OverlayInterface {
window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
}
if (this.isSheetModal) {
/**
* Recalculate isSheetModal because framework bindings (e.g., Angular)
* may not have been applied when componentWillLoad ran.
*/
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
this.isSheetModal = isSheetModal;
if (isSheetModal) {
this.initSheetGesture();
} else if (hasCardModal) {
this.initSwipeToClose();
@@ -753,6 +762,91 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.moveSheetToBreakpoint = moveSheetToBreakpoint;
this.gesture.enable(true);
/**
* When backdrop interaction is allowed, nested router outlets from child routes
* may block pointer events to parent content. Apply passthrough styles only when
* the modal was the sole content of a child route page.
* See https://github.com/ionic-team/ionic-framework/issues/30700
*/
const backdropNotBlocking = this.showBackdrop === false || this.focusTrap === false || backdropBreakpoint > 0;
if (backdropNotBlocking) {
this.setupChildRoutePassthrough();
}
}
/**
* For sheet modals that allow background interaction, sets up pointer-events
* passthrough on child route page wrappers and nested router outlets.
*/
private setupChildRoutePassthrough() {
// Cache the page parent for cleanup
this.cachedPageParent = this.getOriginalPageParent();
const pageParent = this.cachedPageParent;
// Skip ion-app (controller modals) and pages with visible sibling content next to the modal
if (!pageParent || pageParent.tagName === 'ION-APP') {
return;
}
const hasVisibleContent = Array.from(pageParent.children).some(
(child) =>
child !== this.el &&
!(child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') &&
child.tagName !== 'TEMPLATE' &&
child.tagName !== 'SLOT' &&
!(child.nodeType === Node.TEXT_NODE && !child.textContent?.trim())
);
if (hasVisibleContent) {
return;
}
// Child route case: page only contained the modal
pageParent.classList.add('ion-page-overlay-passthrough');
// Also make nested router outlets passthrough
const routerOutlet = pageParent.parentElement;
if (routerOutlet?.tagName === 'ION-ROUTER-OUTLET' && routerOutlet.parentElement?.tagName !== 'ION-APP') {
routerOutlet.style.setProperty('pointer-events', 'none');
routerOutlet.setAttribute('data-overlay-passthrough', 'true');
}
}
/**
* Finds the ion-page ancestor of the modal's original parent location.
*/
private getOriginalPageParent(): HTMLElement | null {
if (!this.cachedOriginalParent) {
return null;
}
let pageParent: HTMLElement | null = this.cachedOriginalParent;
while (pageParent && !pageParent.classList.contains('ion-page')) {
pageParent = pageParent.parentElement;
}
return pageParent;
}
/**
* Removes passthrough styles added by setupChildRoutePassthrough.
*/
private cleanupChildRoutePassthrough() {
const pageParent = this.cachedPageParent;
if (!pageParent) {
return;
}
pageParent.classList.remove('ion-page-overlay-passthrough');
const routerOutlet = pageParent.parentElement;
if (routerOutlet?.hasAttribute('data-overlay-passthrough')) {
routerOutlet.style.removeProperty('pointer-events');
routerOutlet.removeAttribute('data-overlay-passthrough');
}
// Clear the cached reference
this.cachedPageParent = undefined;
}
private sheetOnDismiss() {
@@ -862,6 +956,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
this.cleanupViewTransitionListener();
this.cleanupParentRemovalObserver();
this.cleanupChildRoutePassthrough();
}
this.currentBreakpoint = undefined;
this.animation = undefined;
@@ -1020,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
@@ -1183,6 +1284,20 @@ export class Modal implements ComponentInterface, OverlayInterface {
return;
}
/**
* Don't observe for controller-based modals or when the parent is the
* app root (document.body or ion-app). These parents won't be removed,
* and observing document.body with subtree: true causes performance
* issues with frameworks like Angular during change detection.
*/
if (
this.hasController ||
this.cachedOriginalParent === document.body ||
this.cachedOriginalParent.tagName === 'ION-APP'
) {
return;
}
this.parentRemovalObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {

View File

@@ -1,6 +1,6 @@
import { expect } from '@playwright/test';
import { configs, test, Viewports } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright';
import { configs, test, Viewports } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('modal: focus trapping'), () => {
@@ -104,6 +104,28 @@ configs().forEach(({ title, screenshot, config }) => {
});
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('modal: parent removal observer'), () => {
test('should not set up parentRemovalObserver for controller-created modals', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'FW-6766',
});
await page.goto('/src/components/modal/test/basic', config);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#basic-modal');
await ionModalDidPresent.next();
const modal = page.locator('ion-modal');
const hasObserver = await modal.evaluate((el: any) => {
return el.parentRemovalObserver !== undefined;
});
expect(hasObserver).toBe(false);
});
});
test.describe(title('modal: backdrop'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/modal/test/basic', config);

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

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Modal - Dismiss Behavior</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>Modal - Dismiss Behavior</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<button id="present-first-modal" onclick="presentFirstModal()">Present Modal</button>
</ion-content>
</div>
</ion-app>
<script type="module">
import { modalController } from '../../../../../dist/ionic/index.esm.js';
window.modalController = modalController;
const sharedId = 'shared-modal-id';
const maxModals = 5;
let modalCount = 0;
function createModalComponent(modalNumber) {
const element = document.createElement('div');
const canPresentNext = modalNumber < maxModals;
const presentNextButton = canPresentNext
? `<ion-button id="present-next-modal" onclick="presentNextModal(${modalNumber + 1})">Present Modal ${
modalNumber + 1
}</ion-button>`
: '';
element.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Modal ${modalNumber}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<p>This is modal number ${modalNumber}</p>
${presentNextButton}
<ion-button class="dismiss-by-id">Dismiss By ID</ion-button>
<ion-button class="dismiss-default">Dismiss Default</ion-button>
</ion-content>
`;
return element;
}
window.presentFirstModal = async () => {
modalCount = 0;
await presentNextModal(1);
};
window.presentNextModal = async (modalNumber) => {
if (modalNumber > maxModals) {
return;
}
modalCount = Math.max(modalCount, modalNumber);
const element = createModalComponent(modalNumber);
const modal = await modalController.create({
component: element,
htmlAttributes: {
id: sharedId,
'data-testid': `modal-${modalNumber}`,
},
});
await modal.present();
const dismissByIdButton = element.querySelector('ion-button.dismiss-by-id');
dismissByIdButton.addEventListener('click', () => {
modalController.dismiss(undefined, undefined, sharedId);
});
const dismissDefaultButton = element.querySelector('ion-button.dismiss-default');
dismissDefaultButton.addEventListener('click', () => {
modalController.dismiss();
});
};
</script>
</body>
</html>

View File

@@ -0,0 +1,58 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('modal: dismiss behavior'), () => {
test.describe(title('modal: default dismiss'), () => {
test('should dismiss the last presented modal when the default dismiss button is clicked', async ({ page }) => {
await page.goto('/src/components/modal/test/dismiss-behavior', config);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
await page.click('#present-first-modal');
await ionModalDidPresent.next();
const firstModal = page.locator('ion-modal[data-testid="modal-1"]');
await expect(firstModal).toBeVisible();
await page.click('#present-next-modal');
await ionModalDidPresent.next();
const secondModal = page.locator('ion-modal[data-testid="modal-2"]');
await expect(secondModal).toBeVisible();
await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-default');
await ionModalDidDismiss.next();
await secondModal.waitFor({ state: 'detached' });
await expect(firstModal).toBeVisible();
await expect(secondModal).toBeHidden();
});
});
test.describe(title('modal: dismiss by id'), () => {
test('should dismiss the last presented modal when the dismiss by id button is clicked', async ({ page }) => {
await page.goto('/src/components/modal/test/dismiss-behavior', config);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
await page.click('#present-first-modal');
await ionModalDidPresent.next();
const firstModal = page.locator('ion-modal[data-testid="modal-1"]');
await expect(firstModal).toBeVisible();
await page.click('#present-next-modal');
await ionModalDidPresent.next();
const secondModal = page.locator('ion-modal[data-testid="modal-2"]');
await expect(secondModal).toBeVisible();
await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-by-id');
await ionModalDidDismiss.next();
await secondModal.waitFor({ state: 'detached' });
await expect(firstModal).toBeVisible();
await expect(secondModal).toBeHidden();
});
});
});
});

View File

@@ -1,7 +1,7 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { Modal } from '../modal';
import { h } from '@stencil/core';
describe('modal: id', () => {
it('modal should be assigned an incrementing id', async () => {
@@ -52,4 +52,21 @@ describe('modal: id', () => {
const alert = page.body.querySelector('ion-modal')!;
expect(alert.id).toBe(id);
});
it('should allow multiple modals with the same id', async () => {
const sharedId = 'shared-modal-id';
const page = await newSpecPage({
components: [Modal],
template: () => [
<ion-modal id={sharedId} overlayIndex={1} is-open={true}></ion-modal>,
<ion-modal id={sharedId} overlayIndex={2} is-open={true}></ion-modal>,
],
});
const modals = page.body.querySelectorAll('ion-modal');
expect(modals.length).toBe(2);
expect(modals[0].id).toBe(sharedId);
expect(modals[1].id).toBe(sharedId);
});
});

View File

@@ -64,6 +64,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
private destroyTriggerInteraction?: () => void;
private destroyKeyboardInteraction?: () => void;
private destroyDismissInteraction?: () => void;
private headerResizeObserver?: ResizeObserver;
private inline = false;
private workingDelegate?: FrameworkDelegate;
@@ -361,6 +362,11 @@ export class Popover implements ComponentInterface, PopoverInterface {
if (destroyTriggerInteraction) {
destroyTriggerInteraction();
}
if (this.headerResizeObserver) {
this.headerResizeObserver.disconnect();
this.headerResizeObserver = undefined;
}
}
componentWillLoad() {
@@ -491,6 +497,8 @@ export class Popover implements ComponentInterface, PopoverInterface {
inline
);
this.recalculateContentOnHeaderReady();
if (!this.keyboardEvents) {
this.configureKeyboardInteraction();
}
@@ -540,6 +548,39 @@ export class Popover implements ComponentInterface, PopoverInterface {
unlock();
}
/**
* Watch the header for height changes and trigger content dimension
* recalculation when the header has a height > 0. This sets the offset-top
* of the content to the height of the header correctly.
*/
private recalculateContentOnHeaderReady() {
const popoverContent = this.el.shadowRoot?.querySelector('.popover-content');
if (!popoverContent) {
return;
}
const contentContainer = this.usersElement || popoverContent;
const header = contentContainer.querySelector('ion-header') as HTMLElement | null;
const contentElements = contentContainer.querySelectorAll('ion-content');
if (!header || contentElements.length === 0) {
return;
}
this.headerResizeObserver = new ResizeObserver(async () => {
if (header.offsetHeight > 0) {
this.headerResizeObserver?.disconnect();
this.headerResizeObserver = undefined;
for (const contentEl of contentElements) {
await contentEl.recalculateDimensions();
}
}
});
this.headerResizeObserver.observe(header);
}
/**
* Dismiss the popover overlay after it has been presented.
* This is a no-op if the overlay has not been presented yet. If you want

View File

@@ -556,14 +556,19 @@ export class Select implements ComponentInterface {
.filter((cls) => cls !== 'hydrated')
.join(' ');
const optClass = `${OPTION_CLASS} ${copyClasses}`;
const isSelected = isOptionSelected(selectValue, value, this.compareWith);
return {
role: isOptionSelected(selectValue, value, this.compareWith) ? 'selected' : '',
role: isSelected ? 'selected' : '',
text: option.textContent,
cssClass: optClass,
handler: () => {
this.setValue(value);
},
htmlAttributes: {
'aria-checked': isSelected ? 'true' : 'false',
role: 'radio',
},
} as ActionSheetButton;
});

View File

@@ -3,7 +3,7 @@ import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title, config }) => {
test.describe(title('textarea: a11y'), () => {
test.describe(title('select: a11y'), () => {
test('default layout should not have accessibility violations', async ({ page }) => {
await page.setContent(
`

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

@@ -181,6 +181,15 @@ html.ios ion-modal.modal-card .ion-page {
z-index: $z-index-page-container;
}
/**
* Allows pointer events to pass through child route page wrappers
* when they only contain a sheet modal that permits background interaction.
* https://github.com/ionic-team/ionic-framework/issues/30700
*/
.ion-page.ion-page-overlay-passthrough {
pointer-events: none;
}
/**
* When making custom dialogs, using
* ion-content is not required. As a result,
@@ -243,10 +252,12 @@ html.plt-ios.plt-hybrid, html.plt-ios.plt-pwa {
@supports (padding-top: env(safe-area-inset-top)) {
html {
--ion-safe-area-top: env(safe-area-inset-top);
--ion-safe-area-bottom: env(safe-area-inset-bottom);
--ion-safe-area-left: env(safe-area-inset-left);
--ion-safe-area-right: env(safe-area-inset-right);
// `--safe-area-inset-*` are set by Capacitor
// @see https://capacitorjs.com/docs/apis/system-bars#android-note
--ion-safe-area-top: var(--safe-area-inset-top, env(safe-area-inset-top));
--ion-safe-area-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
--ion-safe-area-left: var(--safe-area-inset-left, env(safe-area-inset-left));
--ion-safe-area-right: var(--safe-area-inset-right, env(safe-area-inset-right));
}
}

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

@@ -38,6 +38,20 @@ let lastId = 0;
export const activeAnimations = new WeakMap<OverlayInterface, Animation[]>();
type OverlayWithFocusTrapProps = HTMLIonOverlayElement & {
focusTrap?: boolean;
showBackdrop?: boolean;
backdropBreakpoint?: number;
};
/**
* Determines if the overlay's backdrop is always blocking (no background interaction).
* Returns false if showBackdrop=false or backdropBreakpoint > 0.
*/
const isBackdropAlwaysBlocking = (el: OverlayWithFocusTrapProps): boolean => {
return el.showBackdrop !== false && !((el.backdropBreakpoint ?? 0) > 0);
};
const createController = <Opts extends object, HTMLElm>(tagName: string) => {
return {
create(options: Opts): Promise<HTMLElm> {
@@ -459,7 +473,9 @@ export const getPresentedOverlay = (
id?: string
): HTMLIonOverlayElement | undefined => {
const overlays = getPresentedOverlays(doc, overlayTag);
return id === undefined ? overlays[overlays.length - 1] : overlays.find((o) => o.id === id);
// If no id is provided, return the last presented overlay
// Otherwise, return the last overlay with the given id
return (id === undefined ? overlays : overlays.filter((o: HTMLIonOverlayElement) => o.id === id)).slice(-1)[0];
};
/**
@@ -539,11 +555,9 @@ export const present = async <OverlayPresentOptions>(
* view container subtree, skip adding aria-hidden/inert there
* to avoid disabling the overlay.
*/
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
const overlayEl = overlay.el as OverlayWithFocusTrapProps;
const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false;
// Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
// expect background interaction to remain enabled.
const shouldLockRoot = shouldTrapFocus && overlayEl.showBackdrop !== false;
const shouldLockRoot = shouldTrapFocus && isBackdropAlwaysBlocking(overlayEl);
overlay.presented = true;
overlay.willPresent.emit();
@@ -680,12 +694,12 @@ export const dismiss = async <OverlayDismissOptions>(
* is dismissed.
*/
const overlaysLockingRoot = presentedOverlays.filter((o) => {
const el = o as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && el.showBackdrop !== false;
const el = o as OverlayWithFocusTrapProps;
return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && isBackdropAlwaysBlocking(el);
});
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
const overlayEl = overlay.el as OverlayWithFocusTrapProps;
const locksRoot =
overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && overlayEl.showBackdrop !== false;
overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && isBackdropAlwaysBlocking(overlayEl);
/**
* If this is the last visible overlay that is trapping focus

View File

@@ -102,6 +102,13 @@ export const setContent = async (page: Page, html: string, testInfo: TestInfo, o
}
});
/**
* URL query parameters cause the custom Playwright `page.route`
* interceptor to fail, which is necessary to inject the test HTML.
*
* To avoid this, the final navigation URL is kept simple by using
* hash params to ensure the route interceptor always works.
*/
await page.goto(`${baseUrl}#`, options);
}
};

View File

@@ -186,7 +186,21 @@ test.describe.only('group of tests', () => {
> [!IMPORTANT]
> After debugging, make sure to remove the `.only` suffix to ensure all tests run again during normal execution.
#### 3. Pausing Test Execution
#### 3. Repeating Tests to Reproduce Flaky Failures
Flaky tests may not fail consistently. To help reproduce intermittent failures locally, you can use Playwright's `--repeat-each` flag to run the same test multiple times in a row.
This is especially useful when CI reports a failure you cannot reproduce on your first local run.
**Example:**
```shell
npm run test.e2e.docker.update-snapshots src/components/radio/test/a11y/radio.e2e.ts -- --repeat-each=10
```
This runs the test 10 times, increasing the chance of catching the flaky behavior.
#### 4. Pausing Test Execution
Additionally, you can pause execution of a test by using the `page.pause()` method. This pauses the script execution and allows you to manually inspect the page in the browser.

View File

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

View File

@@ -3,6 +3,70 @@
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
## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19)
**Note:** Version bump only for package @ionic/angular-server
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "8.7.9",
"version": "8.7.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "8.7.9",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.9"
"@ionic/core": "^8.7.17"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^16.0.0",
@@ -1031,14 +1031,17 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.9",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.9.tgz",
"integrity": "sha512-6cYp3LHjCGX32MiYR09l/O+8klkJ27WpKzllvnLRe5bxX3nOuqJvJJIshzT85qTdZNTFu7zJJrjWkgEOXGarzg==",
"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",
"ionicons": "^8.0.13",
"tslib": "^2.1.0"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/@ionic/eslint-config": {
@@ -7306,9 +7309,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.7.9",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.9.tgz",
"integrity": "sha512-6cYp3LHjCGX32MiYR09l/O+8klkJ27WpKzllvnLRe5bxX3nOuqJvJJIshzT85qTdZNTFu7zJJrjWkgEOXGarzg==",
"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.9",
"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.9"
"@ionic/core": "^8.7.17"
}
}

View File

@@ -3,6 +3,76 @@
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
## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17)
**Note:** Version bump only for package @ionic/angular
## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13)
**Note:** Version bump only for package @ionic/angular
## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10)
### Bug Fixes
* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700)
## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26)
**Note:** Version bump only for package @ionic/angular
## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19)
### Bug Fixes
* **checkbox, toggle, radio-group:** improve screen reader announcement timing for validation errors ([#30714](https://github.com/ionic-team/ionic-framework/issues/30714)) ([92db364](https://github.com/ionic-team/ionic-framework/commit/92db36489cca944caf1593dbd518a1f025a171a2))
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)
**Note:** Version bump only for package @ionic/angular

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "8.7.9",
"version": "8.7.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "8.7.9",
"version": "8.7.17",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.7.9",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -32,7 +32,7 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@schematics/angular": "^17.0.0",
"@types/node": "12.12.5",
"@types/node": "20.19.25",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.0",
@@ -41,7 +41,7 @@
"ng-packagr": "^16.0.0",
"prettier": "^2.4.1",
"rxjs": "~7.5.0",
"typescript": "~4.9.3",
"typescript": "~5.0.2",
"typescript-eslint-language-service": "^5.0.0",
"zone.js": "~0.13.0"
},
@@ -1398,14 +1398,17 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.7.9",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.9.tgz",
"integrity": "sha512-6cYp3LHjCGX32MiYR09l/O+8klkJ27WpKzllvnLRe5bxX3nOuqJvJJIshzT85qTdZNTFu7zJJrjWkgEOXGarzg==",
"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",
"ionicons": "^8.0.13",
"tslib": "^2.1.0"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/@ionic/eslint-config": {
@@ -2402,9 +2405,14 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "12.12.5",
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/resolve": {
"version": "1.20.2",
@@ -8719,16 +8727,17 @@
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=12.20"
}
},
"node_modules/typescript-eslint-language-service": {
@@ -8756,6 +8765,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/unique-filename": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "8.7.9",
"version": "8.7.17",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -48,7 +48,7 @@
}
},
"dependencies": {
"@ionic/core": "^8.7.9",
"@ionic/core": "^8.7.17",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -78,7 +78,7 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@schematics/angular": "^17.0.0",
"@types/node": "12.12.5",
"@types/node": "20.19.25",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.0",
@@ -87,7 +87,7 @@
"ng-packagr": "^16.0.0",
"prettier": "^2.4.1",
"rxjs": "~7.5.0",
"typescript": "~4.9.3",
"typescript": "~5.0.2",
"typescript-eslint-language-service": "^5.0.0",
"zone.js": "~0.13.0"
},

View File

@@ -40,6 +40,7 @@
"@angular/language-service": "^17.0.2",
"@playwright/test": "^1.54.2",
"@types/express": "^4.17.7",
"@types/node": "^18.19.0",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
@@ -567,17 +568,6 @@
"node": ">=12"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@types/node": {
"version": "20.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitejs/plugin-basic-ssl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz",
@@ -595,6 +585,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
"dev": true,
"peer": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
@@ -848,6 +839,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.0.2.tgz",
"integrity": "sha512-32RHWhTgFLMonI3kRdstACay/nvetfxXjdwcTtABjcvBoND7nD9GMhkISQdgS+hcR/IhgXxaPidq8f2UAY5DBw==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -863,6 +855,7 @@
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.0.0.tgz",
"integrity": "sha512-VzKYmN+1wSE2wLBEFu/NHSrRQbeaKkEDO9aZxd7nAnxC5T1Zcva7dfqZIXMHXH2HESYAZCvOS8ikBE/7DCtWWw==",
"dev": true,
"peer": true,
"dependencies": {
"@angular-devkit/architect": "0.1700.0",
"@angular-devkit/core": "17.0.0",
@@ -896,6 +889,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.2.tgz",
"integrity": "sha512-hCW0njHgrcwTWNoKZDwf02DnhYLVWNXM2FMw66MKpfxTp7McSyaXjGBU9/hchW3dZJ0xTwyxoyoqJFoHYvg0yg==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -911,6 +905,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.0.2.tgz",
"integrity": "sha512-ewUFbKhMEhAmw2dGfk0ImhTlyrO2y4pJSKIZdFrkR1d0HiJX8bCHUdTiiR/2jeP7w2eamjXj15Rptb+iZZes2Q==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -931,6 +926,7 @@
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.0.2.tgz",
"integrity": "sha512-IUYL3Yz5RbR0Z0/x7it4GK3sMb2qVihxu0tlgfUW53P1Vi6nU/Zda0bCJTu6Z64qEtS8zwCwF1Ekomuq6UaiKg==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/core": "7.23.2",
"@jridgewell/sourcemap-codec": "^1.4.14",
@@ -958,6 +954,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.0.2.tgz",
"integrity": "sha512-MjDxWeyn3Txi0qo/V/I+B/gndh0uptQ0XWgBRwOx6Wcr5zRGeZIFlXBxPpyXnGTlJkeyErsTN7FfFCZ4C3kCPA==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -973,6 +970,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.0.2.tgz",
"integrity": "sha512-w1QKifaVG4daxUktcBNZqBtOH1vn8t0YiwDR3woEdUYt0XYKMipfDzQfyIK+6fIVPOJUd42pRns1nbWJQHOInA==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -999,6 +997,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.0.2.tgz",
"integrity": "sha512-eTnPILEA/eAMkVUR/+g6fWhhMTmnmOzcZSGX/bBgQcvOhayZrDDxA6/Qf+jIB4RwC0wd3KA9zT5BCMmNojoUsg==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -1037,6 +1036,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-17.0.2.tgz",
"integrity": "sha512-+1uCnAw7Ql2r4BDnfaRvQrXI1H5qUB/1f8CwCjaVoIn7kLJs/ps4I0WbOVtujJ2VPnxIggfVtenXRRMlungZlg==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0",
"xhr2": "^0.2.0"
@@ -1056,6 +1056,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-17.0.2.tgz",
"integrity": "sha512-A1Ulv4qBAtJyK5g1yBlK1qZHe+KaaL5vMPAaPWUxICH8lHEodDkJlbYAUI2e4VL2BN7zBmdOep6tlBKPmHY3mw==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -1115,6 +1116,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
"dev": true,
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.13",
@@ -4492,10 +4494,14 @@
"dev": true
},
"node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
"dev": true
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-forge": {
"version": "1.3.9",
@@ -4617,6 +4623,7 @@
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz",
"integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.10.0",
"@typescript-eslint/types": "6.10.0",
@@ -5024,6 +5031,7 @@
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5095,6 +5103,7 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -5822,6 +5831,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001541",
"electron-to-chromium": "^1.4.535",
@@ -7605,6 +7615,7 @@
"version": "7.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz",
"integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==",
"peer": true,
"dependencies": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.3",
@@ -9838,6 +9849,7 @@
"resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
"dev": true,
"peer": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
@@ -11135,6 +11147,7 @@
"integrity": "sha512-VShJISKCYt3iVJoMUPZiv67+0tiItxWMnfVmTmPZPio2Fu+wGc9U4ijjPxcmp2RJmLRaxkB9cn5rlrAvkIrNMA==",
"dev": true,
"hasInstallScript": true,
"peer": true,
"dependencies": {
"@nrwl/tao": "17.0.3",
"@yarnpkg/lockfile": "^1.1.0",
@@ -12111,6 +12124,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@@ -12856,6 +12870,7 @@
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -12889,6 +12904,7 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
"integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==",
"dev": true,
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -13913,6 +13929,7 @@
"resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz",
"integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -13965,6 +13982,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -14275,6 +14293,7 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14332,9 +14351,7 @@
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"optional": true,
"peer": true
"dev": true
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
@@ -14586,6 +14603,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"dev": true,
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
@@ -14633,6 +14651,7 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
"peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
@@ -14717,6 +14736,7 @@
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
"integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==",
"dev": true,
"peer": true,
"dependencies": {
"@types/bonjour": "^3.5.9",
"@types/connect-history-api-fallback": "^1.3.5",
@@ -14891,6 +14911,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -14912,6 +14933,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -15251,6 +15273,7 @@
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.2.tgz",
"integrity": "sha512-X4U7J1isDhoOmHmFWiLhloWc2lzMkdnumtfQ1LXzf/IOZp5NQYuMUTaviVzG/q1ugMBIXzin2AqeVJUoSEkNyQ==",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
}

Some files were not shown because too many files have changed in this diff Show More