diff --git a/CHANGELOG b/CHANGELOG index a232a534d4..0891b69f65 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add accesskey to widgets and buttons * Support Binary field in CSV import/export Version 6.0.0 - 2021-05-03 diff --git a/src/common.js b/src/common.js index 43de84d9c3..7bfea82dae 100644 --- a/src/common.js +++ b/src/common.js @@ -849,6 +849,9 @@ 'class': 'badge' })); } + this.el.attr( + 'accesskey', + Sao.common.accesskey(attributes.string || '')); } this.icon = this.el.children('img'); if (!this.icon.length) { @@ -3859,6 +3862,16 @@ return string.slice(0, length - ellipsis.length) + ellipsis; }; + Sao.common.accesskey = function(string) { + for (var i=0; i < string.length; i++) { + var c = string.charAt(i).toLowerCase(); + // Skip sao and browser shortcuts + if (!~['d', 'e', 'f', 'i', 'n', 't', 'w'].indexOf(c)) { + return c; + } + } + }; + Sao.common.debounce = function(func, wait) { return function() { var args = [].slice(arguments); diff --git a/src/sao.js b/src/sao.js index e4bea63646..1a09c86c44 100644 --- a/src/sao.js +++ b/src/sao.js @@ -1076,6 +1076,12 @@ var Sao = {}; callback: function() { shortcuts_dialog(); }, + }, { + shortcut: 'ctrl+f1', + label: Sao.i18n.gettext('Show/Hide access keys'), + callback: function() { + jQuery('html').toggleClass('accesskey'); + }, }, ]; } diff --git a/src/sao.less b/src/sao.less index f30d758c68..1cdf0c1535 100644 --- a/src/sao.less +++ b/src/sao.less @@ -188,6 +188,46 @@ html[theme="default"] { .table-row-variant(warning; @state-warning-bg); .table-row-variant(danger; @state-danger-bg); +html.accesskey { + + *[accesskey]+span[data-accesskey]:after, + *[accesskey]:after { + background-color: @brand-info; + color: #fff; + font-size: x-small; + padding: 0 2px; + position: absolute; + text-transform: uppercase; + margin: 0 -1em; + z-index: 5; + } + + input[accesskey][type="checkbox"]:after { + background-color: initial; + } + + *[accesskey]+span[data-accesskey] { + float: right; + position: relative; + + &:after { + content: attr(data-accesskey); + } + } + + *[accesskey]:after { + content: attr(accesskey); + } + + input[accesskey][type="checkbox"], + input[accesskey][type="checkbox"], + select[accesskey] { + &+span[data-accesskey] { + bottom: 1em; + } + } +} + .tab-form, .tab-board { nav.toolbar { position: -webkit-sticky; diff --git a/src/view/form.js b/src/view/form.js index 22fb3732ed..40a65ade6e 100644 --- a/src/view/form.js +++ b/src/view/form.js @@ -84,9 +84,17 @@ function eval_pyson(value){ if (this._mnemonics[name] && widget.labelled) { var label = this._mnemonics[name]; + var accesskey = Sao.common.accesskey(label.label_el.text()); label.label_el.uniqueId(); widget.labelled.uniqueId(); widget.labelled.attr('aria-labelledby', label.el.attr('id')); + widget.labelled.attr('accesskey', accesskey); + if (~['INPUT', 'SELECT'].indexOf( + widget.labelled.get(0).tagName)) { + jQuery('', { + 'data-accesskey': accesskey, + }).insertAfter(widget.labelled); + } label.label_el.attr('for', widget.labelled.attr('id')); } }, @@ -1025,6 +1033,7 @@ function eval_pyson(value){ }, set_label: function(name, domains, counter) { this.label.text(name); + this.el.attr('accesskey', Sao.common.accesskey(name)); if (domains.length) { domains.map(function(d, i) { var name = d[0]; @@ -4245,7 +4254,7 @@ function eval_pyson(value){ init: function(view, attributes) { Sao.View.Form.URL._super.init.call(this, view, attributes); this.input.attr('type', 'url'); - this.button = jQuery('', { + this.button = this.labelled = jQuery('', { 'class': 'btn btn-default', 'target': '_blank', 'rel': 'noreferrer noopener',