Skip to content

Commit 107be8d

Browse files
Merge pull request #568 from universal-ember/heading/deep-search
Heading: allow automatic heading selection when headings are nested in other elements
2 parents 6c3cfc3 + 93899e7 commit 107be8d

File tree

2 files changed

+166
-4
lines changed

2 files changed

+166
-4
lines changed

ember-primitives/src/components/heading.gts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ function isRoot(element: Element) {
4040
return element === document.body || element.id === TEST_BOUNDARY;
4141
}
4242

43+
function findHeadingIn(node: ParentNode): number | undefined {
44+
if (!(node instanceof Element)) return;
45+
46+
if (SECTION_HEADINGS.has(node.tagName)) {
47+
const level = parseInt(node.tagName.replace("h", "").replace("H", ""));
48+
49+
return level;
50+
}
51+
52+
for (const child of node.children) {
53+
const level = findHeadingIn(child);
54+
55+
if (level) return level;
56+
}
57+
}
58+
4359
/**
4460
* The Platform native 'closest' function can't punch through shadow-boundaries
4561
*/
@@ -104,11 +120,9 @@ function levelOf(node: Text): number {
104120
let current: ParentNode | null = ourBoundary.parentNode;
105121

106122
while (current) {
107-
for (const child of current.children) {
108-
if (!SECTION_HEADINGS.has(child.tagName)) continue;
109-
110-
const level = parseInt(child.tagName.replace("h", "").replace("H", ""));
123+
const level = findHeadingIn(current);
111124

125+
if (level) {
112126
return level + 1;
113127
}
114128

test-app/tests/heading/rendering-test.gts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,154 @@ module('Rendering | Heading', function (hooks) {
2424
assert.dom('#b').hasTagName('h2');
2525
});
2626

27+
module('wrapped headings', function () {
28+
test('h1 wrapped in <a>', async function (assert) {
29+
await render(
30+
<template>
31+
<a href="#">
32+
<Heading id="a">one</Heading>
33+
</a>
34+
<section>
35+
<Heading id="b">two</Heading>
36+
</section>
37+
</template>
38+
);
39+
40+
assert.dom('#a').hasTagName('h1');
41+
assert.dom('#b').hasTagName('h2');
42+
});
43+
44+
test('h2 wrapped in <a>', async function (assert) {
45+
await render(
46+
<template>
47+
<Heading id="a">one</Heading>
48+
49+
<section>
50+
<a href="#">
51+
<Heading id="b">two</Heading>
52+
</a>
53+
</section>
54+
</template>
55+
);
56+
57+
assert.dom('#a').hasTagName('h1');
58+
assert.dom('#b').hasTagName('h2');
59+
});
60+
61+
test('h1 wrapped in <header><a>', async function (assert) {
62+
await render(
63+
<template>
64+
<header>
65+
<a href="#">
66+
<Heading id="a">one</Heading>
67+
</a>
68+
</header>
69+
<section>
70+
<Heading id="b">two</Heading>
71+
</section>
72+
</template>
73+
);
74+
75+
assert.dom('#a').hasTagName('h1');
76+
assert.dom('#b').hasTagName('h2');
77+
});
78+
79+
test('h1 wrapped in <header><div><a>', async function (assert) {
80+
await render(
81+
<template>
82+
<header>
83+
<div>
84+
<a href="#">
85+
<Heading id="a">one</Heading>
86+
</a>
87+
</div>
88+
</header>
89+
<section>
90+
<Heading id="b">two</Heading>
91+
</section>
92+
</template>
93+
);
94+
95+
assert.dom('#a').hasTagName('h1');
96+
assert.dom('#b').hasTagName('h2');
97+
});
98+
99+
test('h1 wrapped in <header><div><a>, h2 wrapped in <a>', async function (assert) {
100+
await render(
101+
<template>
102+
<header>
103+
<div>
104+
<a href="#">
105+
<Heading id="a">one</Heading>
106+
</a>
107+
</div>
108+
</header>
109+
<section>
110+
<a href="#">
111+
<Heading id="b">two</Heading>
112+
</a>
113+
114+
<section>
115+
<a href="#">
116+
<Heading id="c">three</Heading>
117+
</a>
118+
</section>
119+
</section>
120+
</template>
121+
);
122+
123+
assert.dom('#a').hasTagName('h1');
124+
assert.dom('#b').hasTagName('h2');
125+
assert.dom('#c').hasTagName('h3');
126+
});
127+
128+
test('[extraneous handling] h1 wrapped in <header><div><a>, h2 wrapped in <a>', async function (assert) {
129+
await render(
130+
<template>
131+
<header>
132+
<div>
133+
<a href="#">
134+
<Heading id="a">one</Heading>
135+
</a>
136+
<section>
137+
<a href="#">
138+
<Heading id="d">two</Heading>
139+
</a>
140+
</section>
141+
</div>
142+
</header>
143+
<section>
144+
<a href="#">
145+
<Heading id="b">two</Heading>
146+
</a>
147+
148+
<Heading id="f">two</Heading>
149+
150+
<section>
151+
<a href="#">
152+
<Heading id="c">three</Heading>
153+
</a>
154+
155+
<section>
156+
<a href="#">
157+
<Heading id="e">four</Heading>
158+
</a>
159+
</section>
160+
</section>
161+
</section>
162+
</template>
163+
);
164+
165+
assert.dom('#a').hasTagName('h1');
166+
assert.dom('#b').hasTagName('h2');
167+
assert.dom('#c').hasTagName('h3');
168+
169+
assert.dom('#d').hasTagName('h2');
170+
assert.dom('#e').hasTagName('h4');
171+
assert.dom('#f').hasTagName('h2');
172+
});
173+
});
174+
27175
module('in shadow', function () {
28176
test('in a section', async function (assert) {
29177
await render(

0 commit comments

Comments
 (0)