59e995d0b5
Composables, tests, category barrels, and README for @robonen/vue.
148 lines
3.6 KiB
TypeScript
148 lines
3.6 KiB
TypeScript
import { shallowReadonly, shallowRef, toValue } from 'vue';
|
|
import type { MaybeRefOrGetter, ShallowRef } from 'vue';
|
|
import type { ResumableActions } from '@/types';
|
|
import { useIntervalFn } from '@/composables/animation/useIntervalFn';
|
|
import type { UseIntervalFnReturn } from '@/composables/animation/useIntervalFn';
|
|
|
|
export interface UseCountdownOptions {
|
|
/**
|
|
* Tick interval in milliseconds. Each tick decrements `remaining` by one.
|
|
*
|
|
* @default 1000
|
|
*/
|
|
interval?: MaybeRefOrGetter<number>;
|
|
|
|
/**
|
|
* Start the countdown immediately when the composable is created
|
|
*
|
|
* @default false
|
|
*/
|
|
immediate?: boolean;
|
|
|
|
/**
|
|
* Callback invoked on every tick with the current remaining value
|
|
*/
|
|
onTick?: (remaining: number) => void;
|
|
|
|
/**
|
|
* Callback invoked once when the countdown reaches zero
|
|
*/
|
|
onComplete?: () => void;
|
|
}
|
|
|
|
export interface UseCountdownReturn extends ResumableActions {
|
|
/**
|
|
* The remaining seconds, read-only (use `reset`/`start` to change it)
|
|
*/
|
|
remaining: Readonly<ShallowRef<number>>;
|
|
|
|
/**
|
|
* Whether the countdown is currently running
|
|
*/
|
|
isActive: UseIntervalFnReturn['isActive'];
|
|
|
|
/**
|
|
* Reset `remaining` (defaults to the initial value) without changing the
|
|
* running state
|
|
*/
|
|
reset: (countdown?: MaybeRefOrGetter<number>) => void;
|
|
|
|
/**
|
|
* Pause the countdown and reset `remaining` to the initial value
|
|
*/
|
|
stop: () => void;
|
|
|
|
/**
|
|
* Reset `remaining` (defaults to the initial value) and start counting down
|
|
*/
|
|
start: (countdown?: MaybeRefOrGetter<number>) => void;
|
|
}
|
|
|
|
/**
|
|
* @name useCountdown
|
|
* @category Animation
|
|
* @description Reactive countdown timer exposing the remaining seconds plus
|
|
* `start`/`stop`/`pause`/`resume`/`reset` controls and `onTick`/`onComplete`
|
|
* callbacks. Built on `useIntervalFn`, so it is SSR-safe and cleans up on scope
|
|
* dispose.
|
|
*
|
|
* @param {MaybeRefOrGetter<number>} initialCountdown The starting value, in seconds (can be reactive)
|
|
* @param {UseCountdownOptions} [options={}] Options
|
|
* @returns {UseCountdownReturn} The reactive remaining value and countdown controls
|
|
*
|
|
* @example
|
|
* const { remaining, start, pause, resume, stop } = useCountdown(60);
|
|
* start();
|
|
*
|
|
* @example
|
|
* useCountdown(10, {
|
|
* immediate: true,
|
|
* onTick: (n) => console.log(n),
|
|
* onComplete: () => console.log('done'),
|
|
* });
|
|
*
|
|
* @since 0.0.15
|
|
*/
|
|
export function useCountdown(
|
|
initialCountdown: MaybeRefOrGetter<number>,
|
|
options: UseCountdownOptions = {},
|
|
): UseCountdownReturn {
|
|
const {
|
|
interval = 1000,
|
|
immediate = false,
|
|
onTick,
|
|
onComplete,
|
|
} = options;
|
|
|
|
const remaining = shallowRef(toValue(initialCountdown));
|
|
|
|
const controls = useIntervalFn(() => {
|
|
const next = remaining.value - 1;
|
|
remaining.value = next < 0 ? 0 : next;
|
|
|
|
onTick?.(remaining.value);
|
|
|
|
if (remaining.value <= 0) {
|
|
controls.pause();
|
|
onComplete?.();
|
|
}
|
|
}, interval, { immediate });
|
|
|
|
const reset = (countdown?: MaybeRefOrGetter<number>): void => {
|
|
remaining.value = toValue(countdown) ?? toValue(initialCountdown);
|
|
};
|
|
|
|
const stop = (): void => {
|
|
controls.pause();
|
|
reset();
|
|
};
|
|
|
|
const resume = (): void => {
|
|
if (!controls.isActive.value && remaining.value > 0)
|
|
controls.resume();
|
|
};
|
|
|
|
const start = (countdown?: MaybeRefOrGetter<number>): void => {
|
|
reset(countdown);
|
|
controls.resume();
|
|
};
|
|
|
|
const toggle = (): void => {
|
|
if (controls.isActive.value)
|
|
controls.pause();
|
|
else
|
|
resume();
|
|
};
|
|
|
|
return {
|
|
remaining: shallowReadonly(remaining),
|
|
isActive: controls.isActive,
|
|
reset,
|
|
stop,
|
|
start,
|
|
pause: controls.pause,
|
|
resume,
|
|
toggle,
|
|
};
|
|
}
|