{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"confetti","type":"registry:ui","title":"Confetti","description":"Confetti celebration effect powered by canvas-confetti with button trigger support","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["effects"],"dependencies":["canvas-confetti@^1.9.4"],"devDependencies":["@types/canvas-confetti@^1.9.0"],"registryDependencies":["https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/sound.json","https://fancy-ui.rama.app/r/utils.json"],"docs":"Installs canvas-confetti as a dependency.","files":[{"path":"react/src/components/confetti/Confetti.tsx","content":"\"use client\";\n\nimport { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport confettiModule from \"canvas-confetti\";\nimport type {\n\tGlobalOptions as ConfettiGlobalOptions,\n\tOptions as ConfettiOptions,\n\tCreateTypes as ConfettiInstance,\n} from \"canvas-confetti\";\nimport { useLiveRef } from \"../../internals/dom/use-live-ref\";\nimport { ConfettiReactContext } from \"./context\";\nimport type { ConfettiContextValue } from \"./context\";\n\n/**\n * The imperative surface the root exposes.\n *\n * The Svelte source declares `export function fire(...)`, which Svelte 5 hangs\n * off the component instance (`bind:this={c}; c.fire()`). React has no instance,\n * so the same method is published through the ref channel. This is the ONE place\n * the port's `forwardRef` is not backed by a `ref = $bindable` declaration: the\n * ref carries the handle, not the DOM node, because there is no bindable node to\n * carry and dropping `fire` would delete a public method.\n */\nexport interface ConfettiHandle {\n\t/** Fire the canvas instance. `opts` is merged over the component's `options`. */\n\tfire: (opts?: ConfettiOptions) => void;\n}\n\nexport interface ConfettiProps {\n\t/** Default confetti options, merged under every `fire()` call. */\n\toptions?: ConfettiOptions;\n\t/** Canvas creation options, read once when the instance is created. */\n\tglobalOptions?: ConfettiGlobalOptions;\n\t/** Skip the automatic fire on mount. */\n\tmanualStart?: boolean;\n\t/** Canvas CSS classes. */\n\tclassName?: string;\n\t/** Content rendered next to the canvas — typically the buttons that fire it. */\n\tchildren?: ReactNode;\n}\n\n/**\n * Canvas wrapper owning one `canvas-confetti` instance and publishing its\n * `fire` through context, so any `ConfettiButton` below it paints onto this\n * canvas rather than the library's own full-page one.\n *\n * Rest props are not spread: the Svelte source reads only these props off\n * `$props()` and has no `...restProps`.\n */\nexport const Confetti = forwardRef<ConfettiHandle, ConfettiProps>(\n\t(\n\t\t{ options = {}, globalOptions = {}, manualStart = false, className = \"\", children },\n\t\tref\n\t) => {\n\t\tconst canvasRef = useRef<HTMLCanvasElement | null>(null);\n\t\tconst instanceRef = useRef<ConfettiInstance | null>(null);\n\n\t\t// `fire` closes over the *current* `options` on the Svelte side because\n\t\t// `options` is reactive there. Live refs reproduce that without rebuilding\n\t\t// the callback — which keeps both the context value and the ref handle\n\t\t// identity-stable for the life of the component.\n\t\tconst optionsRef = useLiveRef(options);\n\t\tconst globalOptionsRef = useLiveRef(globalOptions);\n\t\tconst manualStartRef = useLiveRef(manualStart);\n\n\t\tconst fire = useCallback((opts: ConfettiOptions = {}) => {\n\t\t\tinstanceRef.current?.({ ...optionsRef.current, ...opts });\n\t\t}, [optionsRef]);\n\n\t\tuseImperativeHandle(ref, () => ({ fire }), [fire]);\n\n\t\tconst context = useMemo<ConfettiContextValue>(() => ({ fire }), [fire]);\n\n\t\t// The counterpart of `onMount` + its returned teardown. `fire` is stable,\n\t\t// so this runs exactly once; `globalOptions` and `manualStart` are read\n\t\t// through their live refs because the Svelte source reads them once, at\n\t\t// mount, and never re-creates the instance when they change.\n\t\tuseEffect(() => {\n\t\t\tconst canvas = canvasRef.current;\n\t\t\tif (!canvas) return;\n\n\t\t\tinstanceRef.current = confettiModule.create(canvas, {\n\t\t\t\t...globalOptionsRef.current,\n\t\t\t\tresize: true,\n\t\t\t});\n\n\t\t\tif (!manualStartRef.current) {\n\t\t\t\tfire();\n\t\t\t}\n\n\t\t\treturn () => {\n\t\t\t\tif (instanceRef.current) {\n\t\t\t\t\tinstanceRef.current.reset();\n\t\t\t\t\tinstanceRef.current = null;\n\t\t\t\t}\n\t\t\t};\n\t\t}, [fire, globalOptionsRef, manualStartRef]);\n\n\t\treturn (\n\t\t\t<ConfettiReactContext.Provider value={context}>\n\t\t\t\t<div>\n\t\t\t\t\t<canvas ref={canvasRef} className={className} aria-hidden=\"true\" />\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t</ConfettiReactContext.Provider>\n\t\t);\n\t}\n);\n\nConfetti.displayName = \"Confetti\";\n","type":"registry:ui","target":"@ui/fancy/components/confetti/Confetti.tsx"},{"path":"react/src/components/confetti/ConfettiButton.tsx","content":"\"use client\";\n\nimport { forwardRef } from \"react\";\nimport type { ButtonHTMLAttributes, MouseEvent, ReactNode } from \"react\";\nimport confetti from \"canvas-confetti\";\nimport type { Options as ConfettiOptions } from \"canvas-confetti\";\nimport { useSoundCue } from \"../../sound/use-sound\";\nimport { useSoundFeedback } from \"../../sound/sound-feedback\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { ConfettiReactContext } from \"./context\";\n\ntype BaseProps = {\n\t/** Confetti options for this button, merged over the surrounding root's options. */\n\toptions?: ConfettiOptions;\n\t/** Button content. */\n\tchildren?: ReactNode;\n\t/**\n\t * Plays the `hover` cue on mouse/pen pointer enter and the `press` cue on\n\t * click. Off by default; only audible once the user has enabled sound.\n\t */\n\tsound?: boolean;\n\t/** Custom CSS class */\n\tclassName?: string;\n};\n\nexport interface ConfettiButtonProps\n\textends BaseProps, Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps> {}\n\n/**\n * Button that fires confetti from its own centre.\n *\n * Inside a `Confetti` it paints on that component's canvas; outside one it falls\n * back to the module-level confetti and its full-page canvas.\n *\n * `type` defaults to `\"button\"` so it never submits a surrounding form; `className`\n * and native button attributes are forwarded, and a consumer `onClick` runs once\n * after the burst.\n */\nexport const ConfettiButton = forwardRef<HTMLButtonElement, ConfettiButtonProps>(\n\tfunction ConfettiButton(\n\t\t{ options = {}, children, sound = false, className, type = \"button\", onClick, ...restProps },\n\t\tref\n\t) {\n\t\tconst confettiContext = ConfettiReactContext.useOptional();\n\t\tconst playCue = useSoundCue(sound);\n\t\t// Hover cue for mouse/pen pointers only. `on: {}` binds nothing when sound is\n\t\t// off (and never the default click → press, which `handleClick` already plays).\n\t\tconst soundRef = useSoundFeedback({\n\t\t\ton: sound ? { pointerenter: \"hover\" } : {},\n\t\t\tdisabled: !!restProps.disabled,\n\t\t});\n\t\tconst composedRef = useComposedRefs(ref, soundRef);\n\n\t\tfunction handleClick(event: MouseEvent<HTMLButtonElement>) {\n\t\t\tif (restProps.disabled) return;\n\t\t\t// The cue fires before the burst, matching the Svelte source: the click\n\t\t\t// resolves nothing, so it gets `press` rather than a success cue. The\n\t\t\t// root's own auto-fire on mount stays silent — this button owns the\n\t\t\t// only cue in the family.\n\t\t\tplayCue(\"press\");\n\n\t\t\tconst target = event.currentTarget as HTMLElement;\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst x = rect.left + rect.width / 2;\n\t\t\tconst y = rect.top + rect.height / 2;\n\n\t\t\tconst origin = {\n\t\t\t\tx: x / window.innerWidth,\n\t\t\t\ty: y / window.innerHeight,\n\t\t\t};\n\n\t\t\tif (confettiContext) {\n\t\t\t\tconfettiContext.fire({ ...options, origin });\n\t\t\t} else {\n\t\t\t\tconfetti({ ...options, origin });\n\t\t\t}\n\t\t\tonClick?.(event);\n\t\t}\n\n\t\treturn (\n\t\t\t<button ref={composedRef} className={className} type={type} {...restProps} onClick={handleClick}>\n\t\t\t\t{children}\n\t\t\t</button>\n\t\t);\n\t}\n);\n","type":"registry:ui","target":"@ui/fancy/components/confetti/ConfettiButton.tsx"},{"path":"react/src/components/confetti/context.ts","content":"\"use client\";\n\n/**\n * The contract `Confetti` publishes to the buttons rendered inside it.\n *\n * The Svelte source keys this on the string `\"ConfettiContext\"` via\n * `setContext` / `getContext`; React's own context object plays that role\n * here. It is deliberately NOT re-exported from `index.ts` — the Svelte\n * `index.ts` exports only the two components, so the key has no public\n * identity on either side.\n */\n\nimport type { Options as ConfettiOptions } from \"canvas-confetti\";\nimport { createInternalContext } from \"../../internals/dom/context\";\n\n/** What the canvas root publishes: the one imperative method its children call. */\nexport interface ConfettiContextValue {\n\t/** Fire the shared canvas instance, merging `opts` over the root's own `options`. */\n\tfire: (opts?: ConfettiOptions) => void;\n}\n\n/**\n * Read with `ConfettiReactContext.useOptional()` — a `ConfettiButton` rendered\n * outside a `Confetti` gets `undefined` and falls back to the module-level\n * confetti, exactly as `getContext` returning `undefined` does on the Svelte side.\n */\nexport const ConfettiReactContext = createInternalContext<ConfettiContextValue>(\"ConfettiContext\");\n","type":"registry:ui","target":"@ui/fancy/components/confetti/context.ts"},{"path":"react/src/components/confetti/index.ts","content":"\"use client\";\n\nexport { Confetti } from \"./Confetti\";\nexport type { ConfettiProps } from \"./Confetti\";\nexport { ConfettiButton } from \"./ConfettiButton\";\nexport type { ConfettiButtonProps } from \"./ConfettiButton\";\n","type":"registry:ui","target":"@ui/fancy/components/confetti/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/confetti?framework=react"}}