@scalar/api-reference
Version:
Generate beautiful API references from OpenAPI documents
157 lines (156 loc) • 4.54 kB
JavaScript
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
};