@xtia/jel 0.9.1 → 0.10.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 +47 -29
- package/internal/emitter.js +114 -53
- package/internal/proxy.d.ts +2 -2
- package/internal/proxy.js +11 -9
- package/internal/types.d.ts +35 -9
- 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 } 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, 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, 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,10 +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
|
-
};
|
|
1
|
+
import { Dictionary, EmissionSource, EmitterLike, EventHandlerMap, EventSource, Handler, Period } from "./types";
|
|
8
2
|
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
9
3
|
export type UnsubscribeFunc = () => void;
|
|
10
4
|
export declare class EventEmitter<T> {
|
|
@@ -30,6 +24,7 @@ export declare class EventEmitter<T> {
|
|
|
30
24
|
*/
|
|
31
25
|
map<R>(mapFunc: (value: T) => R): EventEmitter<R>;
|
|
32
26
|
mapAsync<R>(mapFunc: (value: T) => Promise<R>): EventEmitter<R>;
|
|
27
|
+
as<R>(value: R): EventEmitter<R>;
|
|
33
28
|
/**
|
|
34
29
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
35
30
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -90,13 +85,15 @@ export declare class EventEmitter<T> {
|
|
|
90
85
|
throttle(period: Period): EventEmitter<T>;
|
|
91
86
|
batch(ms: number): EventEmitter<T[]>;
|
|
92
87
|
/**
|
|
93
|
-
* Creates a chainable emitter that
|
|
88
|
+
* Creates a chainable emitter that forwards the next emission from the parent
|
|
94
89
|
* **Experimental**: May change in future revisions
|
|
95
90
|
* Note: only listens to the parent while at least one downstream subscription is present
|
|
96
91
|
* @param notifier
|
|
97
92
|
* @returns
|
|
98
93
|
*/
|
|
99
94
|
once(): EventEmitter<T>;
|
|
95
|
+
once(handler: Handler<T>): UnsubscribeFunc;
|
|
96
|
+
getNext(): Promise<T>;
|
|
100
97
|
delay(ms: number): EventEmitter<T>;
|
|
101
98
|
delay(period: Period): EventEmitter<T>;
|
|
102
99
|
scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
|
|
@@ -140,11 +137,38 @@ export declare class EventEmitter<T> {
|
|
|
140
137
|
*/
|
|
141
138
|
or(...emitters: EmitterLike<T>[]): EventEmitter<T>;
|
|
142
139
|
or<U>(...emitters: EmitterLike<U>[]): EventEmitter<T | U>;
|
|
143
|
-
then<R>(handler: (value: T) => R): Promise<R>;
|
|
144
140
|
memo(): Memo<T | undefined>;
|
|
145
141
|
memo(initial: T): Memo<T>;
|
|
146
142
|
memo<U>(initial: U): Memo<T | U>;
|
|
143
|
+
record(): EventRecorder<T>;
|
|
144
|
+
}
|
|
145
|
+
export declare class EventRecorder<T> {
|
|
146
|
+
private startTime;
|
|
147
|
+
private entries;
|
|
148
|
+
private recording;
|
|
149
|
+
private unsubscribe;
|
|
150
|
+
constructor(emitter: EventEmitter<T>);
|
|
151
|
+
private add;
|
|
152
|
+
stop(): EventRecording<T>;
|
|
147
153
|
}
|
|
154
|
+
export declare class EventRecording<T> {
|
|
155
|
+
private _entries;
|
|
156
|
+
constructor(entries: [number, T][]);
|
|
157
|
+
export(): [number, T][];
|
|
158
|
+
play(speed?: number): EventEmitter<T>;
|
|
159
|
+
}
|
|
160
|
+
type EmitEmitterPair<T> = {
|
|
161
|
+
emit: (value: T) => void;
|
|
162
|
+
emitter: EventEmitter<T>;
|
|
163
|
+
};
|
|
164
|
+
type CreateEventSourceOptions<T> = {
|
|
165
|
+
initialHandler?: Handler<T>;
|
|
166
|
+
/**
|
|
167
|
+
* Function to call when subscription count changes from 0
|
|
168
|
+
* Return a *deactivation* function, which will be called when subscription count changes back to 0
|
|
169
|
+
*/
|
|
170
|
+
activate?(): UnsubscribeFunc;
|
|
171
|
+
};
|
|
148
172
|
/**
|
|
149
173
|
* Creates a linked EventEmitter and emit() pair
|
|
150
174
|
* @example
|
|
@@ -176,12 +200,15 @@ export declare class EventEmitter<T> {
|
|
|
176
200
|
* @param initialHandler Optional listener automatically applied to the resulting Emitter
|
|
177
201
|
* @returns
|
|
178
202
|
*/
|
|
179
|
-
export declare function createEventSource<T>(initialHandler?: Handler<T>):
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
export declare function createEventSource<T>(initialHandler?: Handler<T>): EmitEmitterPair<T>;
|
|
204
|
+
export declare function createEventSource<T>(options?: CreateEventSourceOptions<T>): EmitEmitterPair<T>;
|
|
205
|
+
export declare function createEventsSource<Map extends Dictionary<any>>(initialListeners?: EventHandlerMap<Map>): {
|
|
206
|
+
emitters: import("./types").EventEmitterMap<Map>;
|
|
207
|
+
trigger: <K extends keyof Map>(name: K, value: Map[K]) => void;
|
|
182
208
|
};
|
|
183
209
|
export declare function interval(ms: number): EventEmitter<number>;
|
|
184
210
|
export declare function interval(period: Period): EventEmitter<number>;
|
|
211
|
+
export declare const animationFrames: EventEmitter<number>;
|
|
185
212
|
export declare function timeout(ms: number): EventEmitter<void>;
|
|
186
213
|
export declare function timeout(period: Period): EventEmitter<void>;
|
|
187
214
|
declare class Memo<T> {
|
|
@@ -198,26 +225,17 @@ export declare class SubjectEmitter<T> extends EventEmitter<T> {
|
|
|
198
225
|
get value(): T;
|
|
199
226
|
next(value: T): void;
|
|
200
227
|
}
|
|
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
228
|
/**
|
|
213
|
-
* Create an EventEmitter from an event source. Event
|
|
214
|
-
*
|
|
229
|
+
* Create an EventEmitter from an event source. Event source can be RxJS observable, existing `EventEmitter`, an object that
|
|
230
|
+
* provides a `subscribe()`/`listen() => UnsubscribeFunc` method, or a subscribe function itself.
|
|
231
|
+
* @param source
|
|
232
|
+
*/
|
|
233
|
+
export declare function toEventEmitter<E>(source: EmissionSource<E>): EventEmitter<E>;
|
|
234
|
+
/**
|
|
235
|
+
* 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
236
|
* @param source
|
|
216
237
|
*/
|
|
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>;
|
|
238
|
+
export declare function toEventEmitter<E, N>(source: EventSource<E, N>, eventName: N): EventEmitter<E>;
|
|
221
239
|
type ExtractEmitterValue<T> = T extends EmitterLike<infer U> ? U : never;
|
|
222
240
|
type CombinedRecord<T extends Dictionary<EmitterLike<any>>> = {
|
|
223
241
|
readonly [K in keyof T]: ExtractEmitterValue<T[K]>;
|
package/internal/emitter.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createEventsProxy } from "./proxy.js";
|
|
1
2
|
import { isReactiveSource } from "./util";
|
|
3
|
+
const NOOP = () => { };
|
|
2
4
|
function periodAsMilliseconds(t) {
|
|
3
5
|
if (typeof t == "number")
|
|
4
6
|
return t;
|
|
@@ -43,6 +45,10 @@ export class EventEmitter {
|
|
|
43
45
|
const listen = this.transform((value, emit) => mapFunc(value).then(emit));
|
|
44
46
|
return new EventEmitter(listen);
|
|
45
47
|
}
|
|
48
|
+
as(value) {
|
|
49
|
+
const listen = this.transform((_, emit) => emit(value));
|
|
50
|
+
return new EventEmitter(listen);
|
|
51
|
+
}
|
|
46
52
|
/**
|
|
47
53
|
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
48
54
|
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
@@ -152,14 +158,7 @@ export class EventEmitter {
|
|
|
152
158
|
});
|
|
153
159
|
return new EventEmitter(listen);
|
|
154
160
|
}
|
|
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() {
|
|
161
|
+
once(handler) {
|
|
163
162
|
let parentUnsubscribe = null;
|
|
164
163
|
let completed = false;
|
|
165
164
|
const clear = () => {
|
|
@@ -178,11 +177,18 @@ export class EventEmitter {
|
|
|
178
177
|
});
|
|
179
178
|
return clear;
|
|
180
179
|
});
|
|
181
|
-
|
|
180
|
+
const emitter = new EventEmitter(listen);
|
|
181
|
+
return handler
|
|
182
|
+
? emitter.apply(handler)
|
|
183
|
+
: emitter;
|
|
184
|
+
}
|
|
185
|
+
getNext() {
|
|
186
|
+
return new Promise((resolve) => this.once(resolve));
|
|
182
187
|
}
|
|
183
188
|
delay(t) {
|
|
189
|
+
const ms = periodAsMilliseconds(t);
|
|
184
190
|
return new EventEmitter(this.transform((value, emit) => {
|
|
185
|
-
|
|
191
|
+
return timeout(ms).apply(() => emit(value));
|
|
186
192
|
}));
|
|
187
193
|
}
|
|
188
194
|
scan(updater, initial) {
|
|
@@ -322,48 +328,63 @@ export class EventEmitter {
|
|
|
322
328
|
return () => unsubs.forEach(unsub => unsub());
|
|
323
329
|
});
|
|
324
330
|
}
|
|
325
|
-
then(handler) {
|
|
326
|
-
return new Promise(resolve => {
|
|
327
|
-
this.once().apply(v => resolve(handler(v)));
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
331
|
memo(initial) {
|
|
331
332
|
return new Memo(this, initial);
|
|
332
333
|
}
|
|
334
|
+
record() {
|
|
335
|
+
return new EventRecorder(this);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export class EventRecorder {
|
|
339
|
+
constructor(emitter) {
|
|
340
|
+
this.startTime = performance.now();
|
|
341
|
+
this.entries = [];
|
|
342
|
+
this.recording = true;
|
|
343
|
+
this.unsubscribe = emitter.listen(v => this.add(v));
|
|
344
|
+
}
|
|
345
|
+
add(value) {
|
|
346
|
+
const now = performance.now();
|
|
347
|
+
let time = now - this.startTime;
|
|
348
|
+
this.entries.push([time, value]);
|
|
349
|
+
}
|
|
350
|
+
stop() {
|
|
351
|
+
if (!this.recording) {
|
|
352
|
+
throw new Error("EventRecorder already stopped");
|
|
353
|
+
}
|
|
354
|
+
this.unsubscribe();
|
|
355
|
+
return new EventRecording(this.entries);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export class EventRecording {
|
|
359
|
+
constructor(entries) {
|
|
360
|
+
this._entries = entries;
|
|
361
|
+
}
|
|
362
|
+
export() {
|
|
363
|
+
return [...this._entries];
|
|
364
|
+
}
|
|
365
|
+
play(speed = 1) {
|
|
366
|
+
let idx = 0;
|
|
367
|
+
let elapsed = 0;
|
|
368
|
+
const { emit, listen } = createListenable();
|
|
369
|
+
const unsubscribe = animationFrames.listen((frameElapsed) => {
|
|
370
|
+
elapsed += frameElapsed * speed;
|
|
371
|
+
while (idx < this._entries.length && this._entries[idx][0] <= elapsed) {
|
|
372
|
+
emit(this._entries[idx][1]);
|
|
373
|
+
idx++;
|
|
374
|
+
}
|
|
375
|
+
if (idx >= this._entries.length) {
|
|
376
|
+
unsubscribe();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return new EventEmitter(listen);
|
|
380
|
+
}
|
|
333
381
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
* const form = $.form({
|
|
341
|
-
* on: {
|
|
342
|
-
* submit: (e) => {
|
|
343
|
-
* e.preventDefault();
|
|
344
|
-
* const data = new FormData(e.target);
|
|
345
|
-
* submitEvents.emit(data); // emit when form is submitted
|
|
346
|
-
* }
|
|
347
|
-
* }
|
|
348
|
-
* });
|
|
349
|
-
*
|
|
350
|
-
* return createEntity(form, {
|
|
351
|
-
* events: {
|
|
352
|
-
* submit: submitEvents.emitter
|
|
353
|
-
* }
|
|
354
|
-
* })
|
|
355
|
-
* }
|
|
356
|
-
*
|
|
357
|
-
* const form = createForm({
|
|
358
|
-
* onsubmit: (data) => handleSubmission(data)
|
|
359
|
-
* });
|
|
360
|
-
* ```
|
|
361
|
-
*
|
|
362
|
-
* @param initialHandler Optional listener automatically applied to the resulting Emitter
|
|
363
|
-
* @returns
|
|
364
|
-
*/
|
|
365
|
-
export function createEventSource(initialHandler) {
|
|
366
|
-
const { emit, listen } = createListenable();
|
|
382
|
+
export function createEventSource(arg) {
|
|
383
|
+
if (typeof arg === "function") {
|
|
384
|
+
arg = { initialHandler: arg };
|
|
385
|
+
}
|
|
386
|
+
const { initialHandler, activate } = arg !== null && arg !== void 0 ? arg : {};
|
|
387
|
+
const { emit, listen } = createListenable(activate);
|
|
367
388
|
if (initialHandler)
|
|
368
389
|
listen(initialHandler);
|
|
369
390
|
return {
|
|
@@ -371,6 +392,30 @@ export function createEventSource(initialHandler) {
|
|
|
371
392
|
emitter: new EventEmitter(listen)
|
|
372
393
|
};
|
|
373
394
|
}
|
|
395
|
+
export function createEventsSource(initialListeners) {
|
|
396
|
+
const handlers = {};
|
|
397
|
+
const emitters = createEventsProxy({
|
|
398
|
+
on: (name, handler) => {
|
|
399
|
+
if (!handlers[name])
|
|
400
|
+
handlers[name] = [];
|
|
401
|
+
const unique = { fn: handler };
|
|
402
|
+
handlers[name].push(unique);
|
|
403
|
+
return () => {
|
|
404
|
+
const idx = handlers[name].indexOf(unique);
|
|
405
|
+
handlers[name].splice(idx, 1);
|
|
406
|
+
if (handlers[name].length == 0)
|
|
407
|
+
delete handlers[name];
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
}, initialListeners);
|
|
411
|
+
return {
|
|
412
|
+
emitters,
|
|
413
|
+
trigger: (name, value) => {
|
|
414
|
+
var _a;
|
|
415
|
+
(_a = handlers[name]) === null || _a === void 0 ? void 0 : _a.forEach(entry => entry.fn(value));
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
374
419
|
function createListenable(sourceListen) {
|
|
375
420
|
const handlers = [];
|
|
376
421
|
let onRemoveLast;
|
|
@@ -404,15 +449,31 @@ export function interval(t) {
|
|
|
404
449
|
});
|
|
405
450
|
return new EventEmitter(listen);
|
|
406
451
|
}
|
|
452
|
+
export const animationFrames = (() => {
|
|
453
|
+
const { emit, listen } = createListenable(() => {
|
|
454
|
+
let rafId = null;
|
|
455
|
+
let lastTime = null;
|
|
456
|
+
const frame = (time) => {
|
|
457
|
+
rafId = requestAnimationFrame(frame);
|
|
458
|
+
const elapsed = time - lastTime;
|
|
459
|
+
emit(elapsed);
|
|
460
|
+
};
|
|
461
|
+
lastTime = performance.now();
|
|
462
|
+
rafId = requestAnimationFrame(frame);
|
|
463
|
+
return () => cancelAnimationFrame(rafId);
|
|
464
|
+
});
|
|
465
|
+
return new EventEmitter(listen);
|
|
466
|
+
})();
|
|
407
467
|
export function timeout(t) {
|
|
408
468
|
const ms = periodAsMilliseconds(t);
|
|
409
469
|
const targetTime = Date.now() + ms;
|
|
410
|
-
let timeoutId = null;
|
|
411
470
|
const { emit, listen } = createListenable(() => {
|
|
412
471
|
const reminaingMs = targetTime - Date.now();
|
|
413
472
|
if (reminaingMs < 0)
|
|
414
473
|
return;
|
|
415
|
-
timeoutId = setTimeout(
|
|
474
|
+
const timeoutId = setTimeout(() => {
|
|
475
|
+
emit();
|
|
476
|
+
}, reminaingMs);
|
|
416
477
|
return () => clearTimeout(timeoutId);
|
|
417
478
|
});
|
|
418
479
|
return new EventEmitter(listen);
|
|
@@ -437,7 +498,7 @@ export class SubjectEmitter extends EventEmitter {
|
|
|
437
498
|
constructor(initial) {
|
|
438
499
|
const { emit, listen } = createListenable();
|
|
439
500
|
super(h => {
|
|
440
|
-
h(this._value);
|
|
501
|
+
h(this._value); // immediate emit on listen
|
|
441
502
|
return listen(h);
|
|
442
503
|
});
|
|
443
504
|
this.emit = emit;
|
|
@@ -461,8 +522,8 @@ export function toEventEmitter(source, eventName) {
|
|
|
461
522
|
if ("addEventListener" in source) {
|
|
462
523
|
if ("removeEventListener" in source && typeof source.removeEventListener == "function") {
|
|
463
524
|
return new EventEmitter(h => {
|
|
464
|
-
|
|
465
|
-
|
|
525
|
+
source.addEventListener(eventName, h);
|
|
526
|
+
return () => source.removeEventListener(eventName, h);
|
|
466
527
|
});
|
|
467
528
|
}
|
|
468
529
|
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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventEmitter, UnsubscribeFunc } from "./emitter";
|
|
1
|
+
import { ClassAccessor } from "./element";
|
|
2
|
+
import { EventEmitter, ListenFunc, UnsubscribeFunc } 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[];
|
|
@@ -10,6 +10,7 @@ export type EmitterLike<T> = {
|
|
|
10
10
|
} | {
|
|
11
11
|
listen: (callback: (value: T) => void) => UnsubscribeFunc;
|
|
12
12
|
};
|
|
13
|
+
export type EmissionSource<T> = EmitterLike<T> | ListenFunc<T>;
|
|
13
14
|
export type CSSValue = string | number | null | HexCodeContainer;
|
|
14
15
|
export type CSSProperty = keyof StylesDescriptor;
|
|
15
16
|
type HexCodeContainer = {
|
|
@@ -64,7 +65,7 @@ type ElementAPI<T extends HTMLElement> = {
|
|
|
64
65
|
readonly attribs: {
|
|
65
66
|
[key: string]: string | null;
|
|
66
67
|
};
|
|
67
|
-
readonly events:
|
|
68
|
+
readonly events: EventEmitterMap<HTMLElementEventMap>;
|
|
68
69
|
readonly style: StyleAccessor;
|
|
69
70
|
setCSSVariable(variableName: string, value: CSSValue | EmitterLike<CSSValue>): void;
|
|
70
71
|
setCSSVariable(table: Record<string, CSSValue | EmitterLike<CSSValue>>): void;
|
|
@@ -73,6 +74,13 @@ type ElementAPI<T extends HTMLElement> = {
|
|
|
73
74
|
getRect(): DOMRect;
|
|
74
75
|
focus(): void;
|
|
75
76
|
blur(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Add an event listener
|
|
79
|
+
* @param eventId
|
|
80
|
+
* @param handler
|
|
81
|
+
* @returns Function to remove the listener
|
|
82
|
+
* @deprecated Use ent.events
|
|
83
|
+
*/
|
|
76
84
|
on<E extends keyof HTMLElementEventMap>(eventId: E, handler: (this: ElementAPI<T>, data: HTMLElementEventMap[E]) => void): UnsubscribeFunc;
|
|
77
85
|
} & (T extends ContentlessElement ? {} : {
|
|
78
86
|
append(...content: DOMContent[]): void;
|
|
@@ -120,7 +128,7 @@ export type DomHelper = ((
|
|
|
120
128
|
<T extends HTMLElement>(element: T) => DomEntity<T>) & {
|
|
121
129
|
[T in HTMLTag]: (descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>;
|
|
122
130
|
} & {
|
|
123
|
-
[T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]
|
|
131
|
+
[T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (((content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & ((contentEmitter: EmitterLike<DOMContent>) => DomEntity<HTMLElementTagNameMap[T]>));
|
|
124
132
|
});
|
|
125
133
|
type JelEntityData = {
|
|
126
134
|
dom: DOMContent;
|
|
@@ -128,10 +136,28 @@ type JelEntityData = {
|
|
|
128
136
|
export type JelEntity<API extends object | void> = (API extends void ? {} : API) & {
|
|
129
137
|
readonly [entityDataSymbol]: JelEntityData;
|
|
130
138
|
};
|
|
131
|
-
export type
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
export type Handler<T> = (value: T) => void;
|
|
140
|
+
export type Period = {
|
|
141
|
+
asMilliseconds: number;
|
|
142
|
+
} | {
|
|
143
|
+
asSeconds: number;
|
|
144
|
+
};
|
|
145
|
+
export type EventSource<E, N> = {
|
|
146
|
+
on: (eventName: N, handler: Handler<E>) => UnsubscribeFunc;
|
|
147
|
+
} | {
|
|
148
|
+
on: (eventName: N, handler: Handler<E>) => void | UnsubscribeFunc;
|
|
149
|
+
off: (eventName: N, handler: Handler<E>) => void;
|
|
150
|
+
} | {
|
|
151
|
+
addEventListener: (eventName: N, handler: Handler<E>) => UnsubscribeFunc;
|
|
152
|
+
} | {
|
|
153
|
+
addEventListener: (eventName: N, handler: Handler<E>) => void;
|
|
154
|
+
removeEventListener: (eventName: N, handler: Handler<E>) => void;
|
|
155
|
+
};
|
|
156
|
+
export type Dictionary<T> = Record<string | symbol, T>;
|
|
157
|
+
export type EventEmitterMap<Map> = {
|
|
158
|
+
[K in keyof Map]: EventEmitter<Map[K]>;
|
|
159
|
+
};
|
|
160
|
+
export type EventHandlerMap<Map> = {
|
|
161
|
+
[K in keyof Map]?: (value: Map[K]) => void;
|
|
136
162
|
};
|
|
137
163
|
export {};
|