Files
svelte-headlessui/src/lib/utils/Render.svelte
2022-03-18 11:19:25 -07:00

145 lines
3.9 KiB
Svelte

<script lang="ts" context="module">
import type { SupportedAs, SupportedElement } from "$lib/internal/elements";
import { getElementComponent } from "$lib/internal/elements";
import { get_current_component } from "svelte/internal";
export enum Features {
/** No features at all */
None = 0,
/**
* When used, this will allow us to use one of the render strategies.
*
* **The render strategies are:**
* - **Unmount** _(Will unmount the component.)_
* - **Hidden** _(Will hide the component using the [hidden] attribute.)_
*/
RenderStrategy = 1,
/**
* When used, this will allow the user of our component to be in control. This can be used when
* you want to transition based on some state.
*/
Static = 2,
}
export enum RenderStrategy {
Unmount,
Hidden,
}
type TRestProps<T> = T extends SupportedElement
? Omit<
svelte.JSX.HTMLAttributes<HTMLElementTagNameMap[T]>,
"class" | "style"
>
: {};
type TRenderProps<
TSlotProps extends {},
TAsProp extends SupportedAs
> = TRestProps<TAsProp> & {
name: string;
as: TAsProp;
slotProps: TSlotProps;
el?: HTMLElement | null;
use?: HTMLActionArray;
visible?: boolean;
features?: Features;
static?: boolean;
unmount?: boolean;
class?: ((props: TSlotProps) => string) | string;
style?: ((props: TSlotProps) => string) | string;
};
type TInternalProps = "name" | "slotProps" | "el" | "visible" | "features";
export type TPassThroughProps<
TSlotProps extends {},
TAsProp extends SupportedAs
> = Omit<
TRenderProps<TSlotProps, TAsProp>,
TInternalProps | "as" | "static" | "unmount"
> & {
as?: TAsProp;
};
</script>
<script lang="ts">
import type { HTMLActionArray } from "$lib/hooks/use-actions";
import { forwardEventsBuilder } from "$lib/internal/forwardEventsBuilder";
const forwardEvents = forwardEventsBuilder(get_current_component());
type TSlotProps = $$Generic<{}>;
type TAsProp = $$Generic<SupportedAs>;
type $$Props = TRenderProps<TSlotProps, TAsProp>;
export let name: string;
export let as: TAsProp;
export let slotProps: TSlotProps;
export let el: HTMLElement | null = null;
export let use: HTMLActionArray = [];
export let visible = true;
export let features: Features = Features.None;
// The static and unmount props are only used in conjunction with the render strategies
export let unmount = true;
let static_ = false;
export { static_ as static };
let classProp: ((props: TSlotProps) => string) | string | undefined =
undefined;
export { classProp as class };
// This is not in upstream Headless UI, but we might as well add it here
export let style: ((props: TSlotProps) => string) | string | undefined =
undefined;
if (!as) {
throw new Error(`<${name}> did not provide an \`as\` value to <Render>`);
}
let element = typeof as === "string" ? getElementComponent(as) : as;
if (!element) {
throw new Error(
`<${name}> has an invalid or unsupported \`as\` prop: ${as}`
);
}
$: computedClass =
typeof classProp === "function" ? classProp(slotProps) : classProp;
$: computedStyle = typeof style === "function" ? style(slotProps) : style;
$: show =
visible ||
(features & Features.Static && static_) ||
!(features & Features.RenderStrategy && unmount);
$: hidden =
!visible &&
!(features & Features.Static && static_) &&
features & Features.RenderStrategy &&
!unmount;
$: propsWeControl = {
class: computedClass,
style:
`${computedStyle ?? ""}${hidden ? " display: none" : ""}` || undefined,
};
$: if (propsWeControl.style === undefined) {
delete propsWeControl.style;
}
</script>
{#if show}
<svelte:component
this={element}
bind:el
use={[...use, forwardEvents]}
{...$$restProps}
{...propsWeControl}
hidden={hidden || undefined}
>
<slot />
</svelte:component>
{/if}