Skip to content

Commit c257353

Browse files
fix: th role in context (#33)
1 parent 0793d27 commit c257353

File tree

5 files changed

+86
-17
lines changed

5 files changed

+86
-17
lines changed

.changeset/fresh-forks-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"html-aria": patch
3+
---
4+
5+
Fix role for th element in context

src/lib/util.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function calculateAccessibleName(element: VirtualElement): string | undef
7878
}
7979

8080
/** Is an ancestor list provided and is it empty? */
81-
export function isEmptyAncestorList(ancestors?: AncestorList): boolean {
81+
export function isEmptyAncestorList(ancestors?: AncestorList): ancestors is [] {
8282
return Array.isArray(ancestors) && ancestors.length === 0;
8383
}
8484

@@ -120,6 +120,19 @@ export function hasLandmarkParent(ancestors: AncestorList) {
120120
);
121121
}
122122

123+
/** Logic shared by <td> and <th> when determining role */
124+
export function hasGridParent(ancestors: AncestorList) {
125+
const gridParent = firstMatchingAncestor(
126+
[
127+
{ tagName: 'table', attributes: { role: 'grid' } },
128+
{ tagName: 'table', attributes: { role: 'treegrid' } },
129+
],
130+
ancestors,
131+
);
132+
133+
return gridParent?.attributes?.role === 'grid' || gridParent?.attributes?.role === 'treegrid';
134+
}
135+
123136
export interface RemoveProhibitedOptions<P extends ARIAAttribute[]> {
124137
nameProhibited?: boolean;
125138
prohibited?: P;

src/tags/td.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { tags } from '../lib/html.js';
2-
import { firstMatchingAncestor, isEmptyAncestorList } from '../lib/util.js';
1+
import { NO_CORRESPONDING_ROLE, tags } from '../lib/html.js';
2+
import { hasGridParent, isEmptyAncestorList } from '../lib/util.js';
33
import type { AncestorList } from '../types.js';
44

55
/** Special behavior for <td> element */
@@ -13,20 +13,12 @@ export function getTDRole({ ancestors }: { ancestors?: AncestorList } = {}) {
1313
// default, it would likely cause bad results because most users would
1414
// likely skip this optional setup).
1515
if (isEmptyAncestorList(ancestors)) {
16-
return undefined;
16+
return NO_CORRESPONDING_ROLE;
1717
}
1818

19-
const hasGridParent = firstMatchingAncestor(
20-
[
21-
// Note: most of the time, the tagName
22-
{ tagName: 'table', attributes: { role: 'table' } },
23-
{ tagName: 'table', attributes: { role: 'grid' } },
24-
{ tagName: 'table', attributes: { role: 'treegrid' } },
25-
],
26-
ancestors,
27-
);
28-
if (hasGridParent?.attributes?.role === 'grid' || hasGridParent?.attributes?.role === 'treegrid') {
19+
if (hasGridParent(ancestors)) {
2920
return 'gridcell';
3021
}
22+
3123
return tags.td.defaultRole;
3224
}

src/tags/th.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { isEmptyAncestorList } from '../lib/util.js';
1+
import { NO_CORRESPONDING_ROLE, tags } from '../lib/html.js';
2+
import { firstMatchingAncestor, hasGridParent, isEmptyAncestorList } from '../lib/util.js';
23
import type { AncestorList, VirtualElement } from '../types.js';
34

45
/** Special behavior for <th> element */
@@ -11,8 +12,49 @@ export function getTHRole({
1112
// default, it would likely cause bad results because most users would
1213
// likely skip this optional setup).
1314
if (isEmptyAncestorList(ancestors)) {
14-
return undefined;
15+
return NO_CORRESPONDING_ROLE;
1516
}
1617

17-
return attributes?.scope === 'row' ? 'rowheader' : 'columnheader';
18+
// Currently deviates from specification as doesn't handle the `auto`
19+
// behaviour as that would require access to the DOM context.
20+
switch (attributes?.scope) {
21+
/**
22+
* @see https://www.w3.org/TR/html-aam-1.0/#el-th-columnheader
23+
*/
24+
case 'col':
25+
case 'colgroup': {
26+
return 'columnheader';
27+
}
28+
/**
29+
* @see https://www.w3.org/TR/html-aam-1.0/#el-th-rowheader
30+
*/
31+
case 'row':
32+
case 'rowgroup': {
33+
return 'rowheader';
34+
}
35+
}
36+
37+
// See previous comment r.e. special behaviour.
38+
if (!ancestors) {
39+
return tags.th.defaultRole;
40+
}
41+
42+
/**
43+
* @see https://www.w3.org/TR/html-aam-1.0/#el-th-gridcell
44+
*/
45+
if (hasGridParent(ancestors)) {
46+
return 'gridcell';
47+
}
48+
49+
/**
50+
* @see https://www.w3.org/TR/html-aam-1.0/#el-th
51+
*/
52+
const hasTableParent = !!firstMatchingAncestor([{ tagName: 'table', attributes: { role: 'table' } }], ancestors);
53+
54+
if (hasTableParent) {
55+
return 'cell';
56+
}
57+
58+
// See previous comment r.e. special behaviour.
59+
return tags.th.defaultRole;
1860
}

test/get-role.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,24 @@ describe('getRole', () => {
337337
['th', { given: [{ tagName: 'th' }], want: 'columnheader' }],
338338
['th (no ancestors)', { given: [{ tagName: 'th' }, { ancestors: [] }], want: undefined }],
339339
['th[scope=col]', { given: [{ tagName: 'th', attributes: { scope: 'col' } }], want: 'columnheader' }],
340+
['th[scope=colgroup]', { given: [{ tagName: 'th', attributes: { scope: 'colgroup' } }], want: 'columnheader' }],
340341
['th[scope=row]', { given: [{ tagName: 'th', attributes: { scope: 'row' } }], want: 'rowheader' }],
342+
['th[scope=rowgroup]', { given: [{ tagName: 'th', attributes: { scope: 'rowgroup' } }], want: 'rowheader' }],
343+
['th (table)', { given: [{ tagName: 'th' }, { ancestors: [{ tagName: 'table' }] }], want: 'cell' }],
344+
[
345+
'th (grid)',
346+
{
347+
given: [{ tagName: 'th' }, { ancestors: [{ tagName: 'table', attributes: { role: 'grid' } }] }],
348+
want: 'gridcell',
349+
},
350+
],
351+
[
352+
'th (treegrid)',
353+
{
354+
given: [{ tagName: 'th' }, { ancestors: [{ tagName: 'table', attributes: { role: 'treegrid' } }] }],
355+
want: 'gridcell',
356+
},
357+
],
341358
['time', { given: [{ tagName: 'time' }], want: 'time' }],
342359
['title', { given: [{ tagName: 'title' }], want: undefined }],
343360
['tr', { given: [{ tagName: 'tr' }], want: 'row' }],

0 commit comments

Comments
 (0)