Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d81db39
feat(ui5-dialog): focus goes on entire dialog for drag and resize ope…
s-todorova Jun 3, 2026
1ee962c
fix: cypress tests update
s-todorova Jun 4, 2026
be35d42
Merge remote-tracking branch 'origin/main' into dialog-resize-handler…
s-todorova Jun 4, 2026
3800212
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 5, 2026
c3dff78
fix: cypress tests
s-todorova Jun 5, 2026
7f7bad7
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 5, 2026
2d95d70
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 8, 2026
6aed7b8
fix:merge conflicts
s-todorova Jun 9, 2026
7d7894f
fix: correct merge
s-todorova Jun 9, 2026
a39e62a
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 10, 2026
fa3e44c
chore: address review comments
s-todorova Jun 10, 2026
7b68d5c
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 10, 2026
7f1fed6
fix: cypress test
s-todorova Jun 10, 2026
0a030c3
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 10, 2026
16c06c0
fix: test assertion
s-todorova Jun 10, 2026
732aa7e
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 11, 2026
7c8de6a
fix: test assertion
s-todorova Jun 11, 2026
2b5e016
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 11, 2026
00a65fb
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 11, 2026
20de21f
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 12, 2026
a559f07
Merge branch 'main' into dialog-resize-handler-acc
s-todorova Jun 15, 2026
151e5cb
fix: address review comments
s-todorova Jun 15, 2026
90e7b66
Merge remote-tracking branch 'origin/main' into dialog-resize-handler…
s-todorova Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions packages/main/cypress/specs/Dialog.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -580,11 +580,13 @@ describe("Dialog general interaction", () => {
const initialTop = parseInt(dialog.css("top"));
const initialLeft = parseInt(dialog.css("left"));

// Act - Move dialog up using keyboard
cy.get("#header-slot").realClick();
// Act - Focus the drag/resize handle and move dialog up
cy.get("#draggable-dialog").shadow().find(".ui5-popup-drag-resize-handler")
.focus()
.should("be.focused");

cy.get("#header-slot").focused().realPress("{uparrow}");
cy.get("#header-slot").focused().realPress("{uparrow}");
cy.realPress("{uparrow}");
cy.realPress("{uparrow}");

// Assert - Top position changes, left remains the same

Expand All @@ -599,10 +601,8 @@ describe("Dialog general interaction", () => {
})

// Act - Move dialog left using keyboard
cy.get("#header-slot").realClick();

cy.get("#header-slot").focused().realPress("{leftarrow}");
cy.get("#header-slot").focused().realPress("{leftarrow}");
cy.realPress("{leftarrow}");
cy.realPress("{leftarrow}");

// Assert - Left position changes, top remains the same
cy.get("#draggable-dialog")
Expand Down Expand Up @@ -776,9 +776,12 @@ describe("Dialog general interaction", () => {
const initialTop = parseInt(dialog.css("top"));
const initialLeft = parseInt(dialog.css("left"));

// Act - Resize height using keyboard
cy.get("#resizable-dialog").shadow().find(".ui5-popup-resize-handle").click();
cy.get("#resizable-dialog").realPress(["Shift", "ArrowDown"]);
// Act - Focus the drag/resize handle and resize height
cy.get("#resizable-dialog").shadow().find(".ui5-popup-drag-resize-handler")
.focus()
.should("be.focused");

cy.realPress(["Shift", "ArrowDown"]);

// Assert - Height changes, width and position remain the same
cy.get("#resizable-dialog").then(dialogAfterResizeHeight => {
Expand All @@ -791,8 +794,7 @@ describe("Dialog general interaction", () => {
expect(leftAfterResizeHeight).to.equal(initialLeft);

// Act - Resize width using keyboard
cy.get("#resizable-dialog").shadow().find(".ui5-popup-resize-handle").click();
cy.get("#resizable-dialog").realPress(["Shift", "ArrowRight"]);
cy.realPress(["Shift", "ArrowRight"]);

// Assert - Width changes, height and position remain the same
cy.get("#resizable-dialog").then(dialogAfterResizeWidth => {
Expand Down Expand Up @@ -1078,30 +1080,33 @@ describe("Acc", () => {
cy.get("#draggable-dialog").invoke("attr", "open", true);
cy.get<Dialog>("#draggable-dialog").ui5DialogOpened();

// Assert aria-labelledby and aria attributes
// Assert aria-label on the dialog root
cy.get("#draggable-dialog")
.shadow()
.find(".ui5-popup-root")
.should("have.attr", "aria-label", "Draggable");

// Assert aria-describedby is on the drag/resize handle, not the header
cy.get("#draggable-dialog")
.shadow()
.find(".ui5-popup-header-root")
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-describedby");

// Assert hidden text contains keyboard instructions
cy.get("#draggable-dialog")
.shadow()
.find(".ui5-hidden-text")
.should("exist")
.then(hiddenText => {
const valueOfTheHiddenText = hiddenText.text();
const valueOfTheHiddenText = hiddenText.first().text();
cy.wrap(valueOfTheHiddenText).should("equal", "Use Arrow keys to move");
});

// Assert aria-roledescription on the drag/resize handle
cy.get("#draggable-dialog")
.shadow()
.find(".ui5-popup-header-root")
.should("have.attr", "aria-roledescription", "Interactive Header");
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-roledescription", "Handle");
});

it("tests aria-describedby for default header", () => {
Expand All @@ -1119,26 +1124,27 @@ describe("Acc", () => {
cy.get("#resizable-dialog").invoke("attr", "open", true);
cy.get<Dialog>("#resizable-dialog").ui5DialogOpened();

// Assert aria-describedby and aria-roledescription attributes
// Assert aria-describedby is on the drag/resize handle
cy.get("#resizable-dialog")
.shadow()
.find(".ui5-popup-header-root")
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-describedby")
.then($el => {
cy.get("#resizable-dialog")
.shadow()
.find(".ui5-hidden-text")
.should("exist")
.then(hiddenText => {
const valueOfTheHiddenText = hiddenText.text();
const valueOfTheHiddenText = hiddenText.first().text();
cy.wrap(valueOfTheHiddenText).should("equal", "Use Shift+Arrow keys to resize");
});
});

// Assert aria-roledescription on the drag/resize handle
cy.get("#resizable-dialog")
.shadow()
.find(".ui5-popup-header-root")
.should("have.attr", "aria-roledescription", "Interactive Header");
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-roledescription", "Handle");

});

Expand All @@ -1157,26 +1163,27 @@ describe("Acc", () => {
cy.get("#resizable-dialog-custom-header").invoke("attr", "open", true);
cy.get<Dialog>("#resizable-dialog-custom-header").ui5DialogOpened();

// Assert aria-describedby and aria-roledescription attributes
// Assert aria-describedby is on the drag/resize handle
cy.get("#resizable-dialog-custom-header")
.shadow()
.find(".ui5-popup-header-root")
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-describedby")
.then($el => {
cy.get("#resizable-dialog-custom-header")
.shadow()
.find(".ui5-hidden-text")
.should("exist")
.then(hiddenText => {
const valueOfTheHiddenText = hiddenText.text();
const valueOfTheHiddenText = hiddenText.first().text();
cy.wrap(valueOfTheHiddenText).should("equal", "Use Shift+Arrow keys to resize");
});
});

// Assert aria-roledescription on the drag/resize handle
cy.get("#resizable-dialog-custom-header")
.shadow()
.find(".ui5-popup-header-root")
.should("have.attr", "aria-roledescription", "Interactive Header");
.find(".ui5-popup-drag-resize-handler")
.should("have.attr", "aria-roledescription", "Handle");
});

it("tests accessibleName-ref", () => {
Expand Down
99 changes: 81 additions & 18 deletions packages/main/src/Dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ import "@ui5/webcomponents-icons/dist/sys-enter-2.js";
import "@ui5/webcomponents-icons/dist/information.js";

import {
DIALOG_HEADER_ARIA_ROLE_DESCRIPTION,
DIALOG_HEADER_ARIA_DESCRIBEDBY_RESIZABLE,
DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE,
DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE_RESIZABLE,
DIALOG_ARIA_DESCRIBEDBY_RESIZABLE,
DIALOG_ARIA_DESCRIBEDBY_DRAGGABLE,
DIALOG_ARIA_DESCRIBEDBY_DRAGGABLE_RESIZABLE,
DIALOG_ARIA_DESCRIBEDBY_REACH_DRAGGABLE_RESIZABLE,
DIALOG_ARIA_DESCRIBEDBY_REACH_DRAGGABLE,
DIALOG_ARIA_DESCRIBEDBY_REACH_RESIZABLE,
DIALOG_RESIZE_HANDLE_TOOLTIP,
DIALOG_DRAG_AND_RESIZE_HANDLE_ARIA_LABEL,
DIALOG_DRAG_HANDLE_ARIA_LABEL,
DIALOG_RESIZE_HANDLE_ARIA_LABEL,
DIALOG_HANDLE_ARIA_ROLEDESCRIPTION,
DIALOG_HEADER_ARIA_LABEL,
DIALOG_CONTENT_ARIA_LABEL,
DIALOG_FOOTER_ARIA_LABEL,
Expand Down Expand Up @@ -81,14 +88,14 @@ const ICON_PER_STATE: Record<ValueStateWithIcon, string> = {
* ### Keyboard Handling
*
* #### Basic Navigation
* When the `ui5-dialog` has the `draggable` property set to `true` and the header is focused, the user can move the dialog
* When the `ui5-dialog` has the `draggable` property set to `true`, the user can move the dialog
* with the following keyboard shortcuts:
*
* - [Up] or [Down] arrow keys - Move the dialog up/down.
* - [Left] or [Right] arrow keys - Move the dialog left/right.
*
* #### Resizing
* When the `ui5-dialog` has the `resizable` property set to `true` and the header is focused, the user can change the size of the dialog
* When the `ui5-dialog` has the `resizable` property set to `true`, the user can change the size of the dialog
* with the following keyboard shortcuts:
*
* - [Shift] + [Up] or [Down] - Decrease/Increase the height of the dialog.
Expand Down Expand Up @@ -255,24 +262,45 @@ class Dialog extends Popup {
return ariaLabelledById;
}

get ariaRoleDescriptionHeaderText() {
return (this.resizable || this.draggable) ? Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_ROLE_DESCRIPTION) : undefined;
get effectiveAriaDescribedBy() {
return this._movable ? `${this._id}-dialog-descr` : undefined;
}

get effectiveAriaDescribedBy() {
return (this.resizable || this.draggable) ? `${this._id}-descr` : undefined;
get ariaDescribedByIds() {
return [
this.ariaDescriptionTextId,
this.effectiveAriaDescribedBy,
].filter(Boolean).join(" ");
}

get dialogAriaDescribedByText() {
if (!this._movable) {
return "";
}

if (this.resizable && this.draggable) {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_REACH_DRAGGABLE_RESIZABLE);
}
if (this.draggable) {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_REACH_DRAGGABLE);
}
if (this.resizable) {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_REACH_RESIZABLE);
}

return "";
}

get ariaDescribedByHeaderTextResizable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_RESIZABLE);
get ariaDescribedByTextResizable() {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_RESIZABLE);
}

get ariaDescribedByHeaderTextDraggable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE);
get ariaDescribedByTextDraggable() {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_DRAGGABLE);
}

get ariaDescribedByHeaderTextDraggableAndResizable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE_RESIZABLE);
get ariaDescribedByTextDraggableAndResizable() {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_DRAGGABLE_RESIZABLE);
}

/**
Expand All @@ -286,14 +314,44 @@ class Dialog extends Popup {
return !this.stretch && this.onDesktop && (this.draggable || this.resizable);
}

get _headerTabIndex() {
get _dragResizeHandleTabIndex() {
return this._movable ? 0 : undefined;
}

get _dragResizeHandleAriaLabel() {
if (!this._movable) {
return "";
}

if (this.resizable && this.draggable) {
return Dialog.i18nBundle.getText(DIALOG_DRAG_AND_RESIZE_HANDLE_ARIA_LABEL);
}
if (this.draggable) {
return Dialog.i18nBundle.getText(DIALOG_DRAG_HANDLE_ARIA_LABEL);
}
if (this.resizable) {
return Dialog.i18nBundle.getText(DIALOG_RESIZE_HANDLE_ARIA_LABEL);
}

return "";
}

get _dragResizeHandleAriaRoleDescription() {
return this._movable ? Dialog.i18nBundle.getText(DIALOG_HANDLE_ARIA_ROLEDESCRIPTION) : undefined;
}

get _dragResizeHandleAriaDescribedBy() {
return this._movable ? `${this._id}-descr` : undefined;
}

get _showResizeHandle() {
return this.resizable && this.onDesktop;
}

get _resizeHandleTooltip() {
return this._showResizeHandle ? Dialog.i18nBundle.getText(DIALOG_RESIZE_HANDLE_TOOLTIP) : undefined;
}

get _minHeight() {
let minHeight = Number.parseInt(window.getComputedStyle(this.contentDOM).minHeight);

Expand Down Expand Up @@ -489,7 +547,12 @@ class Dialog extends Popup {
}

_onDragOrResizeKeyDown(e: KeyboardEvent) {
if (!this._movable || !Dialog._isHeader(e.target as HTMLElement)) {
if (!this._movable) {
return;
}

const target = e.target as HTMLElement;
if (!target || target.id !== `${this._id}-dragResizeHandler`) {
return;
}

Expand Down
41 changes: 27 additions & 14 deletions packages/main/src/DialogTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ function beforeContent(this: Dialog) {
id="ui5-popup-header"
role="region"
aria-label={this._headerAriaLabel}
aria-describedby={this.effectiveAriaDescribedBy}
aria-roledescription={this.ariaRoleDescriptionHeaderText}
tabIndex={this._headerTabIndex}
onKeyDown={this._onDragOrResizeKeyDown}
onMouseDown={this._onDragMouseDown}
part="header"
// state={this.state}
Expand All @@ -35,16 +31,6 @@ function beforeContent(this: Dialog) {
:
<Title level="H1" id="ui5-popup-header-text" class="ui5-popup-header-text">{this.headerText}</Title>
}

{this.resizable ?
this.draggable ?
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByHeaderTextDraggableAndResizable}</span>
:
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByHeaderTextResizable}</span>
:
this.draggable &&
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByHeaderTextDraggable}</span>
}
</div>
}
</>);
Expand All @@ -66,9 +52,36 @@ function afterContent(this: Dialog) {
<div
class="ui5-popup-resize-handle"
onMouseDown={this._onResizeMouseDown}
title={this._resizeHandleTooltip}
>
<Icon name={resizeCorner}></Icon>
</div>
}
{this._movable &&
<>
<span
id={`${this._id}-dragResizeHandler`}
class="ui5-popup-drag-resize-handler ui5-hidden-text"
tabIndex={this._dragResizeHandleTabIndex}
role="img"
aria-roledescription={this._dragResizeHandleAriaRoleDescription}
aria-label={this._dragResizeHandleAriaLabel}
aria-describedby={this._dragResizeHandleAriaDescribedBy}
onKeyDown={this._onDragOrResizeKeyDown}
></span>
{this.resizable ?
this.draggable ?
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByTextDraggableAndResizable}</span>
:
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByTextResizable}</span>
:
this.draggable &&
<span id={`${this._id}-descr`} aria-hidden="true" class="ui5-hidden-text">{this.ariaDescribedByTextDraggable}</span>
}
{this.dialogAriaDescribedByText &&
<span id={`${this._id}-dialog-descr`} aria-hidden="true" class="ui5-hidden-text">{this.dialogAriaDescribedByText}</span>
}
</>
}
</>);
}
Loading
Loading