|
1 | | -import { Accessor, createSignal } from "solid-js"; |
| 1 | +import { onCleanup } from "solid-js"; |
| 2 | + |
| 3 | +type Listener<T> = (value: T) => void; |
| 4 | + |
| 5 | +type EventBus<T> = { |
| 6 | + dispatch: (...args: T extends undefined ? [] : [value: T]) => void; |
| 7 | + subscribe: (fn: Listener<T>) => () => void; |
| 8 | + useListener: (fn: Listener<T>) => void; |
| 9 | +}; |
2 | 10 |
|
3 | 11 | /** |
4 | | - * Creates a reactive event primitive. |
| 12 | + * Creates a pub-sub event bus. |
5 | 13 | * |
6 | | - * Returns a tuple of: |
7 | | - * - An accessor whose value increments each time the event is dispatched. |
8 | | - * Reactive consumers (effects, memos, etc.) re-run whenever the event fires. |
9 | | - * - A dispatch function that triggers the event. |
10 | | - * |
11 | | - * @returns `[accessor, dispatch]` |
| 14 | + * - `dispatch(value)` notifies all listeners. No args needed when `T` is `undefined`. |
| 15 | + * - `subscribe(fn)` registers a listener, returns an unsubscribe function. |
| 16 | + * - `useListener(fn)` registers a listener that auto-unsubscribes via Solid's `onCleanup`. |
12 | 17 | * |
13 | 18 | * @example |
14 | 19 | * ```ts |
15 | | - * const [onSave, dispatchSave] = createEvent(); |
16 | | - * |
17 | | - * createEffect(() => { |
18 | | - * onSave(); // re-runs every time dispatchSave() is called |
19 | | - * console.log("saved!"); |
20 | | - * }); |
| 20 | + * const clickEvent = createEvent2<{ x: number; y: number }>(); |
| 21 | + * clickEvent.useListener(({ x, y }) => console.log(x, y)); |
| 22 | + * clickEvent.dispatch({ x: 10, y: 20 }); |
21 | 23 | * |
22 | | - * dispatchSave(); // triggers the effect |
| 24 | + * const resetEvent = createEvent2(); |
| 25 | + * resetEvent.useListener(() => console.log("reset")); |
| 26 | + * resetEvent.dispatch(); |
23 | 27 | * ``` |
24 | 28 | */ |
25 | 29 |
|
26 | | -export function createEvent(): [Accessor<number>, () => void] { |
27 | | - const [get, set] = createSignal(0); |
28 | | - return [ |
29 | | - get, |
30 | | - () => { |
31 | | - set((v) => v + 1); |
32 | | - }, |
33 | | - ]; |
| 30 | +export function createEvent<T = undefined>(): EventBus<T> { |
| 31 | + const listeners = new Set<Listener<T>>(); |
| 32 | + |
| 33 | + function dispatch(...args: T extends undefined ? [] : [value: T]): void { |
| 34 | + const value = args[0] as T; |
| 35 | + for (const fn of listeners) { |
| 36 | + try { |
| 37 | + fn(value); |
| 38 | + } catch (e) { |
| 39 | + console.error(e); |
| 40 | + } |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + function subscribe(fn: Listener<T>): () => void { |
| 45 | + listeners.add(fn); |
| 46 | + return () => listeners.delete(fn); |
| 47 | + } |
| 48 | + |
| 49 | + // Auto-unsubscribes when the Solid owner scope is disposed |
| 50 | + function useListener(fn: Listener<T>): void { |
| 51 | + listeners.add(fn); |
| 52 | + onCleanup(() => listeners.delete(fn)); |
| 53 | + } |
| 54 | + |
| 55 | + return { dispatch, subscribe, useListener }; |
34 | 56 | } |
0 commit comments