toggle
toggle(subscribe: () => Unsubscribe): ToggleTurns anything that returns its own unsubscribe function into a switch.
interface Toggle {
readonly isActive: boolean;
start: () => void;
stop: () => void;
}start and stop are bound, so each is a callback as it is.
Usage
import { Base, toggle, useRaf } from '@studiometa/js-toolkit';
class SliderItem extends Base {
static config = { name: 'SliderItem' };
#frame = toggle(() => useRaf().subscribe(({ delta }) => this.follow(delta)));
mounted() {
// `stop` is bound, so it is a cleanup as it is.
return this.#frame.stop;
}
follow(delta) {}
onIndexChange() {
this.#frame.start();
}
onSettled() {
this.#frame.stop();
}
}start() is idempotent and stop() is safe to repeat.
What it works on
Anything whose subscribe call returns a release:
import { signal, toggle, useScroll } from '@studiometa/js-toolkit';
const count = signal(0);
const el = document.body;
// a service
toggle(() => useScroll().subscribe(() => {}));
// a signal
toggle(() => count.subscribe(() => {}));
// a bare listener
toggle(() => {
const handler = () => {};
el.addEventListener('click', handler);
return () => el.removeEventListener('click', handler);
});It knows nothing about Base, so it works outside a component too.
Why it exists
The scheduler has no permanent rAF loop: it requests the next frame while a tick subscriber stays. So a component that needs the loop for part of a cycle should not hold a subscription for the whole cycle — and hand-rolling "unsubscribe if subscribed, and remember which" at every call site is the bug this removes.
{ manual: true } on a service mixin gives you the same object under $services.<hook>. See Mixins.
smoothTo() is a toggle() over useRaf(): one subscription however many times the target is set, started when the value has somewhere to go, released when it arrives.