use-typed-event 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +516 -0
- package/dist/chunk-ZRE57KHP.js +133 -0
- package/dist/index.cjs +159 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +8 -0
- package/dist/react.cjs +234 -0
- package/dist/react.d.cts +50 -0
- package/dist/react.d.ts +50 -0
- package/dist/react.js +82 -0
- package/package.json +100 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createEmitter: () => createEmitter,
|
|
24
|
+
createSelectors: () => createSelectors
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/emitter.ts
|
|
29
|
+
var matchesHandler = (registered, target) => registered === target || typeof registered !== "function" && registered.handler === target;
|
|
30
|
+
var keyOf = (name) => typeof name === "string" ? name : String(name);
|
|
31
|
+
var copyWithout = (list, index) => {
|
|
32
|
+
const length = list.length;
|
|
33
|
+
const next = new Array(length - 1);
|
|
34
|
+
for (let i = 0; i < index; i++) next[i] = list[i];
|
|
35
|
+
for (let i = index + 1; i < length; i++) next[i - 1] = list[i];
|
|
36
|
+
return next;
|
|
37
|
+
};
|
|
38
|
+
var createEmitter = () => {
|
|
39
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
40
|
+
let anyHandlers = [];
|
|
41
|
+
const removeFromList = (name, slot, target) => {
|
|
42
|
+
const length = slot.length;
|
|
43
|
+
let index = 0;
|
|
44
|
+
while (index < length && !matchesHandler(slot[index], target)) index++;
|
|
45
|
+
if (index === length) return;
|
|
46
|
+
if (length === 2) handlers.set(name, slot[1 - index]);
|
|
47
|
+
else handlers.set(name, copyWithout(slot, index));
|
|
48
|
+
};
|
|
49
|
+
const removeNamed = (name, target) => {
|
|
50
|
+
const slot = handlers.get(name);
|
|
51
|
+
if (slot === void 0) return;
|
|
52
|
+
if (Array.isArray(slot)) removeFromList(name, slot, target);
|
|
53
|
+
else if (matchesHandler(slot, target)) handlers.set(name, void 0);
|
|
54
|
+
};
|
|
55
|
+
const subscribe = (name, entry) => {
|
|
56
|
+
const key = keyOf(name);
|
|
57
|
+
const slot = handlers.get(key);
|
|
58
|
+
if (slot === void 0) handlers.set(key, entry);
|
|
59
|
+
else if (Array.isArray(slot)) slot.push(entry);
|
|
60
|
+
else handlers.set(key, [slot, entry]);
|
|
61
|
+
let active = true;
|
|
62
|
+
return () => {
|
|
63
|
+
if (!active) return;
|
|
64
|
+
active = false;
|
|
65
|
+
const current = handlers.get(key);
|
|
66
|
+
if (current === entry) handlers.set(key, void 0);
|
|
67
|
+
else if (Array.isArray(current)) removeFromList(key, current, entry);
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const once = (name, handler) => subscribe(keyOf(name), { handler });
|
|
71
|
+
const off = (name, handler) => {
|
|
72
|
+
const key = keyOf(name);
|
|
73
|
+
if (!handler) {
|
|
74
|
+
handlers.delete(key);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
removeNamed(key, handler);
|
|
78
|
+
};
|
|
79
|
+
const emit = (name, payload) => {
|
|
80
|
+
const key = keyOf(name);
|
|
81
|
+
const slot = handlers.get(key);
|
|
82
|
+
if (slot !== void 0) {
|
|
83
|
+
if (typeof slot === "function") slot(payload);
|
|
84
|
+
else if (Array.isArray(slot)) {
|
|
85
|
+
const length = slot.length;
|
|
86
|
+
for (let i = 0; i < length; i++) {
|
|
87
|
+
const entry = slot[i];
|
|
88
|
+
if (typeof entry === "function") entry(payload);
|
|
89
|
+
else {
|
|
90
|
+
removeNamed(key, entry);
|
|
91
|
+
entry.handler(payload);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
handlers.set(key, void 0);
|
|
96
|
+
slot.handler(payload);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const anyList = anyHandlers;
|
|
100
|
+
const anyLength = anyList.length;
|
|
101
|
+
for (let i = 0; i < anyLength; i++) anyList[i](key, payload);
|
|
102
|
+
};
|
|
103
|
+
const onAny = (handler) => {
|
|
104
|
+
anyHandlers.push(handler);
|
|
105
|
+
let active = true;
|
|
106
|
+
return () => {
|
|
107
|
+
if (!active) return;
|
|
108
|
+
active = false;
|
|
109
|
+
const index = anyHandlers.indexOf(handler);
|
|
110
|
+
if (index !== -1) anyHandlers = copyWithout(anyHandlers, index);
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
const waitFor = (name, options) => {
|
|
114
|
+
const key = keyOf(name);
|
|
115
|
+
const signal = options?.signal;
|
|
116
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const onAbort = () => {
|
|
119
|
+
unsubscribe();
|
|
120
|
+
reject(signal?.reason);
|
|
121
|
+
};
|
|
122
|
+
const unsubscribe = once(key, (payload) => {
|
|
123
|
+
signal?.removeEventListener("abort", onAbort);
|
|
124
|
+
resolve(payload);
|
|
125
|
+
});
|
|
126
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const clear = () => {
|
|
130
|
+
handlers.clear();
|
|
131
|
+
anyHandlers = [];
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
on: subscribe,
|
|
135
|
+
once,
|
|
136
|
+
off,
|
|
137
|
+
emit,
|
|
138
|
+
onAny,
|
|
139
|
+
waitFor,
|
|
140
|
+
clear
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/selectors.ts
|
|
145
|
+
var build = (path) => new Proxy({}, {
|
|
146
|
+
get(_t, prop) {
|
|
147
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf")
|
|
148
|
+
return () => path.join(".");
|
|
149
|
+
if (prop === "$") return path.join(".");
|
|
150
|
+
if (typeof prop === "symbol") return void 0;
|
|
151
|
+
return build([...path, prop]);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
var createSelectors = () => build([]);
|
|
155
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
156
|
+
0 && (module.exports = {
|
|
157
|
+
createEmitter,
|
|
158
|
+
createSelectors
|
|
159
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Maps event names to their payload types. Use `void` for payload-less events. */
|
|
2
|
+
type EventMap = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Argument tuple for a payload of type `P`: no argument for `void` events,
|
|
5
|
+
* an optional argument when `undefined` is part of the payload type, and a
|
|
6
|
+
* required argument otherwise.
|
|
7
|
+
*/
|
|
8
|
+
type PayloadArgs<P> = [P] extends [void] ? [] : undefined extends P ? [payload?: P] : [payload: P];
|
|
9
|
+
/** Handler invoked with the payload of a single event. */
|
|
10
|
+
type EventHandler<P> = (payload: P) => void;
|
|
11
|
+
/** Union of `[name, payload]` tuples across every event in the map. */
|
|
12
|
+
type EventTuple<E extends EventMap> = {
|
|
13
|
+
[K in keyof E & string]: [name: K, payload: E[K]];
|
|
14
|
+
}[keyof E & string];
|
|
15
|
+
/**
|
|
16
|
+
* Wildcard handler receiving `(name, payload)` for every emit. Narrowing on
|
|
17
|
+
* `name` narrows `payload` to the mapped type.
|
|
18
|
+
*/
|
|
19
|
+
type AnyHandler<E extends EventMap> = (...event: EventTuple<E>) => void;
|
|
20
|
+
/** Detaches a previously registered handler. Safe to call more than once. */
|
|
21
|
+
type Unsubscribe = () => void;
|
|
22
|
+
/** Options for {@link Emitter.waitFor}. */
|
|
23
|
+
type WaitForOptions = {
|
|
24
|
+
/** Rejects the pending promise with `signal.reason` when aborted. */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
};
|
|
27
|
+
/** A typed event emitter over the event map `E`. */
|
|
28
|
+
type Emitter<E extends EventMap> = {
|
|
29
|
+
/** Registers `handler` for `name`. Returns an idempotent unsubscribe. */
|
|
30
|
+
on: <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => Unsubscribe;
|
|
31
|
+
/** Registers `handler` for `name`, invoked at most once. Returns an idempotent unsubscribe. */
|
|
32
|
+
once: <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => Unsubscribe;
|
|
33
|
+
/**
|
|
34
|
+
* Removes the first registration of `handler` for `name` — including
|
|
35
|
+
* `once` registrations, matched by their original handler reference — or
|
|
36
|
+
* every handler for `name` when omitted.
|
|
37
|
+
*/
|
|
38
|
+
off: <K extends keyof E & string>(name: K, handler?: EventHandler<E[K]>) => void;
|
|
39
|
+
/** Emits `name`, delivering the payload to named handlers first, then wildcard handlers. */
|
|
40
|
+
emit: <K extends keyof E & string>(name: K, ...args: PayloadArgs<E[K]>) => void;
|
|
41
|
+
/** Registers a wildcard handler for every event. Returns an idempotent unsubscribe. */
|
|
42
|
+
onAny: (handler: AnyHandler<E>) => Unsubscribe;
|
|
43
|
+
/** Resolves with the next payload of `name`; rejects with `signal.reason` on abort. */
|
|
44
|
+
waitFor: <K extends keyof E & string>(name: K, options?: WaitForOptions) => Promise<E[K]>;
|
|
45
|
+
/** Removes every named and wildcard handler. */
|
|
46
|
+
clear: () => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a typed event emitter for the event map `E`.
|
|
51
|
+
*/
|
|
52
|
+
declare const createEmitter: <E extends EventMap>() => Emitter<E>;
|
|
53
|
+
|
|
54
|
+
type After<K extends string, Prefix extends string> = K extends `${Prefix}${infer R}` ? R : never;
|
|
55
|
+
type HeadSeg<R extends string> = R extends `${infer H}.${string}` ? H : R;
|
|
56
|
+
/** The exact event key at `Prefix + Head`, or `never` if no such event exists. */
|
|
57
|
+
type Leaf<E extends EventMap, Prefix extends string, Head extends string> = `${Prefix}${Head}` & (keyof E & string);
|
|
58
|
+
/** The nested selector under `Prefix + Head + "."`, or `never` if nothing nests there. */
|
|
59
|
+
type Deeper<E extends EventMap, Prefix extends string, Head extends string> = After<keyof E & string, `${Prefix}${Head}.`> extends never ? never : EventSelectors<E, `${Prefix}${Head}.`>;
|
|
60
|
+
/**
|
|
61
|
+
* Nested selector object mirroring the dotted event keys of `E`. Each leaf is
|
|
62
|
+
* typed as its exact event key literal — `events.cart.add` has type `"cart.add"`
|
|
63
|
+
* — driving the same payload inference as the raw string. A key that is both a
|
|
64
|
+
* leaf and a prefix exposes its own key via the reserved `$` accessor:
|
|
65
|
+
* `events.cart.$` has type `"cart"`.
|
|
66
|
+
*/
|
|
67
|
+
type EventSelectors<E extends EventMap, Prefix extends string = ""> = {
|
|
68
|
+
[Head in HeadSeg<After<keyof E & string, Prefix>>]: [Leaf<E, Prefix, Head>] extends [never] ? Deeper<E, Prefix, Head> : [Deeper<E, Prefix, Head>] extends [never] ? Leaf<E, Prefix, Head> : Deeper<E, Prefix, Head> & {
|
|
69
|
+
readonly $: Leaf<E, Prefix, Head>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Builds a typed selector proxy for the event map `E`. Property access
|
|
74
|
+
* accumulates a path that coerces to the dotted event key —
|
|
75
|
+
* `String(createSelectors<E>().cart.add) === "cart.add"`. The reserved `$`
|
|
76
|
+
* accessor returns the current path as a plain string.
|
|
77
|
+
*/
|
|
78
|
+
declare const createSelectors: <E extends EventMap>() => EventSelectors<E>;
|
|
79
|
+
|
|
80
|
+
export { type AnyHandler, type Emitter, type EventHandler, type EventMap, type EventSelectors, type EventTuple, type PayloadArgs, type Unsubscribe, type WaitForOptions, createEmitter, createSelectors };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Maps event names to their payload types. Use `void` for payload-less events. */
|
|
2
|
+
type EventMap = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Argument tuple for a payload of type `P`: no argument for `void` events,
|
|
5
|
+
* an optional argument when `undefined` is part of the payload type, and a
|
|
6
|
+
* required argument otherwise.
|
|
7
|
+
*/
|
|
8
|
+
type PayloadArgs<P> = [P] extends [void] ? [] : undefined extends P ? [payload?: P] : [payload: P];
|
|
9
|
+
/** Handler invoked with the payload of a single event. */
|
|
10
|
+
type EventHandler<P> = (payload: P) => void;
|
|
11
|
+
/** Union of `[name, payload]` tuples across every event in the map. */
|
|
12
|
+
type EventTuple<E extends EventMap> = {
|
|
13
|
+
[K in keyof E & string]: [name: K, payload: E[K]];
|
|
14
|
+
}[keyof E & string];
|
|
15
|
+
/**
|
|
16
|
+
* Wildcard handler receiving `(name, payload)` for every emit. Narrowing on
|
|
17
|
+
* `name` narrows `payload` to the mapped type.
|
|
18
|
+
*/
|
|
19
|
+
type AnyHandler<E extends EventMap> = (...event: EventTuple<E>) => void;
|
|
20
|
+
/** Detaches a previously registered handler. Safe to call more than once. */
|
|
21
|
+
type Unsubscribe = () => void;
|
|
22
|
+
/** Options for {@link Emitter.waitFor}. */
|
|
23
|
+
type WaitForOptions = {
|
|
24
|
+
/** Rejects the pending promise with `signal.reason` when aborted. */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
};
|
|
27
|
+
/** A typed event emitter over the event map `E`. */
|
|
28
|
+
type Emitter<E extends EventMap> = {
|
|
29
|
+
/** Registers `handler` for `name`. Returns an idempotent unsubscribe. */
|
|
30
|
+
on: <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => Unsubscribe;
|
|
31
|
+
/** Registers `handler` for `name`, invoked at most once. Returns an idempotent unsubscribe. */
|
|
32
|
+
once: <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => Unsubscribe;
|
|
33
|
+
/**
|
|
34
|
+
* Removes the first registration of `handler` for `name` — including
|
|
35
|
+
* `once` registrations, matched by their original handler reference — or
|
|
36
|
+
* every handler for `name` when omitted.
|
|
37
|
+
*/
|
|
38
|
+
off: <K extends keyof E & string>(name: K, handler?: EventHandler<E[K]>) => void;
|
|
39
|
+
/** Emits `name`, delivering the payload to named handlers first, then wildcard handlers. */
|
|
40
|
+
emit: <K extends keyof E & string>(name: K, ...args: PayloadArgs<E[K]>) => void;
|
|
41
|
+
/** Registers a wildcard handler for every event. Returns an idempotent unsubscribe. */
|
|
42
|
+
onAny: (handler: AnyHandler<E>) => Unsubscribe;
|
|
43
|
+
/** Resolves with the next payload of `name`; rejects with `signal.reason` on abort. */
|
|
44
|
+
waitFor: <K extends keyof E & string>(name: K, options?: WaitForOptions) => Promise<E[K]>;
|
|
45
|
+
/** Removes every named and wildcard handler. */
|
|
46
|
+
clear: () => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a typed event emitter for the event map `E`.
|
|
51
|
+
*/
|
|
52
|
+
declare const createEmitter: <E extends EventMap>() => Emitter<E>;
|
|
53
|
+
|
|
54
|
+
type After<K extends string, Prefix extends string> = K extends `${Prefix}${infer R}` ? R : never;
|
|
55
|
+
type HeadSeg<R extends string> = R extends `${infer H}.${string}` ? H : R;
|
|
56
|
+
/** The exact event key at `Prefix + Head`, or `never` if no such event exists. */
|
|
57
|
+
type Leaf<E extends EventMap, Prefix extends string, Head extends string> = `${Prefix}${Head}` & (keyof E & string);
|
|
58
|
+
/** The nested selector under `Prefix + Head + "."`, or `never` if nothing nests there. */
|
|
59
|
+
type Deeper<E extends EventMap, Prefix extends string, Head extends string> = After<keyof E & string, `${Prefix}${Head}.`> extends never ? never : EventSelectors<E, `${Prefix}${Head}.`>;
|
|
60
|
+
/**
|
|
61
|
+
* Nested selector object mirroring the dotted event keys of `E`. Each leaf is
|
|
62
|
+
* typed as its exact event key literal — `events.cart.add` has type `"cart.add"`
|
|
63
|
+
* — driving the same payload inference as the raw string. A key that is both a
|
|
64
|
+
* leaf and a prefix exposes its own key via the reserved `$` accessor:
|
|
65
|
+
* `events.cart.$` has type `"cart"`.
|
|
66
|
+
*/
|
|
67
|
+
type EventSelectors<E extends EventMap, Prefix extends string = ""> = {
|
|
68
|
+
[Head in HeadSeg<After<keyof E & string, Prefix>>]: [Leaf<E, Prefix, Head>] extends [never] ? Deeper<E, Prefix, Head> : [Deeper<E, Prefix, Head>] extends [never] ? Leaf<E, Prefix, Head> : Deeper<E, Prefix, Head> & {
|
|
69
|
+
readonly $: Leaf<E, Prefix, Head>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Builds a typed selector proxy for the event map `E`. Property access
|
|
74
|
+
* accumulates a path that coerces to the dotted event key —
|
|
75
|
+
* `String(createSelectors<E>().cart.add) === "cart.add"`. The reserved `$`
|
|
76
|
+
* accessor returns the current path as a plain string.
|
|
77
|
+
*/
|
|
78
|
+
declare const createSelectors: <E extends EventMap>() => EventSelectors<E>;
|
|
79
|
+
|
|
80
|
+
export { type AnyHandler, type Emitter, type EventHandler, type EventMap, type EventSelectors, type EventTuple, type PayloadArgs, type Unsubscribe, type WaitForOptions, createEmitter, createSelectors };
|
package/dist/index.js
ADDED
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
createEmitter: () => createEmitter,
|
|
24
|
+
createEventBus: () => createEventBus,
|
|
25
|
+
createEventHooks: () => createEventHooks,
|
|
26
|
+
createSelectors: () => createSelectors
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(react_exports);
|
|
29
|
+
|
|
30
|
+
// src/emitter.ts
|
|
31
|
+
var matchesHandler = (registered, target) => registered === target || typeof registered !== "function" && registered.handler === target;
|
|
32
|
+
var keyOf = (name) => typeof name === "string" ? name : String(name);
|
|
33
|
+
var copyWithout = (list, index) => {
|
|
34
|
+
const length = list.length;
|
|
35
|
+
const next = new Array(length - 1);
|
|
36
|
+
for (let i = 0; i < index; i++) next[i] = list[i];
|
|
37
|
+
for (let i = index + 1; i < length; i++) next[i - 1] = list[i];
|
|
38
|
+
return next;
|
|
39
|
+
};
|
|
40
|
+
var createEmitter = () => {
|
|
41
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
42
|
+
let anyHandlers = [];
|
|
43
|
+
const removeFromList = (name, slot, target) => {
|
|
44
|
+
const length = slot.length;
|
|
45
|
+
let index = 0;
|
|
46
|
+
while (index < length && !matchesHandler(slot[index], target)) index++;
|
|
47
|
+
if (index === length) return;
|
|
48
|
+
if (length === 2) handlers.set(name, slot[1 - index]);
|
|
49
|
+
else handlers.set(name, copyWithout(slot, index));
|
|
50
|
+
};
|
|
51
|
+
const removeNamed = (name, target) => {
|
|
52
|
+
const slot = handlers.get(name);
|
|
53
|
+
if (slot === void 0) return;
|
|
54
|
+
if (Array.isArray(slot)) removeFromList(name, slot, target);
|
|
55
|
+
else if (matchesHandler(slot, target)) handlers.set(name, void 0);
|
|
56
|
+
};
|
|
57
|
+
const subscribe = (name, entry) => {
|
|
58
|
+
const key = keyOf(name);
|
|
59
|
+
const slot = handlers.get(key);
|
|
60
|
+
if (slot === void 0) handlers.set(key, entry);
|
|
61
|
+
else if (Array.isArray(slot)) slot.push(entry);
|
|
62
|
+
else handlers.set(key, [slot, entry]);
|
|
63
|
+
let active = true;
|
|
64
|
+
return () => {
|
|
65
|
+
if (!active) return;
|
|
66
|
+
active = false;
|
|
67
|
+
const current = handlers.get(key);
|
|
68
|
+
if (current === entry) handlers.set(key, void 0);
|
|
69
|
+
else if (Array.isArray(current)) removeFromList(key, current, entry);
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const once = (name, handler) => subscribe(keyOf(name), { handler });
|
|
73
|
+
const off = (name, handler) => {
|
|
74
|
+
const key = keyOf(name);
|
|
75
|
+
if (!handler) {
|
|
76
|
+
handlers.delete(key);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
removeNamed(key, handler);
|
|
80
|
+
};
|
|
81
|
+
const emit = (name, payload) => {
|
|
82
|
+
const key = keyOf(name);
|
|
83
|
+
const slot = handlers.get(key);
|
|
84
|
+
if (slot !== void 0) {
|
|
85
|
+
if (typeof slot === "function") slot(payload);
|
|
86
|
+
else if (Array.isArray(slot)) {
|
|
87
|
+
const length = slot.length;
|
|
88
|
+
for (let i = 0; i < length; i++) {
|
|
89
|
+
const entry = slot[i];
|
|
90
|
+
if (typeof entry === "function") entry(payload);
|
|
91
|
+
else {
|
|
92
|
+
removeNamed(key, entry);
|
|
93
|
+
entry.handler(payload);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
handlers.set(key, void 0);
|
|
98
|
+
slot.handler(payload);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const anyList = anyHandlers;
|
|
102
|
+
const anyLength = anyList.length;
|
|
103
|
+
for (let i = 0; i < anyLength; i++) anyList[i](key, payload);
|
|
104
|
+
};
|
|
105
|
+
const onAny = (handler) => {
|
|
106
|
+
anyHandlers.push(handler);
|
|
107
|
+
let active = true;
|
|
108
|
+
return () => {
|
|
109
|
+
if (!active) return;
|
|
110
|
+
active = false;
|
|
111
|
+
const index = anyHandlers.indexOf(handler);
|
|
112
|
+
if (index !== -1) anyHandlers = copyWithout(anyHandlers, index);
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const waitFor = (name, options) => {
|
|
116
|
+
const key = keyOf(name);
|
|
117
|
+
const signal = options?.signal;
|
|
118
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const onAbort = () => {
|
|
121
|
+
unsubscribe();
|
|
122
|
+
reject(signal?.reason);
|
|
123
|
+
};
|
|
124
|
+
const unsubscribe = once(key, (payload) => {
|
|
125
|
+
signal?.removeEventListener("abort", onAbort);
|
|
126
|
+
resolve(payload);
|
|
127
|
+
});
|
|
128
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const clear = () => {
|
|
132
|
+
handlers.clear();
|
|
133
|
+
anyHandlers = [];
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
on: subscribe,
|
|
137
|
+
once,
|
|
138
|
+
off,
|
|
139
|
+
emit,
|
|
140
|
+
onAny,
|
|
141
|
+
waitFor,
|
|
142
|
+
clear
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/react/use-event.ts
|
|
147
|
+
var import_react = require("react");
|
|
148
|
+
var makeUseEvent = (emitter) => {
|
|
149
|
+
const useEvent = (name, handler) => {
|
|
150
|
+
const key = keyOf(name);
|
|
151
|
+
const handlerRef = (0, import_react.useRef)(handler);
|
|
152
|
+
(0, import_react.useEffect)(() => {
|
|
153
|
+
handlerRef.current = handler;
|
|
154
|
+
});
|
|
155
|
+
(0, import_react.useEffect)(() => emitter.on(key, (payload) => handlerRef.current(payload)), [key]);
|
|
156
|
+
};
|
|
157
|
+
return useEvent;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/react/use-event-state.ts
|
|
161
|
+
var import_react2 = require("react");
|
|
162
|
+
var makeUseEventState = (emitter, last) => {
|
|
163
|
+
function useEventState(name, initialValue) {
|
|
164
|
+
const initialRef = (0, import_react2.useRef)(initialValue);
|
|
165
|
+
const key = keyOf(name);
|
|
166
|
+
const subscribe = (0, import_react2.useCallback)(
|
|
167
|
+
(onStoreChange) => emitter.on(key, (payload) => {
|
|
168
|
+
last.set(key, payload);
|
|
169
|
+
onStoreChange();
|
|
170
|
+
}),
|
|
171
|
+
[key]
|
|
172
|
+
);
|
|
173
|
+
const getSnapshot = () => last.has(key) ? last.get(key) : initialRef.current;
|
|
174
|
+
const getServerSnapshot = () => initialRef.current;
|
|
175
|
+
const value = (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
|
|
176
|
+
const setValue = (0, import_react2.useCallback)(
|
|
177
|
+
(...args) => {
|
|
178
|
+
emitter.emit(key, ...args);
|
|
179
|
+
},
|
|
180
|
+
[key]
|
|
181
|
+
);
|
|
182
|
+
return [value, setValue];
|
|
183
|
+
}
|
|
184
|
+
return useEventState;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/selectors.ts
|
|
188
|
+
var build = (path) => new Proxy({}, {
|
|
189
|
+
get(_t, prop) {
|
|
190
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf")
|
|
191
|
+
return () => path.join(".");
|
|
192
|
+
if (prop === "$") return path.join(".");
|
|
193
|
+
if (typeof prop === "symbol") return void 0;
|
|
194
|
+
return build([...path, prop]);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
var createSelectors = () => build([]);
|
|
198
|
+
|
|
199
|
+
// src/react.ts
|
|
200
|
+
var setUpHooks = (emitter) => {
|
|
201
|
+
const last = /* @__PURE__ */ new Map();
|
|
202
|
+
const record = (name, payload) => {
|
|
203
|
+
last.set(name, payload);
|
|
204
|
+
};
|
|
205
|
+
const installRecorder = () => {
|
|
206
|
+
emitter.onAny(record);
|
|
207
|
+
};
|
|
208
|
+
installRecorder();
|
|
209
|
+
const hooks = {
|
|
210
|
+
useEvent: makeUseEvent(emitter),
|
|
211
|
+
useEventState: makeUseEventState(emitter, last)
|
|
212
|
+
};
|
|
213
|
+
return { hooks, last, installRecorder };
|
|
214
|
+
};
|
|
215
|
+
var createEventHooks = (emitter) => setUpHooks(emitter).hooks;
|
|
216
|
+
var createEventBus = () => {
|
|
217
|
+
const emitter = createEmitter();
|
|
218
|
+
const bus = { ...emitter };
|
|
219
|
+
const { hooks, last, installRecorder } = setUpHooks(bus);
|
|
220
|
+
const clear = () => {
|
|
221
|
+
emitter.clear();
|
|
222
|
+
last.clear();
|
|
223
|
+
installRecorder();
|
|
224
|
+
};
|
|
225
|
+
const events = createSelectors();
|
|
226
|
+
return Object.assign(bus, hooks, { clear, events });
|
|
227
|
+
};
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
createEmitter,
|
|
231
|
+
createEventBus,
|
|
232
|
+
createEventHooks,
|
|
233
|
+
createSelectors
|
|
234
|
+
});
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { EventMap, EventHandler, PayloadArgs, Emitter, EventSelectors } from './index.cjs';
|
|
2
|
+
export { AnyHandler, EventTuple, Unsubscribe, WaitForOptions, createEmitter, createSelectors } from './index.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes to an event for the lifetime of the component. The latest
|
|
6
|
+
* `handler` is always invoked.
|
|
7
|
+
*/
|
|
8
|
+
type UseEvent<E extends EventMap> = <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns a `[value, setValue]` tuple. `value` is the last emitted payload of
|
|
12
|
+
* the event as React state: without an `initialValue` it is `undefined` until
|
|
13
|
+
* the first emit. The `initialValue` is pinned on the first render; later
|
|
14
|
+
* changes to it are ignored. Emits carrying a value equal (`Object.is`) to the
|
|
15
|
+
* current one do not re-render.
|
|
16
|
+
*
|
|
17
|
+
* `setValue` is not local state — it emits the event on the bus
|
|
18
|
+
* (`emit(name, ...)`), so **every** subscriber of `name` updates, not just the
|
|
19
|
+
* calling component. Its argument follows `PayloadArgs`. It is referentially
|
|
20
|
+
* stable across renders for a given `name`.
|
|
21
|
+
*/
|
|
22
|
+
type UseEventState<E extends EventMap> = {
|
|
23
|
+
<K extends keyof E & string>(name: K): [value: E[K] | undefined, setValue: (...args: PayloadArgs<E[K]>) => void];
|
|
24
|
+
<K extends keyof E & string>(name: K, initialValue: E[K]): [value: E[K], setValue: (...args: PayloadArgs<E[K]>) => void];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** React hooks bound to a single emitter. */
|
|
28
|
+
type EventHooks<E extends EventMap> = {
|
|
29
|
+
/** Subscribes `handler` to `name` for the component's lifetime. */
|
|
30
|
+
useEvent: UseEvent<E>;
|
|
31
|
+
/**
|
|
32
|
+
* Returns a `[value, setValue]` tuple: the last emitted payload of `name`
|
|
33
|
+
* as React state, and a stable setter that emits `name` on the bus so
|
|
34
|
+
* every subscriber updates.
|
|
35
|
+
*/
|
|
36
|
+
useEventState: UseEventState<E>;
|
|
37
|
+
};
|
|
38
|
+
/** A typed emitter with bound React hooks. */
|
|
39
|
+
type EventBus<E extends EventMap> = Omit<Emitter<E>, "clear"> & EventHooks<E> & {
|
|
40
|
+
/** Removes every handler and forgets all recorded payloads. */
|
|
41
|
+
clear: () => void;
|
|
42
|
+
/** typed selector object; `bus.events.cart.add` === the event name `"cart.add"`. */
|
|
43
|
+
events: EventSelectors<E>;
|
|
44
|
+
};
|
|
45
|
+
/** Binds React hooks to an existing emitter. */
|
|
46
|
+
declare const createEventHooks: <E extends EventMap>(emitter: Emitter<E>) => EventHooks<E>;
|
|
47
|
+
/** Creates a fresh emitter with bound React hooks, spread into one bus object. */
|
|
48
|
+
declare const createEventBus: <E extends EventMap>() => EventBus<E>;
|
|
49
|
+
|
|
50
|
+
export { Emitter, type EventBus, EventHandler, type EventHooks, EventMap, EventSelectors, PayloadArgs, type UseEvent, type UseEventState, createEventBus, createEventHooks };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { EventMap, EventHandler, PayloadArgs, Emitter, EventSelectors } from './index.js';
|
|
2
|
+
export { AnyHandler, EventTuple, Unsubscribe, WaitForOptions, createEmitter, createSelectors } from './index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes to an event for the lifetime of the component. The latest
|
|
6
|
+
* `handler` is always invoked.
|
|
7
|
+
*/
|
|
8
|
+
type UseEvent<E extends EventMap> = <K extends keyof E & string>(name: K, handler: EventHandler<E[K]>) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns a `[value, setValue]` tuple. `value` is the last emitted payload of
|
|
12
|
+
* the event as React state: without an `initialValue` it is `undefined` until
|
|
13
|
+
* the first emit. The `initialValue` is pinned on the first render; later
|
|
14
|
+
* changes to it are ignored. Emits carrying a value equal (`Object.is`) to the
|
|
15
|
+
* current one do not re-render.
|
|
16
|
+
*
|
|
17
|
+
* `setValue` is not local state — it emits the event on the bus
|
|
18
|
+
* (`emit(name, ...)`), so **every** subscriber of `name` updates, not just the
|
|
19
|
+
* calling component. Its argument follows `PayloadArgs`. It is referentially
|
|
20
|
+
* stable across renders for a given `name`.
|
|
21
|
+
*/
|
|
22
|
+
type UseEventState<E extends EventMap> = {
|
|
23
|
+
<K extends keyof E & string>(name: K): [value: E[K] | undefined, setValue: (...args: PayloadArgs<E[K]>) => void];
|
|
24
|
+
<K extends keyof E & string>(name: K, initialValue: E[K]): [value: E[K], setValue: (...args: PayloadArgs<E[K]>) => void];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** React hooks bound to a single emitter. */
|
|
28
|
+
type EventHooks<E extends EventMap> = {
|
|
29
|
+
/** Subscribes `handler` to `name` for the component's lifetime. */
|
|
30
|
+
useEvent: UseEvent<E>;
|
|
31
|
+
/**
|
|
32
|
+
* Returns a `[value, setValue]` tuple: the last emitted payload of `name`
|
|
33
|
+
* as React state, and a stable setter that emits `name` on the bus so
|
|
34
|
+
* every subscriber updates.
|
|
35
|
+
*/
|
|
36
|
+
useEventState: UseEventState<E>;
|
|
37
|
+
};
|
|
38
|
+
/** A typed emitter with bound React hooks. */
|
|
39
|
+
type EventBus<E extends EventMap> = Omit<Emitter<E>, "clear"> & EventHooks<E> & {
|
|
40
|
+
/** Removes every handler and forgets all recorded payloads. */
|
|
41
|
+
clear: () => void;
|
|
42
|
+
/** typed selector object; `bus.events.cart.add` === the event name `"cart.add"`. */
|
|
43
|
+
events: EventSelectors<E>;
|
|
44
|
+
};
|
|
45
|
+
/** Binds React hooks to an existing emitter. */
|
|
46
|
+
declare const createEventHooks: <E extends EventMap>(emitter: Emitter<E>) => EventHooks<E>;
|
|
47
|
+
/** Creates a fresh emitter with bound React hooks, spread into one bus object. */
|
|
48
|
+
declare const createEventBus: <E extends EventMap>() => EventBus<E>;
|
|
49
|
+
|
|
50
|
+
export { Emitter, type EventBus, EventHandler, type EventHooks, EventMap, EventSelectors, PayloadArgs, type UseEvent, type UseEventState, createEventBus, createEventHooks };
|