component Confetti {
outputs {
start(payload: { sourceEvent?: Event; duration: number })
complete(payload: { sourceEvent?: Event; duration: number })
}
props {
label: string = "Celebrate"
duration: number = 1200
count: number = 24
autoStart: boolean = false
disabled: boolean = false
size: string = "default"
color: string = "primary"
class: string = ""
}
state running = false
functions {
shared function pieces() { return Array.from({ length: Math.max(1, Math.min(60, count)) }) }
shared function pieceStyle(index) {
return "--wrn-confetti-index:" + index + ";--wrn-confetti-x:" + ((index * 37) % 100) + ";--wrn-confetti-delay:" + ((index * 29) % 240) + "ms;"
}
client function finish(sourceEvent) {
running = false
output.complete({ sourceEvent: sourceEvent, duration: duration })
}
client function start(sourceEvent) {
if (disabled || running) {
return
}
running = true
output.start({ sourceEvent: sourceEvent, duration: duration })
setTimeout(finish, duration, sourceEvent)
}
}
lifecycle {
mount { if (autoStart) { start(null) } }
}
view {
{#each pieces() as piece, index}{/each}
}
style {
.wrn-confetti { position: relative; display: inline-flex; }
.wrn-confetti__trigger { display: inline-flex; min-height: 2.5rem; align-items: center; gap: 0.5rem; padding: 0.55rem 0.85rem; border: 1px solid var(--wrn-color-primary); border-radius: var(--wrn-radius); color: var(--wrn-color-on-primary); background: var(--wrn-color-primary); font: inherit; font-weight: 700; cursor: pointer; }
.wrn-confetti__trigger > span:first-child { width: 1rem; height: 1rem; }
.wrn-confetti__trigger:focus-visible { outline: 2px solid var(--wrn-color-focus); outline-offset: 2px; }
.wrn-confetti__trigger:disabled { cursor: not-allowed; opacity: 0.55; }
.wrn-confetti__burst { position: absolute; inset: 50% 0 auto; height: 0; pointer-events: none; }
.wrn-confetti__burst > i { position: absolute; left: calc(var(--wrn-confetti-x) * 1%); width: 0.45rem; height: 0.7rem; border-radius: 0.1rem; opacity: 0; background: hsl(calc(var(--wrn-confetti-index) * 47deg) 80% 55%); transform: translate(-50%, 0) rotate(calc(var(--wrn-confetti-index) * 23deg)); }
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation: wrn-confetti-burst var(--wrn-confetti-duration) cubic-bezier(0.2, 0.7, 0.2, 1) var(--wrn-confetti-delay) both; }
@keyframes wrn-confetti-burst {
0% { opacity: 1; transform: translate(-50%, 0) rotate(0); }
100% { opacity: 0; transform: translate(calc(-50% + (var(--wrn-confetti-x) - 50) * 0.9px), 9rem) rotate(540deg); }
}
@media (prefers-reduced-motion: reduce) {
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation-duration: 1ms; animation-delay: 0ms; }
}
}
}