1
1
/*!
2
- * ctrly v0.5 .0
2
+ * ctrly v0.6 .0
3
3
* Copyright (c) 2018 Jan Sorgalla
4
4
* License: MIT
5
5
*/
@@ -50,14 +50,6 @@ function focus(element) {
50
50
}
51
51
}
52
52
53
- function find ( selector , element ) {
54
- var context = arguments . length > 1 ? element : document ;
55
- if ( ! context || typeof context . querySelectorAll !== 'function' ) {
56
- return [ ] ;
57
- }
58
- return [ ] . slice . call ( context . querySelectorAll ( selector ) ) ;
59
- }
60
-
61
53
function matches ( element , selector ) {
62
54
if ( ! element ) {
63
55
return false ;
@@ -85,6 +77,72 @@ function closest(element, selector) {
85
77
return null ;
86
78
}
87
79
80
+ function find ( selector , element ) {
81
+ var context = arguments . length > 1 ? element : document ;
82
+ if ( ! context || typeof context . querySelectorAll !== 'function' ) {
83
+ return [ ] ;
84
+ }
85
+ return [ ] . slice . call ( context . querySelectorAll ( selector ) ) ;
86
+ }
87
+
88
+ var selector = [ 'a[href]' ,
89
+ 'area[href]' , 'input' , 'select' , 'textarea' , 'button' , 'iframe' , 'object' , 'audio[controls]' , 'video[controls]' , '[contenteditable]' , '[tabindex]' ] . join ( ',' ) ;
90
+ var inputNodeNameRegexp = / ^ ( i n p u t | s e l e c t | t e x t a r e a | b u t t o n | o b j e c t ) $ / ;
91
+ function focusableFilter ( element ) {
92
+ var nodeName = element . nodeName . toLowerCase ( ) ;
93
+ if ( nodeName === 'area' ) {
94
+ return isValidArea ( element ) ;
95
+ }
96
+ if ( element . disabled ) {
97
+ return false ;
98
+ }
99
+ if ( inputNodeNameRegexp . test ( nodeName ) ) {
100
+ var fieldset = closest ( element , 'fieldset' ) ;
101
+ if ( fieldset && fieldset . disabled ) {
102
+ return false ;
103
+ }
104
+ }
105
+ return visible ( element ) ;
106
+ }
107
+ function tabbableFilter ( element ) {
108
+ var index = tabIndex ( element ) ;
109
+ return focusableFilter ( element ) && index >= 0 ;
110
+ }
111
+ function compare ( a , b ) {
112
+ var aIndex = tabIndex ( a , true ) ;
113
+ var bIndex = tabIndex ( b , true ) ;
114
+ if ( aIndex === bIndex ) {
115
+ return a . compareDocumentPosition ( b ) & 2 ? 1 : - 1 ;
116
+ }
117
+ return aIndex - bIndex ;
118
+ }
119
+ function isValidArea ( element ) {
120
+ var map = element . parentNode ;
121
+ var mapName = map . name ;
122
+ if ( ! element . href || ! mapName || map . nodeName . toLowerCase ( ) !== 'map' ) {
123
+ return false ;
124
+ }
125
+ var images = find ( "img[usemap=\"#" . concat ( mapName , "\"]" ) ) ;
126
+ return images . length > 0 && visible ( images [ 0 ] ) ;
127
+ }
128
+ function visible ( element ) {
129
+ var style = getComputedStyle ( element ) ;
130
+ return (
131
+ style . visibility !== 'hidden' && style . visibility !== 'collapse' && style . display !== 'none' && parents ( element ) . every ( function ( el ) {
132
+ return getComputedStyle ( el ) . display !== 'none' ;
133
+ } )
134
+ ) ;
135
+ }
136
+ function tabIndex ( element ) {
137
+ var positiveOnly = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : false ;
138
+ var index = parseInt ( element . getAttribute ( 'tabindex' ) , 10 ) ;
139
+ return isNaN ( index ) ? 0 : positiveOnly && index < 0 ? 0 : index ;
140
+ }
141
+
142
+ function isTabbable ( element ) {
143
+ return matches ( element , selector ) && tabbableFilter ( element ) ;
144
+ }
145
+
88
146
var supportedOptions ;
89
147
function optionsSupport ( ) {
90
148
if ( supportedOptions ) {
@@ -219,60 +277,6 @@ function ready(listener) {
219
277
} ) ) ;
220
278
}
221
279
222
- var selector = [ 'a[href]' ,
223
- 'area[href]' , 'input' , 'select' , 'textarea' , 'button' , 'iframe' , 'object' , 'audio[controls]' , 'video[controls]' , '[contenteditable]' , '[tabindex]' ] . join ( ',' ) ;
224
- var inputNodeNameRegexp = / ^ ( i n p u t | s e l e c t | t e x t a r e a | b u t t o n | o b j e c t ) $ / ;
225
- function focusableFilter ( element ) {
226
- var nodeName = element . nodeName . toLowerCase ( ) ;
227
- if ( nodeName === 'area' ) {
228
- return isValidArea ( element ) ;
229
- }
230
- if ( element . disabled ) {
231
- return false ;
232
- }
233
- if ( inputNodeNameRegexp . test ( nodeName ) ) {
234
- var fieldset = closest ( element , 'fieldset' ) ;
235
- if ( fieldset && fieldset . disabled ) {
236
- return false ;
237
- }
238
- }
239
- return visible ( element ) ;
240
- }
241
- function tabbableFilter ( element ) {
242
- var index = tabIndex ( element ) ;
243
- return focusableFilter ( element ) && index >= 0 ;
244
- }
245
- function compare ( a , b ) {
246
- var aIndex = tabIndex ( a , true ) ;
247
- var bIndex = tabIndex ( b , true ) ;
248
- if ( aIndex === bIndex ) {
249
- return a . compareDocumentPosition ( b ) & 2 ? 1 : - 1 ;
250
- }
251
- return aIndex - bIndex ;
252
- }
253
- function isValidArea ( element ) {
254
- var map = element . parentNode ;
255
- var mapName = map . name ;
256
- if ( ! element . href || ! mapName || map . nodeName . toLowerCase ( ) !== 'map' ) {
257
- return false ;
258
- }
259
- var images = find ( "img[usemap=\"#" . concat ( mapName , "\"]" ) ) ;
260
- return images . length > 0 && visible ( images [ 0 ] ) ;
261
- }
262
- function visible ( element ) {
263
- var style = getComputedStyle ( element ) ;
264
- return (
265
- style . visibility !== 'hidden' && style . visibility !== 'collapse' && style . display !== 'none' && parents ( element ) . every ( function ( el ) {
266
- return getComputedStyle ( el ) . display !== 'none' ;
267
- } )
268
- ) ;
269
- }
270
- function tabIndex ( element ) {
271
- var positiveOnly = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : false ;
272
- var index = parseInt ( element . getAttribute ( 'tabindex' ) , 10 ) ;
273
- return isNaN ( index ) ? 0 : positiveOnly && index < 0 ? 0 : index ;
274
- }
275
-
276
280
function tabbable ( element ) {
277
281
return find ( selector , arguments . length > 0 ? element : document ) . filter ( tabbableFilter ) . sort ( compare ) ;
278
282
}
@@ -312,6 +316,7 @@ function findTarget(control) {
312
316
return document . getElementById ( control . getAttribute ( 'aria-controls' ) || control . getAttribute ( 'data-ctrly' ) ) ;
313
317
}
314
318
function resetControl ( control ) {
319
+ control . removeAttribute ( 'aria-pressed' ) ;
315
320
control . removeAttribute ( 'aria-controls' ) ;
316
321
control . removeAttribute ( 'aria-expanded' ) ;
317
322
}
@@ -368,6 +373,9 @@ function ctrly() {
368
373
destroy ( ) ;
369
374
}
370
375
findControls ( target ) . forEach ( function ( c ) {
376
+ if ( c . tagName . toLowerCase ( ) !== 'button' ) {
377
+ c . setAttribute ( 'aria-pressed' , 'false' ) ;
378
+ }
371
379
c . setAttribute ( 'aria-expanded' , 'false' ) ;
372
380
} ) ;
373
381
target . removeAttribute ( 'data-ctrly-opened' ) ;
@@ -395,7 +403,9 @@ function ctrly() {
395
403
if ( options . closeOnBlur && ! options . trapFocus ) {
396
404
removeFuncs . push ( on ( document , 'focusin' , function ( e ) {
397
405
if ( ! target . contains ( e . target ) ) {
398
- close ( target , false ) ;
406
+ setTimeout ( function ( ) {
407
+ close ( target , false ) ;
408
+ } , 0 ) ;
399
409
}
400
410
} , {
401
411
capture : true ,
@@ -494,6 +504,9 @@ function ctrly() {
494
504
destroy : addEventListeners ( control , target )
495
505
} ;
496
506
findControls ( target ) . forEach ( function ( c ) {
507
+ if ( c . tagName . toLowerCase ( ) !== 'button' ) {
508
+ c . setAttribute ( 'aria-pressed' , 'true' ) ;
509
+ }
497
510
c . setAttribute ( 'aria-expanded' , 'true' ) ;
498
511
} ) ;
499
512
target . setAttribute ( 'data-ctrly-opened' , '' ) ;
@@ -502,38 +515,49 @@ function ctrly() {
502
515
trigger ( target , 'opened' ) ;
503
516
return target ;
504
517
}
518
+ function toggle ( e , control ) {
519
+ var target = findTarget ( control ) ;
520
+ if ( ! target ) {
521
+ if ( close ( findParentTarget ( control ) ) ) {
522
+ e . preventDefault ( ) ;
523
+ }
524
+ return ;
525
+ }
526
+ if ( control . getAttribute ( 'aria-expanded' ) === 'true' ) {
527
+ if ( close ( target ) ) {
528
+ e . preventDefault ( ) ;
529
+ }
530
+ return ;
531
+ }
532
+ if ( ! options . allowMultiple ) {
533
+ closeOthers ( target ) ;
534
+ }
535
+ open ( control ) ;
536
+ if ( target ) {
537
+ e . preventDefault ( ) ;
538
+ if ( options . focusTarget ) {
539
+ focus ( tabbable ( target ) [ 0 ] || target ) ;
540
+ }
541
+ target . scrollTop = 0 ;
542
+ target . scrollLeft = 0 ;
543
+ }
544
+ }
505
545
var removeControlClick ;
546
+ var removeControlKeypress ;
506
547
function init ( ) {
507
548
if ( ! removeControlClick ) {
508
549
removeControlClick = delegate ( document , 'click' , controlSelector , function ( e , control ) {
509
- if ( keyCode ( e ) !== 1 ) {
510
- return ;
511
- }
512
- var target = findTarget ( control ) ;
513
- if ( ! target ) {
514
- if ( close ( findParentTarget ( control ) ) ) {
515
- e . preventDefault ( ) ;
550
+ if ( keyCode ( e ) === 1
551
+ ) {
552
+ toggle ( e , control ) ;
516
553
}
517
- return ;
518
- }
519
- if ( control . getAttribute ( 'aria-expanded' ) === 'true' ) {
520
- if ( close ( target ) ) {
521
- e . preventDefault ( ) ;
522
- }
523
- return ;
524
- }
525
- if ( ! options . allowMultiple ) {
526
- closeOthers ( target ) ;
527
- }
528
- open ( control ) ;
529
- if ( target ) {
530
- e . preventDefault ( ) ;
531
- if ( options . focusTarget ) {
532
- focus ( tabbable ( target ) [ 0 ] || target ) ;
554
+ } ) ;
555
+ removeControlKeypress = delegate ( document , 'keypress' , controlSelector , function ( e , control ) {
556
+ if ( keyCode ( e ) === 13
557
+ || keyCode ( e ) === 32
558
+ ) {
559
+ toggle ( e , control ) ;
533
560
}
534
- target . scrollTop = 0 ;
535
- target . scrollLeft = 0 ;
536
- }
537
561
} ) ;
538
562
}
539
563
find ( controlSelector ) . forEach ( function ( control ) {
@@ -542,6 +566,14 @@ function ctrly() {
542
566
resetControl ( control ) ;
543
567
return ;
544
568
}
569
+ if ( control . tagName . toLowerCase ( ) !== 'button' ) {
570
+ if ( ! control . hasAttribute ( 'role' ) ) {
571
+ control . setAttribute ( 'role' , 'button' ) ;
572
+ }
573
+ if ( ! isTabbable ( control ) ) {
574
+ control . setAttribute ( 'tabindex' , '0' ) ;
575
+ }
576
+ }
545
577
control . setAttribute ( 'aria-controls' , target . id ) ;
546
578
var labelledBy = findControls ( target ) . map ( function ( control ) {
547
579
if ( ! control . id ) {
@@ -557,6 +589,9 @@ function ctrly() {
557
589
open ( control ) ;
558
590
return ;
559
591
}
592
+ if ( control . tagName . toLowerCase ( ) !== 'button' ) {
593
+ control . setAttribute ( 'aria-pressed' , 'false' ) ;
594
+ }
560
595
control . setAttribute ( 'aria-expanded' , 'false' ) ;
561
596
target . setAttribute ( 'aria-hidden' , 'true' ) ;
562
597
target . removeAttribute ( 'tabindex' ) ;
@@ -566,6 +601,8 @@ function ctrly() {
566
601
if ( fullReset && removeControlClick ) {
567
602
removeControlClick ( ) ;
568
603
removeControlClick = null ;
604
+ removeControlKeypress ( ) ;
605
+ removeControlKeypress = null ;
569
606
}
570
607
find ( controlSelector ) . forEach ( function ( control ) {
571
608
if ( fullReset ) {
0 commit comments