openapi-explorer
Version:
OpenAPI Explorer - API viewer with dynamically generated components, documentation, and interaction console
780 lines (758 loc) • 28.4 kB
JavaScript
import { LitElement } from 'lit';
// Styles
import FontStyles from './styles/font-styles.js';
import InputStyles from './styles/input-styles.js';
import SchemaStyles from './styles/schema-styles.js';
import FlexStyles from './styles/flex-styles.js';
import TableStyles from './styles/table-styles.js';
import KeyFrameStyles from './styles/key-frame-styles.js';
import EndpointStyles from './styles/endpoint-styles.js';
import PrismStyles from './styles/prism-styles.js';
import TagInputStyles from './styles/tag-input-styles.js';
import TabStyles from './styles/tab-styles.js';
import NavStyles from './styles/nav-styles.js';
import InfoStyles from './styles/info-styles.js';
import advancedSearchStyles from './styles/advanced-search-styles.js';
import MainBodyStyles from './styles/main-body-styles.js';
import { advancedSearch, getCurrentElement, replaceState, sleep } from './utils/common-utils.js';
import { initI18n } from './languages/index.js';
import ProcessSpec from './utils/spec-parser.js';
import mainBodyTemplate from './templates/mainBodyTemplate.js';
import apiRequestStyles from './styles/api-request-styles.js';
import { checkForAuthToken } from './templates/security-scheme-template.js';
import './components/syntax-highlighter.js';
export default class OpenApiExplorer extends LitElement {
constructor() {
super();
this.loading = true;
const intersectionObserverOptions = {
root: this.getRootNode().host,
rootMargin: '-50px 0px -50px 0px',
// when the element is visible 100px from bottom
threshold: 0
};
this.isIntersectionObserverActive = true;
if (typeof IntersectionObserver !== 'undefined') {
this.intersectionObserver = new IntersectionObserver(entries => {
this.onIntersect(entries);
}, intersectionObserverOptions);
} else {
this.intersectionObserver = {
disconnect() {},
observe() {}
};
}
}
static get properties() {
return {
// Heading
headingText: {
type: String,
attribute: 'heading-text'
},
explorerLocation: {
type: String,
attribute: 'explorer-location'
},
// Spec
specUrl: {
type: String,
attribute: 'spec-url'
},
// UI Layouts
layout: {
type: String
},
collapsed: {
type: Boolean,
attribute: 'collapse',
converter(value) {
return value !== 'false' && value !== false;
}
},
operationsCollapsed: {
type: Boolean
},
componentsCollapsed: {
type: Boolean
},
defaultSchemaTab: {
type: String,
attribute: 'default-schema-tab'
},
responseAreaHeight: {
type: String,
attribute: 'response-area-height'
},
hideDefaults: {
type: Boolean,
attribute: 'hide-defaults',
converter(value) {
return value !== 'false' && value !== false;
}
},
// Schema Styles
displaySchemaAsTree: {
type: Boolean,
attribute: 'tree',
converter(value) {
return value !== 'false' && value !== false;
}
},
schemaExpandLevel: {
type: Number,
attribute: 'schema-expand-level'
},
// API Server
serverUrl: {
type: String,
attribute: 'server-url'
},
// Hide/Show Sections & Enable Disable actions
hideInfo: {
type: Boolean,
attribute: 'hide-info',
converter(value) {
return value !== 'false' && value !== false;
}
},
hideAuthentication: {
type: Boolean,
attribute: 'hide-authentication',
converter(value) {
return value !== 'false' && value !== false;
}
},
hideExecution: {
type: Boolean,
attribute: 'hide-console',
converter(value) {
return value !== 'false' && value !== false;
}
},
includeNulls: {
type: Boolean,
attribute: 'display-nulls',
converter(value) {
return value !== 'false' && value !== false;
}
},
hideSearch: {
type: Boolean,
attribute: 'hide-search',
converter(value) {
return value !== 'false' && value !== false;
}
},
hideServerSelection: {
type: Boolean,
attribute: 'hide-server-selection',
converter(value) {
return value !== 'false' && value !== false;
}
},
hideComponents: {
type: Boolean,
attribute: 'hide-components',
converter(value) {
return value !== 'false' && value !== false;
}
},
// Main Colors and Font
primaryColor: {
type: String,
attribute: 'primary-color'
},
secondaryColor: {
type: String,
attribute: 'secondary-color'
},
bgColor: {
type: String,
attribute: 'bg-color'
},
bgHeaderColor: {
type: String,
attribute: 'header-bg-color'
},
textColor: {
type: String,
attribute: 'text-color'
},
headerColor: {
type: String,
attribute: 'header-color'
},
// Nav Bar Colors
navBgColor: {
type: String,
attribute: 'nav-bg-color'
},
navTextColor: {
type: String,
attribute: 'nav-text-color'
},
navHoverBgColor: {
type: String,
attribute: 'nav-hover-bg-color'
},
navHoverTextColor: {
type: String,
attribute: 'nav-hover-text-color'
},
usePathInNavBar: {
type: Boolean,
attribute: 'use-path-in-nav-bar',
converter(value) {
return value !== 'false' && value !== false;
}
},
// Fetch Options
fetchCredentials: {
type: String,
attribute: 'fetch-credentials'
},
// Filters
matchPaths: {
type: String,
attribute: 'match-paths'
},
// Internal Properties
loading: {
type: Boolean
},
// indicates spec is being loaded
showAdvancedSearchDialog: {
type: Boolean
},
advancedSearchMatches: {
type: Object
}
};
}
static finalizeStyles() {
return [FontStyles, SchemaStyles, InputStyles, FlexStyles, TableStyles, KeyFrameStyles, EndpointStyles, PrismStyles, TabStyles, NavStyles, InfoStyles, TagInputStyles, advancedSearchStyles, apiRequestStyles, MainBodyStyles];
}
// Startup
connectedCallback() {
super.connectedCallback();
this.handleResize = this.handleResize.bind(this);
window.addEventListener('resize', this.handleResize);
this.loading = true;
const parent = this.parentElement;
if (parent) {
if (parent.offsetWidth === 0 && parent.style.width === '') {
parent.style.width = '100vw';
}
if (parent.offsetHeight === 0 && parent.style.height === '') {
parent.style.height = '100vh';
}
if (parent.tagName === 'BODY') {
if (!parent.style.marginTop) {
parent.style.marginTop = '0';
}
if (!parent.style.marginRight) {
parent.style.marginRight = '0';
}
if (!parent.style.marginBottom) {
parent.style.marginBottom = '0';
}
if (!parent.style.marginLeft) {
parent.style.marginLeft = '0';
}
}
}
this.renderStyle = 'focused';
this.operationsCollapsed = this.collapsed;
this.componentsCollapsed = this.collapsed;
this.explorerLocation = this.explorerLocation || getCurrentElement();
if (!this.defaultSchemaTab || !'body, model, form,'.includes(`${this.defaultSchemaTab},`)) {
this.defaultSchemaTab = 'model';
}
if (!this.schemaExpandLevel || this.schemaExpandLevel < 1) {
this.schemaExpandLevel = 99999;
}
this.schemaHideReadOnly = ['post', 'put', 'patch', 'query'].join(',');
this.schemaHideWriteOnly = true;
if (!this.responseAreaHeight) {
this.responseAreaHeight = '300px';
}
if (!this.fetchCredentials || !'omit, same-origin, include,'.includes(`${this.fetchCredentials},`)) {
this.fetchCredentials = '';
}
if (!this.showAdvancedSearchDialog) {
this.showAdvancedSearchDialog = false;
}
window.addEventListener('hashchange', () => {
this.scrollTo(getCurrentElement());
}, true);
this.handleResize();
}
// Cleanup
disconnectedCallback() {
this.intersectionObserver.disconnect();
window.removeEventListener('resize', this.handleResize);
super.disconnectedCallback();
}
render() {
return mainBodyTemplate.call(this);
}
observeExpandedContent() {
// Main Container
const observeOverviewEls = this.shadowRoot.querySelectorAll('.observe-me');
observeOverviewEls.forEach(targetEl => {
this.intersectionObserver.observe(targetEl);
});
}
handleResize() {
const mediaQueryResult = window.matchMedia('(min-width: 768px)');
const newDisplay = mediaQueryResult.matches ? 'focused' : 'view';
if (this.renderStyle !== newDisplay) {
this.renderStyle = newDisplay;
this.requestUpdate();
}
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'spec-url') {
if (oldVal !== newVal) {
window.setTimeout(async () => {
await this.loadSpec(newVal);
// If the initial location is set, then attempt to scroll there
if (this.explorerLocation) {
this.scrollTo(this.explorerLocation);
}
}, 0);
}
}
if (name === 'server-url' && newVal) {
var _this$resolvedSpec;
this.selectedServer = ((_this$resolvedSpec = this.resolvedSpec) === null || _this$resolvedSpec === void 0 ? void 0 : _this$resolvedSpec.servers.find(s => s.url === newVal || !newVal)) || {
url: newVal,
computedUrl: newVal
};
}
if (name === 'render-style') {
if (newVal === 'read') {
window.setTimeout(() => {
this.observeExpandedContent();
}, 100);
} else {
this.intersectionObserver.disconnect();
}
}
if (name === 'explorer-location') {
window.setTimeout(() => {
this.scrollTo(newVal);
}, 0);
}
if (name === 'collapsed') {
this.operationsCollapsed = newVal;
this.componentsCollapsed = newVal;
}
super.attributeChangedCallback(name, oldVal, newVal);
}
onSearchChange(e) {
var _this$matchPaths;
this.matchPaths = e.target.value;
const expand = !!((_this$matchPaths = this.matchPaths) !== null && _this$matchPaths !== void 0 && _this$matchPaths.trim());
this.operationsCollapsed = !expand;
this.componentsCollapsed = !expand;
this.resolvedSpec.tags.forEach(tag => {
tag.expanded = expand;
});
this.resolvedSpec.components.forEach(component => {
component.expanded = expand;
});
this.requestUpdate();
}
onClearSearch() {
const searchEl = this.shadowRoot.getElementById('nav-bar-search');
searchEl.value = '';
this.matchPaths = '';
}
async onShowSearchModalClicked() {
this.showAdvancedSearchDialog = true;
// wait for the dialog to render
await sleep(10);
const inputEl = this.shadowRoot.getElementById('advanced-search-dialog-input');
if (inputEl) {
inputEl.focus();
}
}
// Public Method
async loadSpec(specUrlOrObject) {
if (!specUrlOrObject) {
return;
}
this.matchPaths = '';
try {
var _spec$info;
this.resolvedSpec = null;
this.loading = true;
this.loadingFailedError = null;
const spec = await ProcessSpec(specUrlOrObject, this.serverUrl);
this.loading = false;
if (spec === undefined || spec === null) {
console.error('Unable to resolve the API spec. '); // eslint-disable-line no-console
return;
}
initI18n((_spec$info = spec.info) === null || _spec$info === void 0 ? void 0 : _spec$info['x-locale']);
if (!this.serverUrl) {
var _spec$servers$, _spec$servers$2;
this.serverUrl = ((_spec$servers$ = spec.servers[0]) === null || _spec$servers$ === void 0 ? void 0 : _spec$servers$.computedUrl) || ((_spec$servers$2 = spec.servers[0]) === null || _spec$servers$2 === void 0 ? void 0 : _spec$servers$2.url);
}
this.selectedServer = spec.servers.find(s => s.url === this.serverUrl || !this.serverUrl) || spec.servers[0];
this.afterSpecParsedAndValidated(spec);
} catch (err) {
this.loading = false;
this.loadingFailedError = err.message;
this.resolvedSpec = null;
console.error('OpenAPI Explorer: Unable to resolve the API spec..', err); // eslint-disable-line no-console
}
try {
await checkForAuthToken.call(this);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to check for authentication token', error);
}
}
// Public Method
async setAuthenticationConfiguration(apiKeyId, {
token,
clientId,
clientSecret,
redirectUri
}) {
const securityObj = this.resolvedSpec && this.resolvedSpec.securitySchemes.find(v => v.apiKeyId === apiKeyId);
if (!securityObj) {
throw Error('SecuritySchemeNotFound');
}
let authorizationToken = token && token.replace(/^(Bearer|Basic)\s+/i, '').trim();
if (authorizationToken && securityObj.type && securityObj.type === 'http' && securityObj.scheme && securityObj.scheme.toLowerCase() === 'basic') {
authorizationToken = `Basic ${btoa(authorizationToken)}`;
} else if (authorizationToken && securityObj.scheme && securityObj.scheme.toLowerCase() === 'bearer') {
authorizationToken = `Bearer ${authorizationToken}`;
}
securityObj.clientId = clientId && clientId.trim();
securityObj.clientSecret = clientSecret && clientSecret.trim();
securityObj.redirectUri = new URL(redirectUri && redirectUri.trim() || '', window.location.href).toString();
securityObj.finalKeyValue = authorizationToken;
await checkForAuthToken.call(this);
this.requestUpdate();
}
afterSpecParsedAndValidated(spec) {
this.resolvedSpec = spec;
if (this.operationsCollapsed) {
this.resolvedSpec.tags.forEach(t => t.expanded = false);
}
if (this.componentsCollapsed) {
this.resolvedSpec.components.forEach(c => c.expanded = false);
}
this.dispatchEvent(new CustomEvent('spec-loaded', {
bubbles: true,
detail: spec
}));
this.requestUpdate();
// Initiate IntersectionObserver and put it at the end of event loop, to allow loading all the child elements (must for larger specs)
this.intersectionObserver.disconnect();
if (this.renderStyle === 'focused') {
const defaultElementId = !this.hideInfo ? 'overview' : this.resolvedSpec.tags && this.resolvedSpec.tags[0] && this.resolvedSpec.tags[0].paths[0];
this.scrollTo(this.explorerLocation || defaultElementId);
}
if (this.renderStyle === 'view' && this.explorerLocation) {
this.expandAndGotoOperation(this.explorerLocation);
}
}
expandAndGotoOperation(elementId) {
var _tag$paths;
// Expand full operation and tag
let isExpandingNeeded = false;
const tag = this.resolvedSpec.tags.find(t => t.paths && t.paths.find(p => p.elementId === elementId));
const path = tag === null || tag === void 0 ? void 0 : (_tag$paths = tag.paths) === null || _tag$paths === void 0 ? void 0 : _tag$paths.find(p => p.elementId === elementId);
if (path && (!path.expanded || !tag.expanded)) {
isExpandingNeeded = true;
path.expanded = true;
tag.expanded = true;
this.requestUpdate();
}
// requestUpdate() and delay required, else we cant find element because it won't exist immediately
const tmpElementId = elementId.indexOf('#') === -1 ? elementId : elementId.substring(1);
window.setTimeout(() => {
const gotoEl = this.shadowRoot.getElementById(tmpElementId);
if (gotoEl) {
gotoEl.scrollIntoView({
behavior: 'auto',
block: 'start'
});
replaceState(tmpElementId);
}
}, isExpandingNeeded ? 150 : 0);
}
isValidTopId(id) {
return id.startsWith('overview') || id === 'servers' || id === 'auth';
}
isValidPathId(id) {
if (id === 'overview' && !this.hideInfo) {
return true;
}
if (id === 'servers' && !this.hideServerSelection) {
return true;
}
if (id === 'auth' && !this.hideAuthentication) {
return true;
}
if (id.startsWith('tag--')) {
return this.resolvedSpec.tags && this.resolvedSpec.tags.find(tag => tag.elementId === id);
}
return this.resolvedSpec.tags && this.resolvedSpec.tags.find(tag => tag.paths.find(path => path.elementId === id));
}
onIntersect(entries) {
if (this.isIntersectionObserverActive === false) {
return;
}
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio > 0) {
const oldNavEl = this.shadowRoot.querySelector('.nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active');
const newNavEl = this.shadowRoot.getElementById(`link-${entry.target.id}`);
// Add active class in the new element
if (newNavEl) {
replaceState(entry.target.id);
newNavEl.scrollIntoView({
behavior: 'auto',
block: 'center'
});
newNavEl.classList.add('active');
}
// Remove active class from previous element
if (oldNavEl) {
oldNavEl.classList.remove('active');
}
}
});
}
// Called by anchor tags created using markdown
handleHref(e) {
if (e.target.tagName.toLowerCase() === 'a') {
const anchor = e.target.getAttribute('href');
if (anchor && anchor.startsWith('#')) {
const gotoEl = this.shadowRoot.getElementById(anchor.replace('#', ''));
if (gotoEl) {
gotoEl.scrollIntoView({
behavior: 'auto',
block: 'start'
});
}
}
}
}
/**
* Called by
* - onClick of Navigation Bar
* - onClick of Advanced Search items
*
* Functionality:
* 1. First deactivate IntersectionObserver
* 2. Scroll to the element
* 3. Activate IntersectionObserver (after little delay)
*
*/
scrollToEventTarget(event, scrollNavItemToView = true) {
const navEl = event.currentTarget;
if (!navEl.dataset.contentId) {
return;
}
this.isIntersectionObserverActive = false;
this.scrollTo(navEl.dataset.contentId, scrollNavItemToView);
setTimeout(() => {
this.isIntersectionObserverActive = true;
}, 300);
}
scrollToCustomNavSectionTarget(event, scrollNavItemToView = true) {
const navEl = event.currentTarget;
if (!navEl.dataset.contentId) {
return;
}
const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
const assignedNodes = navSectionSlot === null || navSectionSlot === void 0 ? void 0 : navSectionSlot.assignedNodes();
// clicked child node could be multiple levels deep in a custom nav
const hasChildNode = node => {
return node === event.target || node.children && [...node.children].some(c => hasChildNode(c));
};
let repeatedElementIndex = assignedNodes && [].findIndex.call(assignedNodes, slot => hasChildNode(slot));
if (repeatedElementIndex === -1 && navEl.dataset.contentId.match(/^section--\d+/)) {
repeatedElementIndex = Number(navEl.dataset.contentId.split('--')[1]) - 1;
}
this.isIntersectionObserverActive = false;
this.scrollTo(navEl.dataset.contentId, scrollNavItemToView, repeatedElementIndex);
setTimeout(() => {
this.isIntersectionObserverActive = true;
}, 300);
}
async scrollToSchemaComponentByName(schemaComponentNameEvent) {
var _this$resolvedSpec2, _this$resolvedSpec2$c, _this$resolvedSpec2$c2, _this$resolvedSpec2$c3;
const schemaComponentName = schemaComponentNameEvent.detail;
const schemaComponent = (_this$resolvedSpec2 = this.resolvedSpec) === null || _this$resolvedSpec2 === void 0 ? void 0 : (_this$resolvedSpec2$c = _this$resolvedSpec2.components) === null || _this$resolvedSpec2$c === void 0 ? void 0 : (_this$resolvedSpec2$c2 = _this$resolvedSpec2$c.find(c => c.componentKeyId === 'schemas')) === null || _this$resolvedSpec2$c2 === void 0 ? void 0 : (_this$resolvedSpec2$c3 = _this$resolvedSpec2$c2.subComponents) === null || _this$resolvedSpec2$c3 === void 0 ? void 0 : _this$resolvedSpec2$c3.find(s => s.name === schemaComponentName);
if (schemaComponent) {
await this.scrollTo(`cmp--${schemaComponent.id}`, true);
}
}
// Public Method (scrolls to a given path and highlights the left-nav selection)
async scrollTo(elementId, scrollNavItemToView = true, repeatedElementIndex) {
try {
await this.scrollToOrThrowException(elementId, scrollNavItemToView, repeatedElementIndex);
} catch (error) {
// There's an issue for lit elements for some browsers which are causing this issue we'll log here and still throw
console.error('Failed to scroll to target', elementId, scrollNavItemToView, repeatedElementIndex, error); // eslint-disable-line no-console
throw error;
}
}
async scrollToOrThrowException(elementId, scrollNavItemToView = true, forcedRepeatedElementIndex) {
if (!this.resolvedSpec) {
return;
}
this.emitOperationChangedEvent(elementId);
if (this.renderStyle === 'view') {
this.expandAndGotoOperation(elementId);
return;
}
// explorerLocation will get validated in the focused-endpoint-template
this.explorerLocation = elementId;
const tag = this.resolvedSpec.tags.find(t => t.paths.some(p => p.elementId === elementId));
if (tag) {
tag.expanded = true;
}
// Convert to Async and to the background, so that we can be sure that the operation has been expanded and put into view before trying to directly scroll to it (or it won't be found in the next line and even if it is, it might not be able to be scrolled into view)
await sleep(0);
// In the case of section scrolling, these are hard swaps, so just load "section". In the case of `tags` the headers have the element html Id in the last `--id`, so split that off and check for it
const contentEl = this.shadowRoot.getElementById(elementId !== null && elementId !== void 0 && elementId.startsWith('section') ? 'section' : elementId) || this.shadowRoot.getElementById(elementId.split('--').slice(-1)[0]);
if (!contentEl) {
return;
}
// For focused APIs, always scroll to the top of the component
let newNavEl;
let waitForComponentToExpand = false;
const elementIndex = forcedRepeatedElementIndex || forcedRepeatedElementIndex === 0 ? forcedRepeatedElementIndex : Number(elementId.split('--')[1]) - 1;
if (elementId.match(/^section/)) {
const customSections = this.shadowRoot.querySelector('slot.custom-section');
const assignedNodesToCustomSections = customSections === null || customSections === void 0 ? void 0 : customSections.assignedNodes();
if (assignedNodesToCustomSections) {
try {
assignedNodesToCustomSections.map(customSection => {
customSection.classList.remove('active');
});
const newActiveCustomSection = assignedNodesToCustomSections[elementIndex];
if (newActiveCustomSection && !newActiveCustomSection.classList.contains('active')) {
newActiveCustomSection.classList.add('active');
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to switch between custom sections, usually happens because the DOM is not ready and has not loaded these sections yet.', error);
}
}
const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
const assignedNodes = navSectionSlot === null || navSectionSlot === void 0 ? void 0 : navSectionSlot.assignedNodes();
newNavEl = assignedNodes === null || assignedNodes === void 0 ? void 0 : assignedNodes[elementIndex];
// Update Location Hash
replaceState(`section--${elementIndex + 1}`);
} else if (elementId.match('cmp--')) {
const component = this.resolvedSpec.components.find(c => c.subComponents.find(sub => elementId.includes(sub.id)));
if (component && !component.expanded) {
waitForComponentToExpand = true;
component.expanded = true;
}
contentEl.scrollIntoView({
behavior: 'auto',
block: 'start'
});
// Update Location Hash
replaceState(elementId);
newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
} else if (!elementId.match('cmp--') && !elementId.match('tag--')) {
this.shadowRoot.getElementById('operations-root').scrollIntoView({
behavior: 'auto',
block: 'start'
});
// Update Location Hash
replaceState(elementId);
newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
} else {
contentEl.scrollIntoView({
behavior: 'auto',
block: 'start'
});
// Update Location Hash
replaceState(elementId);
newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
}
// for focused style it is important to reset request-body-selection and response selection which maintains the state for in case of multiple req-body or multiple response mime-type
const requestEl = this.shadowRoot.querySelector('api-request');
if (requestEl) {
requestEl.resetRequestBodySelection();
}
const responseEl = this.shadowRoot.querySelector('api-response');
if (responseEl) {
responseEl.resetSelection();
}
// Update NavBar View and Styles
if (!newNavEl) {
return;
}
if (scrollNavItemToView) {
newNavEl.scrollIntoView({
behavior: 'auto',
block: 'center'
});
// Also force it into view again if for some reason it isn't there
if (waitForComponentToExpand) {
setTimeout(() => newNavEl.scrollIntoView({
behavior: 'auto',
block: 'center'
}), 600);
}
}
await sleep(0);
const oldNavEl = this.shadowRoot.querySelector('.nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active');
if (oldNavEl) {
oldNavEl.classList.remove('active');
}
const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
const assignedNodes = navSectionSlot === null || navSectionSlot === void 0 ? void 0 : navSectionSlot.assignedNodes();
(assignedNodes || []).filter((n, nodeIndex) => isNaN(elementIndex) || nodeIndex !== elementIndex).forEach(node => {
node.classList.remove('active');
});
newNavEl.classList.add('active'); // must add the class after scrolling
this.requestUpdate();
}
// Event handler for Advanced Search text-inputs and checkboxes
onAdvancedSearch(ev) {
const eventTargetEl = ev.target;
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
let searchInputEl;
if (eventTargetEl.type === 'text') {
searchInputEl = eventTargetEl;
} else {
searchInputEl = eventTargetEl.closest('.advanced-search-options').querySelector('input[type=text]');
}
const searchOptions = [...eventTargetEl.closest('.advanced-search-options').querySelectorAll('input:checked')].map(v => v.id);
this.advancedSearchMatches = advancedSearch(searchInputEl.value, this.resolvedSpec.tags, searchOptions);
}, 0);
}
emitOperationChangedEvent(elementId) {
const operation = this.resolvedSpec.tags.map(t => t.paths).flat(1).find(p => p.elementId === elementId);
const event = {
bubbles: true,
composed: true,
detail: {
explorerLocation: elementId,
operation,
type: 'OperationChanged'
}
};
this.dispatchEvent(new CustomEvent('event', event));
}
}
if (!customElements.get('openapi-explorer')) {
customElements.define('openapi-explorer', OpenApiExplorer);
}
import './openapi-explorer-oauth-handler.js';