Recipes and pitfalls
This component combines the prop types, XOR, and variantMap.
<script lang="ts">
import { variantMap } from "@atom-forge/svelte-helpers";
import type {
AnyProp,
ChildrenProp,
ClassProp,
XOR
} from "@atom-forge/svelte-helpers";
import { twMerge } from "tailwind-merge";
type Variant = XOR<{ primary: true }, { secondary: true }, { ghost: true }, {}>;
let {
primary,
secondary,
ghost,
class: classes,
children,
...props
}: Variant & ChildrenProp & ClassProp & AnyProp = $props();
const variant = $derived(variantMap({ primary, secondary, ghost }, "default"));
const cls = $derived(twMerge("btn", `btn-${variant}`, classes));
</script>
<button {...props} class={cls}>{@render children()}</button>
Usage: <Button primary>Save</Button>. <Button primary ghost> is a type error.
<script lang="ts">
import { debounce } from "@atom-forge/svelte-helpers";
let { onsearch }: { onsearch: (q: string) => void } = $props();
const emit = debounce((q: string) => onsearch(q), 300);
</script>
<input oninput={(e) => emit(e.currentTarget.value)} />
Pitfalls
| Symptom |
Cause |
Fix |
| Variant never changes after prop update |
variantMap wrapped in untrack |
Use $derived(variantMap(...)) |
small always wins over compact |
First truthy entry wins and no XOR |
Type the props with XOR |
| Awaited debounced call never resolves |
Superseded debounceAsync promises stay pending |
Do not await it; update state inside the callback |
| Error from debounced async function disappears |
Rejections are not forwarded |
try/catch inside the callback |
Runtime type error after as.string(...) |
as performs no conversion |
Validate or convert the value yourself |
RenderSnippet throws |
Missing snippet, or snippet takes no argument object |
Pass both props; define {#snippet name(args)} |
Recipes and pitfalls
A reusable button
This component combines the prop types,
XOR, andvariantMap.<script lang="ts"> import { variantMap } from "@atom-forge/svelte-helpers"; import type { AnyProp, ChildrenProp, ClassProp, XOR } from "@atom-forge/svelte-helpers"; import { twMerge } from "tailwind-merge"; // external type Variant = XOR<{ primary: true }, { secondary: true }, { ghost: true }, {}>; let { primary, secondary, ghost, class: classes, children, ...props }: Variant & ChildrenProp & ClassProp & AnyProp = $props(); const variant = $derived(variantMap({ primary, secondary, ghost }, "default")); const cls = $derived(twMerge("btn", `btn-${variant}`, classes)); </script> <button {...props} class={cls}>{@render children()}</button>Usage:
<Button primary>Save</Button>.<Button primary ghost>is a type error.A debounced search input
<script lang="ts"> import { debounce } from "@atom-forge/svelte-helpers"; let { onsearch }: { onsearch: (q: string) => void } = $props(); const emit = debounce((q: string) => onsearch(q), 300); </script> <input oninput={(e) => emit(e.currentTarget.value)} />Pitfalls
variantMapwrapped inuntrack$derived(variantMap(...))smallalways wins overcompactXORXORdebounceAsyncpromises stay pendingtry/catchinside the callbackas.string(...)asperforms no conversionRenderSnippetthrowssnippet, or snippet takes no argument object{#snippet name(args)}