import type { Ref, UnwrapRef } from 'vue'; import { onBeforeUnmount, ref } from 'vue'; export type Updater = (prev: State) => State; /** * Execute code before next frame but async */ export function useLayoutState( defaultState: State, ): [Ref, (updater: Updater) => void] { const stateRef = ref(defaultState); // const [, forceUpdate] = useState({}); const lastPromiseRef = ref>(null); const updateBatchRef = ref[]>([]); function setFrameState(updater: Updater) { updateBatchRef.value.push(updater); const promise = Promise.resolve(); lastPromiseRef.value = promise; promise.then(() => { if (lastPromiseRef.value === promise) { const prevBatch = updateBatchRef.value; // const prevState = stateRef.value; updateBatchRef.value = []; prevBatch.forEach(batchUpdater => { stateRef.value = batchUpdater(stateRef.value as State) as UnwrapRef; }); lastPromiseRef.value = null; } }); } onBeforeUnmount(() => { lastPromiseRef.value = null; }); return [stateRef as Ref, setFrameState]; } /** Lock frame, when frame pass reset the lock. */ export function useTimeoutLock( defaultState?: State, ): [(state: UnwrapRef) => void, () => UnwrapRef | null] { const frameRef = ref(defaultState || null); const timeoutRef = ref(); function cleanUp() { window.clearTimeout(timeoutRef.value); } function setState(newState: UnwrapRef) { frameRef.value = newState; cleanUp(); timeoutRef.value = window.setTimeout(() => { frameRef.value = null; timeoutRef.value = undefined; }, 100); } function getState() { return frameRef.value; } onBeforeUnmount(() => { cleanUp(); }); return [setState, getState]; }