@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
JavaScript
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
};