Add/improve slotProps handling for Label & Description

* Add slot props support to Label
* Refactor slot props usage in Description
* Add missing slot props to RadioGroupLabel and RadioGroupDescription
This commit is contained in:
Ryan Gossiaux
2022-01-27 16:02:48 -08:00
parent 0430f2d6b6
commit cdc0d86044
7 changed files with 84 additions and 12 deletions

View File

@@ -12,7 +12,6 @@
export let as: SupportedAs = "p"; export let as: SupportedAs = "p";
export let use: HTMLActionArray = []; export let use: HTMLActionArray = [];
$: slotProps = $contextStore?.props?.slotProps ?? {};
const id = `headlessui-description-${useId()}`; const id = `headlessui-description-${useId()}`;
let contextStore = useDescriptionContext(); let contextStore = useDescriptionContext();
if (!contextStore) { if (!contextStore) {
@@ -22,12 +21,15 @@
} }
onMount(() => $contextStore?.register(id)); onMount(() => $contextStore?.register(id));
$: slotProps = $contextStore!.slotProps;
</script> </script>
<Render <Render
name={"Description"} name={"Description"}
{...$$restProps} {...$$restProps}
{as} {as}
{slotProps}
{...$contextStore?.props} {...$contextStore?.props}
{id} {id}
use={[...use, forwardEvents]} use={[...use, forwardEvents]}

View File

@@ -1,7 +1,8 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export interface DescriptionContext { export interface DescriptionContext {
name?: string; name?: string;
props?: { slotProps?: object }; slotProps?: object;
props?: object;
register: (value: string) => void; register: (value: string) => void;
descriptionIds?: string; descriptionIds?: string;
} }
@@ -19,16 +20,19 @@
import type { Readable, Writable } from "svelte/store"; import type { Readable, Writable } from "svelte/store";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
export let name: string; export let name: string;
export let slotProps = {};
let descriptionIds: string[] = []; let descriptionIds: string[] = [];
let contextStore: Writable<DescriptionContext> = writable({ let contextStore: Writable<DescriptionContext> = writable({
name, name,
register, slotProps,
props: $$restProps, props: $$restProps,
register,
}); });
setContext(DESCRIPTION_CONTEXT_NAME, contextStore); setContext(DESCRIPTION_CONTEXT_NAME, contextStore);
$: contextStore.set({ $: contextStore.set({
name, name,
slotProps,
props: $$restProps, props: $$restProps,
register, register,
descriptionIds: descriptionIds:

View File

@@ -102,3 +102,33 @@ it("should be possible to use a DescriptionProvider and multiple Description com
</div> </div>
`); `);
}); });
it("should be possible to use a DescriptionProvider with slot props", async () => {
let { container } = render(svelte`
<DescriptionProvider name={"test"} slotProps={{num: 12345}} let:describedby>
<div aria-describedby={describedby}>
<Description let:num>{num}</Description>
<span>Contents</span>
</div>
</DescriptionProvider>
`);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-describedby="headlessui-description-1"
>
<p
id="headlessui-description-1"
>
12345
</p>
<span>
Contents
</span>
</div>
`);
});

View File

@@ -24,6 +24,9 @@
let allProps: any = {}; let allProps: any = {};
$: allProps = { ...$$restProps, ...$contextStore!.props, id }; $: allProps = { ...$$restProps, ...$contextStore!.props, id };
$: slotProps = $contextStore!.slotProps;
if (passive) delete allProps["onClick"]; if (passive) delete allProps["onClick"];
</script> </script>
@@ -32,10 +35,11 @@
{...allProps} {...allProps}
name={"Label"} name={"Label"}
{as} {as}
{slotProps}
use={[...use, forwardEvents]} use={[...use, forwardEvents]}
on:click={(event) => { on:click={(event) => {
if (!passive) allProps["onClick"]?.(event); if (!passive) allProps["onClick"]?.(event);
}} }}
> >
<slot /> <slot {...slotProps} />
</Render> </Render>

View File

@@ -1,6 +1,7 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export interface LabelContext { export interface LabelContext {
name?: string; name?: string;
slotProps?: object;
props?: object; props?: object;
register: (value: string) => void; register: (value: string) => void;
labelIds?: string; labelIds?: string;
@@ -17,16 +18,19 @@
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
export let name: string; export let name: string;
export let slotProps = {};
let labelIds: string[] = []; let labelIds: string[] = [];
let contextStore: Writable<LabelContext> = writable({ let contextStore: Writable<LabelContext> = writable({
name, name,
register, slotProps,
props: $$restProps, props: $$restProps,
register,
}); });
setContext(LABEL_CONTEXT_NAME, contextStore); setContext(LABEL_CONTEXT_NAME, contextStore);
$: contextStore.set({ $: contextStore.set({
name, name,
slotProps,
props: $$restProps, props: $$restProps,
register, register,
labelIds: labelIds.length > 0 ? labelIds.join(" ") : undefined, labelIds: labelIds.length > 0 ? labelIds.join(" ") : undefined,

View File

@@ -31,7 +31,7 @@ it(
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 name={"test"} let:labelledby>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
No label No label
</div> </div>
@@ -46,7 +46,7 @@ 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 name={"test"} let:labelledby>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label>I am a label</Label> <Label>I am a label</Label>
<span>Contents</span> <span>Contents</span>
@@ -74,7 +74,7 @@ it("should be possible to use a LabelProvider and a single Label, and have them
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 name={"test"} let:labelledby>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label>I am a label</Label> <Label>I am a label</Label>
<span>Contents</span> <span>Contents</span>
@@ -111,7 +111,7 @@ it("should be possible to use a LabelProvider and multiple Label components, and
it("should be possible to render a Label with an `as` prop", async () => { it("should be possible to render a Label with an `as` prop", async () => {
let { container } = render(svelte` let { container } = render(svelte`
<LabelProvider let:labelledby> <LabelProvider name={"test"} let:labelledby>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label as="p">I am a label</Label> <Label as="p">I am a label</Label>
<span>Contents</span> <span>Contents</span>
@@ -140,7 +140,7 @@ it("should be possible to render a Label with an `as` prop", async () => {
it("should be possible to change the props of a Label", async () => { it("should be possible to change the props of a Label", async () => {
let classStore: Writable<string | null> = writable(null); let classStore: Writable<string | null> = writable(null);
let { container } = render(svelte` let { container } = render(svelte`
<LabelProvider let:labelledby> <LabelProvider name={"test"} let:labelledby>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label class={$classStore}>I am a label</Label> <Label class={$classStore}>I am a label</Label>
<span>Contents</span> <span>Contents</span>
@@ -182,6 +182,34 @@ it("should be possible to change the props of a Label", async () => {
<span>
Contents
</span>
</div>
`);
});
it("should be possible to use a LabelProvider with slot props", async () => {
let { container } = render(svelte`
<LabelProvider name={"test"} slotProps={{num: 12345}} let:labelledby>
<div aria-labelledby={labelledby}>
<Label let:num>{num}</Label>
<span>Contents</span>
</div>
</LabelProvider>
`);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1"
>
<label
id="headlessui-label-1"
>
12345
</label>
<span> <span>
Contents Contents
</span> </span>

View File

@@ -74,8 +74,8 @@
}; };
</script> </script>
<DescriptionProvider name="RadioGroupDescription" let:describedby> <DescriptionProvider name="RadioGroupDescription" {slotProps} let:describedby>
<LabelProvider name="RadioGroupLabel" let:labelledby> <LabelProvider name="RadioGroupLabel" {slotProps} let:labelledby>
<Render <Render
{...{ ...$$restProps, ...propsWeControl }} {...{ ...$$restProps, ...propsWeControl }}
{as} {as}