@@ -1,7 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { HTMLActionArray } from "$lib/hooks/use-actions";
|
||||||
|
import Render from "$lib/utils/Render.svelte";
|
||||||
import { useId } from "$lib/hooks/use-id";
|
import { useId } from "$lib/hooks/use-id";
|
||||||
|
import type { SupportedAs } from "$lib/internal/elements";
|
||||||
|
import { forwardEventsBuilder } from "$lib/internal/forwardEventsBuilder";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { get_current_component } from "svelte/internal";
|
||||||
import { useLabelContext } from "./LabelProvider.svelte";
|
import { useLabelContext } from "./LabelProvider.svelte";
|
||||||
|
const forwardEvents = forwardEventsBuilder(get_current_component());
|
||||||
|
export let as: SupportedAs = "label";
|
||||||
|
export let use: HTMLActionArray = [];
|
||||||
|
|
||||||
const id = `headlessui-label-${useId()}`;
|
const id = `headlessui-label-${useId()}`;
|
||||||
export let passive = false;
|
export let passive = false;
|
||||||
let contextStore = useLabelContext();
|
let contextStore = useLabelContext();
|
||||||
@@ -13,22 +22,20 @@
|
|||||||
|
|
||||||
onMount(() => $contextStore!.register(id));
|
onMount(() => $contextStore!.register(id));
|
||||||
|
|
||||||
let allProps = { ...$$restProps, ...$contextStore!.props, id } as any;
|
let allProps: any = {};
|
||||||
|
$: allProps = { ...$$restProps, ...$contextStore!.props, id };
|
||||||
if (passive) delete allProps["onClick"];
|
if (passive) delete allProps["onClick"];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||||
<label
|
<Render
|
||||||
{...allProps}
|
{...allProps}
|
||||||
on:blur
|
name={"Label"}
|
||||||
on:click
|
{as}
|
||||||
on:focus
|
use={[...use, forwardEvents]}
|
||||||
on:keyup
|
|
||||||
on:keydown
|
|
||||||
on:keypress
|
|
||||||
on:click={(event) => {
|
on:click={(event) => {
|
||||||
if (!passive) allProps["onClick"]?.(event);
|
if (!passive) allProps["onClick"]?.(event);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</label>
|
</Render>
|
||||||
|
|||||||
130
src/lib/components/label/label.test.ts
vendored
130
src/lib/components/label/label.test.ts
vendored
@@ -2,6 +2,9 @@ import { render } from "@testing-library/svelte";
|
|||||||
import Label from "./Label.svelte";
|
import Label from "./Label.svelte";
|
||||||
import LabelProvider from "./LabelProvider.svelte";
|
import LabelProvider from "./LabelProvider.svelte";
|
||||||
import svelte from "svelte-inline-compile";
|
import svelte from "svelte-inline-compile";
|
||||||
|
import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import { tick } from "svelte";
|
||||||
|
|
||||||
let mockId = 0;
|
let mockId = 0;
|
||||||
jest.mock("../../hooks/use-id", () => {
|
jest.mock("../../hooks/use-id", () => {
|
||||||
@@ -13,6 +16,19 @@ jest.mock("../../hooks/use-id", () => {
|
|||||||
beforeEach(() => (mockId = 0));
|
beforeEach(() => (mockId = 0));
|
||||||
afterAll(() => jest.restoreAllMocks());
|
afterAll(() => jest.restoreAllMocks());
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should error when we are using a <Label /> without a parent <LabelProvider />",
|
||||||
|
suppressConsoleLogs(async () => {
|
||||||
|
expect(() => render(Label)).toThrowError(
|
||||||
|
`You used a <Label /> component, but it is not inside a relevant parent.`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
it("should be possible to use a LabelProvider without using a Label", async () => {
|
it("should be possible to use a LabelProvider without using a Label", async () => {
|
||||||
let { container } = render(svelte`
|
let { container } = render(svelte`
|
||||||
<LabelProvider let:labelledby>
|
<LabelProvider let:labelledby>
|
||||||
@@ -31,36 +47,144 @@ it("should be possible to use a LabelProvider without using a Label", async () =
|
|||||||
it("should be possible to use a LabelProvider and a single Label, and have them linked", async () => {
|
it("should be possible to use a LabelProvider and a single Label, and have them linked", async () => {
|
||||||
let { container } = render(svelte`
|
let { container } = render(svelte`
|
||||||
<LabelProvider let:labelledby>
|
<LabelProvider let:labelledby>
|
||||||
<Label>I am a label</Label>
|
|
||||||
<div aria-labelledby={labelledby}>
|
<div aria-labelledby={labelledby}>
|
||||||
|
<Label>I am a label</Label>
|
||||||
<span>Contents</span>
|
<span>Contents</span>
|
||||||
</div>
|
</div>
|
||||||
</LabelProvider>
|
</LabelProvider>
|
||||||
`);
|
`);
|
||||||
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
aria-labelledby="headlessui-label-1"
|
||||||
|
>
|
||||||
<label
|
<label
|
||||||
id="headlessui-label-1"
|
id="headlessui-label-1"
|
||||||
>
|
>
|
||||||
I am a label
|
I am a label
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Contents
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be possible to use a LabelProvider and multiple Label components, and have them linked", async () => {
|
it("should be possible to use a LabelProvider and multiple Label components, and have them linked", async () => {
|
||||||
let { container } = render(svelte`
|
let { container } = render(svelte`
|
||||||
<LabelProvider let:labelledby>
|
<LabelProvider let:labelledby>
|
||||||
<Label>I am a label</Label>
|
|
||||||
<div aria-labelledby={labelledby}>
|
<div aria-labelledby={labelledby}>
|
||||||
|
<Label>I am a label</Label>
|
||||||
<span>Contents</span>
|
<span>Contents</span>
|
||||||
</div>
|
|
||||||
<Label>I am also a label</Label>
|
<Label>I am also a label</Label>
|
||||||
|
</div>
|
||||||
</LabelProvider>
|
</LabelProvider>
|
||||||
`);
|
`);
|
||||||
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
aria-labelledby="headlessui-label-1 headlessui-label-2"
|
||||||
|
>
|
||||||
<label
|
<label
|
||||||
id="headlessui-label-1"
|
id="headlessui-label-1"
|
||||||
>
|
>
|
||||||
I am a label
|
I am a label
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Contents
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<label
|
||||||
|
id="headlessui-label-2"
|
||||||
|
>
|
||||||
|
I am also a label
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be possible to render a Label with an `as` prop", async () => {
|
||||||
|
let { container } = render(svelte`
|
||||||
|
<LabelProvider let:labelledby>
|
||||||
|
<div aria-labelledby={labelledby}>
|
||||||
|
<Label as="p">I am a label</Label>
|
||||||
|
<span>Contents</span>
|
||||||
|
</div>
|
||||||
|
</LabelProvider>
|
||||||
|
`);
|
||||||
|
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
aria-labelledby="headlessui-label-1"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
id="headlessui-label-1"
|
||||||
|
>
|
||||||
|
I am a label
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Contents
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be possible to change the props of a Label", async () => {
|
||||||
|
let classStore: Writable<string | null> = writable(null);
|
||||||
|
let { container } = render(svelte`
|
||||||
|
<LabelProvider let:labelledby>
|
||||||
|
<div aria-labelledby={labelledby}>
|
||||||
|
<Label class={$classStore}>I am a label</Label>
|
||||||
|
<span>Contents</span>
|
||||||
|
</div>
|
||||||
|
</LabelProvider>
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
aria-labelledby="headlessui-label-1"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
id="headlessui-label-1"
|
||||||
|
>
|
||||||
|
I am a label
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Contents
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
classStore.set("test-class");
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
aria-labelledby="headlessui-label-1"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="test-class"
|
||||||
|
id="headlessui-label-1"
|
||||||
|
>
|
||||||
|
I am a label
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Contents
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user