@@ -1,2 +0,18 @@ | ||
| import { DomHelper } from "./types"; | ||
| import { DomHelper, Listenable } from "./types"; | ||
| export declare const $: DomHelper; | ||
| export declare class ClassAccessor { | ||
| private classList; | ||
| private listen; | ||
| private unlisten; | ||
| constructor(classList: DOMTokenList, listen: (className: string, stream: Listenable<boolean>) => void, unlisten: (classNames: string[]) => void); | ||
| add(...className: string[]): void; | ||
| remove(...className: string[]): void; | ||
| toggle(className: string, value?: boolean): boolean; | ||
| toggle(className: string, value: Listenable<boolean>): void; | ||
| contains(className: string): boolean; | ||
| get length(): number; | ||
| toString(): string; | ||
| replace(token: string, newToken: string): void; | ||
| forEach(cb: (token: string, idx: number) => void): void; | ||
| map<R>(cb: (token: string, idx: number) => R): R[]; | ||
| } |
+66
-4
@@ -37,4 +37,8 @@ import { attribsProxy, eventsProxy, styleProxy } from "./proxy"; | ||
| Object.entries(classes).forEach(([className, state]) => { | ||
| if (state) | ||
| if (isReactiveSource(state)) { | ||
| ent.classes.toggle(className, state); | ||
| } | ||
| else if (state) { | ||
| applyClasses(className); | ||
| } | ||
| }); | ||
@@ -133,3 +137,4 @@ }; | ||
| function isReactiveSource(value) { | ||
| return typeof value == "object" && value && ("listen" in value || "subscribe" in value); | ||
| return typeof value == "object" && value && (("listen" in value && typeof value.listen == "function") | ||
| || ("subscribe" in value && typeof value.subscribe == "function")); | ||
| } | ||
@@ -150,2 +155,3 @@ function getWrappedElement(element) { | ||
| content: {}, | ||
| class: {}, | ||
| }; | ||
@@ -159,3 +165,4 @@ function addListener(type, prop, source) { | ||
| recursiveAppend(element, v); | ||
| } | ||
| }, | ||
| class: (v) => element.classList.toggle(prop, v), | ||
| }[type]; | ||
@@ -341,3 +348,8 @@ const subscribe = "subscribe" in source | ||
| style: new Proxy(setStyle, styleProxy), | ||
| classes: element.classList, | ||
| classes: new ClassAccessor(element.classList, (className, stream) => addListener("class", className, stream), (classNames) => { | ||
| classNames.forEach(c => { | ||
| if (listeners.class[c]) | ||
| removeListener("class", c); | ||
| }); | ||
| }), | ||
| events: new Proxy(element, eventsProxy) | ||
@@ -349,1 +361,51 @@ }; | ||
| } | ||
| export class ClassAccessor { | ||
| constructor(classList, listen, unlisten) { | ||
| this.classList = classList; | ||
| this.listen = listen; | ||
| this.unlisten = unlisten; | ||
| } | ||
| add(...className) { | ||
| this.unlisten(className); | ||
| this.classList.add(...className); | ||
| } | ||
| remove(...className) { | ||
| this.unlisten(className); | ||
| this.classList.remove(...className); | ||
| } | ||
| toggle(className, value) { | ||
| this.unlisten([className]); | ||
| if (isReactiveSource(value)) { | ||
| this.listen(className, value); | ||
| return; | ||
| } | ||
| return this.classList.toggle(className, value); | ||
| } | ||
| contains(className) { | ||
| return this.classList.contains(className); | ||
| } | ||
| get length() { | ||
| return this.classList.length; | ||
| } | ||
| toString() { | ||
| return this.classList.toString(); | ||
| } | ||
| replace(token, newToken) { | ||
| this.unlisten([token, newToken]); | ||
| this.classList.replace(token, newToken); | ||
| } | ||
| forEach(cb) { | ||
| this.classList.forEach(cb); | ||
| } | ||
| map(cb) { | ||
| 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(); | ||
| } | ||
| return result; | ||
| } | ||
| } |
@@ -0,9 +1,5 @@ | ||
| import { Listenable } from "./types"; | ||
| type Handler<T> = (value: T) => void; | ||
| export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc; | ||
| export type UnsubscribeFunc = () => void; | ||
| export type Listenable<T> = { | ||
| subscribe: (callback: (value: T) => void) => UnsubscribeFunc; | ||
| } | { | ||
| listen: (callback: (value: T) => void) => UnsubscribeFunc; | ||
| }; | ||
| export declare class EventEmitter<T> { | ||
@@ -10,0 +6,0 @@ protected onListen: ListenFunc<T>; |
+10
-2
@@ -411,5 +411,13 @@ export class EventEmitter { | ||
| const ms = typeof t === "number" ? t : t.asMilliseconds; | ||
| const { emit, listen } = createListenable(); | ||
| setTimeout(emit, ms); | ||
| const targetTime = Date.now() + ms; | ||
| let timeoutId = null; | ||
| const { emit, listen } = createListenable(() => { | ||
| const reminaingMs = targetTime - Date.now(); | ||
| if (reminaingMs < 0) | ||
| return; | ||
| timeoutId = setTimeout(emit, reminaingMs); | ||
| }, () => { | ||
| clearTimeout(timeoutId); | ||
| }); | ||
| return new EventEmitter(listen); | ||
| } |
+15
-14
@@ -0,11 +1,12 @@ | ||
| import { type ClassAccessor } from "./element"; | ||
| import { EventEmitter, UnsubscribeFunc } from "./emitter"; | ||
| import { entityDataSymbol } from "./util"; | ||
| export type ElementClassDescriptor = string | Record<string, boolean | undefined> | undefined | ElementClassDescriptor[]; | ||
| export type ElementClassDescriptor = string | Record<string, boolean | Listenable<boolean> | undefined> | undefined | ElementClassDescriptor[]; | ||
| export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[]; | ||
| export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>; | ||
| export type ReactiveSource<T> = ({ | ||
| listen: (handler: (value: T) => void) => UnsubscribeFunc; | ||
| export type Listenable<T> = { | ||
| subscribe: (callback: (value: T) => void) => UnsubscribeFunc; | ||
| } | { | ||
| subscribe: (handler: (value: T) => void) => UnsubscribeFunc; | ||
| }); | ||
| listen: (callback: (value: T) => void) => UnsubscribeFunc; | ||
| }; | ||
| export type CSSValue = string | number | null | HexCodeContainer; | ||
@@ -21,6 +22,6 @@ export type CSSProperty = keyof StylesDescriptor; | ||
| CSSStyleDeclaration[K] | ||
| ] extends [string, string] ? K : never]+?: CSSValue | ReactiveSource<CSSValue>; | ||
| ] extends [string, string] ? K : never]+?: CSSValue | Listenable<CSSValue>; | ||
| }; | ||
| export type SetStyleFunc = ((property: CSSProperty, value: CSSValue | ReactiveSource<CSSValue>) => void); | ||
| export type SetGetStyleFunc = SetStyleFunc & ((property: CSSProperty) => string | ReactiveSource<CSSValue>); | ||
| export type SetStyleFunc = ((property: CSSProperty, value: CSSValue | Listenable<CSSValue>) => void); | ||
| export type SetGetStyleFunc = SetStyleFunc & ((property: CSSProperty) => string | Listenable<CSSValue>); | ||
| export type StyleAccessor = ((styles: StylesDescriptor) => void) & StylesDescriptor & SetStyleFunc; | ||
@@ -42,7 +43,7 @@ type ContentlessTag = "area" | "br" | "hr" | "iframe" | "input" | "textarea" | "img" | "canvas" | "link" | "meta" | "source" | "embed" | "track" | "base"; | ||
| style?: StylesDescriptor; | ||
| cssVariables?: Record<string, CSSValue | ReactiveSource<CSSValue>>; | ||
| cssVariables?: Record<string, CSSValue | Listenable<CSSValue>>; | ||
| } & (Tag extends TagWithValue ? { | ||
| value?: string | number; | ||
| } : {}) & (Tag extends ContentlessTag ? {} : { | ||
| content?: DOMContent | ReactiveSource<DOMContent>; | ||
| content?: DOMContent | Listenable<DOMContent>; | ||
| }) & (Tag extends TagWithSrc ? { | ||
@@ -62,3 +63,3 @@ src?: string; | ||
| readonly element: T; | ||
| readonly classes: DOMTokenList; | ||
| readonly classes: ClassAccessor; | ||
| readonly attribs: { | ||
@@ -69,4 +70,4 @@ [key: string]: string | null; | ||
| readonly style: StyleAccessor; | ||
| setCSSVariable(variableName: string, value: CSSValue | ReactiveSource<CSSValue>): void; | ||
| setCSSVariable(table: Record<string, CSSValue | ReactiveSource<CSSValue>>): void; | ||
| setCSSVariable(variableName: string, value: CSSValue | Listenable<CSSValue>): void; | ||
| setCSSVariable(table: Record<string, CSSValue | Listenable<CSSValue>>): void; | ||
| qsa(selector: string): (Element | DomEntity<HTMLElement>)[]; | ||
@@ -81,3 +82,3 @@ remove(): void; | ||
| innerHTML: string; | ||
| content: DOMContent | ReactiveSource<DOMContent>; | ||
| content: DOMContent | Listenable<DOMContent>; | ||
| }) & (T extends HTMLElementTagNameMap[TagWithValue] ? { | ||
@@ -84,0 +85,0 @@ value: string; |
+1
-1
| { | ||
| "name": "@xtia/jel", | ||
| "version": "0.6.5", | ||
| "version": "0.7.0", | ||
| "repository": { | ||
@@ -5,0 +5,0 @@ "url": "https://github.com/tiadrop/jel-ts", |
+15
-3
@@ -45,3 +45,2 @@ # Jel | ||
| ]) | ||
| ``` | ||
@@ -145,3 +144,3 @@ | ||
| Style properties can be emitter subscriptions: | ||
| Style properties, content and class presence can be emitter subscriptions: | ||
@@ -153,3 +152,6 @@ ```ts | ||
| const virtualCursor = $.div({ | ||
| classes: "virtual-cursor", | ||
| classes: { | ||
| "virtual-cursor": true, | ||
| "near-top": mousePosition$.map(v => v.y < 100) | ||
| }, | ||
| style: { | ||
@@ -160,3 +162,13 @@ left: mousePosition$.map(v => v.x + "px"), | ||
| }); | ||
| virtualCursor.classes.toggle( | ||
| "near-left", | ||
| mousePosition$.map(v => v.x < 100>) | ||
| ); | ||
| h1.content = websocket$ | ||
| .filter(msg => msg.type == "title") | ||
| .map(msg => msg.text); | ||
| ``` | ||
| Removing an element from the page will unsubscribe from any attached stream, and resubscribe if subsequently appended. | ||
@@ -163,0 +175,0 @@ Emitters for this purpose can be Jel events, [@xtia/timeline](https://github.com/tiadrop/timeline) progressions, RxJS Observables or any object with either `subscribe()` or `listen()` that returns teardown logic. |
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
50360
6.88%1256
7.08%177
7.27%