@xtia/jel 0.9.1 → 0.11.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/README.md +19 -5
- package/index.d.ts +4 -2
- package/index.js +4 -1
- package/internal/element.d.ts +1 -0
- package/internal/element.js +12 -12
- package/internal/emitter.d.ts +50 -31
- package/internal/emitter.js +117 -53
- package/internal/proxy.d.ts +2 -2
- package/internal/proxy.js +11 -9
- package/internal/types.d.ts +39 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,14 +65,14 @@ function showDialogue(content: DOMContent) => {
|
|
|
65
65
|
|
|
66
66
|
interface Job {
|
|
67
67
|
name: string;
|
|
68
|
-
completionMessage: DOMContent;
|
|
68
|
+
completionMessage: () => DOMContent;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
showDialogue("Hello, world");
|
|
72
72
|
showDialogue(["Hello, ", $.i("world")]);
|
|
73
73
|
showDialogue([
|
|
74
74
|
$.h2(`${job.name} Complete`),
|
|
75
|
-
$.p(job.completionMessage),
|
|
75
|
+
$.p(job.completionMessage()),
|
|
76
76
|
]);
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -130,15 +130,15 @@ showDialogue(["Hello ", $("span.green", "world")]);
|
|
|
130
130
|
Event emitters can be chained:
|
|
131
131
|
|
|
132
132
|
```ts
|
|
133
|
-
|
|
133
|
+
div.events.mousemove
|
|
134
134
|
.takeUntil(body.events.mousedown.filter(e => e.button === 1))
|
|
135
135
|
.map(ev => [ev.offsetX, ev.offsetY])
|
|
136
136
|
.apply(([x, y]) => console.log("mouse @ ", x, y));
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
For RxJS users, events can be observed with `fromEvent(element
|
|
139
|
+
For RxJS users, events can be observed with `fromEvent(ent.element, "mousemove")`.
|
|
140
140
|
|
|
141
|
-
## Reactive
|
|
141
|
+
## Reactive properties
|
|
142
142
|
|
|
143
143
|
Style properties, content and class presence can be emitter subscriptions:
|
|
144
144
|
|
|
@@ -165,6 +165,20 @@ virtualCursor.classes.toggle(
|
|
|
165
165
|
h1.content = websocket$
|
|
166
166
|
.filter(msg => msg.type == "title")
|
|
167
167
|
.map(msg => msg.text);
|
|
168
|
+
|
|
169
|
+
const searchInput = $("input.search");
|
|
170
|
+
const searchResults$ = searchInput.events.input
|
|
171
|
+
.debounce(300)
|
|
172
|
+
.map(() => searchInput.value)
|
|
173
|
+
.filter(term => term.length >= 2)
|
|
174
|
+
.mapAsync(term => performSearch(term)); // Returns emitter of search results
|
|
175
|
+
|
|
176
|
+
// Then use it reactively
|
|
177
|
+
$.ul({
|
|
178
|
+
content: searchResults$.map(results =>
|
|
179
|
+
results.map(result => $.li(result.title))
|
|
180
|
+
)
|
|
181
|
+
});
|
|
168
182
|
```
|
|
169
183
|
Removing an element from the page will unsubscribe from any attached stream, and resubscribe if subsequently appended.
|
|
170
184
|
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity } from "./internal/types";
|
|
2
1
|
import { $ } from "./internal/element";
|
|
3
2
|
import { DomEntity } from "./internal/types";
|
|
3
|
+
export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity, EventEmitterMap, EmitterLike, CSSValue } from "./internal/types";
|
|
4
4
|
export { createEntity } from "./internal/util";
|
|
5
|
-
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, type EventEmitter, combineEmitters } from "./internal/emitter";
|
|
5
|
+
export { createEventSource, createEventsSource, interval, timeout, animationFrames, SubjectEmitter, toEventEmitter, type EventEmitter, type EventRecording, type EventRecorder, combineEmitters } from "./internal/emitter";
|
|
6
|
+
export { createEventsProxy } from "./internal/proxy";
|
|
6
7
|
export { $ };
|
|
7
8
|
export declare const $body: DomEntity<HTMLElement>;
|
|
9
|
+
export declare const windowEvents: import(".").EventEmitterMap<WindowEventMap>;
|
package/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { $ } from "./internal/element";
|
|
2
|
+
import { createEventsProxy } from "./internal/proxy";
|
|
2
3
|
export { createEntity } from "./internal/util";
|
|
3
|
-
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, combineEmitters } from "./internal/emitter";
|
|
4
|
+
export { createEventSource, createEventsSource, interval, timeout, animationFrames, SubjectEmitter, toEventEmitter, combineEmitters } from "./internal/emitter";
|
|
5
|
+
export { createEventsProxy } from "./internal/proxy";
|
|
4
6
|
export { $ };
|
|
5
7
|
export const $body = "document" in globalThis ? $(document.body) : undefined;
|
|
8
|
+
export const windowEvents = createEventsProxy(window);
|
package/internal/element.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class ClassAccessor {
|
|
|
11
11
|
toggle(className: string, value: EmitterLike<boolean>): void;
|
|
12
12
|
contains(className: string): boolean;
|
|
13
13
|
get length(): number;
|
|
14
|
+
get value(): string;
|
|
14
15
|
toString(): string;
|
|
15
16
|
replace(token: string, newToken: string): void;
|
|
16
17
|
forEach(cb: (token: string, idx: number) => void): void;
|
package/internal/element.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toEventEmitter } from "./emitter.js";
|
|
2
|
+
import { attribsProxy, createEventsProxy, styleProxy } from "./proxy";
|
|
2
3
|
import { entityDataSymbol, isContent, isJelEntity, isReactiveSource } from "./util";
|
|
3
4
|
const elementWrapCache = new WeakMap();
|
|
4
5
|
const recursiveAppend = (parent, c) => {
|
|
@@ -17,7 +18,7 @@ const recursiveAppend = (parent, c) => {
|
|
|
17
18
|
parent.append(c);
|
|
18
19
|
};
|
|
19
20
|
function createElement(tag, descriptor = {}) {
|
|
20
|
-
if (isContent(descriptor))
|
|
21
|
+
if (isContent(descriptor) || isReactiveSource(descriptor))
|
|
21
22
|
descriptor = {
|
|
22
23
|
content: descriptor,
|
|
23
24
|
};
|
|
@@ -200,8 +201,8 @@ function getWrappedElement(element) {
|
|
|
200
201
|
if (listeners.style[prop])
|
|
201
202
|
removeListener("style", prop);
|
|
202
203
|
if (typeof value == "object" && value) {
|
|
203
|
-
if (
|
|
204
|
-
addListener("style", prop, value);
|
|
204
|
+
if (isReactiveSource(value)) {
|
|
205
|
+
addListener("style", prop, toEventEmitter(value));
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
207
208
|
value = value.toString();
|
|
@@ -347,7 +348,7 @@ function getWrappedElement(element) {
|
|
|
347
348
|
removeListener("class", c);
|
|
348
349
|
});
|
|
349
350
|
}),
|
|
350
|
-
events:
|
|
351
|
+
events: createEventsProxy(element),
|
|
351
352
|
};
|
|
352
353
|
elementWrapCache.set(element, domEntity);
|
|
353
354
|
}
|
|
@@ -381,6 +382,9 @@ export class ClassAccessor {
|
|
|
381
382
|
get length() {
|
|
382
383
|
return this.classList.length;
|
|
383
384
|
}
|
|
385
|
+
get value() {
|
|
386
|
+
return this.classList.value;
|
|
387
|
+
}
|
|
384
388
|
toString() {
|
|
385
389
|
return this.classList.toString();
|
|
386
390
|
}
|
|
@@ -393,13 +397,9 @@ export class ClassAccessor {
|
|
|
393
397
|
}
|
|
394
398
|
map(cb) {
|
|
395
399
|
const result = [];
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const [idx, value] = entry.value;
|
|
400
|
-
result.push(cb(value, idx));
|
|
401
|
-
entry = entries.next();
|
|
402
|
-
}
|
|
400
|
+
this.classList.forEach((v, i) => {
|
|
401
|
+
result.push(cb(v, i));
|
|
402
|
+
});
|
|
403
403
|
return result;
|
|
404
404
|
}
|
|
405
405
|
}
|
package/internal/emitter.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import { EmitterLike } from "./types";
|
|
2
|
-
type Handler<T> = (value: T) => void;
|
|
3
|
-
type Period = {
|
|
4
|
-
asMilliseconds: number;
|
|
5
|
-
} | {
|
|
6
|
-
asSeconds: number;
|
|
7
|
-
};
|
|
8
|
-
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
9
|
-
export type UnsubscribeFunc = () => void;
|
|
1
|
+
import { Dictionary, EmissionSource, EmitterLike, EventHandlerMap, EventSource, Handler, ListenFunc, Period, UnsubscribeFunc } from "./types";
|
|
10
2
|
export declare class EventEmitter<T> {
|
|
11
3
|
protected onListen: ListenFunc<T>;
|
|
12
4
|
constructor(onListen: ListenFunc<T>);
|
|
@@ -30,6 +22,7 @@ export declare class EventEmitter<T> {
|
|
|
30
22
|
*/
|
|
31
23
|
map<R>(mapFunc: (value: T) => R): EventEmitter<R>;
|
|
32
24
|
mapAsync<R>(mapFunc: (value: T) => Promise<R>): EventEmitter<R>;
|
|
25
|
+
as<R>(value: R): EventEmitter<R>;
|
|
33
26
|
/**
|
|
34
27
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
35
28
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -90,13 +83,15 @@ export declare class EventEmitter<T> {
|
|
|
90
83
|
throttle(period: Period): EventEmitter<T>;
|
|
91
84
|
batch(ms: number): EventEmitter<T[]>;
|
|
92
85
|
/**
|
|
93
|
-
* Creates a chainable emitter that
|
|
86
|
+
* Creates a chainable emitter that forwards the next emission from the parent
|
|
94
87
|
* **Experimental**: May change in future revisions
|
|
95
88
|
* Note: only listens to the parent while at least one downstream subscription is present
|
|
96
89
|
* @param notifier
|
|
97
90
|
* @returns
|
|
98
91
|
*/
|
|
99
92
|
once(): EventEmitter<T>;
|
|
93
|
+
once(handler: Handler<T>): UnsubscribeFunc;
|
|
94
|
+
getNext(): Promise<T>;
|
|
100
95
|
delay(ms: number): EventEmitter<T>;
|
|
101
96
|
delay(period: Period): EventEmitter<T>;
|
|
102
97
|
scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
|
|
@@ -140,11 +135,38 @@ export declare class EventEmitter<T> {
|
|
|
140
135
|
*/
|
|
141
136
|
or(...emitters: EmitterLike<T>[]): EventEmitter<T>;
|
|
142
137
|
or<U>(...emitters: EmitterLike<U>[]): EventEmitter<T | U>;
|
|
143
|
-
then<R>(handler: (value: T) => R): Promise<R>;
|
|
144
138
|
memo(): Memo<T | undefined>;
|
|
145
139
|
memo(initial: T): Memo<T>;
|
|
146
140
|
memo<U>(initial: U): Memo<T | U>;
|
|
141
|
+
record(): EventRecorder<T>;
|
|
142
|
+
}
|
|
143
|
+
export declare class EventRecorder<T> {
|
|
144
|
+
private startTime;
|
|
145
|
+
private entries;
|
|
146
|
+
private recording;
|
|
147
|
+
private unsubscribe;
|
|
148
|
+
constructor(emitter: EventEmitter<T>);
|
|
149
|
+
private add;
|
|
150
|
+
stop(): EventRecording<T>;
|
|
151
|
+
}
|
|
152
|
+
export declare class EventRecording<T> {
|
|
153
|
+
private _entries;
|
|
154
|
+
constructor(entries: [number, T][]);
|
|
155
|
+
export(): [number, T][];
|
|
156
|
+
play(speed?: number): EventEmitter<T>;
|
|
147
157
|
}
|
|
158
|
+
type EmitEmitterPair<T> = {
|
|
159
|
+
emit: (value: T) => void;
|
|
160
|
+
emitter: EventEmitter<T>;
|
|
161
|
+
};
|
|
162
|
+
type CreateEventSourceOptions<T> = {
|
|
163
|
+
initialHandler?: Handler<T>;
|
|
164
|
+
/**
|
|
165
|
+
* Function to call when subscription count changes from 0
|
|
166
|
+
* Return a *deactivation* function, which will be called when subscription count changes back to 0
|
|
167
|
+
*/
|
|
168
|
+
activate?(): UnsubscribeFunc;
|
|
169
|
+
};
|
|
148
170
|
/**
|
|
149
171
|
* Creates a linked EventEmitter and emit() pair
|
|
150
172
|
* @example
|
|
@@ -176,12 +198,18 @@ export declare class EventEmitter<T> {
|
|
|
176
198
|
* @param initialHandler Optional listener automatically applied to the resulting Emitter
|
|
177
199
|
* @returns
|
|
178
200
|
*/
|
|
179
|
-
export declare function createEventSource<T>(initialHandler?: Handler<T>):
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
export declare function createEventSource<T>(initialHandler?: Handler<T>): EmitEmitterPair<T>;
|
|
202
|
+
export declare function createEventSource<T>(options?: CreateEventSourceOptions<T>): EmitEmitterPair<T>;
|
|
203
|
+
export declare function createEventsSource<Map extends Dictionary<any>>(initialListeners?: EventHandlerMap<Map>): {
|
|
204
|
+
emitters: import("./types").EventEmitterMap<Map>;
|
|
205
|
+
trigger: <K extends keyof Map>(name: K, value: Map[K]) => void;
|
|
182
206
|
};
|
|
183
207
|
export declare function interval(ms: number): EventEmitter<number>;
|
|
184
208
|
export declare function interval(period: Period): EventEmitter<number>;
|
|
209
|
+
/**
|
|
210
|
+
* Emits time deltas from a shared RAF loop
|
|
211
|
+
*/
|
|
212
|
+
export declare const animationFrames: EventEmitter<number>;
|
|
185
213
|
export declare function timeout(ms: number): EventEmitter<void>;
|
|
186
214
|
export declare function timeout(period: Period): EventEmitter<void>;
|
|
187
215
|
declare class Memo<T> {
|
|
@@ -198,26 +226,17 @@ export declare class SubjectEmitter<T> extends EventEmitter<T> {
|
|
|
198
226
|
get value(): T;
|
|
199
227
|
next(value: T): void;
|
|
200
228
|
}
|
|
201
|
-
type EventSource<T, E extends string> = {
|
|
202
|
-
on: (eventName: E, handler: Handler<T>) => UnsubscribeFunc;
|
|
203
|
-
} | {
|
|
204
|
-
on: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc;
|
|
205
|
-
off: (eventName: E, handler: Handler<T>) => void;
|
|
206
|
-
} | {
|
|
207
|
-
addEventListener: (eventName: E, handler: Handler<T>) => UnsubscribeFunc;
|
|
208
|
-
} | {
|
|
209
|
-
addEventListener: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc;
|
|
210
|
-
removeEventListener: (eventName: E, handler: Handler<T>) => void;
|
|
211
|
-
};
|
|
212
229
|
/**
|
|
213
|
-
* Create an EventEmitter from an event source. Event
|
|
214
|
-
*
|
|
230
|
+
* Create an EventEmitter from an event source. Event source can be RxJS observable, existing `EventEmitter`, an object that
|
|
231
|
+
* provides a `subscribe()`/`listen() => UnsubscribeFunc` method, or a subscribe function itself.
|
|
232
|
+
* @param source
|
|
233
|
+
*/
|
|
234
|
+
export declare function toEventEmitter<E>(source: EmissionSource<E>): EventEmitter<E>;
|
|
235
|
+
/**
|
|
236
|
+
* Create an EventEmitter from an event provider and event name. Event source may provide matching `addEventListener`/`on(name, handler)` and `removeEventListener`/`off(name, handler)` methods, or `addEventListener`/`on(name, handler): UnsubscribeFunc.
|
|
215
237
|
* @param source
|
|
216
238
|
*/
|
|
217
|
-
export declare function toEventEmitter<
|
|
218
|
-
export declare function toEventEmitter<T, E extends string>(source: EventSource<T, E>, eventName: E): EventEmitter<T>;
|
|
219
|
-
export declare function toEventEmitter<T>(subscribe: ListenFunc<T>): EventEmitter<T>;
|
|
220
|
-
type Dictionary<T> = Record<string | symbol, T>;
|
|
239
|
+
export declare function toEventEmitter<E, N>(source: EventSource<E, N>, eventName: N): EventEmitter<E>;
|
|
221
240
|
type ExtractEmitterValue<T> = T extends EmitterLike<infer U> ? U : never;
|
|
222
241
|
type CombinedRecord<T extends Dictionary<EmitterLike<any>>> = {
|
|
223
242
|
readonly [K in keyof T]: ExtractEmitterValue<T[K]>;
|
package/internal/emitter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEventsProxy } from "./proxy.js";
|
|
1
2
|
import { isReactiveSource } from "./util";
|
|
2
3
|
function periodAsMilliseconds(t) {
|
|
3
4
|
if (typeof t == "number")
|
|
@@ -43,6 +44,10 @@ export class EventEmitter {
|
|
|
43
44
|
const listen = this.transform((value, emit) => mapFunc(value).then(emit));
|
|
44
45
|
return new EventEmitter(listen);
|
|
45
46
|
}
|
|
47
|
+
as(value) {
|
|
48
|
+
const listen = this.transform((_, emit) => emit(value));
|
|
49
|
+
return new EventEmitter(listen);
|
|
50
|
+
}
|
|
46
51
|
/**
|
|
47
52
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
48
53
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -152,14 +157,7 @@ export class EventEmitter {
|
|
|
152
157
|
});
|
|
153
158
|
return new EventEmitter(listen);
|
|
154
159
|
}
|
|
155
|
-
|
|
156
|
-
* Creates a chainable emitter that
|
|
157
|
-
* **Experimental**: May change in future revisions
|
|
158
|
-
* Note: only listens to the parent while at least one downstream subscription is present
|
|
159
|
-
* @param notifier
|
|
160
|
-
* @returns
|
|
161
|
-
*/
|
|
162
|
-
once() {
|
|
160
|
+
once(handler) {
|
|
163
161
|
let parentUnsubscribe = null;
|
|
164
162
|
let completed = false;
|
|
165
163
|
const clear = () => {
|
|
@@ -178,11 +176,18 @@ export class EventEmitter {
|
|
|
178
176
|
});
|
|
179
177
|
return clear;
|
|
180
178
|
});
|
|
181
|
-
|
|
179
|
+
const emitter = new EventEmitter(listen);
|
|
180
|
+
return handler
|
|
181
|
+
? emitter.apply(handler)
|
|
182
|
+
: emitter;
|
|
183
|
+
}
|
|
184
|
+
getNext() {
|
|
185
|
+
return new Promise((resolve) => this.once(resolve));
|
|
182
186
|
}
|
|
183
187
|
delay(t) {
|
|
188
|
+
const ms = periodAsMilliseconds(t);
|
|
184
189
|
return new EventEmitter(this.transform((value, emit) => {
|
|
185
|
-
|
|
190
|
+
return timeout(ms).apply(() => emit(value));
|
|
186
191
|
}));
|
|
187
192
|
}
|
|
188
193
|
scan(updater, initial) {
|
|
@@ -322,48 +327,63 @@ export class EventEmitter {
|
|
|
322
327
|
return () => unsubs.forEach(unsub => unsub());
|
|
323
328
|
});
|
|
324
329
|
}
|
|
325
|
-
then(handler) {
|
|
326
|
-
return new Promise(resolve => {
|
|
327
|
-
this.once().apply(v => resolve(handler(v)));
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
330
|
memo(initial) {
|
|
331
331
|
return new Memo(this, initial);
|
|
332
332
|
}
|
|
333
|
+
record() {
|
|
334
|
+
return new EventRecorder(this);
|
|
335
|
+
}
|
|
333
336
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
337
|
+
export class EventRecorder {
|
|
338
|
+
constructor(emitter) {
|
|
339
|
+
this.startTime = performance.now();
|
|
340
|
+
this.entries = [];
|
|
341
|
+
this.recording = true;
|
|
342
|
+
this.unsubscribe = emitter.listen(v => this.add(v));
|
|
343
|
+
}
|
|
344
|
+
add(value) {
|
|
345
|
+
const now = performance.now();
|
|
346
|
+
let time = now - this.startTime;
|
|
347
|
+
this.entries.push([time, value]);
|
|
348
|
+
}
|
|
349
|
+
stop() {
|
|
350
|
+
if (!this.recording) {
|
|
351
|
+
throw new Error("EventRecorder already stopped");
|
|
352
|
+
}
|
|
353
|
+
this.unsubscribe();
|
|
354
|
+
return new EventRecording(this.entries);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
export class EventRecording {
|
|
358
|
+
constructor(entries) {
|
|
359
|
+
this._entries = entries;
|
|
360
|
+
}
|
|
361
|
+
export() {
|
|
362
|
+
return [...this._entries];
|
|
363
|
+
}
|
|
364
|
+
play(speed = 1) {
|
|
365
|
+
let idx = 0;
|
|
366
|
+
let elapsed = 0;
|
|
367
|
+
const { emit, listen } = createListenable();
|
|
368
|
+
const unsubscribe = animationFrames.listen((frameElapsed) => {
|
|
369
|
+
elapsed += frameElapsed * speed;
|
|
370
|
+
while (idx < this._entries.length && this._entries[idx][0] <= elapsed) {
|
|
371
|
+
emit(this._entries[idx][1]);
|
|
372
|
+
idx++;
|
|
373
|
+
}
|
|
374
|
+
if (idx >= this._entries.length) {
|
|
375
|
+
unsubscribe();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
return new EventEmitter(listen);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
export function createEventSource(arg) {
|
|
382
|
+
if (typeof arg === "function") {
|
|
383
|
+
arg = { initialHandler: arg };
|
|
384
|
+
}
|
|
385
|
+
const { initialHandler, activate } = arg !== null && arg !== void 0 ? arg : {};
|
|
386
|
+
const { emit, listen } = createListenable(activate);
|
|
367
387
|
if (initialHandler)
|
|
368
388
|
listen(initialHandler);
|
|
369
389
|
return {
|
|
@@ -371,6 +391,30 @@ export function createEventSource(initialHandler) {
|
|
|
371
391
|
emitter: new EventEmitter(listen)
|
|
372
392
|
};
|
|
373
393
|
}
|
|
394
|
+
export function createEventsSource(initialListeners) {
|
|
395
|
+
const handlers = {};
|
|
396
|
+
const emitters = createEventsProxy({
|
|
397
|
+
on: (name, handler) => {
|
|
398
|
+
if (!handlers[name])
|
|
399
|
+
handlers[name] = [];
|
|
400
|
+
const unique = { fn: handler };
|
|
401
|
+
handlers[name].push(unique);
|
|
402
|
+
return () => {
|
|
403
|
+
const idx = handlers[name].indexOf(unique);
|
|
404
|
+
handlers[name].splice(idx, 1);
|
|
405
|
+
if (handlers[name].length == 0)
|
|
406
|
+
delete handlers[name];
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
}, initialListeners);
|
|
410
|
+
return {
|
|
411
|
+
emitters,
|
|
412
|
+
trigger: (name, value) => {
|
|
413
|
+
var _a;
|
|
414
|
+
(_a = handlers[name]) === null || _a === void 0 ? void 0 : _a.forEach(entry => entry.fn(value));
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
374
418
|
function createListenable(sourceListen) {
|
|
375
419
|
const handlers = [];
|
|
376
420
|
let onRemoveLast;
|
|
@@ -404,15 +448,35 @@ export function interval(t) {
|
|
|
404
448
|
});
|
|
405
449
|
return new EventEmitter(listen);
|
|
406
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Emits time deltas from a shared RAF loop
|
|
453
|
+
*/
|
|
454
|
+
export const animationFrames = (() => {
|
|
455
|
+
const { emit, listen } = createListenable(() => {
|
|
456
|
+
let rafId = null;
|
|
457
|
+
let lastTime = null;
|
|
458
|
+
const frame = (time) => {
|
|
459
|
+
if (lastTime === null)
|
|
460
|
+
lastTime = time;
|
|
461
|
+
rafId = requestAnimationFrame(frame);
|
|
462
|
+
const elapsed = time - lastTime;
|
|
463
|
+
emit(elapsed);
|
|
464
|
+
};
|
|
465
|
+
rafId = requestAnimationFrame(frame);
|
|
466
|
+
return () => cancelAnimationFrame(rafId);
|
|
467
|
+
});
|
|
468
|
+
return new EventEmitter(listen);
|
|
469
|
+
})();
|
|
407
470
|
export function timeout(t) {
|
|
408
471
|
const ms = periodAsMilliseconds(t);
|
|
409
472
|
const targetTime = Date.now() + ms;
|
|
410
|
-
let timeoutId = null;
|
|
411
473
|
const { emit, listen } = createListenable(() => {
|
|
412
474
|
const reminaingMs = targetTime - Date.now();
|
|
413
475
|
if (reminaingMs < 0)
|
|
414
476
|
return;
|
|
415
|
-
timeoutId = setTimeout(
|
|
477
|
+
const timeoutId = setTimeout(() => {
|
|
478
|
+
emit();
|
|
479
|
+
}, reminaingMs);
|
|
416
480
|
return () => clearTimeout(timeoutId);
|
|
417
481
|
});
|
|
418
482
|
return new EventEmitter(listen);
|
|
@@ -437,7 +501,7 @@ export class SubjectEmitter extends EventEmitter {
|
|
|
437
501
|
constructor(initial) {
|
|
438
502
|
const { emit, listen } = createListenable();
|
|
439
503
|
super(h => {
|
|
440
|
-
h(this._value);
|
|
504
|
+
h(this._value); // immediate emit on listen
|
|
441
505
|
return listen(h);
|
|
442
506
|
});
|
|
443
507
|
this.emit = emit;
|
|
@@ -461,8 +525,8 @@ export function toEventEmitter(source, eventName) {
|
|
|
461
525
|
if ("addEventListener" in source) {
|
|
462
526
|
if ("removeEventListener" in source && typeof source.removeEventListener == "function") {
|
|
463
527
|
return new EventEmitter(h => {
|
|
464
|
-
|
|
465
|
-
|
|
528
|
+
source.addEventListener(eventName, h);
|
|
529
|
+
return () => source.removeEventListener(eventName, h);
|
|
466
530
|
});
|
|
467
531
|
}
|
|
468
532
|
return new EventEmitter(h => {
|
package/internal/proxy.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SetGetStyleFunc } from "./types";
|
|
1
|
+
import { SetGetStyleFunc, EventSource, EventEmitterMap, EventHandlerMap } from "./types";
|
|
2
2
|
export declare const styleProxy: ProxyHandler<SetGetStyleFunc>;
|
|
3
3
|
export declare const attribsProxy: ProxyHandler<HTMLElement>;
|
|
4
|
-
export declare
|
|
4
|
+
export declare function createEventsProxy<Map>(source: EventSource<any, keyof Map>, initialListeners?: EventHandlerMap<Map>): EventEmitterMap<Map>;
|
package/internal/proxy.js
CHANGED
|
@@ -34,14 +34,16 @@ export const attribsProxy = {
|
|
|
34
34
|
return element.getAttributeNames();
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
|
-
|
|
38
|
-
get: (
|
|
39
|
-
|
|
40
|
-
return (name, handler) => element.addEventListener(name, handler);
|
|
41
|
-
}
|
|
42
|
-
if (key == "removeEventListener") {
|
|
43
|
-
return (name, handler) => element.removeEventListener(name, handler);
|
|
44
|
-
}
|
|
45
|
-
return toEventEmitter(element, key);
|
|
37
|
+
const eventsProxyDefinition = {
|
|
38
|
+
get: (object, key) => {
|
|
39
|
+
return toEventEmitter(object, key);
|
|
46
40
|
}
|
|
47
41
|
};
|
|
42
|
+
export function createEventsProxy(source, initialListeners) {
|
|
43
|
+
const proxy = new Proxy(source, eventsProxyDefinition);
|
|
44
|
+
if (initialListeners) {
|
|
45
|
+
Object.entries(initialListeners)
|
|
46
|
+
.forEach(([name, handler]) => toEventEmitter(source, name).apply(handler));
|
|
47
|
+
}
|
|
48
|
+
return proxy;
|
|
49
|
+
}
|
package/internal/types.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventEmitter
|
|
1
|
+
import { ClassAccessor } from "./element";
|
|
2
|
+
import { EventEmitter } from "./emitter";
|
|
3
3
|
import { entityDataSymbol } from "./util";
|
|
4
4
|
export type ElementClassDescriptor = string | Record<string, boolean | EmitterLike<boolean> | undefined> | undefined | ElementClassDescriptor[];
|
|
5
5
|
export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[];
|
|
6
6
|
export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>;
|
|
7
7
|
export type HTMLTag = keyof HTMLElementTagNameMap;
|
|
8
|
+
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
9
|
+
export type UnsubscribeFunc = () => void;
|
|
8
10
|
export type EmitterLike<T> = {
|
|
9
|
-
subscribe:
|
|
11
|
+
subscribe: ListenFunc<T>;
|
|
10
12
|
} | {
|
|
11
|
-
listen:
|
|
13
|
+
listen: ListenFunc<T>;
|
|
12
14
|
};
|
|
15
|
+
export type EmissionSource<T> = EmitterLike<T> | ListenFunc<T>;
|
|
13
16
|
export type CSSValue = string | number | null | HexCodeContainer;
|
|
14
17
|
export type CSSProperty = keyof StylesDescriptor;
|
|
15
18
|
type HexCodeContainer = {
|
|
@@ -64,7 +67,7 @@ type ElementAPI<T extends HTMLElement> = {
|
|
|
64
67
|
readonly attribs: {
|
|
65
68
|
[key: string]: string | null;
|
|
66
69
|
};
|
|
67
|
-
readonly events:
|
|
70
|
+
readonly events: EventEmitterMap<HTMLElementEventMap>;
|
|
68
71
|
readonly style: StyleAccessor;
|
|
69
72
|
setCSSVariable(variableName: string, value: CSSValue | EmitterLike<CSSValue>): void;
|
|
70
73
|
setCSSVariable(table: Record<string, CSSValue | EmitterLike<CSSValue>>): void;
|
|
@@ -73,6 +76,13 @@ type ElementAPI<T extends HTMLElement> = {
|
|
|
73
76
|
getRect(): DOMRect;
|
|
74
77
|
focus(): void;
|
|
75
78
|
blur(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Add an event listener
|
|
81
|
+
* @param eventId
|
|
82
|
+
* @param handler
|
|
83
|
+
* @returns Function to remove the listener
|
|
84
|
+
* @deprecated Use ent.events
|
|
85
|
+
*/
|
|
76
86
|
on<E extends keyof HTMLElementEventMap>(eventId: E, handler: (this: ElementAPI<T>, data: HTMLElementEventMap[E]) => void): UnsubscribeFunc;
|
|
77
87
|
} & (T extends ContentlessElement ? {} : {
|
|
78
88
|
append(...content: DOMContent[]): void;
|
|
@@ -120,7 +130,7 @@ export type DomHelper = ((
|
|
|
120
130
|
<T extends HTMLElement>(element: T) => DomEntity<T>) & {
|
|
121
131
|
[T in HTMLTag]: (descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>;
|
|
122
132
|
} & {
|
|
123
|
-
[T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]
|
|
133
|
+
[T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (((content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & ((contentEmitter: EmitterLike<DOMContent>) => DomEntity<HTMLElementTagNameMap[T]>));
|
|
124
134
|
});
|
|
125
135
|
type JelEntityData = {
|
|
126
136
|
dom: DOMContent;
|
|
@@ -128,10 +138,28 @@ type JelEntityData = {
|
|
|
128
138
|
export type JelEntity<API extends object | void> = (API extends void ? {} : API) & {
|
|
129
139
|
readonly [entityDataSymbol]: JelEntityData;
|
|
130
140
|
};
|
|
131
|
-
export type
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
export type Handler<T> = (value: T) => void;
|
|
142
|
+
export type Period = {
|
|
143
|
+
asMilliseconds: number;
|
|
144
|
+
} | {
|
|
145
|
+
asSeconds: number;
|
|
146
|
+
};
|
|
147
|
+
export type EventSource<E, N> = {
|
|
148
|
+
on: (eventName: N, handler: Handler<E>) => UnsubscribeFunc;
|
|
149
|
+
} | {
|
|
150
|
+
on: (eventName: N, handler: Handler<E>) => void | UnsubscribeFunc;
|
|
151
|
+
off: (eventName: N, handler: Handler<E>) => void;
|
|
152
|
+
} | {
|
|
153
|
+
addEventListener: (eventName: N, handler: Handler<E>) => UnsubscribeFunc;
|
|
154
|
+
} | {
|
|
155
|
+
addEventListener: (eventName: N, handler: Handler<E>) => void;
|
|
156
|
+
removeEventListener: (eventName: N, handler: Handler<E>) => void;
|
|
157
|
+
};
|
|
158
|
+
export type Dictionary<T> = Record<string | symbol, T>;
|
|
159
|
+
export type EventEmitterMap<Map> = {
|
|
160
|
+
[K in keyof Map]: EventEmitter<Map[K]>;
|
|
161
|
+
};
|
|
162
|
+
export type EventHandlerMap<Map> = {
|
|
163
|
+
[K in keyof Map]?: (value: Map[K]) => void;
|
|
136
164
|
};
|
|
137
165
|
export {};
|