UNPKG

@zag-js/store

Version:

The reactive store package for zag machines

226 lines (225 loc) • 7.13 kB
// src/proxy.ts import { getUntracked, markToTrack } from "proxy-compare"; import { globalRef, refSet } from "./global.mjs"; import { canProxy, isDev, isObject } from "./utils.mjs"; var proxyStateMap = globalRef("__zag__proxyStateMap", () => /* @__PURE__ */ new WeakMap()); var buildProxyFunction = (objectIs = Object.is, newProxy = (target, handler) => new Proxy(target, handler), snapCache = /* @__PURE__ */ new WeakMap(), createSnapshot = (target, version) => { const cache = snapCache.get(target); if (cache?.[0] === version) { return cache[1]; } const snap = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target)); markToTrack(snap, true); snapCache.set(target, [version, snap]); Reflect.ownKeys(target).forEach((key) => { const value = Reflect.get(target, key); if (refSet.has(value)) { markToTrack(value, false); snap[key] = value; } else if (proxyStateMap.has(value)) { snap[key] = snapshot(value); } else { snap[key] = value; } }); return Object.freeze(snap); }, proxyCache = /* @__PURE__ */ new WeakMap(), versionHolder = [1, 1], proxyFunction2 = (initialObject) => { if (!isObject(initialObject)) { throw new Error("object required"); } const found = proxyCache.get(initialObject); if (found) { return found; } let version = versionHolder[0]; const listeners = /* @__PURE__ */ new Set(); const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => { if (version !== nextVersion) { version = nextVersion; listeners.forEach((listener) => listener(op, nextVersion)); } }; let checkVersion = versionHolder[1]; const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => { if (checkVersion !== nextCheckVersion && !listeners.size) { checkVersion = nextCheckVersion; propProxyStates.forEach(([propProxyState]) => { const propVersion = propProxyState[1](nextCheckVersion); if (propVersion > version) { version = propVersion; } }); } return version; }; const createPropListener = (prop) => (op, nextVersion) => { const newOp = [...op]; newOp[1] = [prop, ...newOp[1]]; notifyUpdate(newOp, nextVersion); }; const propProxyStates = /* @__PURE__ */ new Map(); const addPropListener = (prop, propProxyState) => { if (isDev() && propProxyStates.has(prop)) { throw new Error("prop listener already exists"); } if (listeners.size) { const remove = propProxyState[3](createPropListener(prop)); propProxyStates.set(prop, [propProxyState, remove]); } else { propProxyStates.set(prop, [propProxyState]); } }; const removePropListener = (prop) => { const entry = propProxyStates.get(prop); if (entry) { propProxyStates.delete(prop); entry[1]?.(); } }; const addListener = (listener) => { listeners.add(listener); if (listeners.size === 1) { propProxyStates.forEach(([propProxyState, prevRemove], prop) => { if (isDev() && prevRemove) { throw new Error("remove already exists"); } const remove = propProxyState[3](createPropListener(prop)); propProxyStates.set(prop, [propProxyState, remove]); }); } const removeListener = () => { listeners.delete(listener); if (listeners.size === 0) { propProxyStates.forEach(([propProxyState, remove], prop) => { if (remove) { remove(); propProxyStates.set(prop, [propProxyState]); } }); } }; return removeListener; }; const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject)); const handler = { deleteProperty(target, prop) { const prevValue = Reflect.get(target, prop); removePropListener(prop); const deleted = Reflect.deleteProperty(target, prop); if (deleted) { notifyUpdate(["delete", [prop], prevValue]); } return deleted; }, set(target, prop, value, receiver) { const hasPrevValue = Reflect.has(target, prop); const prevValue = Reflect.get(target, prop, receiver); if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) { return true; } removePropListener(prop); if (isObject(value)) { value = getUntracked(value) || value; } let nextValue = value; if (Object.getOwnPropertyDescriptor(target, prop)?.set) { } else { if (!proxyStateMap.has(value) && canProxy(value)) { nextValue = proxy(value); } const childProxyState = !refSet.has(nextValue) && proxyStateMap.get(nextValue); if (childProxyState) { addPropListener(prop, childProxyState); } } Reflect.set(target, prop, nextValue, receiver); notifyUpdate(["set", [prop], value, prevValue]); return true; } }; const proxyObject = newProxy(baseObject, handler); proxyCache.set(initialObject, proxyObject); const proxyState = [baseObject, ensureVersion, createSnapshot, addListener]; proxyStateMap.set(proxyObject, proxyState); Reflect.ownKeys(initialObject).forEach((key) => { const desc = Object.getOwnPropertyDescriptor(initialObject, key); if (desc.get || desc.set) { Object.defineProperty(baseObject, key, desc); } else { proxyObject[key] = initialObject[key]; } }); return proxyObject; }) => [ // public functions proxyFunction2, // shared state proxyStateMap, refSet, // internal things objectIs, newProxy, canProxy, snapCache, createSnapshot, proxyCache, versionHolder ]; var [proxyFunction] = buildProxyFunction(); function proxy(initialObject = {}) { return proxyFunction(initialObject); } function getVersion(proxyObject) { const proxyState = proxyStateMap.get(proxyObject); return proxyState?.[1](); } function subscribe(proxyObject, callback, notifyInSync) { const proxyState = proxyStateMap.get(proxyObject); if (isDev() && !proxyState) { console.warn("Please use proxy object"); } let promise; const ops = []; const addListener = proxyState[3]; let isListenerActive = false; const listener = (op) => { ops.push(op); if (notifyInSync) { callback(ops.splice(0)); return; } if (!promise) { promise = Promise.resolve().then(() => { promise = void 0; if (isListenerActive) { callback(ops.splice(0)); } }); } }; const removeListener = addListener(listener); isListenerActive = true; return () => { isListenerActive = false; removeListener(); }; } function snapshot(proxyObject) { const proxyState = proxyStateMap.get(proxyObject); if (isDev() && !proxyState) { console.warn("Please use proxy object"); } const [target, ensureVersion, createSnapshot] = proxyState; return createSnapshot(target, ensureVersion()); } function ref(obj) { refSet.add(obj); return obj; } export { getVersion, proxy, ref, snapshot, subscribe };