diff options
-rw-r--r-- | src/element.ts | 13 | ||||
-rw-r--r-- | src/observable.ts | 16 |
2 files changed, 25 insertions, 4 deletions
diff --git a/src/element.ts b/src/element.ts index c5ff4a5..f7dcb04 100644 --- a/src/element.ts +++ b/src/element.ts @@ -14,6 +14,8 @@ interface Opts<E> { href?: string, style?: { [key in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[key] } onclick?: (e: E) => void, + onmouseenter?: (e: E) => void, + onmouseleave?: (e: E) => void, onchange?: (e: E) => void, } @@ -24,6 +26,8 @@ function apply_opts<E extends HTMLElement>(e: E, o: Opts<E>): (() => void) | voi if (o.href && e instanceof HTMLAnchorElement) e.href = o.href; if (o.onclick) e.addEventListener("click", () => o.onclick!(e)) if (o.onchange) e.addEventListener("change", () => o.onchange!(e)) + if (o.onmouseenter) e.addEventListener("mouseenter", () => o.onmouseenter!(e)) + if (o.onmouseleave) e.addEventListener("mouseleave", () => o.onmouseleave!(e)) if (typeof o?.class == "string") e.classList.add(o.class) if (typeof o?.class == "object") e.classList.add(...o.class) if (o.style) for (const k in o.style) { const v = o.style[k]; if (v !== undefined) e.style[k] = v } @@ -54,7 +58,9 @@ type EEl<K extends keyof HTMLElementTagNameMap> = string export function e<K extends keyof HTMLElementTagNameMap>(name: K, ...children: EEl<K>[]): HTMLElementTagNameMap[K] { const el = document.createElement(name) - for (const c of children) e_apply(el, c) + const undo = children.map(c => e_apply(el, c)) + //@ts-ignore yeye, new prop + el["jsh_undo"] = () => undo.forEach(f => f()) return el } @@ -73,13 +79,16 @@ function e_apply<K extends keyof HTMLElementTagNameMap, C extends EEl<K>>(el: HT el.insertBefore(c, redo?.before ?? null) return () => { const p = c.nextSibling as (HTMLElement | void); + //@ts-ignore wubbel + const child_undo: undefined | (() => void) = c["jsh_undo"] + if (child_undo) child_undo() el.removeChild(c) return { before: p ?? undefined } } } else if (c instanceof OVar) { let undo_last: () => RedoParams | void; - // TODO if nested, this is a memory leak + // TODO if nested, this is a memory leak (only partially fixed) return c.onchangeinit(val => { let redo_param = undefined; if (undo_last) redo_param = undo_last() diff --git a/src/observable.ts b/src/observable.ts index 15a1a2e..5185c9f 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -5,6 +5,9 @@ */ export class OVar<T> { private _value: T + private weak = false; // if weak, the source will be unsubscribed from, if all listeners are removed + private disabled = false + private cancel_source?: () => void private observers: ((v: T) => unknown)[] = [] constructor(initial: T) { @@ -16,8 +19,16 @@ export class OVar<T> { change() { this.observers.forEach(o => o(this._value)) } onchange(handler: (v: T) => unknown): () => void { + if (this.disabled) throw new Error("obervable is disabled"); this.observers.push(handler) - return () => this.observers = this.observers.filter(o => o != handler) + if (this.observers.length > 16) console.warn("likely memory leak here:", this); + return () => { + this.observers = this.observers.filter(o => o != handler) + if (this.observers.length == 0 && this.weak) { + this.cancel_source!() + this.disabled = true + } + } } onchangeinit(handler: (v: T) => unknown): () => void { const abort = this.onchange(handler) @@ -26,7 +37,8 @@ export class OVar<T> { } map<U>(fn: (v: T) => U): OVar<U> { const uv = new OVar(fn(this.value)) - this.onchange(v => uv.value = fn(v)) + uv.cancel_source = this.onchange(v => uv.value = fn(v)) + uv.weak = true return uv; } wait_for(val: T) { |