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',