UNPKG

svelte-interval-rune

Version:

Svelte 5 utility for creating intervals

254 lines (251 loc) 6.69 kB
import { createSubscriber } from 'svelte/reactivity'; // src/index.svelte.ts var Interval = class { #subscribe; #update; #interval_id = 0; #duration_input = $state(0); #is_active = $state(true); #is_stopped = $state(false); #tick_count = $state(0); #version = $state(0); /** @private @deprecated DO NOT USE OR YOU WILL BE FIRED */ _; #duration = $derived( typeof this.#duration_input === "function" ? this.#duration_input() : this.#duration_input ); #interval = $derived.by(() => { this.#version; clearInterval(this.#interval_id); this.#interval_id = setInterval(this.#run_func.bind(this), this.#duration); return this.#interval_id; }); #run_func = () => { if (!this.#is_active) return; this.#tick_count++; this.#update?.(); }; #kickoff_subscriptions() { this.#interval; this.#subscribe(); } /** * Creates a new Interval instance. * * @param duration - The interval duration in milliseconds. Can be a number or a reactive function. */ constructor(duration, options = {}) { const { immediate = false } = options; this.#duration_input = duration; const self = this; this._ = { get run_func() { return self.#run_func; }, set run_func(fn) { self.#run_func = fn; }, get is_active() { return self.#is_active; }, set is_active(active) { self.#is_active = active; }, get tick_count() { return self.#tick_count; }, increment_tick() { self.#tick_count = self.#tick_count + 1; }, get update() { return self.#update; }, trigger_update() { if (self.#update) { self.#update(); } }, force_restart() { self.#version = self.#version + 1; } }; this.#subscribe = createSubscriber((update) => { this.#update = update; return () => clearInterval(this.#interval_id); }); if (immediate) this.#kickoff_subscriptions(); } /** * Resumes the interval if it was paused. * * @param immediate - If true, immediately triggers a tick and resets the interval timing. */ resume(immediate = false) { if (this.#is_stopped) return; this.#is_active = true; if (immediate) { this.#version++; this.#run_func(); } } /** * Pauses the interval. The interval continues running in the background but stops executing callbacks and incrementing tick count. */ pause() { this.#is_active = false; } /** * Gets the current paused state of the interval. */ get isActive() { return this.#is_active; } /** * Gets the number of times the interval has fired (tick count). * This count persists across pause/resume cycles and duration changes. */ get tickCount() { this.#kickoff_subscriptions(); return this.#tick_count; } /** * Gets the current date and time, and starts the interval if not already started. * This is the primary way to activate the interval's reactivity. */ get current() { this.#kickoff_subscriptions(); return /* @__PURE__ */ new Date(); } /** * Gets the current duration of the interval in milliseconds. */ get duration() { return this.#duration; } set duration(value) { this.#duration_input = value; this.#version++; } /** * Completely stops and clears the interval. Cannot be resumed. * Use pause()/resume() if you want to temporarily stop. */ stop() { clearInterval(this.#interval_id); this.#is_active = false; this.#is_stopped = true; this.#interval_id = 0; } /** * Check if interval has been completely stopped */ get isStopped() { return this.#is_stopped; } [Symbol.dispose]() { clearInterval(this.#interval_id); } }; function sync(...intervals) { if (intervals.length === 0) { throw new Error("At least one interval is required for sync"); } let leader = intervals[0]; for (const interval of intervals) { if (interval.duration < leader.duration) { leader = interval; } } const original_run_funcs = /* @__PURE__ */ new Map(); for (const interval of intervals) { original_run_funcs.set(interval, interval._.run_func); } let sync_active = $state(false); return { enable() { if (sync_active) return; sync_active = true; for (const interval of intervals) { if (interval === leader) { interval._.run_func = () => { if (!leader._.is_active) return; for (const synced_interval of intervals) { const original_func = original_run_funcs.get(synced_interval); original_func?.call(synced_interval); } }; } else { interval._.run_func = () => { }; } } leader.current; leader._.force_restart(); }, disable() { if (!sync_active) return; sync_active = false; for (const interval of intervals) { const original_func = original_run_funcs.get(interval); if (original_func) { interval._.run_func = original_func; interval._.force_restart(); } } }, get isSynced() { return sync_active; }, get leader() { return leader; } }; } var LimitedInterval = class extends Interval { #max_ticks; #is_completed = false; #completion_baseline = 0; constructor(duration, maxTicks, options = {}) { super(duration, options); if (maxTicks <= 0) { throw new Error("max_ticks must be greater than 0"); } this.#max_ticks = maxTicks; this._.run_func = () => { if (this.#is_completed) return; if (!this._.is_active) return; this._.increment_tick(); this._.trigger_update(); const ticks_since_baseline = this._.tick_count - this.#completion_baseline; if (ticks_since_baseline >= this.#max_ticks) { this.#is_completed = true; this.pause(); } }; } get isCompleted() { return this.#is_completed; } get remainingTicks() { const ticks_since_baseline = this.tickCount - this.#completion_baseline; return Math.max(0, this.#max_ticks - ticks_since_baseline); } get maxTicks() { return this.#max_ticks; } reset() { this.#is_completed = false; this.#completion_baseline = this.tickCount; if (!this.isActive) { this.resume(); } } set maxTicks(new_max) { if (new_max <= 0) { throw new Error("max_ticks must be greater than 0"); } this.#max_ticks = new_max; this.#is_completed = false; } }; export { Interval, LimitedInterval, sync };