Files
core-cooldown/src/lib/components/Stepper.svelte

95 lines
2.5 KiB
Svelte

<script lang="ts">
interface Props {
value: number;
min?: number;
max?: number;
step?: number;
formatValue?: (v: number) => string;
onchange?: (value: number) => void;
label?: string;
}
let {
value = $bindable(),
min = 0,
max = 100,
step = 1,
formatValue = (v: number) => String(v),
onchange,
label = "Value",
}: Props = $props();
let holdTimer: ReturnType<typeof setTimeout> | null = null;
let holdInterval: ReturnType<typeof setInterval> | null = null;
function decrement() {
if (value > min) {
value = Math.max(min, value - step);
onchange?.(value);
}
}
function increment() {
if (value < max) {
value = Math.min(max, value + step);
onchange?.(value);
}
}
function startHold(fn: () => void) {
fn();
// Initial delay before repeating
holdTimer = setTimeout(() => {
// Start repeating, accelerate over time
let delay = 150;
function tick() {
fn();
delay = Math.max(40, delay * 0.85);
holdInterval = setTimeout(tick, delay) as unknown as ReturnType<typeof setInterval>;
}
tick();
}, 400);
}
function stopHold() {
if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
if (holdInterval) { clearTimeout(holdInterval as unknown as ReturnType<typeof setTimeout>); holdInterval = null; }
}
</script>
<div class="flex items-center gap-1.5" role="group" aria-label={label}>
<button
type="button"
aria-label="Decrease"
class="flex h-7 w-7 items-center justify-center rounded-lg
bg-[#141414] text-[#999] transition-colors
hover:bg-[#1c1c1c] hover:text-white
disabled:opacity-20"
onmousedown={() => startHold(decrement)}
onmouseup={stopHold}
onmouseleave={stopHold}
onclick={(e) => { if (e.detail === 0) decrement(); }}
disabled={value <= min}
>
&minus;
</button>
<span class="min-w-[38px] text-center text-[13px] tabular-nums text-white" aria-live="polite" aria-atomic="true">
{formatValue(value)}
</span>
<button
type="button"
aria-label="Increase"
class="flex h-7 w-7 items-center justify-center rounded-lg
bg-[#141414] text-[#999] transition-colors
hover:bg-[#1c1c1c] hover:text-white
disabled:opacity-20"
onmousedown={() => startHold(increment)}
onmouseup={stopHold}
onmouseleave={stopHold}
onclick={(e) => { if (e.detail === 0) increment(); }}
disabled={value >= max}
>
+
</button>
</div>