Skip to content

Commit 1f45f99

Browse files
Merge branch 'main' into header-banner-poc
2 parents 84a3a5c + f47c28e commit 1f45f99

8 files changed

Lines changed: 236 additions & 16 deletions

File tree

packages/base/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"devDependencies": {
6565
"@custom-elements-manifest/analyzer": "^0.10.10",
6666
"@openui5/sap.ui.core": "1.146.0",
67-
"@sap-theming/theming-base-content": "11.34.1",
67+
"@sap-theming/theming-base-content": "11.35.0",
6868
"@ui5/cypress-internal": "0.1.0",
6969
"@ui5/webcomponents-tools": "2.22.0-rc.2",
7070
"clean-css": "^5.2.2",

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

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,4 +566,134 @@ describe("SegmentedButtonItem Accessibility", () => {
566566
.trigger("mouseover")
567567
.should("have.attr", "title", TOOLTIP_TEXT);
568568
});
569-
});
569+
});
570+
571+
describe("SegmentedButtonItem: click event", () => {
572+
it("should fire selection change event when item is clicked", () => {
573+
const clickSpy = cy.spy().as("clickSpy");
574+
const selectionChangeSpy = cy.spy().as("selectionChangeSpy");
575+
576+
cy.mount(
577+
<SegmentedButton>
578+
<SegmentedButtonItem id="item1" selected>First</SegmentedButtonItem>
579+
<SegmentedButtonItem id="item2">Second</SegmentedButtonItem>
580+
</SegmentedButton>
581+
);
582+
583+
cy.get<SegmentedButton>("[ui5-segmented-button]")
584+
.then($el => {
585+
$el[0].addEventListener("selection-change", selectionChangeSpy);
586+
});
587+
588+
cy.get("#item2")
589+
.then($el => {
590+
$el[0].addEventListener("click", clickSpy);
591+
});
592+
593+
cy.get("#item2")
594+
.realClick();
595+
596+
cy.get("@clickSpy")
597+
.should("have.been.calledOnce");
598+
599+
cy.get("@selectionChangeSpy")
600+
.should("have.been.calledOnce");
601+
});
602+
603+
it("should prevent selection when preventDefault is called", () => {
604+
cy.mount(
605+
<SegmentedButton>
606+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
607+
<SegmentedButtonItem id="item2">Second</SegmentedButtonItem>
608+
</SegmentedButton>
609+
);
610+
611+
cy.get<SegmentedButton>("[ui5-segmented-button]")
612+
.then($el => {
613+
$el[0].addEventListener("selection-change", cy.spy().as("selectionChangeSpy"));
614+
})
615+
616+
617+
cy.get("#item2")
618+
.then($el => {
619+
$el[0].addEventListener("click", (e: Event) => {
620+
e.preventDefault();
621+
});
622+
});
623+
624+
cy.get("#item1")
625+
.should("have.attr", "selected");
626+
cy.get("#item2")
627+
.should("not.have.attr", "selected");
628+
629+
cy.get("#item2")
630+
.realClick();
631+
632+
// Item 2 should NOT be selected because we called preventDefault
633+
cy.get("#item1")
634+
.should("have.attr", "selected");
635+
cy.get("#item2")
636+
.should("not.have.attr", "selected");
637+
638+
cy.get("@selectionChangeSpy")
639+
.should("not.have.been.called");
640+
});
641+
642+
it("should not fire click event when disabled item is clicked", () => {
643+
const clickSpy = cy.spy().as("clickSpy");
644+
645+
cy.mount(
646+
<SegmentedButton>
647+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
648+
<SegmentedButtonItem id="item2" disabled>Second</SegmentedButtonItem>
649+
</SegmentedButton>
650+
);
651+
652+
cy.get("#item2")
653+
.then($el => {
654+
$el[0].addEventListener("click", clickSpy);
655+
});
656+
657+
// Click the disabled item directly
658+
cy.get("#item2")
659+
.shadow()
660+
.find("li")
661+
.click({ force: true });
662+
663+
cy.get("@clickSpy").should("not.have.been.called");
664+
cy.get("#item2").should("not.have.attr", "selected");
665+
});
666+
667+
it("should provide item and originalEvent in click event detail", () => {
668+
cy.mount(
669+
<SegmentedButton>
670+
<SegmentedButtonItem id="item1">First</SegmentedButtonItem>
671+
<SegmentedButtonItem id="item2">Second</SegmentedButtonItem>
672+
</SegmentedButton>
673+
);
674+
675+
cy.get("#item2")
676+
.then($el => {
677+
$el[0].addEventListener("click", cy.spy((e: CustomEvent) => {
678+
// Check that event detail contains item and originalEvent
679+
expect(e.detail).to.have.property("item");
680+
expect(e.detail).to.have.property("originalEvent");
681+
682+
// Check item reference and properties
683+
expect(e.detail.item).to.equal($el[0]);
684+
expect(e.detail.item.id).to.equal("item2");
685+
expect(e.detail.item.slotTextContent).to.equal("Second");
686+
687+
// Check originalEvent is a MouseEvent
688+
expect(e.detail.originalEvent).to.be.instanceOf(MouseEvent);
689+
}).as("clickSpy"));
690+
});
691+
692+
cy.get("#item2")
693+
.realClick();
694+
695+
cy.get("@clickSpy")
696+
.should("have.been.calledOnce");
697+
});
698+
});
699+

packages/main/src/SegmentedButton.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,17 @@ class SegmentedButton extends UI5Element {
223223
return;
224224
}
225225

226+
// Check if preventDefault was called on the native event (e.g., by item's semantic click handler)
227+
if (e.defaultPrevented) {
228+
return;
229+
}
230+
226231
switch (this.selectionMode) {
227232
case SegmentedButtonSelectionMode.Multiple:
228-
if (e instanceof KeyboardEvent) {
229-
target.selected = !target.selected;
230-
}
233+
target.selected = !target.selected;
231234
break;
232235
default:
233-
this._applySingleSelection(target);
236+
this._applySingleSelection(target as unknown as ISegmentedButtonItem);
234237
}
235238

236239
this.fireDecoratorEvent("selection-change", {
@@ -256,7 +259,7 @@ class SegmentedButton extends UI5Element {
256259

257260
_onkeydown(e: KeyboardEvent) {
258261
if (isEnter(e)) {
259-
this._selectItem(e); // Enter key behavior remains unaffected
262+
this._selectItem(e);
260263
} else if (isSpace(e)) {
261264
e.preventDefault(); // Prevent scrolling
262265
this._isSpacePressed = true;

packages/main/src/SegmentedButtonItem.ts

Lines changed: 39 additions & 1 deletion
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+
item: SegmentedButtonItem,
28+
originalEvent: Event,
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+
* @param {SegmentedButtonItem} item The segmented button item that was clicked.
65+
* @param {Event} originalEvent The original DOM event that triggered the click. Use this to access modifier keys (altKey, ctrlKey, metaKey, shiftKey) and other native event properties.
66+
* @since 2.22.0
67+
* @public
68+
*/
69+
@event("click", {
70+
bubbles: true,
71+
cancelable: true,
72+
})
73+
5174
class SegmentedButtonItem extends UI5Element implements IButton, ISegmentedButtonItem {
75+
eventDetails!: {
76+
"click": SegmentedButtonItemClickEventDetail,
77+
}
5278
/**
5379
* Defines whether the component is disabled.
5480
* A disabled component can't be selected or
@@ -196,7 +222,18 @@ class SegmentedButtonItem extends UI5Element implements IButton, ISegmentedButto
196222
return;
197223
}
198224

199-
this.selected = !this.selected;
225+
e.stopImmediatePropagation();
226+
227+
// Fire semantic click event (CustomEvent that bubbles)
228+
const prevented = !this.fireDecoratorEvent("click", {
229+
item: this,
230+
originalEvent: e,
231+
});
232+
233+
if (prevented) {
234+
e.preventDefault();
235+
e.stopPropagation();
236+
}
200237
}
201238

202239
onEnterDOM() {
@@ -253,3 +290,4 @@ class SegmentedButtonItem extends UI5Element implements IButton, ISegmentedButto
253290
SegmentedButtonItem.define();
254291

255292
export default SegmentedButtonItem;
293+
export type { SegmentedButtonItemClickEventDetail };

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

270319
</body>

packages/theming/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"directory": "packages/theming"
3030
},
3131
"dependencies": {
32-
"@sap-theming/theming-base-content": "11.34.1",
32+
"@sap-theming/theming-base-content": "11.35.0",
3333
"@ui5/webcomponents-base": "2.22.0-rc.2"
3434
},
3535
"devDependencies": {

packages/website/docs/_samples/main/Table/Popin/sample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function App() {
5050
e: UI5CustomEvent<SegmentedButtonClass, "selection-change">,
5151
) => {
5252
const selectedItem = e.detail.selectedItems[0];
53-
setPopinState((selectedItem as SegmentedButtonItemClass).tooltip === "Hide Details");
53+
setPopinState((selectedItem as unknown as SegmentedButtonItemClass).tooltip === "Hide Details");
5454
};
5555

5656
return (

yarn.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6064,10 +6064,10 @@ __metadata:
60646064
languageName: node
60656065
linkType: hard
60666066

6067-
"@sap-theming/theming-base-content@npm:11.34.1":
6068-
version: 11.34.1
6069-
resolution: "@sap-theming/theming-base-content@npm:11.34.1"
6070-
checksum: 10c0/70b7db10c8d2f719dd9b310c6e38bf993ea2ea36e394292e47d5ae811643722bb58bfe0ffa34643aba7b41a42cb945e155a5cb5adfa9d76dc8e3413bcd83045a
6067+
"@sap-theming/theming-base-content@npm:11.35.0":
6068+
version: 11.35.0
6069+
resolution: "@sap-theming/theming-base-content@npm:11.35.0"
6070+
checksum: 10c0/9463a675da882550b6f0efa373c77399db9dd3d5aaf2134fca4591a0a8bcee3ebf7fd3a85e8ed9b45c9e30f60bd7ffdd1764d66af426173f0c076ec849b951a8
60716071
languageName: node
60726072
linkType: hard
60736073

@@ -7682,7 +7682,7 @@ __metadata:
76827682
"@custom-elements-manifest/analyzer": "npm:^0.10.10"
76837683
"@lit-labs/ssr-dom-shim": "npm:^1.1.2"
76847684
"@openui5/sap.ui.core": "npm:1.146.0"
7685-
"@sap-theming/theming-base-content": "npm:11.34.1"
7685+
"@sap-theming/theming-base-content": "npm:11.35.0"
76867686
"@ui5/cypress-internal": "npm:0.1.0"
76877687
"@ui5/webcomponents-tools": "npm:2.22.0-rc.2"
76887688
clean-css: "npm:^5.2.2"
@@ -7785,7 +7785,7 @@ __metadata:
77857785
version: 0.0.0-use.local
77867786
resolution: "@ui5/webcomponents-theming@workspace:packages/theming"
77877787
dependencies:
7788-
"@sap-theming/theming-base-content": "npm:11.34.1"
7788+
"@sap-theming/theming-base-content": "npm:11.35.0"
77897789
"@ui5/webcomponents-base": "npm:2.22.0-rc.2"
77907790
"@ui5/webcomponents-tools": "npm:2.22.0-rc.2"
77917791
globby: "npm:^13.1.1"

0 commit comments

Comments
 (0)