@xtia/jel 0.7.2 → 0.9.1
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/index.d.ts +3 -2
- package/index.js +2 -2
- package/internal/emitter.d.ts +50 -16
- package/internal/emitter.js +83 -13
- package/internal/types.d.ts +1 -1
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity } from "./internal/types";
|
|
2
2
|
import { $ } from "./internal/element";
|
|
3
|
+
import { DomEntity } from "./internal/types";
|
|
3
4
|
export { createEntity } from "./internal/util";
|
|
4
|
-
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter } from "./internal/emitter";
|
|
5
|
+
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, type EventEmitter, combineEmitters } from "./internal/emitter";
|
|
5
6
|
export { $ };
|
|
6
|
-
export declare const $body:
|
|
7
|
+
export declare const $body: DomEntity<HTMLElement>;
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $ } from "./internal/element";
|
|
2
2
|
export { createEntity } from "./internal/util";
|
|
3
|
-
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter } from "./internal/emitter";
|
|
3
|
+
export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, combineEmitters } from "./internal/emitter";
|
|
4
4
|
export { $ };
|
|
5
|
-
export const $body = $(document.body);
|
|
5
|
+
export const $body = "document" in globalThis ? $(document.body) : undefined;
|
package/internal/emitter.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { EmitterLike } from "./types";
|
|
2
2
|
type Handler<T> = (value: T) => void;
|
|
3
|
+
type Period = {
|
|
4
|
+
asMilliseconds: number;
|
|
5
|
+
} | {
|
|
6
|
+
asSeconds: number;
|
|
7
|
+
};
|
|
3
8
|
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
4
9
|
export type UnsubscribeFunc = () => void;
|
|
5
10
|
export declare class EventEmitter<T> {
|
|
@@ -24,6 +29,7 @@ export declare class EventEmitter<T> {
|
|
|
24
29
|
* @returns Listenable: emits transformed values
|
|
25
30
|
*/
|
|
26
31
|
map<R>(mapFunc: (value: T) => R): EventEmitter<R>;
|
|
32
|
+
mapAsync<R>(mapFunc: (value: T) => Promise<R>): EventEmitter<R>;
|
|
27
33
|
/**
|
|
28
34
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
29
35
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -68,8 +74,20 @@ export declare class EventEmitter<T> {
|
|
|
68
74
|
* @param cb
|
|
69
75
|
*/
|
|
70
76
|
fork(...cb: ((branch: this) => void)[]): this;
|
|
77
|
+
/**
|
|
78
|
+
* Creates a chainable emitter that forwards the parent's last emission after a period of time in which the parent doesn't emit
|
|
79
|
+
* @param ms Delay in milliseconds
|
|
80
|
+
* @returns Debounced emitter
|
|
81
|
+
*/
|
|
71
82
|
debounce(ms: number): EventEmitter<T>;
|
|
83
|
+
debounce(period: Period): EventEmitter<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Creates a chainable emitter that forwards the parent's emissions, with a minimum delay between emissions during which parent emssions are ignored
|
|
86
|
+
* @param ms Delay in milliseconds
|
|
87
|
+
* @returns Throttled emitter
|
|
88
|
+
*/
|
|
72
89
|
throttle(ms: number): EventEmitter<T>;
|
|
90
|
+
throttle(period: Period): EventEmitter<T>;
|
|
73
91
|
batch(ms: number): EventEmitter<T[]>;
|
|
74
92
|
/**
|
|
75
93
|
* Creates a chainable emitter that
|
|
@@ -80,6 +98,7 @@ export declare class EventEmitter<T> {
|
|
|
80
98
|
*/
|
|
81
99
|
once(): EventEmitter<T>;
|
|
82
100
|
delay(ms: number): EventEmitter<T>;
|
|
101
|
+
delay(period: Period): EventEmitter<T>;
|
|
83
102
|
scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
|
|
84
103
|
buffer(count: number): EventEmitter<T[]>;
|
|
85
104
|
/**
|
|
@@ -121,6 +140,10 @@ export declare class EventEmitter<T> {
|
|
|
121
140
|
*/
|
|
122
141
|
or(...emitters: EmitterLike<T>[]): EventEmitter<T>;
|
|
123
142
|
or<U>(...emitters: EmitterLike<U>[]): EventEmitter<T | U>;
|
|
143
|
+
then<R>(handler: (value: T) => R): Promise<R>;
|
|
144
|
+
memo(): Memo<T | undefined>;
|
|
145
|
+
memo(initial: T): Memo<T>;
|
|
146
|
+
memo<U>(initial: U): Memo<T | U>;
|
|
124
147
|
}
|
|
125
148
|
/**
|
|
126
149
|
* Creates a linked EventEmitter and emit() pair
|
|
@@ -157,16 +180,17 @@ export declare function createEventSource<T>(initialHandler?: Handler<T>): {
|
|
|
157
180
|
emit: (value: T) => void;
|
|
158
181
|
emitter: EventEmitter<T>;
|
|
159
182
|
};
|
|
160
|
-
export declare function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
183
|
+
export declare function interval(ms: number): EventEmitter<number>;
|
|
184
|
+
export declare function interval(period: Period): EventEmitter<number>;
|
|
185
|
+
export declare function timeout(ms: number): EventEmitter<void>;
|
|
186
|
+
export declare function timeout(period: Period): EventEmitter<void>;
|
|
187
|
+
declare class Memo<T> {
|
|
188
|
+
private _value;
|
|
189
|
+
private unsubscribeFunc;
|
|
190
|
+
get value(): T;
|
|
191
|
+
constructor(source: EmitterLike<T>, initial: T);
|
|
192
|
+
dispose(): void;
|
|
193
|
+
}
|
|
170
194
|
export declare class SubjectEmitter<T> extends EventEmitter<T> {
|
|
171
195
|
private emit;
|
|
172
196
|
private _value;
|
|
@@ -175,15 +199,15 @@ export declare class SubjectEmitter<T> extends EventEmitter<T> {
|
|
|
175
199
|
next(value: T): void;
|
|
176
200
|
}
|
|
177
201
|
type EventSource<T, E extends string> = {
|
|
178
|
-
on: (eventName: E, handler:
|
|
202
|
+
on: (eventName: E, handler: Handler<T>) => UnsubscribeFunc;
|
|
179
203
|
} | {
|
|
180
|
-
on: (eventName: E, handler:
|
|
181
|
-
off: (eventName: E, handler:
|
|
204
|
+
on: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc;
|
|
205
|
+
off: (eventName: E, handler: Handler<T>) => void;
|
|
182
206
|
} | {
|
|
183
|
-
addEventListener: (eventName: E, handler:
|
|
207
|
+
addEventListener: (eventName: E, handler: Handler<T>) => UnsubscribeFunc;
|
|
184
208
|
} | {
|
|
185
|
-
addEventListener: (eventName: E, handler:
|
|
186
|
-
removeEventListener: (eventName: E, handler:
|
|
209
|
+
addEventListener: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc;
|
|
210
|
+
removeEventListener: (eventName: E, handler: Handler<T>) => void;
|
|
187
211
|
};
|
|
188
212
|
/**
|
|
189
213
|
* Create an EventEmitter from an event source. Event sources can be RxJS observables, existing EventEmitters, or objects that
|
|
@@ -192,4 +216,14 @@ type EventSource<T, E extends string> = {
|
|
|
192
216
|
*/
|
|
193
217
|
export declare function toEventEmitter<T>(source: EmitterLike<T>): EventEmitter<T>;
|
|
194
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>;
|
|
221
|
+
type ExtractEmitterValue<T> = T extends EmitterLike<infer U> ? U : never;
|
|
222
|
+
type CombinedRecord<T extends Dictionary<EmitterLike<any>>> = {
|
|
223
|
+
readonly [K in keyof T]: ExtractEmitterValue<T[K]>;
|
|
224
|
+
};
|
|
225
|
+
export declare function combineEmitters<U extends Dictionary<EmitterLike<any>>>(emitters: U): EventEmitter<CombinedRecord<U>>;
|
|
226
|
+
export declare function combineEmitters<U extends EmitterLike<any>[]>(emitters: [...U]): EventEmitter<{
|
|
227
|
+
[K in keyof U]: ExtractEmitterValue<U[K]>;
|
|
228
|
+
}>;
|
|
195
229
|
export {};
|
package/internal/emitter.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { isReactiveSource } from "./util";
|
|
2
|
+
function periodAsMilliseconds(t) {
|
|
3
|
+
if (typeof t == "number")
|
|
4
|
+
return t;
|
|
5
|
+
return "asMilliseconds" in t ? t.asMilliseconds : (t.asSeconds * 1000);
|
|
6
|
+
}
|
|
2
7
|
export class EventEmitter {
|
|
3
8
|
constructor(onListen) {
|
|
4
9
|
this.onListen = onListen;
|
|
@@ -34,6 +39,10 @@ export class EventEmitter {
|
|
|
34
39
|
const listen = this.transform((value, emit) => emit(mapFunc(value)));
|
|
35
40
|
return new EventEmitter(listen);
|
|
36
41
|
}
|
|
42
|
+
mapAsync(mapFunc) {
|
|
43
|
+
const listen = this.transform((value, emit) => mapFunc(value).then(emit));
|
|
44
|
+
return new EventEmitter(listen);
|
|
45
|
+
}
|
|
37
46
|
/**
|
|
38
47
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
39
48
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -101,14 +110,14 @@ export class EventEmitter {
|
|
|
101
110
|
cb.forEach(cb => cb(this));
|
|
102
111
|
return this;
|
|
103
112
|
}
|
|
104
|
-
debounce(
|
|
113
|
+
debounce(t) {
|
|
105
114
|
let reset = null;
|
|
106
115
|
const listen = this.transform((value, emit) => {
|
|
107
116
|
reset === null || reset === void 0 ? void 0 : reset();
|
|
108
117
|
const timeout = setTimeout(() => {
|
|
109
118
|
reset = null;
|
|
110
119
|
emit(value);
|
|
111
|
-
},
|
|
120
|
+
}, periodAsMilliseconds(t));
|
|
112
121
|
reset = () => {
|
|
113
122
|
reset = null;
|
|
114
123
|
clearTimeout(timeout);
|
|
@@ -116,11 +125,11 @@ export class EventEmitter {
|
|
|
116
125
|
});
|
|
117
126
|
return new EventEmitter(listen);
|
|
118
127
|
}
|
|
119
|
-
throttle(
|
|
128
|
+
throttle(t) {
|
|
120
129
|
let lastTime = -Infinity;
|
|
121
130
|
const listen = this.transform((value, emit) => {
|
|
122
131
|
const now = performance.now();
|
|
123
|
-
if (now >= lastTime +
|
|
132
|
+
if (now >= lastTime + periodAsMilliseconds(t)) {
|
|
124
133
|
lastTime = now;
|
|
125
134
|
emit(value);
|
|
126
135
|
}
|
|
@@ -171,9 +180,9 @@ export class EventEmitter {
|
|
|
171
180
|
});
|
|
172
181
|
return new EventEmitter(listen);
|
|
173
182
|
}
|
|
174
|
-
delay(
|
|
183
|
+
delay(t) {
|
|
175
184
|
return new EventEmitter(this.transform((value, emit) => {
|
|
176
|
-
setTimeout(() => emit(value),
|
|
185
|
+
setTimeout(() => emit(value), periodAsMilliseconds(t));
|
|
177
186
|
}));
|
|
178
187
|
}
|
|
179
188
|
scan(updater, initial) {
|
|
@@ -245,11 +254,10 @@ export class EventEmitter {
|
|
|
245
254
|
if (completed)
|
|
246
255
|
return;
|
|
247
256
|
parentUnsubscribe = this.apply(emit);
|
|
248
|
-
|
|
257
|
+
notifierUnsub = toEventEmitter(notifier).listen(() => {
|
|
249
258
|
completed = true;
|
|
250
259
|
clear();
|
|
251
|
-
};
|
|
252
|
-
notifierUnsub = toEventEmitter(notifier).listen(handler);
|
|
260
|
+
});
|
|
253
261
|
return clear;
|
|
254
262
|
});
|
|
255
263
|
return new EventEmitter(listen);
|
|
@@ -314,6 +322,14 @@ export class EventEmitter {
|
|
|
314
322
|
return () => unsubs.forEach(unsub => unsub());
|
|
315
323
|
});
|
|
316
324
|
}
|
|
325
|
+
then(handler) {
|
|
326
|
+
return new Promise(resolve => {
|
|
327
|
+
this.once().apply(v => resolve(handler(v)));
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
memo(initial) {
|
|
331
|
+
return new Memo(this, initial);
|
|
332
|
+
}
|
|
317
333
|
}
|
|
318
334
|
/**
|
|
319
335
|
* Creates a linked EventEmitter and emit() pair
|
|
@@ -355,7 +371,7 @@ export function createEventSource(initialHandler) {
|
|
|
355
371
|
emitter: new EventEmitter(listen)
|
|
356
372
|
};
|
|
357
373
|
}
|
|
358
|
-
|
|
374
|
+
function createListenable(sourceListen) {
|
|
359
375
|
const handlers = [];
|
|
360
376
|
let onRemoveLast;
|
|
361
377
|
const addListener = (fn) => {
|
|
@@ -377,19 +393,19 @@ export function createListenable(sourceListen) {
|
|
|
377
393
|
emit: (value) => handlers.forEach(h => h.fn(value)),
|
|
378
394
|
};
|
|
379
395
|
}
|
|
380
|
-
export function interval(
|
|
396
|
+
export function interval(t) {
|
|
381
397
|
let intervalId = null;
|
|
382
398
|
let idx = 0;
|
|
383
399
|
const { emit, listen } = createListenable(() => {
|
|
384
400
|
intervalId = setInterval(() => {
|
|
385
401
|
emit(idx++);
|
|
386
|
-
},
|
|
402
|
+
}, periodAsMilliseconds(t));
|
|
387
403
|
return () => clearInterval(intervalId);
|
|
388
404
|
});
|
|
389
405
|
return new EventEmitter(listen);
|
|
390
406
|
}
|
|
391
407
|
export function timeout(t) {
|
|
392
|
-
const ms =
|
|
408
|
+
const ms = periodAsMilliseconds(t);
|
|
393
409
|
const targetTime = Date.now() + ms;
|
|
394
410
|
let timeoutId = null;
|
|
395
411
|
const { emit, listen } = createListenable(() => {
|
|
@@ -401,6 +417,22 @@ export function timeout(t) {
|
|
|
401
417
|
});
|
|
402
418
|
return new EventEmitter(listen);
|
|
403
419
|
}
|
|
420
|
+
class Memo {
|
|
421
|
+
get value() {
|
|
422
|
+
return this._value;
|
|
423
|
+
}
|
|
424
|
+
constructor(source, initial) {
|
|
425
|
+
this._value = initial;
|
|
426
|
+
const emitter = toEventEmitter(source);
|
|
427
|
+
this.unsubscribeFunc = emitter.listen(v => this._value = v);
|
|
428
|
+
}
|
|
429
|
+
dispose() {
|
|
430
|
+
this.unsubscribeFunc();
|
|
431
|
+
this.unsubscribeFunc = () => {
|
|
432
|
+
throw new Error("Memo object already disposed");
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
404
436
|
export class SubjectEmitter extends EventEmitter {
|
|
405
437
|
constructor(initial) {
|
|
406
438
|
const { emit, listen } = createListenable();
|
|
@@ -422,6 +454,8 @@ export class SubjectEmitter extends EventEmitter {
|
|
|
422
454
|
export function toEventEmitter(source, eventName) {
|
|
423
455
|
if (source instanceof EventEmitter)
|
|
424
456
|
return source;
|
|
457
|
+
if (typeof source == "function")
|
|
458
|
+
return new EventEmitter(source);
|
|
425
459
|
if (eventName !== undefined) {
|
|
426
460
|
// addEL()
|
|
427
461
|
if ("addEventListener" in source) {
|
|
@@ -456,3 +490,39 @@ export function toEventEmitter(source, eventName) {
|
|
|
456
490
|
}
|
|
457
491
|
throw new Error("Invalid event source");
|
|
458
492
|
}
|
|
493
|
+
function combineArray(emitters) {
|
|
494
|
+
let values = Array.from({ length: emitters.length });
|
|
495
|
+
const { emit, listen } = createListenable(() => {
|
|
496
|
+
const unsubFuncs = emitters.map((emitter, idx) => {
|
|
497
|
+
return emitter.listen(v => {
|
|
498
|
+
values[idx] = { value: v };
|
|
499
|
+
if (values.every(v => v !== undefined))
|
|
500
|
+
emit(values.map(vc => vc.value));
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
return () => unsubFuncs.forEach(f => f());
|
|
504
|
+
});
|
|
505
|
+
return new EventEmitter(listen);
|
|
506
|
+
}
|
|
507
|
+
function combineRecord(emitters) {
|
|
508
|
+
const keys = Object.keys(emitters);
|
|
509
|
+
let values = {};
|
|
510
|
+
const { emit, listen } = createListenable(() => {
|
|
511
|
+
const unsubFuncs = keys.map(key => {
|
|
512
|
+
return emitters[key].listen(v => {
|
|
513
|
+
values[key] = { value: v };
|
|
514
|
+
if (keys.every(k => values[k] !== undefined)) {
|
|
515
|
+
const record = Object.fromEntries(Object.entries(values).map(([k, vc]) => [k, vc.value]));
|
|
516
|
+
emit(record);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
return () => unsubFuncs.forEach(f => f());
|
|
521
|
+
});
|
|
522
|
+
return new EventEmitter(listen);
|
|
523
|
+
}
|
|
524
|
+
export function combineEmitters(emitters) {
|
|
525
|
+
if (Array.isArray(emitters))
|
|
526
|
+
return combineArray(emitters.map(toEventEmitter));
|
|
527
|
+
return combineRecord(Object.fromEntries(Object.entries(emitters).map(([k, e]) => [k, toEventEmitter(e)])));
|
|
528
|
+
}
|
package/internal/types.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ type TagWithName = 'input' | 'textarea' | 'select' | 'form';
|
|
|
35
35
|
type ContentlessElement = HTMLElementTagNameMap[ContentlessTag];
|
|
36
36
|
export type ElementDescriptor<Tag extends HTMLTag> = {
|
|
37
37
|
classes?: ElementClassDescriptor;
|
|
38
|
-
attribs?: Record<string, string | number | boolean>;
|
|
38
|
+
attribs?: Record<string, string | number | boolean | undefined>;
|
|
39
39
|
on?: {
|
|
40
40
|
[E in keyof HTMLElementEventMap]+?: (event: HTMLElementEventMap[E]) => void;
|
|
41
41
|
};
|