@zag-js/store
Version:
The reactive store package for zag machines
306 lines (300 loc) • 10.7 kB
JavaScript
import { markToTrack, getUntracked } from 'proxy-compare';
// src/global.ts
function glob() {
if (typeof globalThis !== "undefined") return globalThis;
if (typeof self !== "undefined") return self;
if (typeof window !== "undefined") return window;
if (typeof global !== "undefined") return global;
}
function globalRef(key, value) {
const g = glob();
if (!g) return value();
g[key] || (g[key] = value());
return g[key];
}
var refSet = globalRef("__zag__refSet", () => /* @__PURE__ */ new WeakSet());
// src/utils.ts
var isReactElement = (x) => typeof x === "object" && x !== null && "$$typeof" in x && "props" in x;
var isVueElement = (x) => typeof x === "object" && x !== null && "__v_isVNode" in x;
var isDOMElement = (x) => typeof x === "object" && x !== null && "nodeType" in x && typeof x.nodeName === "string";
var isElement = (x) => isReactElement(x) || isVueElement(x) || isDOMElement(x);
var isObject = (x) => x !== null && typeof x === "object";
var canProxy = (x) => isObject(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !isElement(x) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer) && !(x instanceof Promise);
var isDev = () => process.env.NODE_ENV !== "production";
// src/clone.ts
function set(obj, key, val) {
if (typeof val.value === "object" && !canProxy(val.value)) val.value = clone(val.value);
if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === "__proto__") {
Object.defineProperty(obj, key, val);
} else obj[key] = val.value;
}
function clone(x) {
if (typeof x !== "object") return x;
var i = 0, k, list, tmp, str = Object.prototype.toString.call(x);
if (str === "[object Object]") {
tmp = Object.create(Object.getPrototypeOf(x) || null);
} else if (str === "[object Array]") {
tmp = Array(x.length);
} else if (str === "[object Set]") {
tmp = /* @__PURE__ */ new Set();
x.forEach(function(val) {
tmp.add(clone(val));
});
} else if (str === "[object Map]") {
tmp = /* @__PURE__ */ new Map();
x.forEach(function(val, key) {
tmp.set(clone(key), clone(val));
});
} else if (str === "[object Date]") {
tmp = /* @__PURE__ */ new Date(+x);
} else if (str === "[object RegExp]") {
tmp = new RegExp(x.source, x.flags);
} else if (str === "[object DataView]") {
tmp = new x.constructor(clone(x.buffer));
} else if (str === "[object ArrayBuffer]") {
tmp = x.slice(0);
} else if (str === "[object Blob]") {
tmp = x.slice();
} else if (str.slice(-6) === "Array]") {
tmp = new x.constructor(x);
}
if (tmp) {
for (list = Object.getOwnPropertySymbols(x); i < list.length; i++) {
set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i]));
}
for (i = 0, list = Object.getOwnPropertyNames(x); i < list.length; i++) {
if (Object.hasOwnProperty.call(tmp, k = list[i]) && tmp[k] === x[k]) continue;
set(tmp, k, Object.getOwnPropertyDescriptor(x, k));
}
}
return tmp || x;
}
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 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;
}
// src/proxy-computed.ts
function proxyWithComputed(initialObject, computedFns) {
const keys = Object.keys(computedFns);
keys.forEach((key) => {
if (Object.getOwnPropertyDescriptor(initialObject, key)) {
throw new Error("object property already defined");
}
const computedFn = computedFns[key];
const { get, set: set2 } = typeof computedFn === "function" ? { get: computedFn } : computedFn;
const desc = {};
desc.get = () => get(snapshot(proxyObject));
if (set2) {
desc.set = (newValue) => set2(proxyObject, newValue);
}
Object.defineProperty(initialObject, key, desc);
});
const proxyObject = proxy(initialObject);
return proxyObject;
}
export { clone, globalRef, proxy, proxyWithComputed, ref, snapshot, subscribe };