diff --git a/core/src/components/action-sheet/action-sheet.common.scss b/core/src/components/action-sheet/action-sheet.common.scss
index 7e857346ac9..b5427110f52 100644
--- a/core/src/components/action-sheet/action-sheet.common.scss
+++ b/core/src/components/action-sheet/action-sheet.common.scss
@@ -1,4 +1,4 @@
-@import "./action-sheet.vars";
+@use "../../themes/mixins" as mixins;
// Action Sheet
// --------------------------------------------------
@@ -41,25 +41,22 @@
--button-color-hover: var(--button-color);
--button-color-selected: var(--button-color);
--min-width: auto;
- --width: #{$action-sheet-width};
- --max-width: #{$action-sheet-max-width};
+ --width: 100%;
+ --max-width: 500px;
--min-height: auto;
--height: auto;
--max-height: calc(100% - (var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));
- @include font-smoothing();
- @include position(0, 0, 0, 0);
+ @include mixins.font-smoothing();
+ @include mixins.position(0, 0, 0, 0);
display: block;
position: fixed;
outline: none;
- font-family: $font-family-base;
-
touch-action: none;
user-select: none;
- z-index: $z-index-overlay;
}
:host(.overlay-hidden) {
@@ -67,8 +64,8 @@
}
.action-sheet-wrapper {
- @include position(null, 0, 0, 0);
- @include transform(translate3d(0, 100%, 0));
+ @include mixins.position(null, 0, 0, 0);
+ @include mixins.transform(translate3d(0, 100%, 0));
display: block;
position: absolute;
@@ -81,7 +78,6 @@
min-height: var(--min-height);
max-height: var(--max-height);
- z-index: $z-index-overlay-wrapper;
pointer-events: none;
}
@@ -109,6 +105,10 @@
opacity: 0.4;
}
+.action-sheet-button:disabled ion-icon {
+ color: currentColor;
+}
+
.action-sheet-button-inner {
display: flex;
@@ -177,7 +177,7 @@
// --------------------------------------------------
.action-sheet-button::after {
- @include button-state();
+ @include mixins.button-state();
}
// Action Sheet: Selected
@@ -209,7 +209,7 @@
// Action Sheet: Focused
// --------------------------------------------------
-.action-sheet-button.ion-focused {
+.action-sheet-button.ion-focused:not(.ion-activated):not(.action-sheet-selected) {
color: var(--button-color-focused);
&::after {
@@ -243,10 +243,28 @@
align-items: center;
}
+.action-sheet-button-label-has-rich-content,
.select-option-content {
flex: 1;
}
+.select-option-start,
+.select-option-end {
+ display: flex;
+
+ align-items: center;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-width: fit-content;
+}
+
.select-option-description {
display: block;
}
diff --git a/core/src/components/action-sheet/action-sheet.ionic.scss b/core/src/components/action-sheet/action-sheet.ionic.scss
index b2c749d4e0a..b2877c9ce6e 100644
--- a/core/src/components/action-sheet/action-sheet.ionic.scss
+++ b/core/src/components/action-sheet/action-sheet.ionic.scss
@@ -1,10 +1,111 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
+@use "../../themes/mixins" as mixins;
@use "./action-sheet.common";
-@use "./action-sheet.md" as action-sheet-md;
// Ionic Action Sheet
// --------------------------------------------------
+:host {
+ --background: #{globals.$ion-bg-surface-default};
+ --backdrop-opacity: 0.7;
+ --button-background: transparent;
+ --button-background-selected: #{globals.$ion-semantics-primary-100};
+ --button-background-selected-opacity: 1;
+ --button-background-activated: #{globals.$ion-semantics-primary-100};
+ --button-background-activated-opacity: 1;
+ --button-background-hover: #{globals.$ion-semantics-primary-100};
+ --button-background-hover-opacity: 1;
+ --button-color: #{globals.$ion-text-default};
+ --button-color-disabled: #{globals.$ion-text-disabled};
+ --color: #{globals.$ion-text-default};
+
+ z-index: 1001;
+}
+
+.action-sheet-wrapper {
+ z-index: 10;
+}
+
+.action-sheet-button.ion-focused::after {
+ @include globals.focused-state(
+ globals.$ion-border-size-050,
+ globals.$ion-border-style-solid,
+ globals.$ion-border-focus-default
+ );
+
+ outline-offset: calc(globals.$ion-border-size-050 * -1);
+}
+
+// Action Sheet Wrapper
+// -----------------------------------------
+
+.action-sheet-wrapper {
+ @include mixins.margin(var(--ion-safe-area-top, 0), auto, 0, auto);
+}
+
+.action-sheet-title {
+ @include mixins.padding(globals.$ion-space-400);
+ @include globals.typography(globals.$ion-heading-h6-medium);
+
+ color: var(--color);
+
+ text-align: start;
+}
+
+.action-sheet-sub-title {
+ @include globals.typography(globals.$ion-body-md-regular);
+
+ color: globals.$ion-text-subtlest;
+}
+
+// Action Sheet Group
+// -----------------------------------------
+
+.action-sheet-group:first-child {
+ @include mixins.padding(globals.$ion-space-400, null, null, null);
+}
+
+.action-sheet-group:last-child {
+ @include mixins.padding(null, null, globals.$ion-space-400, null);
+}
+
+// Action Sheet Buttons
+// -----------------------------------------
+
+.action-sheet-button {
+ @include mixins.padding(
+ globals.$ion-space-300,
+ globals.$ion-space-400,
+ globals.$ion-space-300,
+ globals.$ion-space-400
+ );
+ @include globals.typography(globals.$ion-body-md-regular);
+
+ position: relative;
+
+ min-height: 52px;
+
+ text-align: start;
+
+ contain: content;
+ overflow: hidden;
+}
+
+.action-sheet-icon {
+ @include mixins.margin(globals.$ion-space-0, globals.$ion-space-600, globals.$ion-space-0, globals.$ion-space-0);
+ @include globals.typography(globals.$ion-body-md-regular);
+
+ color: var(--color, globals.$ion-text-default);
+}
+
+.action-sheet-button-inner {
+ justify-content: flex-start;
+}
+
+.action-sheet-selected {
+ font-weight: bold;
+}
+
// Action Sheet: Select Option
// --------------------------------------------------
@@ -12,11 +113,24 @@
gap: globals.$ion-space-300;
}
+.select-option-start,
+.select-option-end {
+ gap: globals.$ion-space-200;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: globals.$ion-scale-1200;
+}
+
.select-option-description {
@include globals.typography(globals.$ion-body-md-regular);
- @include globals.padding(0);
+ @include globals.padding(globals.$ion-space-0);
color: globals.$ion-text-subtle;
-
- font-size: globals.$ion-font-size-350;
}
diff --git a/core/src/components/action-sheet/action-sheet.ios.scss b/core/src/components/action-sheet/action-sheet.ios.scss
index 94b98447981..04485fe95c3 100644
--- a/core/src/components/action-sheet/action-sheet.ios.scss
+++ b/core/src/components/action-sheet/action-sheet.ios.scss
@@ -215,3 +215,16 @@
color: $action-sheet-ios-button-destructive-text-color;
}
}
+
+// Action Sheet: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: $action-sheet-ios-select-option-slot-size;
+}
diff --git a/core/src/components/action-sheet/action-sheet.ios.vars.scss b/core/src/components/action-sheet/action-sheet.ios.vars.scss
index 0d2302b7183..6c301624c53 100644
--- a/core/src/components/action-sheet/action-sheet.ios.vars.scss
+++ b/core/src/components/action-sheet/action-sheet.ios.vars.scss
@@ -161,3 +161,6 @@ $action-sheet-ios-group-translucent-filter: saturate(280%) blur(20px);
/// @prop - Filter of the translucent action-sheet button
$action-sheet-ios-button-translucent-filter: saturate(120%);
+
+/// @prop - Maximum size of slotted children rendered in a select option's start/end slot
+$action-sheet-ios-select-option-slot-size: dynamic-font-max(24px, 2);
diff --git a/core/src/components/action-sheet/action-sheet.md.scss b/core/src/components/action-sheet/action-sheet.md.scss
index e46f06085b3..ef4134c2c63 100644
--- a/core/src/components/action-sheet/action-sheet.md.scss
+++ b/core/src/components/action-sheet/action-sheet.md.scss
@@ -1,7 +1,7 @@
@import "./action-sheet.native";
@import "./action-sheet.md.vars";
-// Material Design Action Sheet Title
+// Material Design Action Sheet
// -----------------------------------------
:host {
@@ -110,3 +110,16 @@
.action-sheet-selected {
font-weight: bold;
}
+
+// Action Sheet: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: 24px;
+}
diff --git a/core/src/components/action-sheet/action-sheet.native.scss b/core/src/components/action-sheet/action-sheet.native.scss
index affa6aeb126..cdd91732bc6 100644
--- a/core/src/components/action-sheet/action-sheet.native.scss
+++ b/core/src/components/action-sheet/action-sheet.native.scss
@@ -1,4 +1,4 @@
-@use "../../themes/native/native.theme.default" as native;
+@use "../../themes/native/native.globals" as native;
@use "../../themes/mixins" as mixins;
@use "../../themes/functions.font" as font;
@use "./action-sheet.common";
@@ -6,10 +6,28 @@
// Action Sheet: Native
// --------------------------------------------------
+:host {
+ font-family: native.$font-family-base;
+
+ z-index: native.$z-index-overlay;
+}
+
+.action-sheet-wrapper {
+ z-index: native.$z-index-overlay-wrapper;
+}
+
+// Action Sheet: Select Option
+// --------------------------------------------------
+
.action-sheet-button-label {
gap: 12px;
}
+.select-option-start,
+.select-option-end {
+ gap: 8px;
+}
+
.select-option-description {
@include mixins.padding(5px, 0, 0, 0);
diff --git a/core/src/components/action-sheet/action-sheet.vars.scss b/core/src/components/action-sheet/action-sheet.vars.scss
deleted file mode 100644
index d81812ae23e..00000000000
--- a/core/src/components/action-sheet/action-sheet.vars.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-@import "../../themes/native/native.globals";
-
-// Action Sheet
-// --------------------------------------------------
-
-/// @prop - Width of the action sheet
-$action-sheet-width: 100%;
-
-/// @prop - Maximum width of the action sheet
-$action-sheet-max-width: 500px;
diff --git a/core/src/components/action-sheet/test/basic/index.html b/core/src/components/action-sheet/test/basic/index.html
index b95d43b42c7..e7ec4e819ba 100644
--- a/core/src/components/action-sheet/test/basic/index.html
+++ b/core/src/components/action-sheet/test/basic/index.html
@@ -46,6 +46,8 @@
.my-color-class {
--background: #292929;
--button-background-selected: #222222;
+ --button-background-activated: #393838;
+ --button-background-activated-opacity: 1;
--color: #dfdfdf;
--button-color: #dfdfdf;
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts
new file mode 100644
index 00000000000..6929878f12b
--- /dev/null
+++ b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts
@@ -0,0 +1,40 @@
+import { configs, test } from '@utils/test/playwright';
+
+import { ActionSheetFixture } from '../basic/fixture';
+
+/**
+ * This behavior does not vary across directions.
+ */
+configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
+ test.describe(title('action sheet: states'), () => {
+ /**
+ * `(any-hover: hover)` evaluates to "none" in all three mobile-emulated
+ * projects, suppressing the hover rules:
+ *
+ * - Chromium and WebKit suppress it because of hasTouch and isMobile.
+ * - Headless Firefox doesn't detect input devices and reports no hover
+ * capability regardless of context options, so override it via
+ * launchOptions.firefoxUserPrefs. Bit values: 4 = FINE (mouse),
+ * 8 = HOVER, 12 = FINE | HOVER.
+ *
+ * Viewport, userAgent, and scale factor remain mobile-sized.
+ */
+ test.use({
+ hasTouch: false,
+ isMobile: false,
+ });
+
+ test('should render all button states', async ({ page }) => {
+ await page.goto(`/src/components/action-sheet/test/states`, config);
+
+ const actionSheetFixture = new ActionSheetFixture(page, screenshot);
+
+ await actionSheetFixture.open('#basic');
+
+ const defaultButton = page.locator('ion-action-sheet button.action-sheet-button').first();
+ await defaultButton.hover();
+
+ await actionSheetFixture.screenshot('states');
+ });
+ });
+});
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..756e33a6700
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..8251dc17fe4
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..d3bbc4c58b8
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..cf2d63235bb
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..5ae3278c1b2
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..23cba718d75
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..290dd400493
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..0b2cd5cb038
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..59ccb166f40
Binary files /dev/null and b/core/src/components/action-sheet/test/states/action-sheet.e2e.ts-snapshots/action-sheet-states-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/action-sheet/test/states/index.html b/core/src/components/action-sheet/test/states/index.html
new file mode 100644
index 00000000000..5d5339d5d1b
--- /dev/null
+++ b/core/src/components/action-sheet/test/states/index.html
@@ -0,0 +1,97 @@
+
+
+
+
+ Action Sheet - States
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Action Sheet - States
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/alert/alert.common.scss b/core/src/components/alert/alert.common.scss
index 84e35eca5c3..dfbf11c43f8 100644
--- a/core/src/components/alert/alert.common.scss
+++ b/core/src/components/alert/alert.common.scss
@@ -1,4 +1,5 @@
-@import "./alert.vars";
+@use "../../themes/functions.font" as font;
+@use "../../themes/mixins" as mixins;
// Alert
// --------------------------------------------------
@@ -17,14 +18,14 @@
*
* @prop --backdrop-opacity: Opacity of the backdrop
*/
- --min-width: #{$alert-min-width};
+ --min-width: 250px;
--width: auto;
--min-height: auto;
--height: auto;
- --max-height: #{$alert-max-height};
+ --max-height: 90%;
- @include font-smoothing();
- @include position(0, 0, 0, 0);
+ @include mixins.font-smoothing();
+ @include mixins.position(0, 0, 0, 0);
display: flex;
position: absolute;
@@ -34,12 +35,9 @@
outline: none;
- font-family: $font-family-base;
-
contain: strict;
touch-action: none;
user-select: none;
- z-index: $z-index-overlay;
}
:host(.overlay-hidden) {
@@ -47,7 +45,7 @@
}
:host(.alert-top) {
- @include padding(50px, null, null, null);
+ @include mixins.padding(50px, null, null, null);
align-items: flex-start;
}
@@ -69,17 +67,16 @@
contain: content;
opacity: 0;
- z-index: $z-index-overlay-wrapper;
}
.alert-title {
- @include margin(0);
- @include padding(0);
+ @include mixins.margin(0);
+ @include mixins.padding(0);
}
.alert-sub-title {
- @include margin(5px, 0, 0);
- @include padding(0);
+ @include mixins.margin(5px, 0, 0);
+ @include mixins.padding(0);
font-weight: normal;
}
@@ -140,7 +137,7 @@
}
.alert-input {
- @include padding(10px, 0);
+ @include mixins.padding(10px, 0);
width: 100%;
@@ -166,24 +163,19 @@
}
.alert-button {
- @include margin(0);
+ @include mixins.margin(0);
display: block;
border: 0;
- font-size: $alert-button-font-size;
+ font-size: font.dynamic-font(14px);
- line-height: $alert-button-line-height;
+ line-height: font.dynamic-font(20px);
z-index: 0;
}
-.alert-button.ion-focused,
-.alert-tappable.ion-focused {
- background: $background-color-step-100;
-}
-
.alert-button-inner {
display: flex;
@@ -209,8 +201,8 @@
}
.alert-tappable {
- @include margin(0);
- @include padding(0);
+ @include mixins.margin(0);
+ @include mixins.padding(0);
display: flex;
@@ -244,7 +236,7 @@
}
textarea.alert-input {
- min-height: $alert-input-min-height;
+ min-height: 37px;
resize: none;
}
@@ -262,6 +254,23 @@ textarea.alert-input {
flex: 1;
}
+.select-option-start,
+.select-option-end {
+ display: flex;
+
+ align-items: center;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-width: fit-content;
+}
+
.select-option-description {
display: block;
}
diff --git a/core/src/components/alert/alert.ionic.scss b/core/src/components/alert/alert.ionic.scss
index 3c54136b477..1ea7558dee1 100644
--- a/core/src/components/alert/alert.ionic.scss
+++ b/core/src/components/alert/alert.ionic.scss
@@ -1,10 +1,335 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
+@use "../../themes/mixins" as mixins;
@use "./alert.common";
-@use "./alert.md" as alert-md;
// Ionic Alert
// --------------------------------------------------
+:host {
+ --background: #{globals.$ion-bg-surface-default};
+ --max-width: #{globals.$ion-scale-7400};
+ --backdrop-opacity: 0.7;
+
+ z-index: 1001;
+}
+
+.alert-wrapper {
+ @include globals.border-radius(globals.$ion-border-radius-200);
+
+ box-shadow: globals.$ion-elevation-4;
+
+ z-index: 10;
+}
+
+.alert-button.ion-focused {
+ @include globals.focused-state(
+ globals.$ion-border-size-050,
+ globals.$ion-border-style-solid,
+ globals.$ion-border-focus-default
+ );
+}
+
+.alert-tappable.ion-focused {
+ @include globals.focused-state(
+ globals.$ion-border-size-050,
+ globals.$ion-border-style-solid,
+ globals.$ion-border-focus-default
+ );
+
+ outline-offset: calc(globals.$ion-border-size-050 * -1);
+}
+
+.alert-tappable.ion-activated,
+.alert-tappable:not(:disabled):hover,
+.alert-tappable[aria-checked="true"] {
+ background: globals.$ion-semantics-primary-100;
+}
+
+// Ionic Alert Header
+// --------------------------------------------------
+
+.alert-head {
+ @include mixins.padding(globals.$ion-space-400);
+
+ text-align: start;
+}
+
+.alert-title {
+ @include globals.typography(globals.$ion-heading-h6-medium);
+
+ color: globals.$ion-text-default;
+}
+
+.alert-sub-title {
+ @include globals.typography(globals.$ion-body-md-regular);
+
+ color: globals.$ion-text-subtlest;
+}
+
+// Ionic Alert Message
+// --------------------------------------------------
+
+.alert-message,
+.alert-input-group {
+ @include globals.typography(globals.$ion-body-md-regular);
+ @include mixins.padding(globals.$ion-space-400);
+
+ color: globals.$ion-text-default;
+}
+
+/**
+ * Ionic Alerts on tablets can expand vertically up to
+ * a total maximum height. We only want to set a max-height
+ * on mobile phones.
+ */
+@include globals.mobile-viewport() {
+ .alert-message {
+ max-height: globals.$ion-scale-6200;
+ }
+}
+
+.alert-message:empty {
+ @include mixins.padding(globals.$ion-space-0);
+}
+
+.alert-head + .alert-message {
+ padding-top: globals.$ion-space-0;
+}
+
+// Ionic Alert Input
+// --------------------------------------------------
+
+.alert-input {
+ @include mixins.margin(globals.$ion-space-150, 0);
+
+ border-bottom: globals.$ion-border-size-025 globals.$ion-border-style-solid globals.$ion-border-input-default;
+
+ color: globals.$ion-text-default;
+
+ &::placeholder {
+ color: globals.$ion-text-subtlest;
+
+ font-family: inherit;
+ font-weight: inherit;
+ }
+
+ &::-ms-clear {
+ display: none;
+ }
+}
+
+.alert-input:focus {
+ @include mixins.margin(null, null, globals.$ion-scale-100, null);
+
+ border-bottom: globals.$ion-border-size-050 globals.$ion-border-style-solid globals.$ion-border-focus-default;
+}
+
+// Ionic Alert Radio/Checkbox Group
+// --------------------------------------------------
+
+.alert-radio-group,
+.alert-checkbox-group {
+ position: relative;
+
+ border-top: globals.$ion-border-size-025 globals.$ion-border-style-solid globals.$ion-border-default;
+ border-bottom: globals.$ion-border-size-025 globals.$ion-border-style-solid globals.$ion-border-default;
+
+ overflow: auto;
+}
+
+/**
+ * Ionic Alerts on tablets can expand vertically up to
+ * a total maximum height. We only want to set a max-height
+ * on mobile phones.
+ */
+@include globals.mobile-viewport() {
+ .alert-radio-group,
+ .alert-checkbox-group {
+ max-height: globals.$ion-scale-6200;
+ }
+}
+
+.alert-tappable {
+ position: relative;
+
+ min-height: globals.$ion-scale-1200;
+}
+
+// Ionic Alert Radio
+// --------------------------------------------------
+
+.alert-radio-label {
+ @include globals.typography(globals.$ion-body-md-regular);
+ @include mixins.padding(
+ globals.$ion-space-300,
+ globals.$ion-space-700,
+ globals.$ion-space-300,
+ globals.$ion-scale-1400
+ );
+
+ flex: 1;
+
+ color: globals.$ion-text-default;
+}
+
+// Ionic Alert Radio Outer Circle: Unchecked
+// ---------------------------------------------------
+
+.alert-radio-icon {
+ @include globals.position(globals.$ion-space-0, null, null, globals.$ion-space-700);
+ @include globals.border-radius(globals.$ion-border-radius-full);
+
+ display: flex;
+ position: relative;
+
+ align-items: center;
+ justify-content: center;
+
+ width: globals.$ion-scale-600;
+ height: globals.$ion-scale-600;
+
+ border-width: globals.$ion-border-size-025;
+ border-style: globals.$ion-border-style-solid;
+ border-color: globals.$ion-border-input-default;
+
+ background-color: globals.$ion-bg-input-default;
+
+ box-sizing: border-box;
+}
+
+// Ionic Alert Radio Inner Dot
+// ---------------------------------------------------
+
+.alert-radio-inner {
+ @include globals.border-radius(50%);
+
+ width: calc(32% + globals.$ion-border-size-025);
+ height: calc(32% + globals.$ion-border-size-025);
+
+ background-color: globals.$ion-bg-surface-inverse;
+
+ box-sizing: border-box;
+}
+
+// Ionic Alert Radio Outer Circle: Checked
+// ---------------------------------------------------
+
+[aria-checked="true"] .alert-radio-icon {
+ border-color: globals.$ion-bg-primary-base-default;
+
+ background-color: globals.$ion-bg-primary-base-default;
+}
+
+// Ionic Alert Checkbox Label
+// --------------------------------------------------
+
+.alert-checkbox-label {
+ @include globals.typography(globals.$ion-body-md-regular);
+ @include mixins.padding(
+ globals.$ion-space-300,
+ globals.$ion-space-700,
+ globals.$ion-space-300,
+ globals.$ion-scale-1400
+ );
+
+ flex: 1;
+
+ // Required for the checkbox icon to stay on the screen without
+ // being squished when the font size scales up.
+ width: calc(100% - globals.$ion-scale-1400);
+
+ color: globals.$ion-text-default;
+}
+
+// Ionic Alert Checkbox Outline: Unchecked
+// --------------------------------------------------
+
+.alert-checkbox-icon {
+ @include globals.position(globals.$ion-space-0, null, null, globals.$ion-space-700);
+ @include globals.border-radius(globals.$ion-border-radius-100);
+
+ display: flex;
+ position: relative;
+
+ align-items: center;
+ justify-content: center;
+
+ width: globals.$ion-scale-600;
+ height: globals.$ion-scale-600;
+
+ border-width: globals.$ion-border-size-025;
+ border-style: globals.$ion-border-style-solid;
+ border-color: globals.$ion-primitives-neutral-800;
+
+ background-color: globals.$ion-bg-input-default;
+
+ box-sizing: border-box;
+}
+
+.alert-checkbox-inner {
+ width: globals.$ion-scale-400;
+ height: globals.$ion-scale-400;
+}
+
+.alert-checkbox-inner path {
+ fill: globals.$ion-bg-surface-default;
+}
+
+// Ionic Alert Checkbox Checkmark: Checked
+// --------------------------------------------------
+
+[aria-checked="true"] .alert-checkbox-icon {
+ border-color: globals.$ion-semantics-primary-base;
+
+ background-color: globals.$ion-bg-primary-base-default;
+}
+
+// Ionic Alert Button
+// --------------------------------------------------
+
+.alert-button-group {
+ @include mixins.padding(8px);
+
+ box-sizing: border-box;
+
+ flex-wrap: wrap-reverse;
+ justify-content: flex-end;
+}
+
+.alert-button {
+ @include globals.border-radius(globals.$ion-border-size-050);
+ @include mixins.margin(globals.$ion-space-0, globals.$ion-space-200, globals.$ion-space-0, globals.$ion-space-0);
+ @include mixins.padding(globals.$ion-space-250);
+
+ // necessary for ripple to work properly
+ position: relative;
+
+ background-color: transparent;
+ color: globals.ion-color(primary, base);
+
+ font-weight: globals.$ion-font-weight-medium;
+
+ text-align: end;
+ text-transform: uppercase;
+
+ overflow: hidden;
+}
+
+.alert-button-inner {
+ justify-content: flex-end;
+}
+
+/**
+ * Ionic alerts should scale up to 560px x 560px
+ * on tablet dimensions.
+ */
+@include globals.tablet-viewport() {
+ :host {
+ --max-width: #{min(calc(100vw - 96px), 560px)};
+ --max-height: #{min(calc(100vh - 96px), 560px)};
+ }
+}
+
// Alert: Select Option
// --------------------------------------------------
@@ -13,9 +338,24 @@
gap: globals.$ion-space-300;
}
+.select-option-start,
+.select-option-end {
+ gap: globals.$ion-space-200;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: globals.$ion-scale-1200;
+}
+
.select-option-description {
@include globals.typography(globals.$ion-body-md-regular);
- @include globals.padding(0);
+ @include mixins.padding(globals.$ion-space-0);
color: globals.$ion-text-subtle;
diff --git a/core/src/components/alert/alert.ios.scss b/core/src/components/alert/alert.ios.scss
index 2671dc0940b..a0457a76564 100644
--- a/core/src/components/alert/alert.ios.scss
+++ b/core/src/components/alert/alert.ios.scss
@@ -352,7 +352,7 @@
background-color: $alert-ios-button-background-color-activated;
}
-// iOS Action Sheet Button: Destructive
+// iOS Alert Button: Destructive
// ---------------------------------------------------
.alert-button-role-destructive,
@@ -360,3 +360,16 @@
.alert-button-role-destructive.ion-focused {
color: $alert-ios-button-destructive-text-color;
}
+
+// Alert: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: $alert-ios-select-option-slot-size;
+}
diff --git a/core/src/components/alert/alert.ios.vars.scss b/core/src/components/alert/alert.ios.vars.scss
index 2cb4fca98af..c1d0539278c 100644
--- a/core/src/components/alert/alert.ios.vars.scss
+++ b/core/src/components/alert/alert.ios.vars.scss
@@ -316,3 +316,6 @@ $alert-ios-translucent-filter: saturate(180%) blur(20px);
/// @prop - Height of the tappable inputs in the checkbox alert
$alert-ios-tappable-height: $item-ios-min-height;
+
+/// @prop - Maximum size of slotted children rendered in a select option's start/end slot
+$alert-ios-select-option-slot-size: dynamic-font-max(24px, 2);
diff --git a/core/src/components/alert/alert.md.scss b/core/src/components/alert/alert.md.scss
index 2fbd0fd8775..68f0b08323b 100644
--- a/core/src/components/alert/alert.md.scss
+++ b/core/src/components/alert/alert.md.scss
@@ -345,3 +345,16 @@
--max-height: #{$alert-md-max-height-tablet};
}
}
+
+// Alert: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: 24px;
+}
diff --git a/core/src/components/alert/alert.native.scss b/core/src/components/alert/alert.native.scss
index e2d5a87b8a5..d7cf5c086d3 100644
--- a/core/src/components/alert/alert.native.scss
+++ b/core/src/components/alert/alert.native.scss
@@ -1,4 +1,4 @@
-@use "../../themes/native/native.theme.default" as native;
+@use "../../themes/native/native.globals" as native;
@use "../../themes/mixins" as mixins;
@use "../../themes/functions.font" as font;
@use "./alert.common";
@@ -6,11 +6,34 @@
// Alert: Native
// --------------------------------------------------
+:host {
+ font-family: native.$font-family-base;
+
+ z-index: native.$z-index-overlay;
+}
+
+.alert-wrapper {
+ z-index: native.$z-index-overlay-wrapper;
+}
+
+.alert-button.ion-focused,
+.alert-tappable.ion-focused {
+ background: native.$background-color-step-100;
+}
+
+// Alert: Select Option
+// --------------------------------------------------
+
.alert-radio-label,
.alert-checkbox-label {
gap: 12px;
}
+.select-option-start,
+.select-option-end {
+ gap: 8px;
+}
+
.select-option-description {
@include mixins.padding(5px, 0, 0, 0);
diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx
index db9ed59c9ce..ee6d2d89bd7 100644
--- a/core/src/components/alert/alert.tsx
+++ b/core/src/components/alert/alert.tsx
@@ -596,13 +596,20 @@ export class Alert implements ComponentInterface, OverlayInterface {
'alert-tappable': true,
'alert-checkbox': true,
'alert-checkbox-button': true,
- 'ion-focusable': true,
+ 'ion-focusable': !i.disabled,
+ 'ion-activatable': !i.disabled,
'alert-checkbox-button-disabled': i.disabled || false,
}}
>
@@ -652,7 +659,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
'alert-radio-button': true,
'alert-tappable': true,
'alert-radio': true,
- 'ion-focusable': true,
+ 'ion-focusable': !i.disabled,
+ 'ion-activatable': !i.disabled,
'alert-radio-button-disabled': i.disabled || false,
}}
role="radio"
diff --git a/core/src/components/alert/alert.vars.scss b/core/src/components/alert/alert.vars.scss
deleted file mode 100644
index 7502383bd3b..00000000000
--- a/core/src/components/alert/alert.vars.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-@import "../../themes/native/native.globals";
-
-// Alert
-// --------------------------------------------------
-
-/// @prop - Minimum width of the alert
-$alert-min-width: 250px;
-
-/// @prop - Maximum height of the alert
-$alert-max-height: 90%;
-
-/// @prop - Line height of the alert button
-$alert-button-line-height: dynamic-font(20px);
-
-/// @prop - Font size of the alert button
-$alert-button-font-size: dynamic-font(14px);
-
-/// @prop - Minimum height of a textarea in the alert
-$alert-input-min-height: 37px;
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts b/core/src/components/alert/test/basic/alert.e2e.ts
index 92b6c5572a4..f415490db5c 100644
--- a/core/src/components/alert/test/basic/alert.e2e.ts
+++ b/core/src/components/alert/test/basic/alert.e2e.ts
@@ -56,7 +56,7 @@ configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
});
});
-configs().forEach(({ config, screenshot, title }) => {
+configs({ modes: ['md', 'ios', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('should not have visual regressions'), () => {
let alertFixture!: AlertFixture;
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..61b2d4e7be2
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..b6c7f32c98e
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..a13ebe87bde
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..71b316b4d1f
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..aa4655f802f
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..357228b20ea
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-basic-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..6a12b3caba2
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..a3f4af5c8b3
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..3ef774873a1
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..969d949fedc
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..2b2ab74ac3e
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..018e608ff8b
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-checkbox-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..d5549e112d9
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..2d53b39f608
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f5f303b74d6
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..1085905a0cc
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..6d1e3f99f1e
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..8e51217d351
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-confirm-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..17203ef17d3
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..729513de92e
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..7cbcd8318b5
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..590fd979877
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..3f5671659ed
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..4fa83c5a0e9
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-longMessage-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..66b6636bd89
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..0b6221a0fec
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..6b7f9bd4906
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..ce173b36579
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..7cd2ed8d19e
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..4ad81681811
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-noMessage-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..ab34389351b
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..d3cf22a698d
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..b57210280e9
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..0f749428667
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..c02487ad5f8
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..dc3400906fb
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-prompt-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..fcff62675c9
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..00a68be5363
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..c4cc2fa0541
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..ef92eb585d2
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..5061204b95c
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..8f0131b2a45
Binary files /dev/null and b/core/src/components/alert/test/basic/alert.e2e.ts-snapshots/alert-radio-ionic-md-rtl-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts b/core/src/components/alert/test/states/alert.e2e.ts
new file mode 100644
index 00000000000..95f51290a51
--- /dev/null
+++ b/core/src/components/alert/test/states/alert.e2e.ts
@@ -0,0 +1,60 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * This behavior does not vary across directions.
+ */
+configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
+ test.describe(title('alert: input states'), () => {
+ /**
+ * `(any-hover: hover)` evaluates to "none" in all three mobile-emulated
+ * projects, suppressing the hover rules:
+ *
+ * - Chromium and WebKit suppress it because of hasTouch and isMobile.
+ * - Headless Firefox doesn't detect input devices and reports no hover
+ * capability regardless of context options, so override it via
+ * launchOptions.firefoxUserPrefs. Bit values: 4 = FINE (mouse),
+ * 8 = HOVER, 12 = FINE | HOVER.
+ *
+ * Viewport, userAgent, and scale factor remain mobile-sized.
+ */
+ test.use({
+ hasTouch: false,
+ isMobile: false,
+ });
+
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`/src/components/alert/test/states`, config);
+ });
+
+ test('should render all radio states', async ({ page }) => {
+ const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
+ await page.locator('#radio').click();
+ await ionAlertDidPresent.next();
+
+ const alert = page.locator('ion-alert');
+ await expect(alert).toBeVisible();
+
+ const defaultRadio = alert.locator('button.alert-radio-button').first();
+ await defaultRadio.hover();
+
+ await expect(alert).toHaveScreenshot(screenshot('alert-radio-states'));
+ });
+
+ test('should render all checkbox states', async ({ page }) => {
+ const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
+ await page.locator('#checkbox').click();
+ await ionAlertDidPresent.next();
+
+ const alert = page.locator('ion-alert');
+ await expect(alert).toBeVisible();
+
+ const defaultCheckbox = alert.locator('button.alert-checkbox-button').first();
+ await defaultCheckbox.hover();
+
+ await page.waitForChanges();
+
+ await expect(alert).toHaveScreenshot(screenshot('alert-checkbox-states'));
+ });
+ });
+});
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..4dac73cddb0
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..f2d0560630d
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..9f0c497aeb7
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..893fcca3859
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..cd1503d5798
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..a5cbd2c0d6c
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..e5b8db5b3c6
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..b31df9f6116
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..d421d107b1f
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-checkbox-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..fb57f17ca0e
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..d02d4300f38
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..a57d4ba9b1b
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..0f564f091b2
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..e6ebfbdee08
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f8b5fed8ed7
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..2f403c2d82a
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..1282255ce53
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f66d20e96a2
Binary files /dev/null and b/core/src/components/alert/test/states/alert.e2e.ts-snapshots/alert-radio-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/alert/test/states/index.html b/core/src/components/alert/test/states/index.html
new file mode 100644
index 00000000000..4591962127a
--- /dev/null
+++ b/core/src/components/alert/test/states/index.html
@@ -0,0 +1,159 @@
+
+
+
+
+ Alert - States
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Alert - States
+
+
+
+
+
+
+
+
+
+
+
+
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 5769b2e860e..09c93ab3ca7 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 9a1dbee2541..ae012f8623d 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 b56f1c0c9fc..17d0aa4051b 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/item/item.ionic.scss b/core/src/components/item/item.ionic.scss
index 665f5346c69..107480559a0 100644
--- a/core/src/components/item/item.ionic.scss
+++ b/core/src/components/item/item.ionic.scss
@@ -6,7 +6,8 @@
:host {
--background: #{globals.$ion-bg-surface-default};
- --background-activated: #{globals.$ion-bg-select-default};
+ --background-activated: #{globals.$ion-bg-neutral-subtlest-press};
+ --background-activated-opacity: 1;
--border-color: #{globals.$ion-primitives-neutral-300};
--border-style: #{globals.$ion-border-style-solid};
--border-width: #{0 0 globals.$ion-border-size-025 0};
@@ -62,24 +63,20 @@ slot[name="end"]::slotted(*) {
@include globals.disabled-state();
}
-// Item: Activated
-// --------------------------------------------------
-
-:host(.ion-activated) .item-native {
- background: var(--background-activated);
-}
-
// Item: Focused
// --------------------------------------------------
:host(.ion-focused) .item-native::after {
@include globals.border-radius(inherit);
@include globals.position(0, 0, 0, 0);
+ @include globals.focused-state(
+ globals.$ion-border-size-050,
+ globals.$ion-border-style-solid,
+ globals.$ion-border-focus-default
+ );
position: absolute;
- border-width: globals.$ion-border-size-050;
- border-style: globals.$ion-border-style-solid;
- border-color: globals.$ion-border-focus-default;
+ outline-offset: calc(globals.$ion-border-size-050 * -1);
content: "";
}
@@ -111,14 +108,3 @@ slot[name="end"]::slotted(*) {
:host(.item-lines-none) {
--inner-border-width: #{globals.$ion-border-size-0};
}
-
-// Item in Select Modal
-// --------------------------------------------------
-:host(.in-select-modal) {
- --background-focused: #{globals.$ion-bg-neutral-subtlest-press};
- --background-focused-opacity: 0;
-}
-
-:host(.in-select-modal.ion-focused) .item-native {
- --border-radius: #{globals.$ion-border-radius-400};
-}
diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx
index 65d5d949260..3af534b4d50 100644
--- a/core/src/components/item/item.tsx
+++ b/core/src/components/item/item.tsx
@@ -415,7 +415,6 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
'item-control-needs-pointer-cursor': firstInteractiveNeedsPointerCursor,
'item-disabled': disabled,
'in-list': inList,
- 'in-select-modal': hostContext('ion-select-modal', this.el),
'item-multiple-inputs': this.multipleInputs,
'ion-activatable': canActivate,
'ion-focusable': this.focusable,
diff --git a/core/src/components/select-modal/select-modal.common.scss b/core/src/components/select-modal/select-modal.common.scss
index 3bbb48b557d..ec528caf337 100644
--- a/core/src/components/select-modal/select-modal.common.scss
+++ b/core/src/components/select-modal/select-modal.common.scss
@@ -14,6 +14,36 @@
align-items: center;
}
+ion-radio.select-option-has-rich-content::part(label),
+ion-checkbox.select-option-has-rich-content::part(label),
+.select-option-content {
+ flex: 1;
+
+ /**
+ * Let rich content wrap instead of inheriting the label part's
+ * single-line truncation, so arbitrary slotted elements render
+ * without clipping.
+ */
+ white-space: normal;
+}
+
+.select-option-start,
+.select-option-end {
+ display: flex;
+
+ align-items: center;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-width: fit-content;
+}
+
.select-option-description {
display: block;
}
diff --git a/core/src/components/select-modal/select-modal.ionic.scss b/core/src/components/select-modal/select-modal.ionic.scss
index ca137a075d3..7c22f663b06 100644
--- a/core/src/components/select-modal/select-modal.ionic.scss
+++ b/core/src/components/select-modal/select-modal.ionic.scss
@@ -12,7 +12,14 @@
// ----------------------------------------------------------------
ion-item {
- --border-width: 0;
+ --border-width: #{globals.$ion-border-size-0};
+ --background-focused: transparent;
+ --background-focused-opacity: 1;
+ --background-hover: #{globals.$ion-semantics-primary-100};
+ --background-hover-opacity: 1;
+ --background-activated: #{globals.$ion-semantics-primary-100};
+ --background-activated-opacity: 1;
+ --border-radius: #{globals.$ion-border-radius-400};
}
ion-item.ion-focused::part(native)::after {
@@ -20,6 +27,12 @@ ion-item.ion-focused::part(native)::after {
border: none;
}
+ion-item.ion-focused.item-checkbox-checked,
+ion-item.ion-focused.item-radio-checked {
+ --background-focused: #{globals.$ion-semantics-primary-100};
+ --background-focused-opacity: 1;
+}
+
// Toolbar
// ----------------------------------------------------------------
@@ -35,7 +48,7 @@ ion-list ion-radio::part(container) {
}
ion-list ion-radio::part(label) {
- @include globals.margin(0);
+ @include globals.margin(globals.$ion-space-0);
}
// Radio and Checkbox: Label
@@ -52,7 +65,6 @@ ion-list ion-checkbox::part(label) {
.item-radio-checked,
.item-checkbox-checked {
--background: #{globals.$ion-semantics-primary-100};
- --border-radius: #{globals.$ion-border-radius-400};
}
// Content
@@ -71,11 +83,6 @@ ion-content {
--padding-end: #{globals.$ion-space-400} !important;
/* stylelint-disable-next-line declaration-no-important */
--padding-bottom: #{globals.$ion-space-1200} !important;
-
- // Set the background to the focused element within a radio group only when there is a checked radio
- &:has(.radio-checked) .ion-focused:not(.item-radio-checked) {
- --background-focused-opacity: 1;
- }
}
// Select Modal: Select Option
@@ -85,6 +92,21 @@ ion-content {
gap: globals.$ion-space-300;
}
+.select-option-start,
+.select-option-end {
+ gap: globals.$ion-space-200;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: globals.$ion-scale-1200;
+}
+
.select-option-description {
@include globals.typography(globals.$ion-body-md-regular);
diff --git a/core/src/components/select-modal/select-modal.ios.scss b/core/src/components/select-modal/select-modal.ios.scss
index abac9c8220b..6fe502d5eb3 100644
--- a/core/src/components/select-modal/select-modal.ios.scss
+++ b/core/src/components/select-modal/select-modal.ios.scss
@@ -22,3 +22,16 @@ ion-radio::after {
content: "";
}
+
+// Select Modal: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: dynamic-font-max(24px, 2);
+}
diff --git a/core/src/components/select-modal/select-modal.md.scss b/core/src/components/select-modal/select-modal.md.scss
index 260f6aba5be..919390f9c01 100644
--- a/core/src/components/select-modal/select-modal.md.scss
+++ b/core/src/components/select-modal/select-modal.md.scss
@@ -28,3 +28,16 @@ ion-item {
--background-hover: #{$item-md-color};
--color: #{ion-color(primary, base)};
}
+
+// Select Modal: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: 24px;
+}
diff --git a/core/src/components/select-modal/select-modal.native.scss b/core/src/components/select-modal/select-modal.native.scss
index 29b81819fcf..e844a3cb15f 100644
--- a/core/src/components/select-modal/select-modal.native.scss
+++ b/core/src/components/select-modal/select-modal.native.scss
@@ -6,10 +6,18 @@
// Select Modal: Native
// --------------------------------------------------
+// Select Modal: Select Option
+// --------------------------------------------------
+
.select-option-label {
gap: 12px;
}
+.select-option-start,
+.select-option-end {
+ gap: 8px;
+}
+
.select-option-description {
@include mixins.padding(5px, 0, 0, 0);
diff --git a/core/src/components/select-modal/select-modal.tsx b/core/src/components/select-modal/select-modal.tsx
index ca21e73a1f0..80a4717e1d1 100644
--- a/core/src/components/select-modal/select-modal.tsx
+++ b/core/src/components/select-modal/select-modal.tsx
@@ -49,7 +49,11 @@ export class SelectModal implements ComponentInterface {
@Prop() options: SelectModalOption[] = [];
- private closeModal() {
+ private closeModal(isOptionDisabled = false) {
+ if (isOptionDisabled) {
+ return;
+ }
+
const modal = this.el.closest('ion-modal');
if (modal) {
@@ -127,6 +131,7 @@ export class SelectModal implements ComponentInterface {
* part of the public `SelectModalOption` interface.
*/
const richOption = option as SelectOverlayOption;
+ const hasRichContent = !!richOption.startContent || !!richOption.endContent || !!richOption.description;
const optionLabelOptions = {
id: `modal-option-${index}`,
label: richOption.text,
@@ -138,6 +143,8 @@ export class SelectModal implements ComponentInterface {
return (
this.closeModal()}
+ onClick={() => this.closeModal(option.disabled)}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && !ev.repeat) {
this.pendingEnterTarget = ev.currentTarget as HTMLElement;
@@ -158,12 +168,12 @@ export class SelectModal implements ComponentInterface {
onKeyUp={(ev) => {
if (ev.key === ' ') {
// Space selects and dismisses in one press.
- this.closeModal();
+ this.closeModal(option.disabled);
} else if (ev.key === 'Enter') {
const shouldClose = this.pendingEnterTarget === ev.currentTarget;
this.pendingEnterTarget = null;
if (shouldClose) {
- this.closeModal();
+ this.closeModal(option.disabled);
}
}
}}
@@ -186,6 +196,7 @@ export class SelectModal implements ComponentInterface {
* part of the public `SelectModalOption` interface.
*/
const richOption = option as SelectOverlayOption;
+ const hasRichContent = !!richOption.startContent || !!richOption.endContent || !!richOption.description;
const optionLabelOptions = {
id: `modal-option-${index}`,
label: richOption.text,
@@ -196,6 +207,8 @@ export class SelectModal implements ComponentInterface {
return (
- Select - Modal
+ Select Modal - Basic
-
+
Cancel Text (default)
-
+
diff --git a/core/src/components/select-modal/test/states/index.html b/core/src/components/select-modal/test/states/index.html
new file mode 100644
index 00000000000..5bd4553a7c9
--- /dev/null
+++ b/core/src/components/select-modal/test/states/index.html
@@ -0,0 +1,106 @@
+
+
+
+
+ Select Modal - States
+
+
+
+
+
+
+
+
+
+
+
+
+ Select Modal - States
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts b/core/src/components/select-modal/test/states/select-modal.e2e.ts
new file mode 100644
index 00000000000..6a32f78172b
--- /dev/null
+++ b/core/src/components/select-modal/test/states/select-modal.e2e.ts
@@ -0,0 +1,80 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * This behavior does not vary across directions.
+ */
+configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
+ test.describe(title('select-modal: states'), () => {
+ /**
+ * `(any-hover: hover)` evaluates to "none" in all three mobile-emulated
+ * projects, suppressing the hover rules:
+ *
+ * - Chromium and WebKit suppress it because of hasTouch and isMobile.
+ * - Headless Firefox doesn't detect input devices and reports no hover
+ * capability regardless of context options, so override it via
+ * launchOptions.firefoxUserPrefs. Bit values: 4 = FINE (mouse),
+ * 8 = HOVER, 12 = FINE | HOVER.
+ *
+ * Viewport, userAgent, and scale factor remain mobile-sized.
+ */
+ test.use({
+ hasTouch: false,
+ isMobile: false,
+ });
+
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`/src/components/select-modal/test/states`, config);
+ });
+
+ test('should render all radio states', async ({ page }) => {
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+ await page.locator('#single').click();
+ await ionModalDidPresent.next();
+
+ const modal = page.locator('#modal-single');
+ const selectModal = modal.locator('ion-select-modal');
+ await expect(selectModal).toBeVisible();
+
+ /**
+ * After clicking the trigger button, the cursor sits at the
+ * button's screen coordinates — which may coincide with the
+ * "Default" row once the modal opens, depending on mode/viewport.
+ * Without a transition, `mouseenter` doesn't fire and the JS-driven
+ * label swap never runs, causing inconsistent hover states in the
+ * screenshots.
+ */
+ await page.mouse.move(0, 0);
+
+ const defaultRow = selectModal.locator('ion-item').first();
+ await defaultRow.hover();
+
+ await expect(selectModal).toHaveScreenshot(screenshot('select-modal-radio-states'));
+ });
+
+ test('should render all checkbox states', async ({ page }) => {
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+ await page.locator('#multiple').click();
+ await ionModalDidPresent.next();
+
+ const modal = page.locator('#modal-multiple');
+ const selectModal = modal.locator('ion-select-modal');
+ await expect(selectModal).toBeVisible();
+
+ /**
+ * After clicking the trigger button, the cursor sits at the
+ * button's screen coordinates — which may coincide with the
+ * "Default" row once the modal opens, depending on mode/viewport.
+ * Without a transition, `mouseenter` doesn't fire and the JS-driven
+ * label swap never runs, causing inconsistent hover states in the
+ * screenshots.
+ */
+ await page.mouse.move(0, 0);
+
+ const defaultRow = selectModal.locator('ion-item').first();
+ await defaultRow.hover();
+
+ await expect(selectModal).toHaveScreenshot(screenshot('select-modal-checkbox-states'));
+ });
+ });
+});
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..0a6929df656
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..655a81fcba3
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..23e07fab5b8
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..ea4e6da6642
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..1a4744a1dc9
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..c004e0b506d
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..1a5c734ed99
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..4dd93626894
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..0f8d0ccb367
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-checkbox-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..126d833c4a8
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..9fa3e299445
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f3746ce72f7
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..641cbfdf3c7
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..9eec3e12894
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..61b415935e0
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..03dabf1f699
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..5e135f73220
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..9483615da50
Binary files /dev/null and b/core/src/components/select-modal/test/states/select-modal.e2e.ts-snapshots/select-modal-radio-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/select-popover.common.scss b/core/src/components/select-popover/select-popover.common.scss
index 095b6660f35..8fa81cc75e3 100644
--- a/core/src/components/select-popover/select-popover.common.scss
+++ b/core/src/components/select-popover/select-popover.common.scss
@@ -1,15 +1,15 @@
-@import "../../themes/native/native.globals";
+@use "../../themes/mixins" as mixins;
// Select Popover
// --------------------------------------------------
:host ion-list {
- @include margin(0);
+ @include mixins.margin(0);
}
ion-list-header,
ion-label {
- @include margin(0);
+ @include mixins.margin(0);
}
/**
@@ -37,6 +37,36 @@ ion-label {
flex-wrap: wrap;
}
+ion-radio.select-option-has-rich-content::part(label),
+ion-checkbox.select-option-has-rich-content::part(label),
+.select-option-content {
+ flex: 1;
+
+ /**
+ * Let rich content wrap instead of inheriting the label part's
+ * single-line truncation, so arbitrary slotted elements render
+ * without clipping.
+ */
+ white-space: normal;
+}
+
+.select-option-start,
+.select-option-end {
+ display: flex;
+
+ align-items: center;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-width: fit-content;
+}
+
.select-option-description {
display: block;
}
diff --git a/core/src/components/select-popover/select-popover.ionic.scss b/core/src/components/select-popover/select-popover.ionic.scss
index 1813794975d..0a117793383 100644
--- a/core/src/components/select-popover/select-popover.ionic.scss
+++ b/core/src/components/select-popover/select-popover.ionic.scss
@@ -1,10 +1,53 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
+@use "../../themes/mixins" as mixins;
@use "./select-popover.common";
-@use "./select-popover.md" as select-popover-md;
// Ionic Select Popover
// --------------------------------------------------
+ion-item {
+ --border-width: #{globals.$ion-border-size-0};
+ --background-focused: transparent;
+ --background-focused-opacity: 1;
+ --background-hover: #{globals.$ion-semantics-primary-100};
+ --background-hover-opacity: 1;
+ --background-activated: #{globals.$ion-semantics-primary-100};
+ --background-activated-opacity: 1;
+}
+
+ion-item.ion-focused.item-checkbox-checked,
+ion-item.ion-focused.item-radio-checked {
+ --background-focused: #{globals.$ion-semantics-primary-100};
+ --background-focused-opacity: 1;
+}
+
+// Radio
+// ----------------------------------------------------------------
+
+ion-list ion-radio::part(container) {
+ display: none;
+}
+
+ion-list ion-radio::part(label) {
+ @include mixins.margin(globals.$ion-space-0);
+}
+
+// Radio and Checkbox: Label
+// ----------------------------------------------------------------
+
+ion-list ion-radio::part(label),
+ion-list ion-checkbox::part(label) {
+ @include globals.typography(globals.$ion-body-lg-medium);
+}
+
+// Radio and Checkbox: Checked
+// ----------------------------------------------------------------
+
+.item-radio-checked,
+.item-checkbox-checked {
+ --background: #{globals.$ion-semantics-primary-100};
+}
+
// Select Popover: Select Option
// --------------------------------------------------
@@ -12,9 +55,24 @@
gap: globals.$ion-space-300;
}
+.select-option-start,
+.select-option-end {
+ gap: globals.$ion-space-200;
+}
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: globals.$ion-scale-1200;
+}
+
.select-option-description {
@include globals.typography(globals.$ion-body-md-regular);
- @include globals.padding(0);
+ @include mixins.padding(globals.$ion-space-0);
color: globals.$ion-text-subtle;
diff --git a/core/src/components/select-popover/select-popover.ios.scss b/core/src/components/select-popover/select-popover.ios.scss
index de3cfea6135..50f909d94ec 100644
--- a/core/src/components/select-popover/select-popover.ios.scss
+++ b/core/src/components/select-popover/select-popover.ios.scss
@@ -1,2 +1,15 @@
@import "./select-popover.native";
@import "./select-popover.ios.vars";
+
+// Select Popover: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: $select-popover-ios-select-option-slot-size;
+}
diff --git a/core/src/components/select-popover/select-popover.ios.vars.scss b/core/src/components/select-popover/select-popover.ios.vars.scss
index 188e3f5f97b..c45d4804054 100644
--- a/core/src/components/select-popover/select-popover.ios.vars.scss
+++ b/core/src/components/select-popover/select-popover.ios.vars.scss
@@ -3,3 +3,6 @@
// iOS Select Popover
// --------------------------------------------------
+
+/// @prop - Maximum size of slotted children rendered in a select option's start/end slot
+$select-popover-ios-select-option-slot-size: dynamic-font-max(24px, 2);
diff --git a/core/src/components/select-popover/select-popover.md.scss b/core/src/components/select-popover/select-popover.md.scss
index c7728bcaf04..d65c3cf4e34 100644
--- a/core/src/components/select-popover/select-popover.md.scss
+++ b/core/src/components/select-popover/select-popover.md.scss
@@ -27,3 +27,16 @@ ion-item {
--background-hover: #{$item-md-color};
--color: #{ion-color(primary, base)};
}
+
+// Select Popover: Select Option
+// --------------------------------------------------
+
+/**
+ * Cap slotted children so they can't stretch the option
+ * row out of proportion, keeping rows visually uniform
+ * regardless of the content.
+ */
+.select-option-start > *,
+.select-option-end > * {
+ max-height: 24px;
+}
diff --git a/core/src/components/select-popover/select-popover.native.scss b/core/src/components/select-popover/select-popover.native.scss
index 0b52fafe932..f93ea9ecbcb 100644
--- a/core/src/components/select-popover/select-popover.native.scss
+++ b/core/src/components/select-popover/select-popover.native.scss
@@ -6,10 +6,18 @@
// Select Popover: Native
// --------------------------------------------------
+// Select Popover: Select Option
+// --------------------------------------------------
+
.select-option-label {
gap: 12px;
}
+.select-option-start,
+.select-option-end {
+ gap: 8px;
+}
+
.select-option-description {
@include mixins.padding(5px, 0, 0, 0);
diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx
index b7f0f9bb839..4b8b2d42be4 100644
--- a/core/src/components/select-popover/select-popover.tsx
+++ b/core/src/components/select-popover/select-popover.tsx
@@ -82,7 +82,11 @@ export class SelectPopover implements ComponentInterface {
* Dismisses the host popover that the `ion-select-popover`
* is rendered within.
*/
- private dismissParentPopover() {
+ private dismissParentPopover(isOptionDisabled = false) {
+ if (isOptionDisabled) {
+ return;
+ }
+
const popover = this.el.closest('ion-popover');
if (popover) {
popover.dismiss();
@@ -135,6 +139,7 @@ export class SelectPopover implements ComponentInterface {
* part of the public `SelectPopoverOption` interface.
*/
const richOption = option as SelectOverlayOption;
+ const hasRichContent = !!richOption.startContent || !!richOption.endContent || !!richOption.description;
const optionLabelOptions = {
id: `popover-option-${index}`,
label: richOption.text,
@@ -145,6 +150,8 @@ export class SelectPopover implements ComponentInterface {
return (
this.dismissParentPopover()}
+ onClick={() => this.dismissParentPopover(option.disabled)}
onKeyDown={(ev) => {
if (ev.key === 'Enter' && !ev.repeat) {
this.pendingEnterTarget = ev.currentTarget as HTMLElement;
@@ -212,12 +228,12 @@ export class SelectPopover implements ComponentInterface {
onKeyUp={(ev) => {
if (ev.key === ' ') {
// Space selects and dismisses in one press.
- this.dismissParentPopover();
+ this.dismissParentPopover(option.disabled);
} else if (ev.key === 'Enter') {
const shouldDismiss = this.pendingEnterTarget === ev.currentTarget;
this.pendingEnterTarget = null;
if (shouldDismiss) {
- this.dismissParentPopover();
+ this.dismissParentPopover(option.disabled);
}
}
}}
diff --git a/core/src/components/select-popover/test/basic/index.html b/core/src/components/select-popover/test/basic/index.html
index 69b0e78ceba..679ec678d2c 100644
--- a/core/src/components/select-popover/test/basic/index.html
+++ b/core/src/components/select-popover/test/basic/index.html
@@ -2,7 +2,7 @@
- Select - Popover
+ Select Popover - Basic
+
+
+
+ Select Popover - States
+
+
+
+
+
+
+
+
+
+
+
+
+ Select Popover - States
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts b/core/src/components/select-popover/test/states/select-popover.e2e.ts
new file mode 100644
index 00000000000..6963342d39c
--- /dev/null
+++ b/core/src/components/select-popover/test/states/select-popover.e2e.ts
@@ -0,0 +1,60 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * This behavior does not vary across directions.
+ */
+configs({ directions: ['ltr'], modes: ['ios', 'md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
+ test.describe(title('select-popover: states'), () => {
+ /**
+ * `(any-hover: hover)` evaluates to "none" in all three mobile-emulated
+ * projects, suppressing the hover rules:
+ *
+ * - Chromium and WebKit suppress it because of hasTouch and isMobile.
+ * - Headless Firefox doesn't detect input devices and reports no hover
+ * capability regardless of context options, so override it via
+ * launchOptions.firefoxUserPrefs. Bit values: 4 = FINE (mouse),
+ * 8 = HOVER, 12 = FINE | HOVER.
+ *
+ * Viewport, userAgent, and scale factor remain mobile-sized.
+ */
+ test.use({
+ hasTouch: false,
+ isMobile: false,
+ });
+
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`/src/components/select-popover/test/states`, config);
+ });
+
+ test('should render all radio states', async ({ page }) => {
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
+ await page.locator('#single').click();
+ await ionPopoverDidPresent.next();
+
+ const popover = page.locator('#popover-single');
+ const selectPopover = popover.locator('ion-select-popover');
+ await expect(selectPopover).toBeVisible();
+
+ const defaultRow = selectPopover.locator('ion-item').first();
+ await defaultRow.hover();
+
+ await expect(selectPopover).toHaveScreenshot(screenshot('select-popover-radio-states'));
+ });
+
+ test('should render all checkbox states', async ({ page }) => {
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
+ await page.locator('#multiple').click();
+ await ionPopoverDidPresent.next();
+
+ const popover = page.locator('#popover-multiple');
+ const selectPopover = popover.locator('ion-select-popover');
+ await expect(selectPopover).toBeVisible();
+
+ const defaultRow = selectPopover.locator('ion-item').first();
+ await defaultRow.hover();
+
+ await expect(selectPopover).toHaveScreenshot(screenshot('select-popover-checkbox-states'));
+ });
+ });
+});
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..2414709a33a
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..7afa8b07144
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f7375999964
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..1ac0704bb5a
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..647c77b193c
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..067300429d4
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..13c8b49f885
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..1933f822460
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..4a61fa74b92
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-checkbox-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..dee1f660858
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..b0834156969
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..61d6dc6aa4a
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..07f9c36862f
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..122e0a19fbc
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..f0e74b2af61
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..3a4e4e53392
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..94559c1724d
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..c2d202194df
Binary files /dev/null and b/core/src/components/select-popover/test/states/select-popover.e2e.ts-snapshots/select-popover-radio-states-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx
index cde4a52850f..fb0c8368543 100644
--- a/core/src/components/select/select.tsx
+++ b/core/src/components/select/select.tsx
@@ -10,7 +10,7 @@ import { printIonWarning } from '@utils/logging';
import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays';
import type { OverlaySelect } from '@utils/overlays-interface';
import { isRTL } from '@utils/rtl';
-import { sanitizeDOMString } from '@utils/sanitization';
+import { sanitizeDOMTree } from '@utils/sanitization';
import { createColorClasses, hostContext } from '@utils/theme';
import { watchForOptions } from '@utils/watch-options';
import { caretDownSharp, chevronExpand } from 'ionicons/icons';
@@ -591,15 +591,11 @@ export class Select implements ComponentInterface {
.join(' ');
const optClass = `${OPTION_CLASS} ${copyClasses}`;
const isSelected = isOptionSelected(selectValue, value, this.compareWith);
- const text = this.customHTMLEnabled ? getOptionContent(option) : getDefaultSlotPlainText(option);
- const startContent = this.customHTMLEnabled
- ? (getOptionContent(option, 'start') as HTMLElement | null)
- : undefined;
- const endContent = this.customHTMLEnabled ? (getOptionContent(option, 'end') as HTMLElement | null) : undefined;
+ const { content, startContent, endContent } = extractOptionContent(option, this.customHTMLEnabled);
return {
role: isSelected ? 'selected' : '',
- text: text ?? '',
+ text: content ?? '',
cssClass: optClass,
handler: () => {
this.setValue(value);
@@ -608,8 +604,8 @@ export class Select implements ComponentInterface {
'aria-checked': isSelected ? 'true' : 'false',
role: 'radio',
},
- startContent: startContent ?? undefined,
- endContent: endContent ?? undefined,
+ startContent,
+ endContent,
description: option.description,
} as SelectActionSheetButton;
});
@@ -639,21 +635,17 @@ export class Select implements ComponentInterface {
.filter((cls) => cls !== 'hydrated')
.join(' ');
const optClass = `${OPTION_CLASS} ${copyClasses}`;
- const label = this.customHTMLEnabled ? getOptionContent(option) : getDefaultSlotPlainText(option);
- const startContent = this.customHTMLEnabled
- ? (getOptionContent(option, 'start') as HTMLElement | null)
- : undefined;
- const endContent = this.customHTMLEnabled ? (getOptionContent(option, 'end') as HTMLElement | null) : undefined;
+ const { content, startContent, endContent } = extractOptionContent(option, this.customHTMLEnabled);
return {
type: inputType,
cssClass: optClass,
- label: label ?? '',
+ label: content ?? '',
value,
checked: isOptionSelected(selectValue, value, this.compareWith),
disabled: option.disabled,
- startContent: startContent ?? undefined,
- endContent: endContent ?? undefined,
+ startContent,
+ endContent,
description: option.description,
};
});
@@ -670,14 +662,10 @@ export class Select implements ComponentInterface {
.filter((cls) => cls !== 'hydrated')
.join(' ');
const optClass = `${OPTION_CLASS} ${copyClasses}`;
- const text = this.customHTMLEnabled ? getOptionContent(option) : getDefaultSlotPlainText(option);
- const startContent = this.customHTMLEnabled
- ? (getOptionContent(option, 'start') as HTMLElement | null)
- : undefined;
- const endContent = this.customHTMLEnabled ? (getOptionContent(option, 'end') as HTMLElement | null) : undefined;
+ const { content, startContent, endContent } = extractOptionContent(option, this.customHTMLEnabled);
return {
- text: text ?? '',
+ text: content ?? '',
cssClass: optClass,
value,
checked: isOptionSelected(selectValue, value, this.compareWith),
@@ -688,8 +676,8 @@ export class Select implements ComponentInterface {
this.close();
}
},
- startContent: startContent ?? undefined,
- endContent: endContent ?? undefined,
+ startContent,
+ endContent,
description: option.description,
};
});
@@ -1641,8 +1629,15 @@ const getOptionContent = (
// Default slot: get nodes without a slot attribute
const defaultSlot = getOptionDefaultSlot(option) || [];
nodes = defaultSlot.filter((node) => {
- // Exclude whitespace-only text nodes to prevent empty container returns
- return node.textContent?.trim().length !== 0;
+ /**
+ * Exclude whitespace-only text nodes (newline noise between
+ * markup elements). Element nodes are always kept, even when
+ * their textContent is empty (e.g.