Skip to content

Commit 3315502

Browse files
committed
Merge remote branch 'hanshillen/popup-hh'
2 parents 1350e54 + 7ccb0e5 commit 3315502

File tree

2 files changed

+81
-49
lines changed

2 files changed

+81
-49
lines changed

demos/popup/default.html

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<style type="text/css">
3030
.ui-popup { position: absolute; z-index: 5000; }
3131
.ui-menu { width: 200px; }
32-
32+
3333
/*
3434
table {
3535
border-collapse: collapse;
@@ -55,20 +55,18 @@
5555

5656
<div class="demo">
5757
<a href="#login-form">Log In</a>
58-
<div id="login-form" class="ui-widget-content" tabIndex="0">
59-
<form>
60-
<div>
61-
<label>Username</label>
62-
<input type="username" />
63-
</div>
64-
<div>
65-
<label>Password</label>
66-
<input type="password" />
67-
</div>
68-
<div>
69-
<input type="submit" class="submit" value="Login" />
70-
</div>
71-
</form>
58+
<div class="ui-widget-content" id="login-form" aria-label="Login options">
59+
<div>
60+
<label for="un">Username</label>
61+
<input type="text" id="un" />
62+
</div>
63+
<div>
64+
<label for="pw">Password</label>
65+
<input type="password" id="pw" />
66+
</div>
67+
<div>
68+
<input type="submit" value="Login" class="submit" />
69+
</div>
7270
</div>
7371
</div>
7472

ui/jquery.ui.popup.js

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* jquery.ui.position.js
1414
*/
1515
(function($) {
16-
16+
1717
var idIncrement = 0;
1818

1919
$.widget( "ui.popup", {
@@ -28,34 +28,34 @@ $.widget( "ui.popup", {
2828
if ( !this.options.trigger ) {
2929
this.options.trigger = this.element.prev();
3030
}
31-
31+
3232
if ( !this.element.attr( "id" ) ) {
3333
this.element.attr( "id", "ui-popup-" + idIncrement++ );
3434
this.generatedId = true;
3535
}
36-
36+
3737
if ( !this.element.attr( "role" ) ) {
3838
// TODO alternatives to tooltip are dialog and menu, all three aren't generic popups
39-
this.element.attr( "role", "tooltip" );
39+
this.element.attr( "role", "dialog" );
4040
this.generatedRole = true;
4141
}
42-
42+
4343
this.options.trigger
4444
.attr( "aria-haspopup", true )
4545
.attr( "aria-owns", this.element.attr( "id" ) );
46-
46+
4747
this.element
48-
.addClass("ui-popup")
48+
.addClass( "ui-popup" )
4949
this.close();
5050

5151
this._bind(this.options.trigger, {
5252
keydown: function( event ) {
53-
// prevent space-to-open to scroll the page, only hapens for anchor ui.button
54-
if ( this.options.trigger.is( "a:ui-button" ) && event.keyCode == $.ui.keyCode.SPACE) {
55-
event.preventDefault()
53+
// prevent space-to-open to scroll the page, only happens for anchor ui.button
54+
if ( this.options.trigger.is( "a:ui-button" ) && event.keyCode == $.ui.keyCode.SPACE ) {
55+
event.preventDefault();
5656
}
5757
// TODO handle SPACE to open popup? only when not handled by ui.button
58-
if ( event.keyCode == $.ui.keyCode.SPACE && this.options.trigger.is("a:not(:ui-button)") ) {
58+
if ( event.keyCode == $.ui.keyCode.SPACE && this.options.trigger.is( "a:not(:ui-button)" ) ) {
5959
this.options.trigger.trigger( "click", event );
6060
}
6161
// translate keydown to click
@@ -79,60 +79,83 @@ $.widget( "ui.popup", {
7979
}, 1);
8080
}
8181
});
82-
83-
this._bind(this.element, {
84-
// TODO use focusout so that element itself doesn't need to be focussable
85-
blur: function( event ) {
82+
83+
if ( !this.element.is( ":ui-menu" ) ) {
84+
//default use case, wrap tab order in popup
85+
this._bind({ keydown : function( event ) {
86+
if ( event.keyCode !== $.ui.keyCode.TAB ) {
87+
return;
88+
}
89+
var tabbables = $( ":tabbable", this.element ),
90+
first = tabbables.first(),
91+
last = tabbables.last();
92+
if ( event.target === last[ 0 ] && !event.shiftKey ) {
93+
first.focus( 1 );
94+
event.preventDefault();
95+
} else if ( event.target === first[ 0 ] && event.shiftKey ) {
96+
last.focus( 1 );
97+
event.preventDefault();
98+
}
99+
}
100+
});
101+
}
102+
103+
this._bind({
104+
focusout: function( event ) {
86105
var that = this;
87106
// use a timer to allow click to clear it and letting that
88107
// handle the closing instead of opening again
89108
that.closeTimer = setTimeout( function() {
90109
that.close( event );
91110
}, 100);
111+
},
112+
focusin: function( event ) {
113+
clearTimeout( this.closeTimer );
92114
}
93115
});
94116

95117
this._bind({
96-
// TODO only triggerd on element if it can receive focus
118+
// TODO only triggered on element if it can receive focus
97119
// bind to document instead?
98120
// either element itself or a child should be focusable
99121
keyup: function( event ) {
100-
if (event.keyCode == $.ui.keyCode.ESCAPE && this.element.is( ":visible" )) {
122+
if ( event.keyCode == $.ui.keyCode.ESCAPE && this.element.is( ":visible" ) ) {
101123
this.close( event );
102124
// TODO move this to close()? would allow menu.select to call popup.close, and get focus back to trigger
103125
this.options.trigger.focus();
104126
}
105127
}
106128
});
107-
129+
108130
this._bind(document, {
109131
click: function( event ) {
110-
if (this.isOpen && !$(event.target).closest(".ui-popup").length) {
132+
if ( this.isOpen && !$(event.target).closest(".ui-popup").length ) {
111133
this.close( event );
112134
}
113135
}
114136
})
115137
},
116-
138+
117139
_destroy: function() {
118140
this.element
119141
.show()
120142
.removeClass( "ui-popup" )
121143
.removeAttr( "aria-hidden" )
122-
.removeAttr( "aria-expanded" );
144+
.removeAttr( "aria-expanded" )
145+
.unbind( "keypress.ui-popup");
123146

124147
this.options.trigger
125148
.removeAttr( "aria-haspopup" )
126149
.removeAttr( "aria-owns" );
127-
150+
128151
if ( this.generatedId ) {
129152
this.element.removeAttr( "id" );
130153
}
131154
if ( this.generatedRole ) {
132155
this.element.removeAttr( "role" );
133156
}
134157
},
135-
158+
136159
open: function( event ) {
137160
var position = $.extend( {}, {
138161
of: this.options.trigger
@@ -142,17 +165,28 @@ $.widget( "ui.popup", {
142165
.show()
143166
.attr( "aria-hidden", false )
144167
.attr( "aria-expanded", true )
145-
.position( position )
146-
// TODO find a focussable child, otherwise put focus on element, add tabIndex=0 if not focussable
147-
.focus();
168+
.position( position );
148169

149-
if (this.element.is(":ui-menu")) {
150-
this.element.menu("focus", event, this.element.children( "li" ).first() );
170+
if (this.element.is( ":ui-menu" )) { //popup is a menu
171+
this.element.menu( "focus", event, this.element.children( "li" ).first() );
172+
this.element.focus();
173+
} else {
174+
// set focus to the first tabbable element in the popup container
175+
// if there are no tabbable elements, set focus on the popup itself
176+
var tabbables = this.element.find( ":tabbable" );
177+
this.removeTabIndex = false;
178+
if ( !tabbables.length ) {
179+
if ( !this.element.is(":tabbable") ) {
180+
this.element.attr("tabindex", "0");
181+
this.removeTabIndex = true;
182+
}
183+
tabbables = tabbables.add( this.element[ 0 ] );
184+
}
185+
tabbables.first().focus( 1 );
151186
}
152187

153188
// take trigger out of tab order to allow shift-tab to skip trigger
154-
this.options.trigger.attr("tabindex", -1);
155-
189+
this.options.trigger.attr( "tabindex", -1 );
156190
this.isOpen = true;
157191
this._trigger( "open", event );
158192
},
@@ -163,13 +197,13 @@ $.widget( "ui.popup", {
163197
.attr( "aria-hidden", true )
164198
.attr( "aria-expanded", false );
165199

166-
this.options.trigger.attr("tabindex", 0);
167-
200+
this.options.trigger.attr( "tabindex" , 0 );
201+
if ( this.removeTabIndex ) {
202+
this.element.removeAttr( "tabindex" );
203+
}
168204
this.isOpen = false;
169205
this._trigger( "close", event );
170206
}
171-
172-
173207
});
174208

175209
}(jQuery));

0 commit comments

Comments
 (0)