Skip to content

Commit d219d64

Browse files
fix(ui5-li): prevent item-click when interactive content is clicked
Previously, `_onclick` in `ListItemBase` suppressed the `_press` event by checking `:has(:focus-within)` on the focus DOM ref. This approach is fragile: if a nested interactive element (e.g. a button) disables itself synchronously during its own `click` handler, focus is lost before the event bubbles up to the list item. As a result, the `:focus-within` check fails and `ui5-item-click` fires unexpectedly ? a regression observed in Chrome since v2.4.0. Replace the focus-based guard with an event-path inspection: `_isInteractiveContentClicked` walks `e.composedPath()` from the clicked target up to (but not including) the list item boundary and returns `true` if any element matches a native or UI5 interactive control. This correctly suppresses `item-click` regardless of whether the inner element changes its disabled/focused state during the click. Add a regression test: nested `ui5-button` that disables itself on click must not trigger `ui5-item-click` on the parent list item. Fixes: #10976
1 parent fb3a1ca commit d219d64

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

packages/main/cypress/specs/List.cy.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,37 @@ describe("List Tests", () => {
698698
cy.get("@selectionChangeStub").should("have.been.calledOnce");
699699
});
700700

701+
it("does not fire item-click when nested button disables itself on click", () => {
702+
cy.mount(
703+
<List>
704+
<ListItemCustom>
705+
<div>
706+
<span>First List Item</span>
707+
<Button id="action-button">Action</Button>
708+
</div>
709+
</ListItemCustom>
710+
</List>
711+
);
712+
713+
cy.get("[ui5-list]").then(($list) => {
714+
$list[0].addEventListener("ui5-item-click", cy.stub().as("itemClickStub"));
715+
});
716+
717+
cy.get("#action-button").then(($button) => {
718+
const buttonClickStub = cy.stub().as("buttonClickStub");
719+
buttonClickStub.callsFake(() => {
720+
($button[0] as Button).disabled = true;
721+
});
722+
723+
$button[0].addEventListener("click", buttonClickStub);
724+
});
725+
726+
cy.get("#action-button").click();
727+
728+
cy.get("@buttonClickStub").should("have.been.calledOnce");
729+
cy.get("@itemClickStub").should("not.have.been.called");
730+
});
731+
701732
it("selectionChange events provides previousSelection item", () => {
702733
cy.mount(
703734
<div>

packages/main/src/ListItemBase.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,35 @@ class ListItemBase extends UI5Element implements ITabbable {
165165
}
166166

167167
_onclick(e: MouseEvent) {
168-
if (this.getFocusDomRef()!.matches(":has(:focus-within)")) {
168+
if (this._isInteractiveContentClicked(e)) {
169169
return;
170170
}
171171
this.fireItemPress(e);
172172
}
173173

174+
_isInteractiveContentClicked(e: MouseEvent): boolean {
175+
const focusDomRef = this.getFocusDomRef();
176+
if (!focusDomRef) {
177+
return false;
178+
}
179+
180+
const path = e.composedPath();
181+
const boundaryIndex = path.findIndex(target => target === this || target === focusDomRef);
182+
const relevantPath = boundaryIndex === -1 ? path : path.slice(0, boundaryIndex);
183+
184+
return relevantPath.some(target => {
185+
if (!(target instanceof HTMLElement)) {
186+
return false;
187+
}
188+
189+
if (target.matches("button, input, select, textarea, a[href], [contenteditable]:not([contenteditable='false'])")) {
190+
return true;
191+
}
192+
193+
return target.matches("ui5-button, ui5-link, ui5-input, ui5-textarea, ui5-select, ui5-combobox, ui5-multi-combobox, ui5-switch, ui5-checkbox, ui5-radio-button, ui5-date-picker, ui5-daterange-picker, ui5-time-picker, ui5-step-input");
194+
});
195+
}
196+
174197
/**
175198
* Override from subcomponent, if needed
176199
*/

0 commit comments

Comments
 (0)