Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123import type { Events, EventRef, EventRefImpl } from '../events'import { Debouncer } from './debouncer'
/** * A subscription wrapper that debounces event delivery. * * When an event is triggered, the callback is not called immediately. * Instead, the latest arguments are stored and the callback is called * after the debounce delay. Rapid triggers coalesce into a single call. * * Implements EventRef interface for compatibility with Component.registerEvent(). * When Component.unload() calls offref(), pending debounced calls are cancelled. */export class DebouncedEventRef implements EventRefImpl { /** Proxy Events instance that intercepts offref (for EventRef compatibility) */ readonly e: Events /** Event name (for EventRef compatibility) */ readonly n: string /** The internal callback registered with Events (for EventRef compatibility) */ readonly fn: (...data: unknown[]) => unknown
private readonly debouncer: Debouncer<unknown[]> private readonly actualEvents: Events private disposed = false
constructor( events: Events, eventName: string, callback: (...data: unknown[]) => unknown, debounceMs: number ) { this.actualEvents = events this.n = eventName
// Create debouncer that forwards to the user's callback this.debouncer = new Debouncer( (...args: unknown[]) => { if (!this.disposed) { callback(...args) } }, debounceMs )
// Create the inner callback that feeds the debouncer this.fn = (...args: unknown[]) => { if (!this.disposed) { this.debouncer.call(...args) } return undefined }
// Subscribe to the actual events events.on(eventName, this.fn)
// Create a proxy that intercepts offref to also dispose the debouncer. // When Component.unload() calls ref.e.offref(ref), our proxy intercepts // and calls dispose() to cancel pending debounced callbacks. // // Using JavaScript Proxy ensures all other Events methods are forwarded // automatically. This handles additions to the Events API, though signature // changes to offref would require updating this proxy. this.e = new Proxy(events, { get: (target: Events, prop: string | symbol): unknown => { if (prop === 'offref') { return (ref: EventRef): void => { if (ref === this) { this.dispose() } else { target.offref(ref) } } } // Forward all other property accesses to the real Events instance const value = target[prop as keyof typeof target] if (typeof value === 'function') { return value.bind(target) } return value }, }) as Events }
/** * Flush any pending debounced call immediately. */ flush(): void { if (!this.disposed) { this.debouncer.flush() } }
/** * Cancel any pending debounced call. * No-op if already disposed. */ cancel(): void { if (!this.disposed) { this.debouncer.cancel() } }
/** * Unsubscribe and dispose the debouncer. * Pending calls are cancelled (not flushed). */ dispose(): void { if (this.disposed) return this.disposed = true this.actualEvents.off(this.n, this.fn) this.debouncer.dispose() }
/** * Check if there is a pending debounced call. * Returns false if disposed. */ isPending(): boolean { if (this.disposed) return false return this.debouncer.isPending() }}