From 15b2f747e2db3a8bbb30a8c13506a3fac08581b0 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Fri, 12 Jun 2026 17:00:25 +0300 Subject: [PATCH 01/17] feat(ui5-dialog): add fullscreen toggle button The dialog now supports a fullscreen toggle button in the header, controlled by the `showFullscreenButton` property. The button toggles the `stretch` property and is not available on phone devices. - Keyboard shortcut: Shift+Ctrl+F - Double-click on header toggles fullscreen - Button shows Maximize/Restore tooltip and aria-keyshortcuts - Resets drag/resize state when toggling --- packages/main/cypress/specs/Dialog.cy.tsx | 252 ++++++++++++++++++ packages/main/src/Dialog.ts | 92 ++++++- packages/main/src/DialogTemplate.tsx | 14 + .../main/src/i18n/messagebundle.properties | 6 + packages/main/src/themes/Dialog.css | 13 +- packages/main/test/pages/Dialog.html | 21 ++ .../docs/_components_pages/main/Dialog.mdx | 5 + .../main/Dialog/Fullscreen/Fullscreen.md | 5 + .../_samples/main/Dialog/Fullscreen/main.js | 21 ++ .../main/Dialog/Fullscreen/sample.html | 29 ++ .../main/Dialog/Fullscreen/sample.tsx | 50 ++++ 11 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 packages/website/docs/_samples/main/Dialog/Fullscreen/Fullscreen.md create mode 100644 packages/website/docs/_samples/main/Dialog/Fullscreen/main.js create mode 100644 packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html create mode 100644 packages/website/docs/_samples/main/Dialog/Fullscreen/sample.tsx diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index a9ded9140d87..6b70d8d59fb5 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -1844,3 +1844,255 @@ describe("Native drag-and-drop in draggable dialogs", () => { }); }); }); + +describe("Fullscreen Button", () => { + it("should show fullscreen button when showFullscreenButton is set", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("exist") + .and("be.visible"); + }); + + it("should not show fullscreen button when showFullscreenButton is not set", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("not.exist"); + }); + + it("should toggle stretch property when fullscreen button is clicked", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + + cy.get("#dialog").then($dialog => { + ($dialog.get(0) as Dialog)._toggleFullscreen(); + }); + + cy.get("#dialog") + .should("have.attr", "stretch"); + + cy.get("#dialog").then($dialog => { + ($dialog.get(0) as Dialog)._toggleFullscreen(); + }); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + }); + + it("should show full-screen icon when not stretched and exit-full-screen when stretched", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("have.attr", "icon", "full-screen"); + + cy.get("#dialog").then($dialog => { + ($dialog.get(0) as Dialog)._toggleFullscreen(); + }); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("have.attr", "icon", "exit-full-screen"); + }); + + it("should have correct tooltip based on stretch state", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("have.attr", "tooltip", "Maximize"); + + cy.get("#dialog").then($dialog => { + ($dialog.get(0) as Dialog)._toggleFullscreen(); + }); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("have.attr", "tooltip", "Restore"); + }); + + it("should have aria-keyshortcuts attribute", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .shadow() + .find("button") + .should("have.attr", "aria-keyshortcuts", "Shift+Control+F"); + }); + + it("should toggle fullscreen with Shift+Ctrl+F keyboard shortcut", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + + cy.get("#input").realClick(); + cy.realPress(["Shift", "Control", "f"]); + + cy.get("#dialog") + .should("have.attr", "stretch"); + + cy.realPress(["Shift", "Control", "f"]); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + }); + + it("should toggle fullscreen on header double-click", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + + cy.get("#dialog").then($dialog => { + const dialog = $dialog.get(0) as Dialog; + const headerRoot = dialog.shadowRoot!.querySelector(".ui5-popup-header-root")!; + const event = new MouseEvent("dblclick", { bubbles: true, composed: true }); + Object.defineProperty(event, "target", { value: headerRoot }); + headerRoot.dispatchEvent(event); + }); + + cy.get("#dialog") + .should("have.attr", "stretch"); + }); + + it("should not toggle fullscreen on double-click when showFullscreenButton is false", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-popup-header-root") + .realClick({ clickCount: 2 }); + + cy.get("#dialog") + .should("not.have.attr", "stretch"); + }); + + it("should reset drag/resize state when toggling fullscreen", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog").then($dialog => { + const dialog = $dialog.get(0) as Dialog; + dialog._draggedOrResized = true; + Object.assign(dialog.style, { top: "100px", left: "100px", width: "400px", height: "300px" }); + }); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .realClick(); + + cy.get("#dialog").then($dialog => { + const dialog = $dialog.get(0) as Dialog; + expect(dialog._draggedOrResized).to.be.false; + expect(dialog.style.width).to.equal(""); + expect(dialog.style.height).to.equal(""); + }); + }); + + it("should display header when only showFullscreenButton is set", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-popup-header-root") + .should("exist"); + }); + + it("should reflect stretch state if stretch is initially true", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("have.attr", "icon", "exit-full-screen") + .and("have.attr", "tooltip", "Restore"); + + cy.get("#dialog").invoke("prop", "open", false); + }); +}); diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 9596e0562044..61b56f6f288f 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -10,12 +10,15 @@ import { import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type { I18nText } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import toLowercaseEnumValue from "@ui5/webcomponents-base/dist/util/toLowercaseEnumValue.js"; import Popup from "./Popup.js"; import "@ui5/webcomponents-icons/dist/error.js"; import "@ui5/webcomponents-icons/dist/alert.js"; import "@ui5/webcomponents-icons/dist/sys-enter-2.js"; import "@ui5/webcomponents-icons/dist/information.js"; +import "@ui5/webcomponents-icons/dist/full-screen.js"; +import "@ui5/webcomponents-icons/dist/exit-full-screen.js"; import { DIALOG_HEADER_ARIA_ROLE_DESCRIPTION, @@ -25,6 +28,8 @@ import { DIALOG_HEADER_ARIA_LABEL, DIALOG_CONTENT_ARIA_LABEL, DIALOG_FOOTER_ARIA_LABEL, + DIALOG_FULLSCREEN_MAXIMIZE, + DIALOG_FULLSCREEN_RESTORE, } from "./generated/i18n/i18n-defaults.js"; // Template @@ -39,6 +44,10 @@ import PopupAccessibleRole from "./types/PopupAccessibleRole.js"; */ const STEP_SIZE = 16; +const FULLSCREEN_BUTTON_ACCESSIBILITY_ATTRIBUTES = { + ariaKeyShortcuts: "Shift+Control+F", +}; + type ValueStateWithIcon = ValueState.Negative | ValueState.Critical | ValueState.Positive | ValueState.Information; /** * Defines the icons corresponding to the dialog's state. @@ -169,6 +178,18 @@ class Dialog extends Popup { @property({ type: Boolean }) resizable = false; + /** + * Defines whether a fullscreen toggle button is shown in the dialog header. + * When pressed, it toggles the `stretch` property. + * The fullscreen button is not available on phone devices. + * + * **Note:** The fullscreen toggle can also be triggered by pressing Shift+Ctrl+F or by double-clicking the dialog header. + * @default false + * @public + */ + @property({ type: Boolean }) + showFullscreenButton = false; + /** * Defines the state of the `Dialog`. * @@ -187,6 +208,7 @@ class Dialog extends Popup { _resizeMouseMoveHandler: (e: MouseEvent) => void; _resizeMouseUpHandler: (e: MouseEvent) => void; _dragStartHandler: (e: DragEvent) => void; + _fullscreenKeydownHandler: (e: KeyboardEvent) => void; _y?: number; _x?: number; _isRTL?: boolean; @@ -235,6 +257,7 @@ class Dialog extends Popup { this._resizeMouseUpHandler = this._onResizeMouseUp.bind(this); this._dragStartHandler = this._handleDragStart.bind(this); + this._fullscreenKeydownHandler = this._onFullscreenKeydown.bind(this); } static _isHeader(element: HTMLElement) { @@ -279,7 +302,7 @@ class Dialog extends Popup { * Determines if the header should be shown. */ get _displayHeader() { - return this.header.length || this.headerText || this.draggable || this.resizable; + return this.header.length || this.headerText || this.draggable || this.resizable || this.showFullscreenButton; } get _movable() { @@ -294,6 +317,24 @@ class Dialog extends Popup { return this.resizable && this.onDesktop; } + get _showFullscreenButton() { + return this.showFullscreenButton && !this.onPhone; + } + + get _fullscreenButtonIcon() { + return this.stretch ? "exit-full-screen" : "full-screen"; + } + + get _fullscreenButtonTooltip() { + return this.stretch + ? Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_RESTORE as I18nText) + : Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_MAXIMIZE as I18nText); + } + + get _fullscreenButtonAccessibilityAttributes() { + return FULLSCREEN_BUTTON_ACCESSIBILITY_ATTRIBUTES; + } + get _minHeight() { let minHeight = Number.parseInt(window.getComputedStyle(this.contentDOM).minHeight); @@ -375,11 +416,15 @@ class Dialog extends Popup { _attachBrowserEvents() { this._attachScreenResizeHandler(); this._registerDragHandler(); + if (this.showFullscreenButton) { + document.addEventListener("keydown", this._fullscreenKeydownHandler); + } } _detachBrowserEvents() { this._detachScreenResizeHandler(); this._deregisterDragHandler(); + document.removeEventListener("keydown", this._fullscreenKeydownHandler); } _attachScreenResizeHandler() { @@ -432,6 +477,51 @@ class Dialog extends Popup { /** * Event handlers */ + _toggleFullscreen() { + if (this.onPhone) { + return; + } + + const wasStretched = this.stretch; + this.stretch = !this.stretch; + + this._revertSize(); + this._draggedOrResized = false; + + if (wasStretched) { + requestAnimationFrame(() => { + if (this.open) { + this._center(); + } + }); + } + } + + _onHeaderDblClick(e: MouseEvent) { + if (!this._showFullscreenButton) { + return; + } + + const target = e.target as HTMLElement; + const headerRoot = this._root.querySelector(".ui5-popup-header-root"); + if (target !== headerRoot && !target.classList.contains("ui5-popup-header-text")) { + return; + } + + this._toggleFullscreen(); + } + + _onFullscreenKeydown(e: KeyboardEvent) { + if (this._showFullscreenButton && this._isFullscreenShortcut(e)) { + e.preventDefault(); + this._toggleFullscreen(); + } + } + + _isFullscreenShortcut(e: KeyboardEvent) { + return (e.key === "f" || e.key === "F") && e.ctrlKey && e.shiftKey && !e.altKey; + } + _onDragMouseDown(e: MouseEvent) { // allow dragging only on the header if (!this._movable || !this.draggable || !Dialog._isHeader(e.target as HTMLElement)) { diff --git a/packages/main/src/DialogTemplate.tsx b/packages/main/src/DialogTemplate.tsx index 3575a770d021..226e865ba219 100644 --- a/packages/main/src/DialogTemplate.tsx +++ b/packages/main/src/DialogTemplate.tsx @@ -3,6 +3,7 @@ import type Dialog from "./Dialog.js"; import PopupTemplate from "./PopupTemplate.js"; import Title from "./Title.js"; import Icon from "./Icon.js"; +import Button from "./Button.js"; export default function DialogTemplate(this: Dialog) { return PopupTemplate.call(this, { @@ -24,6 +25,7 @@ function beforeContent(this: Dialog) { tabIndex={this._headerTabIndex} onKeyDown={this._onDragOrResizeKeyDown} onMouseDown={this._onDragMouseDown} + onDblClick={this.showFullscreenButton ? this._onHeaderDblClick : undefined} part="header" // state={this.state} > @@ -36,6 +38,18 @@ function beforeContent(this: Dialog) { {this.headerText} } + {this._showFullscreenButton && + + } + {this.resizable ? this.draggable ? diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index a151c7fc8801..1d23eb93dea5 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -852,6 +852,12 @@ DIALOG_CONTENT_ARIA_LABEL=Content #XACT: ARIA label for the Dialog footer region DIALOG_FOOTER_ARIA_LABEL=Footer + +#XACT: Tooltip for dialog fullscreen button (maximize) +DIALOG_FULLSCREEN_MAXIMIZE=Maximize + +#XACT: Tooltip for dialog fullscreen button (restore) +DIALOG_FULLSCREEN_RESTORE=Restore #XFLD: A colon to separate the "label" from an input. In some languages there might be a different symbol used for such a colon LABEL_COLON=: diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index a99dfbbcb206..fc6790c8432a 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -29,7 +29,7 @@ cursor: move; } -:host([draggable]) .ui5-popup-header-root * { +:host([draggable]) .ui5-popup-header-root *:not(.ui5-dialog-fullscreen-btn) { cursor: auto; } @@ -139,6 +139,17 @@ color: var(--sapButton_Lite_TextColor); } +.ui5-dialog-fullscreen-btn { + position: absolute; + inset-inline-end: 0.5rem; + inset-block-start: 50%; + transform: translateY(-50%); +} + +:host([on-phone]) .ui5-dialog-fullscreen-btn { + display: none; +} + :host::backdrop { background-color: var(--_ui5_popup_block_layer_background); opacity: var(--_ui5_popup_block_layer_opacity); diff --git a/packages/main/test/pages/Dialog.html b/packages/main/test/pages/Dialog.html index 160b30e7151e..e32dee93d90d 100644 --- a/packages/main/test/pages/Dialog.html +++ b/packages/main/test/pages/Dialog.html @@ -35,6 +35,7 @@ +

Open Dialog with Input @@ -82,6 +83,9 @@ Open draggable & resizable dialog

+ Open fullscreen dialog +
+
Open RTL draggable & resizable dialog

@@ -439,6 +443,17 @@ + +

This dialog has a fullscreen toggle button in the header.

+

Press the fullscreen button or use Shift+Ctrl+F to toggle fullscreen mode.

+

You can also double-click the header to toggle.

+ + +
+ Close +
+
+

Move this dialog around the screen by dragging it by its header.

@@ -898,6 +913,10 @@ scrollHelper.style.display = cbScrollable.checked ? "block" : "none"; }); + cbRtl.addEventListener("ui5-change", function () { + document.documentElement.dir = cbRtl.checked ? "rtl" : "ltr"; + }); + let preventClosing = true; btnOpenDialog.addEventListener("click", function () { @@ -1000,6 +1019,8 @@ window["resizable-custom-header-close"].addEventListener("click", function () { window["resizable-dialog-custom-header"].open = false; }); window["draggable-and-resizable-open"].addEventListener("click", function () { window["draggable-and-resizable-dialog"].open = true; }); window["draggable-and-resizable-close"].addEventListener("click", function () { window["draggable-and-resizable-dialog"].open = false; }); + window["fullscreen-open"].addEventListener("click", function () { window["fullscreen-dialog"].open = true; }); + window["fullscreen-close"].addEventListener("click", function () { window["fullscreen-dialog"].open = false; }); window["rtl-draggable-and-resizable-open"].addEventListener("click", function () { window["rtl-draggable-and-resizable-dialog"].open = true; }); window["rtl-draggable-and-resizable-close"].addEventListener("click", function () { window["rtl-draggable-and-resizable-dialog"].open = false; }); window["rtl-maxwidth-resizable-open"].addEventListener("click", function () { window["rtl-maxwidth-resizable-dialog"].open = true; }); diff --git a/packages/website/docs/_components_pages/main/Dialog.mdx b/packages/website/docs/_components_pages/main/Dialog.mdx index 403387ab4706..539c059b0c53 100644 --- a/packages/website/docs/_components_pages/main/Dialog.mdx +++ b/packages/website/docs/_components_pages/main/Dialog.mdx @@ -4,6 +4,7 @@ slug: ../Dialog import Basic from "../../_samples/main/Dialog/Basic/Basic.md"; import DraggableAndResizable from "../../_samples/main/Dialog/DraggableAndResizable/DraggableAndResizable.md"; +import Fullscreen from "../../_samples/main/Dialog/Fullscreen/Fullscreen.md"; import BarInDialog from "../../_samples/main/Dialog/BarInDialog/BarInDialog.md"; import WithState from "../../_samples/main/Dialog/WithState/WithState.md"; @@ -19,6 +20,10 @@ import WithState from "../../_samples/main/Dialog/WithState/WithState.md"; ### Draggable and Resizable +### Fullscreen +Users can toggle between standard and full screen size. The full screen button is positioned top-right in the dialog's title bar. The fullscreen toggle can also be triggered by pressing Shift+Ctrl+F or by double-clicking the dialog header. + + ### Usage of Bar as header/footer The Bar component can be used as header and/or footer of the Dialog diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/Fullscreen.md b/packages/website/docs/_samples/main/Dialog/Fullscreen/Fullscreen.md new file mode 100644 index 000000000000..0c062a836e84 --- /dev/null +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/Fullscreen.md @@ -0,0 +1,5 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; +import react from '!!raw-loader!./sample.tsx'; + + diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js b/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js new file mode 100644 index 000000000000..380d54aca254 --- /dev/null +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js @@ -0,0 +1,21 @@ +import "@ui5/webcomponents/dist/Dialog.js"; +import "@ui5/webcomponents/dist/Button.js"; +import "@ui5/webcomponents/dist/Toolbar.js"; +import "@ui5/webcomponents/dist/ToolbarButton.js"; + +var dialogOpener = document.getElementById("dialogOpener"); +var dialog = document.getElementById("dialog"); +var dialogClosers = [...dialog.querySelectorAll(".dialogCloser")]; + +dialogOpener.accessibilityAttributes = { + hasPopup: "dialog", + controls: dialog.id, +}; +dialogOpener.addEventListener("click", () => { + dialog.open = true; +}); +dialogClosers.forEach(btn => { + btn.addEventListener("click", () => { + dialog.open = false; + }); +}) diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html new file mode 100644 index 000000000000..8e162573f4e6 --- /dev/null +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html @@ -0,0 +1,29 @@ + + + + + + + + Sample + + + + + + Open Fullscreen Dialog + + +
This dialog has a fullscreen toggle button in the header.
+
Click the fullscreen button or press Shift+Ctrl+F to toggle fullscreen mode.
+
You can also double-click the header to toggle.
+ + + +
+ + + + + + diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.tsx b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.tsx new file mode 100644 index 000000000000..9bae0850e0f4 --- /dev/null +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.tsx @@ -0,0 +1,50 @@ +import createReactComponent from "@ui5/webcomponents-base/dist/createReactComponent.js"; +import { useState } from "react"; +import ButtonClass from "@ui5/webcomponents/dist/Button.js"; +import DialogClass from "@ui5/webcomponents/dist/Dialog.js"; +import ToolbarClass from "@ui5/webcomponents/dist/Toolbar.js"; +import ToolbarButtonClass from "@ui5/webcomponents/dist/ToolbarButton.js"; + +const Button = createReactComponent(ButtonClass); +const Dialog = createReactComponent(DialogClass); +const Toolbar = createReactComponent(ToolbarClass); +const ToolbarButton = createReactComponent(ToolbarButtonClass); + +function App() { + const [dialogOpen, setDialogOpen] = useState(false); + + return ( + <> + + + setDialogOpen(false)} + > +
This dialog has a fullscreen toggle button in the header.
+
+ Click the fullscreen button or press Shift+Ctrl+F to toggle fullscreen + mode. +
+
You can also double-click the header to toggle.
+ + setDialogOpen(false)} + /> + +
+ + ); +} + +export default App; From 3aab8a06f8354480e6dbf3727d60b43052813d16 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Fri, 12 Jun 2026 17:38:57 +0300 Subject: [PATCH 02/17] feat(ui5-dialog): add fullscreen toggle button Adds a padding-inline-end to prevents header content from overlapping with the button --- packages/main/src/themes/Dialog.css | 5 +++++ packages/main/src/themes/base/Dialog-parameters.css | 1 + 2 files changed, 6 insertions(+) diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index fc6790c8432a..ad94a14a1944 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -146,6 +146,11 @@ transform: translateY(-50%); } +/* Prevents header content from overlapping the absolutely positioned fullscreen button */ +:host([show-fullscreen-button]) .ui5-popup-header-root { + padding-inline-end: var(--_ui5_dialog_fullscreen_button_offset); +} + :host([on-phone]) .ui5-dialog-fullscreen-btn { display: none; } diff --git a/packages/main/src/themes/base/Dialog-parameters.css b/packages/main/src/themes/base/Dialog-parameters.css index c001a627e2db..b1467c34f572 100644 --- a/packages/main/src/themes/base/Dialog-parameters.css +++ b/packages/main/src/themes/base/Dialog-parameters.css @@ -5,4 +5,5 @@ --_ui5_dialog_header_focus_right_offset: 2px; --_ui5_dialog_header_border_radius: 0px; --_ui5_dialog_header_state_line_height: 0.0625rem; + --_ui5_dialog_fullscreen_button_offset: 2.5rem; } From 8a9133b2a4592fbe28611bff8d6f601bee3f1a14 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Fri, 12 Jun 2026 18:12:23 +0300 Subject: [PATCH 03/17] feat(ui5-dialog): add fullscreen toggle button Lint errors fix --- packages/main/src/Dialog.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 61b56f6f288f..80c7e019ebe5 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -10,7 +10,6 @@ import { import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; -import type { I18nText } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import toLowercaseEnumValue from "@ui5/webcomponents-base/dist/util/toLowercaseEnumValue.js"; import Popup from "./Popup.js"; import "@ui5/webcomponents-icons/dist/error.js"; @@ -327,8 +326,8 @@ class Dialog extends Popup { get _fullscreenButtonTooltip() { return this.stretch - ? Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_RESTORE as I18nText) - : Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_MAXIMIZE as I18nText); + ? Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_RESTORE) + : Dialog.i18nBundle.getText(DIALOG_FULLSCREEN_MAXIMIZE); } get _fullscreenButtonAccessibilityAttributes() { From c7a9443c7a41c481f69d2c0d1171db5a07e7f6bc Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Mon, 15 Jun 2026 13:14:16 +0300 Subject: [PATCH 04/17] feat(ui5-dialog): add fullscreen toggle button Lint and bug fixes - Keyboard shortcut: Shift+Ctrl+F (works regardless of focus) - Double-click on header toggles fullscreen - Button shows Maximize/Restore tooltip and aria-keyshortcuts - Resets drag/resize state when toggling --- packages/main/src/Dialog.ts | 2 +- packages/main/src/i18n/messagebundle.properties | 1 + packages/main/src/themes/Dialog.css | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 80c7e019ebe5..06c09ce71dbb 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -313,7 +313,7 @@ class Dialog extends Popup { } get _showResizeHandle() { - return this.resizable && this.onDesktop; + return this.resizable && this.onDesktop && !this.stretch; } get _showFullscreenButton() { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 1d23eb93dea5..a09c2f24e67d 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -858,6 +858,7 @@ DIALOG_FULLSCREEN_MAXIMIZE=Maximize #XACT: Tooltip for dialog fullscreen button (restore) DIALOG_FULLSCREEN_RESTORE=Restore + #XFLD: A colon to separate the "label" from an input. In some languages there might be a different symbol used for such a colon LABEL_COLON=: diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index ad94a14a1944..c34408c53f72 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -142,8 +142,8 @@ .ui5-dialog-fullscreen-btn { position: absolute; inset-inline-end: 0.5rem; - inset-block-start: 50%; - transform: translateY(-50%); + inset-block: 0; + margin-block: auto; } /* Prevents header content from overlapping the absolutely positioned fullscreen button */ From d77f4e56cc1187c84f9800d02c9e85b6d0086ee8 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Mon, 15 Jun 2026 14:56:55 +0300 Subject: [PATCH 05/17] feat(ui5-dialog): add fullscreen toggle button Removes absolute positioning and and adds fullscreen spacing instead of offset - Keyboard shortcut: Shift+Ctrl+F (works regardless of focus) - Double-click on header toggles fullscreen - Button shows Maximize/Restore tooltip and aria-keyshortcuts - Resets drag/resize state when toggling --- packages/main/src/themes/Dialog.css | 12 +++++------- .../main/src/themes/base/Dialog-parameters.css | 2 +- packages/main/test/pages/Dialog.html | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index c34408c53f72..d0c6cc0ec163 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -139,16 +139,14 @@ color: var(--sapButton_Lite_TextColor); } -.ui5-dialog-fullscreen-btn { - position: absolute; - inset-inline-end: 0.5rem; - inset-block: 0; - margin-block: auto; +.ui5-popup-header-text { + flex: 1; + min-width: 0; + justify-content: flex-start; } -/* Prevents header content from overlapping the absolutely positioned fullscreen button */ :host([show-fullscreen-button]) .ui5-popup-header-root { - padding-inline-end: var(--_ui5_dialog_fullscreen_button_offset); + padding-inline-end: var(--_ui5_dialog_fullscreen_button_inline_end_spacing); } :host([on-phone]) .ui5-dialog-fullscreen-btn { diff --git a/packages/main/src/themes/base/Dialog-parameters.css b/packages/main/src/themes/base/Dialog-parameters.css index b1467c34f572..946a6c447704 100644 --- a/packages/main/src/themes/base/Dialog-parameters.css +++ b/packages/main/src/themes/base/Dialog-parameters.css @@ -5,5 +5,5 @@ --_ui5_dialog_header_focus_right_offset: 2px; --_ui5_dialog_header_border_radius: 0px; --_ui5_dialog_header_state_line_height: 0.0625rem; - --_ui5_dialog_fullscreen_button_offset: 2.5rem; + --_ui5_dialog_fullscreen_button_inline_end_spacing: 0.25rem; } diff --git a/packages/main/test/pages/Dialog.html b/packages/main/test/pages/Dialog.html index e32dee93d90d..5758fd24b02b 100644 --- a/packages/main/test/pages/Dialog.html +++ b/packages/main/test/pages/Dialog.html @@ -86,6 +86,9 @@ Open fullscreen dialog

+ Open fullscreen dialog (long title) +
+
Open RTL draggable & resizable dialog

@@ -454,6 +457,15 @@
+ +

Compare the title position with and without the fullscreen button.

+

With flex:1 approach, the title shifts toward the start instead of being visually centered in the header.

+ +
+ Close +
+
+

Move this dialog around the screen by dragging it by its header.

@@ -1021,6 +1033,8 @@ window["draggable-and-resizable-close"].addEventListener("click", function () { window["draggable-and-resizable-dialog"].open = false; }); window["fullscreen-open"].addEventListener("click", function () { window["fullscreen-dialog"].open = true; }); window["fullscreen-close"].addEventListener("click", function () { window["fullscreen-dialog"].open = false; }); + window["fullscreen-long-title-open"].addEventListener("click", function () { window["fullscreen-long-title"].open = true; }); + window["fullscreen-long-title-close"].addEventListener("click", function () { window["fullscreen-long-title"].open = false; }); window["rtl-draggable-and-resizable-open"].addEventListener("click", function () { window["rtl-draggable-and-resizable-dialog"].open = true; }); window["rtl-draggable-and-resizable-close"].addEventListener("click", function () { window["rtl-draggable-and-resizable-dialog"].open = false; }); window["rtl-maxwidth-resizable-open"].addEventListener("click", function () { window["rtl-maxwidth-resizable-dialog"].open = true; }); From 9660dff8c6d6c1ab94a1ea3a65d1746111d9e39f Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Mon, 15 Jun 2026 15:38:31 +0300 Subject: [PATCH 06/17] feat(ui5-dialog): add fullscreen toggle button Removed unnecessary spacing --- packages/main/src/themes/Dialog.css | 4 ---- packages/main/src/themes/base/Dialog-parameters.css | 1 - 2 files changed, 5 deletions(-) diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index d0c6cc0ec163..2cbd0ef5c52c 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -145,10 +145,6 @@ justify-content: flex-start; } -:host([show-fullscreen-button]) .ui5-popup-header-root { - padding-inline-end: var(--_ui5_dialog_fullscreen_button_inline_end_spacing); -} - :host([on-phone]) .ui5-dialog-fullscreen-btn { display: none; } diff --git a/packages/main/src/themes/base/Dialog-parameters.css b/packages/main/src/themes/base/Dialog-parameters.css index 946a6c447704..c001a627e2db 100644 --- a/packages/main/src/themes/base/Dialog-parameters.css +++ b/packages/main/src/themes/base/Dialog-parameters.css @@ -5,5 +5,4 @@ --_ui5_dialog_header_focus_right_offset: 2px; --_ui5_dialog_header_border_radius: 0px; --_ui5_dialog_header_state_line_height: 0.0625rem; - --_ui5_dialog_fullscreen_button_inline_end_spacing: 0.25rem; } From 1b2bb0683b6471dc56ff69d0a3ff1294da65c889 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Wed, 17 Jun 2026 16:14:10 +0300 Subject: [PATCH 07/17] feat(ui5-dialog): add fullscreen toggle button disable fullscreen button if custom header is provided - Keyboard shortcut: Shift+Ctrl+F (works regardless of focus) - Double-click on header toggles fullscreen - Button shows Maximize/Restore tooltip and aria-keyshortcuts - Resets drag/resize state when toggling --- packages/main/src/Dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 06c09ce71dbb..f655566c7817 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -317,7 +317,7 @@ class Dialog extends Popup { } get _showFullscreenButton() { - return this.showFullscreenButton && !this.onPhone; + return this.showFullscreenButton && !this.onPhone && !this.header.length; } get _fullscreenButtonIcon() { From 72e96d66878aa9b87feee16a639fbe6dd6cd40d7 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 22 Jun 2026 11:18:32 +0300 Subject: [PATCH 08/17] chore: resolve merge conflics --- packages/main/src/DialogTemplate.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/main/src/DialogTemplate.tsx b/packages/main/src/DialogTemplate.tsx index 6880028ffa73..011aa09753a7 100644 --- a/packages/main/src/DialogTemplate.tsx +++ b/packages/main/src/DialogTemplate.tsx @@ -3,6 +3,7 @@ import type Dialog from "./Dialog.js"; import PopupTemplate from "./PopupTemplate.js"; import Title from "./Title.js"; import Icon from "./Icon.js"; +import Button from "./Button.js"; export default function DialogTemplate(this: Dialog) { return PopupTemplate.call(this, { @@ -20,6 +21,7 @@ function beforeContent(this: Dialog) { role="region" aria-label={this._headerAriaLabel} onMouseDown={this._onDragMouseDown} + onDblClick={this.showFullscreenButton ? this._onHeaderDblClick : undefined} part="header" // state={this.state} > @@ -31,6 +33,17 @@ function beforeContent(this: Dialog) { : {this.headerText} } + {this._showFullscreenButton && + + }
} ); From 324a5fd032491400d29b03a39a23d3787b440325 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 22 Jun 2026 14:04:15 +0300 Subject: [PATCH 09/17] chore: fix double click and acc texts --- packages/main/cypress/specs/Dialog.cy.tsx | 2 +- packages/main/src/Dialog.ts | 15 ++++++++++----- packages/main/src/i18n/messagebundle.properties | 4 ++-- packages/main/src/themes/Dialog.css | 5 ++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index 13a03b1e2dff..29071a951a0b 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -1968,7 +1968,7 @@ describe("Fullscreen Button", () => { .find(".ui5-dialog-fullscreen-btn") .shadow() .find("button") - .should("have.attr", "aria-keyshortcuts", "Shift+Control+F"); + .should("have.attr", "aria-keyshortcuts", "Shift+Ctrl+F"); }); it("should toggle fullscreen with Shift+Ctrl+F keyboard shortcut", () => { diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index b349c06ff4a4..2fd6453f416d 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -51,7 +51,7 @@ import PopupAccessibleRole from "./types/PopupAccessibleRole.js"; const STEP_SIZE = 16; const FULLSCREEN_BUTTON_ACCESSIBILITY_ATTRIBUTES = { - ariaKeyShortcuts: "Shift+Control+F", + ariaKeyShortcuts: "Shift+Ctrl+F", }; type ValueStateWithIcon = ValueState.Negative | ValueState.Critical | ValueState.Positive | ValueState.Information; @@ -208,6 +208,13 @@ class Dialog extends Popup { @property() state: `${ValueState}` = "None"; + + /* + * @private + */ + @property({ type: Boolean }) + _showFullscreenButton = false; + _screenResizeHandler: () => void; _dragMouseMoveHandler: (e: MouseEvent) => void; _dragMouseUpHandler: (e: MouseEvent) => void; @@ -383,10 +390,6 @@ class Dialog extends Popup { return this.resizable && this.onDesktop && !this.stretch; } - get _showFullscreenButton() { - return this.showFullscreenButton && !this.onPhone && !this.header.length; - } - get _fullscreenButtonIcon() { return this.stretch ? "exit-full-screen" : "full-screen"; } @@ -465,6 +468,8 @@ class Dialog extends Popup { onBeforeRendering() { super.onBeforeRendering(); + this._showFullscreenButton = this.showFullscreenButton && !this.onPhone && !this.header.length; + this._isRTL = this.effectiveDir === "rtl"; } diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 7985c27dccb7..98ce112d6441 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -875,10 +875,10 @@ DIALOG_CONTENT_ARIA_LABEL=Content DIALOG_FOOTER_ARIA_LABEL=Footer #XACT: Tooltip for dialog fullscreen button (maximize) -DIALOG_FULLSCREEN_MAXIMIZE=Maximize +DIALOG_FULLSCREEN_MAXIMIZE=Maximize (Shift+Ctrl+F) #XACT: Tooltip for dialog fullscreen button (restore) -DIALOG_FULLSCREEN_RESTORE=Restore +DIALOG_FULLSCREEN_RESTORE=Restore (Shift+Ctrl+F) #XFLD: A colon to separate the "label" from an input. In some languages there might be a different symbol used for such a colon LABEL_COLON=: diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index b3ccf1627588..b5f1f2ad1507 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -125,6 +125,10 @@ height: 100%; } +:host([header-text][_show-fullscreen-button]) .ui5-popup-header-root { + justify-content: space-between; +} + .ui5-popup-content { min-height: var(--_ui5_dialog_content_min_height); flex: 1 1 auto; @@ -145,7 +149,6 @@ } .ui5-popup-header-text { - flex: 1; min-width: 0; justify-content: flex-start; } From 5e2a3958f3cbee4edbf69054e58f18876136f8e4 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 22 Jun 2026 15:28:57 +0300 Subject: [PATCH 10/17] chore: fix first focusable element --- packages/main/cypress/specs/Dialog.cy.tsx | 27 +++++++++++++++++++++++ packages/main/src/Dialog.ts | 10 +++++++++ packages/main/src/Popup.ts | 10 ++++++++- packages/main/src/themes/Dialog.css | 4 ++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index 29071a951a0b..41932f6635c4 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -2097,4 +2097,31 @@ describe("Fullscreen Button", () => { cy.get("#dialog").invoke("prop", "open", false); }); + + it("should not focus fullscreen button as initial focus when content has focusable elements", () => { + cy.mount( + + + + ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#content-btn").should("be.focused"); + }); + + it("should focus fullscreen button when no other focusable elements exist", () => { + cy.mount( + +

Non-focusable content

+
+ ); + + cy.get("#dialog").ui5DialogOpened(); + + cy.get("#dialog") + .shadow() + .find(".ui5-dialog-fullscreen-btn") + .should("be.focused"); + }); }); diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 2fd6453f416d..fed3912e585c 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -11,6 +11,7 @@ import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import toLowercaseEnumValue from "@ui5/webcomponents-base/dist/util/toLowercaseEnumValue.js"; +import { getFirstFocusableElement } from "@ui5/webcomponents-base/dist/util/FocusableElements.js"; import Popup from "./Popup.js"; import "@ui5/webcomponents-icons/dist/error.js"; import "@ui5/webcomponents-icons/dist/alert.js"; @@ -873,6 +874,15 @@ class Dialog extends Popup { window.removeEventListener("mouseup", this._resizeMouseUpHandler); } + async _getFirstFocusableElement() { + if (this._showFullscreenButton) { + const firstFocusable = await getFirstFocusableElement(this.contentDOM) || (this.footerDOM ? await getFirstFocusableElement(this.footerDOM) : null); + return firstFocusable || getFirstFocusableElement(this); + } + + return getFirstFocusableElement(this); + } + /** * Overrides Popup's forwardToLast to prioritize the drag/resize handler * when Shift+Tab is pressed from the first focusable element. diff --git a/packages/main/src/Popup.ts b/packages/main/src/Popup.ts index aea30042a49f..a0dbda7c97d1 100644 --- a/packages/main/src/Popup.ts +++ b/packages/main/src/Popup.ts @@ -544,7 +544,7 @@ abstract class Popup extends UI5Element { || document.getElementById(this.initialFocus); } - element = element || await getFirstFocusableElement(this) || this._root; // in case of no focusable content focus the root + element = element || await this._getFirstFocusableElement() || this._root; // in case of no focusable content focus the root if (element) { if (element === this._root) { @@ -554,6 +554,10 @@ abstract class Popup extends UI5Element { } } + async _getFirstFocusableElement() { + return getFirstFocusableElement(this); + } + isFocusWithin() { return isFocusedElementWithinNode(this._root); } @@ -722,6 +726,10 @@ abstract class Popup extends UI5Element { return this.shadowRoot!.querySelector(".ui5-popup-content")!; } + get footerDOM(): HTMLElement | null { + return this.shadowRoot!.querySelector(".ui5-popup-footer-root"); + } + get styles() { return { root: {}, diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index b5f1f2ad1507..4d6731e1190b 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -125,6 +125,10 @@ height: 100%; } +:host(:not([header-text])[_show-fullscreen-button]) .ui5-popup-header-root { + justify-content: flex-end; +} + :host([header-text][_show-fullscreen-button]) .ui5-popup-header-root { justify-content: space-between; } From 94b83d80883d104c3aaaa1bfbff3fe5ef23d5880 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 22 Jun 2026 15:32:37 +0300 Subject: [PATCH 11/17] chore: fix tests --- packages/main/cypress/specs/Dialog.cy.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index 41932f6635c4..b1b3a250f3fa 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -1942,7 +1942,7 @@ describe("Fullscreen Button", () => { cy.get("#dialog") .shadow() .find(".ui5-dialog-fullscreen-btn") - .should("have.attr", "tooltip", "Maximize"); + .should("have.attr", "tooltip", "Maximize (Shift+Ctrl+F)"); cy.get("#dialog").then($dialog => { ($dialog.get(0) as Dialog)._toggleFullscreen(); @@ -1951,7 +1951,7 @@ describe("Fullscreen Button", () => { cy.get("#dialog") .shadow() .find(".ui5-dialog-fullscreen-btn") - .should("have.attr", "tooltip", "Restore"); + .should("have.attr", "tooltip", "Restore (Shift+Ctrl+F)"); }); it("should have aria-keyshortcuts attribute", () => { @@ -2093,7 +2093,7 @@ describe("Fullscreen Button", () => { .shadow() .find(".ui5-dialog-fullscreen-btn") .should("have.attr", "icon", "exit-full-screen") - .and("have.attr", "tooltip", "Restore"); + .and("have.attr", "tooltip", "Restore (Shift+Ctrl+F)"); cy.get("#dialog").invoke("prop", "open", false); }); From 0dfdb023e0dca6965c0a91b4016edf85ec4da500 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 22 Jun 2026 15:38:09 +0300 Subject: [PATCH 12/17] chore: fix lint error --- packages/main/src/Dialog.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index fed3912e585c..6598735967cd 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -209,7 +209,6 @@ class Dialog extends Popup { @property() state: `${ValueState}` = "None"; - /* * @private */ From 3bf1226212fcfc54f486e3c07ec59e58c2956531 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 23 Jun 2026 11:06:55 +0300 Subject: [PATCH 13/17] chore: fix test --- packages/main/cypress/specs/Dialog.cy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index b1b3a250f3fa..55f867362733 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -735,7 +735,7 @@ describe("Dialog general interaction", () => { .shadow() .find(".ui5-popup-resize-handle") .realMouseDown() - .realMouseMove(150, 0) // Additional rightward movement beyond min width + .realMouseMove(350, 0) // Additional rightward movement beyond min width .realMouseUp(); cy.get("#rtl-min-width-dialog").then(dialogAfterExtraResize => { From c63cd204e956c47a4128fa2e59c9c1d6958eaa04 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 24 Jun 2026 13:29:36 +0300 Subject: [PATCH 14/17] chore: fix toggling multiple dialogs --- packages/main/src/Dialog.ts | 11 +++++++---- packages/main/test/pages/Dialog.html | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 6598735967cd..ca108185aef3 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -209,8 +209,8 @@ class Dialog extends Popup { @property() state: `${ValueState}` = "None"; - /* - * @private + /** + * @private */ @property({ type: Boolean }) _showFullscreenButton = false; @@ -349,7 +349,7 @@ class Dialog extends Popup { * Determines if the header should be shown. */ get _displayHeader() { - return this.header.length || this.headerText || this.draggable || this.resizable || this.showFullscreenButton; + return this.header.length || this.headerText || this.draggable || this.resizable || this._showFullscreenButton; } get _movable() { @@ -491,6 +491,7 @@ class Dialog extends Popup { _attachBrowserEvents() { this._attachScreenResizeHandler(); this._registerDragHandler(); + if (this.showFullscreenButton) { document.addEventListener("keydown", this._fullscreenKeydownHandler); } @@ -587,8 +588,10 @@ class Dialog extends Popup { } _onFullscreenKeydown(e: KeyboardEvent) { - if (this._showFullscreenButton && this._isFullscreenShortcut(e)) { + if (this.isTopModalPopup && this._showFullscreenButton && this._isFullscreenShortcut(e)) { e.preventDefault(); + e.stopImmediatePropagation(); + this._toggleFullscreen(); } } diff --git a/packages/main/test/pages/Dialog.html b/packages/main/test/pages/Dialog.html index 5758fd24b02b..6ade247e36c2 100644 --- a/packages/main/test/pages/Dialog.html +++ b/packages/main/test/pages/Dialog.html @@ -451,6 +451,19 @@

Press the fullscreen button or use Shift+Ctrl+F to toggle fullscreen mode.

You can also double-click the header to toggle.

+
+ + Open new fullscreen dialog + + +

This dialog has a fullscreen toggle button in the header.

+

Press the fullscreen button or use Shift+Ctrl+F to toggle fullscreen mode.

+

You can also double-click the header to toggle.

+ +
+ Close +
+
Close @@ -1033,6 +1046,8 @@ window["draggable-and-resizable-close"].addEventListener("click", function () { window["draggable-and-resizable-dialog"].open = false; }); window["fullscreen-open"].addEventListener("click", function () { window["fullscreen-dialog"].open = true; }); window["fullscreen-close"].addEventListener("click", function () { window["fullscreen-dialog"].open = false; }); + window["fullscreen-nested-open"].addEventListener("click", function () { window["fullscreen-dialog-nested"].open = true; }); + window["fullscreen-nested-close"].addEventListener("click", function () { window["fullscreen-dialog-nested"].open = false; }); window["fullscreen-long-title-open"].addEventListener("click", function () { window["fullscreen-long-title"].open = true; }); window["fullscreen-long-title-close"].addEventListener("click", function () { window["fullscreen-long-title"].open = false; }); window["rtl-draggable-and-resizable-open"].addEventListener("click", function () { window["rtl-draggable-and-resizable-dialog"].open = true; }); From a5738fc917d865c2ea750c8beec1ddb2c8ca3023 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 24 Jun 2026 13:37:52 +0300 Subject: [PATCH 15/17] chore: address code comments --- packages/main/src/Dialog.ts | 26 ++++++++++++++----- packages/main/test/pages/Dialog.html | 4 +-- .../_samples/main/Dialog/Fullscreen/main.js | 6 ++--- .../main/Dialog/Fullscreen/sample.html | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index ca108185aef3..c844dfd4208b 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -190,7 +190,9 @@ class Dialog extends Popup { * When pressed, it toggles the `stretch` property. * The fullscreen button is not available on phone devices. * - * **Note:** The fullscreen toggle can also be triggered by pressing Shift+Ctrl+F or by double-clicking the dialog header. + * **Note:** The fullscreen button is not available on phone devices, + * nor when a custom header slot is provided — the application is expected + * to render its own toggle inside the custom header in those cases. * @default false * @public */ @@ -236,6 +238,7 @@ class Dialog extends Popup { _cachedMinHeight?: number; _draggedOrResized = false; _dragHandlerRegistered = false; + _fullscreenKeydownHandlerRegistered = false; /** * Defines the header HTML Element. @@ -491,16 +494,13 @@ class Dialog extends Popup { _attachBrowserEvents() { this._attachScreenResizeHandler(); this._registerDragHandler(); - - if (this.showFullscreenButton) { - document.addEventListener("keydown", this._fullscreenKeydownHandler); - } + this._registerFullscreenKeydownHandler(); } _detachBrowserEvents() { this._detachScreenResizeHandler(); this._deregisterDragHandler(); - document.removeEventListener("keydown", this._fullscreenKeydownHandler); + this._deregisterFullscreenKeydownHandler(); } _attachScreenResizeHandler() { @@ -531,6 +531,20 @@ class Dialog extends Popup { } } + _registerFullscreenKeydownHandler() { + if (this.showFullscreenButton && !this._fullscreenKeydownHandlerRegistered) { + document.addEventListener("keydown", this._fullscreenKeydownHandler); + this._fullscreenKeydownHandlerRegistered = true; + } + } + + _deregisterFullscreenKeydownHandler() { + if (this._fullscreenKeydownHandlerRegistered) { + document.removeEventListener("keydown", this._fullscreenKeydownHandler); + this._fullscreenKeydownHandlerRegistered = false; + } + } + _center() { const height = window.innerHeight - this.offsetHeight, width = window.innerWidth - this.offsetWidth; diff --git a/packages/main/test/pages/Dialog.html b/packages/main/test/pages/Dialog.html index 6ade247e36c2..abd635a4416a 100644 --- a/packages/main/test/pages/Dialog.html +++ b/packages/main/test/pages/Dialog.html @@ -470,10 +470,8 @@
- +

Compare the title position with and without the fullscreen button.

-

With flex:1 approach, the title shifts toward the start instead of being visually centered in the header.

-
Close
diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js b/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js index 380d54aca254..4f897dd252e1 100644 --- a/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/main.js @@ -3,9 +3,9 @@ import "@ui5/webcomponents/dist/Button.js"; import "@ui5/webcomponents/dist/Toolbar.js"; import "@ui5/webcomponents/dist/ToolbarButton.js"; -var dialogOpener = document.getElementById("dialogOpener"); -var dialog = document.getElementById("dialog"); -var dialogClosers = [...dialog.querySelectorAll(".dialogCloser")]; +const dialogOpener = document.getElementById("dialogOpener"); +const dialog = document.getElementById("dialog"); +const dialogClosers = [...dialog.querySelectorAll(".dialogCloser")]; dialogOpener.accessibilityAttributes = { hasPopup: "dialog", diff --git a/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html index 8e162573f4e6..b9ab5882749a 100644 --- a/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html +++ b/packages/website/docs/_samples/main/Dialog/Fullscreen/sample.html @@ -11,7 +11,7 @@ - Open Fullscreen Dialog + Open Dialog with Fullscreen Button
This dialog has a fullscreen toggle button in the header.
From 0ced4f0869a8b0121bd903107e500684b7081b4a Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 24 Jun 2026 14:10:24 +0300 Subject: [PATCH 16/17] chore: address code comments and add tests --- packages/main/cypress/specs/Dialog.cy.tsx | 103 ++++++++++++++-------- packages/main/src/Dialog.ts | 6 ++ packages/main/src/themes/Dialog.css | 4 + 3 files changed, 78 insertions(+), 35 deletions(-) diff --git a/packages/main/cypress/specs/Dialog.cy.tsx b/packages/main/cypress/specs/Dialog.cy.tsx index 55f867362733..e794c9e061e7 100644 --- a/packages/main/cypress/specs/Dialog.cy.tsx +++ b/packages/main/cypress/specs/Dialog.cy.tsx @@ -1651,8 +1651,8 @@ describe("Event Registration", () => { ); // Check that resize handler is not attached when dialog is closed - cy.get("#dialog-resize-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-resize-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.undefined; }); @@ -1661,8 +1661,8 @@ describe("Event Registration", () => { cy.get("#dialog-resize-event").ui5DialogOpened(); // Check that resize handler is attached when dialog is open - cy.get("#dialog-resize-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-resize-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.true; }); @@ -1670,8 +1670,8 @@ describe("Event Registration", () => { cy.get("#dialog-resize-event").invoke("prop", "open", false); // Check that resize handler is detached when dialog is closed - cy.get("#dialog-resize-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-resize-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.false; }); }); @@ -1686,8 +1686,8 @@ describe("Event Registration", () => { ); // Check that dragstart handler is not registered when dialog is closed - cy.get("#dialog-dragstart-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-dragstart-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._dragHandlerRegistered).to.be.false; }); @@ -1696,8 +1696,8 @@ describe("Event Registration", () => { cy.get("#dialog-dragstart-event").ui5DialogOpened(); // Check that dragstart handler is registered when dialog is open - cy.get("#dialog-dragstart-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-dragstart-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._dragHandlerRegistered).to.be.true; }); @@ -1705,8 +1705,8 @@ describe("Event Registration", () => { cy.get("#dialog-dragstart-event").invoke("prop", "open", false); // Check that dragstart handler is deregistered when dialog is closed - cy.get("#dialog-dragstart-event").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-dragstart-event").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._dragHandlerRegistered).to.be.false; }); }); @@ -1725,8 +1725,8 @@ describe("Event Registration", () => { cy.get("#dialog-reopen-events").ui5DialogOpened(); // Verify handlers are registered - cy.get("#dialog-reopen-events").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-reopen-events").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.true; expect(dialog._dragHandlerRegistered).to.be.true; }); @@ -1735,8 +1735,8 @@ describe("Event Registration", () => { cy.get("#dialog-reopen-events").invoke("prop", "open", false); // Verify handlers are deregistered - cy.get("#dialog-reopen-events").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-reopen-events").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.false; expect(dialog._dragHandlerRegistered).to.be.false; }); @@ -1746,8 +1746,8 @@ describe("Event Registration", () => { cy.get("#dialog-reopen-events").ui5DialogOpened(); // Verify handlers are registered again - cy.get("#dialog-reopen-events").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog-reopen-events").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._screenResizeHandlerAttached).to.be.true; expect(dialog._dragHandlerRegistered).to.be.true; }); @@ -1765,8 +1765,8 @@ describe("Native drag-and-drop in draggable dialogs", () => { cy.get("#test-dialog").invoke("prop", "open", true); cy.get("#test-dialog").ui5DialogOpened(); - cy.get("#test-dialog").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#test-dialog").then($dialog => { + const dialog = $dialog.get(0); const content = document.getElementById("content-item"); // Create a mock event @@ -1827,9 +1827,9 @@ describe("Native drag-and-drop in draggable dialogs", () => { cy.get("#test-dialog").invoke("prop", "open", true); cy.get("#test-dialog").ui5DialogOpened(); - cy.get("#custom-header").then($header => { + cy.get("#custom-header").then($header => { const dialog = document.getElementById("test-dialog") as Dialog; - const header = $header.get(0) as HTMLElement; + const header = $header.get(0); // Create a mock event let preventDefaultCalled = false; @@ -1891,15 +1891,15 @@ describe("Fullscreen Button", () => { cy.get("#dialog") .should("not.have.attr", "stretch"); - cy.get("#dialog").then($dialog => { - ($dialog.get(0) as Dialog)._toggleFullscreen(); + cy.get("#dialog").then($dialog => { + $dialog.get(0)._toggleFullscreen(); }); cy.get("#dialog") .should("have.attr", "stretch"); - cy.get("#dialog").then($dialog => { - ($dialog.get(0) as Dialog)._toggleFullscreen(); + cy.get("#dialog").then($dialog => { + $dialog.get(0)._toggleFullscreen(); }); cy.get("#dialog") @@ -1920,8 +1920,8 @@ describe("Fullscreen Button", () => { .find(".ui5-dialog-fullscreen-btn") .should("have.attr", "icon", "full-screen"); - cy.get("#dialog").then($dialog => { - ($dialog.get(0) as Dialog)._toggleFullscreen(); + cy.get("#dialog").then($dialog => { + $dialog.get(0)._toggleFullscreen(); }); cy.get("#dialog") @@ -1944,8 +1944,8 @@ describe("Fullscreen Button", () => { .find(".ui5-dialog-fullscreen-btn") .should("have.attr", "tooltip", "Maximize (Shift+Ctrl+F)"); - cy.get("#dialog").then($dialog => { - ($dialog.get(0) as Dialog)._toggleFullscreen(); + cy.get("#dialog").then($dialog => { + $dialog.get(0)._toggleFullscreen(); }); cy.get("#dialog") @@ -2008,7 +2008,7 @@ describe("Fullscreen Button", () => { .should("not.have.attr", "stretch"); cy.get("#dialog").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + const dialog = $dialog.get(0); const headerRoot = dialog.shadowRoot!.querySelector(".ui5-popup-header-root")!; const event = new MouseEvent("dblclick", { bubbles: true, composed: true }); Object.defineProperty(event, "target", { value: headerRoot }); @@ -2046,8 +2046,8 @@ describe("Fullscreen Button", () => { cy.get("#dialog").ui5DialogOpened(); - cy.get("#dialog").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog").then($dialog => { + const dialog = $dialog.get(0); dialog._draggedOrResized = true; Object.assign(dialog.style, { top: "100px", left: "100px", width: "400px", height: "300px" }); }); @@ -2057,8 +2057,8 @@ describe("Fullscreen Button", () => { .find(".ui5-dialog-fullscreen-btn") .realClick(); - cy.get("#dialog").then($dialog => { - const dialog = $dialog.get(0) as Dialog; + cy.get("#dialog").then($dialog => { + const dialog = $dialog.get(0); expect(dialog._draggedOrResized).to.be.false; expect(dialog.style.width).to.equal(""); expect(dialog.style.height).to.equal(""); @@ -2124,4 +2124,37 @@ describe("Fullscreen Button", () => { .find(".ui5-dialog-fullscreen-btn") .should("be.focused"); }); + + it("Ctrl+Shift+F should only toggle the top dialog when two dialogs with fullscreen button are open", () => { + cy.mount( + <> + + + + + + + + ); + + cy.get("#dialog1").invoke("prop", "open", true); + cy.get("#dialog1").ui5DialogOpened(); + + cy.get("#dialog2").invoke("prop", "open", true); + cy.get("#dialog2").ui5DialogOpened(); + + cy.get("#dialog1").should("not.have.attr", "stretch"); + cy.get("#dialog2").should("not.have.attr", "stretch"); + + cy.get("#input2").realClick(); + cy.realPress(["Shift", "Control", "f"]); + + cy.get("#dialog2").should("have.attr", "stretch"); + cy.get("#dialog1").should("not.have.attr", "stretch"); + + cy.realPress(["Shift", "Control", "f"]); + + cy.get("#dialog2").should("not.have.attr", "stretch"); + cy.get("#dialog1").should("not.have.attr", "stretch"); + }); }); diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index c844dfd4208b..415ca9de2cd9 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -110,6 +110,12 @@ const ICON_PER_STATE: Record = { * - [Shift] + [Up] or [Down] - Decrease/Increase the height of the dialog. * - [Shift] + [Left] or [Right] - Decrease/Increase the width of the dialog. * + * #### Fullscreen + * When the `ui5-dialog` has the `showFullscreenButton` property set to `true`, the user can toggle fullscreen mode + * with the following keyboard shortcut: + * + * - [Shift] + [Ctrl] + [F] - Toggle fullscreen mode. + * * ### ES6 Module Import * * `import "@ui5/webcomponents/dist/Dialog";` diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index 4d6731e1190b..31b25dadfa4e 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -133,6 +133,10 @@ justify-content: space-between; } +.ui5-popup-header-root { + gap: 0.5rem; +} + .ui5-popup-content { min-height: var(--_ui5_dialog_content_min_height); flex: 1 1 auto; From 5dc6eaf3072a22ce797369a293bad0e5631e321a Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 25 Jun 2026 10:10:15 +0300 Subject: [PATCH 17/17] chore: add release version --- packages/main/src/Dialog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/main/src/Dialog.ts b/packages/main/src/Dialog.ts index 415ca9de2cd9..657a057436d3 100644 --- a/packages/main/src/Dialog.ts +++ b/packages/main/src/Dialog.ts @@ -200,6 +200,7 @@ class Dialog extends Popup { * nor when a custom header slot is provided — the application is expected * to render its own toggle inside the custom header in those cases. * @default false + * @since 2.24.0 * @public */ @property({ type: Boolean })