Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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("#draggable-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
101 changes: 84 additions & 17 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 ariaDescribedByHeaderTextResizable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_RESIZABLE);
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 ariaDescribedByHeaderTextDraggable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE);
get ariaDescribedByTextResizable() {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_RESIZABLE);
}

get ariaDescribedByHeaderTextDraggableAndResizable() {
return Dialog.i18nBundle.getText(DIALOG_HEADER_ARIA_DESCRIBEDBY_DRAGGABLE_RESIZABLE);
get ariaDescribedByTextDraggable() {
return Dialog.i18nBundle.getText(DIALOG_ARIA_DESCRIBEDBY_DRAGGABLE);
}

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

/**
Expand All @@ -287,13 +315,47 @@ class Dialog extends Popup {
}

get _headerTabIndex() {
return undefined;
}

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 +551,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"
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