import { Controller } from '@hotwired/stimulus'; import debounce from 'debounce'; export type Detail = Record; export class ApplicationController extends Controller { #debounced = new Map<() => void, () => void>(); protected debounce(fn: () => void, interval: number): void { this.globalDispatch('debounced:added'); let debounced = this.#debounced.get(fn); if (!debounced) { const wrapper = () => { fn.bind(this)(); this.#debounced.delete(fn); if (this.#debounced.size == 0) { this.globalDispatch('debounced:empty'); } }; debounced = debounce(wrapper.bind(this), interval); this.#debounced.set(fn, debounced); } debounced(); } protected globalDispatch(type: string, detail?: T): void { this.dispatch(type, { detail: detail as object, prefix: '', target: document.documentElement }); } protected on( eventName: string, handler: (event: HandlerEvent) => void ): void { this.onTarget(this.element, eventName, handler); } protected onGlobal( eventName: string, handler: (event: HandlerEvent) => void ): void { this.onTarget(document.documentElement, eventName, handler); } private onTarget( target: EventTarget, eventName: string, handler: (event: HandlerEvent) => void ): void { const disconnect = this.disconnect; const callback = (event: Event): void => { handler(event as HandlerEvent); }; target.addEventListener(eventName, callback); this.disconnect = () => { target.removeEventListener(eventName, callback); disconnect.call(this); }; } }