UNPKG

@thi.ng/atom

Version:

Mutable wrappers for nested immutable values with optional undo/redo history and transaction support

112 lines (111 loc) 3.09 kB
import { equiv as _equiv } from "@thi.ng/equiv"; import { defGetterUnsafe } from "@thi.ng/paths/getter"; import { toPath } from "@thi.ng/paths/path"; import { nextID } from "./idgen.js"; function defView(parent, path, tx, lazy, equiv) { return new View(parent, path, tx, lazy, equiv); } function defViewUnsafe(parent, path, tx, lazy, equiv) { return new View(parent, path, tx, lazy, equiv); } class View { id; parent; path; state; tx; unprocessed; isDirty; isLazy; constructor(parent, path, tx, lazy = true, equiv = _equiv) { this.parent = parent; this.id = `view-${nextID()}`; this.tx = tx || ((x) => x); this.path = toPath(path); this.isDirty = true; this.isLazy = lazy; const lookup = defGetterUnsafe(this.path); const state = this.parent.deref(); this.unprocessed = state ? lookup(state) : void 0; if (!lazy) { this.state = this.tx(this.unprocessed); this.unprocessed = void 0; } parent.addWatch(this.id, (_, prev, curr) => { const pval = prev ? lookup(prev) : prev; const val = curr ? lookup(curr) : curr; if (!equiv(val, pval)) { if (lazy) { this.unprocessed = val; } else { this.state = this.tx(val); } this.isDirty = true; } }); } get value() { return this.deref(); } /** * Returns view's value. If the view has a transformer, the * transformed value is returned. The transformer is only run once * per value change. * * @remarks * See class comments about difference between lazy/eager behaviors. */ deref() { if (this.isDirty) { if (this.isLazy) { this.state = this.tx(this.unprocessed); this.unprocessed = void 0; } this.isDirty = false; } return this.state; } /** * Returns true, if the view's value has changed since last * [`IDeref`](https://docs.thi.ng/umbrella/api/interfaces/IDeref.html).deref}. */ changed() { return this.isDirty; } /** * Like * [`IDeref`](https://docs.thi.ng/umbrella/api/interfaces/IDeref.html).deref}, * but doesn't update view's cached state and dirty flag if value has * changed. * * @remarks * If there's an unprocessed value change, returns result of this sub's * transformer or else the cached value. * * **Important:** Use this function only if the view has none or or a * stateless transformer. Else might cause undefined/inconsistent behavior * when calling `view` or * [`IDeref`](https://docs.thi.ng/umbrella/api/interfaces/IDeref.html).deref} * subsequently. */ view() { return this.isDirty && this.isLazy ? this.tx(this.unprocessed) : this.state; } /** * Disconnects this view from parent state, marks itself * dirty/changed and sets its unprocessed raw value to `undefined`. */ release() { this.unprocessed = void 0; if (!this.isLazy) { this.state = this.tx(void 0); } this.isDirty = true; return this.parent.removeWatch(this.id); } } export { View, defView, defViewUnsafe };