Skip to content

Commit b498026

Browse files
committed
feat: add semantic selection change event on item
1 parent bdb43dc commit b498026

3 files changed

Lines changed: 165 additions & 1 deletion

File tree

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

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,4 +566,81 @@ describe("SegmentedButtonItem Accessibility", () => {
566566
.trigger("mouseover")
567567
.should("have.attr", "title", TOOLTIP_TEXT);
568568
});
569-
});
569+
});
570+
571+
describe("SegmentedButtonItem: selection-change event", () => {
572+
it("should fire selection-change event when item is clicked", () => {
573+
const selectionChangeSpy = cy.spy().as("selectionChangeSpy");
574+
575+
cy.mount(
576+
<SegmentedButton>
577+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
578+
<SegmentedButtonItem id="item2">Second</SegmentedButtonItem>
579+
</SegmentedButton>
580+
);
581+
582+
cy.get("#item2")
583+
.then($el => {
584+
$el[0].addEventListener("selection-change", selectionChangeSpy);
585+
});
586+
587+
cy.get("#item2")
588+
.ui5SegmentedButtonItemToggleSelect();
589+
590+
cy.get("@selectionChangeSpy").should("have.been.calledOnce");
591+
});
592+
593+
it("should prevent selection when preventDefault is called", () => {
594+
cy.mount(
595+
<SegmentedButton>
596+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
597+
<SegmentedButtonItem id="item2">Second</SegmentedButtonItem>
598+
</SegmentedButton>
599+
);
600+
601+
cy.get("#item2")
602+
.then($el => {
603+
$el[0].addEventListener("selection-change", (e: Event) => {
604+
e.preventDefault();
605+
});
606+
});
607+
608+
// Item 1 should be selected initially
609+
cy.get("#item1").should("have.attr", "selected");
610+
cy.get("#item2").should("not.have.attr", "selected");
611+
612+
// Click item 2
613+
cy.get("#item2").realClick();
614+
615+
// Item 2 should NOT be selected because we called preventDefault
616+
cy.get("#item1").should("have.attr", "selected");
617+
cy.get("#item2").should("not.have.attr", "selected");
618+
});
619+
620+
it("should not fire selection-change event when disabled item is clicked", () => {
621+
const selectionChangeSpy = cy.spy().as("selectionChangeSpy");
622+
623+
cy.mount(
624+
<SegmentedButton>
625+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
626+
<SegmentedButtonItem id="item2" disabled>Second</SegmentedButtonItem>
627+
</SegmentedButton>
628+
);
629+
630+
cy.get("#item2")
631+
.then($el => {
632+
$el[0].addEventListener("selection-change", selectionChangeSpy);
633+
});
634+
635+
// Click the disabled item directly
636+
cy.get("#item2")
637+
.shadow()
638+
.find("li")
639+
.click({ force: true });
640+
641+
cy.get("@selectionChangeSpy").should("not.have.been.called");
642+
cy.get("#item2").should("not.have.attr", "selected");
643+
});
644+
645+
});
646+

packages/main/src/SegmentedButtonItem.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ import type { ISegmentedButtonItem } from "./SegmentedButton.js";
2020
import SegmentedButtonItemTemplate from "./SegmentedButtonItemTemplate.js";
2121

2222
import type { IButton } from "./Button.js";
23+
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
2324
import segmentedButtonItemCss from "./generated/themes/SegmentedButtonItem.css.js";
25+
26+
type SegmentedButtonItemClickEventDetail = {
27+
originalEvent: Event;
28+
item: SegmentedButtonItem;
29+
};
30+
2431
/**
2532
* @class
2633
*
@@ -48,7 +55,26 @@ import segmentedButtonItemCss from "./generated/themes/SegmentedButtonItem.css.j
4855
template: SegmentedButtonItemTemplate,
4956
styles: segmentedButtonItemCss,
5057
})
58+
59+
/**
60+
* Fired when the component is activated either with a mouse/tap or by using the Enter or Space key.
61+
*
62+
* **Note:** The event will not be fired if the `disabled` property is set to `true`.
63+
*
64+
* @since 2.22.0
65+
* @public
66+
* @param {MouseEvent} originalEvent The original mouse event that triggered the selection
67+
* @param {SegmentedButtonItem} item The segmented button item that was clicked
68+
*/
69+
@event("selection-change", {
70+
bubbles: true,
71+
cancelable: true,
72+
})
73+
5174
class SegmentedButtonItem extends UI5Element implements IButton, ISegmentedButtonItem {
75+
eventDetails!: {
76+
"selection-change": SegmentedButtonItemClickEventDetail,
77+
}
5278
/**
5379
* Defines whether the component is disabled.
5480
* A disabled component can't be selected or
@@ -196,6 +222,18 @@ class SegmentedButtonItem extends UI5Element implements IButton, ISegmentedButto
196222
return;
197223
}
198224

225+
const prevented = !this.fireDecoratorEvent("selection-change", {
226+
originalEvent: e,
227+
item: this,
228+
});
229+
230+
if (prevented) {
231+
// Stop the native MouseEvent from reaching the parent
232+
e.stopPropagation();
233+
e.preventDefault();
234+
return;
235+
}
236+
199237
this.selected = !this.selected;
200238
}
201239

packages/main/test/pages/SegmentedButton.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,28 @@ <h3>Accessibility</h3>
245245
<span id="accRefText">accessible ref text</span>
246246
</section>
247247

248+
<section>
249+
<h3>selection-change Event Demo</h3>
250+
<p>Normal behavior - selection works:</p>
251+
<ui5-segmented-button id="eventTestSB1">
252+
<ui5-segmented-button-item id="normalItem1" selected>First</ui5-segmented-button-item>
253+
<ui5-segmented-button-item id="normalItem2">Second</ui5-segmented-button-item>
254+
<ui5-segmented-button-item id="normalItem3">Third</ui5-segmented-button-item>
255+
</ui5-segmented-button>
256+
<br><br>
257+
<ui5-input id="normalEventLog" readonly value="No event yet"></ui5-input>
258+
<br><br>
259+
260+
<p>With preventDefault - "Second" item blocks selection:</p>
261+
<ui5-segmented-button id="eventTestSB2">
262+
<ui5-segmented-button-item id="blockedItem1" selected>First</ui5-segmented-button-item>
263+
<ui5-segmented-button-item id="blockedItem2">Second (Blocked)</ui5-segmented-button-item>
264+
<ui5-segmented-button-item id="blockedItem3">Third</ui5-segmented-button-item>
265+
</ui5-segmented-button>
266+
<br><br>
267+
<ui5-input id="blockedEventLog" readonly value="No event yet"></ui5-input>
268+
</section>
269+
248270
<script>
249271

250272
progSetButton1.addEventListener("click", function() {
@@ -265,6 +287,33 @@ <h3>Accessibility</h3>
265287
segButtonProg.items[0].selected = true;
266288
});
267289

290+
// Normal behavior - events fire, selection works
291+
normalItem1.addEventListener("selection-change", function(e) {
292+
normalEventLog.value = "Item 1 clicked - selection allowed";
293+
});
294+
295+
normalItem2.addEventListener("selection-change", function(e) {
296+
normalEventLog.value = "Item 2 clicked - selection allowed";
297+
});
298+
299+
normalItem3.addEventListener("selection-change", function(e) {
300+
normalEventLog.value = "Item 3 clicked - selection allowed";
301+
});
302+
303+
// Blocked behavior - "Second" item prevents selection
304+
blockedItem1.addEventListener("selection-change", function(e) {
305+
blockedEventLog.value = "Item 1 clicked - selection allowed";
306+
});
307+
308+
blockedItem2.addEventListener("selection-change", function(e) {
309+
e.preventDefault(); // Block selection for this item
310+
blockedEventLog.value = "Item 2 clicked - SELECTION BLOCKED!";
311+
});
312+
313+
blockedItem3.addEventListener("selection-change", function(e) {
314+
blockedEventLog.value = "Item 3 clicked - selection allowed";
315+
});
316+
268317
</script>
269318

270319
</body>

0 commit comments

Comments
 (0)