+4
-2
@@ -1,7 +0,9 @@ | ||
| export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity } from "./internal/types"; | ||
| import { $ } from "./internal/element"; | ||
| import { DomEntity } from "./internal/types"; | ||
| export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity, EventEmitterMap } from "./internal/types"; | ||
| export { createEntity } from "./internal/util"; | ||
| export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, type EventEmitter, combineEmitters } from "./internal/emitter"; | ||
| export { createEventSource, createEventsSource, interval, timeout, SubjectEmitter, toEventEmitter, type EventEmitter, type EventRecording, type EventRecorder, combineEmitters } from "./internal/emitter"; | ||
| export { createEventsProxy } from "./internal/proxy"; | ||
| export { $ }; | ||
| export declare const $body: DomEntity<HTMLElement>; | ||
| export declare const windowEvents: import(".").EventEmitterMap<WindowEventMap>; |
+4
-1
| import { $ } from "./internal/element"; | ||
| import { createEventsProxy } from "./internal/proxy"; | ||
| export { createEntity } from "./internal/util"; | ||
| export { createEventSource, interval, timeout, SubjectEmitter, toEventEmitter, combineEmitters } from "./internal/emitter"; | ||
| export { createEventSource, createEventsSource, interval, timeout, SubjectEmitter, toEventEmitter, combineEmitters } from "./internal/emitter"; | ||
| export { createEventsProxy } from "./internal/proxy"; | ||
| export { $ }; | ||
| export const $body = "document" in globalThis ? $(document.body) : undefined; | ||
| export const windowEvents = createEventsProxy(window); |
@@ -14,2 +14,3 @@ import { DomHelper, EmitterLike } from "./types"; | ||
| get length(): number; | ||
| get value(): string; | ||
| toString(): string; | ||
@@ -16,0 +17,0 @@ replace(token: string, newToken: string): void; |
+12
-12
@@ -1,2 +0,3 @@ | ||
| import { attribsProxy, eventsProxy, styleProxy } from "./proxy"; | ||
| import { toEventEmitter } from "./emitter.js"; | ||
| import { attribsProxy, createEventsProxy, styleProxy } from "./proxy"; | ||
| import { entityDataSymbol, isContent, isJelEntity, isReactiveSource } from "./util"; | ||
@@ -20,3 +21,3 @@ const elementWrapCache = new WeakMap(); | ||
| function createElement(tag, descriptor = {}) { | ||
| if (isContent(descriptor)) | ||
| if (isContent(descriptor) || isReactiveSource(descriptor)) | ||
| descriptor = { | ||
@@ -204,4 +205,4 @@ content: descriptor, | ||
| if (typeof value == "object" && value) { | ||
| if ("listen" in value || "subscribe" in value) { | ||
| addListener("style", prop, value); | ||
| if (isReactiveSource(value)) { | ||
| addListener("style", prop, toEventEmitter(value)); | ||
| return; | ||
@@ -352,3 +353,3 @@ } | ||
| }), | ||
| events: new Proxy(element, eventsProxy) | ||
| events: createEventsProxy(element), | ||
| }; | ||
@@ -387,2 +388,5 @@ elementWrapCache.set(element, domEntity); | ||
| } | ||
| get value() { | ||
| return this.classList.value; | ||
| } | ||
| toString() { | ||
@@ -400,11 +404,7 @@ return this.classList.toString(); | ||
| const result = []; | ||
| const entries = this.classList.entries(); | ||
| let entry = entries.next(); | ||
| while (!entry.done) { | ||
| const [idx, value] = entry.value; | ||
| result.push(cb(value, idx)); | ||
| entry = entries.next(); | ||
| } | ||
| this.classList.forEach((v, i) => { | ||
| result.push(cb(v, i)); | ||
| }); | ||
| return result; | ||
| } | ||
| } |
+47
-29
@@ -1,8 +0,2 @@ | ||
| import { EmitterLike } from "./types"; | ||
| type Handler<T> = (value: T) => void; | ||
| type Period = { | ||
| asMilliseconds: number; | ||
| } | { | ||
| asSeconds: number; | ||
| }; | ||
| import { Dictionary, EmissionSource, EmitterLike, EventHandlerMap, EventSource, Handler, Period } from "./types"; | ||
| export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc; | ||
@@ -33,2 +27,3 @@ export type UnsubscribeFunc = () => void; | ||
| mapAsync<R>(mapFunc: (value: T) => Promise<R>): EventEmitter<R>; | ||
| as<R>(value: R): EventEmitter<R>; | ||
| /** | ||
@@ -94,3 +89,3 @@ * Creates a chainable emitter that selectively forwards emissions along the chain | ||
| /** | ||
| * Creates a chainable emitter that | ||
| * Creates a chainable emitter that forwards the next emission from the parent | ||
| * **Experimental**: May change in future revisions | ||
@@ -102,2 +97,4 @@ * Note: only listens to the parent while at least one downstream subscription is present | ||
| once(): EventEmitter<T>; | ||
| once(handler: Handler<T>): UnsubscribeFunc; | ||
| getNext(): Promise<T>; | ||
| delay(ms: number): EventEmitter<T>; | ||
@@ -146,7 +143,34 @@ delay(period: Period): EventEmitter<T>; | ||
| or<U>(...emitters: EmitterLike<U>[]): EventEmitter<T | U>; | ||
| then<R>(handler: (value: T) => R): Promise<R>; | ||
| memo(): Memo<T | undefined>; | ||
| memo(initial: T): Memo<T>; | ||
| memo<U>(initial: U): Memo<T | U>; | ||
| record(): EventRecorder<T>; | ||
| } | ||
| export declare class EventRecorder<T> { | ||
| private startTime; | ||
| private entries; | ||
| private recording; | ||
| private unsubscribe; | ||
| constructor(emitter: EventEmitter<T>); | ||
| private add; | ||
| stop(): EventRecording<T>; | ||
| } | ||
| export declare class EventRecording<T> { | ||
| private _entries; | ||
| constructor(entries: [number, T][]); | ||
| export(): [number, T][]; | ||
| play(speed?: number): EventEmitter<T>; | ||
| } | ||
| type EmitEmitterPair<T> = { | ||
| emit: (value: T) => void; | ||
| emitter: EventEmitter<T>; | ||
| }; | ||
| type CreateEventSourceOptions<T> = { | ||
| initialHandler?: Handler<T>; | ||
| /** | ||
| * Function to call when subscription count changes from 0 | ||
| * Return a *deactivation* function, which will be called when subscription count changes back to 0 | ||
| */ | ||
| activate?(): UnsubscribeFunc; | ||
| }; | ||
| /** | ||
@@ -183,8 +207,11 @@ * Creates a linked EventEmitter and emit() pair | ||
| */ | ||
| export declare function createEventSource<T>(initialHandler?: Handler<T>): { | ||
| emit: (value: T) => void; | ||
| emitter: EventEmitter<T>; | ||
| export declare function createEventSource<T>(initialHandler?: Handler<T>): EmitEmitterPair<T>; | ||
| export declare function createEventSource<T>(options?: CreateEventSourceOptions<T>): EmitEmitterPair<T>; | ||
| export declare function createEventsSource<Map extends Dictionary<any>>(initialListeners?: EventHandlerMap<Map>): { | ||
| emitters: import("./types").EventEmitterMap<Map>; | ||
| trigger: <K extends keyof Map>(name: K, value: Map[K]) => void; | ||
| }; | ||
| export declare function interval(ms: number): EventEmitter<number>; | ||
| export declare function interval(period: Period): EventEmitter<number>; | ||
| export declare const animationFrames: EventEmitter<number>; | ||
| export declare function timeout(ms: number): EventEmitter<void>; | ||
@@ -206,22 +233,13 @@ export declare function timeout(period: Period): EventEmitter<void>; | ||
| } | ||
| type EventSource<T, E extends string> = { | ||
| on: (eventName: E, handler: Handler<T>) => UnsubscribeFunc; | ||
| } | { | ||
| on: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc; | ||
| off: (eventName: E, handler: Handler<T>) => void; | ||
| } | { | ||
| addEventListener: (eventName: E, handler: Handler<T>) => UnsubscribeFunc; | ||
| } | { | ||
| addEventListener: (eventName: E, handler: Handler<T>) => void | UnsubscribeFunc; | ||
| removeEventListener: (eventName: E, handler: Handler<T>) => void; | ||
| }; | ||
| /** | ||
| * Create an EventEmitter from an event source. Event sources can be RxJS observables, existing EventEmitters, or objects that | ||
| * provide a subscribe()/listen() => UnsubscribeFunc method. | ||
| * Create an EventEmitter from an event source. Event source can be RxJS observable, existing `EventEmitter`, an object that | ||
| * provides a `subscribe()`/`listen() => UnsubscribeFunc` method, or a subscribe function itself. | ||
| * @param source | ||
| */ | ||
| export declare function toEventEmitter<T>(source: EmitterLike<T>): EventEmitter<T>; | ||
| export declare function toEventEmitter<T, E extends string>(source: EventSource<T, E>, eventName: E): EventEmitter<T>; | ||
| export declare function toEventEmitter<T>(subscribe: ListenFunc<T>): EventEmitter<T>; | ||
| type Dictionary<T> = Record<string | symbol, T>; | ||
| export declare function toEventEmitter<E>(source: EmissionSource<E>): EventEmitter<E>; | ||
| /** | ||
| * 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. | ||
| * @param source | ||
| */ | ||
| export declare function toEventEmitter<E, N>(source: EventSource<E, N>, eventName: N): EventEmitter<E>; | ||
| type ExtractEmitterValue<T> = T extends EmitterLike<infer U> ? U : never; | ||
@@ -228,0 +246,0 @@ type CombinedRecord<T extends Dictionary<EmitterLike<any>>> = { |
+114
-53
@@ -0,2 +1,4 @@ | ||
| import { createEventsProxy } from "./proxy.js"; | ||
| import { isReactiveSource } from "./util"; | ||
| const NOOP = () => { }; | ||
| function periodAsMilliseconds(t) { | ||
@@ -46,2 +48,6 @@ if (typeof t == "number") | ||
| } | ||
| as(value) { | ||
| const listen = this.transform((_, emit) => emit(value)); | ||
| return new EventEmitter(listen); | ||
| } | ||
| /** | ||
@@ -156,10 +162,3 @@ * Creates a chainable emitter that selectively forwards emissions along the chain | ||
| } | ||
| /** | ||
| * Creates a chainable emitter that | ||
| * **Experimental**: May change in future revisions | ||
| * Note: only listens to the parent while at least one downstream subscription is present | ||
| * @param notifier | ||
| * @returns | ||
| */ | ||
| once() { | ||
| once(handler) { | ||
| let parentUnsubscribe = null; | ||
@@ -183,7 +182,14 @@ let completed = false; | ||
| }); | ||
| return new EventEmitter(listen); | ||
| const emitter = new EventEmitter(listen); | ||
| return handler | ||
| ? emitter.apply(handler) | ||
| : emitter; | ||
| } | ||
| getNext() { | ||
| return new Promise((resolve) => this.once(resolve)); | ||
| } | ||
| delay(t) { | ||
| const ms = periodAsMilliseconds(t); | ||
| return new EventEmitter(this.transform((value, emit) => { | ||
| setTimeout(() => emit(value), periodAsMilliseconds(t)); | ||
| return timeout(ms).apply(() => emit(value)); | ||
| })); | ||
@@ -328,44 +334,59 @@ } | ||
| } | ||
| then(handler) { | ||
| return new Promise(resolve => { | ||
| this.once().apply(v => resolve(handler(v))); | ||
| }); | ||
| } | ||
| memo(initial) { | ||
| return new Memo(this, initial); | ||
| } | ||
| record() { | ||
| return new EventRecorder(this); | ||
| } | ||
| } | ||
| /** | ||
| * Creates a linked EventEmitter and emit() pair | ||
| * @example | ||
| * ```ts | ||
| * function createForm(options?: { onsubmit?: (data: FormData) => void }) { | ||
| * const submitEvents = createEventSource(options?.onsubmit); | ||
| * const form = $.form({ | ||
| * on: { | ||
| * submit: (e) => { | ||
| * e.preventDefault(); | ||
| * const data = new FormData(e.target); | ||
| * submitEvents.emit(data); // emit when form is submitted | ||
| * } | ||
| * } | ||
| * }); | ||
| * | ||
| * return createEntity(form, { | ||
| * events: { | ||
| * submit: submitEvents.emitter | ||
| * } | ||
| * }) | ||
| * } | ||
| * | ||
| * const form = createForm({ | ||
| * onsubmit: (data) => handleSubmission(data) | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @param initialHandler Optional listener automatically applied to the resulting Emitter | ||
| * @returns | ||
| */ | ||
| export function createEventSource(initialHandler) { | ||
| const { emit, listen } = createListenable(); | ||
| export class EventRecorder { | ||
| constructor(emitter) { | ||
| this.startTime = performance.now(); | ||
| this.entries = []; | ||
| this.recording = true; | ||
| this.unsubscribe = emitter.listen(v => this.add(v)); | ||
| } | ||
| add(value) { | ||
| const now = performance.now(); | ||
| let time = now - this.startTime; | ||
| this.entries.push([time, value]); | ||
| } | ||
| stop() { | ||
| if (!this.recording) { | ||
| throw new Error("EventRecorder already stopped"); | ||
| } | ||
| this.unsubscribe(); | ||
| return new EventRecording(this.entries); | ||
| } | ||
| } | ||
| export class EventRecording { | ||
| constructor(entries) { | ||
| this._entries = entries; | ||
| } | ||
| export() { | ||
| return [...this._entries]; | ||
| } | ||
| play(speed = 1) { | ||
| let idx = 0; | ||
| let elapsed = 0; | ||
| const { emit, listen } = createListenable(); | ||
| const unsubscribe = animationFrames.listen((frameElapsed) => { | ||
| elapsed += frameElapsed * speed; | ||
| while (idx < this._entries.length && this._entries[idx][0] <= elapsed) { | ||
| emit(this._entries[idx][1]); | ||
| idx++; | ||
| } | ||
| if (idx >= this._entries.length) { | ||
| unsubscribe(); | ||
| } | ||
| }); | ||
| return new EventEmitter(listen); | ||
| } | ||
| } | ||
| export function createEventSource(arg) { | ||
| if (typeof arg === "function") { | ||
| arg = { initialHandler: arg }; | ||
| } | ||
| const { initialHandler, activate } = arg !== null && arg !== void 0 ? arg : {}; | ||
| const { emit, listen } = createListenable(activate); | ||
| if (initialHandler) | ||
@@ -378,2 +399,26 @@ listen(initialHandler); | ||
| } | ||
| export function createEventsSource(initialListeners) { | ||
| const handlers = {}; | ||
| const emitters = createEventsProxy({ | ||
| on: (name, handler) => { | ||
| if (!handlers[name]) | ||
| handlers[name] = []; | ||
| const unique = { fn: handler }; | ||
| handlers[name].push(unique); | ||
| return () => { | ||
| const idx = handlers[name].indexOf(unique); | ||
| handlers[name].splice(idx, 1); | ||
| if (handlers[name].length == 0) | ||
| delete handlers[name]; | ||
| }; | ||
| }, | ||
| }, initialListeners); | ||
| return { | ||
| emitters, | ||
| trigger: (name, value) => { | ||
| var _a; | ||
| (_a = handlers[name]) === null || _a === void 0 ? void 0 : _a.forEach(entry => entry.fn(value)); | ||
| } | ||
| }; | ||
| } | ||
| function createListenable(sourceListen) { | ||
@@ -412,6 +457,20 @@ const handlers = []; | ||
| } | ||
| export const animationFrames = (() => { | ||
| const { emit, listen } = createListenable(() => { | ||
| let rafId = null; | ||
| let lastTime = null; | ||
| const frame = (time) => { | ||
| rafId = requestAnimationFrame(frame); | ||
| const elapsed = time - lastTime; | ||
| emit(elapsed); | ||
| }; | ||
| lastTime = performance.now(); | ||
| rafId = requestAnimationFrame(frame); | ||
| return () => cancelAnimationFrame(rafId); | ||
| }); | ||
| return new EventEmitter(listen); | ||
| })(); | ||
| export function timeout(t) { | ||
| const ms = periodAsMilliseconds(t); | ||
| const targetTime = Date.now() + ms; | ||
| let timeoutId = null; | ||
| const { emit, listen } = createListenable(() => { | ||
@@ -421,3 +480,5 @@ const reminaingMs = targetTime - Date.now(); | ||
| return; | ||
| timeoutId = setTimeout(emit, reminaingMs); | ||
| const timeoutId = setTimeout(() => { | ||
| emit(); | ||
| }, reminaingMs); | ||
| return () => clearTimeout(timeoutId); | ||
@@ -447,3 +508,3 @@ }); | ||
| super(h => { | ||
| h(this._value); | ||
| h(this._value); // immediate emit on listen | ||
| return listen(h); | ||
@@ -472,4 +533,4 @@ }); | ||
| return new EventEmitter(h => { | ||
| return source.addEventListener(eventName, h) | ||
| || (() => source.removeEventListener(eventName, h)); | ||
| source.addEventListener(eventName, h); | ||
| return () => source.removeEventListener(eventName, h); | ||
| }); | ||
@@ -476,0 +537,0 @@ } |
@@ -1,4 +0,4 @@ | ||
| import { SetGetStyleFunc } from "./types"; | ||
| import { SetGetStyleFunc, EventSource, EventEmitterMap, EventHandlerMap } from "./types"; | ||
| export declare const styleProxy: ProxyHandler<SetGetStyleFunc>; | ||
| export declare const attribsProxy: ProxyHandler<HTMLElement>; | ||
| export declare const eventsProxy: ProxyHandler<HTMLElement>; | ||
| export declare function createEventsProxy<Map>(source: EventSource<any, keyof Map>, initialListeners?: EventHandlerMap<Map>): EventEmitterMap<Map>; |
+11
-9
@@ -37,12 +37,14 @@ import { toEventEmitter } from "./emitter"; | ||
| }; | ||
| export const eventsProxy = { | ||
| get: (element, key) => { | ||
| if (key == "addEventListener") { | ||
| return (name, handler) => element.addEventListener(name, handler); | ||
| } | ||
| if (key == "removeEventListener") { | ||
| return (name, handler) => element.removeEventListener(name, handler); | ||
| } | ||
| return toEventEmitter(element, key); | ||
| const eventsProxyDefinition = { | ||
| get: (object, key) => { | ||
| return toEventEmitter(object, key); | ||
| } | ||
| }; | ||
| export function createEventsProxy(source, initialListeners) { | ||
| const proxy = new Proxy(source, eventsProxyDefinition); | ||
| if (initialListeners) { | ||
| Object.entries(initialListeners) | ||
| .forEach(([name, handler]) => toEventEmitter(source, name).apply(handler)); | ||
| } | ||
| return proxy; | ||
| } |
+35
-9
@@ -1,3 +0,3 @@ | ||
| import { type ClassAccessor } from "./element"; | ||
| import { EventEmitter, UnsubscribeFunc } from "./emitter"; | ||
| import { ClassAccessor } from "./element"; | ||
| import { EventEmitter, ListenFunc, UnsubscribeFunc } from "./emitter"; | ||
| import { entityDataSymbol } from "./util"; | ||
@@ -13,2 +13,3 @@ export type ElementClassDescriptor = string | Record<string, boolean | EmitterLike<boolean> | undefined> | undefined | ElementClassDescriptor[]; | ||
| }; | ||
| export type EmissionSource<T> = EmitterLike<T> | ListenFunc<T>; | ||
| export type CSSValue = string | number | null | HexCodeContainer; | ||
@@ -68,3 +69,3 @@ export type CSSProperty = keyof StylesDescriptor; | ||
| }; | ||
| readonly events: EventsAccessor; | ||
| readonly events: EventEmitterMap<HTMLElementEventMap>; | ||
| readonly style: StyleAccessor; | ||
@@ -78,2 +79,9 @@ setCSSVariable(variableName: string, value: CSSValue | EmitterLike<CSSValue>): void; | ||
| blur(): void; | ||
| /** | ||
| * Add an event listener | ||
| * @param eventId | ||
| * @param handler | ||
| * @returns Function to remove the listener | ||
| * @deprecated Use ent.events | ||
| */ | ||
| on<E extends keyof HTMLElementEventMap>(eventId: E, handler: (this: ElementAPI<T>, data: HTMLElementEventMap[E]) => void): UnsubscribeFunc; | ||
@@ -126,3 +134,3 @@ } & (T extends ContentlessElement ? {} : { | ||
| } & { | ||
| [T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>; | ||
| [T in HTMLTag]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (((content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & ((contentEmitter: EmitterLike<DOMContent>) => DomEntity<HTMLElementTagNameMap[T]>)); | ||
| }); | ||
@@ -135,8 +143,26 @@ type JelEntityData = { | ||
| }; | ||
| export type EventsAccessor = { | ||
| [K in keyof HTMLElementEventMap]: EventEmitter<HTMLElementEventMap[K]>; | ||
| } & { | ||
| addEventListener<K extends keyof HTMLElementEventMap>(eventName: K, listener: (event: HTMLElementEventMap[K]) => void): void; | ||
| removeEventListener<K extends keyof HTMLElementEventMap>(eventName: K, listener: (event: HTMLElementEventMap[K]) => void): void; | ||
| export type Handler<T> = (value: T) => void; | ||
| export type Period = { | ||
| asMilliseconds: number; | ||
| } | { | ||
| asSeconds: number; | ||
| }; | ||
| export type EventSource<E, N> = { | ||
| on: (eventName: N, handler: Handler<E>) => UnsubscribeFunc; | ||
| } | { | ||
| on: (eventName: N, handler: Handler<E>) => void | UnsubscribeFunc; | ||
| off: (eventName: N, handler: Handler<E>) => void; | ||
| } | { | ||
| addEventListener: (eventName: N, handler: Handler<E>) => UnsubscribeFunc; | ||
| } | { | ||
| addEventListener: (eventName: N, handler: Handler<E>) => void; | ||
| removeEventListener: (eventName: N, handler: Handler<E>) => void; | ||
| }; | ||
| export type Dictionary<T> = Record<string | symbol, T>; | ||
| export type EventEmitterMap<Map> = { | ||
| [K in keyof Map]: EventEmitter<Map[K]>; | ||
| }; | ||
| export type EventHandlerMap<Map> = { | ||
| [K in keyof Map]?: (value: Map[K]) => void; | ||
| }; | ||
| export {}; |
+1
-1
| { | ||
| "name": "@xtia/jel", | ||
| "version": "0.9.1", | ||
| "version": "0.10.0", | ||
| "repository": { | ||
@@ -5,0 +5,0 @@ "url": "https://github.com/tiadrop/jel-ts", |
+19
-5
@@ -68,3 +68,3 @@ # Jel | ||
| name: string; | ||
| completionMessage: DOMContent; | ||
| completionMessage: () => DOMContent; | ||
| } | ||
@@ -76,3 +76,3 @@ | ||
| $.h2(`${job.name} Complete`), | ||
| $.p(job.completionMessage), | ||
| $.p(job.completionMessage()), | ||
| ]); | ||
@@ -135,3 +135,3 @@ ``` | ||
| ```ts | ||
| element.events.mousemove | ||
| div.events.mousemove | ||
| .takeUntil(body.events.mousedown.filter(e => e.button === 1)) | ||
@@ -142,5 +142,5 @@ .map(ev => [ev.offsetX, ev.offsetY]) | ||
| For RxJS users, events can be observed with `fromEvent(element.events, "mousemove")`. | ||
| For RxJS users, events can be observed with `fromEvent(ent.element, "mousemove")`. | ||
| ## Reactive styles | ||
| ## Reactive properties | ||
@@ -172,2 +172,16 @@ Style properties, content and class presence can be emitter subscriptions: | ||
| .map(msg => msg.text); | ||
| const searchInput = $("input.search"); | ||
| const searchResults$ = searchInput.events.input | ||
| .debounce(300) | ||
| .map(() => searchInput.value) | ||
| .filter(term => term.length >= 2) | ||
| .mapAsync(term => performSearch(term)); // Returns emitter of search results | ||
| // Then use it reactively | ||
| $.ul({ | ||
| content: searchResults$.map(results => | ||
| results.map(result => $.li(result.title)) | ||
| ) | ||
| }); | ||
| ``` | ||
@@ -174,0 +188,0 @@ Removing an element from the page will unsubscribe from any attached stream, and resubscribe if subsequently appended. |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
62107
8.37%1538
7.93%191
7.91%