Skip to content

Commit ccdca12

Browse files
authored
fix(Breadcrumb): prevent last breadcrumb item from keyboard navigation [run-chromatic][skip-cd] (#648)
2 parents 1dc6b64 + 575776b commit ccdca12

2 files changed

Lines changed: 81 additions & 3 deletions

File tree

src/components/Breadcrumb/sgds-breadcrumb-item.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { html } from "lit";
1+
import { html, PropertyValueMap } from "lit";
22
import { property } from "lit/decorators.js";
33
import SgdsIcon from "../Icon/sgds-icon";
44
import SgdsLink from "../Link/sgds-link";
@@ -18,6 +18,23 @@ export class SgdsBreadcrumbItem extends SgdsElement {
1818
/** Indicates the link matches the current location of the page. Programmatically handled by SgdsBreadcrumb to set this prop to true for the last breadcrumb item */
1919
@property({ type: Boolean, reflect: true }) active = false;
2020

21+
private _preventNavigation = (e: MouseEvent) => e.preventDefault();
22+
23+
override updated(changedProperties: PropertyValueMap<this>) {
24+
super.updated(changedProperties);
25+
if (changedProperties.has("active")) {
26+
const anchor = this.querySelector<HTMLAnchorElement>("a");
27+
if (anchor) {
28+
if (this.active) {
29+
anchor.setAttribute("tabindex", "-1");
30+
anchor.addEventListener("click", this._preventNavigation);
31+
} else {
32+
anchor.removeEventListener("click", this._preventNavigation);
33+
}
34+
}
35+
}
36+
}
37+
2138
render() {
2239
return html`
2340
<sgds-link><slot class="nav-link"></slot></sgds-link>

test/breadcrumb.test.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ describe("sgds-breadcrumb", () => {
8787
active=""
8888
aria-current="page"
8989
>
90-
<a href="https://www.google.com/">
91-
Last
90+
<a
91+
href="https://www.google.com/"
92+
tabindex="-1"
93+
>
94+
Last
9295
</a>
9396
</sgds-breadcrumb-item>
9497
</div>
@@ -133,4 +136,62 @@ describe("sgds-breadcrumb-item", () => {
133136
`
134137
);
135138
});
139+
140+
it("sets tabindex=-1 on the slotted anchor of the active item", async () => {
141+
const el = await fixture<SgdsBreadcrumb>(html`
142+
<sgds-breadcrumb>
143+
<sgds-breadcrumb-item><a href="#">Home</a></sgds-breadcrumb-item>
144+
<sgds-breadcrumb-item><a href="#">Current</a></sgds-breadcrumb-item>
145+
</sgds-breadcrumb>
146+
`);
147+
const lastItem = el.querySelectorAll("sgds-breadcrumb-item")[1];
148+
expect(lastItem.querySelector("a")?.getAttribute("tabindex")).to.equal("-1");
149+
});
150+
it("does not set tabindex=-1 on non-active item anchors", async () => {
151+
const el = await fixture<SgdsBreadcrumb>(html`
152+
<sgds-breadcrumb>
153+
<sgds-breadcrumb-item><a href="#">Home</a></sgds-breadcrumb-item>
154+
<sgds-breadcrumb-item><a href="#">Current</a></sgds-breadcrumb-item>
155+
</sgds-breadcrumb>
156+
`);
157+
const firstItem = el.querySelectorAll("sgds-breadcrumb-item")[0];
158+
expect(firstItem.querySelector("a")?.getAttribute("tabindex")).to.not.equal("-1");
159+
});
160+
it("prevents mouse click navigation on the active anchor", async () => {
161+
const el = await fixture<SgdsBreadcrumb>(html`
162+
<sgds-breadcrumb>
163+
<sgds-breadcrumb-item><a href="#">Home</a></sgds-breadcrumb-item>
164+
<sgds-breadcrumb-item><a href="#">Current</a></sgds-breadcrumb-item>
165+
</sgds-breadcrumb>
166+
`);
167+
const lastItem = el.querySelectorAll("sgds-breadcrumb-item")[1];
168+
const clickEvent = new MouseEvent("click", { bubbles: true, cancelable: true });
169+
lastItem.querySelector("a")?.dispatchEvent(clickEvent);
170+
expect(clickEvent.defaultPrevented).to.be.true;
171+
});
172+
it("prevents keyboard Enter navigation on the active anchor", async () => {
173+
const el = await fixture<SgdsBreadcrumb>(html`
174+
<sgds-breadcrumb>
175+
<sgds-breadcrumb-item><a href="#">Home</a></sgds-breadcrumb-item>
176+
<sgds-breadcrumb-item><a href="#">Current</a></sgds-breadcrumb-item>
177+
</sgds-breadcrumb>
178+
`);
179+
const lastItem = el.querySelectorAll("sgds-breadcrumb-item")[1];
180+
// browsers fire a click event on a focused anchor when Enter is pressed
181+
const clickEvent = new MouseEvent("click", { bubbles: true, cancelable: true });
182+
lastItem.querySelector("a")?.dispatchEvent(clickEvent);
183+
expect(clickEvent.defaultPrevented).to.be.true;
184+
});
185+
it("does not prevent click navigation on non-active item anchors", async () => {
186+
const el = await fixture<SgdsBreadcrumb>(html`
187+
<sgds-breadcrumb>
188+
<sgds-breadcrumb-item><a href="#">Home</a></sgds-breadcrumb-item>
189+
<sgds-breadcrumb-item><a href="#">Current</a></sgds-breadcrumb-item>
190+
</sgds-breadcrumb>
191+
`);
192+
const firstItem = el.querySelectorAll("sgds-breadcrumb-item")[0];
193+
const clickEvent = new MouseEvent("click", { bubbles: true, cancelable: true });
194+
firstItem.querySelector("a")?.dispatchEvent(clickEvent);
195+
expect(clickEvent.defaultPrevented).to.be.false;
196+
});
136197
});

0 commit comments

Comments
 (0)