diff --git a/packages/main/cypress/specs/ToolbarSelect.cy.tsx b/packages/main/cypress/specs/ToolbarSelect.cy.tsx index f7c1fef6a698c..45ee9c7aa3685 100644 --- a/packages/main/cypress/specs/ToolbarSelect.cy.tsx +++ b/packages/main/cypress/specs/ToolbarSelect.cy.tsx @@ -111,6 +111,243 @@ describe("Toolbar general interaction", () => { .should("have.attr", "accessible-name-ref", "title"); }); + it("Should expose value and selectedToolbarOption in change event detail", () => { + cy.mount( + <> + + + Option 1 + Option 2 + Option 3 + + + + + + ); + + cy.get("[ui5-toolbar-select]").then($select => { + $select.get(0).addEventListener("ui5-change", (e: Event) => { + const ce = e as CustomEvent; + (document.querySelector("[data-testid='result-value']") as HTMLInputElement).value = ce.detail.selectedOption.value; + (document.querySelector("[data-testid='result-toolbar-option']") as HTMLInputElement).value = ce.detail.selectedToolbarOption.value; + }); + }); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realClick(); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowUp"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("Enter"); + + cy.get("[data-testid='result-value']").should("have.prop", "value", "opt1"); + cy.get("[data-testid='result-toolbar-option']").should("have.prop", "value", "opt1"); + }); + + it("Should expose selectedToolbarOption as the actual element reference with correct id and value", () => { + cy.mount( + <> + + + First + Second + Third + + +
+ + ); + + cy.get("[ui5-toolbar-select]").then($select => { + $select.get(0).addEventListener("ui5-change", (e: Event) => { + const ce = e as CustomEvent; + const selectedToolbarOption = ce.detail.selectedToolbarOption as HTMLElement; + const testDiv = document.querySelector("[data-testid='selected-element-id']") as HTMLElement; + testDiv.setAttribute("data-selected-id", selectedToolbarOption.id); + testDiv.setAttribute("data-selected-value", (selectedToolbarOption as any).value || ""); + }); + }); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realClick(); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowDown"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("Enter"); + + cy.get("[data-testid='selected-element-id']") + .should("have.attr", "data-selected-id", "opt-3") + .should("have.attr", "data-selected-value", "value3"); + }); + + it("Should correctly identify selectedToolbarOption when option text does not match value", () => { + cy.mount( + <> + + + Display Text 1 + Display Text 2 + Display Text 3 + + + + + + ); + + cy.get("[ui5-toolbar-select]").then($select => { + $select.get(0).addEventListener("ui5-change", (e: Event) => { + const ce = e as CustomEvent; + const selectedToolbarOption = ce.detail.selectedToolbarOption as any; + (document.querySelector("[data-testid='value-match-test']") as HTMLInputElement).value = selectedToolbarOption.value || ""; + (document.querySelector("[data-testid='text-match-test']") as HTMLInputElement).value = selectedToolbarOption.textContent?.trim() || ""; + }); + }); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realClick(); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowDown"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("Enter"); + + cy.get("[data-testid='value-match-test']").should("have.prop", "value", "internal_3"); + cy.get("[data-testid='text-match-test']").should("have.prop", "value", "Display Text 3"); + }); + + it("Should expose correct selectedToolbarOption for duplicate text options", () => { + cy.mount( + <> + + + Pending + Active + Pending + + + + + ); + + cy.get("[ui5-toolbar-select]").then($select => { + $select.get(0).addEventListener("ui5-change", (e: Event) => { + const ce = e as CustomEvent; + (document.querySelector("[data-testid='dup-result-value']") as HTMLInputElement).value = ce.detail.selectedToolbarOption.value; + }); + }); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realClick(); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowDown"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowDown"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("Enter"); + + cy.get("[data-testid='dup-result-value']").should("have.prop", "value", "pending-urgent"); + }); + + it("Should work correctly when options have no value property set", () => { + cy.mount( + <> + + + Option A + Option B + Option C + + + + + ); + + cy.get("[ui5-toolbar-select]").then($select => { + $select.get(0).addEventListener("ui5-change", (e: Event) => { + const ce = e as CustomEvent; + (document.querySelector("[data-testid='no-value-result']") as HTMLInputElement).value = ce.detail.selectedToolbarOption.textContent?.trim() ?? ""; + }); + }); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realClick(); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("ArrowDown"); + + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .realPress("Enter"); + + cy.get("[data-testid='no-value-result']").should("have.prop", "value", "Option B"); + + // Verify the internal select still shows the correct option selected + cy.get("[ui5-toolbar]") + .find("[ui5-toolbar-select]") + .shadow() + .find("[ui5-select]") + .find("[ui5-option]") + .eq(1) + .should("have.attr", "selected"); + }); + it("Should fire change event on selection change", () => { cy.mount( <> diff --git a/packages/main/src/ToolbarSelect.ts b/packages/main/src/ToolbarSelect.ts index 5885a16ade5f3..74fde918b1adf 100644 --- a/packages/main/src/ToolbarSelect.ts +++ b/packages/main/src/ToolbarSelect.ts @@ -15,7 +15,9 @@ import type ToolbarSelectOption from "./ToolbarSelectOption.js"; import type { SelectChangeEventDetail } from "./Select.js"; import type { DefaultSlot, Slot } from "@ui5/webcomponents-base/dist/UI5Element.js"; -type ToolbarSelectChangeEventDetail = ToolbarItemEventDetail & SelectChangeEventDetail; +type ToolbarSelectChangeEventDetail = ToolbarItemEventDetail & SelectChangeEventDetail & { + selectedToolbarOption: ToolbarSelectOption | undefined; +}; /** * @class @@ -44,6 +46,8 @@ type ToolbarSelectChangeEventDetail = ToolbarItemEventDetail & SelectChangeEvent /** * Fired when the selected option changes. * @param {HTMLElement} selectedOption the selected option. + * @param {HTMLElement} selectedToolbarOption the original toolbar select option. + * @since 2.25.0 * @public */ @event("change", { @@ -193,16 +197,17 @@ class ToolbarSelect extends ToolbarItemBase { onChange(e: CustomEvent): void { e.stopImmediatePropagation(); - const prevented = !this.fireDecoratorEvent("change", { ...e.detail, targetRef: e.target as HTMLElement }); + const selectedOptionIndex = Number(e.detail.selectedOption?.getAttribute("data-ui5-external-action-item-index")); + const selectedToolbarOption = this.options[selectedOptionIndex]; + const prevented = !this.fireDecoratorEvent("change", { ...e.detail, targetRef: e.target as HTMLElement, selectedToolbarOption }); if (!prevented) { this.fireDecoratorEvent("close-overflow"); } - this._syncOptions(e.detail.selectedOption); + this._syncOptions(selectedOptionIndex); } - _syncOptions(selectedOption: HTMLElement): void { - const selectedOptionIndex = Number(selectedOption?.getAttribute("data-ui5-external-action-item-index")); + _syncOptions(selectedOptionIndex: number): void { this.options.forEach((option: ToolbarSelectOption, index: number) => { option.selected = index === selectedOptionIndex; }); diff --git a/packages/main/src/ToolbarSelectOption.ts b/packages/main/src/ToolbarSelectOption.ts index d89aed22e6975..9c324c8fa70a5 100644 --- a/packages/main/src/ToolbarSelectOption.ts +++ b/packages/main/src/ToolbarSelectOption.ts @@ -19,6 +19,15 @@ import type ToolbarSelect from "./ToolbarSelect.js"; */ @customElement("ui5-toolbar-select-option") class ToolbarSelectOption extends UI5Element { + /** + * Defines the value of the component. + * @default undefined + * @public + * @since 2.25.0 + */ + @property() + value?: string; + /** * Defines the selected state of the component. * @default false @@ -47,7 +56,7 @@ class ToolbarSelectOption extends UI5Element { } }); if (parent.select) { - parent.select.value = this.textContent || ""; + parent.select.value = this.value !== undefined && this.value !== "" ? this.value : (this.textContent || ""); } } } diff --git a/packages/main/src/ToolbarSelectTemplate.tsx b/packages/main/src/ToolbarSelectTemplate.tsx index 28e1f2c1dc3e6..b1ffef24f03b2 100644 --- a/packages/main/src/ToolbarSelectTemplate.tsx +++ b/packages/main/src/ToolbarSelectTemplate.tsx @@ -25,6 +25,7 @@ export default function ToolbarSelectTemplate(this: ToolbarSelect) { {this.options.map((option, index) => (