UNPKG

@scalar/api-reference

Version:

Generate beautiful API references from OpenAPI documents

735 lines (734 loc) 34.1 kB
import { AGENT_CONTEXT_SYMBOL, useAgent } from "../hooks/use-agent.js"; import AgentScalarButton_default from "./AgentScalar/AgentScalarButton.vue.js"; import AgentScalarDrawer_default from "./AgentScalar/AgentScalarDrawer.vue.js"; import OpenMCPButton_default from "./AgentScalar/OpenMCPButton.vue.js"; import ClassicHeader_default from "./ClassicHeader.vue.js"; import { useIntersection } from "../hooks/use-intersection.js"; import { createPluginManager } from "../plugins/plugin-manager.js"; import { PLUGIN_MANAGER_SYMBOL } from "../plugins/hooks/usePluginManager.js"; import { persistencePlugin } from "../plugins/persistance-plugin.js"; import { getIdFromUrl, makeUrlFromId, matchesBasePath } from "../helpers/id-routing.js"; import { addToPriorityQueue, blockIntersection, intersectionEnabled, scrollToLazy } from "../helpers/lazy-bus.js"; import Content_default from "./Content/Content.vue.js"; import MobileHeader_default from "./MobileHeader.vue.js"; import DeveloperTools_default from "../features/developer-tools/DeveloperTools.vue.js"; import DocumentSelector_default from "../features/multiple-documents/DocumentSelector.vue.js"; import SearchButton_default from "../features/Search/components/SearchButton.vue.js"; import { getSystemModePreference } from "../helpers/color-mode.js"; import { downloadDocument } from "../helpers/download.js"; import { loadAuthFromStorage, loadClientFromStorage } from "../helpers/load-from-perssistance.js"; import { mapConfigPlugins } from "../helpers/map-config-plugins.js"; import { mapConfigToWorkspaceStore } from "../helpers/map-config-to-workspace-store.js"; import { normalizeConfigurations } from "../helpers/normalize-configurations.js"; import { safeDeepClone } from "../helpers/safe-deep-clone.js"; import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, guardReactiveProps, normalizeClass, normalizeProps, onBeforeMount, onBeforeUnmount, onMounted, onServerPrefetch, openBlock, provide, ref, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, useTemplateRef, watch, withCtx } from "vue"; import { provideUseId } from "@headlessui/vue"; import { OpenApiClientButton } from "@scalar/api-client/blocks/operation-block"; import { createApiClientModal } from "@scalar/api-client/modal"; import { ScalarColorModeToggleButton, ScalarColorModeToggleIcon, ScalarSidebarFooter, addScalarClassesToHeadless } from "@scalar/components"; import { isLocalUrl } from "@scalar/helpers/url/is-local-url"; import { ScalarSidebar, createSidebarState, scrollSidebarToTop } from "@scalar/sidebar"; import { getThemeStyles, hasObtrusiveScrollbars } from "@scalar/themes"; import { apiReferenceConfigurationSchema } from "@scalar/types/api-reference"; import { useClipboard } from "@scalar/use-hooks/useClipboard"; import { useColorMode } from "@scalar/use-hooks/useColorMode"; import { ScalarToasts } from "@scalar/use-toasts"; import { createWorkspaceStore } from "@scalar/workspace-store/client"; import { createWorkspaceEventBus } from "@scalar/workspace-store/events"; import { getActiveEnvironment, getServers } from "@scalar/workspace-store/request-example"; import { useScrollLock } from "@vueuse/core"; import diff from "microdiff"; //#region src/components/ApiReference.vue?vue&type=script&setup=true&lang.ts var _hoisted_1 = { key: 1, class: "flex gap-1.5 px-3 pt-3" }; var _hoisted_2 = { key: 1 }; var _hoisted_3 = ["aria-label", "inert"]; var _hoisted_4 = { class: "w-64 empty:hidden" }; var _hoisted_5 = { key: 2, class: "references-footer" }; var version = "1.55.3"; if (typeof window !== "undefined") console.info(`@scalar/api-reference@${version}`); var ApiReference_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ApiReference", props: { configuration: {} }, setup(__props, { expose: __expose }) { const props = __props; const { copyToClipboard } = useClipboard(); /** * Used to inject the environment into built packages * * Primary use case is the open-in-client button */ const isDevelopment = false; const obtrusiveScrollbars = computed(hasObtrusiveScrollbars); const eventBus = createWorkspaceEventBus({ debug: isDevelopment }); const isSidebarOpen = ref(false); /** * Due to a bug in headless UI, we need to set an ID here that can be shared across server/client * TODO remove this once the bug is fixed * * @see https://github.com/tailwindlabs/headlessui/issues/2979 */ provideUseId(() => useId()); /** * Configuration Handling * * We will normalize the configurations and store them in a computed property. * The active configuration will be associated with the active document. */ const configList = computed(() => normalizeConfigurations(props.configuration)); const isMultiDocument = computed(() => Object.keys(configList.value).length > 1); /** Search for the source with a default attribute or use the first one */ const activeSlug = ref(Object.values(configList.value).find((c) => c.default)?.slug ?? configList.value[Object.keys(configList.value)?.[0] ?? ""]?.slug ?? ""); /** * On initial page load we need to determine if there is a valid document slug in the URL * * If there is we set the active slug to the document slug */ if (typeof window !== "undefined") { const url = new URL(window.location.href); const apiParam = url.searchParams.get("api"); if (apiParam && configList.value[apiParam]) { activeSlug.value = apiParam; const newUrl = makeUrlFromId(getIdFromUrl(url, configList.value[apiParam].config.pathRouting?.basePath, apiParam), configList.value[apiParam].config.pathRouting?.basePath, isMultiDocument.value); if (newUrl) { newUrl.searchParams.delete("api"); window.history.replaceState({}, "", newUrl.toString()); } } const documentSlug = getIdFromUrl(url, Object.values(configList.value).map((c) => c.config.pathRouting?.basePath).find((p) => p ? matchesBasePath(url, p) : false), isMultiDocument.value ? void 0 : activeSlug.value).split("/")[0]; if (documentSlug && configList.value[documentSlug]) activeSlug.value = documentSlug; } /** Computed document options list for the selector logic */ const documentOptionList = computed(() => Object.values(configList.value).map((c) => ({ label: c.title, id: c.slug }))); /** Configuration overrides to apply to the selected document (from the localhost toolbar) */ const configurationOverrides = ref({}); /** Any dev toolbar modifications are merged with the active configuration */ const mergedConfig = computed(() => ({ ...apiReferenceConfigurationSchema.parse({}), ...configList.value[activeSlug.value]?.config, ...configurationOverrides.value })); /** Convenience break out var to determine which routing mode we are using */ const basePath = computed(() => mergedConfig.value.pathRouting?.basePath); const themeStyle = computed(() => getThemeStyles(mergedConfig.value.theme, { fonts: mergedConfig.value.withDefaultFonts })); /** Plugin injection is not reactive. All plugins must be provided at first render */ const pluginManager = createPluginManager({ plugins: Object.values(configList.value).flatMap((c) => c.config.plugins ?? []) }); provide(PLUGIN_MANAGER_SYMBOL, pluginManager); pluginManager.notifyInit(mergedConfig.value); watch(mergedConfig, (config) => pluginManager.notifyConfigChange(config)); /** Navigation State Handling */ if (mergedConfig.value.redirect && typeof window !== "undefined") { const newPath = mergedConfig.value.redirect((mergedConfig.value.pathRouting ? window.location.pathname : "") + window.location.hash); if (newPath) window.history.replaceState({}, "", newPath); } /** * Sets the active slug and updates the URL with the selected document slug * * If an element ID is passed in we will configure the path or hash routing */ function syncSlugAndUrlWithDocument(slug, elementId, config) { const url = makeUrlFromId(elementId || slug, config.pathRouting?.basePath, isMultiDocument.value); if (url) window.history.replaceState({}, "", url.toString()); activeSlug.value = slug; } /** Workspace Store Initialization */ /** * Initializes the new client workspace store. */ const workspaceStore = createWorkspaceStore({ verbose: isDevelopment }); /** * We need to keep the client store separate from the workspace store * This is because we want the client store to be a playground where users can test out their requests without affecting the references store */ const clientStore = createWorkspaceStore({ verbose: isDevelopment, plugins: [persistencePlugin({ prefix: () => activeSlug.value, persistAuth: () => mergedConfig.value.persistAuth ?? false })] }); const { toggleColorMode, isDarkMode } = useColorMode({ initialColorMode: { true: "dark", false: "light", undefined: "system" }[String(mergedConfig.value.darkMode)], overrideColorMode: mergedConfig.value.forceDarkModeState }); /** Initialize the sidebar */ const sidebarState = createSidebarState(computed(() => { return Object.entries(workspaceStore.workspace.documents).map(([slug, document]) => ({ id: slug, type: "document", description: document.info.description, name: document.info.title ?? slug, title: document.info.title ?? slug, children: document?.["x-scalar-navigation"]?.children ?? [] })); }), { hooks: {} }); /** Recursively set all children of the given items to open */ const setChildrenOpen = (items) => { items.forEach((item) => { if (item.type === "tag" || item.type === "models") sidebarState.setExpanded(item.id, true); if ("children" in item && item.children) setChildrenOpen(item.children); }); }; /** We get the sub items for the sidebar based on the configuration/document slug */ const sidebarItems = computed(() => { const config = mergedConfig.value; if (!config) return []; const docItems = sidebarState.items.value.find((item) => item.id === activeSlug.value)?.children ?? []; if (config.defaultOpenAllTags) setChildrenOpen(docItems); if (config.expandAllModelSections) { const models = docItems.find((item) => item.type === "models"); if (models) { sidebarState.setExpanded(models.id, true); models.children?.forEach((child) => { sidebarState.setExpanded(child.id, true); }); } } return docItems; }); /** Find the sidebar entry that represents the introduction section */ const infoSectionId = computed(() => sidebarItems.value.find((item) => item.type === "text" && item.title === "Introduction")?.id ?? `${activeSlug.value}/description/introduction`); /** User for mobile navigation */ const breadcrumb = ref(""); const slotProps = computed(() => ({ breadcrumb: breadcrumb.value })); const setBreadcrumb = (id) => { const item = sidebarState.getEntryById(id); if (!item || item.type === "document") breadcrumb.value = ""; else breadcrumb.value = item.title; }; const scrollToLazyElement = (id) => { setBreadcrumb(id); sidebarState.setSelected(id); scrollToLazy(id, sidebarState.setExpanded, sidebarState.getEntryById); }; /** Maps some config values to the workspace store to keep it reactive */ mapConfigToWorkspaceStore({ config: () => mergedConfig.value, store: workspaceStore, isDarkMode }); mapConfigToWorkspaceStore({ config: () => mergedConfig.value, store: clientStore, isDarkMode }); /** Merged environment variables from workspace and document levels */ const environment = computed(() => getActiveEnvironment(workspaceStore, workspaceStore.workspace.activeDocument ?? null).environment); if (typeof window !== "undefined") window.dataDumpWorkspace = () => workspaceStore; __expose({ eventBus, workspaceStore, sidebarItems }); /** * Computes a mapping from model names to their sidebar entry IDs. * This is used for quick lookups and navigation within the sidebar. */ const modelsIndex = computed(() => { return sidebarItems.value.filter((item) => item.type === "models").flatMap((item) => item.children ?? []).filter((item) => item.type === "model").reduce((acc, item) => { acc[item.name] = item.id; return acc; }, {}); }); eventBus.on("scroll-to:model-by-name", ({ name }) => { /** Find the model in the models index */ const model = modelsIndex.value[name]; if (model) scrollToLazyElement(model); }); const addDocument = async (input, navigationOptions) => { const result = await workspaceStore.addDocument(input, navigationOptions); const state = workspaceStore.exportWorkspace(); clientStore.loadWorkspace({ auth: {}, documents: { [input.name]: safeDeepClone(state.documents[input.name]) ?? { "openapi": "3.1.0", "info": { title: "", version: "" }, "x-scalar-original-document-hash": "" } }, intermediateDocuments: {}, originalDocuments: {}, overrides: {}, history: {}, meta: {} }); return result; }; /** * Handle changing the active document * * 1. If the document has not be loaded to the workspace store we set it to empty and asynchronously load it * 2. If the document has been loaded to the workspace store we just set it to active * 3. If the content from the configuration has changes we need to update the document in the workspace store */ const changeSelectedDocument = async (slug, elementId) => { const normalized = configList.value[slug]; if (!normalized) { console.warn(`Document ${slug} not found in configList`); return; } const config = { ...normalized.config, ...configurationOverrides.value }; const onDocumentSelectPromise = config.onDocumentSelect?.(); syncSlugAndUrlWithDocument(slug, elementId, config); apiClient.value?.route({ documentSlug: slug, method: "get", path: "/" }); if (!workspaceStore.workspace.documents[slug]) { const result = await addDocument(normalized.source.url ? { name: slug, url: normalized.source.url, fetch: config.fetch } : { name: slug, document: normalized.source.content ?? {} }, config); const document = clientStore.workspace.documents[slug]; if (result === true && document !== void 0 && document["x-scalar-selected-server"] === void 0) { const servers = getServers(normalized.config.servers ?? document.servers, { baseServerUrl: mergedConfig.value.baseServerURL, documentUrl: normalized.source.url }); if (servers.length > 0) clientStore.updateDocument(slug, "x-scalar-selected-server", servers[0].url); } } workspaceStore.update("x-scalar-active-document", slug); clientStore.update("x-scalar-active-document", slug); if (config.persistAuth) loadAuthFromStorage(clientStore, slug); (async () => { await onDocumentSelectPromise; config.onLoaded?.(slug); })(); if (elementId && elementId !== slug) scrollToLazyElement(elementId); else if (config.defaultOpenFirstTag) { const firstTag = sidebarItems.value.find((item) => item.type === "tag"); if (firstTag) sidebarState.setExpanded(firstTag.id, true); } }; /** * TODO:Move this to a dedicated updateDocument function in the future and * away from vue-reactivity based updates */ watch(() => Object.values(configList.value), async (newConfigList, oldConfigList) => { /** * Handles replacing and updating documents within the workspace store * when we detect configuration changes. */ const updateSource = async (updated, previous) => { /** If we have not loaded the document previously we don't need to handle any updates to store */ if (!workspaceStore.workspace.documents[updated.slug]) return; /** If the URL has changed we fetch and rebase */ if (updated.source.url && updated.source.url !== previous?.source.url) { await addDocument({ name: updated.slug, url: updated.source.url, fetch: updated.config.fetch }, updated.config); return; } if (!updated.source.content) return; /** * We need to deeply check for document changes. Parse documents and then only rebase * if we detect deep changes in the two sources */ if (diff(updated.source.content, previous && "content" in previous.source ? previous.source.content ?? {} : {}).length) await addDocument({ name: updated.slug, document: updated.source.content }, updated.config); }; newConfigList.forEach((newConfig, index) => updateSource(newConfig, oldConfigList[index])); const newSlugs = newConfigList.map((c) => c.slug); const oldSlugs = oldConfigList.map((c) => c.slug); if (newSlugs.length !== oldSlugs.length || !newSlugs.every((slug, index) => slug === oldSlugs[index])) await changeSelectedDocument(newSlugs[0] ?? ""); }, { deep: true }); /** Preload the first document during SSR */ onServerPrefetch(() => changeSelectedDocument(activeSlug.value)); /** Load the first document on page load */ onBeforeMount(async () => { loadClientFromStorage(clientStore); await changeSelectedDocument(activeSlug.value, getIdFromUrl(window.location.href, configList.value[activeSlug.value]?.config.pathRouting?.basePath, isMultiDocument.value ? void 0 : activeSlug.value)); }); const documentUrl = computed(() => { return configList.value[activeSlug.value]?.source?.url; }); /** * Determines if Agent Scalar should be enabled based on the configuration and the current URL * * - If the agent is disabled in the configuration, it should not be enabled * - If the current URL is a local URL, it should be enabled * - If the agent key is set, it should be enabled */ const agent = useAgent({ agentEnabled: computed(() => { if (configList.value[activeSlug.value]?.agent?.disabled) return false; if (typeof window !== "undefined" && isLocalUrl(window.location.href)) return true; return Boolean(configList.value[activeSlug.value]?.agent?.key); }) }); provide(AGENT_CONTEXT_SYMBOL, agent); const modal = useTemplateRef("modal"); const apiClient = ref(null); onMounted(() => { if (!modal.value) return; apiClient.value = createApiClientModal({ el: modal.value, eventBus, workspaceStore: clientStore, options: mergedConfig, plugins: [...pluginManager.getApiClientPlugins(), ...mapConfigPlugins(mergedConfig, environment)] }); }); onBeforeUnmount(() => { pluginManager.notifyDestroy(); apiClient.value?.app.unmount(); }); /** Ensure we call the onServerChange callback */ eventBus.on("server:update:selected", ({ url }) => mergedConfig.value.onServerChange?.(url)); /** Download the document from the store */ eventBus.on("ui:download:document", ({ format }) => { const document = workspaceStore.exportActiveDocument(format); if (!document) { console.error("No document found to download"); return; } downloadDocument(document, activeSlug.value ?? "openapi", format); }); /** * Handler for a direct navigation event such as a sidebar or search click * * Depending on the item type we handle a selection event differently: * * - Tag: If a tag is closed we open it and all its parents and scroll to it * If a tag is open we just close the tag * - Operation: * Open all parents and scroll to the operation */ const handleSelectSidebarEntry = (id, caller) => { const item = sidebarState.getEntryById(id); if ((item?.type === "tag" || item?.type === "models" || item?.type === "text") && sidebarState.isExpanded(id) && sidebarState.selectedItem.value === id) { const unblock = blockIntersection(); sidebarState.setExpanded(id, false); unblock(); return; } if (item?.type !== "tag" && item?.type !== "models") isSidebarOpen.value = false; scrollToLazyElement(id); const url = makeUrlFromId(id, basePath.value, isMultiDocument.value); if (url) { window.history.pushState({}, "", url); if (caller === "sidebar") mergedConfig.value.onSidebarClick?.(url.toString()); } if (agent.showAgent.value) agent.closeAgent(); }; /** Handle a navigation item selection event */ eventBus.on("select:nav-item", ({ id }) => handleSelectSidebarEntry(id)); /** Handle a scroll to navigation item event */ eventBus.on("scroll-to:nav-item", ({ id }) => handleSelectSidebarEntry(id)); /** Handle an intersecting navigation item event */ eventBus.on("intersecting:nav-item", ({ id }) => { if (!intersectionEnabled.value) return; sidebarState.setSelected(id); setBreadcrumb(id); scrollSidebarToTop(id); const url = makeUrlFromId(id, basePath.value, isMultiDocument.value); if (url && workspaceStore.workspace.activeDocument) window.history.replaceState({}, "", url.toString()); }); eventBus.on("toggle:nav-item", ({ id, open }) => { if (open) { mergedConfig.value.onShowMore?.(id); const entry = sidebarState.getEntryById(id); if (entry && "children" in entry && entry.children) { const first = entry.children[0]; if (first) addToPriorityQueue(first.id); } } sidebarState.setExpanded(id, open ?? !sidebarState.isExpanded(id)); }); eventBus.on("copy-url:nav-item", ({ id }) => { const url = makeUrlFromId(id, basePath.value, isMultiDocument.value)?.toString(); return url && copyToClipboard(url); }); onBeforeMount(() => { window.history.scrollRestoration = "manual"; addScalarClassesToHeadless(); window.addEventListener("popstate", () => { const id = getIdFromUrl(window.location.href, mergedConfig.value.pathRouting?.basePath, isMultiDocument.value ? void 0 : activeSlug.value); if (id) scrollToLazyElement(id); }); }); const documentStartRef = useTemplateRef("documentStartRef"); /** * Uses `immediate` so the sentinel fires as soon as it enters the viewport (not just at the center strip). * When the user scrolls away from the top, both this observer and the first section's center-strip * observer are intersecting simultaneously, so the section observer does not re-fire on its own. * The `onExit` callback bridges that gap by finding whichever section is at the viewport center * and re-emitting the nav event for it. */ useIntersection(documentStartRef, () => { eventBus.emit("intersecting:nav-item", { id: activeSlug.value }); }, { onExit: () => { const centerY = window.innerHeight / 2; const section = document.elementsFromPoint(window.innerWidth / 2, centerY).find((el) => el.tagName === "SECTION" && el.id); if (section?.id) eventBus.emit("intersecting:nav-item", { id: section.id }); }, immediate: true }); const colorMode = computed(() => { const mode = workspaceStore.workspace["x-scalar-color-mode"]; if (mode === "system") return getSystemModePreference(); return mode; }); const bodyScrollLocked = useScrollLock(typeof document !== "undefined" ? document.body : null); watch(agent.showAgent, () => bodyScrollLocked.value = agent.showAgent.value); const showMCPButton = computed(() => { if (mergedConfig.value.mcp?.disabled) return false; if (typeof window !== "undefined" && isLocalUrl(window.location.href)) return true; if (mergedConfig.value.mcp) return true; return false; }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", null, [ (openBlock(), createBlock(resolveDynamicComponent("style"), null, { default: withCtx(() => [createTextVNode(toDisplayString(mergedConfig.value.customCss) + " " + toDisplayString(themeStyle.value), 1)]), _: 1 })), createElementVNode("div", { ref: "documentEl", class: normalizeClass(["scalar-app scalar-api-reference references-layout", [{ "scalar-api-references-standalone-mobile": mergedConfig.value.showSidebar, "scalar-scrollbars-obtrusive": obtrusiveScrollbars.value, "references-editable": mergedConfig.value.isEditable, "references-sidebar": mergedConfig.value.showSidebar, "references-sidebar-mobile-open": isSidebarOpen.value, "references-classic": mergedConfig.value.layout === "classic" }, _ctx.$attrs.class]]) }, [ unref(agent).agentEnabled.value ? (openBlock(), createBlock(unref(AgentScalarDrawer_default), { key: 0, agentScalarConfiguration: configList.value[activeSlug.value]?.agent, externalUrls: mergedConfig.value.externalUrls, workspaceStore: unref(workspaceStore) }, null, 8, [ "agentScalarConfiguration", "externalUrls", "workspaceStore" ])) : createCommentVNode("", true), mergedConfig.value.layout === "modern" ? (openBlock(), createBlock(MobileHeader_default, { key: 1, breadcrumb: breadcrumb.value, isSidebarOpen: isSidebarOpen.value, showSidebar: mergedConfig.value.showSidebar, onToggleSidebar: _cache[3] || (_cache[3] = () => isSidebarOpen.value = !isSidebarOpen.value) }, { search: withCtx(() => [!mergedConfig.value.hideSearch ? (openBlock(), createBlock(SearchButton_default, { key: 0, class: "my-2", document: unref(workspaceStore).workspace.activeDocument, eventBus: unref(eventBus), hideModels: mergedConfig.value.hideModels, searchHotKey: mergedConfig.value.searchHotKey, showSidebar: mergedConfig.value.showSidebar }, null, 8, [ "document", "eventBus", "hideModels", "searchHotKey", "showSidebar" ])) : createCommentVNode("", true)]), sidebar: withCtx(({ sidebarClasses }) => [mergedConfig.value.showSidebar && mergedConfig.value.layout === "modern" ? (openBlock(), createBlock(unref(ScalarSidebar), { key: 0, "aria-label": `Sidebar for ${unref(workspaceStore).workspace.activeDocument?.info?.title}`, class: normalizeClass(["t-doc__sidebar", sidebarClasses]), isExpanded: unref(sidebarState).isExpanded, isSelected: unref(sidebarState).isSelected, items: sidebarItems.value, layout: "reference", options: mergedConfig.value, role: "navigation", onSelectItem: _cache[1] || (_cache[1] = (id) => handleSelectSidebarEntry(id, "sidebar")), onToggleGroup: _cache[2] || (_cache[2] = (id) => unref(sidebarState).setExpanded(id, !unref(sidebarState).isExpanded(id))) }, { header: withCtx(() => [ documentOptionList.value.length > 1 ? (openBlock(), createBlock(DocumentSelector_default, { key: 0, modelValue: activeSlug.value, options: documentOptionList.value, "onUpdate:modelValue": changeSelectedDocument }, null, 8, ["modelValue", "options"])) : createCommentVNode("", true), !mergedConfig.value.hideSearch ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(SearchButton_default, { document: unref(workspaceStore).workspace.activeDocument, eventBus: unref(eventBus), hideModels: mergedConfig.value.hideModels, searchHotKey: mergedConfig.value.searchHotKey }, null, 8, [ "document", "eventBus", "hideModels", "searchHotKey" ]), unref(agent).agentEnabled.value ? (openBlock(), createBlock(unref(AgentScalarButton_default), { key: 0 })) : createCommentVNode("", true)])) : createCommentVNode("", true), renderSlot(_ctx.$slots, "sidebar-start", normalizeProps(guardReactiveProps(slotProps.value)), void 0, true) ]), footer: withCtx(() => [renderSlot(_ctx.$slots, "sidebar-end", normalizeProps(guardReactiveProps(slotProps.value)), () => [createVNode(unref(ScalarSidebarFooter), { class: "darklight-reference" }, { toggle: withCtx(() => [!mergedConfig.value.hideDarkModeToggle && !mergedConfig.value.forceDarkModeState ? (openBlock(), createBlock(unref(ScalarColorModeToggleButton), { key: 0, modelValue: colorMode.value === "dark", "onUpdate:modelValue": _cache[0] || (_cache[0] = () => unref(toggleColorMode)()) }, null, 8, ["modelValue"])) : (openBlock(), createElementBlock("span", _hoisted_2))]), default: withCtx(() => [!mergedConfig.value.hideClientButton && !showMCPButton.value ? (openBlock(), createBlock(unref(OpenApiClientButton), { key: 0, buttonSource: "sidebar", integration: mergedConfig.value._integration, isDevelopment: unref(isDevelopment), url: documentUrl.value }, null, 8, [ "integration", "isDevelopment", "url" ])) : createCommentVNode("", true), showMCPButton.value ? (openBlock(), createBlock(unref(OpenMCPButton_default), { key: 1, config: mergedConfig.value.mcp, externalUrls: mergedConfig.value.externalUrls, isDevelopment: unref(isDevelopment), url: documentUrl.value, workspace: unref(workspaceStore) }, null, 8, [ "config", "externalUrls", "isDevelopment", "url", "workspace" ])) : createCommentVNode("", true)]), _: 1 })], true)]), _: 3 }, 8, [ "aria-label", "class", "isExpanded", "isSelected", "items", "options" ])) : createCommentVNode("", true)]), _: 3 }, 8, [ "breadcrumb", "isSidebarOpen", "showSidebar" ])) : createCommentVNode("", true), createElementVNode("main", { "aria-label": `Open API Documentation for ${unref(workspaceStore).workspace.activeDocument?.info?.title}`, class: "references-rendered", inert: unref(agent).showAgent.value }, [createVNode(Content_default, { authStore: unref(clientStore).auth, clientDocument: unref(clientStore).workspace.activeDocument, document: unref(workspaceStore).workspace.activeDocument, environment: environment.value, eventBus: unref(eventBus), expandedItems: unref(sidebarState).expandedItems.value, headingSlugGenerator: mergedConfig.value.generateHeadingSlug ?? ((heading) => `${activeSlug.value}/description/${heading.slug}`), infoSectionId: infoSectionId.value, items: sidebarItems.value, options: mergedConfig.value, xScalarDefaultClient: unref(clientStore).workspace["x-scalar-default-client"] }, createSlots({ start: withCtx(() => [ createElementVNode("div", { ref_key: "documentStartRef", ref: documentStartRef }, null, 512), unref(workspaceStore).workspace.activeDocument ? (openBlock(), createBlock(unref(DeveloperTools_default), { key: 0, overrides: configurationOverrides.value, "onUpdate:overrides": _cache[4] || (_cache[4] = ($event) => configurationOverrides.value = $event), class: "references-developer-tools", configuration: mergedConfig.value, externalUrls: mergedConfig.value.externalUrls, workspace: unref(workspaceStore) }, null, 8, [ "overrides", "configuration", "externalUrls", "workspace" ])) : createCommentVNode("", true), mergedConfig.value.layout === "classic" ? (openBlock(), createBlock(ClassicHeader_default, { key: 1 }, { "dark-mode-toggle": withCtx(() => [!mergedConfig.value.hideDarkModeToggle && !mergedConfig.value.forceDarkModeState ? (openBlock(), createBlock(unref(ScalarColorModeToggleIcon), { key: 0, class: "text-c-2 hover:text-c-1", mode: colorMode.value, style: { "transform": "scale(1.4)" }, variant: "icon", onClick: _cache[5] || (_cache[5] = () => unref(toggleColorMode)()) }, null, 8, ["mode"])) : createCommentVNode("", true)]), default: withCtx(() => [createElementVNode("div", _hoisted_4, [documentOptionList.value.length > 1 ? (openBlock(), createBlock(DocumentSelector_default, { key: 0, modelValue: activeSlug.value, options: documentOptionList.value, "onUpdate:modelValue": changeSelectedDocument }, null, 8, ["modelValue", "options"])) : createCommentVNode("", true)]), !mergedConfig.value.hideSearch ? (openBlock(), createBlock(SearchButton_default, { key: 0, class: "t-doc__sidebar max-w-64", document: unref(workspaceStore).workspace.activeDocument, eventBus: unref(eventBus), hideModels: mergedConfig.value.hideModels, searchHotKey: mergedConfig.value.searchHotKey }, null, 8, [ "document", "eventBus", "hideModels", "searchHotKey" ])) : createCommentVNode("", true)]), _: 1 })) : createCommentVNode("", true), renderSlot(_ctx.$slots, "content-start", normalizeProps(guardReactiveProps(slotProps.value)), void 0, true) ]), end: withCtx(() => [renderSlot(_ctx.$slots, "content-end", normalizeProps(guardReactiveProps(slotProps.value)), void 0, true)]), _: 2 }, [mergedConfig.value.isEditable ? { name: "empty-state", fn: withCtx(() => [renderSlot(_ctx.$slots, "editor-placeholder", normalizeProps(guardReactiveProps(slotProps.value)), void 0, true)]), key: "0" } : void 0]), 1032, [ "authStore", "clientDocument", "document", "environment", "eventBus", "expandedItems", "headingSlugGenerator", "infoSectionId", "items", "options", "xScalarDefaultClient" ])], 8, _hoisted_3), _ctx.$slots.footer ? (openBlock(), createElementBlock("div", _hoisted_5, [renderSlot(_ctx.$slots, "footer", normalizeProps(guardReactiveProps(slotProps.value)), void 0, true)])) : createCommentVNode("", true), createElementVNode("div", { ref_key: "modal", ref: modal }, null, 512) ], 2), createVNode(unref(ScalarToasts)) ]); }; } }); //#endregion export { ApiReference_vue_vue_type_script_setup_true_lang_default as default }; //# sourceMappingURL=ApiReference.vue.script.js.map