Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113<script lang="ts"> import { untrack } from "svelte";
interface Props { value: number; format?: (value: number) => string; }
let { value, format = String }: Props = $props();
// a rise brings the new digits up from below, a drop brings them down from above let direction = $state(1); let flipped = $state(false); let leaving = $state<string | null>(null); let previous = untrack(() => value); $effect.pre(() => { if (value !== previous) { direction = value > previous ? 1 : -1; leaving = format(previous); previous = value; flipped = true; } });
const text = $derived(format(value));
const settle = (event: AnimationEvent & { currentTarget: HTMLElement }) => { if (event.target === event.currentTarget.lastElementChild) leaving = null; };</script>
<span class="number" style="--flip: {direction}"> <span class="sr-only">{text}</span> {#key text} <span class="digits" class:enter={untrack(() => flipped)} aria-hidden="true"> {#each [...text] as char, index (index)} <span class="digit" style="--stagger: {Math.min(index, 2)}">{char}</span> {/each} </span> {/key} {#if leaving !== null} {#key leaving} <span class="digits leave" aria-hidden="true" onanimationend={settle}> {#each [...leaving] as char, index (index)} <span class="digit" style="--stagger: {Math.min(index, 2)}">{char}</span> {/each} </span> {/key} {/if}</span>
<style> .number { position: relative; display: inline-flex; font-variant-numeric: tabular-nums; }
.digits { display: inline-flex; align-items: baseline; }
.digit { display: inline-block; }
.leave { position: absolute; inset: 0; pointer-events: none; }
.enter .digit, .leave .digit { animation: 500ms cubic-bezier(0.34, 1.45, 0.64, 1) calc(var(--stagger) * 70ms) both; }
.enter .digit { animation-name: pop-in; }
.leave .digit { animation-name: pop-out; }
@keyframes pop-in { from { opacity: 0; translate: 0 calc(var(--flip) * 8px); filter: blur(2px); } }
@keyframes pop-out { to { opacity: 0; translate: 0 calc(var(--flip) * -8px); filter: blur(2px); } }
@media (prefers-reduced-motion: reduce) { .enter .digit { animation: none; }
.leave { display: none; } }</style>