@input-kit/confetti 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Input Kit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @input-kit/confetti
2
+
3
+ Lightweight, zero-dependency confetti effect for the browser. Built with the Canvas API, fully typed, SSR-safe, and cancellable.
4
+
5
+ ## Features
6
+
7
+ - Canvas-based particle renderer — no external dependencies
8
+ - Configurable launch angle, spread, gravity, drift, and particle count
9
+ - Built-in `celebrate()` multi-burst preset
10
+ - Returns a cancellation function to stop an in-flight animation
11
+ - Automatically cancels when the tab is hidden (via `visibilitychange`)
12
+ - SSR-safe — no-ops when `document` is unavailable
13
+ - Zero React required — works with any framework or plain HTML
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @input-kit/confetti
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```ts
24
+ import { confetti, celebrate } from '@input-kit/confetti';
25
+
26
+ // Basic burst from center
27
+ confetti();
28
+
29
+ // Custom burst
30
+ confetti({
31
+ particleCount: 150,
32
+ angle: 90,
33
+ spread: 60,
34
+ origin: { x: 0.5, y: 0.8 },
35
+ colors: ['#ff6b6b', '#feca57', '#48dbfb'],
36
+ gravity: 0.6,
37
+ ticks: 250,
38
+ });
39
+
40
+ // Built-in multi-burst celebration
41
+ celebrate();
42
+ ```
43
+
44
+ ### Cancel an animation
45
+
46
+ ```ts
47
+ const stop = confetti({ particleCount: 500, ticks: 600 });
48
+ // Later:
49
+ stop?.();
50
+ ```
51
+
52
+ ## API
53
+
54
+ ### `confetti(options?)`
55
+
56
+ Fires a confetti burst. Returns a cancellation function, or `undefined` in SSR environments.
57
+
58
+ ```ts
59
+ function confetti(options?: ConfettiOptions): (() => void) | undefined
60
+ ```
61
+
62
+ ### `fireConfetti(options?)`
63
+
64
+ Alias for `confetti`.
65
+
66
+ ### `celebrate()`
67
+
68
+ Fires three sequential bursts — left cannon, right cannon, center explosion — for a full-screen celebration effect.
69
+
70
+ ```ts
71
+ function celebrate(): void
72
+ ```
73
+
74
+ ## ConfettiOptions
75
+
76
+ | Option | Type | Default | Description |
77
+ |---|---|---|---|
78
+ | `particleCount` | `number` | `100` | Number of particles to spawn |
79
+ | `angle` | `number` | `90` | Launch direction in degrees. `0` = right, `90` = up, `180` = left, `270` = down |
80
+ | `spread` | `number` | `360` | Cone width in degrees centered on `angle`. `360` = all directions, `60` = narrow cone |
81
+ | `origin` | `{ x: number; y: number }` | `{ x: 0.5, y: 0.5 }` | Launch point as fraction of viewport. `(0, 0)` = top-left, `(1, 1)` = bottom-right |
82
+ | `colors` | `string[]` | 6-color rainbow | Array of CSS color strings for particle fill |
83
+ | `gravity` | `number` | `0.5` | Downward acceleration per frame. Higher = particles fall faster |
84
+ | `drift` | `number` | `0` | Horizontal drift per frame. Positive = right, negative = left |
85
+ | `ticks` | `number` | `200` | Lifetime of each particle in frames. Roughly `ticks / 60` seconds |
86
+
87
+ ## Examples
88
+
89
+ ### Fireworks effect
90
+
91
+ ```ts
92
+ const end = Date.now() + 3000;
93
+
94
+ function frame() {
95
+ confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0, y: 0.6 } });
96
+ confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1, y: 0.6 } });
97
+ if (Date.now() < end) requestAnimationFrame(frame);
98
+ }
99
+
100
+ frame();
101
+ ```
102
+
103
+ ### Custom color palette
104
+
105
+ ```ts
106
+ confetti({
107
+ particleCount: 200,
108
+ colors: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'],
109
+ spread: 180,
110
+ });
111
+ ```
112
+
113
+ ### React usage
114
+
115
+ ```tsx
116
+ import { confetti, celebrate } from '@input-kit/confetti';
117
+
118
+ function SubmitButton() {
119
+ const handleClick = () => {
120
+ celebrate();
121
+ };
122
+
123
+ return <button onClick={handleClick}>Submit</button>;
124
+ }
125
+ ```
126
+
127
+ ## TypeScript
128
+
129
+ All types are exported:
130
+
131
+ ```ts
132
+ import type { ConfettiOptions } from '@input-kit/confetti';
133
+ ```
134
+
135
+ ## Browser Support
136
+
137
+ Requires Canvas 2D API — supported in all modern browsers. Not supported in Node.js / SSR (calls are silently no-ops).
138
+
139
+ ## License
140
+
141
+ MIT © Harshit
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var M=["#ff0000","#00ff00","#0000ff","#ffff00","#ff00ff","#00ffff"];function o(r={}){if(typeof document>"u"||!document.body)return;let{particleCount:g=100,angle:p=90,spread:y=360,origin:l={x:.5,y:.5},colors:c=M,gravity:b=.5,drift:x=0,ticks:v=200}=r,e=document.createElement("canvas");e.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999",document.body.appendChild(e);let d=e.getContext("2d");if(!d){document.body.removeChild(e);return}let n=d;e.width=window.innerWidth,e.height=window.innerHeight;let s=[],C=p*(Math.PI/180),A=y/2*(Math.PI/180);for(let i=0;i<g;i++){let t=C+(Math.random()*2-1)*A,h=Math.random()*10+5;s.push({x:l.x*e.width,y:l.y*e.height,color:c[Math.floor(Math.random()*c.length)],velocity:{x:Math.cos(t)*h,y:-Math.sin(t)*h},gravity:b,drift:x,ticks:v,tilt:Math.random()*10,tiltAngle:0,tiltAngleIncrement:Math.random()*.1+.05});}let f,u=new AbortController;function a(){cancelAnimationFrame(f),u.abort(),e.parentNode&&e.parentNode.removeChild(e);}function m(){n.clearRect(0,0,e.width,e.height);let i=0;for(let t of s)t.ticks>0&&(t.ticks--,t.x+=t.velocity.x+t.drift,t.y+=t.velocity.y,t.velocity.y+=t.gravity,t.tiltAngle+=t.tiltAngleIncrement,t.tilt=Math.sin(t.tiltAngle)*15,n.beginPath(),n.lineWidth=8,n.strokeStyle=t.color,n.moveTo(t.x+t.tilt,t.y),n.lineTo(t.x,t.y+10+t.tilt),n.stroke(),i++);i>0?f=requestAnimationFrame(m):a();}return document.addEventListener("visibilitychange",()=>{document.hidden&&a();},{signal:u.signal}),m(),a}function w(r={}){return o(r)}function k(){setTimeout(()=>{o({origin:{x:.1,y:.8},particleCount:50,spread:60,angle:60});},0),setTimeout(()=>{o({origin:{x:.9,y:.8},particleCount:50,spread:60,angle:120});},100),setTimeout(()=>{o({origin:{x:.5,y:.5},particleCount:100,spread:360});},200);}exports.celebrate=k;exports.confetti=o;exports.fireConfetti=w;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["defaultColors","confetti","options","particleCount","angle","spread","origin","colors","gravity","drift","ticks","canvas","rawCtx","ctx","particles","launchRad","halfSpreadRad","particleAngle","speed","animationId","controller","cleanup","update","activeParticles","p","fireConfetti","celebrate"],"mappings":"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","file":"index.cjs","sourcesContent":["// @input-kit/confetti - Lightweight confetti effects\r\n\r\nexport interface ConfettiOptions {\r\n particleCount?: number;\r\n /** Launch angle in degrees. 0 = right, 90 = up, 180 = left, 270 = down. Default: 90 */\r\n angle?: number;\r\n spread?: number;\r\n origin?: { x: number; y: number };\r\n colors?: string[];\r\n gravity?: number;\r\n drift?: number;\r\n ticks?: number;\r\n}\r\n\r\ninterface Particle {\r\n x: number;\r\n y: number;\r\n color: string;\r\n velocity: { x: number; y: number };\r\n gravity: number;\r\n drift: number;\r\n ticks: number;\r\n tilt: number;\r\n tiltAngle: number;\r\n tiltAngleIncrement: number;\r\n}\r\n\r\nconst defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'];\r\n\r\n/**\r\n * Fire a confetti burst. Returns a cancellation function.\r\n * Safe to call in SSR environments (no-ops when `document` is unavailable).\r\n */\r\nexport function confetti(options: ConfettiOptions = {}): (() => void) | undefined {\r\n if (typeof document === 'undefined' || !document.body) return undefined;\r\n\r\n const {\r\n particleCount = 100,\r\n angle = 90,\r\n spread = 360,\r\n origin = { x: 0.5, y: 0.5 },\r\n colors = defaultColors,\r\n gravity = 0.5,\r\n drift = 0,\r\n ticks = 200,\r\n } = options;\r\n\r\n const canvas = document.createElement('canvas');\r\n canvas.style.cssText =\r\n 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';\r\n document.body.appendChild(canvas);\r\n\r\n const rawCtx = canvas.getContext('2d');\r\n if (!rawCtx) {\r\n document.body.removeChild(canvas);\r\n return undefined;\r\n }\r\n // Rebind with explicit non-null type so TypeScript keeps the narrowing inside closures.\r\n const ctx: CanvasRenderingContext2D = rawCtx;\r\n\r\n canvas.width = window.innerWidth;\r\n canvas.height = window.innerHeight;\r\n\r\n const particles: Particle[] = [];\r\n const launchRad = angle * (Math.PI / 180);\r\n const halfSpreadRad = (spread / 2) * (Math.PI / 180);\r\n\r\n for (let i = 0; i < particleCount; i++) {\r\n const particleAngle = launchRad + (Math.random() * 2 - 1) * halfSpreadRad;\r\n const speed = Math.random() * 10 + 5;\r\n\r\n particles.push({\r\n x: origin.x * canvas.width,\r\n y: origin.y * canvas.height,\r\n color: colors[Math.floor(Math.random() * colors.length)],\r\n velocity: {\r\n x: Math.cos(particleAngle) * speed,\r\n y: -Math.sin(particleAngle) * speed,\r\n },\r\n gravity,\r\n drift,\r\n ticks,\r\n tilt: Math.random() * 10,\r\n tiltAngle: 0,\r\n tiltAngleIncrement: Math.random() * 0.1 + 0.05,\r\n });\r\n }\r\n\r\n let animationId: number;\r\n const controller = new AbortController();\r\n\r\n function cleanup() {\r\n cancelAnimationFrame(animationId);\r\n controller.abort();\r\n if (canvas.parentNode) {\r\n canvas.parentNode.removeChild(canvas);\r\n }\r\n }\r\n\r\n function update() {\r\n ctx.clearRect(0, 0, canvas.width, canvas.height);\r\n\r\n let activeParticles = 0;\r\n\r\n for (const p of particles) {\r\n if (p.ticks > 0) {\r\n p.ticks--;\r\n p.x += p.velocity.x + p.drift;\r\n p.y += p.velocity.y;\r\n p.velocity.y += p.gravity;\r\n p.tiltAngle += p.tiltAngleIncrement;\r\n p.tilt = Math.sin(p.tiltAngle) * 15;\r\n\r\n ctx.beginPath();\r\n ctx.lineWidth = 8;\r\n ctx.strokeStyle = p.color;\r\n ctx.moveTo(p.x + p.tilt, p.y);\r\n ctx.lineTo(p.x, p.y + 10 + p.tilt);\r\n ctx.stroke();\r\n\r\n activeParticles++;\r\n }\r\n }\r\n\r\n if (activeParticles > 0) {\r\n animationId = requestAnimationFrame(update);\r\n } else {\r\n cleanup();\r\n }\r\n }\r\n\r\n // Auto-cancel when the tab is hidden; listener is removed via AbortController\r\n document.addEventListener(\r\n 'visibilitychange',\r\n () => {\r\n if (document.hidden) cleanup();\r\n },\r\n { signal: controller.signal },\r\n );\r\n\r\n update();\r\n\r\n return cleanup;\r\n}\r\n\r\n/** Alias for `confetti`. Returns a cancellation function. */\r\nexport function fireConfetti(options: ConfettiOptions = {}): (() => void) | undefined {\r\n return confetti(options);\r\n}\r\n\r\n/** Multi-burst celebration: left cannon + right cannon + center burst. */\r\nexport function celebrate(): void {\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.1, y: 0.8 }, particleCount: 50, spread: 60, angle: 60 });\r\n }, 0);\r\n\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.9, y: 0.8 }, particleCount: 50, spread: 60, angle: 120 });\r\n }, 100);\r\n\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.5, y: 0.5 }, particleCount: 100, spread: 360 });\r\n }, 200);\r\n}\r\n"]}
@@ -0,0 +1,25 @@
1
+ interface ConfettiOptions {
2
+ particleCount?: number;
3
+ /** Launch angle in degrees. 0 = right, 90 = up, 180 = left, 270 = down. Default: 90 */
4
+ angle?: number;
5
+ spread?: number;
6
+ origin?: {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ colors?: string[];
11
+ gravity?: number;
12
+ drift?: number;
13
+ ticks?: number;
14
+ }
15
+ /**
16
+ * Fire a confetti burst. Returns a cancellation function.
17
+ * Safe to call in SSR environments (no-ops when `document` is unavailable).
18
+ */
19
+ declare function confetti(options?: ConfettiOptions): (() => void) | undefined;
20
+ /** Alias for `confetti`. Returns a cancellation function. */
21
+ declare function fireConfetti(options?: ConfettiOptions): (() => void) | undefined;
22
+ /** Multi-burst celebration: left cannon + right cannon + center burst. */
23
+ declare function celebrate(): void;
24
+
25
+ export { type ConfettiOptions, celebrate, confetti, fireConfetti };
@@ -0,0 +1,25 @@
1
+ interface ConfettiOptions {
2
+ particleCount?: number;
3
+ /** Launch angle in degrees. 0 = right, 90 = up, 180 = left, 270 = down. Default: 90 */
4
+ angle?: number;
5
+ spread?: number;
6
+ origin?: {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ colors?: string[];
11
+ gravity?: number;
12
+ drift?: number;
13
+ ticks?: number;
14
+ }
15
+ /**
16
+ * Fire a confetti burst. Returns a cancellation function.
17
+ * Safe to call in SSR environments (no-ops when `document` is unavailable).
18
+ */
19
+ declare function confetti(options?: ConfettiOptions): (() => void) | undefined;
20
+ /** Alias for `confetti`. Returns a cancellation function. */
21
+ declare function fireConfetti(options?: ConfettiOptions): (() => void) | undefined;
22
+ /** Multi-burst celebration: left cannon + right cannon + center burst. */
23
+ declare function celebrate(): void;
24
+
25
+ export { type ConfettiOptions, celebrate, confetti, fireConfetti };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var M=["#ff0000","#00ff00","#0000ff","#ffff00","#ff00ff","#00ffff"];function o(r={}){if(typeof document>"u"||!document.body)return;let{particleCount:g=100,angle:p=90,spread:y=360,origin:l={x:.5,y:.5},colors:c=M,gravity:b=.5,drift:x=0,ticks:v=200}=r,e=document.createElement("canvas");e.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999",document.body.appendChild(e);let d=e.getContext("2d");if(!d){document.body.removeChild(e);return}let n=d;e.width=window.innerWidth,e.height=window.innerHeight;let s=[],C=p*(Math.PI/180),A=y/2*(Math.PI/180);for(let i=0;i<g;i++){let t=C+(Math.random()*2-1)*A,h=Math.random()*10+5;s.push({x:l.x*e.width,y:l.y*e.height,color:c[Math.floor(Math.random()*c.length)],velocity:{x:Math.cos(t)*h,y:-Math.sin(t)*h},gravity:b,drift:x,ticks:v,tilt:Math.random()*10,tiltAngle:0,tiltAngleIncrement:Math.random()*.1+.05});}let f,u=new AbortController;function a(){cancelAnimationFrame(f),u.abort(),e.parentNode&&e.parentNode.removeChild(e);}function m(){n.clearRect(0,0,e.width,e.height);let i=0;for(let t of s)t.ticks>0&&(t.ticks--,t.x+=t.velocity.x+t.drift,t.y+=t.velocity.y,t.velocity.y+=t.gravity,t.tiltAngle+=t.tiltAngleIncrement,t.tilt=Math.sin(t.tiltAngle)*15,n.beginPath(),n.lineWidth=8,n.strokeStyle=t.color,n.moveTo(t.x+t.tilt,t.y),n.lineTo(t.x,t.y+10+t.tilt),n.stroke(),i++);i>0?f=requestAnimationFrame(m):a();}return document.addEventListener("visibilitychange",()=>{document.hidden&&a();},{signal:u.signal}),m(),a}function w(r={}){return o(r)}function k(){setTimeout(()=>{o({origin:{x:.1,y:.8},particleCount:50,spread:60,angle:60});},0),setTimeout(()=>{o({origin:{x:.9,y:.8},particleCount:50,spread:60,angle:120});},100),setTimeout(()=>{o({origin:{x:.5,y:.5},particleCount:100,spread:360});},200);}export{k as celebrate,o as confetti,w as fireConfetti};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["defaultColors","confetti","options","particleCount","angle","spread","origin","colors","gravity","drift","ticks","canvas","rawCtx","ctx","particles","launchRad","halfSpreadRad","particleAngle","speed","animationId","controller","cleanup","update","activeParticles","p","fireConfetti","celebrate"],"mappings":"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","file":"index.js","sourcesContent":["// @input-kit/confetti - Lightweight confetti effects\r\n\r\nexport interface ConfettiOptions {\r\n particleCount?: number;\r\n /** Launch angle in degrees. 0 = right, 90 = up, 180 = left, 270 = down. Default: 90 */\r\n angle?: number;\r\n spread?: number;\r\n origin?: { x: number; y: number };\r\n colors?: string[];\r\n gravity?: number;\r\n drift?: number;\r\n ticks?: number;\r\n}\r\n\r\ninterface Particle {\r\n x: number;\r\n y: number;\r\n color: string;\r\n velocity: { x: number; y: number };\r\n gravity: number;\r\n drift: number;\r\n ticks: number;\r\n tilt: number;\r\n tiltAngle: number;\r\n tiltAngleIncrement: number;\r\n}\r\n\r\nconst defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'];\r\n\r\n/**\r\n * Fire a confetti burst. Returns a cancellation function.\r\n * Safe to call in SSR environments (no-ops when `document` is unavailable).\r\n */\r\nexport function confetti(options: ConfettiOptions = {}): (() => void) | undefined {\r\n if (typeof document === 'undefined' || !document.body) return undefined;\r\n\r\n const {\r\n particleCount = 100,\r\n angle = 90,\r\n spread = 360,\r\n origin = { x: 0.5, y: 0.5 },\r\n colors = defaultColors,\r\n gravity = 0.5,\r\n drift = 0,\r\n ticks = 200,\r\n } = options;\r\n\r\n const canvas = document.createElement('canvas');\r\n canvas.style.cssText =\r\n 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999';\r\n document.body.appendChild(canvas);\r\n\r\n const rawCtx = canvas.getContext('2d');\r\n if (!rawCtx) {\r\n document.body.removeChild(canvas);\r\n return undefined;\r\n }\r\n // Rebind with explicit non-null type so TypeScript keeps the narrowing inside closures.\r\n const ctx: CanvasRenderingContext2D = rawCtx;\r\n\r\n canvas.width = window.innerWidth;\r\n canvas.height = window.innerHeight;\r\n\r\n const particles: Particle[] = [];\r\n const launchRad = angle * (Math.PI / 180);\r\n const halfSpreadRad = (spread / 2) * (Math.PI / 180);\r\n\r\n for (let i = 0; i < particleCount; i++) {\r\n const particleAngle = launchRad + (Math.random() * 2 - 1) * halfSpreadRad;\r\n const speed = Math.random() * 10 + 5;\r\n\r\n particles.push({\r\n x: origin.x * canvas.width,\r\n y: origin.y * canvas.height,\r\n color: colors[Math.floor(Math.random() * colors.length)],\r\n velocity: {\r\n x: Math.cos(particleAngle) * speed,\r\n y: -Math.sin(particleAngle) * speed,\r\n },\r\n gravity,\r\n drift,\r\n ticks,\r\n tilt: Math.random() * 10,\r\n tiltAngle: 0,\r\n tiltAngleIncrement: Math.random() * 0.1 + 0.05,\r\n });\r\n }\r\n\r\n let animationId: number;\r\n const controller = new AbortController();\r\n\r\n function cleanup() {\r\n cancelAnimationFrame(animationId);\r\n controller.abort();\r\n if (canvas.parentNode) {\r\n canvas.parentNode.removeChild(canvas);\r\n }\r\n }\r\n\r\n function update() {\r\n ctx.clearRect(0, 0, canvas.width, canvas.height);\r\n\r\n let activeParticles = 0;\r\n\r\n for (const p of particles) {\r\n if (p.ticks > 0) {\r\n p.ticks--;\r\n p.x += p.velocity.x + p.drift;\r\n p.y += p.velocity.y;\r\n p.velocity.y += p.gravity;\r\n p.tiltAngle += p.tiltAngleIncrement;\r\n p.tilt = Math.sin(p.tiltAngle) * 15;\r\n\r\n ctx.beginPath();\r\n ctx.lineWidth = 8;\r\n ctx.strokeStyle = p.color;\r\n ctx.moveTo(p.x + p.tilt, p.y);\r\n ctx.lineTo(p.x, p.y + 10 + p.tilt);\r\n ctx.stroke();\r\n\r\n activeParticles++;\r\n }\r\n }\r\n\r\n if (activeParticles > 0) {\r\n animationId = requestAnimationFrame(update);\r\n } else {\r\n cleanup();\r\n }\r\n }\r\n\r\n // Auto-cancel when the tab is hidden; listener is removed via AbortController\r\n document.addEventListener(\r\n 'visibilitychange',\r\n () => {\r\n if (document.hidden) cleanup();\r\n },\r\n { signal: controller.signal },\r\n );\r\n\r\n update();\r\n\r\n return cleanup;\r\n}\r\n\r\n/** Alias for `confetti`. Returns a cancellation function. */\r\nexport function fireConfetti(options: ConfettiOptions = {}): (() => void) | undefined {\r\n return confetti(options);\r\n}\r\n\r\n/** Multi-burst celebration: left cannon + right cannon + center burst. */\r\nexport function celebrate(): void {\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.1, y: 0.8 }, particleCount: 50, spread: 60, angle: 60 });\r\n }, 0);\r\n\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.9, y: 0.8 }, particleCount: 50, spread: 60, angle: 120 });\r\n }, 100);\r\n\r\n setTimeout(() => {\r\n confetti({ origin: { x: 0.5, y: 0.5 }, particleCount: 100, spread: 360 });\r\n }, 200);\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@input-kit/confetti",
3
+ "version": "0.1.0",
4
+ "description": "Confetti effects",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "input-kit",
33
+ "confetti",
34
+ "typescript",
35
+ "headless"
36
+ ],
37
+ "author": "Harshit",
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^18.0.0 || ^19.0.0",
44
+ "react-dom": "^18.0.0 || ^19.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/react": "^18.3.18",
48
+ "@types/react-dom": "^18.3.5",
49
+ "@testing-library/react": "^14.2.0",
50
+ "jsdom": "^26.0.0",
51
+ "react": "^18.3.1",
52
+ "react-dom": "^18.3.1",
53
+ "tsup": "^8.3.5",
54
+ "typescript": "^5.7.3",
55
+ "vitest": "^3.0.4"
56
+ }
57
+ }