Skip to content

Commit 0bc4794

Browse files
authored
Merge pull request #7492 from plotly/david-enhancement-modebar-emilykl
feat: Improve keyboard navigation of modebar (by @davidangarita1)
2 parents 2cac1b1 + 4698ac6 commit 0bc4794

File tree

5 files changed

+44
-19
lines changed

5 files changed

+44
-19
lines changed

build/plotcss.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ var rules = {
3434
"X .ease-bg": "-webkit-transition:background-color .3s ease 0s;-moz-transition:background-color .3s ease 0s;-ms-transition:background-color .3s ease 0s;-o-transition:background-color .3s ease 0s;transition:background-color .3s ease 0s;",
3535
"X .modebar--hover>:not(.watermark)": "opacity:0;-webkit-transition:opacity .3s ease 0s;-moz-transition:opacity .3s ease 0s;-ms-transition:opacity .3s ease 0s;-o-transition:opacity .3s ease 0s;transition:opacity .3s ease 0s;",
3636
"X:hover .modebar--hover .modebar-group": "opacity:1;",
37+
"X:focus-within .modebar--hover .modebar-group": "opacity:1;",
3738
"X .modebar-group": "float:left;display:inline-block;box-sizing:border-box;padding-left:8px;position:relative;vertical-align:middle;white-space:nowrap;",
38-
"X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;",
39-
"X .modebar-btn svg": "position:relative;top:2px;",
39+
"X .modebar-group a": "display:grid;place-content:center;",
40+
"X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;border:none;background:rgba(0,0,0,0);",
41+
"X .modebar-btn svg": "position:relative;",
42+
"X .modebar-btn:focus-visible": "outline:1px solid #000;outline-offset:1px;border-radius:3px;",
4043
"X .modebar.vertical": "display:flex;flex-direction:column;flex-wrap:wrap;align-content:flex-end;max-height:100%;",
4144
"X .modebar.vertical svg": "top:-1px;",
4245
"X .modebar.vertical .modebar-group": "display:block;float:none;padding-left:0px;padding-bottom:8px;",

draftlogs/7492_add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Make modebar keyboard-accessible via tabbing [[#7492](https://github.com/plotly/plotly.js/pull/7492)], with thanks to @davidangarita1 for the contribution!

src/components/modebar/modebar.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ proto.update = function(graphInfo, buttons) {
4343
var modeBarId = 'modebar-' + fullLayout._uid;
4444

4545
this.element.setAttribute('id', modeBarId);
46-
this._uid = modeBarId;
46+
this.element.setAttribute('role', 'toolbar');
4747

48-
this.element.className = 'modebar';
48+
this._uid = modeBarId;
49+
this.element.className = 'modebar modebar--custom';
4950
if(context.displayModeBar === 'hover') this.element.className += ' modebar--hover ease-bg';
5051

5152
if(fullLayout.modebar.orientation === 'v') {
@@ -145,8 +146,9 @@ proto.createGroup = function() {
145146
*/
146147
proto.createButton = function(config) {
147148
var _this = this;
148-
var button = document.createElement('a');
149+
var button = document.createElement('button');
149150

151+
button.setAttribute('type', 'button');
150152
button.setAttribute('rel', 'tooltip');
151153
button.className = 'modebar-btn';
152154

@@ -155,7 +157,10 @@ proto.createButton = function(config) {
155157
// for localization: allow title to be a callable that takes gd as arg
156158
else if(typeof title === 'function') title = title(this.graphInfo);
157159

158-
if(title || title === 0) button.setAttribute('data-title', title);
160+
if(title || title === 0) {
161+
button.setAttribute('data-title', title)
162+
button.setAttribute("aria-label", title)
163+
};
159164

160165
if(config.attr !== undefined) button.setAttribute('data-attr', config.attr);
161166

src/css/_modebar.scss

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@include vendor('transition', background-color 0.3s ease 0s);
99
}
1010

11-
.modebar--hover > :not(.watermark) {
11+
.modebar--hover> :not(.watermark) {
1212
opacity: 0;
1313
@include vendor('transition', opacity 0.3s ease 0s);
1414
}
@@ -17,6 +17,10 @@
1717
opacity: 1;
1818
}
1919

20+
&:focus-within .modebar--hover .modebar-group {
21+
opacity: 1;
22+
}
23+
2024
.modebar-group {
2125
float: left;
2226
display: inline-block;
@@ -25,6 +29,11 @@
2529
position: relative;
2630
vertical-align: middle;
2731
white-space: nowrap;
32+
33+
a {
34+
display: grid;
35+
place-content: center;
36+
}
2837
}
2938

3039
.modebar-btn {
@@ -36,15 +45,20 @@
3645
cursor: pointer;
3746
line-height: normal;
3847
box-sizing: border-box;
48+
border: none;
49+
background: transparent;
3950

4051
svg {
4152
position: relative;
42-
top: 2px;
4353
}
4454

45-
&.modebar-btn--logo {
46-
55+
&:focus-visible {
56+
outline: 1px solid black;
57+
outline-offset: 1px;
58+
border-radius: 3px;
4759
}
60+
61+
&.modebar-btn--logo {}
4862
}
4963

5064
.modebar.vertical {
@@ -53,9 +67,11 @@
5367
flex-wrap: wrap;
5468
align-content: flex-end;
5569
max-height: 100%;
70+
5671
svg {
57-
top: -1px;
72+
top: -1px;
5873
}
74+
5975
.modebar-group {
6076
display: block;
6177
float: none;
@@ -67,4 +83,4 @@
6783
text-align: center;
6884
}
6985
}
70-
}
86+
}

test/jasmine/tests/modebar_test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ describe('ModeBar', function() {
6969
}
7070

7171
function countButtons(modeBar) {
72-
return d3Select(modeBar.element).selectAll('a.modebar-btn').size();
72+
return d3Select(modeBar.element).selectAll('button.modebar-btn, a.modebar-btn').size();
7373
}
7474

7575
function countLogo(modeBar) {
7676
return d3Select(modeBar.element).selectAll('a.plotlyjsicon').size();
7777
}
7878

7979
function checkBtnAttr(modeBar, index, attr) {
80-
var buttons = d3Select(modeBar.element).selectAll('a.modebar-btn');
80+
var buttons = d3Select(modeBar.element).selectAll('button.modebar-btn, a.modebar-btn');
8181
return d3Select(buttons[0][index]).attr(attr);
8282
}
8383

@@ -1676,7 +1676,7 @@ describe('ModeBar', function() {
16761676
it('add predefined shape drawing and hover buttons via layout.modebar.add', function(done) {
16771677
function countButtons() {
16781678
var modeBarEl = gd._fullLayout._modeBar.element;
1679-
return d3Select(modeBarEl).selectAll('a.modebar-btn').size();
1679+
return d3Select(modeBarEl).selectAll('button.modebar-btn, a.modebar-btn').size();
16801680
}
16811681

16821682
var initial = 10;
@@ -1761,7 +1761,7 @@ describe('ModeBar', function() {
17611761
it('remove buttons using exact (camel case) and short (lower case) names via layout.modebar.remove and template', function(done) {
17621762
function countButtons() {
17631763
var modeBarEl = gd._fullLayout._modeBar.element;
1764-
return d3Select(modeBarEl).selectAll('a.modebar-btn').size();
1764+
return d3Select(modeBarEl).selectAll('button.modebar-btn, a.modebar-btn').size();
17651765
}
17661766

17671767
var initial = 10;
@@ -1842,7 +1842,7 @@ describe('ModeBar', function() {
18421842
it('add buttons using template', function(done) {
18431843
function countButtons() {
18441844
var modeBarEl = gd._fullLayout._modeBar.element;
1845-
return d3Select(modeBarEl).selectAll('a.modebar-btn').size();
1845+
return d3Select(modeBarEl).selectAll('button.modebar-btn, a.modebar-btn').size();
18461846
}
18471847

18481848
var initial = 10;
@@ -1865,7 +1865,7 @@ describe('ModeBar', function() {
18651865
it('add ' + t + ' button if removed by layout and added by config', function(done) {
18661866
function countButtons() {
18671867
var modeBarEl = gd._fullLayout._modeBar.element;
1868-
return d3Select(modeBarEl).selectAll('a.modebar-btn').size();
1868+
return d3Select(modeBarEl).selectAll('button.modebar-btn, a.modebar-btn').size();
18691869
}
18701870

18711871
var initial = 10;
@@ -1886,7 +1886,7 @@ describe('ModeBar', function() {
18861886
it('remove button if added by layout and removed by config', function(done) {
18871887
function countButtons() {
18881888
var modeBarEl = gd._fullLayout._modeBar.element;
1889-
return d3Select(modeBarEl).selectAll('a.modebar-btn').size();
1889+
return d3Select(modeBarEl).selectAll('button.modebar-btn, a.modebar-btn').size();
18901890
}
18911891

18921892
var initial = 10;

0 commit comments

Comments
 (0)