From c3bebf9c312e9eb87218545bc9a1618c57dcb134 Mon Sep 17 00:00:00 2001 From: nathangray Date: Thu, 13 May 2021 10:01:38 -0600 Subject: [PATCH] Widget accessibility improvements --- api/js/egw_action/egw_keymanager.js | 3 +++ .../etemplate/et2_extension_customfields.js | 19 +++++++++++-------- .../etemplate/et2_extension_customfields.ts | 17 ++++++++++------- api/js/etemplate/et2_extension_nextmatch.js | 1 + api/js/etemplate/et2_extension_nextmatch.ts | 3 ++- api/js/etemplate/et2_widget_date.js | 3 +++ api/js/etemplate/et2_widget_date.ts | 7 ++++++- api/js/etemplate/et2_widget_description.js | 15 +++++++++++++-- api/js/etemplate/et2_widget_description.ts | 16 ++++++++++++++-- api/js/etemplate/et2_widget_link.js | 5 ++++- api/js/etemplate/et2_widget_link.ts | 5 ++++- api/js/etemplate/et2_widget_selectbox.js | 3 +++ api/js/etemplate/et2_widget_selectbox.ts | 3 +++ api/js/etemplate/et2_widget_tabs.js | 15 ++++++++++----- api/js/etemplate/et2_widget_tabs.ts | 15 ++++++++++----- api/js/etemplate/et2_widget_taglist.js | 5 ++++- api/js/etemplate/et2_widget_taglist.ts | 7 ++++++- api/lang/egw_en.lang | 1 + api/templates/default/etemplate2.css | 4 ++++ 19 files changed, 112 insertions(+), 35 deletions(-) diff --git a/api/js/egw_action/egw_keymanager.js b/api/js/egw_action/egw_keymanager.js index 3b899ba629..c7a1b619d0 100644 --- a/api/js/egw_action/egw_keymanager.js +++ b/api/js/egw_action/egw_keymanager.js @@ -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); diff --git a/api/js/etemplate/et2_extension_customfields.js b/api/js/etemplate/et2_extension_customfields.js index 3b0acbc012..b6465455a3 100644 --- a/api/js/etemplate/et2_extension_customfields.js +++ b/api/js/etemplate/et2_extension_customfields.js @@ -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; diff --git a/api/js/etemplate/et2_extension_customfields.ts b/api/js/etemplate/et2_extension_customfields.ts index 4acea52451..e44dd782e9 100644 --- a/api/js/etemplate/et2_extension_customfields.ts +++ b/api/js/etemplate/et2_extension_customfields.ts @@ -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)) diff --git a/api/js/etemplate/et2_extension_nextmatch.js b/api/js/etemplate/et2_extension_nextmatch.js index 3ef574ab91..49523a786c 100644 --- a/api/js/etemplate/et2_extension_nextmatch.js +++ b/api/js/etemplate/et2_extension_nextmatch.js @@ -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 diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index c4d18e4aff..df614dee34 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -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 diff --git a/api/js/etemplate/et2_widget_date.js b/api/js/etemplate/et2_widget_date.js index 09ebf2e4c5..56fb6518d4 100644 --- a/api/js/etemplate/et2_widget_date.js +++ b/api/js/etemplate/et2_widget_date.js @@ -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); diff --git a/api/js/etemplate/et2_widget_date.ts b/api/js/etemplate/et2_widget_date.ts index 80f5a6b609..2f04daaa4b 100644 --- a/api/js/etemplate/et2_widget_date.ts +++ b/api/js/etemplate/et2_widget_date.ts @@ -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()) { diff --git a/api/js/etemplate/et2_widget_description.js b/api/js/etemplate/et2_widget_description.js index 3fc352bbda..5207c5894a 100644 --- a/api/js/etemplate/et2_widget_description.js +++ b/api/js/etemplate/et2_widget_description.js @@ -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(); diff --git a/api/js/etemplate/et2_widget_description.ts b/api/js/etemplate/et2_widget_description.ts index dcfa5aadc4..4e6438a317 100644 --- a/api/js/etemplate/et2_widget_description.ts +++ b/api/js/etemplate/et2_widget_description.ts @@ -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); diff --git a/api/js/etemplate/et2_widget_link.js b/api/js/etemplate/et2_widget_link.js index 16e5761d0b..e4d0220a9b 100644 --- a/api/js/etemplate/et2_widget_link.js +++ b/api/js/etemplate/et2_widget_link.js @@ -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 diff --git a/api/js/etemplate/et2_widget_link.ts b/api/js/etemplate/et2_widget_link.ts index 4b1ec23ae2..3089a684d2 100644 --- a/api/js/etemplate/et2_widget_link.ts +++ b/api/js/etemplate/et2_widget_link.ts @@ -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); diff --git a/api/js/etemplate/et2_widget_selectbox.js b/api/js/etemplate/et2_widget_selectbox.js index 293a207f0d..bac0f06bf3 100644 --- a/api/js/etemplate/et2_widget_selectbox.js +++ b/api/js/etemplate/et2_widget_selectbox.js @@ -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') { diff --git a/api/js/etemplate/et2_widget_selectbox.ts b/api/js/etemplate/et2_widget_selectbox.ts index 7743c6a18e..d4c4ae0f4d 100644 --- a/api/js/etemplate/et2_widget_selectbox.ts +++ b/api/js/etemplate/et2_widget_selectbox.ts @@ -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') { diff --git a/api/js/etemplate/et2_widget_tabs.js b/api/js/etemplate/et2_widget_tabs.js index afc1821fdf..f655a9c046 100644 --- a/api/js/etemplate/et2_widget_tabs.js +++ b/api/js/etemplate/et2_widget_tabs.js @@ -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("" + (entry.label || "Tab") + ""); 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. diff --git a/api/js/etemplate/et2_widget_tabs.ts b/api/js/etemplate/et2_widget_tabs.ts index 5236ebf2aa..34c5e23932 100644 --- a/api/js/etemplate/et2_widget_tabs.ts +++ b/api/js/etemplate/et2_widget_tabs.ts @@ -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("" + (entry.label || "Tab") + ""); 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 diff --git a/api/js/etemplate/et2_widget_taglist.js b/api/js/etemplate/et2_widget_taglist.js index 90569638ad..822816f79c 100644 --- a/api/js/etemplate/et2_widget_taglist.js +++ b/api/js/etemplate/et2_widget_taglist.js @@ -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); diff --git a/api/js/etemplate/et2_widget_taglist.ts b/api/js/etemplate/et2_widget_taglist.ts index 4eb9b624e9..78f55c50ac 100644 --- a/api/js/etemplate/et2_widget_taglist.ts +++ b/api/js/etemplate/et2_widget_taglist.ts @@ -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) diff --git a/api/lang/egw_en.lang b/api/lang/egw_en.lang index 0d8d0be166..c24cdb1b26 100644 --- a/api/lang/egw_en.lang +++ b/api/lang/egw_en.lang @@ -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 diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css index 55711fd57a..db30807f02 100644 --- a/api/templates/default/etemplate2.css +++ b/api/templates/default/etemplate2.css @@ -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;