diff --git a/packages/main/cypress/specs/Select.cy.tsx b/packages/main/cypress/specs/Select.cy.tsx
index 54ae56d3906f..264655ccaf05 100644
--- a/packages/main/cypress/specs/Select.cy.tsx
+++ b/packages/main/cypress/specs/Select.cy.tsx
@@ -1913,3 +1913,53 @@ describe("Select general interaction", () => {
.should("be.focused");
});
});
+
+describe("Select - active/down state", () => {
+ it("sets active attribute on ui5-option while mouse is pressed", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-select]").realClick();
+ cy.get("[ui5-select]").should("have.attr", "opened");
+
+ cy.get("[ui5-select]")
+ .find("[ui5-option]")
+ .eq(0)
+ .realMouseDown()
+ .should("have.attr", "active");
+
+ cy.get("[ui5-select]")
+ .find("[ui5-option]")
+ .eq(0)
+ .realMouseUp()
+ .should("not.have.attr", "active");
+ });
+
+ it("sets active attribute on ui5-option-custom while mouse is pressed", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-select]").realClick();
+ cy.get("[ui5-select]").should("have.attr", "opened");
+
+ cy.get("[ui5-select]")
+ .find("[ui5-option-custom]")
+ .eq(0)
+ .realMouseDown()
+ .should("have.attr", "active");
+
+ cy.get("[ui5-select]")
+ .find("[ui5-option-custom]")
+ .eq(0)
+ .realMouseUp()
+ .should("not.have.attr", "active");
+ });
+});
diff --git a/packages/main/src/ListItemBaseTemplate.tsx b/packages/main/src/ListItemBaseTemplate.tsx
index 5af5ef6b3628..fcca79d61ede 100644
--- a/packages/main/src/ListItemBaseTemplate.tsx
+++ b/packages/main/src/ListItemBaseTemplate.tsx
@@ -1,9 +1,11 @@
import type ListItemBase from "./ListItemBase.js";
-import type { AriaRole } from "@ui5/webcomponents-base/";
+import type { AriaRole, JsxTemplate } from "@ui5/webcomponents-base/";
-export default function ListItemBaseTemplate(this: ListItemBase, hooks?: { listItemContent: () => void }, injectedProps?: {
+export default function ListItemBaseTemplate(this: ListItemBase, hooks?: { listItemContent: JsxTemplate }, injectedProps?: {
role?: AriaRole,
title?: string,
+ onMouseDown?: (e: MouseEvent) => void,
+ onTouchStart?: (e: TouchEvent) => void,
}) {
const listItemContent = hooks?.listItemContent || defaultListItemContent;
@@ -20,8 +22,10 @@ export default function ListItemBaseTemplate(this: ListItemBase, hooks?: { listI
onKeyUp={this._onkeyup}
onKeyDown={this._onkeydown}
onClick={this._onclick}
+ onMouseDown={injectedProps?.onMouseDown}
+ onTouchStart={injectedProps?.onTouchStart}
>
- { listItemContent.call(this) }
+ { listItemContent.call(this) as JSX.Element }
);
}
diff --git a/packages/main/src/Option.ts b/packages/main/src/Option.ts
index 6cd5c44cdfc2..0c5d8066be85 100644
--- a/packages/main/src/Option.ts
+++ b/packages/main/src/Option.ts
@@ -41,6 +41,30 @@ import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
class Option extends ListItemBase implements IOption {
eventDetails!: ListItemBase["eventDetails"];
+ // Note: same active state logic exists in OptionCustom.
+ deactivate: () => void;
+
+ constructor() {
+ super();
+ this.deactivate = () => {
+ if (this.active) {
+ this.active = false;
+ }
+ };
+ }
+
+ onEnterDOM() {
+ super.onEnterDOM();
+ document.addEventListener("mouseup", this.deactivate);
+ document.addEventListener("touchend", this.deactivate);
+ }
+
+ onExitDOM() {
+ super.onExitDOM();
+ document.removeEventListener("mouseup", this.deactivate);
+ document.removeEventListener("touchend", this.deactivate);
+ }
+
/**
* Defines the text of the component.
*
@@ -103,9 +127,20 @@ class Option extends ListItemBase implements IOption {
return !!this.icon;
}
+ /**
+ * Indicates if the option is active (pressed down).
+ * @private
+ */
+ @property({ type: Boolean })
+ active = false;
+
get effectiveDisplayText() {
return this.textContent || "";
}
+
+ _onmousedown() {
+ this.active = true;
+ }
}
Option.define();
diff --git a/packages/main/src/OptionCustom.ts b/packages/main/src/OptionCustom.ts
index daedc153314b..81e1aaaa836e 100644
--- a/packages/main/src/OptionCustom.ts
+++ b/packages/main/src/OptionCustom.ts
@@ -40,6 +40,30 @@ import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
class OptionCustom extends ListItemBase implements IOption {
eventDetails!: ListItemBase["eventDetails"];
+ // Note: same active state logic exists in Option.
+ deactivate: () => void;
+
+ constructor() {
+ super();
+ this.deactivate = () => {
+ if (this.active) {
+ this.active = false;
+ }
+ };
+ }
+
+ onEnterDOM() {
+ super.onEnterDOM();
+ document.addEventListener("mouseup", this.deactivate);
+ document.addEventListener("touchend", this.deactivate);
+ }
+
+ onExitDOM() {
+ super.onExitDOM();
+ document.removeEventListener("mouseup", this.deactivate);
+ document.removeEventListener("touchend", this.deactivate);
+ }
+
/**
* Defines the text, displayed inside the `ui5-select` input filed
* when the option gets selected.
@@ -84,6 +108,17 @@ class OptionCustom extends ListItemBase implements IOption {
get effectiveDisplayText() {
return this.displayText || this.textContent || "";
}
+
+ /**
+ * Indicates if the option is active (pressed down).
+ * @private
+ */
+ @property({ type: Boolean })
+ active = false;
+
+ _onmousedown() {
+ this.active = true;
+ }
}
OptionCustom.define();
diff --git a/packages/main/src/OptionCustomTemplate.tsx b/packages/main/src/OptionCustomTemplate.tsx
index 04ff585299f7..af577565825c 100644
--- a/packages/main/src/OptionCustomTemplate.tsx
+++ b/packages/main/src/OptionCustomTemplate.tsx
@@ -2,7 +2,12 @@ import ListItemBaseTemplate from "./ListItemBaseTemplate.js";
import type OptionCustom from "./OptionCustom.js";
export default function OptionCustomTemplate(this: OptionCustom) {
- return ListItemBaseTemplate.call(this, { listItemContent }, { role: "option", title: this.tooltip });
+ return ListItemBaseTemplate.call(this, { listItemContent }, {
+ role: "option",
+ title: this.tooltip,
+ onMouseDown: this._onmousedown,
+ onTouchStart: this._onmousedown,
+ });
}
function listItemContent(this: OptionCustom) {
diff --git a/packages/main/src/OptionTemplate.tsx b/packages/main/src/OptionTemplate.tsx
index 56bf7e404af7..916e7bdffb57 100644
--- a/packages/main/src/OptionTemplate.tsx
+++ b/packages/main/src/OptionTemplate.tsx
@@ -3,7 +3,12 @@ import ListItemBaseTemplate from "./ListItemBaseTemplate.js";
import type Option from "./Option.js";
export default function OptionTemplate(this: Option) {
- return ListItemBaseTemplate.call(this, { listItemContent }, { role: "option", title: this.tooltip });
+ return ListItemBaseTemplate.call(this, { listItemContent }, {
+ role: "option",
+ title: this.tooltip,
+ onMouseDown: this._onmousedown,
+ onTouchStart: this._onmousedown,
+ });
}
function listItemContent(this: Option) {
diff --git a/packages/main/src/themes/ListItemBase.css b/packages/main/src/themes/ListItemBase.css
index 832c8d170177..cbe92d6cdea1 100644
--- a/packages/main/src/themes/ListItemBase.css
+++ b/packages/main/src/themes/ListItemBase.css
@@ -51,6 +51,7 @@
:host([active][actionable]:not([data-moving])),
:host([active][actionable][selected]:not([data-moving])) {
background-color: var(--sapList_Active_Background);
+ border-bottom-color: var(--sapList_Active_Background);
}
/* focused */