diff --git a/src/lib/components/Toggle.svelte b/src/lib/components/Toggle.svelte index 1457659f..fb5e8887 100644 --- a/src/lib/components/Toggle.svelte +++ b/src/lib/components/Toggle.svelte @@ -11,14 +11,38 @@ const dispatch = createEventDispatcher(); const id = nextElementId("toggle-"); + + let input: HTMLInputElement | undefined; + + const onKeyDown = ({ code }: KeyboardEvent) => { + if (code !== "Space") { + return; + } + + input?.click(); + }; + + let toggle = checked; + + const onInput = (checked: boolean) => { + dispatch("nnsToggle", checked); + toggle = checked; + }; -
+
- dispatch("nnsToggle", currentTarget.checked)} + on:input={({ currentTarget }) => onInput(currentTarget.checked)} {checked} aria-label={ariaLabel} {disabled} @@ -37,6 +61,8 @@ align-items: center; margin-top: 1px; + width: fit-content; + &.disabled { opacity: var(--toggle-disabled-opacity, 0.25); } diff --git a/src/tests/lib/components/Toggle.spec.ts b/src/tests/lib/components/Toggle.spec.ts index a8a9c40d..897dc73d 100644 --- a/src/tests/lib/components/Toggle.spec.ts +++ b/src/tests/lib/components/Toggle.spec.ts @@ -1,5 +1,5 @@ import Toggle from "$lib/components/Toggle.svelte"; -import { fireEvent, render } from "@testing-library/svelte"; +import { fireEvent, render, waitFor } from "@testing-library/svelte"; describe("Toggle", () => { const props = { @@ -53,4 +53,54 @@ describe("Toggle", () => { expect(onToggle).toBeCalled(); }); + + it("should toggle checked with keyboard", () => { + const { component, container } = render(Toggle, { props }); + + const toggle = container.querySelector("div.toggle") as HTMLDivElement; + + const onToggle = vi.fn(); + component.$on("nnsToggle", onToggle); + + fireEvent.keyDown(toggle, { code: "Space" }); + + expect(onToggle).toBeCalled(); + }); + + it("should not toggle checked with another keyboard event than Space", () => { + const { component, container } = render(Toggle, { props }); + + const toggle = container.querySelector("div.toggle") as HTMLDivElement; + + const onToggle = vi.fn(); + component.$on("nnsToggle", onToggle); + + fireEvent.keyDown(toggle, { code: "a" }); + + expect(onToggle).not.toBeCalled(); + expect(toggle.getAttribute("aria-pressed")).toEqual("false"); + }); + + it("should reflect toggle state on aria pressed", async () => { + const { container } = render(Toggle, { props }); + + const toggle = container.querySelector("div.toggle") as HTMLDivElement; + + expect(toggle.getAttribute("aria-pressed")).toEqual("false"); + + fireEvent.keyDown(toggle, { code: "Space" }); + + await waitFor(() => + expect(toggle.getAttribute("aria-pressed")).toEqual("true"), + ); + }); + + it("should have an accessible toggle", () => { + const { container } = render(Toggle, { props }); + + const toggle = container.querySelector("div.toggle") as HTMLDivElement; + + expect(toggle.getAttribute("role")).toEqual("button"); + expect(toggle.getAttribute("tabindex")).toEqual("0"); + }); });