UNPKG

wana

Version:

Easy observable state for React

319 lines (310 loc) 9.28 kB
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