aria-api 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Tobias Bengfort
3
+ Copyright (c) 2017-2023 Tobias Bengfort
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -20,7 +20,7 @@ browserify. There is also an UMD build included as `dist/aria.js`.
20
20
 
21
21
  var aria = require('aria-api'):
22
22
 
23
- aria.querySelector('landmark').forEach(function(landmark) {
23
+ aria.querySelector('landmark').forEach(landmark => {
24
24
  if (!aria.matches(landmark, ':hidden')) {
25
25
  var role = aria.getRole(landmark);
26
26
  var name = aria.getName(landmark);
@@ -106,11 +106,11 @@ That said, this is what I think it could also be used for:
106
106
 
107
107
  # Implemented standards
108
108
 
109
- - [Accessible Rich Internet Applications 1.1](https://www.w3.org/TR/wai-aria-1.1/)
110
- - [Core Accessibility API Mappings 1.1](https://www.w3.org/TR/core-aam-1.1/)
109
+ - [Accessible Rich Internet Applications 1.2](https://www.w3.org/TR/wai-aria-1.2/)
111
110
  - [HTML Accessibility API Mappings 1.0](https://www.w3.org/TR/html-aam-1.0/)
111
+ - [SVG Accessibility API Mappings](https://www.w3.org/TR/svg-aam-1.0/) (very incomplete)
112
112
  - [WAI-ARIA Graphics Module 1.0](https://www.w3.org/TR/graphics-aria-1.0/)
113
- - [Digital Publishing WAI-ARIA Module 1.0](https://www.w3.org/TR/dpub-aria-1.0/)
113
+ - [Digital Publishing WAI-ARIA Module 1.1](https://www.w3.org/TR/dpub-aria-1.1/)
114
114
  - [Accessible Name and Description Computation 1.1](https://www.w3.org/TR/accname-1.1/)
115
115
 
116
116
  I try to update the code whenever a new version of these specs becomes a
@@ -135,7 +135,7 @@ recommendation.
135
135
 
136
136
  # Related projects
137
137
 
138
- - [Visual ARIA Bookmarklet](http://whatsock.com/training/matrices/visual-aria.htm):
138
+ - [Visual ARIA Bookmarklet](https://whatsock.github.io/visual-aria/github-bookmarklet/visual-aria.htm):
139
139
  Displays role, name, and description in any website. Maintained by one of
140
140
  the editors of the [accname]() spec.
141
141
  - [axe-core](https://github.com/dequelabs/axe-core/) and
package/dist/aria.js CHANGED
@@ -21,52 +21,56 @@ module.exports = {
21
21
  },{"./lib/atree.js":2,"./lib/name.js":5,"./lib/query.js":6}],2:[function(require,module,exports){
22
22
  var attrs = require('./attrs');
23
23
 
24
- var _getOwner = function(node) {
24
+ var _getOwner = function(node, owners) {
25
25
  if (node.nodeType === node.ELEMENT_NODE && node.id) {
26
- var owner = document.querySelector('[aria-owns~="' + CSS.escape(node.id) + '"]');
27
- if (owner) {
28
- return owner;
26
+ var selector = '[aria-owns~="' + CSS.escape(node.id) + '"]';
27
+ if (owners) {
28
+ for (var owner of owners) {
29
+ if (owner.matches(selector)) {
30
+ return owner;
31
+ }
32
+ }
33
+ } else {
34
+ return document.querySelector(selector);
29
35
  }
30
36
  }
31
37
  };
32
38
 
33
- var _getParentNode = function(node) {
34
- return _getOwner(node) || node.parentNode;
39
+ var _getParentNode = function(node, owners) {
40
+ return _getOwner(node, owners) || node.parentNode;
35
41
  };
36
42
 
37
- var detectLoop = function(node) {
38
- var seen = [node]
39
- while ((node = _getParentNode(node))) {
43
+ var detectLoop = function(node, owners) {
44
+ var seen = [node];
45
+ while ((node = _getParentNode(node, owners))) {
40
46
  if (seen.includes(node)) {
41
47
  return true;
42
48
  }
43
- seen.push(node)
49
+ seen.push(node);
44
50
  }
45
51
  };
46
52
 
47
- var getOwner = function(node) {
48
- if (node.nodeType === node.ELEMENT_NODE && node.id) {
49
- var owner = document.querySelector('[aria-owns~="' + CSS.escape(node.id) + '"]');
50
- if (owner && !detectLoop(node)) {
51
- return owner;
52
- }
53
+ var getOwner = function(node, owners) {
54
+ var owner = _getOwner(node, owners);
55
+ if (owner && !detectLoop(node, owners)) {
56
+ return owner;
53
57
  }
54
58
  };
55
59
 
56
- var getParentNode = function(node) {
57
- return getOwner(node) || node.parentNode;
60
+ var getParentNode = function(node, owners) {
61
+ return getOwner(node, owners) || node.parentNode;
58
62
  };
59
63
 
60
64
  var isHidden = function(node) {
61
65
  return node.nodeType === node.ELEMENT_NODE && attrs.getAttribute(node, 'hidden');
62
66
  };
63
67
 
64
- var getChildNodes = function(node) {
68
+ var getChildNodes = function(node, owners) {
65
69
  var childNodes = [];
66
70
 
67
71
  for (var i = 0; i < node.childNodes.length; i++) {
68
72
  var child = node.childNodes[i];
69
- if (!getOwner(child) && !isHidden(child)) {
73
+ if (!getOwner(child, owners) && !isHidden(child)) {
70
74
  childNodes.push(child);
71
75
  }
72
76
  }
@@ -76,7 +80,7 @@ var getChildNodes = function(node) {
76
80
  for (var i = 0; i < owns.length; i++) {
77
81
  var child = document.getElementById(owns[i]);
78
82
  // double check with getOwner for consistency
79
- if (child && getOwner(child) === node && !isHidden(child)) {
83
+ if (child && getOwner(child, owners) === node && !isHidden(child)) {
80
84
  childNodes.push(child);
81
85
  }
82
86
  }
@@ -86,10 +90,13 @@ var getChildNodes = function(node) {
86
90
  };
87
91
 
88
92
  var walk = function(root, fn) {
89
- fn(root);
90
- getChildNodes(root).forEach(function(child) {
91
- walk(child, fn);
92
- });
93
+ var owners = document.querySelectorAll('[aria-owns]');
94
+ var queue = [root];
95
+ while (queue.length) {
96
+ var item = queue.shift();
97
+ fn(item);
98
+ queue = getChildNodes(item, owners).concat(queue);
99
+ }
93
100
  };
94
101
 
95
102
  var searchUp = function(node, test) {
@@ -116,35 +123,34 @@ var constants = require('./constants.js');
116
123
  // candidates can be passed for performance optimization
117
124
  var getRole = function(el, candidates) {
118
125
  if (el.hasAttribute('role')) {
119
- return el.getAttribute('role');
120
- }
121
- for (var role in constants.roles) {
122
- var selector = (constants.roles[role].selectors || []).join(',');
123
- if (selector && (!candidates || candidates.includes(role)) && el.matches(selector)) {
124
- return role;
126
+ var roles = el.getAttribute('role').toLowerCase().split(/\s+/);
127
+ if (roles.length > 1 && candidates) {
128
+ return [roles, candidates];
125
129
  }
126
- }
127
-
128
- if (!candidates ||
129
- candidates.includes('banner') ||
130
- candidates.includes('contentinfo')) {
131
- if (!el.matches(constants.scoped)) {
132
- if (el.matches('header')) {
133
- return 'banner';
130
+ for (var role of roles) {
131
+ if (!candidates || candidates.includes(role)) {
132
+ return role;
134
133
  }
135
- if (el.matches('footer')) {
136
- return 'contentinfo';
134
+ }
135
+ } else {
136
+ var roles = candidates ? candidates : Object.keys(constants.roles);
137
+ for (var role of roles) {
138
+ var r = constants.roles[role];
139
+ if (r) {
140
+ var selector = (r.selectors || []).join(',');
141
+ if (selector && el.matches(selector)) {
142
+ return role;
143
+ }
137
144
  }
138
145
  }
139
146
  }
140
147
  };
141
148
 
142
149
  var hasRole = function(el, roles) {
143
- var candidates = [].concat.apply([], roles.map(function(role) {
150
+ var candidates = [].concat.apply([], roles.map(role => {
144
151
  return (constants.roles[role] || {}).subRoles || [role];
145
152
  }));
146
- var actual = getRole(el, candidates);
147
- return candidates.includes(actual);
153
+ return !!getRole(el, candidates);
148
154
  };
149
155
 
150
156
  var getAttribute = function(el, key) {
@@ -299,6 +305,9 @@ exports.attributeWeakMapping = {
299
305
  'selected': 'selected',
300
306
  };
301
307
 
308
+ // https://www.w3.org/TR/html/dom.html#sectioning-content-2
309
+ var scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
310
+
302
311
  // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
303
312
  // https://www.w3.org/TR/wai-aria/roles
304
313
  exports.roles = {
@@ -312,6 +321,12 @@ exports.roles = {
312
321
  article: {
313
322
  selectors: ['article'],
314
323
  },
324
+ banner: {
325
+ selectors: [`header:not(main *, ${scoped})`],
326
+ },
327
+ blockquote: {
328
+ selectors: ['blockquote'],
329
+ },
315
330
  button: {
316
331
  selectors: [
317
332
  'button',
@@ -323,26 +338,32 @@ exports.roles = {
323
338
  ],
324
339
  nameFromContents: true,
325
340
  },
341
+ caption: {
342
+ selectors: ['caption', 'figcaption'],
343
+ },
326
344
  cell: {
327
- selectors: ['td'],
328
- childRoles: ['gridcell', 'rowheader'],
345
+ selectors: ['td', 'th:not([scope])'],
346
+ childRoles: ['columnheader', 'gridcell', 'rowheader'],
329
347
  nameFromContents: true,
330
348
  },
331
349
  checkbox: {
332
350
  selectors: ['input[type="checkbox"]'],
333
- childRoles: ['menuitemcheckbox', 'switch'],
351
+ childRoles: ['switch'],
334
352
  nameFromContents: true,
335
353
  defaults: {
336
354
  'checked': 'false',
337
355
  },
338
356
  },
357
+ code: {
358
+ selectors: ['code'],
359
+ },
339
360
  columnheader: {
340
361
  selectors: ['th[scope="col"]'],
341
362
  nameFromContents: true,
342
363
  },
343
364
  combobox: {
344
365
  selectors: [
345
- 'input:not([type])[list]',
366
+ 'input:not([type])[list]',
346
367
  'input[type="email"][list]',
347
368
  'input[type="search"][list]',
348
369
  'input[type="tel"][list]',
@@ -361,14 +382,25 @@ exports.roles = {
361
382
  childRoles: ['button', 'link', 'menuitem'],
362
383
  },
363
384
  complementary: {
364
- selectors: ['aside'],
385
+ selectors: [
386
+ `aside:not(${scoped})`,
387
+ 'aside[aria-label]',
388
+ 'aside[aria-labelledby]',
389
+ 'aside[title]',
390
+ ],
365
391
  },
366
392
  composite: {
367
393
  childRoles: ['grid', 'select', 'spinbutton', 'tablist'],
368
394
  },
395
+ contentinfo: {
396
+ selectors: [`footer:not(main *, ${scoped})`],
397
+ },
369
398
  definition: {
370
399
  selectors: ['dd'],
371
400
  },
401
+ deletion: {
402
+ selectors: ['del', 's'],
403
+ },
372
404
  dialog: {
373
405
  selectors: ['dialog'],
374
406
  childRoles: ['alertdialog'],
@@ -385,16 +417,32 @@ exports.roles = {
385
417
  'doc-noteref': {
386
418
  nameFromContents: true,
387
419
  },
420
+ 'doc-pagebreak': {
421
+ nameFromContents: true,
422
+ },
423
+ 'doc-subtitle': {
424
+ nameFromContents: true,
425
+ },
388
426
  document: {
389
- selectors: ['body'],
427
+ selectors: ['html'],
390
428
  childRoles: ['article', 'graphics-document'],
391
429
  },
430
+ emphasis: {
431
+ selectors: ['em'],
432
+ },
392
433
  figure: {
393
434
  selectors: ['figure'],
435
+ childRoles: ['doc-example'],
394
436
  },
395
437
  form: {
396
438
  selectors: ['form[aria-label]', 'form[aria-labelledby]', 'form[title]'],
397
439
  },
440
+ generic: {
441
+ // too many selectors to list
442
+ },
443
+ 'graphics-document': {
444
+ selectors: ['svg'],
445
+ },
398
446
  grid: {
399
447
  childRoles: ['treegrid'],
400
448
  },
@@ -403,7 +451,14 @@ exports.roles = {
403
451
  nameFromContents: true,
404
452
  },
405
453
  group: {
406
- selectors: ['details', 'optgroup'],
454
+ selectors: [
455
+ 'address',
456
+ 'details',
457
+ 'fieldset',
458
+ 'hgroup',
459
+ 'optgroup',
460
+ 'text',
461
+ ],
407
462
  childRoles: ['row', 'select', 'toolbar', 'graphics-object'],
408
463
  },
409
464
  heading: {
@@ -418,7 +473,18 @@ exports.roles = {
418
473
  childRoles: ['doc-cover'],
419
474
  },
420
475
  input: {
421
- childRoles: ['checkbox', 'option', 'radio', 'slider', 'spinbutton', 'textbox'],
476
+ childRoles: [
477
+ 'checkbox',
478
+ 'combobox',
479
+ 'option',
480
+ 'radio',
481
+ 'slider',
482
+ 'spinbutton',
483
+ 'textbox',
484
+ ],
485
+ },
486
+ insertion: {
487
+ selectors: ['ins'],
422
488
  },
423
489
  landmark: {
424
490
  childRoles: [
@@ -449,16 +515,17 @@ exports.roles = {
449
515
  ],
450
516
  },
451
517
  link: {
452
- selectors: ['a[href]', 'area[href]', 'link[href]'],
518
+ selectors: ['a[href]', 'area[href]'],
453
519
  childRoles: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'],
454
520
  nameFromContents: true,
455
521
  },
456
522
  list: {
457
- selectors: ['dl', 'ol', 'ul'],
523
+ selectors: ['dl', 'ol', 'ul', 'menu'],
458
524
  childRoles: ['directory', 'feed'],
459
525
  },
460
526
  listbox: {
461
527
  selectors: [
528
+ 'datalist',
462
529
  'select[multiple]',
463
530
  'select[size]:not([size="0"]):not([size="1"])',
464
531
  ],
@@ -467,7 +534,7 @@ exports.roles = {
467
534
  },
468
535
  },
469
536
  listitem: {
470
- selectors: ['dt', 'ul > li', 'ol > li'],
537
+ selectors: ['li'],
471
538
  childRoles: ['doc-biblioentry', 'doc-endnote', 'treeitem'],
472
539
  },
473
540
  log: {
@@ -481,8 +548,14 @@ exports.roles = {
481
548
  math: {
482
549
  selectors: ['math'],
483
550
  },
551
+ meter: {
552
+ selectors: ['meter'],
553
+ defaults: {
554
+ 'valuemin': 0,
555
+ 'valuemax': 100,
556
+ },
557
+ },
484
558
  menu: {
485
- selectors: ['menu[type="context"]'],
486
559
  childRoles: ['menubar'],
487
560
  defaults: {
488
561
  'orientation': 'vertical',
@@ -494,12 +567,10 @@ exports.roles = {
494
567
  },
495
568
  },
496
569
  menuitem: {
497
- selectors: ['menuitem[type="command"]'],
498
570
  childRoles: ['menuitemcheckbox'],
499
571
  nameFromContents: true,
500
572
  },
501
573
  menuitemcheckbox: {
502
- selectors: ['menuitem[type="checkbox"]'],
503
574
  childRoles: ['menuitemradio'],
504
575
  nameFromContents: true,
505
576
  defaults: {
@@ -507,7 +578,6 @@ exports.roles = {
507
578
  },
508
579
  },
509
580
  menuitemradio: {
510
- selectors: ['menuitem[type="radio"]'],
511
581
  nameFromContents: true,
512
582
  defaults: {
513
583
  'checked': 'false',
@@ -528,11 +598,18 @@ exports.roles = {
528
598
  'selected': 'false',
529
599
  },
530
600
  },
601
+ paragraph: {
602
+ selectors: ['p'],
603
+ },
531
604
  presentation: {
532
605
  selectors: ['img[alt=""]'],
533
606
  },
534
607
  progressbar: {
535
608
  selectors: ['progress'],
609
+ defaults: {
610
+ 'valuemin': 0,
611
+ 'valuemax': 100,
612
+ },
536
613
  },
537
614
  radio: {
538
615
  selectors: ['input[type="radio"]'],
@@ -543,7 +620,7 @@ exports.roles = {
543
620
  },
544
621
  },
545
622
  range: {
546
- childRoles: ['progressbar', 'scrollbar', 'slider', 'spinbutton'],
623
+ childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'],
547
624
  },
548
625
  region: {
549
626
  selectors: ['section[aria-label]', 'section[aria-labelledby]', 'section[title]'],
@@ -557,7 +634,6 @@ exports.roles = {
557
634
  },
558
635
  rowgroup: {
559
636
  selectors: ['tbody', 'thead', 'tfoot'],
560
- nameFromContents: true,
561
637
  },
562
638
  rowheader: {
563
639
  selectors: ['th[scope="row"]'],
@@ -568,29 +644,38 @@ exports.roles = {
568
644
  'orientation': 'vertical',
569
645
  'valuemin': 0,
570
646
  'valuemax': 100,
571
- // FIXME: halfway between actual valuemin and valuemax
572
- 'valuenow': 50,
573
647
  },
574
648
  },
649
+ search: {
650
+ selectors: ['search'],
651
+ },
575
652
  searchbox: {
576
653
  selectors: ['input[type="search"]:not([list])'],
577
654
  },
578
655
  section: {
579
656
  childRoles: [
580
657
  'alert',
658
+ 'blockquote',
659
+ 'caption',
581
660
  'cell',
661
+ 'code',
582
662
  'definition',
663
+ 'deletion',
583
664
  'doc-abstract',
584
665
  'doc-colophon',
585
666
  'doc-credit',
586
667
  'doc-dedication',
587
668
  'doc-epigraph',
588
- 'doc-example',
589
669
  'doc-footnote',
670
+ 'doc-pagefooter',
671
+ 'doc-pageheader',
672
+ 'doc-pullquote',
590
673
  'doc-qna',
674
+ 'emphasis',
591
675
  'figure',
592
676
  'group',
593
677
  'img',
678
+ 'insertion',
594
679
  'landmark',
595
680
  'list',
596
681
  'listitem',
@@ -598,10 +683,15 @@ exports.roles = {
598
683
  'marquee',
599
684
  'math',
600
685
  'note',
686
+ 'paragraph',
601
687
  'status',
688
+ 'strong',
689
+ 'subscript',
690
+ 'superscript',
602
691
  'table',
603
692
  'tabpanel',
604
693
  'term',
694
+ 'time',
605
695
  'tooltip',
606
696
  ],
607
697
  },
@@ -616,7 +706,7 @@ exports.roles = {
616
706
  nameFromContents: true,
617
707
  },
618
708
  select: {
619
- childRoles: ['combobox', 'listbox', 'menu', 'radiogroup', 'tree'],
709
+ childRoles: ['listbox', 'menu', 'radiogroup', 'tree'],
620
710
  },
621
711
  separator: {
622
712
  // assume not focussable because <hr> is not
@@ -624,6 +714,8 @@ exports.roles = {
624
714
  childRoles: ['doc-pagebreak'],
625
715
  defaults: {
626
716
  'orientation': 'horizontal',
717
+ 'valuemin': 0,
718
+ 'valuemax': 100,
627
719
  },
628
720
  },
629
721
  slider: {
@@ -639,8 +731,7 @@ exports.roles = {
639
731
  spinbutton: {
640
732
  selectors: ['input[type="number"]'],
641
733
  defaults: {
642
- // FIXME: no valuemin/valuemax
643
- 'valuenow': 0,
734
+ // FIXME: no valuemin/valuemax/valuenow
644
735
  },
645
736
  },
646
737
  status: {
@@ -651,18 +742,29 @@ exports.roles = {
651
742
  'atomic': true,
652
743
  },
653
744
  },
745
+ strong: {
746
+ selectors: ['strong'],
747
+ },
654
748
  structure: {
655
749
  childRoles: [
656
750
  'application',
657
751
  'document',
658
752
  'none',
753
+ 'generic',
659
754
  'presentation',
755
+ 'range',
660
756
  'rowgroup',
661
757
  'section',
662
758
  'sectionhead',
663
759
  'separator',
664
760
  ],
665
761
  },
762
+ subscript: {
763
+ selectors: ['sub'],
764
+ },
765
+ superscript: {
766
+ selectors: ['sup'],
767
+ },
666
768
  switch: {
667
769
  nameFromContents: true,
668
770
  defaults: {
@@ -698,6 +800,14 @@ exports.roles = {
698
800
  ],
699
801
  childRoles: ['searchbox'],
700
802
  },
803
+ time: {
804
+ selectors: ['time'],
805
+ },
806
+ timer: {
807
+ defaults: {
808
+ 'live': 'off',
809
+ },
810
+ },
701
811
  toolbar: {
702
812
  defaults: {
703
813
  'orientation': 'horizontal',
@@ -721,8 +831,10 @@ exports.roles = {
721
831
  'composite',
722
832
  'gridcell',
723
833
  'input',
724
- 'range',
834
+ 'progressbar',
725
835
  'row',
836
+ 'scrollbar',
837
+ 'separator',
726
838
  'tab',
727
839
  ],
728
840
  },
@@ -731,22 +843,14 @@ exports.roles = {
731
843
  },
732
844
  };
733
845
 
734
- exports.scoped = [
735
- 'main *',
736
- // https://www.w3.org/TR/html/dom.html#sectioning-content-2
737
- 'article *', 'aside *', 'nav *', 'section *',
738
- // https://www.w3.org/TR/html/sections.html#sectioning-roots
739
- 'blockquote *', 'details *', 'dialog *', 'fieldset *', 'figure *', 'td *',
740
- ].join(',');
741
-
742
846
  var getSubRoles = function(role) {
743
847
  var children = (exports.roles[role] || {}).childRoles || [];
744
848
  var descendents = children.map(getSubRoles);
745
849
 
746
850
  var result = [role];
747
851
 
748
- descendents.forEach(function(list) {
749
- list.forEach(function(r) {
852
+ descendents.forEach(list => {
853
+ list.forEach(r => {
750
854
  if (!result.includes(r)) {
751
855
  result.push(r);
752
856
  }
@@ -858,7 +962,7 @@ var getName = function(el, recursive, visited, directReference) {
858
962
  // B
859
963
  if (!recursive && el.matches('[aria-labelledby]')) {
860
964
  var ids = el.getAttribute('aria-labelledby').split(/\s+/);
861
- var strings = ids.map(function(id) {
965
+ var strings = ids.map(id => {
862
966
  var label = document.getElementById(id);
863
967
  return label ? getName(label, true, visited, true) : '';
864
968
  });
@@ -873,7 +977,7 @@ var getName = function(el, recursive, visited, directReference) {
873
977
 
874
978
  // D
875
979
  if (!ret.trim() && !recursive && el.labels) {
876
- var strings = Array.prototype.map.call(el.labels, function(label) {
980
+ var strings = Array.prototype.map.call(el.labels, label => {
877
981
  return getName(label, true, visited);
878
982
  });
879
983
  ret = strings.join(' ');
@@ -897,6 +1001,12 @@ var getName = function(el, recursive, visited, directReference) {
897
1001
  }
898
1002
  }
899
1003
  }
1004
+ if (!ret.trim() && el.matches('svg *')) {
1005
+ var svgTitle = el.querySelector('title');
1006
+ if (svgTitle && svgTitle.parentElement === el) {
1007
+ ret = svgTitle.textContent;
1008
+ }
1009
+ }
900
1010
 
901
1011
  // E
902
1012
  if (!ret.trim() && (recursive || isInLabelForOtherWidget(el) || query.matches(el, 'button'))) {
@@ -959,15 +1069,20 @@ var getDescription = function(el) {
959
1069
 
960
1070
  if (el.matches('[aria-describedby]')) {
961
1071
  var ids = el.getAttribute('aria-describedby').split(/\s+/);
962
- var strings = ids.map(function(id) {
1072
+ var strings = ids.map(id => {
963
1073
  var label = document.getElementById(id);
964
1074
  return label ? getName(label, true) : '';
965
1075
  });
966
1076
  ret = strings.join(' ');
1077
+ } else if (el.matches('[aria-description]')) {
1078
+ ret = el.getAttribute('aria-description');
1079
+ } else if (el.matches('svg *')) {
1080
+ var svgDesc = el.querySelector('desc');
1081
+ if (svgDesc && svgDesc.parentElement === el) {
1082
+ ret = svgDesc.textContent;
1083
+ }
967
1084
  } else if (el.title) {
968
1085
  ret = el.title;
969
- } else if (el.placeholder) {
970
- ret = el.placeholder;
971
1086
  }
972
1087
 
973
1088
  ret = (ret || '').trim().replace(/\s+/g, ' ');
@@ -1009,7 +1124,7 @@ var _querySelector = function(all) {
1009
1124
  return function(root, role) {
1010
1125
  var results = [];
1011
1126
  try {
1012
- atree.walk(root, function(node) {
1127
+ atree.walk(root, node => {
1013
1128
  if (node.nodeType === node.ELEMENT_NODE) {
1014
1129
  // FIXME: skip hidden elements
1015
1130
  if (matches(node, role)) {
@@ -1030,7 +1145,7 @@ var _querySelector = function(all) {
1030
1145
  };
1031
1146
 
1032
1147
  var closest = function(el, selector) {
1033
- return atree.searchUp(el, function(candidate) {
1148
+ return atree.searchUp(el, candidate => {
1034
1149
  if (candidate.nodeType === candidate.ELEMENT_NODE) {
1035
1150
  return matches(candidate, selector);
1036
1151
  }
@@ -1038,9 +1153,7 @@ var closest = function(el, selector) {
1038
1153
  };
1039
1154
 
1040
1155
  module.exports = {
1041
- getRole: function(el) {
1042
- return attrs.getRole(el);
1043
- },
1156
+ getRole: el => attrs.getRole(el),
1044
1157
  getAttribute: attrs.getAttribute,
1045
1158
  matches: matches,
1046
1159
  querySelector: _querySelector(),
package/lib/atree.js CHANGED
@@ -1,51 +1,55 @@
1
1
  var attrs = require('./attrs');
2
2
 
3
- var _getOwner = function(node) {
3
+ var _getOwner = function(node, owners) {
4
4
  if (node.nodeType === node.ELEMENT_NODE && node.id) {
5
- var owner = document.querySelector('[aria-owns~="' + CSS.escape(node.id) + '"]');
6
- if (owner) {
7
- return owner;
5
+ var selector = '[aria-owns~="' + CSS.escape(node.id) + '"]';
6
+ if (owners) {
7
+ for (var owner of owners) {
8
+ if (owner.matches(selector)) {
9
+ return owner;
10
+ }
11
+ }
12
+ } else {
13
+ return document.querySelector(selector);
8
14
  }
9
15
  }
10
16
  };
11
17
 
12
- var _getParentNode = function(node) {
13
- return _getOwner(node) || node.parentNode;
18
+ var _getParentNode = function(node, owners) {
19
+ return _getOwner(node, owners) || node.parentNode;
14
20
  };
15
21
 
16
- var detectLoop = function(node) {
17
- var seen = [node]
18
- while ((node = _getParentNode(node))) {
22
+ var detectLoop = function(node, owners) {
23
+ var seen = [node];
24
+ while ((node = _getParentNode(node, owners))) {
19
25
  if (seen.includes(node)) {
20
26
  return true;
21
27
  }
22
- seen.push(node)
28
+ seen.push(node);
23
29
  }
24
30
  };
25
31
 
26
- var getOwner = function(node) {
27
- if (node.nodeType === node.ELEMENT_NODE && node.id) {
28
- var owner = document.querySelector('[aria-owns~="' + CSS.escape(node.id) + '"]');
29
- if (owner && !detectLoop(node)) {
30
- return owner;
31
- }
32
+ var getOwner = function(node, owners) {
33
+ var owner = _getOwner(node, owners);
34
+ if (owner && !detectLoop(node, owners)) {
35
+ return owner;
32
36
  }
33
37
  };
34
38
 
35
- var getParentNode = function(node) {
36
- return getOwner(node) || node.parentNode;
39
+ var getParentNode = function(node, owners) {
40
+ return getOwner(node, owners) || node.parentNode;
37
41
  };
38
42
 
39
43
  var isHidden = function(node) {
40
44
  return node.nodeType === node.ELEMENT_NODE && attrs.getAttribute(node, 'hidden');
41
45
  };
42
46
 
43
- var getChildNodes = function(node) {
47
+ var getChildNodes = function(node, owners) {
44
48
  var childNodes = [];
45
49
 
46
50
  for (var i = 0; i < node.childNodes.length; i++) {
47
51
  var child = node.childNodes[i];
48
- if (!getOwner(child) && !isHidden(child)) {
52
+ if (!getOwner(child, owners) && !isHidden(child)) {
49
53
  childNodes.push(child);
50
54
  }
51
55
  }
@@ -55,7 +59,7 @@ var getChildNodes = function(node) {
55
59
  for (var i = 0; i < owns.length; i++) {
56
60
  var child = document.getElementById(owns[i]);
57
61
  // double check with getOwner for consistency
58
- if (child && getOwner(child) === node && !isHidden(child)) {
62
+ if (child && getOwner(child, owners) === node && !isHidden(child)) {
59
63
  childNodes.push(child);
60
64
  }
61
65
  }
@@ -65,10 +69,13 @@ var getChildNodes = function(node) {
65
69
  };
66
70
 
67
71
  var walk = function(root, fn) {
68
- fn(root);
69
- getChildNodes(root).forEach(function(child) {
70
- walk(child, fn);
71
- });
72
+ var owners = document.querySelectorAll('[aria-owns]');
73
+ var queue = [root];
74
+ while (queue.length) {
75
+ var item = queue.shift();
76
+ fn(item);
77
+ queue = getChildNodes(item, owners).concat(queue);
78
+ }
72
79
  };
73
80
 
74
81
  var searchUp = function(node, test) {
package/lib/attrs.js CHANGED
@@ -3,35 +3,34 @@ var constants = require('./constants.js');
3
3
  // candidates can be passed for performance optimization
4
4
  var getRole = function(el, candidates) {
5
5
  if (el.hasAttribute('role')) {
6
- return el.getAttribute('role');
7
- }
8
- for (var role in constants.roles) {
9
- var selector = (constants.roles[role].selectors || []).join(',');
10
- if (selector && (!candidates || candidates.includes(role)) && el.matches(selector)) {
11
- return role;
6
+ var roles = el.getAttribute('role').toLowerCase().split(/\s+/);
7
+ if (roles.length > 1 && candidates) {
8
+ return [roles, candidates];
12
9
  }
13
- }
14
-
15
- if (!candidates ||
16
- candidates.includes('banner') ||
17
- candidates.includes('contentinfo')) {
18
- if (!el.matches(constants.scoped)) {
19
- if (el.matches('header')) {
20
- return 'banner';
10
+ for (var role of roles) {
11
+ if (!candidates || candidates.includes(role)) {
12
+ return role;
21
13
  }
22
- if (el.matches('footer')) {
23
- return 'contentinfo';
14
+ }
15
+ } else {
16
+ var roles = candidates ? candidates : Object.keys(constants.roles);
17
+ for (var role of roles) {
18
+ var r = constants.roles[role];
19
+ if (r) {
20
+ var selector = (r.selectors || []).join(',');
21
+ if (selector && el.matches(selector)) {
22
+ return role;
23
+ }
24
24
  }
25
25
  }
26
26
  }
27
27
  };
28
28
 
29
29
  var hasRole = function(el, roles) {
30
- var candidates = [].concat.apply([], roles.map(function(role) {
30
+ var candidates = [].concat.apply([], roles.map(role => {
31
31
  return (constants.roles[role] || {}).subRoles || [role];
32
32
  }));
33
- var actual = getRole(el, candidates);
34
- return candidates.includes(actual);
33
+ return !!getRole(el, candidates);
35
34
  };
36
35
 
37
36
  var getAttribute = function(el, key) {
package/lib/constants.js CHANGED
@@ -66,6 +66,9 @@ exports.attributeWeakMapping = {
66
66
  'selected': 'selected',
67
67
  };
68
68
 
69
+ // https://www.w3.org/TR/html/dom.html#sectioning-content-2
70
+ var scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
71
+
69
72
  // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
70
73
  // https://www.w3.org/TR/wai-aria/roles
71
74
  exports.roles = {
@@ -79,6 +82,12 @@ exports.roles = {
79
82
  article: {
80
83
  selectors: ['article'],
81
84
  },
85
+ banner: {
86
+ selectors: [`header:not(main *, ${scoped})`],
87
+ },
88
+ blockquote: {
89
+ selectors: ['blockquote'],
90
+ },
82
91
  button: {
83
92
  selectors: [
84
93
  'button',
@@ -90,26 +99,32 @@ exports.roles = {
90
99
  ],
91
100
  nameFromContents: true,
92
101
  },
102
+ caption: {
103
+ selectors: ['caption', 'figcaption'],
104
+ },
93
105
  cell: {
94
- selectors: ['td'],
95
- childRoles: ['gridcell', 'rowheader'],
106
+ selectors: ['td', 'th:not([scope])'],
107
+ childRoles: ['columnheader', 'gridcell', 'rowheader'],
96
108
  nameFromContents: true,
97
109
  },
98
110
  checkbox: {
99
111
  selectors: ['input[type="checkbox"]'],
100
- childRoles: ['menuitemcheckbox', 'switch'],
112
+ childRoles: ['switch'],
101
113
  nameFromContents: true,
102
114
  defaults: {
103
115
  'checked': 'false',
104
116
  },
105
117
  },
118
+ code: {
119
+ selectors: ['code'],
120
+ },
106
121
  columnheader: {
107
122
  selectors: ['th[scope="col"]'],
108
123
  nameFromContents: true,
109
124
  },
110
125
  combobox: {
111
126
  selectors: [
112
- 'input:not([type])[list]',
127
+ 'input:not([type])[list]',
113
128
  'input[type="email"][list]',
114
129
  'input[type="search"][list]',
115
130
  'input[type="tel"][list]',
@@ -128,14 +143,25 @@ exports.roles = {
128
143
  childRoles: ['button', 'link', 'menuitem'],
129
144
  },
130
145
  complementary: {
131
- selectors: ['aside'],
146
+ selectors: [
147
+ `aside:not(${scoped})`,
148
+ 'aside[aria-label]',
149
+ 'aside[aria-labelledby]',
150
+ 'aside[title]',
151
+ ],
132
152
  },
133
153
  composite: {
134
154
  childRoles: ['grid', 'select', 'spinbutton', 'tablist'],
135
155
  },
156
+ contentinfo: {
157
+ selectors: [`footer:not(main *, ${scoped})`],
158
+ },
136
159
  definition: {
137
160
  selectors: ['dd'],
138
161
  },
162
+ deletion: {
163
+ selectors: ['del', 's'],
164
+ },
139
165
  dialog: {
140
166
  selectors: ['dialog'],
141
167
  childRoles: ['alertdialog'],
@@ -152,16 +178,32 @@ exports.roles = {
152
178
  'doc-noteref': {
153
179
  nameFromContents: true,
154
180
  },
181
+ 'doc-pagebreak': {
182
+ nameFromContents: true,
183
+ },
184
+ 'doc-subtitle': {
185
+ nameFromContents: true,
186
+ },
155
187
  document: {
156
- selectors: ['body'],
188
+ selectors: ['html'],
157
189
  childRoles: ['article', 'graphics-document'],
158
190
  },
191
+ emphasis: {
192
+ selectors: ['em'],
193
+ },
159
194
  figure: {
160
195
  selectors: ['figure'],
196
+ childRoles: ['doc-example'],
161
197
  },
162
198
  form: {
163
199
  selectors: ['form[aria-label]', 'form[aria-labelledby]', 'form[title]'],
164
200
  },
201
+ generic: {
202
+ // too many selectors to list
203
+ },
204
+ 'graphics-document': {
205
+ selectors: ['svg'],
206
+ },
165
207
  grid: {
166
208
  childRoles: ['treegrid'],
167
209
  },
@@ -170,7 +212,14 @@ exports.roles = {
170
212
  nameFromContents: true,
171
213
  },
172
214
  group: {
173
- selectors: ['details', 'optgroup'],
215
+ selectors: [
216
+ 'address',
217
+ 'details',
218
+ 'fieldset',
219
+ 'hgroup',
220
+ 'optgroup',
221
+ 'text',
222
+ ],
174
223
  childRoles: ['row', 'select', 'toolbar', 'graphics-object'],
175
224
  },
176
225
  heading: {
@@ -185,7 +234,18 @@ exports.roles = {
185
234
  childRoles: ['doc-cover'],
186
235
  },
187
236
  input: {
188
- childRoles: ['checkbox', 'option', 'radio', 'slider', 'spinbutton', 'textbox'],
237
+ childRoles: [
238
+ 'checkbox',
239
+ 'combobox',
240
+ 'option',
241
+ 'radio',
242
+ 'slider',
243
+ 'spinbutton',
244
+ 'textbox',
245
+ ],
246
+ },
247
+ insertion: {
248
+ selectors: ['ins'],
189
249
  },
190
250
  landmark: {
191
251
  childRoles: [
@@ -216,16 +276,17 @@ exports.roles = {
216
276
  ],
217
277
  },
218
278
  link: {
219
- selectors: ['a[href]', 'area[href]', 'link[href]'],
279
+ selectors: ['a[href]', 'area[href]'],
220
280
  childRoles: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'],
221
281
  nameFromContents: true,
222
282
  },
223
283
  list: {
224
- selectors: ['dl', 'ol', 'ul'],
284
+ selectors: ['dl', 'ol', 'ul', 'menu'],
225
285
  childRoles: ['directory', 'feed'],
226
286
  },
227
287
  listbox: {
228
288
  selectors: [
289
+ 'datalist',
229
290
  'select[multiple]',
230
291
  'select[size]:not([size="0"]):not([size="1"])',
231
292
  ],
@@ -234,7 +295,7 @@ exports.roles = {
234
295
  },
235
296
  },
236
297
  listitem: {
237
- selectors: ['dt', 'ul > li', 'ol > li'],
298
+ selectors: ['li'],
238
299
  childRoles: ['doc-biblioentry', 'doc-endnote', 'treeitem'],
239
300
  },
240
301
  log: {
@@ -248,8 +309,14 @@ exports.roles = {
248
309
  math: {
249
310
  selectors: ['math'],
250
311
  },
312
+ meter: {
313
+ selectors: ['meter'],
314
+ defaults: {
315
+ 'valuemin': 0,
316
+ 'valuemax': 100,
317
+ },
318
+ },
251
319
  menu: {
252
- selectors: ['menu[type="context"]'],
253
320
  childRoles: ['menubar'],
254
321
  defaults: {
255
322
  'orientation': 'vertical',
@@ -261,12 +328,10 @@ exports.roles = {
261
328
  },
262
329
  },
263
330
  menuitem: {
264
- selectors: ['menuitem[type="command"]'],
265
331
  childRoles: ['menuitemcheckbox'],
266
332
  nameFromContents: true,
267
333
  },
268
334
  menuitemcheckbox: {
269
- selectors: ['menuitem[type="checkbox"]'],
270
335
  childRoles: ['menuitemradio'],
271
336
  nameFromContents: true,
272
337
  defaults: {
@@ -274,7 +339,6 @@ exports.roles = {
274
339
  },
275
340
  },
276
341
  menuitemradio: {
277
- selectors: ['menuitem[type="radio"]'],
278
342
  nameFromContents: true,
279
343
  defaults: {
280
344
  'checked': 'false',
@@ -295,11 +359,18 @@ exports.roles = {
295
359
  'selected': 'false',
296
360
  },
297
361
  },
362
+ paragraph: {
363
+ selectors: ['p'],
364
+ },
298
365
  presentation: {
299
366
  selectors: ['img[alt=""]'],
300
367
  },
301
368
  progressbar: {
302
369
  selectors: ['progress'],
370
+ defaults: {
371
+ 'valuemin': 0,
372
+ 'valuemax': 100,
373
+ },
303
374
  },
304
375
  radio: {
305
376
  selectors: ['input[type="radio"]'],
@@ -310,7 +381,7 @@ exports.roles = {
310
381
  },
311
382
  },
312
383
  range: {
313
- childRoles: ['progressbar', 'scrollbar', 'slider', 'spinbutton'],
384
+ childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'],
314
385
  },
315
386
  region: {
316
387
  selectors: ['section[aria-label]', 'section[aria-labelledby]', 'section[title]'],
@@ -324,7 +395,6 @@ exports.roles = {
324
395
  },
325
396
  rowgroup: {
326
397
  selectors: ['tbody', 'thead', 'tfoot'],
327
- nameFromContents: true,
328
398
  },
329
399
  rowheader: {
330
400
  selectors: ['th[scope="row"]'],
@@ -335,29 +405,38 @@ exports.roles = {
335
405
  'orientation': 'vertical',
336
406
  'valuemin': 0,
337
407
  'valuemax': 100,
338
- // FIXME: halfway between actual valuemin and valuemax
339
- 'valuenow': 50,
340
408
  },
341
409
  },
410
+ search: {
411
+ selectors: ['search'],
412
+ },
342
413
  searchbox: {
343
414
  selectors: ['input[type="search"]:not([list])'],
344
415
  },
345
416
  section: {
346
417
  childRoles: [
347
418
  'alert',
419
+ 'blockquote',
420
+ 'caption',
348
421
  'cell',
422
+ 'code',
349
423
  'definition',
424
+ 'deletion',
350
425
  'doc-abstract',
351
426
  'doc-colophon',
352
427
  'doc-credit',
353
428
  'doc-dedication',
354
429
  'doc-epigraph',
355
- 'doc-example',
356
430
  'doc-footnote',
431
+ 'doc-pagefooter',
432
+ 'doc-pageheader',
433
+ 'doc-pullquote',
357
434
  'doc-qna',
435
+ 'emphasis',
358
436
  'figure',
359
437
  'group',
360
438
  'img',
439
+ 'insertion',
361
440
  'landmark',
362
441
  'list',
363
442
  'listitem',
@@ -365,10 +444,15 @@ exports.roles = {
365
444
  'marquee',
366
445
  'math',
367
446
  'note',
447
+ 'paragraph',
368
448
  'status',
449
+ 'strong',
450
+ 'subscript',
451
+ 'superscript',
369
452
  'table',
370
453
  'tabpanel',
371
454
  'term',
455
+ 'time',
372
456
  'tooltip',
373
457
  ],
374
458
  },
@@ -383,7 +467,7 @@ exports.roles = {
383
467
  nameFromContents: true,
384
468
  },
385
469
  select: {
386
- childRoles: ['combobox', 'listbox', 'menu', 'radiogroup', 'tree'],
470
+ childRoles: ['listbox', 'menu', 'radiogroup', 'tree'],
387
471
  },
388
472
  separator: {
389
473
  // assume not focussable because <hr> is not
@@ -391,6 +475,8 @@ exports.roles = {
391
475
  childRoles: ['doc-pagebreak'],
392
476
  defaults: {
393
477
  'orientation': 'horizontal',
478
+ 'valuemin': 0,
479
+ 'valuemax': 100,
394
480
  },
395
481
  },
396
482
  slider: {
@@ -406,8 +492,7 @@ exports.roles = {
406
492
  spinbutton: {
407
493
  selectors: ['input[type="number"]'],
408
494
  defaults: {
409
- // FIXME: no valuemin/valuemax
410
- 'valuenow': 0,
495
+ // FIXME: no valuemin/valuemax/valuenow
411
496
  },
412
497
  },
413
498
  status: {
@@ -418,18 +503,29 @@ exports.roles = {
418
503
  'atomic': true,
419
504
  },
420
505
  },
506
+ strong: {
507
+ selectors: ['strong'],
508
+ },
421
509
  structure: {
422
510
  childRoles: [
423
511
  'application',
424
512
  'document',
425
513
  'none',
514
+ 'generic',
426
515
  'presentation',
516
+ 'range',
427
517
  'rowgroup',
428
518
  'section',
429
519
  'sectionhead',
430
520
  'separator',
431
521
  ],
432
522
  },
523
+ subscript: {
524
+ selectors: ['sub'],
525
+ },
526
+ superscript: {
527
+ selectors: ['sup'],
528
+ },
433
529
  switch: {
434
530
  nameFromContents: true,
435
531
  defaults: {
@@ -465,6 +561,14 @@ exports.roles = {
465
561
  ],
466
562
  childRoles: ['searchbox'],
467
563
  },
564
+ time: {
565
+ selectors: ['time'],
566
+ },
567
+ timer: {
568
+ defaults: {
569
+ 'live': 'off',
570
+ },
571
+ },
468
572
  toolbar: {
469
573
  defaults: {
470
574
  'orientation': 'horizontal',
@@ -488,8 +592,10 @@ exports.roles = {
488
592
  'composite',
489
593
  'gridcell',
490
594
  'input',
491
- 'range',
595
+ 'progressbar',
492
596
  'row',
597
+ 'scrollbar',
598
+ 'separator',
493
599
  'tab',
494
600
  ],
495
601
  },
@@ -498,22 +604,14 @@ exports.roles = {
498
604
  },
499
605
  };
500
606
 
501
- exports.scoped = [
502
- 'main *',
503
- // https://www.w3.org/TR/html/dom.html#sectioning-content-2
504
- 'article *', 'aside *', 'nav *', 'section *',
505
- // https://www.w3.org/TR/html/sections.html#sectioning-roots
506
- 'blockquote *', 'details *', 'dialog *', 'fieldset *', 'figure *', 'td *',
507
- ].join(',');
508
-
509
607
  var getSubRoles = function(role) {
510
608
  var children = (exports.roles[role] || {}).childRoles || [];
511
609
  var descendents = children.map(getSubRoles);
512
610
 
513
611
  var result = [role];
514
612
 
515
- descendents.forEach(function(list) {
516
- list.forEach(function(r) {
613
+ descendents.forEach(list => {
614
+ list.forEach(r => {
517
615
  if (!result.includes(r)) {
518
616
  result.push(r);
519
617
  }
package/lib/name.js CHANGED
@@ -72,7 +72,7 @@ var getName = function(el, recursive, visited, directReference) {
72
72
  // B
73
73
  if (!recursive && el.matches('[aria-labelledby]')) {
74
74
  var ids = el.getAttribute('aria-labelledby').split(/\s+/);
75
- var strings = ids.map(function(id) {
75
+ var strings = ids.map(id => {
76
76
  var label = document.getElementById(id);
77
77
  return label ? getName(label, true, visited, true) : '';
78
78
  });
@@ -87,7 +87,7 @@ var getName = function(el, recursive, visited, directReference) {
87
87
 
88
88
  // D
89
89
  if (!ret.trim() && !recursive && el.labels) {
90
- var strings = Array.prototype.map.call(el.labels, function(label) {
90
+ var strings = Array.prototype.map.call(el.labels, label => {
91
91
  return getName(label, true, visited);
92
92
  });
93
93
  ret = strings.join(' ');
@@ -111,6 +111,12 @@ var getName = function(el, recursive, visited, directReference) {
111
111
  }
112
112
  }
113
113
  }
114
+ if (!ret.trim() && el.matches('svg *')) {
115
+ var svgTitle = el.querySelector('title');
116
+ if (svgTitle && svgTitle.parentElement === el) {
117
+ ret = svgTitle.textContent;
118
+ }
119
+ }
114
120
 
115
121
  // E
116
122
  if (!ret.trim() && (recursive || isInLabelForOtherWidget(el) || query.matches(el, 'button'))) {
@@ -173,15 +179,20 @@ var getDescription = function(el) {
173
179
 
174
180
  if (el.matches('[aria-describedby]')) {
175
181
  var ids = el.getAttribute('aria-describedby').split(/\s+/);
176
- var strings = ids.map(function(id) {
182
+ var strings = ids.map(id => {
177
183
  var label = document.getElementById(id);
178
184
  return label ? getName(label, true) : '';
179
185
  });
180
186
  ret = strings.join(' ');
187
+ } else if (el.matches('[aria-description]')) {
188
+ ret = el.getAttribute('aria-description');
189
+ } else if (el.matches('svg *')) {
190
+ var svgDesc = el.querySelector('desc');
191
+ if (svgDesc && svgDesc.parentElement === el) {
192
+ ret = svgDesc.textContent;
193
+ }
181
194
  } else if (el.title) {
182
195
  ret = el.title;
183
- } else if (el.placeholder) {
184
- ret = el.placeholder;
185
196
  }
186
197
 
187
198
  ret = (ret || '').trim().replace(/\s+/g, ' ');
package/lib/query.js CHANGED
@@ -22,7 +22,7 @@ var _querySelector = function(all) {
22
22
  return function(root, role) {
23
23
  var results = [];
24
24
  try {
25
- atree.walk(root, function(node) {
25
+ atree.walk(root, node => {
26
26
  if (node.nodeType === node.ELEMENT_NODE) {
27
27
  // FIXME: skip hidden elements
28
28
  if (matches(node, role)) {
@@ -43,7 +43,7 @@ var _querySelector = function(all) {
43
43
  };
44
44
 
45
45
  var closest = function(el, selector) {
46
- return atree.searchUp(el, function(candidate) {
46
+ return atree.searchUp(el, candidate => {
47
47
  if (candidate.nodeType === candidate.ELEMENT_NODE) {
48
48
  return matches(candidate, selector);
49
49
  }
@@ -51,9 +51,7 @@ var closest = function(el, selector) {
51
51
  };
52
52
 
53
53
  module.exports = {
54
- getRole: function(el) {
55
- return attrs.getRole(el);
56
- },
54
+ getRole: el => attrs.getRole(el),
57
55
  getAttribute: attrs.getAttribute,
58
56
  matches: matches,
59
57
  querySelector: _querySelector(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aria-api",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Access ARIA information from JavaScript",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -14,8 +14,8 @@
14
14
  "brfs": "^2.0.0",
15
15
  "browserify": "^17.0.0",
16
16
  "expect": "^1.20.2",
17
- "mocha": "^9.0.1",
18
- "mocha-headless-chrome": "^3.1.0"
17
+ "mocha": "^10.2.0",
18
+ "mocha-headless-chrome": "^4.0.0"
19
19
  },
20
20
  "files": [
21
21
  "index.js",