Skip to content

Commit 0ce5e3f

Browse files
microbit-markmicrobit-carlos
authored andcommitted
A11y: Make Load/Save and Snippet modals accessible (#290)
1 parent 495936c commit 0ce5e3f

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

editor.html

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,26 +82,30 @@
8282
UPY_VERSION = "1.0.1";
8383
</script>
8484
<script id="files-template" type="x-tmpl-mustache">
85+
<div tabindex="-1" role="dialog" aria-labelledby="loadSave-modal" aria-modal="true" class="modal-div">
8586
<h2 class="modal-title"><i class="fa fa-upload"></i> <strong>{{ load-title }}</strong></h2>
8687
<div class="load-drag-target" id="load-drag-target">
8788
<input type="file" style="display: none" name="load-form-file-upload" id="file-upload-input">
8889
<p>{{ instructions }}<br><a href="#" id="file-upload-link" class="load-drag-target load-toggle action">{{ toggle-file }}</a></p>
8990
</div>
9091
<h2 class="modal-title"><i class="fa fa-download"></i> <strong>{{ save-but }}</strong></h2>
9192
<div class="save-buttons-container">
92-
<button type="button" class="action save-button py" id="save-py" title="{{ save-py }}"><i class="fa fa-download"></i> {{ save-py}}</button>
93-
<button type="button" class="action save-button hex" id="save-hex" title="{{ save-hex }}"><i class="fa fa-download"></i> {{ save-hex}}</button>
93+
<button aria-labelledby="save-py" type="button" class="action save-button py" id="save-py" title="{{ save-py }}"><i class="fa fa-download"></i> {{ save-py}}</button>
94+
<button aria-labelledby="save-hex" type="button" class="action save-button hex" id="save-hex" title="{{ save-hex }}"><i class="fa fa-download"></i> {{ save-hex}}</button>
9495
</div>
9596
<div id="addFile">
9697
<div id="addFileHeader">
9798
<h2 class="modal-title"><i class="fa fa-download"></i> <strong>{{ files-title }}</strong></h2>
9899
</div>
99-
<div id="addFileHelp"><a class="action" id="files-expand-help"><i class="fa fa-info-circle" aria-hidden="true"> <span>{{ help-button }}</span></i></a></div>
100+
<div id="addFileHelp">
101+
<button aria-labelledby="add-files" aria-expanded="false" type="button" class="snippet-button" id="expandHelpPara"><i class="fa fa-info-circle"></i> {{ help-button }}</button>
102+
</div>
103+
</div>
104+
<div id="fileHelpPara">{{ file-help-text }}<a href="help.html#fs" target="_blank" class="action" id="files-help-link">{{ help-link }}</a>.
100105
</div>
101-
<div id="fileHelpPara">{{ file-help-text }}<a href="help.html#fs" target="_blank" class="action" id="files-help-link">{{ help-link }}</a>.</div>
102106
<div class="expandable">
103107
<div class="expandable-always-visible">
104-
<button type="button" class="action save-button show" id="show-files" title="{{ show-files }}">{{ show-files }} &nbsp;<i class="fa fa-caret-down"></i></button>
108+
<button aria-labelledby="show-files" aria-expanded="false" type="button" class="action save-button show" id="show-files" title="{{ show-files }}">{{ show-files }} &nbsp;<i class="fa fa-caret-down"></i></button>
105109
</div>
106110
<div class="expandable-content">
107111
<div class="fs-file-list" id="fs-file-list">
@@ -128,13 +132,15 @@ <h2 class="modal-title"><i class="fa fa-download"></i> <strong>{{ files-title }}
128132
</tbody>
129133
</table>
130134
<div>
131-
<button type="button" class="action save-button add" id="fs-file-upload-button" tile="{{ fs-add-file }}"><i class="fa fa-plus-circle"></i> {{ fs-add-file }}</button>
135+
<button role=button type="button" class="action save-button add" id="fs-file-upload-button" tile="{{ fs-add-file }}"><i class="fa fa-plus-circle"></i> {{ fs-add-file }}</button>
132136
<input type="file" style="display: none" name="fs-file-upload-input" id="fs-file-upload-input" multiple>
133137
</div>
134138
</div>
135139
</div>
140+
</div>
136141
</script>
137142
<script id="snippet-template" type="x-tmpl-mustache">
143+
<div tabindex="-1" role="dialog" aria-labelledby="snippets-modal" aria-modal="true" class="modal-div">
138144
<h2><i class="fa fa-cogs"></i> <strong>{{ title }}</strong></h2>
139145
<p>{{ description }}</p>
140146
<p>{{ instructions }}</p>
@@ -146,11 +152,12 @@ <h2><i class="fa fa-cogs"></i> <strong>{{ title }}</strong></h2>
146152
</tr>
147153
{{#snippets}}
148154
<tr id="snippet-{{trigger}}" class="action snippet-selection">
149-
<td>{{trigger}}<span class="snippet-name">{{name}}</span></td>
155+
<td><button class="snippet-button">{{trigger}}</button> <span class="snippet-name">{{name}}</span></td>
150156
<td>{{#describe}}{{name}}{{/describe}}</td>
151157
</tr>
152158
{{/snippets}}
153159
</table>
160+
</div>
154161
</script>
155162
<script id="share-template" type="x-tmpl-mustache">
156163
<h2><i class="fa fa-share-alt"></i> <strong>{{ title }}</strong></h2>
@@ -388,7 +395,7 @@ <h2><i class="fa fa-unlock-alt"></i> <strong>{{ title }}</strong></h2>
388395
<div id="editor" class="flex3" tabindex="-1"></div>
389396
</div>
390397
<div id="flashing-overlay-container" class="modal-overlay-container">
391-
<div id="flashing-overlay" class="modal-overlay">
398+
<div id="flashing-overlay" class="modal-overlay" tabindex="-1" role="dialog" aria-labelledby="flashing-overlay" aria-modal="true">
392399
<div id="flashing-info">
393400
<h2 id="flashing-text">Flashing micro:bit</h2>
394401
<div id="webusb-flashing-loader"></div>
@@ -400,7 +407,7 @@ <h2 id="flashing-text">Flashing micro:bit</h2>
400407
</div>
401408
</div>
402409
<div id="modal-msg-overlay-container" class="modal-overlay-container modal-msg-overlay-container">
403-
<div id="modal-msg-overlay" class="modal-overlay">
410+
<div id="modal-msg-overlay" class="modal-overlay" tabindex="-1" role="dialog" aria-labelledby="flashing-overlay" aria-modal="true">
404411
<a class="vex-close modal-close" onclick="$('#modal-msg-overlay-container').hide()"></a>
405412
<div id="modal-msg-text">
406413
<h2 id="modal-msg-title"></h2>

python-main.js

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ function web_editor(config) {
876876
$('.fs-file-list table tbody').append(
877877
'<tr><td>' + name + '</td>' +
878878
'<td>' + (micropythonFs.size(filename)/1024).toFixed(2) + ' Kb</td>' +
879-
'<td><button id="' + pseudoUniqueId + '_remove" class="action save-button remove ' + disabled + '" title='+ loadStrings["remove-but"] +'><i class="fa fa-trash"></i></button>' +
879+
'<td><button id="' + pseudoUniqueId + '_remove" class="action save-button remove ' + disabled + '" title='+ loadStrings["remove-but"] + ' ' + disabled + '><i class="fa fa-trash"></i></button>' +
880880
'<button id="' + pseudoUniqueId + '_save" class="action save-button save" title='+ loadStrings["save-but"] +'><i class="fa fa-download"></i></button></td></tr>'
881881
);
882882
$('#' + pseudoUniqueId + '_save').click(function(e) {
@@ -918,6 +918,39 @@ function web_editor(config) {
918918
return fullHex;
919919
}
920920

921+
// Trap focus in modal and pass focus to first actionable element
922+
function focusModal() {
923+
document.querySelector('body > :not(.vex)').setAttribute('aria-hidden', true);
924+
var dialog = document.querySelector('.modal-div');
925+
var focusableEls = dialog.querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])');
926+
$(focusableEls).each(function() {
927+
$(this).attr('tabindex', '0');
928+
});
929+
dialog.focus();
930+
dialog.onkeydown = function(event) {
931+
if (event.which == 9) {
932+
// if tab key is pressed
933+
var focusedEl = document.activeElement;
934+
var numberOfFocusableEls = focusableEls.length;
935+
var focusedElIndex = Array.prototype.indexOf.call(focusableEls, focusedEl);
936+
937+
if (event.which == 16) {
938+
// if focused on first item and user shift-tabs back, go to the last focusable item
939+
if (focusedElIndex == 0) {
940+
focusableEls.item(numberOfFocusableEls - 1).focus();
941+
event.preventDefault();
942+
}
943+
} else {
944+
// if focused on the last item and user tabs forward, go to the first focusable item
945+
if (focusedElIndex == numberOfFocusableEls - 1) {
946+
focusableEls[0].focus();
947+
event.preventDefault();
948+
}
949+
}
950+
}
951+
};
952+
}
953+
921954
// This function describes what to do when the download button is clicked.
922955
function doDownload() {
923956
try {
@@ -945,7 +978,6 @@ function web_editor(config) {
945978
modalMsg(config['translate']['load']['invalid-file-title'], config['translate']['load']['extension-warning'],"");
946979
}
947980
}
948-
949981
// Describes what to do when the save/load button is clicked.
950982
function doFiles() {
951983
var template = $('#files-template').html();
@@ -955,6 +987,7 @@ function web_editor(config) {
955987
vex.open({
956988
content: Mustache.render(template, loadStrings),
957989
afterOpen: function(vexContent) {
990+
focusModal();
958991
$("#show-files").attr("title", loadStrings["show-files"] +" (" + micropythonFs.ls().length + ")");
959992
document.getElementById("show-files").innerHTML = loadStrings["show-files"] + " (" + micropythonFs.ls().length + ") <i class='fa fa-caret-down'>";
960993
$('#save-hex').click(function() {
@@ -968,12 +1001,14 @@ function web_editor(config) {
9681001
}
9691002
downloadFileFromFilesystem('main.py');
9701003
});
971-
$("#files-expand-help").click(function(){
1004+
$("#expandHelpPara").click(function(){
9721005
if ($("#fileHelpPara").css("display")=="none"){
9731006
$("#fileHelpPara").show();
1007+
$("#expandHelpPara").attr("aria-expanded","true");
9741008
$("#addFile").css("margin-bottom","10px");
9751009
}else{
9761010
$("#fileHelpPara").hide();
1011+
$("#expandHelpPara").attr("aria-expanded","false");
9771012
$("#addFile").css("margin-bottom","22px");
9781013
}
9791014
});
@@ -982,11 +1017,13 @@ function web_editor(config) {
9821017
if (content.style.maxHeight){
9831018
content.style.maxHeight = null;
9841019
$("#hide-files").attr("id", "show-files");
1020+
$("#show-files").attr("aria-expanded","false");
9851021
$("#show-files").attr("title", loadStrings["show-files"] + " (" + micropythonFs.ls().length + ")");
9861022
document.getElementById("show-files").innerHTML = loadStrings["show-files"] + " (" + micropythonFs.ls().length + ") <i class='fa fa-caret-down'>";
9871023
} else {
9881024
content.style.maxHeight = content.scrollHeight + "px";
9891025
$("#show-files").attr("id", "hide-files");
1026+
$("#hide-files").attr("aria-expanded","true");
9901027
$("#hide-files").attr("title", loadStrings["hide-files"]);
9911028
document.getElementById("hide-files").innerHTML =loadStrings["hide-files"] + " <i class='fa fa-caret-up'>";
9921029
}
@@ -1136,6 +1173,7 @@ function web_editor(config) {
11361173
vex.open({
11371174
content: Mustache.render(template, context),
11381175
afterOpen: function(vexContent) {
1176+
focusModal();
11391177
$(vexContent).find('.snippet-selection').click(function(e){
11401178
var snippet_name = $(this).find('.snippet-name').text();
11411179
EDITOR.triggerSnippet(snippet_name);
@@ -1680,7 +1718,7 @@ function web_editor(config) {
16801718
var overlayContainer = "#modal-msg-overlay-container";
16811719
$(overlayContainer).css("display","block");
16821720
$("#modal-msg-title").text(title);
1683-
$("#modal-msg-content").html(content);
1721+
$("#modal-msg-content").html(content);
16841722
var modalLinks = [];
16851723
if (links) {
16861724
Object.keys(links).forEach(function(key) {

static/css/style.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,21 @@ a.command:hover {
506506
width: 200px;
507507
}
508508

509+
.snippet-button {
510+
text-decoration: none;
511+
background: none;
512+
color: inherit;
513+
border: none;
514+
border-radius: 5px;
515+
padding: 2px;
516+
font: inherit;
517+
cursor: pointer;
518+
outline: inherit;
519+
}
520+
.snippet-button:focus {
521+
box-shadow: 0px 0px 5px 3px #FFCC33;
522+
}
523+
509524
#share-link {
510525
display: none;
511526
}
@@ -689,6 +704,9 @@ input:checked + .menu-switch-slider:before {
689704
}
690705

691706
/* Modals */
707+
.modal-div:focus {
708+
outline: 0;
709+
}
692710
.modal-title {
693711
margin: 28px 0px 22px 0px;
694712
text-transform: capitalize;
@@ -844,6 +862,10 @@ input:checked + .menu-switch-slider:before {
844862
color: #ffffff !important;
845863
}
846864

865+
.save-button:focus {
866+
box-shadow: 0px 0px 5px 3px #FFCC33;
867+
}
868+
847869
.save-buttons-container {
848870
display: inline-block;
849871
width: 100%;
@@ -871,6 +893,11 @@ input:checked + .menu-switch-slider:before {
871893
color: black !important;
872894
border: none;
873895
}
896+
897+
.save-button.hex:focus {
898+
box-shadow: 0px 0px 5px 3px #FFCC33;
899+
}
900+
874901
.save-button.py {
875902
width: 49%;
876903
min-width: 250px;
@@ -885,6 +912,11 @@ input:checked + .menu-switch-slider:before {
885912
background-color: #eee;
886913
color: black;
887914
}
915+
916+
.save-button.py:focus {
917+
box-shadow: 0px 0px 5px 3px #FFCC33;
918+
}
919+
888920
@media (max-width: 480px) {
889921
.save-button.py,
890922
.save-button.hex {

0 commit comments

Comments
 (0)