Widget accessibility improvements

This commit is contained in:
nathangray 2021-05-13 10:01:38 -06:00
parent 5bc74522c5
commit c3bebf9c31
19 changed files with 112 additions and 35 deletions

View File

@ -159,6 +159,9 @@ jQuery(document).ready(function() {
// Only go on if this is a valid key code - call the key handler
if (keyCode != -1)
{
// Check whether the event came from the sidebox - if yes, ignore
if(jQuery(e.target).parents("#egw_fw_sidemenu").length > 0) return;
// Check whether the event came from an input field - if yes, only
// allow function keys (like F1) to be captured by our code
var inInput = _egw_nodeIsInInput(e.target);

View File

@ -111,6 +111,9 @@ var et2_customfields_list = /** @class */ (function (_super) {
if (this.rows && _sender.id && this.rows[_sender.id]) {
return this.rows[_sender.id];
}
if (this.rows && _sender.id && _sender.id.indexOf("_label") && this.rows[_sender.id.replace("_label", "")]) {
return jQuery(this.rows[_sender.id.replace("_label", "")]).prev("td")[0] || null;
}
return _super.prototype.getDOMNode.call(this, _sender);
};
/**
@ -173,6 +176,7 @@ var et2_customfields_list = /** @class */ (function (_super) {
if (!no_skip)
continue;
}
this.rows[id] = cf[0];
if (this.getType() == 'customfields-list') {
// No label, cust widget
attrs.readonly = true;
@ -187,11 +191,10 @@ var et2_customfields_list = /** @class */ (function (_super) {
}
else {
// Label in first column, widget in 2nd
cf.text(field.label + "");
cf = jQuery(document.createElement("td"))
.appendTo(row);
jQuery(document.createElement("td"))
.prependTo(row);
et2_core_widget_1.et2_createWidget("label", { id: id + "_label", value: field.label, for: id }, this);
}
this.rows[id] = cf[0];
// Set any additional attributes set in options, but not for widgets that pass actual options
if (['select', 'radio', 'radiogroup', 'checkbox', 'button'].indexOf(field.type) == -1 && !jQuery.isEmptyObject(field.values)) {
var w = et2_registry[attrs.type ? attrs.type : field.type];
@ -202,7 +205,7 @@ var et2_customfields_list = /** @class */ (function (_super) {
}
}
// Create widget
var widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
var widget = this.widgets[field_name] = et2_core_widget_1.et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
}
// Field is not to be shown
if (!this.options.fields || jQuery.isEmptyObject(this.options.fields) || this.options.fields[field_name] == true) {
@ -492,7 +495,7 @@ var et2_customfields_list = /** @class */ (function (_super) {
// This controls where the button is placed in the DOM
this.rows[button_attrs.id] = cf[0];
// Do not store in the widgets list, one name for multiple widgets would cause problems
/*this.widgets[field_name] = */ et2_createWidget(attrs.type ? attrs.type : field.type, button_attrs, this);
/*this.widgets[field_name] = */ et2_core_widget_1.et2_createWidget(attrs.type ? attrs.type : field.type, button_attrs, this);
}
return false;
}
@ -524,7 +527,7 @@ var et2_customfields_list = /** @class */ (function (_super) {
cf = jQuery(document.createElement("td"))
.appendTo(row);
// Create upload widget
var widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
var widget = this.widgets[field_name] = et2_core_widget_1.et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
// This controls where the widget is placed in the DOM
this.rows[attrs.id] = cf[0];
jQuery(widget.getDOMNode(widget)).css('vertical-align', 'top');
@ -542,7 +545,7 @@ var et2_customfields_list = /** @class */ (function (_super) {
// This controls where the button is placed in the DOM
this.rows[select_attrs.id] = cf[0];
// Do not store in the widgets list, one name for multiple widgets would cause problems
widget = et2_createWidget(select_attrs.type, select_attrs, this);
widget = et2_core_widget_1.et2_createWidget(select_attrs.type, select_attrs, this);
jQuery(widget.getDOMNode(widget)).css('vertical-align', 'top').prependTo(cf);
}
return false;

View File

@ -18,7 +18,7 @@
et2_core_inputWidget;
*/
import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {et2_createWidget, et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_valueWidget} from "./et2_core_valueWidget";
@ -173,10 +173,13 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
{
// Check whether the _sender object exists inside the management array
if(this.rows && _sender.id && this.rows[_sender.id])
{
if (this.rows && _sender.id && this.rows[_sender.id]) {
return this.rows[_sender.id];
}
if (this.rows && _sender.id && _sender.id.indexOf("_label") && this.rows[_sender.id.replace("_label", "")])
{
return jQuery(this.rows[_sender.id.replace("_label","")]).prev("td")[0] || null;
}
return super.getDOMNode(_sender);
}
@ -251,6 +254,7 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
const no_skip = this[setup_function].call(this, field_name, field, attrs);
if(!no_skip) continue;
}
this.rows[id] = cf[0];
if(this.getType() == 'customfields-list') {
// No label, cust widget
@ -267,11 +271,10 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
else
{
// Label in first column, widget in 2nd
cf.text(field.label + "");
cf = jQuery(document.createElement("td"))
.appendTo(row);
jQuery(document.createElement("td"))
.prependTo(row);
et2_createWidget("label",{id: id + "_label", value: field.label,for: id},this);
}
this.rows[id] = cf[0];
// Set any additional attributes set in options, but not for widgets that pass actual options
if(['select','radio','radiogroup','checkbox','button'].indexOf(field.type) == -1 && !jQuery.isEmptyObject(field.values))

View File

@ -2510,6 +2510,7 @@ var et2_nextmatch_header_bar = /** @class */ (function (_super) {
// Set activeFilters to current value
this.nextmatch.activeFilters.search = settings.search;
this.et2_searchbox.set_value(settings.search);
jQuery(this.et2_searchbox.getInputNode()).attr("aria-label", egw.lang("search"));
/**
* Mobile theme specific part for nm header
* nm header has very different behaivior for mobile theme and basically

View File

@ -3240,7 +3240,7 @@ class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INextmatchHe
// Search
this.search_box = jQuery(document.createElement("div"))
.addClass('search')
.prependTo(egwIsMobile()?this.nextmatch.getDOMNode():this.row_div);
.prependTo(egwIsMobile()?this.nextmatch.getDOMNode():this.row_div)
// searchbox widget options
const searchbox_options = {
@ -3259,6 +3259,7 @@ class et2_nextmatch_header_bar extends et2_DOMWidget implements et2_INextmatchHe
this.nextmatch.activeFilters.search = settings.search;
this.et2_searchbox.set_value(settings.search);
jQuery(this.et2_searchbox.getInputNode()).attr("aria-label",egw.lang("search"));
/**
* Mobile theme specific part for nm header
* nm header has very different behaivior for mobile theme and basically

View File

@ -142,6 +142,9 @@ var et2_date = /** @class */ (function (_super) {
this.set_value(null);
}
};
et2_date.prototype.getInputNode = function () {
return this.input_date[0];
};
et2_date.prototype.set_type = function (_type) {
if (_type != this.getType()) {
_super.prototype.setType.call(this, _type);

View File

@ -210,7 +210,12 @@ String: A string in the user\'s date format, or a relative date. Relative dates
}
}
set_type(_type)
getInputNode(): HTMLElement
{
return this.input_date[0];
}
set_type(_type)
{
if(_type != this.getType())
{

View File

@ -33,6 +33,7 @@ require("./et2_core_common");
var et2_core_inheritance_1 = require("./et2_core_inheritance");
var et2_core_widget_1 = require("./et2_core_widget");
var et2_core_baseWidget_1 = require("./et2_core_baseWidget");
var et2_core_inputWidget_1 = require("./et2_core_inputWidget");
/**
* Class which implements the "description" XET-Tag
*/
@ -72,16 +73,26 @@ var et2_description = /** @class */ (function (_super) {
_super.prototype.doLoadingFinished.call(this);
// Get the real id of the 'for' widget
var for_widget = null;
var for_id = "";
if (this.options["for"] && ((for_widget = this.getParent().getWidgetById(this.options.for)) ||
(for_widget = this.getRoot().getWidgetById(this.options.for))) && for_widget && for_widget.id) {
if (for_widget.dom_id) {
this.span.attr("for", for_widget.dom_id);
for_id = for_widget.dom_id;
if (for_widget.instanceOf(et2_core_inputWidget_1.et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode().id) {
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
}
else {
// Target widget is not done yet, need to wait
var tab_deferred = jQuery.Deferred();
window.setTimeout(function () {
this.span.attr("for", for_widget.dom_id);
var _a;
for_id = for_widget.dom_id;
if (for_widget.instanceOf(et2_core_inputWidget_1.et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== ((_a = for_widget.getInputNode()) === null || _a === void 0 ? void 0 : _a.id)) {
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
tab_deferred.resolve();
}.bind(this), 0);
return tab_deferred.promise();

View File

@ -18,6 +18,7 @@ import './et2_core_common';
import {ClassWithAttributes} from "./et2_core_inheritance";
import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
import {et2_baseWidget} from './et2_core_baseWidget'
import {et2_inputWidget} from "./et2_core_inputWidget";
/**
* Class which implements the "description" XET-Tag
@ -158,6 +159,7 @@ export class et2_description extends expose(class et2_description extends et2_ba
// Get the real id of the 'for' widget
var for_widget = null;
let for_id = "";
if (this.options["for"] && (
(for_widget = this.getParent().getWidgetById(this.options.for)) ||
(for_widget = this.getRoot().getWidgetById(this.options.for))
@ -165,14 +167,24 @@ export class et2_description extends expose(class et2_description extends et2_ba
{
if(for_widget.dom_id)
{
this.span.attr("for", for_widget.dom_id);
for_id = for_widget.dom_id;
if(for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode().id)
{
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
}
else
{
// Target widget is not done yet, need to wait
var tab_deferred = jQuery.Deferred();
window.setTimeout(function() {
this.span.attr("for", for_widget.dom_id);
for_id = for_widget.dom_id;
if(for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode()?.id)
{
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
tab_deferred.resolve();
}.bind(this),0);

View File

@ -557,7 +557,8 @@ var et2_link_entry = /** @class */ (function (_super) {
if (typeof self.options.value != 'object')
self.options.value = {};
self.options.value.app = self.app_select.val();
});
})
.attr("aria-label", egw.lang("linkapps"));
var opt_count = 0;
for (var key in this.options.select_options) {
opt_count++;
@ -611,6 +612,8 @@ var et2_link_entry = /** @class */ (function (_super) {
}
}
})
.attr("aria-label", egw.lang("Link search"))
.attr("role", "searchbox")
.appendTo(this.div);
this.set_blur(this.options.blur ? this.options.blur : this.egw().lang("search"), this.search);
// Autocomplete

View File

@ -732,7 +732,8 @@ export class et2_link_entry extends et2_inputWidget
if(typeof self.options.value != 'object') self.options.value = {};
self.options.value.app = self.app_select.val();
});
})
.attr("aria-label", egw.lang("linkapps"));
var opt_count = 0;
for(var key in this.options.select_options)
{
@ -793,6 +794,8 @@ export class et2_link_entry extends et2_inputWidget
}
}
})
.attr("aria-label",egw.lang("Link search"))
.attr("role","searchbox")
.appendTo(this.div);
this.set_blur(this.options.blur ? this.options.blur : this.egw().lang("search"), this.search);

View File

@ -638,6 +638,9 @@ var et2_selectbox = /** @class */ (function (_super) {
allow_single_deselect: this.options.allow_single_deselect,
no_results_text: this.egw().lang('No results match')
});
this.input.siblings().filter(".chzn-container")
.attr("role", "combobox")
.attr("tabindex", this.options.tabindex || "0");
// set autocomplete for search input field to an arbitary value in order to stop it.
this.input.siblings().find('.chzn-search input').attr('autocomplete', 'new-password');
if (this.getType() == 'select-country') {

View File

@ -859,6 +859,9 @@ export class et2_selectbox extends et2_inputWidget
allow_single_deselect: this.options.allow_single_deselect,
no_results_text: this.egw().lang('No results match')
});
this.input.siblings().filter(".chzn-container")
.attr("role","combobox")
.attr("tabindex",this.options.tabindex || "0");
// set autocomplete for search input field to an arbitary value in order to stop it.
this.input.siblings().find('.chzn-search input').attr('autocomplete', 'new-password');
if (this.getType() == 'select-country') {

View File

@ -53,8 +53,9 @@ var et2_tabbox = /** @class */ (function (_super) {
_this.container = jQuery(document.createElement("div"))
.addClass("et2_tabbox");
// Create the upper container for the tab flags
_this.flagContainer = jQuery(document.createElement("div"))
_this.flagContainer = jQuery(document.createElement("ul"))
.addClass("et2_tabheader")
.attr("role", "tablist")
.appendTo(_this.container);
// Create the lower tab container
_this.tabContainer = jQuery(document.createElement("div"))
@ -275,14 +276,14 @@ var et2_tabbox = /** @class */ (function (_super) {
this.flagContainer.empty();
for (var i = 0; i < this.tabData.length; i++) {
var entry = this.tabData[i];
entry.flagDiv = jQuery(document.createElement("span"))
entry.flagDiv = jQuery(document.createElement("li"))
.addClass("et2_tabflag")
.appendTo(this.flagContainer);
// Class to tab's div container
if (entry.widget_options && typeof entry.widget_options.class != 'undefined') {
entry.flagDiv.addClass(entry.widget_options.class);
}
entry.flagDiv.text(entry.label || "Tab");
entry.flagDiv.html("<a href=\"#\" role='tab'>" + (entry.label || "Tab") + "</a>");
if (entry.hidden || this.tabData.length === 1) {
entry.flagDiv.hide();
}
@ -293,6 +294,7 @@ var et2_tabbox = /** @class */ (function (_super) {
}
entry.contentDiv = jQuery(document.createElement("div"))
.addClass("et2_tabcntr")
.attr("role", "tabpanel")
.appendTo(this.tabContainer);
if (this.options.align_tabs == 'v') {
entry.flagDiv.unbind('click');
@ -341,12 +343,15 @@ var et2_tabbox = /** @class */ (function (_super) {
et2_tabbox.prototype.setActiveTab = function (_idx) {
this.selected_index = _idx;
// Remove the "active" flag from all tabs-flags
jQuery(".et2_tabflag", this.flagContainer).removeClass("active");
jQuery(".et2_tabflag", this.flagContainer).removeClass("active")
.attr("aria-selected", "false");
// Hide all tab containers
this.tabContainer.children().hide();
// Set the tab flag with the given index active and show the corresponding
// container
this.flagContainer.children(":eq(" + _idx + ")").addClass("active");
this.flagContainer.children(":eq(" + _idx + ")")
.addClass("active")
.attr("aria-selected", "true");
this.tabContainer.children(":eq(" + _idx + ")").show();
// lookup for nm children and trigger a resize, since nm inside inactive
// tabs are not getting render due to tab's deffer loading.

View File

@ -74,8 +74,9 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e
.addClass("et2_tabbox");
// Create the upper container for the tab flags
this.flagContainer = jQuery(document.createElement("div"))
this.flagContainer = jQuery(document.createElement("ul"))
.addClass("et2_tabheader")
.attr("role","tablist")
.appendTo(this.container);
// Create the lower tab container
@ -350,7 +351,7 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e
for (var i = 0; i < this.tabData.length; i++)
{
var entry = this.tabData[i];
entry.flagDiv = jQuery(document.createElement("span"))
entry.flagDiv = jQuery(document.createElement("li"))
.addClass("et2_tabflag")
.appendTo(this.flagContainer);
// Class to tab's div container
@ -358,7 +359,7 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e
{
entry.flagDiv.addClass(entry.widget_options.class);
}
entry.flagDiv.text(entry.label || "Tab");
entry.flagDiv.html("<a href=\"#\" role='tab'>" + (entry.label || "Tab") + "</a>");
if(entry.hidden || this.tabData.length === 1)
{
entry.flagDiv.hide();
@ -371,6 +372,7 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e
}
entry.contentDiv = jQuery(document.createElement("div"))
.addClass("et2_tabcntr")
.attr("role","tabpanel")
.appendTo(this.tabContainer);
if (this.options.align_tabs == 'v') {
entry.flagDiv.unbind('click');
@ -429,14 +431,17 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e
this.selected_index = _idx;
// Remove the "active" flag from all tabs-flags
jQuery(".et2_tabflag", this.flagContainer).removeClass("active");
jQuery(".et2_tabflag", this.flagContainer).removeClass("active")
.attr("aria-selected","false");
// Hide all tab containers
this.tabContainer.children().hide();
// Set the tab flag with the given index active and show the corresponding
// container
this.flagContainer.children(":eq(" + _idx + ")").addClass("active");
this.flagContainer.children(":eq(" + _idx + ")")
.addClass("active")
.attr("aria-selected","true");
this.tabContainer.children(":eq(" + _idx + ")").show();
// lookup for nm children and trigger a resize, since nm inside inactive

View File

@ -122,7 +122,6 @@ var et2_taglist = /** @class */ (function (_super) {
noSuggestionText: this.egw().lang("No suggestions"),
required: this.options.required,
allowFreeEntries: this.options.allowFreeEntries,
useTabKey: true,
useCommaKey: this.options.useCommaKey,
disabled: this.options.disabled || this.options.readonly,
editable: !(this.options.disabled || this.options.readonly),
@ -152,6 +151,7 @@ var et2_taglist = /** @class */ (function (_super) {
}
this.taglist = this.taglist.magicSuggest(this.taglist_options);
this.$taglist = jQuery(this.taglist);
this.div.find("input").attr("id", this.dom_id);
if (this.options.value) {
this.taglist.addToSelection(this.options.value, true);
}
@ -399,6 +399,9 @@ var et2_taglist = /** @class */ (function (_super) {
}
return label;
};
et2_taglist.prototype.getInputNode = function () {
return this.div ? this.div.filter("input")[0] : null;
};
et2_taglist.prototype.set_autocomplete_url = function (source) {
if (source && source[0] != '/' && source.indexOf('http') != 0) {
source = this.egw().ajaxUrl(source);

View File

@ -244,7 +244,6 @@ export class et2_taglist extends et2_selectbox implements et2_IResizeable
noSuggestionText: this.egw().lang("No suggestions"),
required: this.options.required,
allowFreeEntries: this.options.allowFreeEntries,
useTabKey: true,
useCommaKey: this.options.useCommaKey,
disabled: this.options.disabled || this.options.readonly,
editable: !(this.options.disabled || this.options.readonly),
@ -278,6 +277,7 @@ export class et2_taglist extends et2_selectbox implements et2_IResizeable
this.taglist = this.taglist.magicSuggest(this.taglist_options);
this.$taglist = jQuery(this.taglist);
this.div.find("input").attr("id",this.dom_id);
if(this.options.value)
{
this.taglist.addToSelection(this.options.value,true);
@ -574,6 +574,11 @@ export class et2_taglist extends et2_selectbox implements et2_IResizeable
return label;
}
getInputNode(): HTMLElement
{
return this.div ? this.div.filter("input")[0] : null;
}
set_autocomplete_url(source)
{
if(source && source[0] != '/' && source.indexOf('http') != 0)

View File

@ -800,6 +800,7 @@ link common en Link
link is appended to mail allowing recipients to download currently attached version of files common en Link is appended to mail allowing recipients to download currently attached version of files
link is appended to mail allowing recipients to download or modify up to date version of files (epl only) common en Link is appended to mail allowing recipients to download or modify up to date version of files (EPL only)
link is appended to mail allowing recipients to download up to date version of files common en Link is appended to mail allowing recipients to download up to date version of files
link search common en Link search
link target %1 not found! common en Link target %1 not found!
linkapps common en Link apps
linked common en Linked

View File

@ -1178,6 +1178,10 @@ div.et2_vfsPath li img {
-moz-user-select: none;
user-select: none;
}
.et2_tabflag a:link, .et2_tabflag a:visited, .et2_tabflag a:hover {
color: inherit;
text-decoration: none;
}
.et2_tabflag:hover {
color: #050505;
border: 1px solid gray;