wana
Version:
Easy observable state for React
319 lines (310 loc) • 9.28 kB
JavaScript
import './shims.mjs';
import { derive, mountAuto, o, isDerived, Auto, batch } from './shared/2f8394f9.mjs';
export { Auto, auto, isDerived, o, shallowChanges, watch, when } from './shared/2f8394f9.mjs';
import { noto, no, getDebug, addDebugAction, setDebug } from './shared/6ab9b088.mjs';
export { no, noto } from './shared/6ab9b088.mjs';
import '@alloc/queue-microtask';
import { batchedUpdates } from 'react-batched-updates';
import { is } from '@alloc/is';
import { useMemoOne } from 'use-memo-one';
import { emptyArray, getOwnDescriptor, globals, noop, SIZE, $O } from './shared/815e560c.mjs';
import * as React from 'react';
import { useMemo, useEffect, useState, forwardRef, useRef } from 'react';
import { useLayoutEffect } from 'react-layout-effect';
import { isDev } from '@alloc/is-dev';
import { AutoDepth, useAutoDepth, useChanges } from './shared/79278540.mjs';
export { useChanges } from './shared/79278540.mjs';
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;
}
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(AutoDepth, {
value: 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;
}
if (isDev) {
withAuto.dev = (render) => {
const Component = render.length > 1 ? React.forwardRef(render) : render;
const AutoRender = React.forwardRef((props, ref) => {
const {auto, depth, commit} = useAutoRender(Component);
function useCommit(observer) {
let nonce = 0;
useLayoutEffect(() => commit(observer, nonce));
return () => {
nonce = observer.nonce;
return /* @__PURE__ */ React.createElement(RenderAction, {
useAction: () => {
auto.nonce = nonce;
}
});
};
}
return /* @__PURE__ */ React.createElement(AutoDepth, {
value: depth + 1
}, /* @__PURE__ */ React.createElement(Component, {
...props,
ref,
$auto: auto,
$useCommit: useCommit
}));
});
Object.defineProperty(AutoRender, "displayName", {
get: () => "AutoRender",
set(displayName) {
Component.displayName = displayName;
}
});
return AutoRender;
};
}
function useAutoRender(component) {
const depth = useAutoDepth();
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 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 { useAuto, useBinding, useDerived, useEffects, useO, withAuto };
//# sourceMappingURL=wana.mjs.map