- The deprecated color step CSS variables, `--ion-color-step-[number]`, have been removed and should be replaced with either `--ion-background-color-step-[number]` or `--ion-text-color-step-[number]` depending on the specific use case, as outlined in the [migration guide](https://ionicframework.com/docs/updating/8-0#step-color-tokens).
-
Components
+
Components
-
Button
+
Button
- The `border-radius` of the `ios` and `md` button now defaults to `6px` and `999px` instead of `14px` and `4px`, respectively, in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"soft"` for `md` and override the `--border-radius` CSS variable for `ios` to `14px`, or set it to a different value entirely.
-
Card
+
Card
- **ion-card**: The `border-radius` of the `ios` and `md` card now defaults to `14px` and `12px` instead of `8px` and `4px`, respectively, in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"soft"`, or override the `--border-radius` CSS variable to specify a different value.
@@ -80,7 +81,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
}
```
-
Chip
+
Chip
- Component CSS variables have been removed. The component now utilizes the centralized Ionic Theming system. Global updates should be managed via the theme tokens file, while component-specific overrides are handled through localized CSS variables.
- `--color` is replaced by `IonChip.hue.bold.solid.default` for global styles and `--ion-chip-hue-bold-solid-default-color` for component-specific styles if the chip has a bold hue and solid fill.
@@ -91,14 +92,14 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- Specific theme classes (e.g., `ion-chip.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting.
- The `border-radius` of the `ios` and `md` chip now defaults to `10px` and `8px`, respectively, instead of `16px` in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"round"`, or override the `IonChip.shape.round.border.radius` to specify a different value for global styles and `--ion-chip-shape-round-border-radius` for component-specific styles.
-
Col
+
Col
The following breaking changes apply to `ion-col`:
-1. `--ion-grid-column-padding-*` CSS variables have been replaced with per-side, per-breakpoint tokens in the `col` namespace. [1](#version-9x-col-padding-variables)
-2. Theme classes (`ion-col.md`, `ion-col.ios`) are no longer supported. [2](#version-9x-col-theme-classes)
+1. `--ion-grid-column-padding-*` CSS variables have been replaced with per-side, per-breakpoint tokens in the `col` namespace. [1](#version-10x-col-padding-variables)
+2. Theme classes (`ion-col.md`, `ion-col.ios`) are no longer supported. [2](#version-10x-col-theme-classes)
-
Padding variables
+
Padding variables
Column padding was a single value per breakpoint and is now set per-side, and the variables have moved from the `grid` namespace to the `col` namespace. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:
@@ -106,11 +107,11 @@ Column padding was a single value per breakpoint and is now set per-side, and th
|---|---|---|
| `--ion-grid-column-padding-{bp}` | `IonCol.breakpoint.{bp}.padding.{top\|end\|bottom\|start}` | `--ion-col-breakpoint-{bp}-padding-{top\|end\|bottom\|start}` |
-
Theme classes
+
Theme classes
Remove any instances that target the theme classes: `ion-col.md`, `ion-col.ios`.
-
Content
+
Content
The following breaking changes apply to `ion-content`:
@@ -158,20 +159,20 @@ These are managed by `ion-content` itself (keyboard avoidance and header/footer
Remove any instances that target the theme classes: `ion-content.md`, `ion-content.ios`.
-
Datetime
+
Datetime
- The `ion-buttons` component has been removed from the internal implementation of `ion-datetime` and is no longer required when passing custom buttons to the `slot="buttons"`. When providing custom buttons, use a `div` element instead of `ion-buttons`. While existing code using `ion-buttons` may continue to work visually, future updates to the `ion-buttons` component may cause any styles you rely on to break.
-
Grid
+
Grid
The following breaking changes apply to `ion-grid`:
-1. `--ion-grid-padding-*` CSS variables have been replaced with per-side, per-breakpoint tokens. [1](#version-9x-grid-padding-variables)
-2. `--ion-grid-width-*` CSS variables for the fixed grid have been replaced with per-breakpoint tokens. [2](#version-9x-grid-fixed-width-variables)
-3. The `push` and `pull` properties have been deprecated. [3](#version-9x-grid-deprecated-push-and-pull-properties)
-4. Theme classes (`ion-grid.md`, `ion-grid.ios`) are no longer supported. [4](#version-9x-grid-theme-classes)
+1. `--ion-grid-padding-*` CSS variables have been replaced with per-side, per-breakpoint tokens. [1](#version-10x-grid-padding-variables)
+2. `--ion-grid-width-*` CSS variables for the fixed grid have been replaced with per-breakpoint tokens. [2](#version-10x-grid-fixed-width-variables)
+3. The `push` and `pull` properties have been deprecated. [3](#version-10x-grid-deprecated-push-and-pull-properties)
+4. Theme classes (`ion-grid.md`, `ion-grid.ios`) are no longer supported. [4](#version-10x-grid-theme-classes)
-
Padding variables
+
Padding variables
Grid padding was a single value per breakpoint and is now set per-side. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:
@@ -179,13 +180,13 @@ Grid padding was a single value per breakpoint and is now set per-side. Use the
|---|---|---|
| `--ion-grid-padding-{bp}` | `IonGrid.breakpoint.{bp}.padding.{top\|end\|bottom\|start}` | `--ion-grid-breakpoint-{bp}-padding-{top\|end\|bottom\|start}` |
-
Fixed width variables
+
Fixed width variables
| Old (8.x) | New token (global) | New CSS variable (component-specific) |
|---|---|---|
| `--ion-grid-width-{bp}` | `IonGrid.breakpoint.{bp}.width` | `--ion-grid-breakpoint-{bp}-width` |
-
Deprecated push and pull properties
+
Deprecated push and pull properties
The `push` and `pull` properties have been disabled. They now log a deprecation warning and no longer affect layout. Use the `order` property to achieve a similar result.
@@ -301,11 +302,11 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has `
```
-
Theme classes
+
Theme classes
Remove any instances that target the theme classes: `ion-grid.md`, `ion-grid.ios`.
-
Image
+
Image
The following breaking changes apply to `ion-img`:
@@ -315,13 +316,13 @@ The following breaking changes apply to `ion-img`:
Remove any instances that target the theme classes: `ion-img.md`, `ion-img.ios`.
-
Input Otp
+
Input Otp
Converted `ion-input-otp` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
If you were targeting the internals of `ion-input-otp` in your CSS, you will need to target the `group`, `container`, `native`, `separator` or `description` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.
-
Item Divider
+
Item Divider
- Component CSS variables have been removed. The component now utilizes the centralized Ionic Theming system. Global updates should be managed via the theme tokens file, while component-specific overrides are handled through localized CSS variables.
- `--background` is replaced by `IonItemDivider.background` for global styles and `--ion-item-divider-background` for component-specific overrides.
@@ -336,7 +337,7 @@ If you were targeting the internals of `ion-input-otp` in your CSS, you will nee
- `--inner-padding-start` is replaced by `IonItemDivider.inner.padding.start` for global styles and `--ion-item-divider-inner-padding-start` for component-specific overrides.
- Specific theme classes (e.g., `ion-item-divider.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting.
-
Menu Toggle
+
Menu Toggle
The following breaking changes apply to `ion-menu-toggle`:
@@ -346,7 +347,7 @@ The following breaking changes apply to `ion-menu-toggle`:
Remove any instances that target the theme classes: `ion-menu-toggle.md`, `ion-menu-toggle.ios`.
-
Radio Group
+
Radio Group
Converted `ion-radio-group` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
@@ -354,14 +355,14 @@ If you were targeting the internals of `ion-radio-group` in your CSS, you will n
Additionally, the `radio-group-wrapper` div element has been removed, causing slotted elements to be direct children of the `ion-radio-group`.
-
Ripple Effect
+
Ripple Effect
The following breaking changes apply to `ion-ripple-effect`:
-1. The previously undocumented `--ripple-opacity` CSS variable has been renamed to `--ion-ripple-effect-opacity`. [1](#version-9x-ripple-effect-opacity-variable)
-2. Theme classes (`ion-ripple-effect.md`, `ion-ripple-effect.ios`) are no longer supported. [2](#version-9x-ripple-effect-theme-classes)
+1. The previously undocumented `--ripple-opacity` CSS variable has been renamed to `--ion-ripple-effect-opacity`. [1](#version-10x-ripple-effect-opacity-variable)
+2. Theme classes (`ion-ripple-effect.md`, `ion-ripple-effect.ios`) are no longer supported. [2](#version-10x-ripple-effect-theme-classes)
-
Opacity variable
+
Opacity variable
The ripple fade opacity is now part of the centralized Ionic Theming system. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:
@@ -369,29 +370,29 @@ The ripple fade opacity is now part of the centralized Ionic Theming system. Use
|---|---|---|
| `--ripple-opacity` | `IonRippleEffect.opacity` | `--ion-ripple-effect-opacity` |
-
Theme classes
+
Theme classes
Remove any instances that target the theme classes: `ion-ripple-effect.md`, `ion-ripple-effect.ios`.
-
Row
+
Row
The following breaking changes apply to `ion-row`:
-1. Theme classes (`ion-row.md`, `ion-row.ios`) are no longer supported. [1](#version-9x-row-theme-classes)
+1. Theme classes (`ion-row.md`, `ion-row.ios`) are no longer supported. [1](#version-10x-row-theme-classes)
-
Theme classes
+
Theme classes
Remove any instances that target the theme classes: `ion-row.md`, `ion-row.ios`.
-
Skeleton Text
+
Skeleton Text
The following breaking changes apply to `ion-skeleton-text`:
-1. `--background` and `--background-rgb` CSS variables have been replaced with per-state background tokens, each split into an RGB value and an alpha. [1](#version-9x-skeleton-text-replaced-css-variables)
-2. `--border-radius` has been replaced. [1](#version-9x-skeleton-text-replaced-css-variables)
-3. Theme classes (`ion-skeleton-text.md`, `ion-skeleton-text.ios`) are no longer supported. [2](#version-9x-skeleton-text-theme-classes)
+1. `--background` and `--background-rgb` CSS variables have been replaced with per-state background tokens, each split into an RGB value and an alpha. [1](#version-10x-skeleton-text-replaced-css-variables)
+2. `--border-radius` has been replaced. [1](#version-10x-skeleton-text-replaced-css-variables)
+3. Theme classes (`ion-skeleton-text.md`, `ion-skeleton-text.ios`) are no longer supported. [2](#version-10x-skeleton-text-theme-classes)
-
Replaced CSS variables
+
Replaced CSS variables
The background is now defined per state (resting and animated), and each state exposes an RGB value plus an alpha so the color and its opacity can be set independently. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:
@@ -406,11 +407,11 @@ The previously fixed opacities are now adjustable through new per-state alpha to
> [!NOTE]
> `--background` (the resting color) has been removed with no one-to-one replacement; set `--ion-skeleton-text-default-background-rgb` (and optionally `--ion-skeleton-text-default-background-alpha`) instead. The single `--background-rgb` variable previously tinted both the resting fill and the animated shimmer. It is now split per state, so set both `--ion-skeleton-text-default-background-rgb` and `--ion-skeleton-text-animated-background-rgb` to recolor both.
-
Theme classes
+
Theme classes
Remove any instances that target the theme classes: `ion-skeleton-text.md`, `ion-skeleton-text.ios`.
-
Spinner
+
Spinner
- Component CSS variables have been removed. The component now utilizes the centralized Ionic Theming system. Global updates should be managed via the theme tokens file, while component-specific overrides are handled through localized CSS variables.
- `--color` is replaced by `IonSpinner.color` for global styles and
@@ -419,7 +420,7 @@ Remove any instances that target the theme classes: `ion-skeleton-text.md`, `ion
- `.spinner-[spinner-name]` β `.spinner-name-[spinner-name]`
- Specific theme classes (e.g., `ion-spinner.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting.
-
Text
+
Text
The following breaking changes apply to `ion-text`:
@@ -441,21 +442,36 @@ When `color` is set, the text color now reads from a token instead of `--ion-col
Remove any instances that target the theme classes: `ion-text.md`, `ion-text.ios`.
-
Textarea
+
Textarea
Converted `ion-textarea` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
-If you were targeting the internals of `ion-textarea` in your CSS, you will need to target the `wrapper`, `container`, `label`, `native`, `supporting-text`, `helper-text`, `error-text`, `counter`, or `bottom` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.
+If you were targeting the internals of `ion-textarea` in your CSS, you will need to target the `wrapper`, `container`, `label`, `native`, `supporting-text`, `helper-text`, `error-text`, `counter`, `bottom`, `start`, `control`, or `end` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.
-
Thumbnail
+The internal wrappers that Ionic 9 introduced are no longer reachable as descendants. Slotted content stays in the light DOM, so it is still reachable directly:
+
+```diff
+-ion-textarea .textarea-control .native-wrapper { }
++ion-textarea::part(container) { }
+
+-ion-textarea .textarea-start [slot="start"] { }
++ion-textarea [slot="start"] { }
+
+-ion-textarea .textarea-end [slot="end"] { }
++ion-textarea [slot="end"] { }
+```
+
+To style the wrappers themselves rather than the slotted content, use `part="start"` and `part="end"`.
+
+
Thumbnail
The following breaking changes apply to `ion-thumbnail`:
-1. `--size` has been split into separate `--ion-thumbnail-width` and `--ion-thumbnail-height` CSS variables. [1](#version-9x-thumbnail-replaced-css-variables)
-2. `--border-radius` has been replaced. [1](#version-9x-thumbnail-replaced-css-variables)
-3. Theme classes (`ion-thumbnail.md`, `ion-thumbnail.ios`) are no longer supported. [2](#version-9x-thumbnail-theme-classes)
+1. `--size` has been split into separate `--ion-thumbnail-width` and `--ion-thumbnail-height` CSS variables. [1](#version-10x-thumbnail-replaced-css-variables)
+2. `--border-radius` has been replaced. [1](#version-10x-thumbnail-replaced-css-variables)
+3. Theme classes (`ion-thumbnail.md`, `ion-thumbnail.ios`) are no longer supported. [2](#version-10x-thumbnail-theme-classes)
-
Replaced CSS variables
+
Replaced CSS variables
`--size` and `--border-radius` have been replaced. Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides:
@@ -468,7 +484,7 @@ The following breaking changes apply to `ion-thumbnail`:
> [!NOTE]
> Code that previously set `--size: 48px` on `ion-thumbnail` must now set both `--ion-thumbnail-width: 48px` and `--ion-thumbnail-height: 48px`.
-
Slotted inside `ion-item` or `ion-item-divider`
+
Slotted inside `ion-item` or `ion-item-divider`
When `ion-thumbnail` is slotted inside a parent component, the parent owns the sizing.
diff --git a/BREAKING_ARCHIVE/v9.md b/BREAKING_ARCHIVE/v9.md
new file mode 100644
index 00000000000..6917a36d11b
--- /dev/null
+++ b/BREAKING_ARCHIVE/v9.md
@@ -0,0 +1,700 @@
+# Breaking Changes
+
+## Version 9.x
+
+- [Browser and Platform Support](#version-9x-browser-platform-support)
+- [Package Exports](#version-9x-package-exports)
+- [Components](#version-9x-components)
+ - [Input](#version-9x-input)
+ - [Legacy Picker](#version-9x-legacy-picker)
+ - [Modal](#version-9x-modal)
+ - [Nav](#version-9x-nav)
+ - [Router Outlet](#version-9x-router-outlet)
+ - [Searchbar](#version-9x-searchbar)
+ - [Select](#version-9x-select)
+ - [Textarea](#version-9x-textarea)
+- [Framework Specific](#version-9x-framework-specific)
+ - [Angular](#version-9x-angular)
+ - [React](#version-9x-react)
+ - [Vue](#version-9x-vue)
+
+
Browser and Platform Support
+
+This section details the desktop browser, JavaScript framework, and mobile platform versions that are supported by Ionic 9.
+
+**Minimum Browser Versions**
+| Desktop Browser | Supported Versions |
+| --------------- | ------------------ |
+| Chrome | 89+ |
+| Safari | 16+ |
+| Edge | 89+ |
+| Firefox | 75+ |
+
+**Minimum JavaScript Framework Versions**
+| Framework | Supported Version |
+| --------- | --------------------- |
+| Angular | 18+ |
+| React | 18 or 19 |
+| Vue | 3.5+ |
+
+**Minimum Mobile Platform Versions**
+| Platform | Supported Version |
+| -------- | ---------------------- |
+| iOS | 16+ |
+| Android | 5.1+ with Chromium 89+ |
+
+**Minimum Native Runtime Versions**
+| Native Runtime | Supported Version |
+| -------------- | ----------------- |
+| Capacitor | 7+ |
+
+Ionic's native platform detection no longer checks the Capacitor 2 `isNative` flag. `isCapacitorNative` now relies solely on `Capacitor.isNativePlatform()`, which was added in Capacitor 3. Apps running Capacitor 2 will no longer be detected as a native/hybrid platform, so `isPlatform('capacitor')`, `isPlatform('hybrid')`, and `getPlatforms()` will report web instead of native. Upgrade to a supported Capacitor version (7 or later).
+
+
Package Exports
+
+`@ionic/core`'s `package.json` now declares an `exports` field. Subpaths like `@ionic/core/components` and `@ionic/core/loader` previously failed under Node ESM (Angular 21's default Vitest builder, raw Node, etc.) with `ERR_UNSUPPORTED_DIR_IMPORT`, because the strict ESM resolver doesn't read the nested `package.json` files this package relied on. The new `exports` map declares the documented subpaths explicitly.
+
+`exports` is an allowlist. Apps using Node ESM, webpack 5, or TypeScript `moduleResolution: "bundler"`/`"node16"`/`"nodenext"` that import from undocumented internal paths need to switch to one of the supported subpaths:
+
+| Subpath | Use |
+| ---------------------------------- | ----------------------------------------------------- |
+| `@ionic/core` | Root entry, controllers, animation builders |
+| `@ionic/core/components` | Custom-element constructors and shared utilities |
+| `@ionic/core/components/ion-*.js` | Single-component custom-element constructor |
+| `@ionic/core/loader` | `defineCustomElements` lazy loader |
+| `@ionic/core/hydrate` | SSR hydration entry |
+| `@ionic/core/css/*.css` | Global stylesheets and palettes |
+
+Apps on `moduleResolution: "node"` (classic) and webpack 4 keep resolving through the legacy fields and are unaffected.
+
+
Components
+
+
Input
+
+**`autocorrect` Property Type Changed to Boolean**
+
+The `autocorrect` property on `ion-input` is now a `boolean` and defaults to `false`. It was previously typed as `'on' | 'off'` with a default of `'off'`. This resolves a type conflict introduced when TypeScript 5.9 added `autocorrect: boolean` to the DOM `HTMLElement` interface.
+
+The string form no longer behaves the same way. Because an HTML attribute coerces to `true` for any non-empty string, `autocorrect="off"` now evaluates to `true` (autocorrect enabled). Migrate to the boolean property:
+
+- Remove the attribute to keep autocorrect disabled (the default).
+- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the input contains slotted content. Labels float only when the input is focused or has a value.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content.
+
+Added:
+- `.input-start`
+- `.input-control`
+- `.input-end`
+
+Restructured:
+- `.label-text-wrapper` moved from `.input-wrapper` into `.input-control`
+- `.native-wrapper` moved from `.input-wrapper` into `.input-control`
+- Start slot moved from `.native-wrapper` into `.input-start`
+- Clear button icon moved from `.native-wrapper` into `.input-end`
+- End slot moved from `.native-wrapper` into `.input-end`
+- `.input-control` now contains the label text and native `input`, while start/end content is separated into dedicated wrappers
+
+Update your selectors to account for these structural changes:
+
+```diff
+-ion-input .input-wrapper .native-wrapper { }
++ion-input .input-control .native-wrapper { }
+
+-ion-input .input-wrapper .native-wrapper [slot="start"] { }
++ion-input .input-start [slot="start"] { }
+
+-ion-input .input-wrapper .native-wrapper .input-clear-icon { }
++ion-input .input-end .input-clear-icon { }
+
+-ion-input .input-wrapper .native-wrapper [slot="end"] { }
++ion-input .input-end [slot="end"] { }
+```
+
+
Legacy Picker
+
+- `ion-picker-legacy` and `ion-picker-legacy-column` have been removed. The legacy picker component has been replaced with an inline picker component.
+ - Usages such as `ion-picker-legacy` or `IonPickerLegacy` should be changed to `ion-picker` and `IonPicker`, respectively.
+- Remove any usages of `pickerController`. If using React, remove any usages of the `useIonPicker` hook. These controller-based APIs have been removed. Use the inline picker component instead.
+- Remove any usages of the `PickerOptions`, `PickerButton`, `PickerColumn`, and `PickerColumnOption` type exports. These types were associated with the legacy picker and have been removed.
+
+
Modal
+
+The `handleBehavior` property on `ion-modal` now defaults to `"cycle"` instead of `"none"`. For sheet modals that display a handle, this means the handle is now focusable and activating it (by click, keyboard, or screen reader) cycles the sheet through its available breakpoints. This matches the native iOS sheet behavior and keeps sheet modals operable for assistive technology users by default.
+
+Sheet modals that relied on the handle being inert should set `handleBehavior="none"` to restore the previous behavior:
+
+```html
+
+```
+
+
Nav
+
+`ion-nav` no longer integrates with `ion-router`. It is now a standalone imperative stack navigation component, driven only through its own API (`root`, `push`, `pop`, `setRoot`, etc.) and `ion-nav-link`.
+
+The following behaviors have been removed:
+
+- The router no longer discovers or drives an `ion-nav`. Placing an `ion-nav` inside an `ion-router` no longer turns it into a routed outlet.
+- Navigating an `ion-nav` (via `push`, `pop`, `ion-nav-link`, or the swipe-to-go-back gesture) no longer updates the URL, and the router's navigation guards no longer run for `ion-nav` transitions.
+- The internal `setRouteId()` and `getRouteId()` methods and the `updateURL` nav option have been removed.
+
+Apps that relied on `ion-nav` to update the URL (for example, pushing components and expecting the browser URL to change) should use `ion-router-outlet` for URL-based routing. Keep the `ion-route` definitions and swap the outlet element:
+
+```diff
+
+
+
+
+
+-
++
+```
+
+An `ion-nav` can still be used inside a routed page for local, URL-less stack navigation. It manages its own stack via `root` and `ion-nav-link`, and the URL never changes as you push and pop:
+
+```html
+
+
+
+
+```
+
+
Router Outlet
+
+`ion-router-outlet` now exposes a `swipeGesture` property that controls the swipe-to-go-back gesture per outlet. This property defaults to `true` in `"ios"` mode and `false` in `"md"` mode.
+
+**`swipeBackEnabled` Config Behavior Change**
+
+In React and Vue, the `swipeBackEnabled` config option is now read once when the outlet mounts. Apps that dynamically toggle this config value at runtime should migrate to the `swipeGesture` property instead:
+
+**React:**
+
+```diff
+- setupIonicReact({ swipeBackEnabled: someCondition });
++
+```
+
+**Vue:**
+
+```diff
+- createApp(App).use(IonicVue, { swipeBackEnabled: someCondition })
++
+```
+
+**Disabling Swipe-to-Go-Back**
+
+To disable the gesture on a specific outlet, set `swipeGesture` to `false`:
+
+```tsx
+
+```
+
+The `swipeBackEnabled` config option is still respected as the initial default and does not need to change for apps that set it once at startup.
+
+
Searchbar
+
+The `autocorrect` property on `ion-searchbar` is now a `boolean` and defaults to `false`. It was previously typed as `'on' | 'off'` with a default of `'off'`. This resolves a type conflict introduced when TypeScript 5.9 added `autocorrect: boolean` to the DOM `HTMLElement` interface.
+
+The string form no longer behaves the same way. Because an HTML attribute coerces to `true` for any non-empty string, `autocorrect="off"` now evaluates to `true` (autocorrect enabled). Migrate to the boolean property:
+
+- Remove the attribute to keep autocorrect disabled (the default).
+- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
+
+
Select
+
+**`ionChange` Only Fires When the Value Changes**
+
+The `ionChange` event on `ion-select` now only fires when the selected value actually changes. Previously, the `alert` and `action-sheet` interfaces emitted `ionChange` every time the overlay was confirmed, even when the user chose the option that was already selected. This aligns the `alert` and `action-sheet` interfaces with the existing behavior of the `popover` and `modal` interfaces, and with the documented contract of `ionChange`.
+
+Apps that relied on `ionChange` firing on every confirmation (for example, to detect overlay dismissal without a value change) should listen for `ionDismiss` instead, or use the `didDismiss` event on the underlying alert or action sheet.
+
+**Action Sheet Interface `selected` Role Removed**
+
+When using `interface="action-sheet"`, `ion-select` no longer assigns the `selected` role to the action sheet button for the currently selected option. This aligns the `action-sheet` interface with the `alert`, `popover`, and `modal` interfaces, none of which assign this role. This does not change the selected option's styling.
+
+Previously, the `selected` role was assigned only to the option matching the select's current value. Because the dismiss role mirrors the tapped button, this surfaced in just one case: re-selecting the already-selected option dismissed the action sheet with `role: "selected"` in `ionActionSheetDidDismiss`. Tapping any other option changed the value and dismissed with `role: ""`. Now that the role is no longer assigned, both cases dismiss with `role: undefined`. Apps that inspected this role to detect that a value was chosen, such as reading `role` from the underlying action sheet's `onDidDismiss` result, should listen for `ion-select`'s `ionChange` event instead, which emits the selected value when the selection changes.
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the select contains slotted content. Labels float only when the select is focused or has a value. Additionally, when using a floating label, the placeholder is only visible when the select is focused.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content. This changes the structure and location of several exposed shadow parts.
+
+Added:
+- `.select-start` β `part="start"`
+- `.select-control` β `part="control"`
+- `.select-end` β `part="end"`
+
+Removed:
+- `.select-wrapper-inner` β `part="inner"`
+
+Restructured:
+- `.label-text-wrapper` remains `part="label"` but moved from `.select-wrapper` into `.select-control`
+- `.native-wrapper` remains `part="container"` but moved from `.select-wrapper-inner` into `.select-control`
+- Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`)
+- End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`)
+- `.select-icon` remains `part="icon"` but its location depends on the label state:
+ - With a start/end label, the icon is inside `.native-wrapper`
+ - With a floating/stacked label, the icon is inside `.select-end`
+
+Update selectors that target the exposed shadow parts to account for the new structure:
+
+If you currently target `part="inner"`, that part has been removed. Update those styles to target the new parts as appropriate.
+
+If you target `part="label"`, `part="container"`, or `part="icon"`, the part names remain unchanged, but their position in the shadow DOM has changed. This may affect styles that depend on the relationship or layout of these parts.
+
+Use the new `part="start"`, `part="control"`, and `part="end"` parts to target the new structural wrappers.
+
+
Textarea
+
+**Floating Label Behavior**
+
+Floating labels no longer automatically float when the textarea contains slotted content. Labels float only when the textarea is focused or has a value.
+
+**Internal DOM Structure Changes**
+
+The internal DOM structure has been reorganized to support floating labels with slotted content.
+
+Removed: `.textarea-wrapper-inner`
+
+Added: `.textarea-control`
+
+Renamed:
+- `.start-slot-wrapper` β `.textarea-start`
+- `.end-slot-wrapper` β `.textarea-end`
+
+Restructured:
+- `.label-text-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
+- `.native-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
+- `.start-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-start`
+- `.end-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-end`
+
+Update your selectors to account for these structural changes:
+
+```diff
+-ion-textarea .textarea-wrapper-inner .native-wrapper { }
++ion-textarea .textarea-control .native-wrapper { }
+
+-ion-textarea .start-slot-wrapper [slot="start"] { }
++ion-textarea .textarea-start [slot="start"] { }
+
+-ion-textarea .end-slot-wrapper [slot="end"] { }
++ion-textarea .textarea-end [slot="end"] { }
+```
+
+**Minimum Height Change**
+
+The minimum height of textarea in Material Design (`md` mode) is now `72px`. At the default number of rows this makes textareas the same height regardless of the `fill` property or `labelPlacement`. Previously the minimum height was:
+
+| Fill | Label placement | Previous minimum height |
+| --- | --- | --- |
+| default | `start`, `end`, `fixed` | `44px` |
+| default | `floating`, `stacked` | `56px` |
+| `solid`, `outline` | any | `56px` |
+
+These were minimums, not the heights textareas actually rendered at. A textarea with content in the `start` or `end` slots was already taller than its minimum, so the change affects it differently. For example, a `fill="solid"` textarea with slotted icons and buttons previously rendered at `72px` with a `start` label and `81px` with a `floating` label. Both are now `72px`, so that floating label case is `9px` shorter than before rather than taller.
+
+Because `72px` is taller than two rows of text, `rows` values below `3` no longer change the height of the textarea in `md` mode: `rows="1"` and `rows="2"` both render at `72px`.
+
+If you were relying on the previous heights, or you need `rows` to control the height, override the minimum height back. The override has to be more specific than the component's own style, so a bare `ion-textarea` selector will not apply. Add a custom class to the textarea to increase specificity:
+
+```css
+/* Add a custom class to the textarea */
+ion-textarea.custom {
+ min-height: 44px;
+}
+```
+
+
Framework Specific
+
+
Angular
+
+**Minimum Angular Version**
+
+Ionic 9 requires Angular 18 or later. Angular 16 and 17 are no longer supported.
+
+**Standalone Components Imported by Default**
+
+Following industry standards, Ionic 9 makes standalone components the default import path. Standalone component imports have changed from `@ionic/angular/standalone` to `@ionic/angular`. Lazy-loaded component imports have changed from `@ionic/angular` to `@ionic/angular/lazy`.
+
+**IonicModule Deprecation**
+
+`IonicModule` is deprecated in Ionic 9 and will be removed in a future major version. It remains fully functional in Ionic 9, so existing applications continue to work without changes.
+
+Applications should migrate to `provideIonicAngular()`, which works in both standalone and NgModule-based applications. For an NgModule-based app, replace `IonicModule.forRoot(config)` in the `imports` array with `provideIonicAngular(config)` in the `providers` array. Any config passed to `IonicModule.forRoot()` can be passed as an object to `provideIonicAngular()`. Refer to the [build options guide](https://ionicframework.com/docs/angular/build-options) for migration steps.
+
+**Zoneless Change Detection by Default**
+
+Ionic 9 defaults to zoneless change detection. Angular 21 bootstraps zoneless out of the box, so a new Ionic 9 app on Angular 21 runs without Zone.js and requires no change-detection provider. The `ng add @ionic/angular` schematic no longer registers `provideZoneChangeDetection()`.
+
+Because Zone.js no longer triggers change detection automatically, component state that you update from an asynchronous callback that Angular doesn't wrap (awaiting an overlay result such as `modal.onWillDismiss()`, `setTimeout`, RxJS subscriptions, `Platform` events) no longer re-renders on its own. Update a signal or call `ChangeDetectorRef.markForCheck()` in those callbacks. Template event bindings, `@HostListener`, reactive forms, and Ionic lifecycle hooks (`ionViewWillEnter`, etc.) that set state synchronously are unaffected. Refer to the [Zoneless Change Detection guide](https://ionicframework.com/docs/angular/zoneless) for the patterns.
+
+On Angular 18 through 20, Zone.js remains Angular's default, so those versions are unaffected and require no change. To adopt zoneless there, add `provideZonelessChangeDetection()` (named `provideExperimentalZonelessChangeDetection()` on Angular 18 and 19).
+
+**Keeping Zone.js on Angular 21 (optional)**
+
+To keep using Zone.js on Angular 21, opt back in with `provideZoneChangeDetection()` and keep `zone.js` in your polyfills.
+
+Standalone bootstrap:
+
+```diff
+ import { bootstrapApplication } from '@angular/platform-browser';
++ import { provideZoneChangeDetection } from '@angular/core';
+
+ bootstrapApplication(AppComponent, {
+ providers: [
++ provideZoneChangeDetection(),
+ // ...other providers
+ ],
+ });
+```
+
+NgModule bootstrap:
+
+```diff
+ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
++ import { provideZoneChangeDetection } from '@angular/core';
+
+ platformBrowserDynamic()
+- .bootstrapModule(AppModule)
++ .bootstrapModule(AppModule, {
++ applicationProviders: [provideZoneChangeDetection()],
++ })
+ .catch((err) => console.error(err));
+```
+
+Angular forbids `provideZoneChangeDetection()` inside an NgModule's `providers` array, so for NgModule apps it must be passed as `applicationProviders` on the `bootstrapModule()` call. Both paths also require `zone.js` in your polyfills, which Angular 21's default scaffold omits:
+
+```ts
+// src/polyfills.ts
+import 'zone.js';
+```
+
+**OnPush by Default on Angular 22**
+
+Angular 22 changes the default change detection strategy to `OnPush` for components that don't declare one. Combined with the zoneless default above, any component state that you mutate as a plain field from an Ionic lifecycle hook (`ionViewWillEnter`, etc.) no longer re-renders on its own. Run `ng update`, which migrates existing components to eager change detection and preserves the previous behavior, or use a signal (or `ChangeDetectorRef.markForCheck()`) for state set in those hooks. Ionic's own Angular components already declare `OnPush` explicitly and are unaffected. Angular 18 through 21 keep the eager default, so they require no change.
+
+**TypeScript**
+
+Ionic 9 supports TypeScript 5.4 or later, matching the minimum for Angular 18. Angular 21 requires TypeScript 5.9 or later, and Angular 22 requires TypeScript 6.0 or later, per Angular's own requirements.
+
+**Node.js**
+
+Angular 22 raises the minimum Node.js version to `^22.22.3 || ^24.15.0 || ^26.0.0`. Angular 18 through 21 are unaffected.
+
+**Module Resolution**
+
+`@ionic/angular` is now published with `exports`-based subpath resolution. Apps using TypeScript `moduleResolution: "node"` (classic) can fail to resolve subpaths such as `@ionic/angular/lazy`. Set `moduleResolution` to `"bundler"` (the default for `ng new` on Angular 17 and later). Refer to [Package Exports](#version-9x-package-exports).
+
+**CSS Imports No Longer Use the `~` Prefix**
+
+Angular's current build pipeline no longer supports the webpack-loader `~` prefix in CSS `@import` statements:
+
+```diff
+- @import '~@ionic/angular/css/core.css';
++ @import '@ionic/angular/css/core.css';
+```
+
+
React
+
+The `@ionic/react` and `@ionic/react-router` packages now require React 18 or 19. React 17 is no longer supported.
+
+The `@ionic/react-router` package now requires React Router v6. React Router v5 is no longer supported.
+
+**Minimum Version Requirements**
+| Package | Supported Version |
+| ---------------- | ----------------- |
+| react | 18 or 19 |
+| react-dom | 18 or 19 |
+| react-router | 6.4.0+ |
+| react-router-dom | 6.4.0+ |
+
+**TypeScript**
+
+The `@ionic/react` package now requires TypeScript 5.4 or later. Its type definitions use `NoInfer`, which TypeScript added in 5.4. This matches the minimum that `@ionic/angular` already requires.
+
+**Typed Overlay Hook Props**
+
+The `useIonModal` and `useIonPopover` hooks type `componentProps` against the component they are given, instead of accepting `any`. Props that do not match the component are a compile error, and `componentProps` is required when the component declares required props. Applications passing incorrect props will see new type errors at build time rather than failing at runtime.
+
+```diff
+ const Modal: React.FC<{ title: string }> = ({ title }) => {title};
+
+- const [present, dismiss] = useIonModal(Modal, { subtitle: 'Wrong' });
++ const [present, dismiss] = useIonModal(Modal, { title: 'Hello' });
+```
+
+Props are read from the component rather than from `componentProps`, so a component declared inline needs its props annotated:
+
+```diff
+- const [present, dismiss] = useIonModal(({ name }) =>
, { name: 'Dave' });
+```
+
+Passing a JSX element rather than a component is unchanged, and `componentProps` is not type checked in that case.
+
+`npx @ionic/migrate` reports the calls this affects and names what is wrong with each, but does not rewrite them, since the right fix depends on what the call was meant to do. For the inline case above, `--experimental` can annotate the parameter from the `componentProps` object literal being passed.
+
+React Router v6 introduces several API changes that will require updates to your application's routing configuration:
+
+**Route Definition Changes**
+
+The `component` prop has been replaced with the `element` prop, which accepts JSX:
+
+```diff
+-
++ } />
+```
+
+**Redirect Changes**
+
+The `` component has been replaced with ``:
+
+```diff
+- import { Redirect } from 'react-router-dom';
++ import { Navigate } from 'react-router-dom';
+
+-
++
+```
+
+**Nested Route Paths**
+
+Routes that contain nested routes or child `IonRouterOutlet` components need a `/*` suffix to match sub-paths:
+
+```diff
+- } />
++ } />
+```
+
+**Accessing Route Parameters**
+
+Route parameters are now accessed via the `useParams` hook instead of props:
+
+```diff
+- import { RouteComponentProps } from 'react-router-dom';
++ import { useParams } from 'react-router-dom';
+
+- const MyComponent: React.FC> = ({ match }) => {
+- const id = match.params.id;
++ const MyComponent: React.FC = () => {
++ const { id } = useParams<{ id: string }>();
+```
+
+**RouteComponentProps Removed**
+
+The `RouteComponentProps` type and its `history`, `location`, and `match` props are no longer available in React Router v6. Use the equivalent hooks instead:
+
+- `history` -> `useNavigate` (see below) or `useIonRouter`
+- `match.params` -> `useParams` (covered above)
+- `location` -> `useLocation`
+
+```diff
+- import { RouteComponentProps } from 'react-router-dom';
++ import { useNavigate, useLocation } from 'react-router-dom';
++ import { useIonRouter } from '@ionic/react';
+
+- const MyComponent: React.FC = ({ history, location }) => {
+- history.push('/path');
+- history.replace('/path');
+- history.goBack();
+- console.log(location.pathname);
++ const MyComponent: React.FC = () => {
++ const navigate = useNavigate();
++ const router = useIonRouter();
++ const location = useLocation();
++ // In an event handler or useEffect:
++ navigate('/path');
++ navigate('/path', { replace: true });
++ router.goBack();
++ console.log(location.pathname);
+```
+
+**Exact Prop Removed**
+
+The `exact` prop is no longer needed. React Router v6 routes match exactly by default. To match sub-paths, use a `/*` suffix on the path:
+
+```diff
+-
++
+```
+
+**Render Prop Removed**
+
+The `render` prop has been replaced with the `element` prop:
+
+```diff
+- } />
++ } />
+```
+
+**Programmatic Navigation**
+
+The `useHistory` hook has been replaced with `useNavigate`:
+
+```diff
+- import { useHistory } from 'react-router-dom';
++ import { useNavigate } from 'react-router-dom';
++ import { useIonRouter } from '@ionic/react';
+
+- const history = useHistory();
++ const navigate = useNavigate();
++ const router = useIonRouter();
+
+- history.push('/path');
++ navigate('/path');
+
+- history.replace('/path');
++ navigate('/path', { replace: true });
+
+- history.goBack();
++ router.goBack();
+```
+
+**Custom History Prop Removed**
+
+The `history` prop has been removed from `IonReactRouter`, `IonReactHashRouter`, and `IonReactMemoryRouter`. React Router v6's `BrowserRouter`, `HashRouter`, and `MemoryRouter` no longer accept custom `history` objects.
+
+```diff
+- import { createBrowserHistory } from 'history';
+- const history = createBrowserHistory();
+-
++
+```
+
+For `IonReactMemoryRouter` (commonly used in tests), use `initialEntries` instead:
+
+```diff
+- import { createMemoryHistory } from 'history';
+- const history = createMemoryHistory({ initialEntries: ['/start'] });
+-
++
+```
+
+**IonRedirect Removed**
+
+The `IonRedirect` component has been removed. Use React Router's `` component instead:
+
+```diff
+- import { IonRedirect } from '@ionic/react';
+-
++ import { Navigate } from 'react-router-dom';
++ } />
+```
+
+**Path Regex Constraints Removed**
+
+React Router v6 no longer supports regex constraints in path parameters (e.g., `/:tab(sessions)`). Use literal paths instead:
+
+```diff
+-
+-
++ } />
++ } />
+```
+
+**IonRoute API Changes**
+
+The `IonRoute` component follows the same API changes as React Router's ``. The `render` prop has been replaced with `element`, and the `exact` prop has been removed:
+
+```diff
+- } />
++ } />
+```
+
+For more information on migrating from React Router v5 to v6, refer to the [React Router v6 Upgrade Guide](https://reactrouter.com/6.28.0/upgrading/v5).
+
+
diff --git a/core/scripts/verify/lazy-imports.js b/core/scripts/verify/lazy-imports.js
new file mode 100644
index 00000000000..d0008130474
--- /dev/null
+++ b/core/scripts/verify/lazy-imports.js
@@ -0,0 +1,56 @@
+/**
+ * Fails if the built lazy loader is missing a literal import path for any component bundle.
+ * Not in `scripts/testing/` because `vercel-build.sh` publishes that directory.
+ * https://github.com/ionic-team/ionic-framework/issues/31333
+ */
+const fs = require('fs');
+const path = require('path');
+
+const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
+const OUTPUT_DIRS = ['dist/esm', 'dist/cjs'];
+
+function main() {
+ const failures = OUTPUT_DIRS.map(verify).filter((message) => message !== null);
+
+ if (failures.length > 0) {
+ failures.forEach((message) => console.error(message));
+ process.exit(1);
+ }
+
+ console.error(`Success! Every lazy bundle is imported by path in ${OUTPUT_DIRS.join(' and ')}`);
+}
+
+function verify(dir) {
+ const resolved = path.join(PACKAGE_ROOT, dir);
+
+ if (!fs.existsSync(resolved)) {
+ return `${dir} does not exist. Run npm run build first.`;
+ }
+
+ const files = fs.readdirSync(resolved).filter((file) => file.endsWith('.js'));
+ const entries = files.filter((file) => file.endsWith('.entry.js'));
+
+ if (entries.length === 0) {
+ return `${dir} does not contain any .entry.js files.`;
+ }
+
+ // A non-loader chunk that happens to name an entry file would be a false pass.
+ const loader = files
+ .filter((file) => !file.endsWith('.entry.js'))
+ .map((file) => fs.readFileSync(path.join(resolved, file), 'utf-8'))
+ .find((source) => /switch\s*\(bundleId\)/.test(source));
+
+ if (loader === undefined) {
+ return `${dir} has no lazy loader with a bundle id switch. Check that extras.enableImportInjection is still set in core/stencil.config.ts.`;
+ }
+
+ const missing = entries.filter((entry) => !loader.includes(`'./${entry}'`) && !loader.includes(`"./${entry}"`));
+
+ if (missing.length > 0) {
+ return `${dir} is missing an import for ${missing.length} of ${entries.length} lazy bundles: ${missing.join(', ')}`;
+ }
+
+ return null;
+}
+
+main();
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 661ab287668..d6fc1010fcf 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -18,8 +18,10 @@ import { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/bre
import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
import { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
import { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces";
-import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
+import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimeParts, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
+import { FooterScrollEffect } from "./components/footer/footer-interface";
import { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
+import { HeaderScrollEffect } from "./components/header/header-interface";
import { SpinnerTypes } from "./components/spinner/spinner-configs";
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
import { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface";
@@ -29,7 +31,6 @@ import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, S
import { ViewController } from "./components/nav/view-controller";
import { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
import { PickerColumnChangeEventDetail, PickerColumnValue } from "./components/picker-column/picker-column-interfaces";
-import { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
import { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
import { IonProgressBarShape } from "./components/progress-bar/progress-bar.interfaces";
import { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
@@ -46,7 +47,7 @@ import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./com
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
import { SpinnerSize } from "./components/spinner/spinner.interfaces";
-import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
+import { TabBarChangedEventDetail, TabBarScrollEffect, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
import { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
import { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
@@ -63,8 +64,10 @@ export { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/bre
export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
export { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
export { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces";
-export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
+export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimeParts, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
+export { FooterScrollEffect } from "./components/footer/footer-interface";
export { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
+export { HeaderScrollEffect } from "./components/header/header-interface";
export { SpinnerTypes } from "./components/spinner/spinner-configs";
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
export { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface";
@@ -74,7 +77,6 @@ export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, S
export { ViewController } from "./components/nav/view-controller";
export { PickerChangeEventDetail } from "./components/picker/picker-interfaces";
export { PickerColumnChangeEventDetail, PickerColumnValue } from "./components/picker-column/picker-column-interfaces";
-export { PickerButton, PickerColumn } from "./components/picker-legacy/picker-interface";
export { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
export { IonProgressBarShape } from "./components/progress-bar/progress-bar.interfaces";
export { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
@@ -91,7 +93,7 @@ export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./com
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
export { SpinnerSize } from "./components/spinner/spinner.interfaces";
-export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
+export { TabBarChangedEventDetail, TabBarScrollEffect, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
export { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
export { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
@@ -1138,8 +1140,7 @@ export namespace Components {
*/
"clearText": string;
/**
- * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
+ * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, refer to [theming](/docs/theming/basics).
*/
"color"?: Color;
/**
@@ -1170,6 +1171,10 @@ export namespace Components {
* Formatting options for dates and times. Should include a 'date' and/or 'time' object, each of which is of type [Intl.DateTimeFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options).
*/
"formatOptions"?: FormatOptions;
+ /**
+ * Returns the default parts the datetime falls back to when no value is set: today's date and time snapped to the closest value allowed by the component's constraints (`min`, `max`, and the `*Values` props).
+ */
+ "getDefaultPart": () => Promise;
/**
* Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`.
*/
@@ -1291,7 +1296,6 @@ export namespace Components {
interface IonDatetimeButton {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -1383,6 +1387,10 @@ export namespace Components {
* This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want).
*/
"download": string | undefined;
+ /**
+ * The HTML form element or form element id. Used to submit a form when the button is not a child of the form.
+ */
+ "form"?: string | HTMLFormElement;
/**
* Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered.
*/
@@ -1455,12 +1463,17 @@ export namespace Components {
interface IonFooter {
/**
* Describes the scroll effect that will be applied to the footer. Only applies when the theme is `"ios"`.
+ * @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'fade';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
+ /**
+ * Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
+ */
+ "scrollEffect"?: FooterScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
@@ -1528,6 +1541,7 @@ export namespace Components {
interface IonHeader {
/**
* Describes the scroll effect that will be applied to the header. Only applies when the theme is `"ios"`. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
+ * @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'condense' | 'fade';
/**
@@ -1539,6 +1553,10 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
+ /**
+ * Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
+ */
+ "scrollEffect"?: HeaderScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
@@ -1628,9 +1646,9 @@ export namespace Components {
"autocomplete": AutocompleteTypes;
/**
* Whether auto correction should be enabled when the user is entering/editing the text value.
- * @default 'off'
+ * @default false
*/
- "autocorrect": 'on' | 'off';
+ "autocorrect": boolean;
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* @default false
@@ -2430,8 +2448,8 @@ export namespace Components {
*/
"handle"?: boolean;
/**
- * The interaction behavior for the sheet modal when the handle is pressed. Defaults to `"none"`, which means the modal will not change size or position when the handle is pressed. Set to `"cycle"` to let the modal cycle between available breakpoints when pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal).
- * @default 'none'
+ * The interaction behavior for the sheet modal when the handle is pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal). Set to `"cycle"` to make the handle focusable and let the sheet modal cycle between available breakpoints when pressed. This keeps the sheet operable with assistive technology. Set to `"none"` to make the handle purely decorative when pressed and removed from the tab order. Defaults to `"cycle"`.
+ * @default 'cycle'
*/
"handleBehavior"?: ModalHandleBehavior;
/**
@@ -2543,10 +2561,6 @@ export namespace Components {
* @param view The view to get.
*/
"getPrevious": (view?: ViewController) => Promise;
- /**
- * Called by to retrieve the current component.
- */
- "getRouteId": () => Promise;
/**
* Inserts a component into the navigation stack at the specified index. This is useful to add a component at any point in the navigation stack.
* @param insertIndex The index to insert the component at in the stack.
@@ -2626,15 +2640,6 @@ export namespace Components {
* @param done The transition complete function.
*/
"setRoot": (component: T, componentProps?: ComponentProps | null, opts?: NavOptions | null, done?: TransitionDoneFn) => Promise;
- /**
- * Called by the router to update the view.
- * @param id The component tag.
- * @param params The component params.
- * @param direction A direction hint.
- * @param animation an AnimationBuilder.
- * @return the status.
- */
- "setRouteId": (id: string, params: ComponentProps | undefined, direction: RouterDirection, animation?: AnimationBuilder) => Promise;
/**
* If the nav component should allow for swipe-to-go-back.
*/
@@ -2699,7 +2704,6 @@ export namespace Components {
interface IonPickerColumn {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -2737,7 +2741,6 @@ export namespace Components {
interface IonPickerColumnOption {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -2758,111 +2761,6 @@ export namespace Components {
*/
"value"?: any | null;
}
- interface IonPickerLegacy {
- /**
- * If `true`, the picker will animate.
- * @default true
- */
- "animated": boolean;
- /**
- * If `true`, the picker will be dismissed when the backdrop is clicked.
- * @default true
- */
- "backdropDismiss": boolean;
- /**
- * Array of buttons to be displayed at the top of the picker.
- * @default []
- */
- "buttons": PickerButton[];
- /**
- * Array of columns to be displayed in the picker.
- * @default []
- */
- "columns": PickerColumn[];
- /**
- * Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces.
- */
- "cssClass"?: string | string[];
- "delegate"?: FrameworkDelegate;
- /**
- * Dismiss the picker overlay after it has been presented.
- * @param data Any data to emit in the dismiss events.
- * @param role The role of the element that is dismissing the picker. This can be useful in a button handler for determining which button was clicked to dismiss the picker. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
- */
- "dismiss": (data?: any, role?: string) => Promise;
- /**
- * Number of milliseconds to wait before dismissing the picker.
- * @default 0
- */
- "duration": number;
- /**
- * Animation to use when the picker is presented.
- */
- "enterAnimation"?: AnimationBuilder;
- /**
- * Get the column that matches the specified name.
- * @param name The name of the column.
- */
- "getColumn": (name: string) => Promise;
- /**
- * @default false
- */
- "hasController": boolean;
- /**
- * Additional attributes to pass to the picker.
- */
- "htmlAttributes"?: { [key: string]: any };
- /**
- * If `true`, the picker will open. If `false`, the picker will close. Use this if you need finer grained control over presentation, otherwise just use the pickerController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the picker dismisses. You will need to do that in your code.
- * @default false
- */
- "isOpen": boolean;
- /**
- * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
- * @default true
- */
- "keyboardClose": boolean;
- /**
- * Animation to use when the picker is dismissed.
- */
- "leaveAnimation"?: AnimationBuilder;
- /**
- * The mode determines the platform behaviors of the component.
- */
- "mode"?: "ios" | "md";
- /**
- * Returns a promise that resolves when the picker did dismiss.
- */
- "onDidDismiss": () => Promise>;
- /**
- * Returns a promise that resolves when the picker will dismiss.
- */
- "onWillDismiss": () => Promise>;
- "overlayIndex": number;
- /**
- * Present the picker overlay after it has been created.
- */
- "present": () => Promise;
- /**
- * If `true`, a backdrop will be displayed behind the picker.
- * @default true
- */
- "showBackdrop": boolean;
- /**
- * The theme determines the visual appearance of the component.
- */
- "theme"?: "ios" | "md" | "ionic";
- /**
- * An ID corresponding to the trigger element that causes the picker to open when clicked.
- */
- "trigger": string | undefined;
- }
- interface IonPickerLegacyColumn {
- /**
- * Picker column data
- */
- "col": PickerColumn;
- }
interface IonPopover {
/**
* Describes how to align the popover content with the `reference` point. Defaults to `"center"` for `"ios"` theme, and `"start"` for `"md"` theme.
@@ -3148,7 +3046,7 @@ export namespace Components {
*/
"disabled": boolean;
/**
- * Show two knobs.
+ * If `true`, the range shows two knobs and `value` is an object with `lower` and `upper` properties. If `false`, the range shows one knob and `value` is a number.
* @default false
*/
"dualKnobs": boolean;
@@ -3350,7 +3248,7 @@ export namespace Components {
*/
"beforeLeave"?: NavigationHookCallback;
/**
- * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
+ * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-router-outlet`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
"component": string;
/**
@@ -3467,8 +3365,12 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
* @default getIonMode(this)
*/
- "mode": "md" | "ios";
+ "mode": "ios" | "md";
"setRouteId": (id: string, params: ComponentProps | undefined, direction: RouterDirection, animation?: AnimationBuilder) => Promise;
+ /**
+ * If `true`, the router-outlet should allow navigation via swipe-to-go-back gesture. Defaults to `true` for `"ios"` mode and `false` for `"md"` mode.
+ */
+ "swipeGesture"?: boolean;
"swipeHandler"?: SwipeGestureHandler;
/**
* The theme determines the visual appearance of the component.
@@ -3499,9 +3401,9 @@ export namespace Components {
"autocomplete": AutocompleteTypes;
/**
* Set the input's autocorrect property.
- * @default 'off'
+ * @default false
*/
- "autocorrect": 'on' | 'off';
+ "autocorrect": boolean;
/**
* Set the cancel button icon. Only available when the theme is `"md"`. Defaults to `"arrow-back-sharp"`.
*/
@@ -3845,7 +3747,7 @@ export namespace Components {
*/
"description"?: string;
/**
- * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
+ * If `true`, the user cannot interact with the select option.
* @default false
*/
"disabled": boolean;
@@ -3997,15 +3899,14 @@ export namespace Components {
* @default 'full'
*/
"expand": 'compact' | 'full';
- /**
- * If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`.
- * @default false
- */
- "hideOnScroll": boolean;
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
+ /**
+ * Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
+ */
+ "scrollEffect"?: TabBarScrollEffect;
/**
* The selected tab component
*/
@@ -4589,14 +4490,6 @@ export interface IonPickerColumnCustomEvent extends CustomEvent {
detail: T;
target: HTMLIonPickerColumnElement;
}
-export interface IonPickerLegacyCustomEvent extends CustomEvent {
- detail: T;
- target: HTMLIonPickerLegacyElement;
-}
-export interface IonPickerLegacyColumnCustomEvent extends CustomEvent {
- detail: T;
- target: HTMLIonPickerLegacyColumnElement;
-}
export interface IonPopoverCustomEvent extends CustomEvent {
detail: T;
target: HTMLIonPopoverElement;
@@ -5372,47 +5265,6 @@ declare global {
prototype: HTMLIonPickerColumnOptionElement;
new (): HTMLIonPickerColumnOptionElement;
};
- interface HTMLIonPickerLegacyElementEventMap {
- "ionPickerDidPresent": void;
- "ionPickerWillPresent": void;
- "ionPickerWillDismiss": OverlayEventDetail;
- "ionPickerDidDismiss": OverlayEventDetail;
- "didPresent": void;
- "willPresent": void;
- "willDismiss": OverlayEventDetail;
- "didDismiss": OverlayEventDetail;
- }
- interface HTMLIonPickerLegacyElement extends Components.IonPickerLegacy, HTMLStencilElement {
- addEventListener(type: K, listener: (this: HTMLIonPickerLegacyElement, ev: IonPickerLegacyCustomEvent) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
- removeEventListener(type: K, listener: (this: HTMLIonPickerLegacyElement, ev: IonPickerLegacyCustomEvent) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
- }
- var HTMLIonPickerLegacyElement: {
- prototype: HTMLIonPickerLegacyElement;
- new (): HTMLIonPickerLegacyElement;
- };
- interface HTMLIonPickerLegacyColumnElementEventMap {
- "ionPickerColChange": PickerColumn;
- }
- interface HTMLIonPickerLegacyColumnElement extends Components.IonPickerLegacyColumn, HTMLStencilElement {
- addEventListener(type: K, listener: (this: HTMLIonPickerLegacyColumnElement, ev: IonPickerLegacyColumnCustomEvent) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
- addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
- removeEventListener(type: K, listener: (this: HTMLIonPickerLegacyColumnElement, ev: IonPickerLegacyColumnCustomEvent) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
- removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
- }
- var HTMLIonPickerLegacyColumnElement: {
- prototype: HTMLIonPickerLegacyColumnElement;
- new (): HTMLIonPickerLegacyColumnElement;
- };
interface HTMLIonPopoverElementEventMap {
"ionPopoverDidPresent": void;
"ionPopoverWillPresent": void;
@@ -6013,8 +5865,6 @@ declare global {
"ion-picker": HTMLIonPickerElement;
"ion-picker-column": HTMLIonPickerColumnElement;
"ion-picker-column-option": HTMLIonPickerColumnOptionElement;
- "ion-picker-legacy": HTMLIonPickerLegacyElement;
- "ion-picker-legacy-column": HTMLIonPickerLegacyColumnElement;
"ion-popover": HTMLIonPopoverElement;
"ion-progress-bar": HTMLIonProgressBarElement;
"ion-radio": HTMLIonRadioElement;
@@ -6057,7 +5907,7 @@ declare global {
}
}
declare namespace LocalJSX {
- type OneOf = { [P in K]: PropT } & { [P in `attr:${K}` | `prop:${K}`]?: never } | { [P in `attr:${K}`]: AttrT } & { [P in K | `prop:${K}`]?: never } | { [P in `prop:${K}`]: PropT } & { [P in K | `attr:${K}`]?: never };
+ type OneOf = { [P in K]: PropT } & { [P in `attr:${K}`]?: never } | { [P in `attr:${K}`]: AttrT } & { [P in K]?: never };
interface IonAccordion {
/**
@@ -7137,8 +6987,7 @@ declare namespace LocalJSX {
*/
"clearText"?: string;
/**
- * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
+ * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, refer to [theming](/docs/theming/basics).
*/
"color"?: Color;
/**
@@ -7308,7 +7157,6 @@ declare namespace LocalJSX {
interface IonDatetimeButton {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -7392,6 +7240,10 @@ declare namespace LocalJSX {
* This attribute instructs browsers to download a URL instead of navigating to it, so the user will be prompted to save it as a local file. If the attribute has a value, it is used as the pre-filled file name in the Save prompt (the user can still change the file name if they want).
*/
"download"?: string | undefined;
+ /**
+ * The HTML form element or form element id. Used to submit a form when the button is not a child of the form.
+ */
+ "form"?: string | HTMLFormElement;
/**
* Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered.
*/
@@ -7472,12 +7324,17 @@ declare namespace LocalJSX {
interface IonFooter {
/**
* Describes the scroll effect that will be applied to the footer. Only applies when the theme is `"ios"`.
+ * @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'fade';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
+ /**
+ * Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
+ */
+ "scrollEffect"?: FooterScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
@@ -7541,6 +7398,7 @@ declare namespace LocalJSX {
interface IonHeader {
/**
* Describes the scroll effect that will be applied to the header. Only applies when the theme is `"ios"`. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
+ * @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'condense' | 'fade';
/**
@@ -7552,6 +7410,10 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
+ /**
+ * Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
+ */
+ "scrollEffect"?: HeaderScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
@@ -7653,9 +7515,9 @@ declare namespace LocalJSX {
"autocomplete"?: AutocompleteTypes;
/**
* Whether auto correction should be enabled when the user is entering/editing the text value.
- * @default 'off'
+ * @default false
*/
- "autocorrect"?: 'on' | 'off';
+ "autocorrect"?: boolean;
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* @default false
@@ -8469,8 +8331,8 @@ declare namespace LocalJSX {
*/
"handle"?: boolean;
/**
- * The interaction behavior for the sheet modal when the handle is pressed. Defaults to `"none"`, which means the modal will not change size or position when the handle is pressed. Set to `"cycle"` to let the modal cycle between available breakpoints when pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal).
- * @default 'none'
+ * The interaction behavior for the sheet modal when the handle is pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal). Set to `"cycle"` to make the handle focusable and let the sheet modal cycle between available breakpoints when pressed. This keeps the sheet operable with assistive technology. Set to `"none"` to make the handle purely decorative when pressed and removed from the tab order. Defaults to `"cycle"`.
+ * @default 'cycle'
*/
"handleBehavior"?: ModalHandleBehavior;
/**
@@ -8682,7 +8544,6 @@ declare namespace LocalJSX {
interface IonPickerColumn {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -8715,7 +8576,6 @@ declare namespace LocalJSX {
interface IonPickerColumnOption {
/**
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
- * @default 'primary'
*/
"color"?: Color;
/**
@@ -8736,124 +8596,6 @@ declare namespace LocalJSX {
*/
"value"?: any | null;
}
- interface IonPickerLegacy {
- /**
- * If `true`, the picker will animate.
- * @default true
- */
- "animated"?: boolean;
- /**
- * If `true`, the picker will be dismissed when the backdrop is clicked.
- * @default true
- */
- "backdropDismiss"?: boolean;
- /**
- * Array of buttons to be displayed at the top of the picker.
- * @default []
- */
- "buttons"?: PickerButton[];
- /**
- * Array of columns to be displayed in the picker.
- * @default []
- */
- "columns"?: PickerColumn[];
- /**
- * Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces.
- */
- "cssClass"?: string | string[];
- "delegate"?: FrameworkDelegate;
- /**
- * Number of milliseconds to wait before dismissing the picker.
- * @default 0
- */
- "duration"?: number;
- /**
- * Animation to use when the picker is presented.
- */
- "enterAnimation"?: AnimationBuilder;
- /**
- * @default false
- */
- "hasController"?: boolean;
- /**
- * Additional attributes to pass to the picker.
- */
- "htmlAttributes"?: { [key: string]: any };
- /**
- * If `true`, the picker will open. If `false`, the picker will close. Use this if you need finer grained control over presentation, otherwise just use the pickerController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the picker dismisses. You will need to do that in your code.
- * @default false
- */
- "isOpen"?: boolean;
- /**
- * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
- * @default true
- */
- "keyboardClose"?: boolean;
- /**
- * Animation to use when the picker is dismissed.
- */
- "leaveAnimation"?: AnimationBuilder;
- /**
- * The mode determines the platform behaviors of the component.
- */
- "mode"?: "ios" | "md";
- /**
- * Emitted after the picker has dismissed. Shorthand for ionPickerDidDismiss.
- */
- "onDidDismiss"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted after the picker has presented. Shorthand for ionPickerWillDismiss.
- */
- "onDidPresent"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted after the picker has dismissed.
- */
- "onIonPickerDidDismiss"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted after the picker has presented.
- */
- "onIonPickerDidPresent"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted before the picker has dismissed.
- */
- "onIonPickerWillDismiss"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted before the picker has presented.
- */
- "onIonPickerWillPresent"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted before the picker has dismissed. Shorthand for ionPickerWillDismiss.
- */
- "onWillDismiss"?: (event: IonPickerLegacyCustomEvent) => void;
- /**
- * Emitted before the picker has presented. Shorthand for ionPickerWillPresent.
- */
- "onWillPresent"?: (event: IonPickerLegacyCustomEvent) => void;
- "overlayIndex": number;
- /**
- * If `true`, a backdrop will be displayed behind the picker.
- * @default true
- */
- "showBackdrop"?: boolean;
- /**
- * The theme determines the visual appearance of the component.
- */
- "theme"?: "ios" | "md" | "ionic";
- /**
- * An ID corresponding to the trigger element that causes the picker to open when clicked.
- */
- "trigger"?: string | undefined;
- }
- interface IonPickerLegacyColumn {
- /**
- * Picker column data
- */
- "col": PickerColumn;
- /**
- * Emitted when the selected value has changed
- */
- "onIonPickerColChange"?: (event: IonPickerLegacyColumnCustomEvent) => void;
- }
interface IonPopover {
/**
* Describes how to align the popover content with the `reference` point. Defaults to `"center"` for `"ios"` theme, and `"start"` for `"md"` theme.
@@ -9162,7 +8904,7 @@ declare namespace LocalJSX {
*/
"disabled"?: boolean;
/**
- * Show two knobs.
+ * If `true`, the range shows two knobs and `value` is an object with `lower` and `upper` properties. If `false`, the range shows one knob and `value` is a number.
* @default false
*/
"dualKnobs"?: boolean;
@@ -9403,7 +9145,7 @@ declare namespace LocalJSX {
*/
"beforeLeave"?: NavigationHookCallback;
/**
- * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
+ * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-router-outlet`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
"component": string;
/**
@@ -9520,10 +9262,14 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
* @default getIonMode(this)
*/
- "mode"?: "md" | "ios";
+ "mode"?: "ios" | "md";
"onIonNavDidChange"?: (event: IonRouterOutletCustomEvent) => void;
"onIonNavWillChange"?: (event: IonRouterOutletCustomEvent) => void;
"onIonNavWillLoad"?: (event: IonRouterOutletCustomEvent) => void;
+ /**
+ * If `true`, the router-outlet should allow navigation via swipe-to-go-back gesture. Defaults to `true` for `"ios"` mode and `false` for `"md"` mode.
+ */
+ "swipeGesture"?: boolean;
"swipeHandler"?: SwipeGestureHandler;
/**
* The theme determines the visual appearance of the component.
@@ -9554,9 +9300,9 @@ declare namespace LocalJSX {
"autocomplete"?: AutocompleteTypes;
/**
* Set the input's autocorrect property.
- * @default 'off'
+ * @default false
*/
- "autocorrect"?: 'on' | 'off';
+ "autocorrect"?: boolean;
/**
* Set the cancel button icon. Only available when the theme is `"md"`. Defaults to `"arrow-back-sharp"`.
*/
@@ -9949,7 +9695,7 @@ declare namespace LocalJSX {
*/
"description"?: string;
/**
- * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
+ * If `true`, the user cannot interact with the select option.
* @default false
*/
"disabled"?: boolean;
@@ -10104,17 +9850,16 @@ declare namespace LocalJSX {
* @default 'full'
*/
"expand"?: 'compact' | 'full';
- /**
- * If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`.
- * @default false
- */
- "hideOnScroll"?: boolean;
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
"onIonTabBarChanged"?: (event: IonTabBarCustomEvent) => void;
"onIonTabBarLoaded"?: (event: IonTabBarCustomEvent) => void;
+ /**
+ * Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up. Note: `"hide"` is ignored when the tab bar is nested inside an `ion-footer` to avoid leaving an empty footer visible on screen. Set `scroll-effect="hide"` on the footer instead so the entire footer hides together.
+ */
+ "scrollEffect"?: TabBarScrollEffect;
/**
* The selected tab component
*/
@@ -10896,6 +10641,7 @@ declare namespace LocalJSX {
"show": boolean;
"translucent": boolean;
"type": 'submit' | 'reset' | 'button';
+ "form": string | HTMLFormElement;
"size": 'small';
"closeIcon": string;
}
@@ -10904,6 +10650,7 @@ declare namespace LocalJSX {
"side": 'start' | 'end' | 'top' | 'bottom';
}
interface IonFooterAttributes {
+ "scrollEffect": FooterScrollEffect;
"collapse": 'fade';
"translucent": boolean;
}
@@ -10917,6 +10664,7 @@ declare namespace LocalJSX {
"fixed": boolean;
}
interface IonHeaderAttributes {
+ "scrollEffect": HeaderScrollEffect;
"collapse": 'condense' | 'fade';
"divider": boolean;
"translucent": boolean;
@@ -10939,7 +10687,7 @@ declare namespace LocalJSX {
"color": Color;
"autocapitalize": string;
"autocomplete": AutocompleteTypes;
- "autocorrect": 'on' | 'off';
+ "autocorrect": boolean;
"autofocus": boolean;
"clearInput": boolean;
"clearInputIcon": string;
@@ -11120,18 +10868,6 @@ declare namespace LocalJSX {
"value": string;
"color": Color;
}
- interface IonPickerLegacyAttributes {
- "overlayIndex": number;
- "hasController": boolean;
- "keyboardClose": boolean;
- "cssClass": string | string[];
- "duration": number;
- "showBackdrop": boolean;
- "backdropDismiss": boolean;
- "animated": boolean;
- "isOpen": boolean;
- "trigger": string | undefined;
- }
interface IonPopoverAttributes {
"hasController": boolean;
"overlayIndex": number;
@@ -11238,15 +10974,16 @@ declare namespace LocalJSX {
"target": string | undefined;
}
interface IonRouterOutletAttributes {
- "mode": "md" | "ios";
+ "mode": "ios" | "md";
"animated": boolean;
+ "swipeGesture": boolean;
}
interface IonSearchbarAttributes {
"color": Color;
"animated": boolean;
"autocapitalize": string;
"autocomplete": AutocompleteTypes;
- "autocorrect": 'on' | 'off';
+ "autocorrect": boolean;
"cancelButtonIcon": string;
"cancelButtonText": string;
"clearIcon": string;
@@ -11354,7 +11091,7 @@ declare namespace LocalJSX {
interface IonTabBarAttributes {
"color": Color;
"selectedTab": string;
- "hideOnScroll": boolean;
+ "scrollEffect": TabBarScrollEffect;
"translucent": boolean;
"expand": 'compact' | 'full';
"shape": 'soft' | 'round' | 'rectangular';
@@ -11511,8 +11248,6 @@ declare namespace LocalJSX {
"ion-picker": IonPicker;
"ion-picker-column": Omit & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes]?: IonPickerColumn[K] } & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes as `attr:${K}`]?: IonPickerColumnAttributes[K] } & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes as `prop:${K}`]?: IonPickerColumn[K] };
"ion-picker-column-option": Omit & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes]?: IonPickerColumnOption[K] } & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes as `attr:${K}`]?: IonPickerColumnOptionAttributes[K] } & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes as `prop:${K}`]?: IonPickerColumnOption[K] };
- "ion-picker-legacy": Omit & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes]?: IonPickerLegacy[K] } & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes as `attr:${K}`]?: IonPickerLegacyAttributes[K] } & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes as `prop:${K}`]?: IonPickerLegacy[K] } & OneOf<"overlayIndex", IonPickerLegacy["overlayIndex"], IonPickerLegacyAttributes["overlayIndex"]>;
- "ion-picker-legacy-column": IonPickerLegacyColumn;
"ion-popover": Omit & { [K in keyof IonPopover & keyof IonPopoverAttributes]?: IonPopover[K] } & { [K in keyof IonPopover & keyof IonPopoverAttributes as `attr:${K}`]?: IonPopoverAttributes[K] } & { [K in keyof IonPopover & keyof IonPopoverAttributes as `prop:${K}`]?: IonPopover[K] } & OneOf<"overlayIndex", IonPopover["overlayIndex"], IonPopoverAttributes["overlayIndex"]>;
"ion-progress-bar": Omit & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes]?: IonProgressBar[K] } & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes as `attr:${K}`]?: IonProgressBarAttributes[K] } & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes as `prop:${K}`]?: IonProgressBar[K] };
"ion-radio": Omit & { [K in keyof IonRadio & keyof IonRadioAttributes]?: IonRadio[K] } & { [K in keyof IonRadio & keyof IonRadioAttributes as `attr:${K}`]?: IonRadioAttributes[K] } & { [K in keyof IonRadio & keyof IonRadioAttributes as `prop:${K}`]?: IonRadio[K] };
@@ -11617,8 +11352,6 @@ declare module "@stencil/core" {
"ion-picker": LocalJSX.IntrinsicElements["ion-picker"] & JSXBase.HTMLAttributes;
"ion-picker-column": LocalJSX.IntrinsicElements["ion-picker-column"] & JSXBase.HTMLAttributes;
"ion-picker-column-option": LocalJSX.IntrinsicElements["ion-picker-column-option"] & JSXBase.HTMLAttributes;
- "ion-picker-legacy": LocalJSX.IntrinsicElements["ion-picker-legacy"] & JSXBase.HTMLAttributes;
- "ion-picker-legacy-column": LocalJSX.IntrinsicElements["ion-picker-legacy-column"] & JSXBase.HTMLAttributes;
"ion-popover": LocalJSX.IntrinsicElements["ion-popover"] & JSXBase.HTMLAttributes;
"ion-progress-bar": LocalJSX.IntrinsicElements["ion-progress-bar"] & JSXBase.HTMLAttributes;
"ion-radio": LocalJSX.IntrinsicElements["ion-radio"] & JSXBase.HTMLAttributes;
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png
index 9453467642d..836e2cca2c1 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Firefox-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Firefox-linux.png
index f4a0511dce0..53124bc3aee 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Safari-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Safari-linux.png
index 8d312c5f0e0..227d9fb95e2 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Safari-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png
index dac7aa36332..121d2dd16ca 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Firefox-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Firefox-linux.png
index a3887543f84..0198077466a 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Safari-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Safari-linux.png
index 8eab5d49de3..df1da9e3380 100644
Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Safari-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/accordion/test/basic/index.html b/core/src/components/accordion/test/basic/index.html
index 0307c164aa3..a2471ee4c34 100644
--- a/core/src/components/accordion/test/basic/index.html
+++ b/core/src/components/accordion/test/basic/index.html
@@ -34,7 +34,7 @@
-
+ Accordion - Basic
diff --git a/core/src/components/accordion/test/disabled/index.html b/core/src/components/accordion/test/disabled/index.html
index 38452b7adec..af59083c859 100644
--- a/core/src/components/accordion/test/disabled/index.html
+++ b/core/src/components/accordion/test/disabled/index.html
@@ -25,7 +25,7 @@
-
+ Accordion - Disabled
diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-md-ltr-Mobile-Chrome-linux.png
index 09d83694f60..e89c97ef2bc 100644
Binary files a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/accordion/test/nested/index.html b/core/src/components/accordion/test/nested/index.html
index 783740c6634..3e4e01a8c89 100644
--- a/core/src/components/accordion/test/nested/index.html
+++ b/core/src/components/accordion/test/nested/index.html
@@ -34,7 +34,7 @@
-
+ Accordion - Basic
diff --git a/core/src/components/accordion/test/readonly/index.html b/core/src/components/accordion/test/readonly/index.html
index 7b1a9ac84c6..43288c7a848 100644
--- a/core/src/components/accordion/test/readonly/index.html
+++ b/core/src/components/accordion/test/readonly/index.html
@@ -25,7 +25,7 @@
-
+ Accordion - Readonly
diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx
index 1a4922f5746..35ad13737d9 100644
--- a/core/src/components/action-sheet/action-sheet.tsx
+++ b/core/src/components/action-sheet/action-sheet.tsx
@@ -6,6 +6,7 @@ import { raf } from '@utils/helpers';
import { createLockController } from '@utils/lock-controller';
import {
BACKDROP,
+ cleanupRootFocusTrapAccessibility,
createDelegateController,
createTriggerController,
dismiss,
@@ -13,6 +14,7 @@ import {
isCancel,
prepareOverlay,
present,
+ restoreRootFocusTrapAccessibility,
safeCall,
setOverlayId,
} from '@utils/overlays';
@@ -456,6 +458,15 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
connectedCallback() {
prepareOverlay(this.el);
this.triggerChanged();
+
+ // `componentDidLoad` only fires once per instance, so a reconnect has to
+ // rebuild the gesture its disconnect destroyed.
+ this.setupButtonActiveGesture();
+
+ // Re-apply the root lock if moved without dismiss() being called
+ if (this.presented) {
+ restoreRootFocusTrapAccessibility(this.el);
+ }
}
disconnectedCallback() {
@@ -464,6 +475,11 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
this.gesture = undefined;
}
this.triggerController.removeClickListener();
+
+ // Clean up aria-hidden if removed without dismiss() being called
+ if (this.presented) {
+ cleanupRootFocusTrapAccessibility();
+ }
}
componentWillLoad() {
@@ -474,27 +490,36 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
this.buttonsChanged();
}
- componentDidLoad() {
- const mode = getIonMode(this);
- /**
- * Only create gesture if:
- * 1. A gesture does not already exist
- * 2. App is running in iOS mode
- * 3. A wrapper ref exists
- * 4. A group ref exists
- */
+ /**
+ * Only create gesture if:
+ * 1. A gesture does not already exist
+ * 2. App is running in iOS mode
+ * 3. A wrapper ref exists
+ * 4. A group ref exists
+ * 5. The host is still connected, since a reconnect can schedule this and
+ * disconnect again before the task runs
+ */
+ private setupButtonActiveGesture() {
const { groupEl, wrapperEl } = this;
- if (!this.gesture && mode === 'ios' && wrapperEl && groupEl) {
- readTask(() => {
- const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
- if (!isScrollable) {
- this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
- refEl.classList.contains('action-sheet-button')
- );
- this.gesture.enable(true);
- }
- });
+ if (getIonMode(this) !== 'ios' || !wrapperEl || !groupEl) {
+ return;
}
+ readTask(() => {
+ // Bail if a call queued ahead of this one already built the gesture
+ // (a second would orphan the first with its listeners still bound), if
+ // the host disconnected while this task waited, or if the group scrolls.
+ if (this.gesture || !this.el.isConnected || groupEl.scrollHeight > groupEl.clientHeight) {
+ return;
+ }
+ this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
+ refEl.classList.contains('action-sheet-button')
+ );
+ this.gesture.enable(true);
+ });
+ }
+
+ componentDidLoad() {
+ this.setupButtonActiveGesture();
/**
* If action sheet was rendered with isOpen="true"
diff --git a/core/src/components/action-sheet/test/basic/action-sheet.e2e.ts b/core/src/components/action-sheet/test/basic/action-sheet.e2e.ts
index d0921d354da..19368dd652f 100644
--- a/core/src/components/action-sheet/test/basic/action-sheet.e2e.ts
+++ b/core/src/components/action-sheet/test/basic/action-sheet.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { configs, detachAndReattach, test } from '@utils/test/playwright';
import { ActionSheetFixture } from './fixture';
@@ -166,3 +166,56 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
});
});
});
+
+/**
+ * The button gesture only exists in iOS mode, so these run there.
+ */
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ config, title }) => {
+ test.describe(title('action sheet: moved while presented'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/src/components/action-sheet/test/basic', config);
+ });
+
+ test('should keep the app root locked', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://github.com/ionic-team/ionic-framework/issues/31389',
+ });
+
+ const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
+
+ await page.click('#basic');
+ await ionActionSheetDidPresent.next();
+
+ await expect(page.locator('body')).toHaveClass(/backdrop-no-scroll/);
+
+ await detachAndReattach(page.locator('ion-action-sheet'));
+
+ await expect(page.locator('body')).toHaveClass(/backdrop-no-scroll/);
+ });
+
+ test('should keep activating buttons on press', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://github.com/ionic-team/ionic-framework/issues/31389',
+ });
+
+ const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
+
+ await page.click('#basic');
+ await ionActionSheetDidPresent.next();
+
+ await detachAndReattach(page.locator('ion-action-sheet'));
+
+ const button = page.locator('ion-action-sheet .action-sheet-button').first();
+ const box = (await button.boundingBox())!;
+
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
+ await page.mouse.down();
+
+ await expect(button).toHaveClass(/ion-activated/);
+
+ await page.mouse.up();
+ });
+ });
+});
diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx
index 44adc065aa9..351c2c4349d 100644
--- a/core/src/components/alert/alert.tsx
+++ b/core/src/components/alert/alert.tsx
@@ -8,14 +8,16 @@ import { createLockController } from '@utils/lock-controller';
import { printIonWarning } from '@utils/logging';
import { getOverlayLabelJustify, getOverlayLabelPlacement } from '@utils/overlay-control-label';
import {
+ BACKDROP,
+ cleanupRootFocusTrapAccessibility,
createDelegateController,
createTriggerController,
- BACKDROP,
dismiss,
eventMethod,
isCancel,
prepareOverlay,
present,
+ restoreRootFocusTrapAccessibility,
safeCall,
setOverlayId,
} from '@utils/overlays';
@@ -364,10 +366,16 @@ export class Alert implements ComponentInterface, OverlayInterface {
this.triggerChanged();
/**
* If the alert was previously connected and is being reattached, the
- * ResizeObserver was disconnected. componentDidLoad only fires once per
- * instance, so re-establish the observer here on reconnect.
+ * `ResizeObserver` and the button gesture were torn down. `componentDidLoad`
+ * only fires once per instance, so re-establish both here on reconnect.
*/
this.setupButtonGroupResizeObserver();
+ this.setupButtonActiveGesture();
+
+ // Re-apply the root lock if moved without dismiss() being called
+ if (this.presented) {
+ restoreRootFocusTrapAccessibility(this.el);
+ }
}
componentWillLoad() {
@@ -386,24 +394,32 @@ export class Alert implements ComponentInterface, OverlayInterface {
this.gesture = undefined;
}
+ // Clean up aria-hidden if removed without dismiss() being called
+ if (this.presented) {
+ cleanupRootFocusTrapAccessibility();
+ }
+
this.buttonGroupResizeObserver?.disconnect();
this.buttonGroupResizeObserver = undefined;
}
- componentDidLoad() {
- /**
- * Only create gesture if:
- * 1. A gesture does not already exist
- * 2. App is running in iOS mode
- * 3. A wrapper ref exists
- */
+ /**
+ * Only create gesture if:
+ * 1. A gesture does not already exist
+ * 2. App is running in iOS mode
+ * 3. A wrapper ref exists
+ */
+ private setupButtonActiveGesture() {
if (!this.gesture && getIonMode(this) === 'ios' && this.wrapperEl) {
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
}
+ }
+ componentDidLoad() {
+ this.setupButtonActiveGesture();
this.setupButtonGroupResizeObserver();
/**
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-ios-ltr-Mobile-Chrome-linux.png
index f67d7697e94..9f777bfbe8e 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-md-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-md-ltr-Mobile-Chrome-linux.png
index 63cfb58a6d3..fea53fe3d0a 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-scale-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-text-wrap-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-text-wrap-ios-ltr-Mobile-Chrome-linux.png
index 0e4c7296e26..702aac68103 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-text-wrap-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-checkbox-text-wrap-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-ios-ltr-Mobile-Chrome-linux.png
index 5345dedf71f..2af165c1e6a 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-md-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-md-ltr-Mobile-Chrome-linux.png
index 1dff67d6512..4d9419a3bd5 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-scale-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-text-wrap-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-text-wrap-ios-ltr-Mobile-Chrome-linux.png
index 39e52cb8c99..cc17202dcde 100644
Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-text-wrap-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-radio-text-wrap-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts b/core/src/components/alert/test/basic/alert.e2e.ts
index aaaf5d8795c..c5b8e8a0bf2 100644
--- a/core/src/components/alert/test/basic/alert.e2e.ts
+++ b/core/src/components/alert/test/basic/alert.e2e.ts
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
-import { configs, test } from '@utils/test/playwright';
+import { configs, detachAndReattach, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('alert: basic'), () => {
@@ -203,3 +203,56 @@ class AlertFixture {
await expect(this.alert).toHaveScreenshot(screenshotFn(`alert-${modifier}`));
}
}
+
+/**
+ * The button gesture only exists in iOS mode, so these run there.
+ */
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ config, title }) => {
+ test.describe(title('alert: moved while presented'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/src/components/alert/test/basic', config);
+ });
+
+ test('should keep the app root locked', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://github.com/ionic-team/ionic-framework/issues/31389',
+ });
+
+ const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
+
+ await page.click('#basic');
+ await ionAlertDidPresent.next();
+
+ await expect(page.locator('body')).toHaveClass(/backdrop-no-scroll/);
+
+ await detachAndReattach(page.locator('ion-alert'));
+
+ await expect(page.locator('body')).toHaveClass(/backdrop-no-scroll/);
+ });
+
+ test('should keep activating buttons on press', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://github.com/ionic-team/ionic-framework/issues/31389',
+ });
+
+ const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
+
+ await page.click('#multipleButtons');
+ await ionAlertDidPresent.next();
+
+ await detachAndReattach(page.locator('ion-alert'));
+
+ const button = page.locator('ion-alert .alert-button').first();
+ const box = (await button.boundingBox())!;
+
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
+ await page.mouse.down();
+
+ await expect(button).toHaveClass(/ion-activated/);
+
+ await page.mouse.up();
+ });
+ });
+});
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-Mobile-Chrome-linux.png
index 5909d31177b..422dc78df14 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-dark-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-dark-Mobile-Chrome-linux.png
index 3a25efb2319..ace7921fe82 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-dark-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-ltr-dark-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-Mobile-Chrome-linux.png
index 28906d81ae7..c1f3aa6076c 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-dark-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-dark-Mobile-Chrome-linux.png
index c1a9f818693..0ca5b035758 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-dark-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-ios-rtl-dark-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-Mobile-Chrome-linux.png
index 57f6cbfed1b..e8c4b4d1989 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-dark-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-dark-Mobile-Chrome-linux.png
index 7c7fad2aba5..5521188a4d2 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-dark-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-ltr-dark-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-Mobile-Chrome-linux.png
index ffa93bf0a91..ea61ad62798 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-dark-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-dark-Mobile-Chrome-linux.png
index b78b1518281..05671bfa723 100644
Binary files a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-dark-Mobile-Chrome-linux.png and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-multipleButtons-md-rtl-dark-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts b/core/src/components/app/test/safe-area/app.e2e.ts
index 8778a0ecb7a..784ae125414 100644
--- a/core/src/components/app/test/safe-area/app.e2e.ts
+++ b/core/src/components/app/test/safe-area/app.e2e.ts
@@ -37,9 +37,6 @@ configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ co
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');
});
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png
index 09c93ab3ca7..149e331ac3e 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png
index ae012f8623d..8a7e7627c4f 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Safari-linux.png
index aea4148d201..872b381e73e 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png
index 236e564c681..fd0723f0b10 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png
index d6978bd6ab2..836fa89e6ee 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png
index b1e8b7e164f..26c0a73e29c 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png
index aa1b98b4ae0..d814abde139 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png
index fcf49ecb77d..1740e83d055 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png
index 8c321b2eb7f..1f3878e99af 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png
index 7e375ecb515..1716e46dfb2 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png
index 432c8f0b269..2adf16c32cc 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Safari-linux.png
index fd4877e17f3..8ebb205c207 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png
index 2925d01cc40..27cfb472784 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png
index 625d61957f4..fb99cd5c8a8 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png
index 4c52df37741..9c810db363e 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png
index ea931ec8fd1..e8ad96dc261 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png
index 02908f4aeb5..cd66f657693 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png
index 6b4c5855199..a4e8c22c496 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png
deleted file mode 100644
index 23e78ac278d..00000000000
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png
deleted file mode 100644
index 6796d9a484f..00000000000
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png and /dev/null differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png
deleted file mode 100644
index 82f95faa291..00000000000
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png and /dev/null differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png
index a9eb52d18e9..65e29ada333 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png
index 07d81c3d0bb..e499f9554a7 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Safari-linux.png
index fabe9ef03ae..b28f2116d06 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png
index 67e9728bdc4..287ecf3e58d 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png
index 6d49226ac1d..3547ba868e2 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png
index 28cf5b612cd..c04d8fdfee2 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png
index 083cea5b3f8..1bd5cf03827 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png
index d95fc4b3aab..9c585f8d9f6 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png
index a7684bbf153..c16e3f0513c 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/index.html b/core/src/components/app/test/safe-area/index.html
index 4ad7e6beed4..6c2974100fb 100644
--- a/core/src/components/app/test/safe-area/index.html
+++ b/core/src/components/app/test/safe-area/index.html
@@ -55,7 +55,6 @@
-
@@ -115,26 +114,6 @@
await menu.open();
}
- async function showPicker() {
- const picker = Object.assign(document.createElement('ion-picker-legacy'), {
- columns: [
- {
- name: 'Picker',
- selectedIndex: 2,
- options: [
- { text: 'First' },
- { text: 'Second' },
- { text: 'Third' },
- { text: 'Fourth' },
- { text: 'Fifth' },
- ],
- },
- ],
- });
- document.body.appendChild(picker);
- await picker.present();
- }
-
async function showToast() {
const toast = Object.assign(document.createElement('ion-toast'), {
message: 'Hello world!',
diff --git a/core/src/components/avatar/avatar.ionic.scss b/core/src/components/avatar/avatar.ionic.scss
index d31e6c9c5cb..613aeb87bcf 100644
--- a/core/src/components/avatar/avatar.ionic.scss
+++ b/core/src/components/avatar/avatar.ionic.scss
@@ -140,33 +140,27 @@
}
:host(.avatar-xxsmall) ::slotted(ion-icon) {
- width: globals.$ion-scale-300;
- height: globals.$ion-scale-300;
+ font-size: globals.$ion-font-size-300;
}
:host(.avatar-xsmall) ::slotted(ion-icon) {
- width: globals.$ion-scale-400;
- height: globals.$ion-scale-400;
+ font-size: globals.$ion-font-size-400;
}
:host(.avatar-small) ::slotted(ion-icon) {
- width: globals.$ion-scale-500;
- height: globals.$ion-scale-500;
+ font-size: globals.$ion-font-size-500;
}
:host(.avatar-medium) ::slotted(ion-icon) {
- width: globals.$ion-scale-600;
- height: globals.$ion-scale-600;
+ font-size: globals.$ion-font-size-600;
}
:host(.avatar-large) ::slotted(ion-icon) {
- width: globals.$ion-scale-700;
- height: globals.$ion-scale-700;
+ font-size: globals.$ion-font-size-700;
}
:host(.avatar-xlarge) ::slotted(ion-icon) {
- width: globals.$ion-scale-800;
- height: globals.$ion-scale-800;
+ font-size: globals.$ion-font-size-800;
}
// Avatar Disabled
diff --git a/core/src/components/badge/badge.interfaces.ts b/core/src/components/badge/badge.interfaces.ts
index 7e8faeacbe5..8f7104a25f4 100644
--- a/core/src/components/badge/badge.interfaces.ts
+++ b/core/src/components/badge/badge.interfaces.ts
@@ -68,8 +68,9 @@ type IonBadgeSizeContentDefinition = IonBadgeSizeDefinition & {
};
icon?: {
- width?: string;
- height?: string;
+ font?: {
+ size?: string;
+ };
};
};
diff --git a/core/src/components/badge/badge.scss b/core/src/components/badge/badge.scss
index e98d65740cf..ed12662a900 100644
--- a/core/src/components/badge/badge.scss
+++ b/core/src/components/badge/badge.scss
@@ -38,8 +38,7 @@
* @prop --ion-badge-size-small-content-font-size: Font size of the `small` size when badge has content
* @prop --ion-badge-size-small-content-font-weight: Font weight of the `small` size when badge has content
* @prop --ion-badge-size-small-content-line-height: Line height of the `small` size when badge has content
- * @prop --ion-badge-size-small-content-icon-width: Width of the slotted `ion-icon` of the `small` size
- * @prop --ion-badge-size-small-content-icon-height: Height of the slotted `ion-icon` of the `small` size
+ * @prop --ion-badge-size-small-content-icon-font-size: Size of the slotted `ion-icon` of the `small` size
* @prop --ion-badge-size-medium-content-height: Height of the `medium` size when badge has content
* @prop --ion-badge-size-medium-content-min-height: Minimum height of the `medium` size when badge has content
* @prop --ion-badge-size-medium-content-min-width: Minimum width of the `medium` size when badge has content
@@ -51,8 +50,7 @@
* @prop --ion-badge-size-medium-content-font-size: Font size of the `medium` size when badge has content
* @prop --ion-badge-size-medium-content-font-weight: Font weight of the `medium` size when badge has content
* @prop --ion-badge-size-medium-content-line-height: Line height of the `medium` size when badge has content
- * @prop --ion-badge-size-medium-content-icon-width: Width of the slotted `ion-icon` of the `medium` size
- * @prop --ion-badge-size-medium-content-icon-height: Height of the slotted `ion-icon` of the `medium` size
+ * @prop --ion-badge-size-medium-content-icon-font-size: Size of the slotted `ion-icon` of the `medium` size
* @prop --ion-badge-size-large-content-height: Height of the `large` size when badge has content
* @prop --ion-badge-size-large-content-min-height: Minimum height of the `large` size when badge has content
* @prop --ion-badge-size-large-content-min-width: Minimum width of the `large` size when badge has content
@@ -64,8 +62,7 @@
* @prop --ion-badge-size-large-content-font-size: Font size of the `large` size when badge has content
* @prop --ion-badge-size-large-content-font-weight: Font weight of the `large` size when badge has content
* @prop --ion-badge-size-large-content-line-height: Line height of the `large` size when badge has content
- * @prop --ion-badge-size-large-content-icon-width: Width of the slotted `ion-icon` of the `large` size
- * @prop --ion-badge-size-large-content-icon-height: Height of the slotted `ion-icon` of the `large` size
+ * @prop --ion-badge-size-large-content-icon-font-size: Size of the slotted `ion-icon` of the `large` size
*
* Sizes: Dot (empty with no text or icon)
* @prop --ion-badge-size-small-dot-height: Height of the `small` size when badge is empty
@@ -177,8 +174,7 @@
}
:host(.badge-size-small) ::slotted(ion-icon) {
- width: var(--ion-badge-size-small-content-icon-width, revert-layer);
- height: var(--ion-badge-size-small-content-icon-height, revert-layer);
+ font-size: var(--ion-badge-size-small-content-icon-font-size, revert-layer);
}
// Medium
@@ -202,8 +198,7 @@
}
:host(.badge-size-medium) ::slotted(ion-icon) {
- width: var(--ion-badge-size-medium-content-icon-width, revert-layer);
- height: var(--ion-badge-size-medium-content-icon-height, revert-layer);
+ font-size: var(--ion-badge-size-medium-content-icon-font-size, revert-layer);
}
// Large
@@ -227,8 +222,7 @@
}
:host(.badge-size-large) ::slotted(ion-icon) {
- width: var(--ion-badge-size-large-content-icon-width, revert-layer);
- height: var(--ion-badge-size-large-content-icon-height, revert-layer);
+ font-size: var(--ion-badge-size-large-content-icon-font-size, revert-layer);
}
// Badge Sizes: Dot (empty with no text or icon)
diff --git a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png
index c7ad180ad38..2f19fdb62d5 100644
Binary files a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Firefox-linux.png
index be8195ffb01..8badf6b4865 100644
Binary files a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-bottom-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Chrome-linux.png
index af6eb12ebd2..e66a4397b9f 100644
Binary files a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Firefox-linux.png
index f64a184abc8..85551c17401 100644
Binary files a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Safari-linux.png
index f6974e2c586..442c50e6c5c 100644
Binary files a/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/badge/test/hint/badge.e2e.ts-snapshots/badge-hint-tab-button-icon-top-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/button/button.ionic.scss b/core/src/components/button/button.ionic.scss
index 22344274858..54f1911d09d 100644
--- a/core/src/components/button/button.ionic.scss
+++ b/core/src/components/button/button.ionic.scss
@@ -148,8 +148,7 @@
::slotted(ion-icon[slot="start"]),
::slotted(ion-icon[slot="end"]),
::slotted(ion-icon[slot="icon-only"]) {
- width: globals.$ion-scale-500;
- height: globals.$ion-scale-500;
+ font-size: globals.$ion-font-size-500;
}
}
@@ -157,8 +156,7 @@
::slotted(ion-icon[slot="start"]),
::slotted(ion-icon[slot="end"]),
::slotted(ion-icon[slot="icon-only"]) {
- width: globals.$ion-scale-600;
- height: globals.$ion-scale-600;
+ font-size: globals.$ion-font-size-600;
}
}
diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx
index 939ad04545c..a5f3640078f 100644
--- a/core/src/components/button/button.tsx
+++ b/core/src/components/button/button.tsx
@@ -209,9 +209,13 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
/**
* If the form already has a rendered form button
- * then do not append a new one again.
+ * then do not append a new one again. Sync the
+ * disabled state and type if it changes after button
+ * creation (e.g., runtime property updates).
*/
if (formButtonEl !== null && formEl.contains(formButtonEl)) {
+ formButtonEl.disabled = this.disabled;
+ formButtonEl.type = this.type;
return;
}
diff --git a/core/src/components/button/test/form-reference/button.e2e.ts b/core/src/components/button/test/form-reference/button.e2e.ts
index 5710f52da67..c85cd20760d 100644
--- a/core/src/components/button/test/form-reference/button.e2e.ts
+++ b/core/src/components/button/test/form-reference/button.e2e.ts
@@ -153,6 +153,41 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
expect(submitEvent).toHaveReceivedEvent();
});
+
+ test('should keep hidden button type in sync with visible button', async ({ page }) => {
+ // Set the form to have a submit button
+ await page.setContent(
+ `
+
+
+ Submit
+
+ `,
+ config
+ );
+
+ // Get visible button
+ const button = page.locator('ion-button');
+
+ // Get type of the hidden button
+ const getHiddenType = () =>
+ page.evaluate(() => {
+ const hidden = document.querySelector('form button[style*="display: none"]') as HTMLButtonElement;
+
+ return hidden?.type;
+ });
+
+ // Type of hidden button should be submit to start
+ expect(await getHiddenType()).toBe('submit');
+
+ // Set type of visible button to reset
+ await button.evaluate((el: HTMLIonButtonElement) => {
+ el.type = 'reset';
+ });
+
+ // Expect hidden button type to be reset
+ await expect.poll(async () => await getHiddenType()).toBe('reset');
+ });
});
test.describe(title('should throw a warning if the form cannot be found'), () => {
diff --git a/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Chrome-linux.png
index 738d37bb3a4..dd33abba24d 100644
Binary files a/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Chrome-linux.png
index fb38d7bd1f3..4e47e7bd996 100644
Binary files a/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/button/test/icon/button.e2e.ts-snapshots/button-icon-ios-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/buttons/buttons.ionic.scss b/core/src/components/buttons/buttons.ionic.scss
index e4df0bb0d37..31f7e76cc4f 100644
--- a/core/src/components/buttons/buttons.ionic.scss
+++ b/core/src/components/buttons/buttons.ionic.scss
@@ -29,5 +29,5 @@
}
::slotted(*) ion-icon[slot="icon-only"] {
- font-size: globals.$ion-font-size-600;
+ font-size: globals.$ion-font-size-500;
}
diff --git a/core/src/components/checkbox/checkbox.common.scss b/core/src/components/checkbox/checkbox.common.scss
index 20d86101e78..9a0f95c76c2 100644
--- a/core/src/components/checkbox/checkbox.common.scss
+++ b/core/src/components/checkbox/checkbox.common.scss
@@ -53,7 +53,7 @@
width: auto;
}
-// Hide the native focus outline.
+// Hide the native focus ring since we draw our own focus indicator.
:host(:focus) {
outline: none;
}
diff --git a/core/src/components/checkbox/checkbox.native.scss b/core/src/components/checkbox/checkbox.native.scss
index 4c9021f114f..2eb5c8859b2 100644
--- a/core/src/components/checkbox/checkbox.native.scss
+++ b/core/src/components/checkbox/checkbox.native.scss
@@ -23,6 +23,38 @@
--checkmark-color: #{current-color(contrast)};
}
+// Checkbox: Keyboard Focus
+// ---------------------------------------------
+
+// The indicator is positioned against the wrapper, which is only made a
+// containing block here because the `ionic` theme draws its own ring instead.
+.native-wrapper {
+ position: relative;
+}
+
+:host(.ion-focused) .native-wrapper::after {
+ @include border-radius(50%);
+
+ display: block;
+ position: absolute;
+
+ width: $checkbox-focus-ring-size;
+ height: $checkbox-focus-ring-size;
+
+ background: $checkbox-background-color-focused;
+
+ content: "";
+ opacity: 0.2;
+
+ // The indicator overhangs the control, so it must not take pointer events;
+ // otherwise it widens the hit area and swallows clicks aimed past the control.
+ pointer-events: none;
+
+ // Centered with insets because `transform` is physical and breaks in RTL.
+ inset-block-start: calc(50% - #{$checkbox-focus-ring-size} / 2);
+ inset-inline-start: calc(50% - #{$checkbox-focus-ring-size} / 2);
+}
+
// Checkboxes that are not slotted inside an item and are not used with a
// stacked label should have margins equal to those of the label.
:host(.in-item) .label-text-wrapper,
diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx
index 2a26468fa1c..e350b4fb7c0 100644
--- a/core/src/components/checkbox/checkbox.tsx
+++ b/core/src/components/checkbox/checkbox.tsx
@@ -1,6 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
-import { Build, Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core';
-import { checkInvalidState } from '@utils/forms';
+import { Build, Component, Element, Event, Host, Method, Prop, State, forceUpdate, h } from '@stencil/core';
+import { checkInvalidState, createItemMultipleInputsObserver } from '@utils/forms';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -39,6 +39,7 @@ export class Checkbox implements ComponentInterface {
private errorTextId = `${this.inputId}-error-text`;
private inheritedAttributes: Attributes = {};
private validationObserver?: MutationObserver;
+ private itemFocusObserver?: MutationObserver;
@Element() el!: HTMLIonCheckboxElement;
@@ -212,6 +213,8 @@ export class Checkbox implements ComponentInterface {
// Always set initial state
this.isInvalid = checkInvalidState(el);
this.hasLabelContent = this.el.textContent !== '';
+
+ this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this));
}
componentWillLoad() {
@@ -223,11 +226,14 @@ export class Checkbox implements ComponentInterface {
}
disconnectedCallback() {
- // Clean up validation observer to prevent memory leaks.
if (this.validationObserver) {
this.validationObserver.disconnect();
this.validationObserver = undefined;
}
+ if (this.itemFocusObserver) {
+ this.itemFocusObserver.disconnect();
+ this.itemFocusObserver = undefined;
+ }
}
/** @internal */
@@ -354,6 +360,7 @@ export class Checkbox implements ComponentInterface {
const theme = getIonTheme(this);
const path = getSVGPath(theme, indeterminate);
const inItem = hostContext('ion-item', el);
+ const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el);
renderHiddenInput(true, el, name, checked ? value : '', disabled);
@@ -379,10 +386,11 @@ export class Checkbox implements ComponentInterface {
'in-item': inItem,
'checkbox-checked': checked,
'checkbox-disabled': disabled,
- // Focus styling should not apply when the checkbox is in an item
- 'ion-focusable': !inItem,
'checkbox-indeterminate': indeterminate,
interactive: true,
+ // A single-input item has the input cover and draws the indicator itself.
+ // A multi-input item has no cover, so each control draws its own.
+ 'ion-focusable': !inItem || inMultipleInputsItem,
[`checkbox-justify-${justify}`]: justify !== undefined,
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
[`checkbox-label-placement-${labelPlacement}`]: true,
diff --git a/core/src/components/checkbox/checkbox.vars.scss b/core/src/components/checkbox/checkbox.vars.scss
index cc9c23bbf84..b7d5ce5a28f 100644
--- a/core/src/components/checkbox/checkbox.vars.scss
+++ b/core/src/components/checkbox/checkbox.vars.scss
@@ -1,5 +1,12 @@
+@import "../../themes/native/native.globals";
+
/// @prop - Top margin of checkbox's label when in an item
$checkbox-item-label-margin-top: 10px;
/// @prop - Bottom margin of checkbox's label when in an item
$checkbox-item-label-margin-bottom: 10px;
+
+/// @prop - Background color of focus indicator for checkbox when focused
+$checkbox-background-color-focused: ion-color(primary, tint);
+
+$checkbox-focus-ring-size: 36px;
diff --git a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
index 6d0b6645ae5..989a7f97a96 100644
--- a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
+++ b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts
@@ -25,6 +25,64 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title,
});
});
+/**
+ * These assert `ion-focusable`, not the rendered ring, because `ion-focused`
+ * relies on keyboard-mode detection that is flaky on WebKit. Gating is mode-independent.
+ */
+configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => {
+ test.describe(title('checkbox: focus indicator'), () => {
+ test('standalone checkbox should be focusable', async ({ page }) => {
+ await page.setContent(
+ `
+
+ Checkbox
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+ await expect(checkbox).toHaveClass(/ion-focusable/);
+ });
+
+ test('checkbox in a single-input item should not show its own focus indicator', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox
+
+
+ `,
+ config
+ );
+
+ const checkbox = page.locator('ion-checkbox');
+ const item = page.locator('ion-item');
+ await expect(checkbox).not.toHaveClass(/ion-focusable/);
+ await expect(item).toHaveClass(/ion-focusable/);
+ });
+
+ test('checkbox in a multi-input item should be focusable', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ Checkbox 1
+ Checkbox 2
+
+
+ `,
+ config
+ );
+
+ const checkboxes = page.locator('ion-checkbox');
+ await expect(checkboxes.nth(0)).toHaveClass(/ion-focusable/);
+ await expect(checkboxes.nth(1)).toHaveClass(/ion-focusable/);
+ });
+ });
+});
+
configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('checkbox: a11y'), () => {
test.describe(title('checkbox: font scaling'), () => {
diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts b/core/src/components/checkbox/test/basic/checkbox.e2e.ts
index 26197359481..f7e53deb4f1 100644
--- a/core/src/components/checkbox/test/basic/checkbox.e2e.ts
+++ b/core/src/components/checkbox/test/basic/checkbox.e2e.ts
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { configs, test } from '@utils/test/playwright';
+import { applyKeyboardFocus, configs, test } from '@utils/test/playwright';
configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: basic visual tests'), () => {
@@ -58,7 +58,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf
/**
* This behavior does not vary across modes/directions
*/
-configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('checkbox: ionChange'), () => {
test('should fire ionChange when interacting with checkbox', async ({ page }) => {
await page.setContent(
@@ -149,48 +149,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
});
test.describe(title('checkbox: ionFocus'), () => {
- test('should not have visual regressions', async ({ page, pageUtils }) => {
- await page.setContent(
- `
-
-
-