Skip to content

Instantly share code, notes, and snippets.

@den-churbanov
Created April 4, 2024 18:00
Show Gist options
  • Save den-churbanov/82e949d2ac408b07e700c1caf111ad2b to your computer and use it in GitHub Desktop.
Save den-churbanov/82e949d2ac408b07e700c1caf111ad2b to your computer and use it in GitHub Desktop.
Setup event listener
import {
Event,
attach,
createEvent,
createStore,
sample,
scopeBind,
Store,
} from 'effector';
export function toStore<T extends any>(value: T | Store<T> | StoreWritable<T>, name?: string) {
if (is.store(value)) return value;
return createStore(value, { name });
}
export interface Setupable {
setup: Event<any>,
teardown?: Event<any>
}
type ListenerEvent<K extends string> = K extends keyof HTMLElementEventMap
? HTMLElementEventMap[K]
: K extends keyof ElementEventMap
? ElementEventMap[K]
: K extends keyof DocumentEventMap
? DocumentEventMap[K]
: K extends keyof WindowEventMap
? WindowEventMap[K]
: never;
type PayloadGetter<K extends string, Payload = void> = (e: ListenerEvent<K>) => Payload;
type Listener<K extends string = string> = (e: ListenerEvent<K>) => void;
export type Target = HTMLElement | Element | Window | Document;
interface SetupListenerOptions<
K extends string,
T extends Target = Document,
Payload = void
> extends Setupable {
eventName: K,
readPayload?: PayloadGetter<K, Payload>,
target?: T | Store<T>,
capture?: boolean,
once?: boolean,
passive?: boolean,
}
function setupListener<K extends keyof HTMLElementEventMap, Payload = void>(
options: SetupListenerOptions<K, HTMLElement, Payload>
): Event<Payload>;
function setupListener<K extends keyof ElementEventMap, Payload = void>(
options: SetupListenerOptions<K, Element, Payload>
): Event<Payload>;
function setupListener<K extends keyof DocumentEventMap, Payload = void>(
options: SetupListenerOptions<K, Document, Payload>
): Event<Payload>;
function setupListener<K extends keyof WindowEventMap, Payload = void>(
options: SetupListenerOptions<K, Window, Payload>
): Event<Payload>;
function setupListener(
{
readPayload,
teardown,
setup,
target = document,
eventName,
...listenerOptions
}: SetupListenerOptions<string, Target, any>
) {
const $eventName = toStore(eventName);
const $target = toStore(target);
const event = createEvent();
const $listener = createStore<Listener>(null);
const startWatchingFx = attach({
source: { target: $target, eventName: $eventName },
effect({ target, eventName }) {
let listener: Listener;
if (readPayload) {
const boundEvent = scopeBind(event, { safe: true });
listener = e => boundEvent(readPayload(e));
} else {
const boundEvent = scopeBind(event.prepend(() => undefined), { safe: true });
listener = () => boundEvent();
}
target.addEventListener(eventName, listener, listenerOptions);
return listener;
}
})
const stopWatchingFx = attach({
source: { target: $target, eventName: $eventName, listener: $listener },
effect({ target, eventName, listener }) {
if (!listener || !target) return;
target.removeEventListener(eventName, listener, listenerOptions);
}
});
sample({
clock: setup,
fn: () => {},
target: startWatchingFx
});
sample({
clock: startWatchingFx.doneData,
filter: Boolean,
target: $listener,
});
if (teardown) {
sample({
clock: teardown,
fn: () => {},
target: stopWatchingFx
});
}
sample({ clock: stopWatchingFx.done, target: $listener.reinit });
return event;
}
export { setupListener };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment