Skip to content

Commit 0e0c2ad

Browse files
authored
Merge pull request #2640 from zerline/zerline/tabbable
Tabbable or not tabbable
2 parents a1c940a + 12941cd commit 0e0c2ad

File tree

8 files changed

+164
-24
lines changed

8 files changed

+164
-24
lines changed

ipywidgets/widgets/domwidget.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"""Contains the DOMWidget class"""
55

6-
from traitlets import Unicode
6+
from traitlets import Bool, Unicode
77
from .widget import Widget, widget_serialization
88
from .trait_types import InstanceDict, TypedTuple
99
from .widget_layout import Layout
@@ -15,6 +15,7 @@ class DOMWidget(Widget):
1515

1616
_model_name = Unicode('DOMWidgetModel').tag(sync=True)
1717
_dom_classes = TypedTuple(trait=Unicode(), help="CSS classes applied to widget DOM element").tag(sync=True)
18+
tabbable = Bool(help="Is widget tabbable?", allow_none=True, default_value=None).tag(sync=True)
1819
layout = InstanceDict(Layout).tag(sync=True, **widget_serialization)
1920

2021
def add_class(self, className):

packages/base/src/widget.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ class DOMWidgetModel extends WidgetModel {
577577

578578
defaults() {
579579
return utils.assign(super.defaults(), {
580-
_dom_classes: []
580+
_dom_classes: [],
581+
tabbable: null
581582
// We do not declare defaults for the layout and style attributes.
582583
// Those defaults are constructed on the kernel side and synced here
583584
// as needed, and our code here copes with those attributes being
@@ -966,6 +967,17 @@ class DOMWidgetView extends WidgetView {
966967
}
967968
}
968969

970+
updateTabindex() {
971+
let tabbable = this.model.get('tabbable');
972+
if (tabbable === true) {
973+
this.el.setAttribute('tabIndex', '0');
974+
} else if (tabbable === false) {
975+
this.el.setAttribute('tabIndex', '-1');
976+
} else if (tabbable === null) {
977+
this.el.removeAttribute('tabIndex');
978+
}
979+
}
980+
969981
'$el': any;
970982
pWidget: Widget;
971983
layoutPromise: Promise<any>;

packages/controls/src/widget_bool.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@ class CheckboxView extends DescriptionView {
6868
this.checkboxLabel.appendChild(this.descriptionSpan);
6969

7070
this.listenTo(this.model, 'change:indent', this.updateIndent);
71+
this.listenTo(this.model, 'change:tabbable', this.updateTabindex);
7172

7273
this.update(); // Set defaults.
7374
this.updateDescription();
7475
this.updateIndent();
76+
this.updateTabindex();
7577
}
7678

7779
/**
@@ -101,6 +103,20 @@ class CheckboxView extends DescriptionView {
101103
this.label.style.display = indent ? '' : 'none';
102104
}
103105

106+
updateTabindex() {
107+
if (!this.checkbox) {
108+
return; // we might be constructing the parent
109+
}
110+
let tabbable = this.model.get('tabbable');
111+
if (tabbable === true) {
112+
this.checkbox.setAttribute('tabIndex', '0');
113+
} else if (tabbable === false) {
114+
this.checkbox.setAttribute('tabIndex', '-1');
115+
} else if (tabbable === null) {
116+
this.checkbox.removeAttribute('tabIndex');
117+
}
118+
}
119+
104120
events(): {[e: string]: string} {
105121
return {
106122
'click input[type="checkbox"]': '_handle_click'
@@ -164,6 +180,7 @@ class ToggleButtonView extends DOMWidgetView {
164180
this.el.classList.add('jupyter-button');
165181
this.el.classList.add('widget-toggle-button');
166182
this.listenTo(this.model, 'change:button_style', this.update_button_style);
183+
this.listenTo(this.model, 'change:tabbable', this.updateTabindex);
167184
this.set_button_style();
168185
this.update(); // Set defaults.
169186
}
@@ -191,6 +208,7 @@ class ToggleButtonView extends DOMWidgetView {
191208

192209
if (options === undefined || options.updated_view !== this) {
193210
this.el.disabled = this.model.get('disabled');
211+
this.el.setAttribute('tabbable', this.model.get('tabbable'));
194212
this.el.setAttribute('title', this.model.get('tooltip'));
195213

196214
let description = this.model.get('description');
@@ -208,6 +226,7 @@ class ToggleButtonView extends DOMWidgetView {
208226
this.el.appendChild(document.createTextNode(description));
209227
}
210228
}
229+
this.updateTabindex();
211230
return super.update();
212231
}
213232

packages/controls/src/widget_button.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class ButtonView extends DOMWidgetView {
6767
this.el.classList.add('jupyter-button');
6868
this.el.classList.add('widget-button');
6969
this.listenTo(this.model, 'change:button_style', this.update_button_style);
70+
this.listenTo(this.model, 'change:tabbable', this.updateTabindex);
7071
this.set_button_style();
7172
this.update(); // Set defaults.
7273
}
@@ -79,6 +80,7 @@ class ButtonView extends DOMWidgetView {
7980
*/
8081
update() {
8182
this.el.disabled = this.model.get('disabled');
83+
this.updateTabindex();
8284
this.el.setAttribute('title', this.model.get('tooltip'));
8385

8486
let description = this.model.get('description');

packages/controls/src/widget_description.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ class DescriptionView extends DOMWidgetView {
5858

5959
this.listenTo(this.model, 'change:description', this.updateDescription);
6060
this.listenTo(this.model, 'change:description_tooltip', this.updateDescription);
61+
this.listenTo(this.model, 'change:tabbable', this.updateTabindex);
6162
this.updateDescription();
63+
this.updateTabindex();
6264
}
6365

6466
typeset(element: HTMLElement, text?: string){

packages/controls/src/widget_selection.ts

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,52 @@ class SelectionModel extends CoreDescriptionModel {
2929
}
3030
}
3131

32+
export
33+
class SelectionView extends DescriptionView {
34+
/**
35+
* Called when view is rendered.
36+
*/
37+
render() {
38+
super.render(); // Incl. setting some defaults.
39+
this.el.classList.add('jupyter-widgets');
40+
this.el.classList.add('widget-inline-hbox');
41+
}
42+
43+
/**
44+
* Update the contents of this view
45+
*
46+
* Called when the model is changed. The model may have been
47+
* changed by another view or by a state update from the back-end.
48+
*/
49+
update() {
50+
super.update();
51+
52+
// Disable listbox if needed
53+
if (this.listbox) {
54+
this.listbox.disabled = this.model.get('disabled');
55+
}
56+
57+
// Set tabindex
58+
this.updateTabindex();
59+
}
60+
61+
updateTabindex() {
62+
if (!this.listbox) {
63+
return; // we might be constructing the parent
64+
}
65+
let tabbable = this.model.get('tabbable');
66+
if (tabbable === true) {
67+
this.listbox.setAttribute('tabIndex', '0');
68+
} else if (tabbable === false) {
69+
this.listbox.setAttribute('tabIndex', '-1');
70+
} else if (tabbable === null) {
71+
this.listbox.removeAttribute('tabIndex');
72+
}
73+
}
74+
75+
listbox: HTMLSelectElement;
76+
}
77+
3278
export
3379
class DropdownModel extends SelectionModel {
3480
defaults() {
@@ -48,7 +94,7 @@ class DropdownModel extends SelectionModel {
4894
// For the old code, see commit f68bfbc566f3a78a8f3350b438db8ed523ce3642
4995

5096
export
51-
class DropdownView extends DescriptionView {
97+
class DropdownView extends SelectionView {
5298
/**
5399
* Public constructor.
54100
*/
@@ -63,8 +109,6 @@ class DropdownView extends DescriptionView {
63109
render() {
64110
super.render();
65111

66-
this.el.classList.add('jupyter-widgets');
67-
this.el.classList.add('widget-inline-hbox');
68112
this.el.classList.add('widget-dropdown');
69113

70114
this.listbox = document.createElement('select');
@@ -78,9 +122,6 @@ class DropdownView extends DescriptionView {
78122
* Update the contents of this view
79123
*/
80124
update() {
81-
// Disable listbox if needed
82-
this.listbox.disabled = this.model.get('disabled');
83-
84125
// Select the correct element
85126
let index = this.model.get('index');
86127
this.listbox.selectedIndex = index === null ? -1 : index;
@@ -113,8 +154,6 @@ class DropdownView extends DescriptionView {
113154
this.model.set('index', this.listbox.selectedIndex === -1 ? null : this.listbox.selectedIndex);
114155
this.touch();
115156
}
116-
117-
listbox: HTMLSelectElement;
118157
}
119158

120159

@@ -131,7 +170,7 @@ class SelectModel extends SelectionModel {
131170
}
132171

133172
export
134-
class SelectView extends DescriptionView {
173+
class SelectView extends SelectionView {
135174
/**
136175
* Public constructor.
137176
*/
@@ -148,9 +187,6 @@ class SelectView extends DescriptionView {
148187
*/
149188
render() {
150189
super.render();
151-
152-
this.el.classList.add('jupyter-widgets');
153-
this.el.classList.add('widget-inline-hbox');
154190
this.el.classList.add('widget-select');
155191

156192
this.listbox.id = this.label.htmlFor = uuid();
@@ -165,7 +201,6 @@ class SelectView extends DescriptionView {
165201
*/
166202
update() {
167203
super.update();
168-
this.listbox.disabled = this.model.get('disabled');
169204
let rows = this.model.get('rows');
170205
if (rows === null) {
171206
rows = '';
@@ -207,8 +242,6 @@ class SelectView extends DescriptionView {
207242
this.model.set('index', this.listbox.selectedIndex, {updated_view: this});
208243
this.touch();
209244
}
210-
211-
listbox: HTMLSelectElement;
212245
}
213246

214247
export
@@ -233,8 +266,6 @@ class RadioButtonsView extends DescriptionView {
233266
render() {
234267
super.render();
235268

236-
this.el.classList.add('jupyter-widgets');
237-
this.el.classList.add('widget-inline-hbox');
238269
this.el.classList.add('widget-radio');
239270

240271
this.container = document.createElement('div');
@@ -401,8 +432,6 @@ class ToggleButtonsView extends DescriptionView {
401432
render() {
402433
super.render();
403434

404-
this.el.classList.add('jupyter-widgets');
405-
this.el.classList.add('widget-inline-hbox');
406435
this.el.classList.add('widget-toggle-buttons');
407436

408437
this.buttongroup = document.createElement('div');
@@ -586,8 +615,6 @@ class SelectionSliderView extends DescriptionView {
586615
render () {
587616
super.render();
588617

589-
this.el.classList.add('jupyter-widgets');
590-
this.el.classList.add('widget-inline-hbox');
591618
this.el.classList.add('widget-hslider');
592619
this.el.classList.add('widget-slider');
593620

packages/controls/src/widget_string.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class TextareaView extends DescriptionView {
197197
* changed by another view or by a state update from the back-end.
198198
*/
199199
update(options?: any) {
200-
if (options === undefined || options.updated_view != this) {
200+
if (options === undefined || options.updated_view !== this) {
201201
this.textbox.value = this.model.get('value');
202202
let rows = this.model.get('rows');
203203
if (rows === null) {
@@ -206,9 +206,24 @@ class TextareaView extends DescriptionView {
206206
this.textbox.setAttribute('rows', rows);
207207
this.textbox.disabled = this.model.get('disabled');
208208
}
209+
this.updateTabindex();
209210
return super.update();
210211
}
211212

213+
updateTabindex() {
214+
if (!this.textbox) {
215+
return; // we might be constructing the parent
216+
}
217+
let tabbable = this.model.get('tabbable');
218+
if (tabbable === true) {
219+
this.textbox.setAttribute('tabIndex', '0');
220+
} else if (tabbable === false) {
221+
this.textbox.setAttribute('tabIndex', '-1');
222+
} else if (tabbable === null) {
223+
this.textbox.removeAttribute('tabIndex');
224+
}
225+
}
226+
212227
events() {
213228
return {
214229
'keydown input': 'handleKeyDown',
@@ -294,6 +309,7 @@ class TextView extends DescriptionView {
294309

295310
this.update_placeholder();
296311
this.update_title();
312+
this.updateTabindex();
297313
}
298314

299315
update_placeholder(value?: string) {
@@ -309,6 +325,20 @@ class TextView extends DescriptionView {
309325
}
310326
}
311327

328+
updateTabindex() {
329+
if (!this.textbox) {
330+
return; // we might be constructing the parent
331+
}
332+
let tabbable = this.model.get('tabbable');
333+
if (tabbable === true) {
334+
this.textbox.setAttribute('tabIndex', '0');
335+
} else if (tabbable === false) {
336+
this.textbox.setAttribute('tabIndex', '-1');
337+
} else if (tabbable === null) {
338+
this.textbox.removeAttribute('tabIndex');
339+
}
340+
}
341+
312342
update(options?: any) {
313343
/**
314344
* Update the contents of this view

0 commit comments

Comments
 (0)