From cfa33f5e769d283eba30d573f5044f3ec4904edf Mon Sep 17 00:00:00 2001 From: Nikolay Hristov Date: Mon, 9 Mar 2026 14:14:49 +0200 Subject: [PATCH 1/3] wip(ui5-view-settings-dialog): add custom tabs (PoC) --- .../cypress/specs/ViewSettingsDialog.cy.tsx | 118 ++++++++++ packages/fiori/src/ViewSettingsCustomTab.ts | 85 +++++++ .../src/ViewSettingsCustomTabTemplate.tsx | 5 + packages/fiori/src/ViewSettingsDialog.ts | 107 ++++++++- .../fiori/src/ViewSettingsDialogTemplate.tsx | 22 ++ packages/fiori/src/bundle.esm.ts | 1 + .../fiori/src/themes/ViewSettingsDialog.css | 29 +++ .../fiori/test/pages/ViewSettingsDialog.html | 207 +++++++++++++++++- .../test/pages/styles/ViewSettingsDialog.css | 27 +++ .../fiori/ViewSettingsDialog/Basic/main.js | 10 + .../ViewSettingsDialog/Basic/sample.html | 32 +++ 11 files changed, 635 insertions(+), 8 deletions(-) create mode 100644 packages/fiori/src/ViewSettingsCustomTab.ts create mode 100644 packages/fiori/src/ViewSettingsCustomTabTemplate.tsx diff --git a/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx b/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx index c19924bf120f..3d56bfdc5986 100644 --- a/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx +++ b/packages/fiori/cypress/specs/ViewSettingsDialog.cy.tsx @@ -3,6 +3,7 @@ import GroupItem from "../../src/GroupItem.js"; import SortItem from "../../src/SortItem.js"; import FilterItem from "../../src/FilterItem.js"; import FilterItemOption from "../../src/FilterItemOption.js"; +import ViewSettingsCustomTab from "../../src/ViewSettingsCustomTab.js"; describe("View settings dialog - confirm event", () => { it("should throw confirm event after selecting sort options and confirm button", () => { @@ -484,4 +485,121 @@ describe("ViewSettingsDialog Tests", () => { cy.get("@items") .should("have.length", 3); }); + + it("should render custom tabs after built-in tabs", () => { + cy.mount( + + + + + + + +
Advanced settings
+
+ +
Metrics settings
+
+
+ ); + + cy.get("#vsdCustomOrder") + .as("vsd") + .invoke("prop", "open", true); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .as("items") + .should("have.length", 5); + + cy.get("@items") + .eq(0) + .should("have.attr", "data-mode", "Sort"); + + cy.get("@items") + .eq(1) + .should("have.attr", "data-mode", "Filter"); + + cy.get("@items") + .eq(2) + .should("have.attr", "data-mode", "Group"); + + cy.get("@items") + .eq(3) + .should("have.attr", "data-mode", "Custom-0"); + + cy.get("@items") + .eq(3) + .should("have.attr", "tooltip", "Advanced"); + + cy.get("@items") + .eq(4) + .should("have.attr", "data-mode", "Custom-1"); + + cy.get("@items") + .eq(3) + .realClick(); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-title") + .should("have.text", "View Settings"); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "Advanced Settings"); + + cy.get("@vsd") + .find("#advanced-tab-content") + .should("be.visible"); + }); + + it("should render only custom tabs when no built-in tabs are provided", () => { + cy.mount( + + +
General content
+
+ +
Extra content
+
+
+ ); + + cy.get("#vsdCustomOnly") + .as("vsd") + .invoke("prop", "open", true); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .should("have.length", 2); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-title") + .should("have.text", "View Settings"); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "General Settings"); + + cy.get("@vsd") + .find("#general-tab-content") + .should("be.visible"); + + cy.get("@vsd") + .shadow() + .find("[ui5-segmented-button-item]") + .eq(1) + .realClick(); + + cy.get("@vsd") + .shadow() + .find(".ui5-vsd-custom-tab-title") + .should("have.text", "Extra Settings"); + }); }); diff --git a/packages/fiori/src/ViewSettingsCustomTab.ts b/packages/fiori/src/ViewSettingsCustomTab.ts new file mode 100644 index 000000000000..a96c4e8dbe78 --- /dev/null +++ b/packages/fiori/src/ViewSettingsCustomTab.ts @@ -0,0 +1,85 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import ViewSettingsCustomTabTemplate from "./ViewSettingsCustomTabTemplate.js"; + +/** + * @class + * + * ### Overview + * + * The `ui5-view-settings-custom-tab` component allows defining custom tabs for the `ui5-view-settings-dialog`. + * + * ### ES6 Module Import + * + * `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` + * + * @constructor + * @extends UI5Element + * @since 2.21.0 + * @public + * @abstract + * @slot {Node[]} default - Defines the custom tab content. + */ +@customElement({ + tag: "ui5-view-settings-custom-tab", + renderer: jsxRenderer, + template: ViewSettingsCustomTabTemplate, +}) +class ViewSettingsCustomTab extends UI5Element { + /** + * Defines the title of the custom tab. + * + * **Note:** It is displayed in the dialog header when this tab is selected. + * @default "" + * @public + */ + @property({ type: String }) + title = ""; + + /** + * Defines the tooltip of the custom tab button. + * + * **Note:** It is shown on the segmented button item. + * @default "" + * @public + */ + @property({ type: String }) + tooltip = ""; + + /** + * Defines the icon of the custom tab. + * + * **Note:** If not provided, the segmented button item is rendered with text. + * @default undefined + * @public + */ + @property() + icon?: string; + + /** + * Defines whether the custom tab is selected initially. + * + * **Note:** If multiple custom tabs are marked as selected, the first one is used. + * @default false + * @public + */ + @property({ type: Boolean }) + selected = false; + + /** + * Defines the custom tab content. + * @public + */ + @slot({ type: Node, "default": true }) + content!: DefaultSlot; + + _individualSlot?: string; +} + +ViewSettingsCustomTab.define(); + +export default ViewSettingsCustomTab; \ No newline at end of file diff --git a/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx new file mode 100644 index 000000000000..8832baedd9ac --- /dev/null +++ b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx @@ -0,0 +1,5 @@ +import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; + +export default function ViewSettingsCustomTabTemplate(this: ViewSettingsCustomTab) { + return ; +} \ No newline at end of file diff --git a/packages/fiori/src/ViewSettingsDialog.ts b/packages/fiori/src/ViewSettingsDialog.ts index 2cfae5ed43fa..e91e8927213a 100644 --- a/packages/fiori/src/ViewSettingsDialog.ts +++ b/packages/fiori/src/ViewSettingsDialog.ts @@ -23,6 +23,7 @@ import "@ui5/webcomponents-icons/dist/nav-back.js"; import type SortItem from "./SortItem.js"; import type FilterItem from "./FilterItem.js"; import type GroupItem from "./GroupItem.js"; +import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; import { VSD_DIALOG_TITLE_SORT, @@ -88,6 +89,8 @@ type VSDInternalSettings = { groupBy: Array, } +const CUSTOM_MODE_PREFIX = "Custom-"; + /** * @class * ### Overview @@ -254,7 +257,7 @@ class ViewSettingsDialog extends UI5Element { * @private */ @property() - _currentMode: `${ViewSettingsDialogMode}` = "Sort"; + _currentMode: string = ViewSettingsDialogMode.Sort; /** * When in Filter By mode, defines whether we need to show the list of keys, or the list with values. @@ -291,6 +294,25 @@ class ViewSettingsDialog extends UI5Element { @slot() groupItems!: Slot; + /** + * Defines custom tabs for the dialog. + * + * The custom tabs are rendered after the built-in tabs (`Sort`, `Filter`, `Group`). + * + * **Note:** If you want to use this slot, you need to import the item: `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` + * @public + * @since 2.21.0 + */ + @slot({ + type: HTMLElement, + individualSlots: true, + invalidateOnChildChange: { + properties: true, + slots: false, + }, + }) + customTabs!: Slot; + @query("[ui5-list]") _list!: List; @@ -328,6 +350,11 @@ class ViewSettingsDialog extends UI5Element { if (this.shouldBuildGroup) { this._currentMode = ViewSettingsDialogMode.Group; + return; + } + + if (this.shouldBuildCustomTabs && (!this.isModeCustom || !this._selectedCustomTab)) { + this._currentMode = this._defaultMode; } } @@ -377,9 +404,54 @@ class ViewSettingsDialog extends UI5Element { return !!this.groupItems.length; } + get shouldBuildCustomTabs() { + return !!this.customTabs.length; + } + get hasPagination() { - const buildConditions = [this.shouldBuildSort, this.shouldBuildFilter, this.shouldBuildGroup]; - return buildConditions.filter(condition => condition).length > 1; + const builtInTabsCount = [this.shouldBuildSort, this.shouldBuildFilter, this.shouldBuildGroup] + .filter(condition => condition) + .length; + + if (this.shouldBuildCustomTabs) { + return builtInTabsCount + this.customTabs.length > 1; + } + + return builtInTabsCount > 1; + } + + get _defaultMode() { + if (this.shouldBuildSort) { + return ViewSettingsDialogMode.Sort; + } + + if (this.shouldBuildFilter) { + return ViewSettingsDialogMode.Filter; + } + + if (this.shouldBuildGroup) { + return ViewSettingsDialogMode.Group; + } + + if (this.shouldBuildCustomTabs) { + return this._customMode(this._defaultCustomTabIndex); + } + + return ViewSettingsDialogMode.Sort; + } + + get _defaultCustomTabIndex() { + const selectedIndex = this.customTabs.findIndex(item => item.selected); + return selectedIndex > -1 ? selectedIndex : 0; + } + + get _selectedCustomTab() { + if (!this.isModeCustom) { + return; + } + + const currentTabIndex = this._getCustomTabIndexByMode(this._currentMode); + return this.customTabs[currentTabIndex]; } get _filterByTitle() { @@ -580,6 +652,10 @@ class ViewSettingsDialog extends UI5Element { return this._currentMode === ViewSettingsDialogMode.Group; } + get isModeCustom() { + return this._currentMode.startsWith(CUSTOM_MODE_PREFIX); + } + get showBackButton() { return this.isModeFilter && this._filterStepTwo; } @@ -613,8 +689,13 @@ class ViewSettingsDialog extends UI5Element { } _handleModeChange(e: CustomEvent) { // use SegmentedButton event when done - const mode: ViewSettingsDialogMode = e.detail.selectedItems[0].getAttribute("data-mode"); - this._currentMode = ViewSettingsDialogMode[mode]; + const mode = e.detail.selectedItems[0].getAttribute("data-mode"); + + if (!mode) { + return; + } + + this._currentMode = mode; } _handleFilterValueItemClick(e: CustomEvent) { @@ -755,7 +836,7 @@ class ViewSettingsDialog extends UI5Element { _restoreConfirmedOnEscape(evt: CustomEvent) { // Dialog#before-close if (evt.detail.escPressed) { this._cancelSettings(); - this._currentMode = ViewSettingsDialogMode.Sort; + this._currentMode = this._defaultMode; this._filterStepTwo = false; } } @@ -776,10 +857,22 @@ class ViewSettingsDialog extends UI5Element { */ _restoreSettings(settings: VSDInternalSettings) { this._currentSettings = JSON.parse(JSON.stringify(settings)); - this._currentMode = ViewSettingsDialogMode.Sort; + this._currentMode = this._defaultMode; this._filterStepTwo = false; } + isCurrentCustomMode(index: number) { + return this._currentMode === this._customMode(index); + } + + _customMode(index: number) { + return `${CUSTOM_MODE_PREFIX}${index}`; + } + + _getCustomTabIndexByMode(mode: string) { + return Number(mode.replace(CUSTOM_MODE_PREFIX, "")); + } + /** * Stores `Sort Order` list as recently used control and its selected item in current state. */ diff --git a/packages/fiori/src/ViewSettingsDialogTemplate.tsx b/packages/fiori/src/ViewSettingsDialogTemplate.tsx index 23a000235087..2d1a16a81803 100644 --- a/packages/fiori/src/ViewSettingsDialogTemplate.tsx +++ b/packages/fiori/src/ViewSettingsDialogTemplate.tsx @@ -92,6 +92,19 @@ function _getSplitButtonItems(this: ViewSettingsDialog) { ); } + if (this.shouldBuildCustomTabs) { + this.customTabs.forEach((customTab, index) => { + buttonItems.push( + {customTab.icon ? undefined : customTab.title} + ); + }); + } + return buttonItems; } @@ -115,6 +128,15 @@ function ViewSettingsDialogTemplateContent(this: ViewSettingsDialog) { ViewSettingsDialogSortAndGroupTemplate.call(this, false) )} + {this.isModeCustom && this._selectedCustomTab && ( +
+ {this._selectedCustomTab.title && ( +
{this._selectedCustomTab.title}
+ )} + +
+ )} + ); } diff --git a/packages/fiori/src/bundle.esm.ts b/packages/fiori/src/bundle.esm.ts index 9ccfbb26b1af..7c3d50df4db9 100644 --- a/packages/fiori/src/bundle.esm.ts +++ b/packages/fiori/src/bundle.esm.ts @@ -64,6 +64,7 @@ import UserMenuAccount from "./UserMenuAccount.js"; import UserMenuItem from "./UserMenuItem.js"; import UserMenuItemGroup from "./UserMenuItemGroup.js"; import ViewSettingsDialog from "./ViewSettingsDialog.js"; +import ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; import Wizard from "./Wizard.js"; testAssets.defaultTexts = { ...testAssets.defaultTexts, ...defaultFioriTexts }; diff --git a/packages/fiori/src/themes/ViewSettingsDialog.css b/packages/fiori/src/themes/ViewSettingsDialog.css index f56c85e57ee6..0fd6387ff10b 100644 --- a/packages/fiori/src/themes/ViewSettingsDialog.css +++ b/packages/fiori/src/themes/ViewSettingsDialog.css @@ -65,6 +65,35 @@ height: 100%; } +.ui5-vsd-custom-tab-content { + width: 100%; + height: 100%; + box-sizing: border-box; + padding: 0.5rem 1rem; + overflow: auto; + min-width: 0; +} + +.ui5-vsd-custom-tab-title { + color: var(--sapList_TableGroupHeaderTextColor); + font-family: var(--sapFontHeaderFamily); + font-size: var(--sapFontHeader6Size); + font-weight: bold; + line-height: 2rem; + min-height: 2rem; + margin: 0; +} + +.ui5-vsd-custom-tab-slot::slotted([ui5-view-settings-custom-tab]) { + display: block; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + font-family: var(--sapFontFamily); + color: var(--sapTextColor); +} + [ui5-li-group]::part(header) { overflow: hidden; } diff --git a/packages/fiori/test/pages/ViewSettingsDialog.html b/packages/fiori/test/pages/ViewSettingsDialog.html index f3efd0fbc425..22440366e36e 100644 --- a/packages/fiori/test/pages/ViewSettingsDialog.html +++ b/packages/fiori/test/pages/ViewSettingsDialog.html @@ -128,6 +128,108 @@

ViewSettingsDialog with dataset properties

+

ViewSettingsDialog with default + custom tabs

+ + VSD - Default + Custom Tabs +

+	
+
+		
+		
+
+		
+		
+		
+
+		
+		
+		
+
+		
+			
+			
+		
+
+		
+			
+			
+		
+
+		
+			
+
+ Compact mode + +
+
+ Live updates + +
+ + Cozy + Compact + +
+
+ + +
+ + List + Grid + + + + + + + +
+
+ +
+ +

ViewSettingsDialog with just custom tabs

+ + VSD - Custom Tabs Only +

+	
+		
+			
+
+ Enable notifications + +
+
+ Auto-save + +
+ + Normal + High + +
+
+ + +
+ + Cards + Table + + + + + + + +
+
+
+ + + + diff --git a/packages/fiori/test/pages/styles/ViewSettingsDialog.css b/packages/fiori/test/pages/styles/ViewSettingsDialog.css index c568381c5893..2b2c9315e0a6 100644 --- a/packages/fiori/test/pages/styles/ViewSettingsDialog.css +++ b/packages/fiori/test/pages/styles/ViewSettingsDialog.css @@ -1,3 +1,30 @@ .viewsettingsdialog1auto { background-color: var(--sapBackgroundColor); } + +.vsd-custom-tab-section { + display: flex; + flex-direction: column; + gap: 0.75rem; + font-family: var(--sapFontFamily); + color: var(--sapTextColor); +} + +.vsd-custom-tab-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} + +.vsd-custom-tab-state { + font-family: Consolas, "Courier New", monospace; + font-size: 0.75rem; + white-space: pre-wrap; + background: var(--sapField_Background); + border: 1px solid var(--sapField_BorderColor); + border-radius: 0.25rem; + padding: 0.5rem; + margin: 0.5rem 0 0.75rem; + max-width: 32rem; +} diff --git a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js index 8a5f641d0c3a..64de0e9b286d 100644 --- a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js +++ b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/main.js @@ -1,10 +1,20 @@ import "@ui5/webcomponents/dist/Button.js"; +import "@ui5/webcomponents/dist/ComboBox.js"; +import "@ui5/webcomponents/dist/ComboBoxItem.js"; +import "@ui5/webcomponents/dist/SegmentedButton.js"; +import "@ui5/webcomponents/dist/SegmentedButtonItem.js"; +import "@ui5/webcomponents/dist/StepInput.js"; +import "@ui5/webcomponents/dist/Switch.js"; +import "@ui5/webcomponents/dist/Title.js"; +import "@ui5/webcomponents-icons/dist/action-settings.js"; +import "@ui5/webcomponents-icons/dist/palette.js"; import "@ui5/webcomponents-fiori/dist/ViewSettingsDialog.js"; import "@ui5/webcomponents-fiori/dist/GroupItem.js"; import "@ui5/webcomponents-fiori/dist/SortItem.js"; import "@ui5/webcomponents-fiori/dist/FilterItem.js"; import "@ui5/webcomponents-fiori/dist/FilterItemOption.js"; +import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js"; var vsdResults = document.getElementById("vsdResults"); diff --git a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html index e0d12e54ea89..0b26b846e574 100644 --- a/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html +++ b/packages/website/docs/_samples/fiori/ViewSettingsDialog/Basic/sample.html @@ -43,6 +43,38 @@ + + +
+
+ Compact mode + +
+
+ Live updates + +
+ + Cozy + Compact + +
+
+ + +
+ + List + Grid + + + + + + + +
+


From 53dcdb9b3a08125f656e22c3339fc20879a0961c Mon Sep 17 00:00:00 2001 From: Nikolay Hristov Date: Mon, 9 Mar 2026 14:40:15 +0200 Subject: [PATCH 2/3] wip(ui5-view-settings-dialog): fix lint errors --- packages/fiori/src/ViewSettingsCustomTab.ts | 168 ++++++++++---------- packages/fiori/src/ViewSettingsDialog.ts | 28 +++- 2 files changed, 105 insertions(+), 91 deletions(-) diff --git a/packages/fiori/src/ViewSettingsCustomTab.ts b/packages/fiori/src/ViewSettingsCustomTab.ts index a96c4e8dbe78..17ba2367327f 100644 --- a/packages/fiori/src/ViewSettingsCustomTab.ts +++ b/packages/fiori/src/ViewSettingsCustomTab.ts @@ -1,85 +1,85 @@ -import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; -import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; -import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; -import property from "@ui5/webcomponents-base/dist/decorators/property.js"; -import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js"; -import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import ViewSettingsCustomTabTemplate from "./ViewSettingsCustomTabTemplate.js"; - -/** - * @class - * - * ### Overview - * - * The `ui5-view-settings-custom-tab` component allows defining custom tabs for the `ui5-view-settings-dialog`. - * - * ### ES6 Module Import - * - * `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` - * - * @constructor - * @extends UI5Element - * @since 2.21.0 - * @public - * @abstract - * @slot {Node[]} default - Defines the custom tab content. - */ -@customElement({ - tag: "ui5-view-settings-custom-tab", - renderer: jsxRenderer, - template: ViewSettingsCustomTabTemplate, -}) -class ViewSettingsCustomTab extends UI5Element { - /** - * Defines the title of the custom tab. - * - * **Note:** It is displayed in the dialog header when this tab is selected. - * @default "" - * @public - */ - @property({ type: String }) - title = ""; - - /** - * Defines the tooltip of the custom tab button. - * - * **Note:** It is shown on the segmented button item. - * @default "" - * @public - */ - @property({ type: String }) - tooltip = ""; - - /** - * Defines the icon of the custom tab. - * - * **Note:** If not provided, the segmented button item is rendered with text. - * @default undefined - * @public - */ - @property() - icon?: string; - - /** - * Defines whether the custom tab is selected initially. - * - * **Note:** If multiple custom tabs are marked as selected, the first one is used. - * @default false - * @public - */ - @property({ type: Boolean }) - selected = false; - - /** - * Defines the custom tab content. - * @public - */ - @slot({ type: Node, "default": true }) - content!: DefaultSlot; - - _individualSlot?: string; -} - -ViewSettingsCustomTab.define(); - +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import ViewSettingsCustomTabTemplate from "./ViewSettingsCustomTabTemplate.js"; + +/** + * @class + * + * ### Overview + * + * The `ui5-view-settings-custom-tab` component allows defining custom tabs for the `ui5-view-settings-dialog`. + * + * ### ES6 Module Import + * + * `import "@ui5/webcomponents-fiori/dist/ViewSettingsCustomTab.js";` + * + * @constructor + * @extends UI5Element + * @since 2.21.0 + * @public + * @abstract + * @slot {Node[]} default - Defines the custom tab content. + */ +@customElement({ + tag: "ui5-view-settings-custom-tab", + renderer: jsxRenderer, + template: ViewSettingsCustomTabTemplate, +}) +class ViewSettingsCustomTab extends UI5Element { + /** + * Defines the title of the custom tab. + * + * **Note:** It is displayed in the dialog header when this tab is selected. + * @default "" + * @public + */ + @property({ type: String }) + title = ""; + + /** + * Defines the tooltip of the custom tab button. + * + * **Note:** It is shown on the segmented button item. + * @default "" + * @public + */ + @property({ type: String }) + tooltip = ""; + + /** + * Defines the icon of the custom tab. + * + * **Note:** If not provided, the segmented button item is rendered with text. + * @default undefined + * @public + */ + @property() + icon?: string; + + /** + * Defines whether the custom tab is selected initially. + * + * **Note:** If multiple custom tabs are marked as selected, the first one is used. + * @default false + * @public + */ + @property({ type: Boolean }) + selected = false; + + /** + * Defines the custom tab content. + * @public + */ + @slot({ type: Node, "default": true }) + content!: DefaultSlot; + + _individualSlot?: string; +} + +ViewSettingsCustomTab.define(); + export default ViewSettingsCustomTab; \ No newline at end of file diff --git a/packages/fiori/src/ViewSettingsDialog.ts b/packages/fiori/src/ViewSettingsDialog.ts index e91e8927213a..2944b4387f4f 100644 --- a/packages/fiori/src/ViewSettingsDialog.ts +++ b/packages/fiori/src/ViewSettingsDialog.ts @@ -89,6 +89,9 @@ type VSDInternalSettings = { groupBy: Array, } +type ViewSettingsCustomMode = `Custom-${number}`; +type ViewSettingsDialogInternalMode = `${ViewSettingsDialogMode}` | ViewSettingsCustomMode; + const CUSTOM_MODE_PREFIX = "Custom-"; /** @@ -257,7 +260,7 @@ class ViewSettingsDialog extends UI5Element { * @private */ @property() - _currentMode: string = ViewSettingsDialogMode.Sort; + _currentMode: ViewSettingsDialogInternalMode = ViewSettingsDialogMode.Sort; /** * When in Filter By mode, defines whether we need to show the list of keys, or the list with values. @@ -420,7 +423,7 @@ class ViewSettingsDialog extends UI5Element { return builtInTabsCount > 1; } - get _defaultMode() { + get _defaultMode(): ViewSettingsDialogInternalMode { if (this.shouldBuildSort) { return ViewSettingsDialogMode.Sort; } @@ -446,7 +449,7 @@ class ViewSettingsDialog extends UI5Element { } get _selectedCustomTab() { - if (!this.isModeCustom) { + if (!this._isCustomMode(this._currentMode)) { return; } @@ -653,7 +656,7 @@ class ViewSettingsDialog extends UI5Element { } get isModeCustom() { - return this._currentMode.startsWith(CUSTOM_MODE_PREFIX); + return this._isCustomMode(this._currentMode); } get showBackButton() { @@ -691,7 +694,7 @@ class ViewSettingsDialog extends UI5Element { _handleModeChange(e: CustomEvent) { // use SegmentedButton event when done const mode = e.detail.selectedItems[0].getAttribute("data-mode"); - if (!mode) { + if (!mode || !this._isValidMode(mode)) { return; } @@ -865,14 +868,25 @@ class ViewSettingsDialog extends UI5Element { return this._currentMode === this._customMode(index); } - _customMode(index: number) { + _customMode(index: number): ViewSettingsCustomMode { return `${CUSTOM_MODE_PREFIX}${index}`; } - _getCustomTabIndexByMode(mode: string) { + _getCustomTabIndexByMode(mode: ViewSettingsCustomMode) { return Number(mode.replace(CUSTOM_MODE_PREFIX, "")); } + _isCustomMode(mode: string): mode is ViewSettingsCustomMode { + return mode.startsWith(CUSTOM_MODE_PREFIX); + } + + _isValidMode(mode: string): mode is ViewSettingsDialogInternalMode { + return mode === ViewSettingsDialogMode.Sort + || mode === ViewSettingsDialogMode.Filter + || mode === ViewSettingsDialogMode.Group + || this._isCustomMode(mode); + } + /** * Stores `Sort Order` list as recently used control and its selected item in current state. */ From 249e13a95f77b6a9dd810a7f507c083bddd17d07 Mon Sep 17 00:00:00 2001 From: Nikolay Hristov Date: Mon, 9 Mar 2026 15:36:42 +0200 Subject: [PATCH 3/3] test(ui5-view-settings-dialog): fix lint errors --- packages/fiori/src/ViewSettingsCustomTab.ts | 2 +- packages/fiori/src/ViewSettingsCustomTabTemplate.tsx | 10 +++++----- packages/fiori/src/ViewSettingsDialog.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/fiori/src/ViewSettingsCustomTab.ts b/packages/fiori/src/ViewSettingsCustomTab.ts index 17ba2367327f..3ff828cc7ec8 100644 --- a/packages/fiori/src/ViewSettingsCustomTab.ts +++ b/packages/fiori/src/ViewSettingsCustomTab.ts @@ -82,4 +82,4 @@ class ViewSettingsCustomTab extends UI5Element { ViewSettingsCustomTab.define(); -export default ViewSettingsCustomTab; \ No newline at end of file +export default ViewSettingsCustomTab; diff --git a/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx index 8832baedd9ac..e79ecf3d7d05 100644 --- a/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx +++ b/packages/fiori/src/ViewSettingsCustomTabTemplate.tsx @@ -1,5 +1,5 @@ -import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; - -export default function ViewSettingsCustomTabTemplate(this: ViewSettingsCustomTab) { - return ; -} \ No newline at end of file +import type ViewSettingsCustomTab from "./ViewSettingsCustomTab.js"; + +export default function ViewSettingsCustomTabTemplate(this: ViewSettingsCustomTab) { + return ; +} diff --git a/packages/fiori/src/ViewSettingsDialog.ts b/packages/fiori/src/ViewSettingsDialog.ts index 2944b4387f4f..5713446e8d69 100644 --- a/packages/fiori/src/ViewSettingsDialog.ts +++ b/packages/fiori/src/ViewSettingsDialog.ts @@ -692,7 +692,7 @@ class ViewSettingsDialog extends UI5Element { } _handleModeChange(e: CustomEvent) { // use SegmentedButton event when done - const mode = e.detail.selectedItems[0].getAttribute("data-mode"); + const mode = (e.detail.selectedItems[0].getAttribute("data-mode") as string | null); if (!mode || !this._isValidMode(mode)) { return; @@ -881,9 +881,9 @@ class ViewSettingsDialog extends UI5Element { } _isValidMode(mode: string): mode is ViewSettingsDialogInternalMode { - return mode === ViewSettingsDialogMode.Sort - || mode === ViewSettingsDialogMode.Filter - || mode === ViewSettingsDialogMode.Group + return mode === (ViewSettingsDialogMode.Sort as string) + || mode === (ViewSettingsDialogMode.Filter as string) + || mode === (ViewSettingsDialogMode.Group as string) || this._isCustomMode(mode); }