Skip to content

Commit

Permalink
fix #1808 JAWS reading dialog title when closing dropdown widget
Browse files Browse the repository at this point in the history
This commit contains a better fix for the problem described in #1797.
When a dropdown widget with waiAria:true is inside a dialog with role=dialog
the dropdown DOM element is no longer appended to the <body> but to the
element that has role=dialog. Therefore, screen readers correctly understand
that the dropdown is not outside the dialog and no longer read the dialog
title when changing the focus after closing the dropdown.

PTR 14068536
  • Loading branch information
divdavem committed Jan 23, 2019
1 parent 111c3c1 commit 40b097e
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 44 deletions.
6 changes: 3 additions & 3 deletions src/aria/popups/container/DomElement.js
Expand Up @@ -186,10 +186,10 @@ module.exports = Aria.classDefinition({
if (base) {
this.$logError(this.BASE_NOT_IMPLEMENTED, ["centerInside"]);
}
var container = this.container;
var geometry = this._getGeometry();
return {
left : Math.floor(container.scrollLeft + (container.clientWidth - size.width) / 2),
top : Math.floor(container.scrollTop + (container.clientHeight - size.height) / 2)
left : Math.floor(geometry.x + (geometry.width - size.width) / 2),
top : Math.floor(geometry.y + (geometry.height - size.height) / 2)
};
}

Expand Down
51 changes: 51 additions & 0 deletions src/aria/popups/container/ViewportElement.js
@@ -0,0 +1,51 @@
/*
* Copyright 2015 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Aria = require("ariatemplates/Aria");
var DomUtils = require("../../utils/Dom");
var Viewport = require("./Viewport");

module.exports = Aria.classDefinition({
$classpath : "aria.popups.container.ViewportElement",
$extends : require("./DomElement"),
$constructor : function (parentContainer) {
this.parentContainer = parentContainer;
var childContainer = parentContainer.ownerDocument.createElement("div");
childContainer.style.position = "absolute";
parentContainer.appendChild(childContainer);
this.$DomElement.$constructor.call(this, childContainer);
this._updateChildPosition();
},
$destructor : function () {
this.parentContainer.removeChild(this.container);
this.parentContainer = null;
this.$DomElement.$destructor.call(this);
},
$prototype : {
_updateChildPosition : function () {
var viewportSize = Viewport.getClientSize();
var parentPosition = DomUtils.calculatePosition(this.parentContainer);
var containerStyle = this.container.style;
containerStyle.left = (-parentPosition.left) + "px";
containerStyle.top = (-parentPosition.top) + "px";
containerStyle.width = viewportSize.width + "px";
containerStyle.height = viewportSize.height + "px";
},

_getGeometry : function () {
this._updateChildPosition();
return this.$DomElement._getGeometry.call(this);
}
}
});
56 changes: 19 additions & 37 deletions src/aria/widgets/form/DropDownTrait.js
Expand Up @@ -15,6 +15,7 @@
var Aria = require("../../Aria");
var DomEvent = require("../../DomEvent");
var ariaPopupsPopup = require("../../popups/Popup");
var ViewportElement = require("../../popups/container/ViewportElement");

/**
* Class whose prototype is intended to be imported for all widgets that use a drop-down popup
Expand Down Expand Up @@ -47,6 +48,7 @@ module.exports = Aria.classDefinition({

var popup = new ariaPopupsPopup();
this._dropdownPopup = popup;
this._popupContainer = this._getPopupContainer();

var onDescription = {
"onAfterOpen" : this._afterDropdownOpen,
Expand All @@ -65,6 +67,7 @@ module.exports = Aria.classDefinition({
});
popup.open({
section : section,
popupContainer: this._popupContainer,
domReference : this._getInputMarkupDomElt(),
preferredPositions : [{
reference : "bottom left",
Expand Down Expand Up @@ -168,52 +171,28 @@ module.exports = Aria.classDefinition({
},

/**
* Removes role="dialog" from all parent elements and stores those elements in this._waiDialogRoleRemoved.
* Returns the value to be passed to the popupContainer property of the popup configuration.
* @return {IPopupContainer|null} IPopupContainer
*/
_removeDialogRole : function () {
if (!this._cfg.waiAria) {
return;
}
var waiDialogRoleRemoved = this._waiDialogRoleRemoved;
if (!waiDialogRoleRemoved) {
this._waiDialogRoleRemoved = waiDialogRoleRemoved = [];
}
var domElt = this.getDom().parentElement;
while (domElt) {
if (domElt.getAttribute("role") === "dialog") {
domElt.removeAttribute("role");
waiDialogRoleRemoved.push(domElt);
}
domElt = domElt.parentElement;
}
},

/**
* Asynchronously restores role="dialog" on all elements in this._waiDialogRoleRemoved
* (and removes those elements from this array).
*/
_restoreDialogRole : function () {
var waiDialogRoleRemoved = this._waiDialogRoleRemoved;
if (!waiDialogRoleRemoved || waiDialogRoleRemoved.length === 0) {
return;
}
setTimeout(function () {
while (waiDialogRoleRemoved.length > 0) {
var domElt = waiDialogRoleRemoved.pop();
// don't restore the attribute if it changed in the mean time:
if (!domElt.hasAttribute("role")) {
domElt.setAttribute("role", "dialog");
_getPopupContainer : function () {
if (this._cfg.waiAria) {
var domElt = this.getDom().parentElement;
while (domElt) {
if (domElt.getAttribute("role") === "dialog") {
// uses the closest parent with role=dialog as the popup container
return new ViewportElement(domElt);
}
domElt = domElt.parentElement;
}
}, 1000);
}
return null;
},

/**
* Callback for the event onAfterOpen raised by the popup.
* @protected
*/
_afterDropdownOpen : function () {
this._removeDialogRole();
this._setPopupOpenProperty(true);
// when the popup is clicked, keep the focus on the right element:
if (this._hasFocus) {
Expand All @@ -230,6 +209,10 @@ module.exports = Aria.classDefinition({
this._setPopupOpenProperty(false);
this._dropdownPopup.$dispose();
this._dropdownPopup = null;
if (this._popupContainer) {
this._popupContainer.$dispose();
this._popupContainer = null;
}
aria.templates.Layout.$unregisterListeners(this);
if (this._keepFocusOnPopupClose) {
this.focus(null, true);
Expand All @@ -243,7 +226,6 @@ module.exports = Aria.classDefinition({

}
this._keepFocus = false;
this._restoreDialogRole();
},

/**
Expand Down
1 change: 0 additions & 1 deletion src/aria/widgets/form/MultiSelect.js
Expand Up @@ -324,7 +324,6 @@ module.exports = Aria.classDefinition({
if (dropDownIcon) {
dropDownIcon.setAttribute("aria-expanded", "true");
}
this._removeDialogRole();
}
},

Expand Down
Expand Up @@ -78,13 +78,11 @@ Aria.classDefinition({
"DropDownLabelForDatePicker",
"Calendar table. Use arrow keys to navigate and space to validate.",
"DatePickerLabel Edit",
"MyDialogTitle dialog", // must be after "DatePickerLabel Edit"
"DropDownLabelForDatePicker",
"AutoCompleteLabel Edit",
"List view Desktop device",
"AutoCompleteLabel Edit",
"Desktop device",
"MyDialogTitle dialog", // must be after "AutoCompleteLabel Edit"
"DropDownLabelForAutoComplete",
"MultiSelectLabel Edit",
"DropDownLabelForMultiSelect",
Expand All @@ -93,7 +91,6 @@ Aria.classDefinition({
"Desktop device check box checked",
"MultiSelectLabel Edit",
"Desktop device",
"MyDialogTitle dialog", // must be after "MultiSelectLabel Edit"
"DropDownLabelForMultiSelect",
"LastFieldLabel Edit"
].join("\n"), this.end, function (response) {
Expand Down
3 changes: 3 additions & 0 deletions test/aria/widgets/wai/dropdown/dialogTitle/Tpl.tpl
Expand Up @@ -23,6 +23,9 @@
waiAria: true,
title: "MyDialogTitle",
modal: true,
movable: true,
resizable: true,
maximizable: true,
macro: "dialogContent",
bind: {
visible: {
Expand Down

0 comments on commit 40b097e

Please sign in to comment.