UNPKG

@scalar/api-reference

Version:

Generate beautiful API references from OpenAPI documents

157 lines (156 loc) 4.54 kB
import { watchDebounced } from "@vueuse/core"; import { nanoid } from "nanoid"; import { computed, ref, reactive, onBeforeUnmount, nextTick } from "vue"; import { getSchemaParamsFromId } from "./id-routing.js"; const priorityQueue = reactive(/* @__PURE__ */ new Set()); const pendingQueue = reactive(/* @__PURE__ */ new Set()); const readyQueue = reactive(/* @__PURE__ */ new Set()); const isRunning = ref(false); const firstLazyLoadComplete = ref(false); const intersectionBlockers = reactive(/* @__PURE__ */ new Set()); const onRenderComplete = /* @__PURE__ */ new Set(); const addLazyCompleteCallback = (callback) => { if (callback) { onRenderComplete.add(callback); } }; const blockIntersection = () => { const blockId = nanoid(); intersectionBlockers.add(blockId); return () => setTimeout(() => intersectionBlockers.delete(blockId), 100); }; const intersectionEnabled = computed(() => intersectionBlockers.size === 0); const runLazyBus = () => { if (typeof window === "undefined") { return; } const unblock = blockIntersection(); const processQueue = async () => { if (pendingQueue.size > 0 || priorityQueue.size > 0) { isRunning.value = true; for (const id of [...pendingQueue, ...priorityQueue]) { readyQueue.add(id); pendingQueue.delete(id); priorityQueue.delete(id); } } await nextTick(); onRenderComplete.forEach((fn) => fn()); onRenderComplete.clear(); unblock(); isRunning.value = false; firstLazyLoadComplete.value = true; }; if (window.requestIdleCallback) { window.requestIdleCallback(processQueue, { timeout: 1500 }); } else { nextTick(processQueue); } }; watchDebounced( [() => pendingQueue.size, () => priorityQueue.size, () => isRunning.value], () => { if ((pendingQueue.size > 0 || priorityQueue.size > 0) && !isRunning.value) { runLazyBus(); } }, { debounce: 300, maxWait: 1500 } ); const addToPendingQueue = (id) => { if (!!id && !priorityQueue.has(id)) { pendingQueue.add(id); } }; const addToPriorityQueue = (id) => { if (id) { priorityQueue.add(id); } }; const resetLazyElement = (id) => { priorityQueue.delete(id); pendingQueue.delete(id); readyQueue.delete(id); }; function useLazyBus(id) { addToPendingQueue(id); onBeforeUnmount(() => { resetLazyElement(id); }); return { isReady: computed(() => typeof window === "undefined" || priorityQueue.has(id) || readyQueue.has(id)) }; } const scrollToLazy = (id, setExpanded, getEntryById) => { const item = getEntryById(id); const isLazy = !readyQueue.has(id) || item?.children?.some((child) => !readyQueue.has(child.id)); const unfreeze = isLazy ? freeze(id) : void 0; addLazyCompleteCallback(unfreeze); const unblock = blockIntersection(); const { rawId } = getSchemaParamsFromId(id); addToPriorityQueue(id); addToPriorityQueue(rawId); if (item?.children) { item.children.slice(0, 2).forEach((child) => { addToPriorityQueue(child.id); }); } if (item?.parent) { const parent = getEntryById(item.parent.id); const elementIdx = parent?.children?.findIndex((child) => child.id === id); if (elementIdx !== void 0 && elementIdx >= 0) { parent?.children?.slice(elementIdx, elementIdx + 2).forEach((child) => { addToPriorityQueue(child.id); }); } } tryScroll(id, Date.now() + 1e3, unblock, unfreeze); setExpanded(rawId, true); const addParents = (currentId) => { const parent = getEntryById(currentId)?.parent; if (parent) { addToPriorityQueue(parent.id); setExpanded(parent.id, true); addParents(parent.id); } }; addParents(rawId); }; const tryScroll = (id, stopTime, onComplete, onFailure) => { const element = document.getElementById(id); if (element) { element.scrollIntoView({ block: "start" }); onComplete(); } else if (Date.now() < stopTime) { requestAnimationFrame(() => tryScroll(id, stopTime, onComplete)); } else { onComplete(); onFailure?.(); } }; const freeze = (id) => { let stop = false; const runFrame = (stopAfterFrame) => { const element = document.getElementById(id); if (element) { element.scrollIntoView({ block: "start" }); } if (!stopAfterFrame) { requestAnimationFrame(() => runFrame(stop)); } }; runFrame(false); return () => { stop = true; }; }; export { blockIntersection, firstLazyLoadComplete, intersectionEnabled, scrollToLazy, useLazyBus };