@xtia/jel 0.5.1 → 0.6.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 +1 -1
- package/index.js +1 -1
- package/internal/element.js +76 -1
- package/internal/emitter.d.ts +64 -2
- package/internal/emitter.js +116 -12
- package/internal/proxy.d.ts +2 -1
- package/internal/proxy.js +9 -10
- package/internal/types.d.ts +13 -5
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity } from "./internal/types";
|
|
2
2
|
export { $ } from "./internal/element";
|
|
3
3
|
export { createEntity } from "./internal/util";
|
|
4
|
-
export { createEventSource } from "./internal/emitter";
|
|
4
|
+
export { createEventSource, interval } from "./internal/emitter";
|
package/index.js
CHANGED
package/internal/element.js
CHANGED
|
@@ -97,6 +97,30 @@ export const $ = new Proxy(createElement, {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
|
+
const elementMutationMap = new WeakMap();
|
|
101
|
+
let mutationObserver = null;
|
|
102
|
+
function observeMutations() {
|
|
103
|
+
if (mutationObserver !== null)
|
|
104
|
+
return;
|
|
105
|
+
mutationObserver = new MutationObserver((mutations) => {
|
|
106
|
+
mutations.forEach(mut => {
|
|
107
|
+
mut.addedNodes.forEach(node => {
|
|
108
|
+
if (elementMutationMap.has(node)) {
|
|
109
|
+
elementMutationMap.get(node).add();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
mut.removedNodes.forEach(node => {
|
|
113
|
+
if (elementMutationMap.has(node)) {
|
|
114
|
+
elementMutationMap.get(node).remove();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
mutationObserver.observe(document.body, {
|
|
120
|
+
childList: true,
|
|
121
|
+
subtree: true
|
|
122
|
+
});
|
|
123
|
+
}
|
|
100
124
|
function getWrappedElement(element) {
|
|
101
125
|
if (!elementWrapCache.has(element)) {
|
|
102
126
|
const setCSSVariable = (k, v) => {
|
|
@@ -107,6 +131,57 @@ function getWrappedElement(element) {
|
|
|
107
131
|
element.style.setProperty("--" + k, v);
|
|
108
132
|
}
|
|
109
133
|
};
|
|
134
|
+
const styleListeners = {};
|
|
135
|
+
function addStyleListener(prop, source) {
|
|
136
|
+
const subscribe = "subscribe" in source
|
|
137
|
+
? () => source.subscribe(v => element.style[prop] = v)
|
|
138
|
+
: () => source.listen(v => element.style[prop] = v);
|
|
139
|
+
styleListeners[prop] = {
|
|
140
|
+
subscribe,
|
|
141
|
+
unsubscribe: element.isConnected ? subscribe() : null,
|
|
142
|
+
};
|
|
143
|
+
if (!elementMutationMap.has(element)) {
|
|
144
|
+
elementMutationMap.set(element, {
|
|
145
|
+
add: () => {
|
|
146
|
+
Object.values(styleListeners).forEach(l => { var _a; return l.unsubscribe = (_a = l.subscribe) === null || _a === void 0 ? void 0 : _a.call(l); });
|
|
147
|
+
},
|
|
148
|
+
remove: () => {
|
|
149
|
+
Object.values(styleListeners).forEach(l => {
|
|
150
|
+
var _a;
|
|
151
|
+
(_a = l.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(l);
|
|
152
|
+
l.unsubscribe = null;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
observeMutations();
|
|
158
|
+
}
|
|
159
|
+
function removeStyleListener(prop) {
|
|
160
|
+
if (styleListeners[prop].unsubscribe) {
|
|
161
|
+
styleListeners[prop].unsubscribe();
|
|
162
|
+
}
|
|
163
|
+
delete styleListeners[prop];
|
|
164
|
+
if (Object.keys(styleListeners).length == 0) {
|
|
165
|
+
elementMutationMap.delete(element);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function setStyle(prop, value) {
|
|
169
|
+
if (styleListeners[prop])
|
|
170
|
+
removeStyleListener(prop);
|
|
171
|
+
if (typeof value == "object" && value) {
|
|
172
|
+
if ("listen" in value || "subscribe" in value) {
|
|
173
|
+
addStyleListener(prop, value);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
value = value.toString();
|
|
177
|
+
}
|
|
178
|
+
if (value === undefined) {
|
|
179
|
+
return prop in styleListeners
|
|
180
|
+
? styleListeners[prop].subscribe
|
|
181
|
+
: element.style[prop];
|
|
182
|
+
}
|
|
183
|
+
element.style[prop] = value;
|
|
184
|
+
}
|
|
110
185
|
const domEntity = {
|
|
111
186
|
[entityDataSymbol]: {
|
|
112
187
|
dom: element,
|
|
@@ -210,7 +285,7 @@ function getWrappedElement(element) {
|
|
|
210
285
|
element.setAttribute("name", v);
|
|
211
286
|
}
|
|
212
287
|
},
|
|
213
|
-
style: new Proxy(
|
|
288
|
+
style: new Proxy(setStyle, styleProxy),
|
|
214
289
|
classes: element.classList,
|
|
215
290
|
events: new Proxy(element, eventsProxy)
|
|
216
291
|
};
|
package/internal/emitter.d.ts
CHANGED
|
@@ -73,13 +73,72 @@ export declare class EventEmitter<T> extends Emitter<T> {
|
|
|
73
73
|
debounce(ms: number): EventEmitter<T>;
|
|
74
74
|
throttle(ms: number): EventEmitter<T>;
|
|
75
75
|
batch(ms: number): Emitter<T[]>;
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* **Experimental**: May change in future revisions
|
|
78
|
+
* Note: potential leak - This link will remain subscribed to the parent
|
|
79
|
+
* until it emits, regardless of subscriptions to this link.
|
|
80
|
+
* @param notifier
|
|
81
|
+
* @returns
|
|
82
|
+
*/
|
|
77
83
|
once(): EventEmitter<T>;
|
|
78
84
|
scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
|
|
79
85
|
buffer(count: number): EventEmitter<T[]>;
|
|
86
|
+
/**
|
|
87
|
+
* **Experimental**: May change in future revisions
|
|
88
|
+
* Note: potential leak - This link will remain subscribed to the parent
|
|
89
|
+
* until emission limit is reached, regardless of subscriptions to this link.
|
|
90
|
+
* @param notifier
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
80
93
|
take(limit: number): EventEmitter<T>;
|
|
81
|
-
|
|
94
|
+
/**
|
|
95
|
+
* **Experimental**: May change in future revisions
|
|
96
|
+
* Note: potential leak - This link will remain subscribed to the notifier
|
|
97
|
+
* until it emits, regardless of subscriptions to this link.
|
|
98
|
+
* @param notifier
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
82
101
|
takeUntil(notifier: Emitter<any>): Emitter<T>;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a chainable emitter that immediately emits a value to every new subscriber,
|
|
104
|
+
* then forwards parent emissions
|
|
105
|
+
* @param value
|
|
106
|
+
* @returns A new emitter that emits a value to new subscribers and forwards all values from the parent
|
|
107
|
+
*/
|
|
108
|
+
immediate(value: T): EventEmitter<T>;
|
|
109
|
+
cached(): EventEmitter<T>;
|
|
110
|
+
/**
|
|
111
|
+
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
112
|
+
* @param mapFunc
|
|
113
|
+
* @returns Listenable: emits transformed values
|
|
114
|
+
*/
|
|
115
|
+
map<R>(mapFunc: (value: T) => R): EventEmitter<R>;
|
|
116
|
+
/**
|
|
117
|
+
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
118
|
+
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
119
|
+
* @returns Listenable: emits values that pass the filter
|
|
120
|
+
*/
|
|
121
|
+
filter(check: (value: T) => boolean): EventEmitter<T>;
|
|
122
|
+
/**
|
|
123
|
+
* Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
124
|
+
* @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
|
|
125
|
+
*
|
|
126
|
+
* If no `compare` function is provided, values will be compared via `===`
|
|
127
|
+
* @returns Listenable: emits non-repeating values
|
|
128
|
+
*/
|
|
129
|
+
dedupe(compare?: (a: T, b: T) => boolean): EventEmitter<T>;
|
|
130
|
+
/**
|
|
131
|
+
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
132
|
+
*
|
|
133
|
+
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
134
|
+
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
135
|
+
*
|
|
136
|
+
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
137
|
+
*
|
|
138
|
+
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
139
|
+
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
140
|
+
*/
|
|
141
|
+
tap(cb: Handler<T>): EventEmitter<T>;
|
|
83
142
|
}
|
|
84
143
|
/**
|
|
85
144
|
* Creates a linked Emitter and emit() pair
|
|
@@ -120,4 +179,7 @@ export declare function createListenable<T>(onAddFirst?: () => void, onRemoveLas
|
|
|
120
179
|
listen: (fn: (v: T) => void) => UnsubscribeFunc;
|
|
121
180
|
emit: (value: T) => void;
|
|
122
181
|
};
|
|
182
|
+
export declare function interval(t: number | {
|
|
183
|
+
asMilliseconds: number;
|
|
184
|
+
}): EventEmitter<number>;
|
|
123
185
|
export {};
|
package/internal/emitter.js
CHANGED
|
@@ -152,10 +152,13 @@ export class EventEmitter extends Emitter {
|
|
|
152
152
|
});
|
|
153
153
|
return new EventEmitter(listen);
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
/**
|
|
156
|
+
* **Experimental**: May change in future revisions
|
|
157
|
+
* Note: potential leak - This link will remain subscribed to the parent
|
|
158
|
+
* until it emits, regardless of subscriptions to this link.
|
|
159
|
+
* @param notifier
|
|
160
|
+
* @returns
|
|
161
|
+
*/
|
|
159
162
|
once() {
|
|
160
163
|
const { emit, listen } = createListenable();
|
|
161
164
|
const unsub = this.apply(v => {
|
|
@@ -183,6 +186,13 @@ export class EventEmitter extends Emitter {
|
|
|
183
186
|
});
|
|
184
187
|
return new EventEmitter(listen);
|
|
185
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* **Experimental**: May change in future revisions
|
|
191
|
+
* Note: potential leak - This link will remain subscribed to the parent
|
|
192
|
+
* until emission limit is reached, regardless of subscriptions to this link.
|
|
193
|
+
* @param notifier
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
186
196
|
take(limit) {
|
|
187
197
|
const { emit, listen } = createListenable();
|
|
188
198
|
let count = 0;
|
|
@@ -197,13 +207,13 @@ export class EventEmitter extends Emitter {
|
|
|
197
207
|
});
|
|
198
208
|
return new EventEmitter(listen);
|
|
199
209
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
/**
|
|
211
|
+
* **Experimental**: May change in future revisions
|
|
212
|
+
* Note: potential leak - This link will remain subscribed to the notifier
|
|
213
|
+
* until it emits, regardless of subscriptions to this link.
|
|
214
|
+
* @param notifier
|
|
215
|
+
* @returns
|
|
216
|
+
*/
|
|
207
217
|
takeUntil(notifier) {
|
|
208
218
|
const { emit, listen } = createListenable();
|
|
209
219
|
const unsub = this.apply(v => {
|
|
@@ -213,7 +223,91 @@ export class EventEmitter extends Emitter {
|
|
|
213
223
|
unsub();
|
|
214
224
|
unsubNotifier();
|
|
215
225
|
});
|
|
216
|
-
return new
|
|
226
|
+
return new EventEmitter(listen);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Creates a chainable emitter that immediately emits a value to every new subscriber,
|
|
230
|
+
* then forwards parent emissions
|
|
231
|
+
* @param value
|
|
232
|
+
* @returns A new emitter that emits a value to new subscribers and forwards all values from the parent
|
|
233
|
+
*/
|
|
234
|
+
immediate(value) {
|
|
235
|
+
return new EventEmitter(handle => {
|
|
236
|
+
handle(value);
|
|
237
|
+
return this.onListen(handle);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
cached() {
|
|
241
|
+
let cache = null;
|
|
242
|
+
let unsub = null;
|
|
243
|
+
const { listen, emit } = createListenable(() => {
|
|
244
|
+
unsub = this.onListen((value => {
|
|
245
|
+
cache = { value };
|
|
246
|
+
emit(value);
|
|
247
|
+
}));
|
|
248
|
+
}, () => {
|
|
249
|
+
unsub();
|
|
250
|
+
});
|
|
251
|
+
return new EventEmitter(handler => {
|
|
252
|
+
if (cache)
|
|
253
|
+
handler(cache.value);
|
|
254
|
+
return listen(handler);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
259
|
+
* @param mapFunc
|
|
260
|
+
* @returns Listenable: emits transformed values
|
|
261
|
+
*/
|
|
262
|
+
map(mapFunc) {
|
|
263
|
+
const listen = this.transform((value, emit) => emit(mapFunc(value)));
|
|
264
|
+
return new EventEmitter(listen);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Creates a chainable emitter that selectively forwards emissions along the chain
|
|
268
|
+
* @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
|
|
269
|
+
* @returns Listenable: emits values that pass the filter
|
|
270
|
+
*/
|
|
271
|
+
filter(check) {
|
|
272
|
+
const listen = this.transform((value, emit) => check(value) && emit(value));
|
|
273
|
+
return new EventEmitter(listen);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
277
|
+
* @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
|
|
278
|
+
*
|
|
279
|
+
* If no `compare` function is provided, values will be compared via `===`
|
|
280
|
+
* @returns Listenable: emits non-repeating values
|
|
281
|
+
*/
|
|
282
|
+
dedupe(compare) {
|
|
283
|
+
let previous = null;
|
|
284
|
+
const listen = this.transform((value, emit) => {
|
|
285
|
+
if (!previous || (compare
|
|
286
|
+
? !compare(previous.value, value)
|
|
287
|
+
: (previous.value !== value))) {
|
|
288
|
+
emit(value);
|
|
289
|
+
previous = { value };
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
return new EventEmitter(listen);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
|
|
296
|
+
*
|
|
297
|
+
* The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
|
|
298
|
+
* All listeners attached to the returned emitter receive the same values as the parent emitter.
|
|
299
|
+
*
|
|
300
|
+
* *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
|
|
301
|
+
*
|
|
302
|
+
* @param cb A function to be called as a side effect for each value emitted by the parent emitter.
|
|
303
|
+
* @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
|
|
304
|
+
*/
|
|
305
|
+
tap(cb) {
|
|
306
|
+
const listen = this.transform((value, emit) => {
|
|
307
|
+
cb(value);
|
|
308
|
+
emit(value);
|
|
309
|
+
});
|
|
310
|
+
return new EventEmitter(listen);
|
|
217
311
|
}
|
|
218
312
|
}
|
|
219
313
|
/**
|
|
@@ -277,3 +371,13 @@ export function createListenable(onAddFirst, onRemoveLast) {
|
|
|
277
371
|
emit: (value) => handlers.forEach(h => h.fn(value)),
|
|
278
372
|
};
|
|
279
373
|
}
|
|
374
|
+
export function interval(t) {
|
|
375
|
+
let intervalId = null;
|
|
376
|
+
let idx = 0;
|
|
377
|
+
const { emit, listen } = createListenable(() => {
|
|
378
|
+
intervalId = setInterval(() => {
|
|
379
|
+
emit(idx++);
|
|
380
|
+
}, typeof t == "number" ? t : t.asMilliseconds);
|
|
381
|
+
}, () => clearInterval(intervalId));
|
|
382
|
+
return new EventEmitter(listen);
|
|
383
|
+
}
|
package/internal/proxy.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { SetGetStyleFunc } from "./types";
|
|
2
|
+
export declare const styleProxy: ProxyHandler<SetGetStyleFunc>;
|
|
2
3
|
export declare const attribsProxy: ProxyHandler<HTMLElement>;
|
|
3
4
|
export declare const eventsProxy: ProxyHandler<HTMLElement>;
|
package/internal/proxy.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { EventEmitter } from "./emitter";
|
|
2
2
|
export const styleProxy = {
|
|
3
|
-
get(
|
|
4
|
-
return
|
|
3
|
+
get(style, prop) {
|
|
4
|
+
return style(prop);
|
|
5
5
|
},
|
|
6
|
-
set(
|
|
7
|
-
|
|
6
|
+
set(style, prop, value) {
|
|
7
|
+
style(prop, value);
|
|
8
8
|
return true;
|
|
9
9
|
},
|
|
10
|
-
apply(
|
|
11
|
-
const style = getStyle();
|
|
10
|
+
apply(style, _, [stylesOrProp, value]) {
|
|
12
11
|
if (typeof stylesOrProp == "object") {
|
|
13
|
-
Object.entries(stylesOrProp).forEach(([prop, val]) => style
|
|
12
|
+
Object.entries(stylesOrProp).forEach(([prop, val]) => style(prop, val));
|
|
14
13
|
return;
|
|
15
14
|
}
|
|
16
|
-
style
|
|
15
|
+
style(stylesOrProp, value);
|
|
17
16
|
},
|
|
18
|
-
deleteProperty(
|
|
19
|
-
|
|
17
|
+
deleteProperty(style, prop) {
|
|
18
|
+
style(prop, null);
|
|
20
19
|
return true;
|
|
21
20
|
}
|
|
22
21
|
};
|
package/internal/types.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { EventEmitter } from "./emitter";
|
|
1
|
+
import { EventEmitter, UnsubscribeFunc } from "./emitter";
|
|
2
2
|
import { entityDataSymbol } from "./util";
|
|
3
3
|
export type ElementClassDescriptor = string | Record<string, boolean | undefined> | undefined | ElementClassDescriptor[];
|
|
4
4
|
export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[];
|
|
5
5
|
export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>;
|
|
6
|
-
type
|
|
6
|
+
export type ReactiveSource<T> = ({
|
|
7
|
+
listen: (handler: (value: T) => void) => UnsubscribeFunc;
|
|
8
|
+
} | {
|
|
9
|
+
subscribe: (handler: (value: T) => void) => UnsubscribeFunc;
|
|
10
|
+
});
|
|
11
|
+
export type CSSValue = string | number | null | HexCodeContainer;
|
|
12
|
+
export type CSSProperty = keyof StylesDescriptor;
|
|
7
13
|
type HexCodeContainer = {
|
|
8
14
|
hexCode: string;
|
|
9
15
|
toString(): string;
|
|
@@ -12,9 +18,11 @@ export type StylesDescriptor = {
|
|
|
12
18
|
[K in keyof CSSStyleDeclaration as [
|
|
13
19
|
K,
|
|
14
20
|
CSSStyleDeclaration[K]
|
|
15
|
-
] extends [string, string] ? K : never]+?: CSSValue
|
|
21
|
+
] extends [string, string] ? K : never]+?: CSSValue | ReactiveSource<CSSValue>;
|
|
16
22
|
};
|
|
17
|
-
export type
|
|
23
|
+
export type SetStyleFunc = ((property: CSSProperty, value: CSSValue | ReactiveSource<CSSValue>) => void);
|
|
24
|
+
export type SetGetStyleFunc = SetStyleFunc & ((property: CSSProperty) => string | ReactiveSource<CSSValue>);
|
|
25
|
+
export type StyleAccessor = ((styles: StylesDescriptor) => void) & StylesDescriptor & SetStyleFunc;
|
|
18
26
|
type ContentlessTag = "area" | "br" | "hr" | "iframe" | "input" | "textarea" | "img" | "canvas" | "link" | "meta" | "source" | "embed" | "track" | "base";
|
|
19
27
|
type TagWithHref = "a" | "link" | "base";
|
|
20
28
|
type TagWithSrc = "img" | "script" | "iframe" | "video" | "audio" | "embed" | "source" | "track";
|
|
@@ -34,7 +42,7 @@ export type ElementDescriptor<Tag extends string> = {
|
|
|
34
42
|
} & (Tag extends TagWithValue ? {
|
|
35
43
|
value?: string | number;
|
|
36
44
|
} : {}) & (Tag extends ContentlessTag ? {} : {
|
|
37
|
-
content?: DOMContent
|
|
45
|
+
content?: DOMContent | ReactiveSource<DOMContent>;
|
|
38
46
|
}) & (Tag extends TagWithSrc ? {
|
|
39
47
|
src?: string;
|
|
40
48
|
} : {}) & (Tag extends TagWithHref ? {
|