wana
Version:
Easy observable state for React
1,172 lines (1,143 loc) • 30.1 kB
JavaScript
import { is } from '@alloc/is';
import { isDev } from '@alloc/is-dev';
import queueMicrotask from '@alloc/queue-microtask';
import { batchedUpdates } from 'react-batched-updates';
import { useMemoOne } from 'use-memo-one';
import * as React from 'react';
import { useMemo, useEffect, useState, forwardRef, useRef } from 'react';
import { useLayoutEffect } from 'react-layout-effect';
const emptyArray = Object.freeze([]);
const setHidden = (obj, key, value) => Object.defineProperty(obj, key, {value, configurable: true});
function rethrowError(error) {
throw error;
}
const noop = () => {
};
const nope = () => false;
const flop = () => {
throw Error("Not yet implemented");
};
const hasOwn = Function.call.bind({}.hasOwnProperty);
const getOwnDescriptor = Object.getOwnPropertyDescriptor;
function getDescriptor(self, key) {
let desc;
let proto = self;
do {
if (desc = getOwnDescriptor(proto, key)) {
return desc;
}
} while (proto = Object.getPrototypeOf(proto));
}
const globals = {
observe: null,
auto: null,
onRender: null,
onChange: null
};
const observe = (target, key) => !!globals.observe && (globals.observe(target, key), true);
const $$ = Symbol.for("wana:state");
const $O = Symbol.for("wana:observable");
const SIZE = Symbol.for("wana:size");
const $T = Symbol.for("wana:traps");
const shims = [
[Object, ["keys", "values", "entries"]],
[Array, ["from"]]
];
shims.forEach(([namespace, keys]) => keys.forEach((key) => {
const fn = namespace[key];
const shim = (...args) => {
const target = args[0] && args[0][$$];
if (target) {
observe(target, $O);
args[0] = target;
}
return fn(...args);
};
setHidden(shim, "name", fn.name);
Object.defineProperty(namespace, key, {
get: () => globals.observe ? shim : fn
});
}));
let runQueue = [];
const renderQueue = [];
const batch = {
run(effect) {
runQueue.push(effect);
flushAsync();
},
render(depth, effect) {
let i = renderQueue.findIndex((other) => other.depth > depth);
if (i < 0)
i = renderQueue.length;
renderQueue.splice(i, 0, {depth, effect});
flushAsync();
}
};
let isQueued = false;
function flushAsync() {
if (!isQueued) {
isQueued = true;
queueMicrotask(() => {
isQueued = false;
if (flushSync()) {
flushAsync();
}
});
}
}
function flushSync() {
batchedUpdates(() => {
let runs = 0;
for (const effect of runQueue) {
if (++runs > 1e5)
break;
effect();
}
runQueue = runQueue.slice(runs);
renderQueue.forEach(({effect}) => effect());
renderQueue.length = 0;
});
return !!(runQueue.length || renderQueue.length);
}
let nextDebugId = 1;
const $D = Symbol.for("wana:debug");
function getCurrentAuto() {
return globals.auto;
}
function getDebug(target) {
return target[$D];
}
function setDebug(target, debug) {
debug.name += "#" + nextDebugId++;
target[$D] = debug;
return target;
}
function addDebugAction(target, action) {
const debug = getDebug(target);
if (debug && debug.actions) {
debug.actions.push(action);
}
}
function noto(effect) {
const {auto, observe} = globals;
globals.auto = null;
globals.observe = null;
try {
return effect();
} finally {
globals.auto = auto;
globals.observe = observe;
}
}
const {get} = Map.prototype;
function onChange(observable, key, change) {
const observers = get.call(observable, key);
if (observers) {
observers.nonce++;
observers.onChange(change);
}
}
function emit(target, change) {
noto(() => {
const observable = target[$O];
if (globals.onChange) {
globals.onChange(change);
}
if (change.op !== "clear") {
onChange(observable, change.key, change);
} else if (is.map(change.oldValue)) {
change.oldValue.forEach((_, key) => onChange(observable, key, change));
}
if (change.key !== SIZE) {
onChange(observable, $O, change);
}
});
return true;
}
const emitAdd = (target, key, value) => emit(target, {
op: "add",
target,
key,
value
});
const emitRemove = (target, key, oldValue) => emit(target, {
op: "remove",
target,
key,
oldValue
});
const emitReplace = (target, key, value, oldValue) => emit(target, {
op: "replace",
target,
key,
value,
oldValue
});
const emitSplice = (target, key, value, oldValue) => emit(target, {
op: "splice",
target,
key,
value,
oldValue
});
const emitClear = (target, oldValue) => emit(target, {
op: "clear",
target,
oldValue
});
const emitDefine = (target, key, value, oldValue) => emit(target, {
op: "define",
target,
key,
value,
oldValue
});
const observeAll = [
Symbol.iterator,
"entries",
"every",
"filter",
"find",
"findIndex",
"flat",
"flatMap",
"forEach",
"includes",
"indexOf",
"join",
"keys",
"lastIndexOf",
"map",
"reduce",
"reduceRight",
"slice",
"some",
"values"
];
const wrapIterators = (ctr) => observeAll.reduce((methods, name) => (ctr.prototype[name] && (methods[name] = function(...args) {
const self = this[$$];
observe(self, $O);
return self[name](...args);
}), methods), {});
const ArrayIterators = wrapIterators(Array);
const MapIterators = wrapIterators(Map);
const SetIterators = wrapIterators(Set);
const ArrayOverrides = {
concat(...args) {
const self = this[$$];
observe(self, $O);
args.forEach((arg) => {
arg = arg && arg[$$];
if (is.array(arg)) {
observe(arg, $O);
}
});
return noto(() => self.concat(...args));
},
copyWithin: flop,
fill: flop,
pop() {
const self = this[$$];
const oldLength = self.length;
if (oldLength) {
const index = oldLength - 1;
const exists = index in self;
const value = self.pop();
if (exists)
emitRemove(self, index, value);
emitReplace(self, SIZE, index, oldLength);
return value;
}
},
push(...values) {
const self = this[$$];
const oldLength = self.length;
if (values.length) {
const length = self.push(...values);
emitSplice(self, oldLength, values, emptyArray);
emitReplace(self, SIZE, length, oldLength);
return length;
}
return oldLength;
},
reverse() {
const self = this[$$];
const oldValue = [...self];
self.reverse();
emitSplice(self, 0, [...self], oldValue);
return this;
},
shift() {
const self = this[$$];
const oldLength = self.length;
if (oldLength) {
const exists = 0 in self;
const value = self.shift();
if (exists)
emitRemove(self, 0, value);
emitReplace(self, SIZE, oldLength - 1, oldLength);
return value;
}
},
sort(compare) {
const self = this[$$];
const oldValue = [...self];
self.sort(compare);
emitSplice(self, 0, [...self], oldValue);
return this;
},
splice(start, removeCount = 0, ...values) {
const self = this[$$];
const oldLength = self.length;
if (start < 0) {
start = Math.max(0, start + oldLength);
} else if (start > oldLength) {
start = oldLength;
}
const removed = self.splice(start, removeCount, ...values);
removeCount = removed.length;
const addCount = values.length;
if (addCount || removeCount) {
const delta = addCount - removeCount;
emitSplice(self, start, values, [...removed]);
if (delta)
emitReplace(self, SIZE, oldLength + delta, oldLength);
}
return removed;
},
unshift(...values) {
const self = this[$$];
const oldLength = self.length;
if (values.length) {
const length = self.unshift(...values);
emitSplice(self, 0, values, emptyArray);
emitReplace(self, SIZE, length, oldLength);
return length;
}
return oldLength;
}
};
const ObjectTraps = {
has: (self, key) => (observe(self, key), Reflect.has(self, key)),
get(self, key, proxy) {
if (key === $$)
return self;
if (key !== $O) {
const desc = getDescriptor(self, key);
const get = desc && desc.get;
if (get) {
return get.call(proxy);
}
if (!desc || hasOwn(self, key)) {
observe(self, key);
}
}
return self[key];
},
set(self, key, value, proxy) {
const desc = getDescriptor(self, key);
return desc && desc.get ? Reflect.set(self, key, value, proxy) : setProperty(self, key, value);
},
defineProperty(self, key, desc) {
const prevDesc = getOwnDescriptor(self, key);
return Reflect.defineProperty(self, key, desc) && (desc.get || prevDesc && prevDesc.get) ? emitDefine(self, key, desc, prevDesc) : prevDesc ? desc.value === prevDesc.value || emitReplace(self, key, desc.value, prevDesc.value) : emitAdd(self, key, desc.value);
},
deleteProperty,
preventExtensions: nope
};
const ArrayTraps = {
has: ObjectTraps.has,
get(self, key) {
if (key === $$)
return self;
if (key === "length") {
observe(self, SIZE);
return self[key];
}
return ArrayOverrides[key] || globals.observe && ArrayIterators[key] || self[key];
},
set(self, key, value) {
if (key === "length") {
const oldLength = self.length;
if (value !== oldLength) {
const oldValues = self.slice(value);
self.length = value;
emitReplace(self, SIZE, value, oldLength);
if (value < oldLength) {
emitSplice(self, value, emptyArray, oldValues);
}
}
} else {
const oldLength = self.length;
setProperty(self, key, value);
if (oldLength !== self.length) {
emitReplace(self, SIZE, self.length, oldLength);
}
}
return true;
},
deleteProperty,
preventExtensions: nope
};
class ObservableMap extends Map {
constructor(source) {
super();
setHidden(this, $$, source);
}
get [($O)]() {
return this[$$][$O];
}
get size() {
observe(this[$$], SIZE);
return this[$$].size;
}
has(key) {
observe(this[$$], key);
return this[$$].has(key);
}
get(key) {
observe(this[$$], key);
return this[$$].get(key);
}
set(key, value) {
const exists = this[$$].has(key);
const oldValue = exists ? this[$$].get(key) : void 0;
if (!exists || value !== oldValue) {
this[$$].set(key, value);
if (exists) {
emitReplace(this[$$], key, value, oldValue);
} else {
const oldSize = this[$$].size;
emitAdd(this[$$], key, value);
emitReplace(this[$$], SIZE, oldSize + 1, oldSize);
}
}
return this;
}
delete(key) {
const oldSize = this[$$].size;
const oldValue = this[$$].get(key);
return this[$$].delete(key) && emitRemove(this[$$], key, oldValue) && emitReplace(this[$$], SIZE, oldSize - 1, oldSize);
}
clear() {
const oldSize = this[$$].size;
if (oldSize) {
const oldValues = new Map(this[$$]);
this[$$].clear();
emitClear(this[$$], oldValues);
emitReplace(this[$$], SIZE, 0, oldSize);
}
}
}
class ObservableSet extends Set {
constructor(source) {
super();
setHidden(this, $$, source);
}
get [($O)]() {
return this[$$][$O];
}
get size() {
observe(this, SIZE);
return this[$$].size;
}
has(value) {
observe(this[$$], SIZE);
return this[$$].has(value);
}
add(value) {
const oldSize = this[$$].size;
this[$$].add(value);
if (oldSize !== this[$$].size) {
emitAdd(this[$$], void 0, value);
emitReplace(this[$$], SIZE, oldSize + 1, oldSize);
}
return this;
}
delete(value) {
const oldSize = this[$$].size;
return this[$$].delete(value) && emitRemove(this[$$], void 0, value) && emitReplace(this[$$], SIZE, oldSize - 1, oldSize);
}
clear() {
const oldSize = this[$$].size;
if (oldSize) {
const oldValues = new Set(this[$$]);
this[$$].clear();
emitClear(this[$$], oldValues);
emitReplace(this[$$], SIZE, 0, oldSize);
}
}
}
defineMethods(ObservableMap, MapIterators);
defineMethods(ObservableSet, SetIterators);
function defineMethods(Class, overrides) {
Object.defineProperties(Class.prototype, Object.getOwnPropertyDescriptors(overrides));
}
function setProperty(self, key, value) {
const exists = hasOwn(self, key);
const oldValue = self[key];
self[key] = value;
return exists ? value === oldValue || emitReplace(self, key, value, oldValue) : emitAdd(self, key, value);
}
function deleteProperty(self, key) {
if (!hasOwn(self, key))
return true;
const oldValue = self[key];
delete self[key];
return emitRemove(self, key, oldValue);
}
const shallowChanges = (target, onChange) => target[$O].observe($O, onChange);
const canMakeObservable = (value) => is.object(value) && !is.date(value) && !is.regExp(value) && !is.promise(value) && !is.generator(value) && !is.generatorFunction(value) && !is.asyncFunction(value) && !is.weakMap(value) && !is.weakSet(value);
class Observer {
constructor() {
this.onChange = null;
}
get nonce() {
let nonce = 0;
this.observed.forEach((slot) => {
nonce += slot.nonce;
});
return nonce;
}
dispose() {
if (this.onChange) {
this.onChange = null;
this.observed.forEach((value) => value.delete(this));
}
}
}
class Observable extends Map {
constructor(source) {
super();
this.source = source;
if (isDev) {
setDebug(this, {
name: this.constructor.name || "Unknown"
});
}
if (source) {
this.proxy = is.map(source) ? new ObservableMap(source) : is.set(source) ? new ObservableSet(source) : new Proxy(source, source[$T] || (is.array(source) ? ArrayTraps : ObjectTraps));
}
}
has(key) {
const observers = super.get(key);
return !!(observers && observers.size);
}
get(key) {
let observers = super.get(key);
if (!observers) {
observers = new ObservedSlot(this, key);
this.set(key, observers);
}
return observers;
}
observe(key, onChange) {
const observers = this.get(key);
const observer = {
onChange,
dispose() {
observers.delete(observer);
}
};
observers.add(observer);
return observer;
}
}
class ObservedSlot extends Set {
constructor(owner, key) {
super();
this.owner = owner;
this.key = key;
this.nonce = 1;
}
onChange(change) {
if (this.size) {
for (const observer of Array.from(this)) {
if (observer.onChange) {
observer.onChange(change);
}
}
}
}
}
function auto(effect, config) {
const auto2 = new Auto(config);
auto2.run(effect);
return auto2;
}
class Auto {
constructor(config = {}) {
this.dirty = true;
this.nonce = 0;
this.observer = null;
this.sync = !!config.sync;
this.onDirty = config.onDirty || this._onDirty;
this.onError = config.onError || rethrowError;
this.onCommit = config.onCommit || noop;
this.onDispose = config.onDispose || noop;
}
run(compute) {
const observer = this.start(compute);
try {
const result = compute();
this.stop().commit(observer);
this.nonce = observer.nonce;
return result;
} catch (error) {
this.stop().onError(error);
}
}
rerun() {
const {observer} = this;
return observer && noto(() => this.run(observer.effect));
}
commit(observer, nonce) {
if (this.observer) {
this.observer.dispose();
}
if (nonce && nonce != observer.nonce) {
this.observer = null;
return false;
}
observer.onChange = this._onChange.bind(this);
observer.observed.forEach((observable) => observable.add(observer));
this.observer = observer;
this.onCommit(observer);
return true;
}
dispose() {
if (this.observer) {
this.observer.dispose();
this.observer = null;
this.onDispose();
}
}
start(effect) {
if (globals.observe) {
throw Error("Nested tracking is forbidden");
}
const observer = new AutoObserver(effect);
globals.auto = this;
globals.observe = (target, key) => {
observer.observed.add(target[$O].get(key));
};
return observer;
}
stop() {
if (globals.auto == this) {
globals.auto = globals.observe = null;
}
this.dirty = false;
return this;
}
clear() {
if (!this.dirty) {
this.dirty = true;
this.onDirty();
}
}
_onChange(change) {
if (isDev && getDebug(this)) {
addDebugAction(this, change);
}
if (this.sync && change.op == "clear" && is.function(change.target)) {
change.target();
}
this.clear();
}
_onDirty() {
const {observer, nonce} = this;
const maybeRerun = () => observer.nonce > nonce ? this.rerun() : this.dirty = false;
if (this.sync) {
maybeRerun();
} else {
batch.run(() => observer == this.observer && maybeRerun());
}
}
}
class AutoObserver extends Observer {
constructor(effect) {
super();
this.effect = effect;
this.observed = new Set();
}
}
function derive(run, discard) {
let memo;
const observable = new Observable();
const auto = new Auto({
onDirty() {
if (observable.has($O)) {
batch.run(derived);
observable.get($O).onChange({
op: "clear",
target: derived,
oldValue: memo
});
}
}
});
const derived = () => {
if (auto.dirty) {
noto(() => {
const oldMemo = memo;
const newMemo = run(auto);
if (newMemo !== oldMemo && !(discard && discard(newMemo, oldMemo))) {
emitReplace(derived, null, memo = newMemo, oldMemo);
}
});
}
observe(derived, $O);
return memo;
};
derived.auto = auto;
derived.dispose = () => auto.dispose();
setHidden(derived, $O, observable);
return derived;
}
function isDerived(value) {
return is.function(value) && !!value[$O];
}
function o(value) {
let state = value && value[$O];
if (!state || !getOwnDescriptor(value, $O)) {
if (!canMakeObservable(value)) {
return value;
}
if (is.function(value)) {
return derive((auto) => auto.run(value));
}
if (Object.isFrozen(value)) {
return value;
}
setHidden(value, $O, state = new Observable(value));
}
return state.proxy || value;
}
const when = (condition) => new Promise((resolve, reject) => {
const auto = new Auto({
onDirty() {
if (noto(() => this.run(condition))) {
this.dispose();
resolve();
}
},
onError(error) {
this.dispose();
reject(error);
}
});
auto.onDirty();
});
function watch(root, arg2, arg3) {
return arg3 ? new Watcher(root, arg3, arg2) : new Watcher(root, arg2);
}
class Watcher extends Observer {
constructor(root, onChange, key) {
super();
this.root = root;
this.key = key;
this.observed = new Set();
this.counts = new Map();
this.watch = (value, key, ctx) => {
if (canWatch(key) && is.map(ctx))
this._watch(key);
if (canWatch(value))
this._watch(value);
};
this.unwatch = (value, key, ctx) => {
if (canWatch(key) && is.map(ctx))
this._unwatch(key);
if (canWatch(value))
this._unwatch(value);
};
this.onChange = (change) => {
const {op, target, key: key2, value, oldValue} = change;
switch (op) {
case "replace":
if (canWatch(oldValue))
this._unwatch(oldValue);
case "add":
this.watch(value, key2, target);
break;
case "remove":
this.unwatch(value, key2, target);
break;
case "splice":
value.forEach(this.watch);
default:
oldValue.forEach(this.unwatch);
}
onChange(change);
};
if (key) {
this.counts.set(root, 1);
const observable = root[$O];
this.observed.add(observable.get(key).add(this));
this.watch(root[key], key, root);
} else {
this._watch(root);
}
}
dispose() {
if (this.onChange) {
super.dispose();
this.counts.clear();
}
}
_watch(target) {
const {counts} = this;
const count = counts.get(target) || 0;
counts.set(target, count + 1);
if (!count) {
if (target[$O]) {
this.observed.add(target[$O].get($O).add(this));
}
if (!target.forEach) {
target = Object.values(target);
}
target.forEach(this.watch);
}
}
_unwatch(target) {
const {counts} = this;
const count = counts.get(target);
if (is.undefined(count))
return;
if (count > 1) {
counts.set(target, count - 1);
} else {
counts.delete(target);
if (target[$O]) {
const value = target[$O].get($O);
this.observed.delete(value);
value.delete(this);
}
if (!target.forEach) {
target = Object.values(target);
}
target.forEach(this.unwatch);
}
}
}
const canWatch = (value) => value && typeof value == "object";
function no(value) {
if (is.function(value)) {
if (value[$$])
return value;
const fn = function(...args) {
return noto(value.bind(this, ...args));
};
setHidden(fn, "name", value.name);
setHidden(fn, $$, value);
return fn;
}
return value && value[$$] || value;
}
function mountAuto(auto) {
let mounted = false;
let observer;
let nonce = 0;
return (props) => {
if (props.observer) {
observer = props.observer;
if (!mounted) {
nonce = observer.nonce;
} else if (observer != auto.observer) {
auto.commit(observer);
}
} else {
mounted = props.mounted;
if (observer) {
if (!mounted) {
nonce = observer.nonce;
auto.dispose();
} else if (!auto.commit(observer, nonce)) {
auto.clear();
}
}
}
};
}
function useDerived(compute, discardOrDeps, deps) {
let discard;
if (typeof discardOrDeps == "function") {
discard = discardOrDeps;
} else {
deps = discardOrDeps;
}
const [derived, setState] = useMemo(() => {
if (!compute) {
return [null, null];
}
const derived2 = derive((auto) => {
const observer = auto.start(compute);
try {
var result = compute();
} finally {
auto.stop();
}
setState2({observer});
return result;
}, discard);
const setState2 = mountAuto(derived2.auto);
return [derived2, setState2];
}, deps || emptyArray);
useLayoutEffect(() => {
if (setState) {
setState({mounted: true});
return () => setState({mounted: false});
}
}, [setState]);
return derived;
}
function useO(state, deps) {
const result = useMemoOne(() => convertDerived(is.function(state) ? noto(state) : state), deps || emptyArray);
return is.function(result) ? useDerived(result, [result]) : o(result);
}
function convertDerived(state) {
if (is.plainObject(state)) {
for (const key in state) {
const desc = getOwnDescriptor(state, key);
if (isDerived(desc.value) && desc.configurable) {
Object.defineProperty(state, key, {
get: desc.value
});
}
}
}
return state;
}
const RenderAction = ({useAction}) => (useAction(), null);
function useForceUpdate() {
const update = useState()[1];
const mounted = useMemo(makeMountedRef, []);
useEffect(mounted.unmount, emptyArray);
return () => {
if (mounted.current) {
update({});
}
};
}
function makeMountedRef() {
const mounted = {
current: true,
unmount: () => () => {
mounted.current = false;
}
};
return mounted;
}
const useConstant = (create) => useMemoOne(create, emptyArray);
const useDispose = (dispose) => useEffect(() => dispose, emptyArray);
function useAuto(computeOrEffect, effectOrDeps, deps) {
const auto = useConstant(() => new Auto());
useDispose(() => auto.dispose());
let effect;
if (is.function(effectOrDeps)) {
const compute = useDerived(computeOrEffect, deps);
effect = () => no(effectOrDeps)(compute());
} else {
effect = computeOrEffect;
deps = effectOrDeps;
}
useEffect(() => batchedUpdates(() => {
auto.run(effect);
}), deps);
return auto;
}
const context = React.createContext({depth: 0});
const {Provider} = context;
const AutoContext = ({children, ...props}) => /* @__PURE__ */ React.createElement(Provider, {
value: React.useRef(props).current
}, children);
const useAutoContext = () => React.useContext(context);
function withAuto(render) {
let component = (props, ref) => {
const {auto, depth, commit} = useAutoRender(component);
useLayoutEffect(() => commit(observer, nonce));
const observer = auto.start(render);
try {
var content = render(props, ref);
} finally {
auto.stop();
}
const {nonce} = observer;
const recon = /* @__PURE__ */ React.createElement(RenderAction, {
useAction: () => {
auto.nonce = nonce;
}
});
return /* @__PURE__ */ React.createElement(AutoContext, {
depth: depth + 1
}, recon, content);
};
if (render.length > 1) {
Object.defineProperty(component, "displayName", {
get: () => component.displayName
});
component = forwardRef(component);
}
if (/^[A-Z]/.test(render.name)) {
component.displayName = render.name;
}
if (isDev) {
Object.defineProperty(component, "__render", {
value: render
});
}
return component;
}
function useAutoRender(component) {
const {depth} = useAutoContext();
const forceUpdate = useForceUpdate();
const auto = useMemo(() => {
const auto2 = new Auto({
onDirty() {
if (isDev) {
addDebugAction(auto2, "dirty");
}
const {observer} = auto2;
if (!observer) {
return;
}
batch.render(depth, () => {
if (observer == auto2.observer) {
if (observer.nonce > auto2.nonce) {
if (isDev) {
addDebugAction(auto2, "batch");
}
forceUpdate();
} else {
auto2.dirty = false;
}
}
});
}
});
if (isDev) {
setDebug(auto2, {
name: component.displayName || "Anonymous",
actions: ["init"],
renders: 0
});
}
return auto2;
}, []);
if (isDev) {
getDebug(auto).renders++;
if (globals.onRender) {
globals.onRender(auto, depth, component);
}
}
useDispose(() => auto.dispose());
return {
auto,
depth,
commit(observer, nonce) {
if (isDev) {
getDebug(auto).actions = [];
}
if (!auto.commit(observer, nonce)) {
if (isDev) {
addDebugAction(auto, "dirty");
}
forceUpdate();
} else if (isDev) {
addDebugAction(auto, "observe");
}
}
};
}
function useChanges(target, onChange, deps) {
const observable = target[$O];
if (!observable) {
throw Error("Expected an observable object");
}
const observer = useMemoOne(() => ({onChange}), emptyArray);
useLayoutEffect(() => {
observer.onChange = no(onChange);
}, deps);
useLayoutEffect(() => {
const observers = observable.get($O);
observers.add(observer);
return () => {
observers.delete(observer);
};
}, [observable]);
}
function useEffects(source, effect, deps) {
const unmountFns = useMemo(() => new Map(), []);
const usedEffect = useRef(effect);
useLayoutEffect(() => {
usedEffect.current = effect;
}, deps);
const target = no(source);
const isValueBased = is.array(target) || is.set(target);
useLayoutEffect(() => {
if (is.function(target.forEach)) {
target.forEach(mount);
} else {
Object.keys(target).forEach((key) => mount(target[key], key));
}
return () => unmountFns.forEach((unmount) => unmount());
}, [source]);
useChanges(source, onChange);
function mount(value, key) {
const cacheKey = isValueBased ? value : key;
if (!(is.array(target) && unmountFns.has(cacheKey))) {
const effect2 = usedEffect.current;
const unmount = isValueBased ? effect2(value) : effect2(value, key);
unmountFns.set(cacheKey, unmount || noop);
}
}
function onChange({op, key, value, oldValue}) {
if (key == SIZE)
return;
if (op == "clear") {
unmountFns.forEach((unmount) => unmount());
unmountFns.clear();
} else if (op == "splice") {
if (is.array(target)) {
oldValue.forEach((value2) => {
if (!target.includes(value2)) {
unmountFns.get(value2)();
unmountFns.delete(value2);
}
});
value.forEach((value2) => {
if (!unmountFns.has(value2)) {
mount(value2);
}
});
}
} else {
if (op != "add") {
const cacheKey = isValueBased ? oldValue : key;
if (!(is.array(target) && target.includes(cacheKey))) {
unmountFns.get(cacheKey)();
unmountFns.delete(cacheKey);
}
}
if (op != "remove") {
const cacheKey = isValueBased ? value : key;
if (!(is.array(target) && unmountFns.has(cacheKey))) {
mount(value, key);
}
}
}
}
}
function useBinding(target, key) {
const observable = target[$O];
const forceUpdate = useForceUpdate();
useLayoutEffect(() => observable?.observe(resolveKey(target, key), (change) => {
if (change.op == "clear" && is.function(target))
return;
forceUpdate();
}).dispose, [observable, key]);
return key ? is.map(target) ? target.get(key) : target[key] : is.function(target) ? target() : target;
}
const resolveKey = (target, key = $O) => key == "length" && is.array(target) || key == "size" && is.set(target) ? SIZE : key;
export { $$, $O, $T, Auto, ObjectTraps, Observable, ObservedSlot, Watcher, addDebugAction, auto, flushSync, getCurrentAuto, getDebug, globals, isDerived, mountAuto, no, noto, o, setDebug, shallowChanges, useAuto, useAutoContext, useBinding, useChanges, useDerived, useEffects, useO, watch, when, withAuto };
//# sourceMappingURL=wana.mjs.map