mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-12 21:58:57 +02:00
79 lines
2.9 KiB
JavaScript
79 lines
2.9 KiB
JavaScript
import { reactive, watch, nextTick, onScopeDispose } from "vue";
|
|
|
|
export function getPath(object, path) {
|
|
return path?.split(".").reduce((value, key) => value?.[key], object);
|
|
}
|
|
|
|
// Page state inherits the shared session without copying it. All mutations are
|
|
// tracked by Vue, including those made by fetch callbacks and browser events.
|
|
export function createPageState(parent = null, events = new Map()) {
|
|
const own = reactive({});
|
|
const cleanups = [];
|
|
let disposed = false;
|
|
const state = new Proxy(own, {
|
|
get(target, key) {
|
|
// Forward Vue metadata so writes through this proxy notify render effects.
|
|
if ((typeof key === "string" && key.startsWith("__v_")) || Reflect.has(target, key)) return Reflect.get(target, key);
|
|
return parent?.[key];
|
|
},
|
|
});
|
|
Object.defineProperty(own, "parent", { value: parent, configurable: true });
|
|
Object.defineProperty(own, "viewState", { get: () => state });
|
|
state.watch = (source, callback, deep = false) => {
|
|
const read = typeof source === "function" ? () => source(state) : () => getPath(state, source);
|
|
const stop = watch(read, callback, { deep, flush: "post" });
|
|
cleanups.push(stop);
|
|
nextTick(() => { if (!disposed) callback(read(), read()); });
|
|
return stop;
|
|
};
|
|
state.watchGroup = (sources, callback) => state.watch(() => sources.map(s => getPath(state, s)), callback, true);
|
|
state.on = (name, callback) => {
|
|
if (name === "dispose") { cleanups.push(callback); return () => {}; }
|
|
if (!events.has(name)) events.set(name, new Set());
|
|
events.get(name).add(callback);
|
|
const off = () => events.get(name)?.delete(callback);
|
|
cleanups.push(off);
|
|
return off;
|
|
};
|
|
state.emit = (name, ...args) => {
|
|
for (const callback of [...(events.get(name) || [])]) callback({}, ...args);
|
|
};
|
|
state.dispose = () => {
|
|
disposed = true;
|
|
cleanups.splice(0).reverse().forEach(fn => fn());
|
|
};
|
|
onScopeDispose(state.dispose);
|
|
return state;
|
|
}
|
|
|
|
export function createTimers() {
|
|
const timeouts = new Set();
|
|
const intervals = new Set();
|
|
let disposed = false;
|
|
const timeout = (callback, delay = 0) => {
|
|
if (disposed) return;
|
|
const id = setTimeout(() => { timeouts.delete(id); callback(); }, delay);
|
|
timeouts.add(id);
|
|
return id;
|
|
};
|
|
timeout.cancel = id => { clearTimeout(id); timeouts.delete(id); };
|
|
const interval = (callback, delay) => {
|
|
if (disposed) return;
|
|
const id = setInterval(callback, delay);
|
|
intervals.add(id);
|
|
return id;
|
|
};
|
|
interval.cancel = id => { clearInterval(id); intervals.delete(id); };
|
|
onScopeDispose(() => { disposed = true; timeouts.forEach(clearTimeout); intervals.forEach(clearInterval); });
|
|
return { timeout, interval };
|
|
}
|
|
|
|
export function createListeners() {
|
|
const cleanups = [];
|
|
onScopeDispose(() => cleanups.forEach(fn => fn()));
|
|
return (target, type, callback, options) => {
|
|
target.addEventListener(type, callback, options);
|
|
cleanups.push(() => target.removeEventListener(type, callback, options));
|
|
};
|
|
}
|