@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
173 lines (172 loc) • 6.29 kB
TypeScript
/** A function that performs cleanup when called */
export type DisposeFn = () => void;
/**
* Interface for objects that hold resources and can be disposed.
* Implement this interface on any object that needs deterministic cleanup.
*
* @example
* ```ts
* class MyResource implements IDisposable {
* dispose() { /* release resources *\/ }
* }
* ```
*
* @category Utilities
* @group Lifecycle
*/
export interface IDisposable {
dispose(): void;
}
/**
* Type guard to check if an object implements {@link IDisposable}.
*
* @example
* ```ts
* if (isDisposable(obj)) {
* obj.dispose(); // safe to call
* }
* ```
*/
export declare function isDisposable(value: unknown): value is IDisposable;
/**
* @experimental
* Subscribe to a DOM event on any {@link EventTarget} (window, document, HTML elements, etc.)
* and return an {@link IDisposable} that removes the listener when disposed.
*
* Provides full TypeScript event type inference — the callback parameter
* is automatically typed based on the event name (e.g. `"resize"` → `UIEvent`,
* `"click"` → `MouseEvent`).
*
* Use with {@link DisposableStore.add} for automatic lifecycle cleanup in components.
*
* @param target The EventTarget to listen on (window, document, an element, etc.)
* @param type The event name (e.g. `"resize"`, `"click"`, `"keydown"`)
* @param listener The event handler callback
* @param options Optional addEventListener options (passive, capture, once, signal)
* @returns An {@link IDisposable} that removes the event listener when disposed
*
* @example Standalone usage
* ```ts
* import { on } from "@needle-tools/engine";
*
* const sub = on(window, "resize", (ev) => {
* // ev is typed as UIEvent
* console.log("resized", ev.target);
* });
*
* // Later: clean up
* sub.dispose();
* ```
*
* @example With autoCleanup in a component
* ```ts
* import { Behaviour, on } from "@needle-tools/engine";
*
* export class MyComponent extends Behaviour {
* onEnable() {
* this.autoCleanup(on(window, "resize", (ev) => { /* UIEvent *\/ }));
* this.autoCleanup(on(document, "keydown", (ev) => { /* KeyboardEvent *\/ }));
* this.autoCleanup(on(this.context.domElement, "click", (ev) => { /* MouseEvent *\/ }));
* }
* // All listeners removed automatically on disable!
* }
* ```
*
* @category Utilities
* @group Lifecycle
*/
export declare function on<K extends keyof WindowEventMap>(target: Window, type: K, listener: (ev: WindowEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
export declare function on<K extends keyof DocumentEventMap>(target: Document, type: K, listener: (ev: DocumentEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
export declare function on<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, listener: (ev: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
export declare function on(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): IDisposable;
/**
* A store for managing disposable resources (event subscriptions, listeners, callbacks)
* that should be cleaned up together.
*
* DisposableStore collects disposables and disposes them all at once when
* {@link dispose} is called. After disposal, the store can be reused — new items
* can be added and a subsequent {@link dispose} call will clean those up.
*
* This is the same pattern used internally by VSCode for lifecycle-bound resource management.
*
* @example Basic usage
* ```ts
* import { DisposableStore, on } from "@needle-tools/engine";
*
* const store = new DisposableStore();
*
* // Register a DOM event listener (typed!)
* store.add(on(window, "resize", (ev) => console.log(ev)));
*
* // Register the return value of EventList.on()
* store.add(myEventList.on(data => console.log(data)));
*
* // Register a raw cleanup function
* store.add(() => someSDK.off("event", handler));
*
* // Later: dispose everything at once
* store.dispose();
* ```
*
* @example Use with Needle Engine components
* ```ts
* import { Behaviour, serializable, EventList, on } from "@needle-tools/engine";
*
* export class MyComponent extends Behaviour {
* @serializable(EventList)
* onClick?: EventList;
*
* onEnable() {
* // DOM events — fully typed
* this.autoCleanup(on(window, "resize", (ev) => this.onResize(ev)));
*
* // EventList — .on() returns a function, autoCleanup accepts it
* this.autoCleanup(this.onClick?.on(() => console.log("clicked!")));
* }
* // No onDisable needed — cleaned up automatically!
* }
* ```
*
* @category Utilities
* @group Lifecycle
*/
export declare class DisposableStore implements IDisposable {
private _disposables;
/** The number of registered disposables */
get size(): number;
/**
* Register a disposable resource. Accepts:
* - An {@link IDisposable} object (has a `dispose()` method) — e.g. from {@link on}
* - A cleanup function (e.g. return value of `EventList.on()`)
* - `null` or `undefined` (safe no-op for conditional subscriptions)
*
* When {@link dispose} is called, all registered resources are cleaned up.
*
* @param disposable The resource to register for disposal
*
* @example
* ```ts
* const store = new DisposableStore();
*
* // IDisposable object from on()
* store.add(on(window, "resize", handler));
*
* // Function returned by EventList.on()
* store.add(myEvent.on(handler));
*
* // Raw cleanup function
* store.add(() => connection.close());
*
* // Conditional — safe with undefined
* store.add(this.maybeEvent?.on(handler));
* ```
*/
add(disposable: IDisposable | DisposeFn | Function | null | undefined): void;
/**
* Dispose all registered resources. Each registered disposable is cleaned up,
* then the internal list is cleared. The store can be reused after disposal.
*
* Called automatically by the engine when a component's `onDisable` lifecycle fires.
*/
dispose(): void;
}