From f81c6cff61981c146578d17a39a945b02425aeac Mon Sep 17 00:00:00 2001 From: "Martin R. Hristov" Date: Tue, 28 Jul 2026 15:22:49 +0300 Subject: [PATCH 1/3] refactor(ui5-input-icon): simplify public API and forward value state via CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove `disabled`, `readonly`, `showTooltip`, and `valueState` public properties from `ui5-input-icon`. Also remove the private `_parentDisabled` flag — the icon has no notion of parent state. - Tooltip is now always on when `accessibleName` is set (no opt-in flag). - Icon uses `tabindex="-1"` so it stays out of the tab chain. - Forward the parent Input's value state to slotted icons via inherited CSS custom properties instead of imperative `setAttribute` in `onBeforeRendering`. Parent Input's `:host([value-state="…"])` publishes `--_ui5_input_icon_state_*` custom properties that inherit through the shadow boundary into slotted icons — no JS DOM mutation per render, no stale attributes on the icon. - Collapse InputIcon.css from seven state-specific selector blocks to a single set of rules reading the inherited custom properties, with fallbacks preserving the plain (no-value-state) appearance. - Add `InputIcon.mdx` under Input's docs folder so it appears as a related page under Input in the website sidebar (auto-picked up by the autogenerated sidebar). - Update Cypress test to verify value-state forwarding via inherited CSS custom properties rather than the removed attribute. --- packages/main/cypress/specs/Input.cy.tsx | 9 +- packages/main/src/Input.ts | 9 -- packages/main/src/InputIcon.ts | 76 +--------------- packages/main/src/InputIconTemplate.tsx | 64 ++++++------- packages/main/src/themes/Input.css | 40 ++++++++ packages/main/src/themes/InputIcon.css | 91 +++---------------- .../test/pages/InputInteractiveIcons.html | 9 +- .../main/Input/InputIcon.mdx | 7 ++ 8 files changed, 101 insertions(+), 204 deletions(-) create mode 100644 packages/website/docs/_components_pages/main/Input/InputIcon.mdx diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx index 4924b72911157..c3faa05a998da 100644 --- a/packages/main/cypress/specs/Input.cy.tsx +++ b/packages/main/cypress/specs/Input.cy.tsx @@ -3624,16 +3624,21 @@ describe("Input built-in filtering", () => { .should("exist"); }); - it("should sync value-state to slotted input icons", () => { + it("should forward value-state styling to slotted input icons via inherited CSS custom properties", () => { cy.mount( ); + // The parent Input publishes --_ui5_input_icon_state_padding on its host when value-state is set. + // CSS custom properties inherit through the shadow boundary, so the slotted icon reads it inside its own shadow root. cy.get("[ui5-input]") .find("[ui5-input-icon]") - .should("have.attr", "value-state", "Negative"); + .then($icon => { + const computedPadding = getComputedStyle($icon[0]).getPropertyValue("--_ui5_input_icon_state_padding").trim(); + expect(computedPadding).to.not.equal(""); + }); }); it("should work with multiple icons and clear icon", () => { diff --git a/packages/main/src/Input.ts b/packages/main/src/Input.ts index b7bac5a95cae3..9379a44bc5a4d 100644 --- a/packages/main/src/Input.ts +++ b/packages/main/src/Input.ts @@ -770,15 +770,6 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement this._effectiveShowClearIcon = (this.showClearIcon && !!this.value && !this.readonly && !this.disabled); this.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`); - // Sync value state to slotted input icons - this.icon.forEach((iconElement: IIcon) => { - if (iconElement.hasAttribute("ui5-input-icon")) { - (iconElement as any).valueState = this.valueState; - (iconElement as any).readonly = this.readonly; - (iconElement as any)._parentDisabled = this.disabled; - } - }); - const hasItems = !!this._flattenItems.length; const hasValue = !!this.value; const isFocused = this.shadowRoot!.querySelector("input") === getActiveElement(); diff --git a/packages/main/src/InputIcon.ts b/packages/main/src/InputIcon.ts index 9340285aac73e..9bb98166af19a 100644 --- a/packages/main/src/InputIcon.ts +++ b/packages/main/src/InputIcon.ts @@ -3,7 +3,6 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import type ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js"; import InputIconTemplate from "./InputIconTemplate.js"; import inputIconCss from "./generated/themes/InputIcon.css.js"; @@ -63,6 +62,7 @@ class InputIcon extends UI5Element { * Defines the accessible name of the icon. * * **Note:** This property is used for accessibility purposes and will be announced by screen readers. + * When set, it is also rendered as a native `title` tooltip. * * @default undefined * @public @@ -70,50 +70,6 @@ class InputIcon extends UI5Element { @property() accessibleName?: string; - /** - * Defines whether the tooltip is shown. - * - * **Note:** The tooltip text should be provided via the `accessible-name` property. - * - * @default false - * @public - */ - @property({ type: Boolean }) - showTooltip = false; - - /** - * Defines the value state of the icon. - * - * **Note:** This property should match the parent input's value state for consistent styling. - * - * @default "None" - * @public - */ - @property() - valueState: `${ValueState}` = "None"; - - /** - * Defines whether the icon is disabled. - * - * **Note:** Disabled icons are not interactive and do not fire click events. - * - * @default false - * @public - */ - @property({ type: Boolean }) - disabled = false; - - /** - * Defines whether the icon is readonly. - * - * **Note:** Readonly icons are not interactive and do not fire click events. - * - * @default false - * @public - */ - @property({ type: Boolean }) - readonly = false; - /** * @private */ @@ -126,27 +82,13 @@ class InputIcon extends UI5Element { @property({ type: Boolean, noAttribute: true }) _focused = false; - /** - * @private - */ - @property({ type: Boolean, noAttribute: true }) - _parentDisabled = false; - _onclick(e: MouseEvent) { - if (this.disabled || this.readonly || this._parentDisabled) { - e.stopImmediatePropagation(); - e.preventDefault(); - return; - } - e.stopImmediatePropagation(); this.fireDecoratorEvent("click"); } _onmousedown() { - if (!this.disabled && !this.readonly && !this._parentDisabled) { - this._pressed = true; - } + this._pressed = true; } _onmouseup() { @@ -167,10 +109,6 @@ class InputIcon extends UI5Element { } _onkeydown(e: KeyboardEvent) { - if (this.disabled || this.readonly || this._parentDisabled) { - return; - } - if (isEnter(e) || isSpace(e)) { this._pressed = true; e.preventDefault(); // Prevent scrolling on Space @@ -178,26 +116,18 @@ class InputIcon extends UI5Element { } _onkeyup(e: KeyboardEvent) { - if (this.disabled || this.readonly || this._parentDisabled) { - return; - } - if (isEnter(e) || isSpace(e)) { this._pressed = false; this.fireDecoratorEvent("click"); } } - get effectiveTabIndex() { - return (this.disabled || this.readonly || this._parentDisabled) ? -1 : 0; - } - get effectiveAriaLabel() { return this.accessibleName || undefined; } get effectiveTitle() { - return this.showTooltip && this.accessibleName ? this.accessibleName : undefined; + return this.accessibleName || undefined; } } diff --git a/packages/main/src/InputIconTemplate.tsx b/packages/main/src/InputIconTemplate.tsx index 249fcce58cc7a..20dbd20d812d3 100644 --- a/packages/main/src/InputIconTemplate.tsx +++ b/packages/main/src/InputIconTemplate.tsx @@ -3,40 +3,34 @@ import Icon from "./Icon.js"; export default function InputIconTemplate(this: InputIcon) { return ( - <> - {!this.readonly && -
- {this.name && ( -
- } - +
+ {this.name && ( +
); } diff --git a/packages/main/src/themes/Input.css b/packages/main/src/themes/Input.css index 1e4b79a63087b..190ef7083a415 100644 --- a/packages/main/src/themes/Input.css +++ b/packages/main/src/themes/Input.css @@ -517,4 +517,44 @@ [ui5-responsive-popover] [ui5-input] { width: 100%; +} + +/* + * Value-state forwarding for slotted . + * + * The parent Input publishes state-specific values via CSS custom properties on its host. + * CSS custom properties inherit through the shadow boundary, so a slotted + * automatically picks them up inside its own shadow root — no JS DOM mutation needed. + */ +:host([value-state="Negative"]), +:host([value-state="Critical"]) { + --_ui5_input_icon_state_padding: var(--_ui5_input_error_warning_icon_padding); +} + +:host([value-state="Information"]) { + --_ui5_input_icon_state_padding: var(--_ui5_input_information_icon_padding); +} + +:host([value-state="Negative"]) { + --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_error_icon_box_shadow); + --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_error_pressed_color); + --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_error_focus_outline_color); +} + +:host([value-state="Critical"]) { + --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_warning_icon_box_shadow); + --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_warning_pressed_color); + --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_warning_focus_outline_color); +} + +:host([value-state="Information"]) { + --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_information_icon_box_shadow); + --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_information_pressed_color); + --_ui5_input_icon_state_focus_outline_color: var(--sapContent_FocusColor); +} + +:host([value-state="Positive"]) { + --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_success_icon_box_shadow); + --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_success_pressed_color); + --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_success_focus_outline_color); } \ No newline at end of file diff --git a/packages/main/src/themes/InputIcon.css b/packages/main/src/themes/InputIcon.css index 876b59e176645..924e24ddc7405 100644 --- a/packages/main/src/themes/InputIcon.css +++ b/packages/main/src/themes/InputIcon.css @@ -7,7 +7,8 @@ color: var(--_ui5_input_icon_color); cursor: pointer; outline: none; - padding: var(--_ui5_input_icon_padding); + /* padding is state-aware; parent Input publishes the state-specific value via CSS custom property */ + padding: var(--_ui5_input_icon_state_padding, var(--_ui5_input_icon_padding)); border-inline-start: var(--_ui5_input_icon_border); min-width: 1rem; min-height: 1rem; @@ -18,32 +19,33 @@ } .inputIcon:focus-visible { - outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor); + outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--_ui5_input_icon_state_focus_outline_color, var(--sapContent_FocusColor)); outline-offset: -0.125rem; } .inputIcon.inputIcon--pressed { background: var(--_ui5_input_icon_pressed_bg); - box-shadow: var(--_ui5_input_icon_box_shadow); + /* box-shadow / color fall back to the plain variant when no value state is set */ + box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); border-inline-start: var(--_ui5_select_hover_icon_left_border); - color: var(--_ui5_input_icon_pressed_color); + color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color)); } .inputIcon:not(.inputIcon--disabled):active { background-color: var(--sapButton_Active_Background); - box-shadow: var(--_ui5_input_icon_box_shadow); + box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); border-inline-start: var(--_ui5_select_hover_icon_left_border); - color: var(--_ui5_input_icon_pressed_color); + color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color)); } .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { background: var(--_ui5_input_icon_hover_bg); - box-shadow: var(--_ui5_input_icon_box_shadow); + box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); } .inputIcon:not(.inputIcon--disabled):hover { border-inline-start: var(--_ui5_select_hover_icon_left_border); - box-shadow: var(--_ui5_input_icon_box_shadow); + box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); } /* Disabled state styling */ @@ -63,73 +65,8 @@ flex-shrink: 0; } -/* Value state styling */ -:host([value-state="Negative"]) .inputIcon, -:host([value-state="Critical"]) .inputIcon { - padding: var(--_ui5_input_error_warning_icon_padding); -} - -:host([value-state="Information"]) .inputIcon { - padding: var(--_ui5_input_information_icon_padding); -} - -:host([value-state="Negative"]) .inputIcon:not(.inputIcon--disabled):active, -:host([value-state="Negative"]) .inputIcon.inputIcon--pressed { - box-shadow: var(--_ui5_input_error_icon_box_shadow); - color: var(--_ui5_input_icon_error_pressed_color); -} - -:host([value-state="Negative"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { - box-shadow: var(--_ui5_input_error_icon_box_shadow); -} - -:host([value-state="Critical"]) .inputIcon:not(.inputIcon--disabled):active, -:host([value-state="Critical"]) .inputIcon.inputIcon--pressed { - box-shadow: var(--_ui5_input_warning_icon_box_shadow); - color: var(--_ui5_input_icon_warning_pressed_color); -} - -:host([value-state="Critical"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { - box-shadow: var(--_ui5_input_warning_icon_box_shadow); -} - -:host([value-state="Information"]) .inputIcon:not(.inputIcon--disabled):active, -:host([value-state="Information"]) .inputIcon.inputIcon--pressed { - box-shadow: var(--_ui5_input_information_icon_box_shadow); - color: var(--_ui5_input_icon_information_pressed_color); -} - -:host([value-state="Information"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { - box-shadow: var(--_ui5_input_information_icon_box_shadow); -} - -:host([value-state="Positive"]) .inputIcon:not(.inputIcon--disabled):active, -:host([value-state="Positive"]) .inputIcon.inputIcon--pressed { - box-shadow: var(--_ui5_input_success_icon_box_shadow); - color: var(--_ui5_input_icon_success_pressed_color); -} - -:host([value-state="Positive"]) .inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { - box-shadow: var(--_ui5_input_success_icon_box_shadow); -} - -/* Override focus outline colors for value states */ -:host([value-state="Negative"]) .inputIcon.inputIcon--focused { - outline-color: var(--_ui5_input_focused_value_state_error_focus_outline_color); - border-inline-start-color: var(--_ui5_input_focused_value_state_error_focus_outline_color); -} - -:host([value-state="Critical"]) .inputIcon.inputIcon--focused { - outline-color: var(--_ui5_input_focused_value_state_warning_focus_outline_color); - border-inline-start-color: var(--_ui5_input_focused_value_state_warning_focus_outline_color); -} - -:host([value-state="Information"]) .inputIcon.inputIcon--focused { - outline-color: var(--sapContent_FocusColor); - border-inline-start-color: var(--sapContent_FocusColor); -} - -:host([value-state="Positive"]) .inputIcon.inputIcon--focused { - outline-color: var(--_ui5_input_focused_value_state_success_focus_outline_color); - border-inline-start-color: var(--_ui5_input_focused_value_state_success_focus_outline_color); +/* Focus-outline colour when the parent Input has a value state — driven by inherited CSS custom property */ +.inputIcon.inputIcon--focused { + outline-color: var(--_ui5_input_icon_state_focus_outline_color, var(--sapContent_FocusColor)); + border-inline-start-color: var(--_ui5_input_icon_state_focus_outline_color); } diff --git a/packages/main/test/pages/InputInteractiveIcons.html b/packages/main/test/pages/InputInteractiveIcons.html index c8d6c2e81006e..a29c9071a9cde 100644 --- a/packages/main/test/pages/InputInteractiveIcons.html +++ b/packages/main/test/pages/InputInteractiveIcons.html @@ -180,9 +180,6 @@

With InputIcon (Interactive)

-
- Toggle Icon Disabled -
Negative state: @@ -536,11 +533,7 @@

9. Disabled Inputs with Interactive Icons

}); // Toggle disabled for a single icon - const toggleIconDisabledBtn = document.getElementById("toggle-icon-disabled"); - const toggleIcon = document.getElementById("toggle-icon"); - toggleIconDisabledBtn.addEventListener("click", () => { - toggleIcon.toggleAttribute("disabled"); - }); + // (Removed: ui5-input-icon no longer exposes a `disabled` property.) // Event logging for accessibility demo const eventLog = document.getElementById("event-log-content"); diff --git a/packages/website/docs/_components_pages/main/Input/InputIcon.mdx b/packages/website/docs/_components_pages/main/Input/InputIcon.mdx new file mode 100644 index 0000000000000..fb322caab9073 --- /dev/null +++ b/packages/website/docs/_components_pages/main/Input/InputIcon.mdx @@ -0,0 +1,7 @@ +--- +slug: ../../InputIcon +--- + +<%COMPONENT_OVERVIEW%> + +<%COMPONENT_METADATA%> From 99f71e5a4641f33e4b63ff7531ff7a863acf3ebb Mon Sep 17 00:00:00 2001 From: "Martin R. Hristov" Date: Tue, 28 Jul 2026 15:28:29 +0300 Subject: [PATCH 2/3] refactor(ui5-input-icon): forward parent Input disabled via inherited CSS custom properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the same CSS-custom-property forwarding pattern used for value state to the parent Input's disabled state: - Parent Input publishes `--_ui5_input_icon_state_opacity`, `--_ui5_input_icon_state_pointer_events`, and `--_ui5_input_icon_state_cursor` on `:host([disabled])`. - Icon reads them via `var(…, default)` — defaults preserve the plain interactive appearance. Native `pointer-events: none` blocks hover/active/click, so the icon's `:not(.inputIcon--disabled)` guards (dead selectors since the disabled property was removed) are also cleaned up. - Adds a Cypress test verifying the vars actually inherit into a slotted icon when the parent input is disabled. --- packages/main/cypress/specs/Input.cy.tsx | 20 ++++++++++++++++++++ packages/main/src/themes/Input.css | 10 ++++++++++ packages/main/src/themes/InputIcon.css | 18 ++++++++---------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx index c3faa05a998da..93a77681f60fe 100644 --- a/packages/main/cypress/specs/Input.cy.tsx +++ b/packages/main/cypress/specs/Input.cy.tsx @@ -3675,5 +3675,25 @@ describe("Input built-in filtering", () => { cy.get("@iconClick") .should("have.been.calledOnce"); }); + + it("should forward disabled state to slotted input icons via inherited CSS custom properties", () => { + cy.mount( + + + + ); + + // The parent Input publishes --_ui5_input_icon_state_pointer_events: none on :host([disabled]). + // That inherits into the icon's shadow root and natively blocks hover/active/click. + cy.get("[ui5-input]") + .find("[ui5-input-icon]") + .then($icon => { + const styles = getComputedStyle($icon[0]); + expect(styles.getPropertyValue("--_ui5_input_icon_state_pointer_events").trim()).to.equal("none"); + expect(styles.getPropertyValue("--_ui5_input_icon_state_cursor").trim()).to.equal("default"); + // Opacity inherited as a custom prop, and the icon's shadow root applies it via opacity: var(...) + expect(styles.getPropertyValue("--_ui5_input_icon_state_opacity").trim()).to.not.equal(""); + }); + }); }); }); diff --git a/packages/main/src/themes/Input.css b/packages/main/src/themes/Input.css index 190ef7083a415..4b53fa5a918aa 100644 --- a/packages/main/src/themes/Input.css +++ b/packages/main/src/themes/Input.css @@ -557,4 +557,14 @@ --_ui5_input_icon_state_pressed_shadow: var(--_ui5_input_success_icon_box_shadow); --_ui5_input_icon_state_pressed_color: var(--_ui5_input_icon_success_pressed_color); --_ui5_input_icon_state_focus_outline_color: var(--_ui5_input_focused_value_state_success_focus_outline_color); +} + +/* + * Disabled forwarding for slotted . Same mechanism: parent publishes vars, + * icon reads them via var() with defaults, no JS DOM mutation. + */ +:host([disabled]) { + --_ui5_input_icon_state_opacity: var(--sapContent_DisabledOpacity); + --_ui5_input_icon_state_pointer_events: none; + --_ui5_input_icon_state_cursor: default; } \ No newline at end of file diff --git a/packages/main/src/themes/InputIcon.css b/packages/main/src/themes/InputIcon.css index 924e24ddc7405..08319972bd94e 100644 --- a/packages/main/src/themes/InputIcon.css +++ b/packages/main/src/themes/InputIcon.css @@ -5,7 +5,11 @@ .inputIcon { color: var(--_ui5_input_icon_color); - cursor: pointer; + /* cursor / opacity / pointer-events are state-aware — parent Input publishes overrides on :host([disabled]). + Defaults preserve the plain interactive appearance. */ + cursor: var(--_ui5_input_icon_state_cursor, pointer); + opacity: var(--_ui5_input_icon_state_opacity, 1); + pointer-events: var(--_ui5_input_icon_state_pointer_events, auto); outline: none; /* padding is state-aware; parent Input publishes the state-specific value via CSS custom property */ padding: var(--_ui5_input_icon_state_padding, var(--_ui5_input_icon_padding)); @@ -31,29 +35,23 @@ color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color)); } -.inputIcon:not(.inputIcon--disabled):active { +.inputIcon:active { background-color: var(--sapButton_Active_Background); box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); border-inline-start: var(--_ui5_select_hover_icon_left_border); color: var(--_ui5_input_icon_state_pressed_color, var(--_ui5_input_icon_pressed_color)); } -.inputIcon:not(.inputIcon--pressed):not(:active):not(.inputIcon--disabled):hover { +.inputIcon:not(.inputIcon--pressed):not(:active):hover { background: var(--_ui5_input_icon_hover_bg); box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); } -.inputIcon:not(.inputIcon--disabled):hover { +.inputIcon:hover { border-inline-start: var(--_ui5_select_hover_icon_left_border); box-shadow: var(--_ui5_input_icon_state_pressed_shadow, var(--_ui5_input_icon_box_shadow)); } -/* Disabled state styling */ -.inputIcon.inputIcon--disabled { - opacity: var(--sapContent_DisabledOpacity); - cursor: default; -} - /* Style the inner Icon component */ .ui5-input-icon-inner { From 7f1cc1c97adc3837a77c8583205b102736d0dd55 Mon Sep 17 00:00:00 2001 From: "Martin R. Hristov" Date: Tue, 28 Jul 2026 15:51:51 +0300 Subject: [PATCH 3/3] refactor(ui5-input-icon): forward parent Input readonly via inherited CSS custom properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the disabled forwarding pattern for readonly: - Parent Input's `:host([readonly]:not([disabled]))` publishes `--_ui5_input_icon_state_pointer_events: none` and `--_ui5_input_icon_state_cursor: default`. No opacity change — readonly inputs stay readable, they don't dim. - Interactive icons (search, voice, camera, …) become non-interactive alongside the input; the icon reads the inherited vars via the same `var(…, default)` pattern already in place. - Cypress test asserts the vars actually inherit into a slotted icon when the parent is readonly, and that opacity is *not* forwarded. --- packages/main/cypress/specs/Input.cy.tsx | 19 +++++++++++++++++++ packages/main/src/themes/Input.css | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/main/cypress/specs/Input.cy.tsx b/packages/main/cypress/specs/Input.cy.tsx index 93a77681f60fe..247421e2a0d53 100644 --- a/packages/main/cypress/specs/Input.cy.tsx +++ b/packages/main/cypress/specs/Input.cy.tsx @@ -3695,5 +3695,24 @@ describe("Input built-in filtering", () => { expect(styles.getPropertyValue("--_ui5_input_icon_state_opacity").trim()).to.not.equal(""); }); }); + + it("should forward readonly state to slotted input icons via inherited CSS custom properties", () => { + cy.mount( + + + + ); + + // Readonly parent publishes pointer-events: none + cursor: default (no opacity change). + cy.get("[ui5-input]") + .find("[ui5-input-icon]") + .then($icon => { + const styles = getComputedStyle($icon[0]); + expect(styles.getPropertyValue("--_ui5_input_icon_state_pointer_events").trim()).to.equal("none"); + expect(styles.getPropertyValue("--_ui5_input_icon_state_cursor").trim()).to.equal("default"); + // Readonly does not dim — opacity var should remain unset. + expect(styles.getPropertyValue("--_ui5_input_icon_state_opacity").trim()).to.equal(""); + }); + }); }); }); diff --git a/packages/main/src/themes/Input.css b/packages/main/src/themes/Input.css index 4b53fa5a918aa..c1b61ca559788 100644 --- a/packages/main/src/themes/Input.css +++ b/packages/main/src/themes/Input.css @@ -567,4 +567,14 @@ --_ui5_input_icon_state_opacity: var(--sapContent_DisabledOpacity); --_ui5_input_icon_state_pointer_events: none; --_ui5_input_icon_state_cursor: default; +} + +/* + * Readonly forwarding: the input is still readable but non-editable, so slotted interactive + * icons (search, voice, camera, …) should not fire either. No opacity change — the input + * itself does not dim in readonly. + */ +:host([readonly]:not([disabled])) { + --_ui5_input_icon_state_pointer_events: none; + --_ui5_input_icon_state_cursor: default; } \ No newline at end of file