@ngxviewbuilder/plugin-openapi 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Puts your REST API's OpenAPI description inside the [NGX View Builder](https://n
|
|
|
6
6
|
- **Use as data source** adds a REST data source for the endpoint to the open view.
|
|
7
7
|
- In the data source editor, an **API** row searches the endpoints by path, name or tag. Picking one fills in the URL (path parameters stay as `{placeholders}`), the method and, for `POST`, `PUT` and `PATCH`, a request body with every field the API accepts. Fields named like a field of the view are bound to it as `{name}`; the rest are trimmed in the visual body builder.
|
|
8
8
|
- The row also says where the rows are in a wrapped list (`items`) and which fields suit an option's value and label, and warns when the API no longer has the endpoint a source was made from.
|
|
9
|
+
- Live endpoints: `text/event-stream` responses (OpenAPI 3.2 `itemSchema`) become event stream sources, and an AsyncAPI 3 document in `specs` adds its WebSocket channels. They show only where the builder enables those source types.
|
|
9
10
|
- AI clients connected over MCP read the endpoints too (`nvb_read_plugin` with `openApi`) and get a ready data source to add through `execute()`.
|
|
10
11
|
|
|
11
12
|
The data sources it makes are plain REST sources: views keep working at runtime without the plugin.
|
|
@@ -39,7 +40,7 @@ bootstrapApplication(AppComponent, {
|
|
|
39
40
|
| `title` | Shown in the API tab; defaults to `info.title`. |
|
|
40
41
|
| `baseUrl` | Prefix of the data source URLs, e.g. `/api`; defaults to the first `servers` entry. |
|
|
41
42
|
|
|
42
|
-
The document is loaded through your `HttpClient`, so your interceptors add any authentication it needs. OpenAPI 3.0 and 3.
|
|
43
|
+
The document is loaded through your `HttpClient`, so your interceptors add any authentication it needs. OpenAPI 3.0, 3.1 and 3.2 and AsyncAPI 3 in JSON are supported; YAML, Swagger 2.0 and `$ref` to other files are not yet.
|
|
43
44
|
|
|
44
45
|
## Translating
|
|
45
46
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{NGX_VIEW_BUILDER_SHARED_UI_EN as de,NvbPluginI18n as j,NvbProFeatureGate as x,NvbTranslatePipe as _,registerPluginEnglish as ue}from"@ngxviewbuilder/plugin-shared";import*as d from"@angular/core";import{ChangeDetectionStrategy as F,Component as U,Injectable as me,InjectionToken as he,computed as u,effect as G,inject as b,input as H,makeEnvironmentProviders as be,provideEnvironmentInitializer as ve,signal as h,untracked as A,viewChild as fe}from"@angular/core";import{BuilderSearchInput as X,LicenseService as ge,NgxViewBuilderAiPluginRegistryService as ye,NgxViewBuilderApiService as T,NvbIcon as w,provideNgxViewBuilderExtensions as xe}from"@ngxviewbuilder/designer";import{HttpClient as Z}from"@angular/common/http";import{firstValueFrom as K}from"rxjs";import{NgTemplateOutlet as W}from"@angular/common";const _e=["GET","POST","PUT","PATCH","DELETE"];const we=8;const m=t=>!!t&&typeof t==="object"&&!Array.isArray(t);const c=t=>typeof t==="string"?t.trim():"";function J(t,e,n){if(!m(t))throw new Error("The API description is not a JSON object.");if(c(t["swagger"]))throw new Error("Swagger 2.0 is not supported. Convert the description to OpenAPI 3 first.");if(!c(t["openapi"]).startsWith("3."))throw new Error('Not an OpenAPI 3 document: the "openapi" field is missing.');const o=m(t["info"])?t["info"]:{};const r=new Se(t);const a=[];const i=new Set;const p=m(t["paths"])?t["paths"]:{};Object.entries(p).forEach(([l,v])=>{const f=r.deref(v);if(!m(f))return;const ce=Array.isArray(f["parameters"])?f["parameters"]:[];_e.forEach(V=>{const L=r.deref(f[V.toLowerCase()]);if(!m(L))return;a.push(ke(e.id,V,l,L,ce,r,i))})});const s=(Array.isArray(t["tags"])?t["tags"]:[]).filter(m).map(l=>({name:c(l["name"]),description:c(l["description"])})).filter(l=>l.name);return{specId:e.id,title:c(e.title)||c(o["title"])||e.id,version:c(o["version"]),description:c(o["description"]),baseUrl:Oe(e,t,n),tags:s,operations:a}}function ke(t,e,n,o,r,a,i){const p=new Map;[...r,...Array.isArray(o["parameters"])?o["parameters"]:[]].map(l=>a.deref(l)).filter(m).forEach(l=>{const v=c(l["in"]);const f=c(l["name"]);if(!f||!["path","query","header","cookie"].includes(v))return;p.set(`${v}:${f}`,{name:f,in:v,required:v==="path"||l["required"]===true,description:c(l["description"])||void 0,schema:l["schema"]?a.schema(l["schema"]):void 0})});let s=c(o["operationId"])||Ie(e,n);if(i.has(s))s=`${s}${e.charAt(0)}${e.slice(1).toLowerCase()}`;i.add(s);return{key:`${t} ${e} ${n}`,specId:t,operationId:s,method:e,path:n,summary:c(o["summary"]),description:c(o["description"]),tags:(Array.isArray(o["tags"])?o["tags"]:[]).map(c).filter(Boolean),deprecated:o["deprecated"]===true,parameters:Array.from(p.values()),requestBody:Ae(a.deref(o["requestBody"]),a),response:Te(o["responses"],a)}}function Ae(t,e){if(!m(t))return;const n=Q(t["content"]);if(!n)return;return{required:t["required"]===true,contentType:n.contentType,schema:n.schema?e.schema(n.schema):void 0}}function Te(t,e){if(!m(t))return;const n=Object.keys(t).filter(a=>/^2(\d\d|XX)$/i.test(a)).sort()[0];if(!n)return;const o=e.deref(t[n]);if(!m(o))return;const r=Q(o["content"]);return{status:n,description:c(o["description"]),schema:r?.schema?e.schema(r.schema):void 0}}function Q(t){if(!m(t))return null;const e=Object.keys(t).find(o=>/^application\/json/i.test(o))??Object.keys(t).find(o=>/\+json/i.test(o));if(!e)return null;const n=t[e];return{contentType:e,schema:m(n)?n["schema"]:void 0}}function Ie(t,e){const n=e.split("/").filter(Boolean).map(o=>o.replace(/[{}]/g,"")).flatMap(o=>o.split(/[^a-zA-Z0-9]+/)).filter(Boolean);return[t.toLowerCase(),...n].map((o,r)=>r===0?o:o.charAt(0).toUpperCase()+o.slice(1)).join("")}function Oe(t,e,n){const o=c(t.baseUrl);const r=Array.isArray(e["servers"])?e["servers"]:[];const a=o||c(m(r[0])?r[0]["url"]:"");if(!a)return"";if(o||/^https?:\/\//i.test(a)||!n||typeof window==="undefined")return a.replace(/\/+$/,"");const i=new URL(a,new URL(n,window.location.href));return(i.origin===window.location.origin?i.pathname:i.href).replace(/\/+$/,"")}var Se=class{document;constructor(t){this.document=t}deref(t,e=new Set){if(!m(t)||typeof t["$ref"]!=="string")return t;const n=t["$ref"];if(e.has(n))return{};e.add(n);return this.deref(this.pointer(n),e)}schema(t,e=0,n=[]){if(!m(t))return{};let o;if(typeof t["$ref"]==="string"){const s=t["$ref"];o=s.startsWith("#/components/schemas/")?s.split("/").pop():void 0;if(n.includes(s)||e>=we)return{type:"object",refName:o,circular:true};const l=this.pointer(s);return{...this.schema(l,e+1,[...n,s]),...o?{refName:o}:{}}}const r=s=>this.schema(s,e+1,n);const a={};const i=t["type"];if(Array.isArray(i)){const s=i.map(c).filter(Boolean);a.type=s.find(l=>l!=="null");a.nullable=s.includes("null")||void 0}else if(c(i))a.type=c(i);if(t["nullable"]===true)a.nullable=true;if(c(t["format"]))a.format=c(t["format"]);if(c(t["title"]))a.title=c(t["title"]);if(c(t["description"]))a.description=c(t["description"]);if(Array.isArray(t["enum"]))a.enum=[...t["enum"]];if(t["example"]!==void 0)a.example=t["example"];else if(Array.isArray(t["examples"])&&t["examples"].length)a.example=t["examples"][0];if(t["default"]!==void 0)a.default=t["default"];if(t["readOnly"]===true)a.readOnly=true;if(t["writeOnly"]===true)a.writeOnly=true;if(t["items"]!==void 0)a.items=r(t["items"]);if(m(t["properties"]))a.properties=Object.fromEntries(Object.entries(t["properties"]).map(([s,l])=>[s,r(l)]));if(Array.isArray(t["required"]))a.required=t["required"].map(c).filter(Boolean);if(Array.isArray(t["allOf"]))t["allOf"].map(r).forEach(s=>Y(a,s));const p=Array.isArray(t["oneOf"])?t["oneOf"]:Array.isArray(t["anyOf"])?t["anyOf"]:null;if(p){const s=p.map(r);const l=s.filter(v=>v.type!=="null");if(l.length===1){Y(a,l[0]);if(l.length<s.length)a.nullable=true}else a.variants=l}if(!a.type&&a.properties)a.type="object";if(!a.type&&a.items)a.type="array";return a}pointer(t){if(!t.startsWith("#/"))return{};return t.slice(2).split("/").map(e=>e.replace(/~1/g,"/").replace(/~0/g,"~")).reduce((e,n)=>m(e)?e[n]:void 0,this.document)}};function Y(t,e){t.type??=e.type;t.format??=e.format;t.title??=e.title;t.description??=e.description;t.enum??=e.enum;t.example??=e.example;t.items??=e.items;t.refName??=e.refName;if(e.properties)t.properties={...t.properties??{},...e.properties};if(e.required)t.required=Array.from(new Set([...t.required??[],...e.required]))}function g(t,e=0){if(!t||e>6)return null;if(t.example!==void 0)return t.example;if(t.default!==void 0)return t.default;if(t.enum?.length)return t.enum[0];if(t.variants?.length)return g(t.variants[0],e+1);switch(t.type){case"object":return Object.fromEntries(Object.entries(t.properties??{}).filter(([,n])=>!n.writeOnly).map(([n,o])=>[n,g(o,e+1)]));case"array":return t.items?[g(t.items,e+1)]:[];case"integer":case"number":return 0;case"boolean":return false;case"string":return Ne(t.format);default:return null}}function Ne(t){switch(t){case"date":return"2026-01-31";case"date-time":return"2026-01-31T09:30:00Z";case"email":return"name@example.com";case"uuid":return"3fa85f64-5717-4562-b3fc-2c963f66afa6";case"uri":return"https://example.com";default:return"string"}}function ee(t,e,n="",o=0){if(!t||o>5)return{};if(t.type==="array")return[];if(t.type!=="object"&&!t.properties)return te(t);return Object.fromEntries(Object.entries(t.properties??{}).filter(([,r])=>!r.readOnly).map(([r,a])=>{const i=n?`${n}.${r}`:r;if(e.has(i))return[r,`{${i}}`];if(a.type==="object"||a.properties)return[r,ee(a,e,i,o+1)];return[r,te(a)]}))}function te(t){switch(t.type){case"integer":case"number":return 0;case"boolean":return false;case"array":return[];case"object":return{};default:return""}}function k(t){if(!t)return null;if(t.type==="array")return"";const e=Object.entries(t.properties??{});const n=e.filter(([,r])=>r.type==="array");const o=["items","data","results","content","records","rows","list"].map(r=>n.find(([a])=>a===r)).find(Boolean)??(n.length===1?n[0]:void 0);if(o)return o[0];for(const[r,a]of e)if(a.type==="object"){const i=k(a);if(i)return`${r}.${i}`}return null}function I(t){const e=k(t);if(e===null||!t)return;return e.split(".").filter(Boolean).reduce((n,o)=>n?.properties?.[o],t)?.items}function ne(t){const e=Object.keys(t?.properties??{});const n=o=>o.find(r=>e.includes(r))??e.find(r=>o.some(a=>r.toLowerCase()===a.toLowerCase()));return{value:n(["id","code","key","value","uuid","slug"]),label:n(["name","title","label","fullName","displayName","description","email"])}}function Ce(t,e){const n=e.parameters.filter(o=>o.in==="query"&&o.required).map(o=>`${encodeURIComponent(o.name)}={${o.name}}`).join("&");return`${t}${e.path}${n?`?${n}`:""}`}function O(t){const e=t.operationId.replace(/[^a-zA-Z0-9_]/g,"_").replace(/^_+|_+$/g,"");return/^[a-zA-Z]/.test(e)?e:`api_${e}`}function Pe(t){const e=new Set;const n=(o,r)=>{if(!o||typeof o!=="object")return;Object.values(o).forEach(a=>{const i=a;const p=String(i?.["name"]??"").trim();if(!i||!p||i["type"]==="page")return;const s=r?`${r}.${p}`:p;e.add(s);if(i["type"]==="objectPanel")n(i["template"],s)})};n(t?.["elements"],"");return e}function S(t,e){if(!e.length)return true;const n=[t.method,t.path,t.summary,t.operationId,...t.tags].join(" ").toLowerCase();return e.every(o=>n.includes(o))}const N=new Set(["POST","PUT","PATCH"]);function De(t){return{specId:t.specId,operationId:t.operationId,method:t.method,path:t.path}}function C(t,e,n){const o={url:Ce(e.baseUrl,t),method:t.method,openApi:De(t)};if(N.has(t.method)&&t.requestBody?.schema)o["body"]=ee(t.requestBody.schema,Pe(n));return o}function qe(t,e,n,o){const r=t.dataSources??=[];const a=new Set(r.map(s=>s.name));const i=String(o??"").trim()||O(e);let p=i;for(let s=2;a.has(p);s+=1)p=`${i}${s}`;r.push({name:p,title:e.summary||e.operationId,type:"rest",params:C(e,n,t)});return p}const oe="openApi";const P=80;function Ee(t,e,n){return{id:oe,title:"API (OpenAPI)",description:"The application's REST API as its OpenAPI description lists it: endpoints with method, path, parameters, request body and response fields. Read it before adding a REST data source. Reading one operationId returns a ready `dataSource` object (URL, method, and for POST/PUT/PATCH a body with every field the API accepts, fields named like view fields bound as {name}); add it to the view through execute() unchanged or trimmed, and bind elements to its name.",query:{search:"Optional words that must all appear in the method, path, summary, operationId or tag.",operationId:"Optional. Returns that one operation in full: parameters, request body fields, response fields and an example response."},available:()=>e.proAccess()==="ready",unavailableReason:()=>"The OpenAPI plugin needs an active NGX View Builder license key in the builder settings.",read:async o=>{await t.ensureLoaded();const r=String(o["operationId"]??"").trim();if(r){const p=t.operations().find(l=>l.operationId===r);if(!p)throw new Error(`No operation '${r}' in the API descriptions.`);const s=t.catalogOf(p.specId);return{...ze(p,s?.baseUrl??""),dataSource:s?{name:O(p),title:p.summary||p.operationId,type:"rest",params:C(p,s,n.getStructure())}:void 0}}const a=String(o["search"]??"").toLowerCase().split(/\s+/).filter(Boolean);const i=t.operations().filter(p=>S(p,a));return{apis:t.catalogs().map(p=>({id:p.specId,title:p.title,version:p.version,baseUrl:p.baseUrl})),errors:t.specs().filter(({status:p})=>p.state==="error").map(({spec:p,status:s})=>({id:p.id,message:s.state==="error"?s.message:""})),total:i.length,operations:i.slice(0,P).map(p=>({operationId:p.operationId,api:p.specId,method:p.method,path:p.path,summary:p.summary||void 0,tags:p.tags.length?p.tags:void 0,deprecated:p.deprecated||void 0})),...i.length>P?{note:`Showing ${P}; narrow it with search.`}:{}}}}}function ze(t,e){return{operationId:t.operationId,method:t.method,url:`${e}${t.path}`,summary:t.summary||void 0,description:t.description||void 0,deprecated:t.deprecated||void 0,parameters:t.parameters.map(n=>({name:n.name,in:n.in,required:n.required,type:D(n.schema),description:n.description})),requestBody:t.requestBody?{required:t.requestBody.required,fields:q(t.requestBody.schema)}:void 0,response:t.response?{status:t.response.status,rowsPath:k(t.response.schema),optionFields:ne(I(t.response.schema)),fields:q(I(t.response.schema)??t.response.schema),example:g(t.response.schema)}:void 0}}function D(t){if(!t)return;if(t.type==="array")return`${D(t.items)??"any"}[]`;return[t.refName??t.type,t.format].filter(Boolean).join(":")||void 0}function q(t,e="",n=0){if(!t?.properties||n>3)return[];const o=new Set(t.required??[]);return Object.entries(t.properties).flatMap(([r,a])=>{const i=e?`${e}.${r}`:r;return[{path:i,type:D(a),required:o.has(r)||void 0,enum:a.enum,readOnly:a.readOnly||void 0,description:a.description},...a.type==="object"?q(a,i,n+1):[]]})}const E={"tab.openApi":"API","tab.openApi.description":"Browse the API and turn its endpoints into data sources","openApi.title":"API","openApi.meta":"{count} endpoints in {specs} API descriptions","openApi.endpoints":"Endpoints","openApi.search":"Search by path, name or tag","openApi.clearSearch":"Clear search","openApi.methods":"Show methods","openApi.spec":"API","openApi.spec.all":"All APIs","openApi.untagged":"Other","openApi.loading":"Loading the API description…","openApi.loadFailed":"Could not load {title}: {message}","openApi.retry":"Try again","openApi.noSpecs":"No API description is configured. The application adds one with provideNgxViewBuilderOpenApi({ specs: [...] }).","openApi.noMatch":"No endpoint matches the search.","openApi.deprecated":"Deprecated","openApi.deprecatedText":"This endpoint is deprecated and may be removed from the API.","openApi.use":"Use as data source","openApi.added":'Data source "{name}" added to the view.',"openApi.usedBy":"Used by data source: {names}","openApi.parameters":"Parameters","openApi.col.name":"Name","openApi.col.in":"In","openApi.col.type":"Type","openApi.col.description":"Description","openApi.required":"required","openApi.readOnly":"read only","openApi.oneOf":"One of:","openApi.requestBody":"Request body","openApi.response":"Response","openApi.example":"Example","openApi.schema":"Schema","openApi.try":"Try it","openApi.try.getOnly":"Only GET requests can be sent from here, so nothing is changed on the server.","openApi.try.send":"Send request","openApi.try.sending":"Sending…","openApi.try.unreachable":"The address could not be reached","openApi.try.truncated":"the rest of the response is not shown","openApi.picker.row":"API","openApi.picker.label":"Find an API endpoint","openApi.picker.placeholder":"Find an endpoint, e.g. customers","openApi.picker.noSpecs":"No API description is configured for this application.","openApi.picker.noMatch":"No endpoint matches.","openApi.picker.listRoot":"The response is the list itself.","openApi.picker.listAt":'The rows are in "{path}": use it as the Items path of the element that shows them.',"openApi.picker.optionFields":'For options, "{value}" works as the value and "{label}" as the label.',"openApi.picker.editBody":"Choose body fields","openApi.picker.change":"Pick another endpoint","openApi.picker.show":"Show in API tab","openApi.picker.unlink":"Unlink","openApi.picker.missing":"This data source was made from {method} {path}, which the API description no longer has."};const ae={api:"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 -960 960 960\"><path d=\"M480-375 375-480l105-105 105 105-105 105Zm-85-294-83-83 168-168 168 168-83 83-85-85-85 85ZM208-312 40-480l168-168 83 83-85 85 85 85-83 83Zm544 0-83-83 85-85-85-85 83-83 168 168-168 168ZM480-40 312-208l83-83 85 85 85-85 83 83L480-40Z\"/></svg>",play_arrow:"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 -960 960 960\"><path d=\"M320-203v-560l440 280-440 280Zm60-280Zm0 171 269-171-269-171v342Z\"/></svg>"};const z=new he("NGX_VIEW_BUILDER_OPENAPI_CONFIG");const re="openApi";var y=class M{config=b(z,{optional:true});http=b(Z,{optional:true});statuses=h({},...ngDevMode?[{debugName:"statuses"}]:[]);loading=null;selectedKey=h(null,...ngDevMode?[{debugName:"selectedKey"}]:[]);specs=u(()=>(this.config?.specs??[]).map(e=>({spec:e,status:this.statuses()[e.id]??{state:"loading"}})),...ngDevMode?[{debugName:"specs"}]:[]);catalogs=u(()=>this.specs().map(({status:e})=>e.state==="ready"?e.catalog:null).filter(e=>!!e),...ngDevMode?[{debugName:"catalogs"}]:[]);operations=u(()=>this.catalogs().flatMap(e=>e.operations),...ngDevMode?[{debugName:"operations"}]:[]);hasSpecs=u(()=>(this.config?.specs??[]).length>0,...ngDevMode?[{debugName:"hasSpecs"}]:[]);ensureLoaded(){this.loading??=this.loadAll();return this.loading}reload(){this.statuses.set({});this.loading=this.loadAll();return this.loading}catalogOf(e){return this.catalogs().find(n=>n.specId===e)??null}findOperation(e,n){return this.operations().find(o=>o.specId===e&&o.operationId===n)??null}async loadAll(){await Promise.all((this.config?.specs??[]).map(e=>this.loadOne(e.id)))}async loadOne(e){const n=this.config?.specs.find(o=>o.id===e);if(!n)return;this.setStatus(e,{state:"loading"});try{const o=J(n.document??await this.fetchDocument(n.url),n,n.url);this.setStatus(e,{state:"ready",catalog:o})}catch(o){this.setStatus(e,{state:"error",message:$e(o)})}}async fetchDocument(e){const n=String(e??"").trim();if(!n)throw new Error("The spec has neither a url nor a document.");if(this.http)return K(this.http.get(n));const o=await fetch(n);if(!o.ok)throw new Error(`HTTP ${o.status}`);return o.json()}setStatus(e,n){this.statuses.update(o=>({...o,[e]:n}))}static ɵfac=d.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:M,deps:[],target:d.ɵɵFactoryTarget.Injectable});static ɵprov=d.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:M,providedIn:"root"})};d.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:y,decorators:[{type:me,args:[{providedIn:"root"}]}]});function $e(t){const e=t;if(e&&typeof e.status==="number"&&e.status>0)return`HTTP ${e.status}${e.statusText?` ${e.statusText}`:""}`;if(e&&e.status===0)return"The address could not be reached.";return String(e?.message??t??"Unknown error")}const Me=60;var ie=class B{source=H(null,...ngDevMode?[{debugName:"source"}]:[]);context=H(null,...ngDevMode?[{debugName:"context"}]:[]);state=b(y);api=b(T);i18n=b(j);searchInput=fe("searchInput",...ngDevMode?[{debugName:"searchInput"}]:[]);query=h("",...ngDevMode?[{debugName:"query"}]:[]);open=h(false,...ngDevMode?[{debugName:"open"}]:[]);activeIndex=h(0,...ngDevMode?[{debugName:"activeIndex"}]:[]);revision=h(0,...ngDevMode?[{debugName:"revision"}]:[]);listId=`nvb-openapi-picker-${Math.random().toString(36).slice(2,8)}`;link=u(()=>{this.revision();const e=this.source()?.params?.["openApi"];return e&&e.specId&&e.operationId?e:null},...ngDevMode?[{debugName:"link"}]:[]);linkedOperation=u(()=>{const e=this.link();return e?this.state.findOperation(e.specId,e.operationId):null},...ngDevMode?[{debugName:"linkedOperation"}]:[]);multipleSpecs=u(()=>this.state.catalogs().length>1,...ngDevMode?[{debugName:"multipleSpecs"}]:[]);results=u(()=>{const e=this.query().toLowerCase().split(/\s+/).filter(Boolean);return this.state.operations().filter(n=>S(n,e)).slice(0,Me)},...ngDevMode?[{debugName:"results"}]:[]);loadErrors=u(()=>this.state.specs().filter(({status:e})=>e.state==="error").map(({spec:e,status:n})=>({title:e.title||e.id,message:n.state==="error"?n.message:""})),...ngDevMode?[{debugName:"loadErrors"}]:[]);loading=u(()=>this.state.specs().some(({status:e})=>e.state==="loading"),...ngDevMode?[{debugName:"loading"}]:[]);responseHint=u(()=>{const e=this.linkedOperation();const n=e?.response?.schema;if(!e||!n)return null;const o=k(n);const r=ne(I(n));return{listPath:o,value:r.value??null,label:r.label??null}},...ngDevMode?[{debugName:"responseHint"}]:[]);hasBody=u(()=>{this.revision();const e=String(this.source()?.params?.["method"]??"").toUpperCase();return N.has(e==="TABLE-POST"?"POST":e)&&!!this.linkedOperation()?.requestBody},...ngDevMode?[{debugName:"hasBody"}]:[]);constructor(){this.state.ensureLoaded()}methodTone(e){return e.toLowerCase()}onQuery(e){this.query.set(e);this.activeIndex.set(0);this.open.set(true)}onFocus(){this.open.set(true)}onBlur(e){if(e.relatedTarget?.closest(`#${this.listId}`))return;this.open.set(false)}onKeydown(e){const n=this.results().length;if(e.key==="ArrowDown"){e.preventDefault();this.open.set(true);this.activeIndex.set(n?(this.activeIndex()+1)%n:0)}else if(e.key==="ArrowUp"){e.preventDefault();this.activeIndex.set(n?(this.activeIndex()-1+n)%n:0)}else if(e.key==="Enter"){const o=this.results()[this.activeIndex()];if(this.open()&&o){e.preventDefault();this.choose(o)}}else if(e.key==="Escape"&&this.open()){e.stopPropagation();this.open.set(false)}}choose(e){const n=this.context();const o=this.source();const r=this.state.catalogOf(e.specId);if(!n||!o||!r)return;const a=C(e,r,this.api.getStructure());if(!N.has(e.method))a["body"]=void 0;a["payload"]=void 0;const i=!/^source\d+(_\d+)?$/i.test(String(o.name??"").trim());const p=!/^source \d+$/i.test(String(o.title??"").trim());n.apply({params:a,...i?{}:{name:O(e)},...p?{}:{title:e.summary||e.operationId}});this.revision.update(s=>s+1);this.query.set("");this.open.set(false)}change(){this.open.set(true);queueMicrotask(()=>this.searchInput()?.nativeElement.focus())}unlink(){this.context()?.apply({params:{openApi:void 0}});this.revision.update(e=>e+1)}openBodyBuilder(){this.context()?.openBodyBuilder()}showInApiTab(){const e=this.linkedOperation();if(!e)return;this.state.selectedKey.set(e.key);this.api.switchToTab("openApi")}specTitle(e){return this.state.catalogOf(e)?.title??e}placeholder(){return this.i18n.t("openApi.picker.placeholder")}static ɵfac=d.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:B,deps:[],target:d.ɵɵFactoryTarget.Component});static ɵcmp=d.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.2.1",type:B,isStandalone:true,selector:"nvb-openapi-picker",inputs:{source:{classPropertyName:"source",publicName:"source",isSignal:true,isRequired:false,transformFunction:null},context:{classPropertyName:"context",publicName:"context",isSignal:true,isRequired:false,transformFunction:null}},viewQueries:[{propertyName:"searchInput",first:true,predicate:["searchInput"],descendants:true,isSignal:true}],ngImport:d,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\" [compact]=\"true\">\n <div class=\"oap\" data-testid=\"openapi-picker\">\n @if (!state.hasSpecs()) {\n <p class=\"oap-hint\">{{ 'openApi.picker.noSpecs' | nvbT }}</p>\n } @else {\n @if (linkedOperation(); as operation) {\n <div class=\"oap-card\" data-testid=\"openapi-picker-linked\">\n <div class=\"oap-card__line\">\n <span class=\"oap-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <code class=\"oap-card__path\">{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <p class=\"oap-card__summary\">{{ operation.summary }}</p>\n }\n @if (responseHint(); as hint) {\n <ul class=\"oap-card__facts\">\n @if (hint.listPath === '') {\n <li>{{ 'openApi.picker.listRoot' | nvbT }}</li>\n } @else if (hint.listPath) {\n <li>{{ 'openApi.picker.listAt' | nvbT: { path: hint.listPath } }}</li>\n }\n @if (hint.value && hint.label) {\n <li>{{ 'openApi.picker.optionFields' | nvbT: { value: hint.value, label: hint.label } }}</li>\n }\n </ul>\n }\n <div class=\"oap-card__actions\">\n @if (hasBody()) {\n <button type=\"button\" class=\"oap-button oap-button--primary\" (click)=\"openBodyBuilder()\" data-testid=\"openapi-picker-body\">\n {{ 'openApi.picker.editBody' | nvbT }}\n </button>\n }\n <button type=\"button\" class=\"oap-button\" (click)=\"change()\" data-testid=\"openapi-picker-change\">\n {{ 'openApi.picker.change' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button\" (click)=\"showInApiTab()\" data-testid=\"openapi-picker-show\">\n {{ 'openApi.picker.show' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button oap-button--quiet\" (click)=\"unlink()\" data-testid=\"openapi-picker-unlink\">\n {{ 'openApi.picker.unlink' | nvbT }}\n </button>\n </div>\n </div>\n } @else if (link(); as staleLink) {\n <p class=\"oap-hint oap-hint--warning\" data-testid=\"openapi-picker-missing\">\n {{ 'openApi.picker.missing' | nvbT: { method: staleLink.method, path: staleLink.path } }}\n </p>\n }\n\n <div class=\"oap-combo\" [class.oap-combo--hidden]=\"linkedOperation() && !open()\">\n <span class=\"oap-combo__icon\" aria-hidden=\"true\"><nvb-icon name=\"search\" /></span>\n <input\n #searchInput\n type=\"text\"\n class=\"oap-combo__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"open() && results().length ? listId + '-' + activeIndex() : null\"\n [attr.aria-label]=\"'openApi.picker.label' | nvbT\"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onQuery($any($event.target).value)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur($event)\"\n (keydown)=\"onKeydown($event)\"\n data-testid=\"openapi-picker-search\"\n />\n @if (open()) {\n <ul class=\"oap-list\" role=\"listbox\" [id]=\"listId\" data-testid=\"openapi-picker-results\">\n @for (operation of results(); track operation.key; let index = $index) {\n <li\n role=\"option\"\n tabindex=\"-1\"\n class=\"oap-option\"\n [id]=\"listId + '-' + index\"\n [class.oap-option--active]=\"index === activeIndex()\"\n [attr.aria-selected]=\"index === activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mouseenter)=\"activeIndex.set(index)\"\n (click)=\"choose(operation)\"\n [attr.data-testid]=\"'openapi-picker-option-' + operation.operationId\"\n >\n <span class=\"oap-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <span class=\"oap-option__text\">\n <code class=\"oap-option__path\">{{ operation.path }}</code>\n <span class=\"oap-option__summary\">\n {{ operation.summary || operation.operationId }}\n @if (multipleSpecs()) {\n · {{ specTitle(operation.specId) }}\n }\n </span>\n </span>\n @if (operation.deprecated) {\n <span class=\"oap-option__flag\">{{ 'openApi.deprecated' | nvbT }}</span>\n }\n </li>\n } @empty {\n <li class=\"oap-list__empty\" role=\"presentation\">\n @if (loading()) {\n {{ 'openApi.loading' | nvbT }}\n } @else {\n {{ 'openApi.picker.noMatch' | nvbT }}\n }\n </li>\n }\n </ul>\n }\n </div>\n\n @for (failure of loadErrors(); track failure.title) {\n <p class=\"oap-hint oap-hint--warning\">{{ 'openApi.loadFailed' | nvbT: { title: failure.title, message: failure.message } }}</p>\n }\n }\n </div>\n</nvb-pro-feature-gate>\n",styles:[":host{display:block;min-width:0}.oap{display:flex;flex-direction:column;gap:6px;min-width:0}.oap-hint{margin:0;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.4}.oap-hint--warning{color:var(--nvb-color-warning-700, #b45309)}.oap-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oap-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oap-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oap-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oap-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oap-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oap-card{display:flex;flex-direction:column;gap:6px;padding:8px 10px;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:color-mix(in oklab,var(--nvb-color-neutral-050) 70%,var(--nvb-color-surface-mix-target, white))}.oap-card__line{display:flex;align-items:center;gap:8px;min-width:0}.oap-card__path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-card__summary{margin:0;color:var(--nvb-color-neutral-700);font-size:12px}.oap-card__facts{margin:0;padding-left:16px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.5}.oap-card__actions{display:flex;flex-wrap:wrap;gap:6px}.oap-button{height:26px;padding:0 10px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.oap-button:hover{border-color:var(--nvb-color-primary-400);color:var(--nvb-color-primary-700)}.oap-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oap-button--primary{border-color:color-mix(in oklab,var(--nvb-color-primary-500) 40%,transparent);background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000));color:var(--nvb-color-primary-700)}.oap-button--quiet{border-color:transparent;background:transparent;color:var(--nvb-color-neutral-500)}.oap-combo{position:relative}.oap-combo--hidden{display:none}.oap-combo__icon{position:absolute;top:50%;left:8px;display:inline-flex;width:16px;height:16px;color:var(--nvb-color-neutral-500);transform:translateY(-50%);pointer-events:none}.oap-combo__icon nvb-icon{width:16px;height:16px}.oap-combo__input{box-sizing:border-box;width:100%;height:var(--nvb-control-height, 32px);padding:0 10px 0 30px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-control-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px}.oap-combo__input:focus{border-color:var(--nvb-color-primary-500);outline:2px solid color-mix(in oklab,var(--nvb-color-primary-500) 25%,transparent);outline-offset:0}.oap-list{position:absolute;z-index:20;top:calc(100% + 4px);right:0;left:0;max-height:320px;margin:0;padding:4px;overflow-y:auto;list-style:none;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:var(--nvb-color-neutral-000);box-shadow:var(--nvb-shadow-8, 0 8px 24px rgba(15, 23, 42, .14))}.oap-list__empty{padding:10px;color:var(--nvb-color-neutral-500);font-size:12px}.oap-option{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer}.oap-option--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000))}.oap-option__text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.oap-option__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-option__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oap-option__flag{flex:0 0 auto;color:var(--nvb-color-warning-700, #b45309);font-size:11px}\n"],dependencies:[{kind:"component",type:w,selector:"nvb-icon",inputs:["name","svg","ariaLabel"]},{kind:"component",type:x,selector:"nvb-pro-feature-gate",inputs:["feature","compact"]},{kind:"pipe",type:_,name:"nvbT"}],changeDetection:d.ChangeDetectionStrategy.OnPush})};d.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:ie,decorators:[{type:U,args:[{selector:"nvb-openapi-picker",imports:[w,x,_],changeDetection:F.OnPush,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\" [compact]=\"true\">\n <div class=\"oap\" data-testid=\"openapi-picker\">\n @if (!state.hasSpecs()) {\n <p class=\"oap-hint\">{{ 'openApi.picker.noSpecs' | nvbT }}</p>\n } @else {\n @if (linkedOperation(); as operation) {\n <div class=\"oap-card\" data-testid=\"openapi-picker-linked\">\n <div class=\"oap-card__line\">\n <span class=\"oap-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <code class=\"oap-card__path\">{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <p class=\"oap-card__summary\">{{ operation.summary }}</p>\n }\n @if (responseHint(); as hint) {\n <ul class=\"oap-card__facts\">\n @if (hint.listPath === '') {\n <li>{{ 'openApi.picker.listRoot' | nvbT }}</li>\n } @else if (hint.listPath) {\n <li>{{ 'openApi.picker.listAt' | nvbT: { path: hint.listPath } }}</li>\n }\n @if (hint.value && hint.label) {\n <li>{{ 'openApi.picker.optionFields' | nvbT: { value: hint.value, label: hint.label } }}</li>\n }\n </ul>\n }\n <div class=\"oap-card__actions\">\n @if (hasBody()) {\n <button type=\"button\" class=\"oap-button oap-button--primary\" (click)=\"openBodyBuilder()\" data-testid=\"openapi-picker-body\">\n {{ 'openApi.picker.editBody' | nvbT }}\n </button>\n }\n <button type=\"button\" class=\"oap-button\" (click)=\"change()\" data-testid=\"openapi-picker-change\">\n {{ 'openApi.picker.change' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button\" (click)=\"showInApiTab()\" data-testid=\"openapi-picker-show\">\n {{ 'openApi.picker.show' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button oap-button--quiet\" (click)=\"unlink()\" data-testid=\"openapi-picker-unlink\">\n {{ 'openApi.picker.unlink' | nvbT }}\n </button>\n </div>\n </div>\n } @else if (link(); as staleLink) {\n <p class=\"oap-hint oap-hint--warning\" data-testid=\"openapi-picker-missing\">\n {{ 'openApi.picker.missing' | nvbT: { method: staleLink.method, path: staleLink.path } }}\n </p>\n }\n\n <div class=\"oap-combo\" [class.oap-combo--hidden]=\"linkedOperation() && !open()\">\n <span class=\"oap-combo__icon\" aria-hidden=\"true\"><nvb-icon name=\"search\" /></span>\n <input\n #searchInput\n type=\"text\"\n class=\"oap-combo__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"open() && results().length ? listId + '-' + activeIndex() : null\"\n [attr.aria-label]=\"'openApi.picker.label' | nvbT\"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onQuery($any($event.target).value)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur($event)\"\n (keydown)=\"onKeydown($event)\"\n data-testid=\"openapi-picker-search\"\n />\n @if (open()) {\n <ul class=\"oap-list\" role=\"listbox\" [id]=\"listId\" data-testid=\"openapi-picker-results\">\n @for (operation of results(); track operation.key; let index = $index) {\n <li\n role=\"option\"\n tabindex=\"-1\"\n class=\"oap-option\"\n [id]=\"listId + '-' + index\"\n [class.oap-option--active]=\"index === activeIndex()\"\n [attr.aria-selected]=\"index === activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mouseenter)=\"activeIndex.set(index)\"\n (click)=\"choose(operation)\"\n [attr.data-testid]=\"'openapi-picker-option-' + operation.operationId\"\n >\n <span class=\"oap-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <span class=\"oap-option__text\">\n <code class=\"oap-option__path\">{{ operation.path }}</code>\n <span class=\"oap-option__summary\">\n {{ operation.summary || operation.operationId }}\n @if (multipleSpecs()) {\n · {{ specTitle(operation.specId) }}\n }\n </span>\n </span>\n @if (operation.deprecated) {\n <span class=\"oap-option__flag\">{{ 'openApi.deprecated' | nvbT }}</span>\n }\n </li>\n } @empty {\n <li class=\"oap-list__empty\" role=\"presentation\">\n @if (loading()) {\n {{ 'openApi.loading' | nvbT }}\n } @else {\n {{ 'openApi.picker.noMatch' | nvbT }}\n }\n </li>\n }\n </ul>\n }\n </div>\n\n @for (failure of loadErrors(); track failure.title) {\n <p class=\"oap-hint oap-hint--warning\">{{ 'openApi.loadFailed' | nvbT: { title: failure.title, message: failure.message } }}</p>\n }\n }\n </div>\n</nvb-pro-feature-gate>\n",styles:[":host{display:block;min-width:0}.oap{display:flex;flex-direction:column;gap:6px;min-width:0}.oap-hint{margin:0;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.4}.oap-hint--warning{color:var(--nvb-color-warning-700, #b45309)}.oap-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oap-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oap-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oap-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oap-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oap-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oap-card{display:flex;flex-direction:column;gap:6px;padding:8px 10px;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:color-mix(in oklab,var(--nvb-color-neutral-050) 70%,var(--nvb-color-surface-mix-target, white))}.oap-card__line{display:flex;align-items:center;gap:8px;min-width:0}.oap-card__path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-card__summary{margin:0;color:var(--nvb-color-neutral-700);font-size:12px}.oap-card__facts{margin:0;padding-left:16px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.5}.oap-card__actions{display:flex;flex-wrap:wrap;gap:6px}.oap-button{height:26px;padding:0 10px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.oap-button:hover{border-color:var(--nvb-color-primary-400);color:var(--nvb-color-primary-700)}.oap-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oap-button--primary{border-color:color-mix(in oklab,var(--nvb-color-primary-500) 40%,transparent);background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000));color:var(--nvb-color-primary-700)}.oap-button--quiet{border-color:transparent;background:transparent;color:var(--nvb-color-neutral-500)}.oap-combo{position:relative}.oap-combo--hidden{display:none}.oap-combo__icon{position:absolute;top:50%;left:8px;display:inline-flex;width:16px;height:16px;color:var(--nvb-color-neutral-500);transform:translateY(-50%);pointer-events:none}.oap-combo__icon nvb-icon{width:16px;height:16px}.oap-combo__input{box-sizing:border-box;width:100%;height:var(--nvb-control-height, 32px);padding:0 10px 0 30px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-control-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px}.oap-combo__input:focus{border-color:var(--nvb-color-primary-500);outline:2px solid color-mix(in oklab,var(--nvb-color-primary-500) 25%,transparent);outline-offset:0}.oap-list{position:absolute;z-index:20;top:calc(100% + 4px);right:0;left:0;max-height:320px;margin:0;padding:4px;overflow-y:auto;list-style:none;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:var(--nvb-color-neutral-000);box-shadow:var(--nvb-shadow-8, 0 8px 24px rgba(15, 23, 42, .14))}.oap-list__empty{padding:10px;color:var(--nvb-color-neutral-500);font-size:12px}.oap-option{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer}.oap-option--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000))}.oap-option__text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.oap-option__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-option__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oap-option__flag{flex:0 0 auto;color:var(--nvb-color-warning-700, #b45309);font-size:11px}\n"]}]}],ctorParameters:()=>[],propDecorators:{source:[{type:d.Input,args:[{isSignal:true,alias:"source",required:false}]}],context:[{type:d.Input,args:[{isSignal:true,alias:"context",required:false}]}],searchInput:[{type:d.ViewChild,args:["searchInput",{isSignal:true}]}]}});const Be=["GET","POST","PUT","PATCH","DELETE"];const se=2e5;var pe=class R{state=b(y);api=b(T);http=b(Z,{optional:true});i18n=b(j);methods=Be;icons=ae;search=h("",...ngDevMode?[{debugName:"search"}]:[]);hiddenMethods=h(new Set,...ngDevMode?[{debugName:"hiddenMethods"}]:[]);specFilter=h("",...ngDevMode?[{debugName:"specFilter"}]:[]);responseView=h("example",...ngDevMode?[{debugName:"responseView"}]:[]);tryValues=h({},...ngDevMode?[{debugName:"tryValues"}]:[]);tryResult=h(null,...ngDevMode?[{debugName:"tryResult"}]:[]);trying=h(false,...ngDevMode?[{debugName:"trying"}]:[]);lastAdded=h(null,...ngDevMode?[{debugName:"lastAdded"}]:[]);visibleOperations=u(()=>{const e=this.search().toLowerCase().split(/\s+/).filter(Boolean);const n=this.hiddenMethods();const o=this.specFilter();return this.state.operations().filter(r=>!o||r.specId===o).filter(r=>!n.has(r.method)).filter(r=>S(r,e))},...ngDevMode?[{debugName:"visibleOperations"}]:[]);groups=u(()=>{const e=new Map;const n=new Map;this.state.catalogs().forEach(a=>a.tags.forEach(i=>n.set(i.name,i.description)));const o=Array.from(n.keys());this.visibleOperations().forEach(a=>{const i=a.tags[0]??this.i18n.t("openApi.untagged");if(!e.has(i))e.set(i,{tag:i,description:n.get(i)??"",operations:[]});e.get(i).operations.push(a)});const r=a=>{const i=o.indexOf(a);return i<0?o.length:i};return Array.from(e.values()).sort((a,i)=>r(a.tag)-r(i.tag))},...ngDevMode?[{debugName:"groups"}]:[]);selected=u(()=>{const e=this.state.selectedKey();return this.state.operations().find(n=>n.key===e)??null},...ngDevMode?[{debugName:"selected"}]:[]);selectedCatalog=u(()=>{const e=this.selected();return e?this.state.catalogOf(e.specId):null},...ngDevMode?[{debugName:"selectedCatalog"}]:[]);exampleResponse=u(()=>{const e=this.selected()?.response?.schema;return e?JSON.stringify(g(e),null,2):""},...ngDevMode?[{debugName:"exampleResponse"}]:[]);exampleBody=u(()=>{const e=this.selected()?.requestBody?.schema;return e?JSON.stringify(g(e),null,2):""},...ngDevMode?[{debugName:"exampleBody"}]:[]);linkedSources=u(()=>{const e=this.selected();this.lastAdded();if(!e)return[];return(this.api.getStructure()?.dataSources??[]).filter(n=>{const o=n?.params?.["openApi"];return o?.specId===e.specId&&o?.operationId===e.operationId}).map(n=>n.name)},...ngDevMode?[{debugName:"linkedSources"}]:[]);tryParameters=u(()=>(this.selected()?.parameters??[]).filter(e=>e.in==="path"||e.in==="query"),...ngDevMode?[{debugName:"tryParameters"}]:[]);canTry=u(()=>{const e=this.selected();if(!e||e.method!=="GET"||!this.http)return false;const n=this.tryValues();return this.tryParameters().filter(o=>o.required).every(o=>String(n[o.name]??"").trim())},...ngDevMode?[{debugName:"canTry"}]:[]);specCount=u(()=>this.state.specs().length,...ngDevMode?[{debugName:"specCount"}]:[]);constructor(){this.state.ensureLoaded();G(()=>{const e=this.state.operations();if(!A(()=>this.selected())&&e.length)A(()=>this.state.selectedKey.set(e[0].key))});G(()=>{this.selected();A(()=>{this.tryValues.set({});this.tryResult.set(null)})})}select(e){this.state.selectedKey.set(e.key)}toggleMethod(e){this.hiddenMethods.update(n=>{const o=new Set(n);if(o.has(e))o.delete(e);else o.add(e);return o})}methodTone(e){return e.toLowerCase()}schemaType(e){if(!e)return"";if(e.type==="array")return`${this.schemaType(e.items)||"any"}[]`;if(e.variants?.length)return e.variants.map(o=>this.schemaType(o)).join(" | ");const n=e.refName??e.type??"any";return e.format&&!e.refName?`${n} (${e.format})`:n}childrenOf(e){const n=e?.type==="array"?e.items:e;if(!n?.properties||n.circular)return[];const o=new Set(n.required??[]);return Object.entries(n.properties).map(([r,a])=>({name:r,schema:a,required:o.has(r)}))}enumText(e){return(e.enum??[]).map(n=>JSON.stringify(n)).join(", ")}setTryValue(e,n){this.tryValues.update(o=>({...o,[e]:n}))}async tryIt(){const e=this.selected();const n=this.selectedCatalog();if(!e||!n||!this.http||!this.canTry())return;const o=this.tryValues();let r=e.path;const a=new URLSearchParams;this.tryParameters().forEach(s=>{const l=String(o[s.name]??"").trim();if(s.in==="path")r=r.replace(`{${s.name}}`,encodeURIComponent(l));else if(l)a.set(s.name,l)});const i=`${n.baseUrl}${r}${a.size?`?${a}`:""}`;this.trying.set(true);const p=performance.now();try{const s=await K(this.http.get(i,{observe:"response",responseType:"text"}));this.tryResult.set(this.toResult(s,p,true))}catch(s){const l=s;this.tryResult.set({status:l.status??0,statusText:l.status?l.statusText:this.i18n.t("openApi.try.unreachable"),ms:Math.round(performance.now()-p),body:this.pretty(typeof l.error==="string"?l.error:JSON.stringify(l.error??"")),ok:false})}finally{this.trying.set(false)}}useAsDataSource(){const e=this.selected();const n=this.selectedCatalog();if(!e||!n)return;let o="";this.api.updateStructure(r=>{o=qe(r,e,n)});if(o)this.lastAdded.set(o)}reload(){this.state.reload()}toResult(e,n,o){return{status:e.status,statusText:e.statusText,ms:Math.round(performance.now()-n),body:this.pretty(e.body??""),ok:o}}pretty(e){let n=e;try{n=JSON.stringify(JSON.parse(e),null,2)}catch{}return n.length>se?`${n.slice(0,se)}
|
|
2
|
-
… ${this.i18n.t("openApi.try.truncated")}`:n}static ɵfac=d.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:R,deps:[],target:d.ɵɵFactoryTarget.Component});static ɵcmp=d.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.2.1",type:R,isStandalone:true,selector:"nvb-openapi-tab",ngImport:d,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\">\n <div class=\"oat\" data-testid=\"openapi-tab\">\n <aside class=\"oat-side\" [attr.aria-label]=\"'openApi.endpoints' | nvbT\">\n <header class=\"oat-side__header\">\n <h2 class=\"oat-side__title\">{{ 'openApi.title' | nvbT }}</h2>\n <p class=\"oat-side__meta\" data-testid=\"openapi-summary\">\n {{ 'openApi.meta' | nvbT: { count: state.operations().length, specs: state.catalogs().length } }}\n </p>\n </header>\n\n <div class=\"oat-side__search\">\n <builder-search-input\n [value]=\"search()\"\n [placeholder]=\"'openApi.search' | nvbT\"\n [ariaLabel]=\"'openApi.search' | nvbT\"\n [clearAriaLabel]=\"'openApi.clearSearch' | nvbT\"\n testId=\"openapi-search\"\n (valueChanged)=\"search.set($event)\"\n (cleared)=\"search.set('')\"\n />\n </div>\n\n <div class=\"oat-filters\" role=\"group\" [attr.aria-label]=\"'openApi.methods' | nvbT\">\n @for (method of methods; track method) {\n <button\n type=\"button\"\n class=\"oat-filter oat-method\"\n [attr.data-method]=\"methodTone(method)\"\n [class.oat-filter--off]=\"hiddenMethods().has(method)\"\n [attr.aria-pressed]=\"!hiddenMethods().has(method)\"\n (click)=\"toggleMethod(method)\"\n [attr.data-testid]=\"'openapi-filter-' + methodTone(method)\"\n >\n {{ method }}\n </button>\n }\n </div>\n\n @if (specCount() > 1) {\n <label class=\"oat-spec\">\n <span>{{ 'openApi.spec' | nvbT }}</span>\n <select [value]=\"specFilter()\" (change)=\"specFilter.set($any($event.target).value)\" data-testid=\"openapi-spec-filter\">\n <option value=\"\">{{ 'openApi.spec.all' | nvbT }}</option>\n @for (catalog of state.catalogs(); track catalog.specId) {\n <option [value]=\"catalog.specId\">{{ catalog.title }}</option>\n }\n </select>\n </label>\n }\n\n <nav class=\"oat-side__scroll\">\n @for (entry of state.specs(); track entry.spec.id) {\n @if (entry.status.state === 'error') {\n <p class=\"oat-notice oat-notice--error\" data-testid=\"openapi-load-error\">\n {{ 'openApi.loadFailed' | nvbT: { title: entry.spec.title || entry.spec.id, message: entry.status.message } }}\n <button type=\"button\" class=\"oat-link\" (click)=\"reload()\">{{ 'openApi.retry' | nvbT }}</button>\n </p>\n } @else if (entry.status.state === 'loading') {\n <p class=\"oat-notice\">{{ 'openApi.loading' | nvbT }}</p>\n }\n }\n @if (!state.hasSpecs()) {\n <p class=\"oat-notice\" data-testid=\"openapi-no-specs\">{{ 'openApi.noSpecs' | nvbT }}</p>\n }\n\n @for (group of groups(); track group.tag) {\n <section class=\"oat-group\">\n <h3 class=\"oat-group__title\" [title]=\"group.description\">{{ group.tag }}</h3>\n <ul class=\"oat-group__list\" role=\"list\">\n @for (operation of group.operations; track operation.key) {\n <li>\n <button\n type=\"button\"\n class=\"oat-item\"\n [class.oat-item--active]=\"selected()?.key === operation.key\"\n [class.oat-item--deprecated]=\"operation.deprecated\"\n [attr.aria-current]=\"selected()?.key === operation.key ? 'true' : null\"\n (click)=\"select(operation)\"\n [attr.data-testid]=\"'openapi-item-' + operation.operationId\"\n >\n <span class=\"oat-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <span class=\"oat-item__text\">\n <span class=\"oat-item__path\">{{ operation.path }}</span>\n @if (operation.summary) {\n <span class=\"oat-item__summary\">{{ operation.summary }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </section>\n } @empty {\n @if (state.operations().length) {\n <p class=\"oat-notice\">{{ 'openApi.noMatch' | nvbT }}</p>\n }\n }\n </nav>\n </aside>\n\n <main class=\"oat-main\">\n @if (selected(); as operation) {\n <article class=\"oat-detail\" data-testid=\"openapi-detail\">\n <header class=\"oat-detail__header\">\n <div class=\"oat-detail__line\">\n <span class=\"oat-method oat-method--large\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <code class=\"oat-detail__path\" data-testid=\"openapi-detail-path\">{{ selectedCatalog()?.baseUrl }}{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <h2 class=\"oat-detail__summary\">{{ operation.summary }}</h2>\n }\n @if (operation.deprecated) {\n <p class=\"oat-notice oat-notice--warning\">{{ 'openApi.deprecatedText' | nvbT }}</p>\n }\n @if (operation.description) {\n <p class=\"oat-detail__description\">{{ operation.description }}</p>\n }\n <div class=\"oat-detail__actions\">\n <button type=\"button\" class=\"oat-button oat-button--primary\" (click)=\"useAsDataSource()\" data-testid=\"openapi-use\">\n {{ 'openApi.use' | nvbT }}\n </button>\n <span class=\"oat-detail__id\">operationId: <code>{{ operation.operationId }}</code></span>\n </div>\n @if (lastAdded(); as added) {\n <p class=\"oat-notice oat-notice--success\" role=\"status\" data-testid=\"openapi-added\">\n {{ 'openApi.added' | nvbT: { name: added } }}\n </p>\n } @else if (linkedSources().length) {\n <p class=\"oat-notice\" data-testid=\"openapi-linked\">\n {{ 'openApi.usedBy' | nvbT: { names: linkedSources().join(', ') } }}\n </p>\n }\n </header>\n\n @if (operation.parameters.length) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">{{ 'openApi.parameters' | nvbT }}</h3>\n <table class=\"oat-table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'openApi.col.name' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.in' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.type' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.description' | nvbT }}</th>\n </tr>\n </thead>\n <tbody>\n @for (parameter of operation.parameters; track parameter.in + parameter.name) {\n <tr>\n <td>\n <code>{{ parameter.name }}</code>\n @if (parameter.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n </td>\n <td>{{ parameter.in }}</td>\n <td class=\"oat-type\">{{ schemaType(parameter.schema) }}</td>\n <td>{{ parameter.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </section>\n }\n\n @if (operation.requestBody; as body) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ 'openApi.requestBody' | nvbT }}\n <span class=\"oat-section__meta\">{{ body.contentType }}{{ body.required ? ' · ' + ('openApi.required' | nvbT) : '' }}</span>\n </h3>\n @if (body.schema) {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: body.schema, depth: 0 }\" />\n </div>\n }\n </section>\n }\n\n @if (operation.response; as response) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ 'openApi.response' | nvbT }}\n <span class=\"oat-section__meta\">{{ response.status }}{{ response.description ? ' · ' + response.description : '' }}</span>\n </h3>\n @if (response.schema) {\n <div class=\"oat-segmented\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'example'\"\n [class.oat-segmented__on]=\"responseView() === 'example'\"\n (click)=\"responseView.set('example')\"\n >\n {{ 'openApi.example' | nvbT }}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'schema'\"\n [class.oat-segmented__on]=\"responseView() === 'schema'\"\n (click)=\"responseView.set('schema')\"\n >\n {{ 'openApi.schema' | nvbT }}\n </button>\n </div>\n @if (responseView() === 'example') {\n <pre class=\"oat-code\" data-testid=\"openapi-example\">{{ exampleResponse() }}</pre>\n } @else {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: response.schema, depth: 0 }\" />\n </div>\n }\n }\n </section>\n }\n\n <section class=\"oat-section\" data-testid=\"openapi-try\">\n <h3 class=\"oat-section__title\">{{ 'openApi.try' | nvbT }}</h3>\n @if (operation.method !== 'GET') {\n <p class=\"oat-notice\">{{ 'openApi.try.getOnly' | nvbT }}</p>\n } @else {\n @if (tryParameters().length) {\n <div class=\"oat-try__fields\">\n @for (parameter of tryParameters(); track parameter.name) {\n <label class=\"oat-try__field\">\n <span>\n {{ parameter.name }}\n @if (parameter.required) {\n <span class=\"oat-required\">*</span>\n }\n </span>\n <input\n type=\"text\"\n [value]=\"tryValues()[parameter.name] ?? ''\"\n (input)=\"setTryValue(parameter.name, $any($event.target).value)\"\n (keydown.enter)=\"tryIt()\"\n [attr.data-testid]=\"'openapi-try-' + parameter.name\"\n />\n </label>\n }\n </div>\n }\n <button\n type=\"button\"\n class=\"oat-button\"\n [disabled]=\"!canTry() || trying()\"\n (click)=\"tryIt()\"\n data-testid=\"openapi-try-send\"\n >\n <nvb-icon [svg]=\"icons.play_arrow\" />\n {{ (trying() ? 'openApi.try.sending' : 'openApi.try.send') | nvbT }}\n </button>\n @if (tryResult(); as result) {\n <p class=\"oat-try__status\" [class.oat-try__status--error]=\"!result.ok\" data-testid=\"openapi-try-status\">\n {{ result.status || '' }} {{ result.statusText }} · {{ result.ms }} ms\n </p>\n <pre class=\"oat-code\" data-testid=\"openapi-try-body\">{{ result.body }}</pre>\n }\n }\n </section>\n </article>\n } @else if (state.operations().length === 0 && state.hasSpecs()) {\n <p class=\"oat-empty\">{{ 'openApi.loading' | nvbT }}</p>\n }\n </main>\n </div>\n</nvb-pro-feature-gate>\n\n<ng-template #schemaRows let-schema let-depth=\"depth\">\n <ul class=\"oat-tree\" role=\"list\">\n @for (field of childrenOf(schema); track field.name) {\n <li class=\"oat-tree__row\">\n <div class=\"oat-tree__line\">\n <code class=\"oat-tree__name\">{{ field.name }}</code>\n @if (field.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n <span class=\"oat-type\">{{ schemaType(field.schema) }}</span>\n @if (field.schema.nullable) {\n <span class=\"oat-flag\">null</span>\n }\n @if (field.schema.readOnly) {\n <span class=\"oat-flag\">{{ 'openApi.readOnly' | nvbT }}</span>\n }\n </div>\n @if (field.schema.description) {\n <p class=\"oat-tree__description\">{{ field.schema.description }}</p>\n }\n @if (field.schema.enum?.length) {\n <p class=\"oat-tree__description\">{{ 'openApi.oneOf' | nvbT }} {{ enumText(field.schema) }}</p>\n }\n @if (depth < 5 && childrenOf(field.schema).length) {\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: field.schema, depth: depth + 1 }\" />\n }\n </li>\n } @empty {\n <li class=\"oat-tree__row oat-tree__line\">\n <span class=\"oat-type\">{{ schemaType(schema) }}</span>\n </li>\n }\n </ul>\n</ng-template>\n",styles:[":host{--nvb-input-height: 32px;display:block;height:100%;min-height:0;background:color-mix(in oklab,var(--nvb-color-neutral-050) 84%,var(--nvb-color-surface-mix-target, white))}.oat{display:grid;grid-template-columns:280px minmax(0,1fr);gap:10px;height:100%;min-height:0;padding:10px;box-sizing:border-box}.oat-side,.oat-main{min-height:0;background:var(--nvb-card-bg, var(--nvb-color-neutral-000));border:1px solid var(--nvb-card-border-color, var(--nvb-color-neutral-200));border-radius:var(--nvb-card-radius, 8px);box-shadow:var(--nvb-card-shadow, none)}.oat-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oat-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oat-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oat-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oat-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oat-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oat-method--large{min-width:64px;padding:4px 8px;font-size:12px}.oat-side{display:flex;flex-direction:column;overflow:hidden}.oat-side__header{padding:12px 12px 8px}.oat-side__title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--nvb-color-neutral-900)}.oat-side__meta{margin:2px 0 0;font-size:11px;line-height:16px;color:var(--nvb-color-neutral-500);font-variant-numeric:tabular-nums}.oat-side__search{padding:0 12px 10px}.oat-filters{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 10px;border-bottom:1px solid var(--nvb-color-neutral-200)}.oat-filter{border:1px solid color-mix(in oklab,currentColor 30%,transparent);cursor:pointer}.oat-filter--off{opacity:.4;text-decoration:line-through}.oat-spec{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-bottom:1px solid var(--nvb-color-neutral-200);color:var(--nvb-color-neutral-600);font-size:12px}.oat-spec select{height:var(--nvb-input-height);border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-side__scroll{flex:1 1 auto;min-height:0;padding:0 8px 16px;overflow-y:auto}.oat-group__title{position:sticky;top:0;z-index:1;margin:8px 0 2px;padding:6px 8px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-600);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.oat-group__list{margin:0;padding:0;list-style:none}.oat-item{display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.oat-item:hover{background:color-mix(in oklab,var(--nvb-color-neutral-100) 70%,transparent)}.oat-item:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:-2px}.oat-item--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))}.oat-item--active:hover{background:color-mix(in oklab,var(--nvb-color-primary-500) 16%,var(--nvb-color-neutral-000))}.oat-item--deprecated .oat-item__path{text-decoration:line-through}.oat-item__text{display:flex;flex-direction:column;min-width:0}.oat-item__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oat-item__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oat-main{overflow-y:auto}.oat-detail{max-width:960px;padding:20px 28px 40px}.oat-detail__line{display:flex;align-items:center;gap:10px;min-width:0}.oat-detail__path{min-width:0;overflow-wrap:anywhere;font-family:Consolas,Courier New,monospace;font-size:14px;color:var(--nvb-color-neutral-900)}.oat-detail__summary{margin:10px 0 0;font-size:18px;font-weight:600;color:var(--nvb-color-neutral-900)}.oat-detail__description{margin:6px 0 0;color:var(--nvb-color-neutral-700);font-size:13px;line-height:1.5;white-space:pre-line}.oat-detail__actions{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:14px}.oat-detail__id{color:var(--nvb-color-neutral-500);font-size:12px}.oat-button{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 14px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.oat-button nvb-icon{width:18px;height:18px}.oat-button:hover:not(:disabled){border-color:var(--nvb-color-primary-400)}.oat-button:disabled{opacity:.5;cursor:default}.oat-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oat-button--primary{border-color:var(--nvb-color-primary-600);background:var(--nvb-color-primary-600);color:var(--nvb-color-neutral-000, #fff)}.oat-button--primary:hover:not(:disabled){border-color:var(--nvb-color-primary-700);background:var(--nvb-color-primary-700)}.oat-link{margin-left:6px;padding:0;border:0;background:none;color:var(--nvb-color-primary-700);font:inherit;text-decoration:underline;cursor:pointer}.oat-notice{margin:8px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.45}.oat-detail .oat-notice{margin:10px 0 0}.oat-notice--error{color:var(--nvb-color-risk-700, #b91c1c)}.oat-notice--warning{color:var(--nvb-color-warning-700, #b45309)}.oat-notice--success{color:var(--nvb-color-success-700, #15803d)}.oat-empty{padding:40px;color:var(--nvb-color-neutral-500)}.oat-section{margin-top:26px}.oat-section__title{display:flex;align-items:baseline;gap:10px;margin:0 0 8px;font-size:13px;font-weight:700;color:var(--nvb-color-neutral-800)}.oat-section__meta{color:var(--nvb-color-neutral-500);font-size:12px;font-weight:400}.oat-table{width:100%;border-collapse:collapse;font-size:12px}.oat-table th,.oat-table td{padding:6px 8px;border-bottom:1px solid var(--nvb-color-neutral-200);text-align:left;vertical-align:top}.oat-table th{color:var(--nvb-color-neutral-600);font-weight:600}.oat-table code{font-family:Consolas,Courier New,monospace}.oat-required{margin-left:2px;color:var(--nvb-color-risk-600, #dc2626);font-weight:700}.oat-type{color:var(--nvb-color-info-700, #1d4ed8);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-flag{padding:0 5px;border-radius:3px;background:var(--nvb-color-neutral-100);color:var(--nvb-color-neutral-600);font-size:11px}.oat-schema{padding:8px 12px;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000)}.oat-tree{margin:0;padding:0;list-style:none}.oat-tree .oat-tree{margin:2px 0 4px 6px;padding-left:12px;border-left:1px solid var(--nvb-color-neutral-200)}.oat-tree__row{padding:3px 0}.oat-tree__line{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.oat-tree__name{font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-900)}.oat-tree__description{margin:1px 0 0;color:var(--nvb-color-neutral-600);font-size:12px}.oat-segmented{display:inline-flex;margin-bottom:8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;overflow:hidden}.oat-segmented button{padding:4px 12px;border:0;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;cursor:pointer}.oat-segmented button+button{border-left:1px solid var(--nvb-color-neutral-300)}.oat-segmented__on{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))!important;color:var(--nvb-color-primary-700)!important;font-weight:600}.oat-code{max-height:420px;margin:0;padding:10px 12px;overflow:auto;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font-family:Consolas,Courier New,monospace;font-size:12px;line-height:1.45}.oat-try__fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-bottom:10px}.oat-try__field{display:flex;flex-direction:column;gap:4px;color:var(--nvb-color-neutral-700);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-try__field input{height:var(--nvb-input-height);padding:0 8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-try__status{margin:10px 0 6px;color:var(--nvb-color-success-700, #15803d);font-size:12px;font-weight:600}.oat-try__status--error{color:var(--nvb-color-risk-700, #b91c1c)}@media(max-width:760px){.oat{grid-template-columns:1fr;grid-template-rows:minmax(200px,40%) 1fr}.oat-detail{padding:16px}}\n"],dependencies:[{kind:"directive",type:W,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:w,selector:"nvb-icon",inputs:["name","svg","ariaLabel"]},{kind:"component",type:x,selector:"nvb-pro-feature-gate",inputs:["feature","compact"]},{kind:"component",type:X,selector:"builder-search-input",inputs:["value","placeholder","ariaLabel","testId","clearAriaLabel","showSearchIcon"],outputs:["valueChanged","cleared"]},{kind:"pipe",type:_,name:"nvbT"}],changeDetection:d.ChangeDetectionStrategy.OnPush})};d.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:d,type:pe,decorators:[{type:U,args:[{selector:"nvb-openapi-tab",imports:[W,w,x,_,X],changeDetection:F.OnPush,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\">\n <div class=\"oat\" data-testid=\"openapi-tab\">\n <aside class=\"oat-side\" [attr.aria-label]=\"'openApi.endpoints' | nvbT\">\n <header class=\"oat-side__header\">\n <h2 class=\"oat-side__title\">{{ 'openApi.title' | nvbT }}</h2>\n <p class=\"oat-side__meta\" data-testid=\"openapi-summary\">\n {{ 'openApi.meta' | nvbT: { count: state.operations().length, specs: state.catalogs().length } }}\n </p>\n </header>\n\n <div class=\"oat-side__search\">\n <builder-search-input\n [value]=\"search()\"\n [placeholder]=\"'openApi.search' | nvbT\"\n [ariaLabel]=\"'openApi.search' | nvbT\"\n [clearAriaLabel]=\"'openApi.clearSearch' | nvbT\"\n testId=\"openapi-search\"\n (valueChanged)=\"search.set($event)\"\n (cleared)=\"search.set('')\"\n />\n </div>\n\n <div class=\"oat-filters\" role=\"group\" [attr.aria-label]=\"'openApi.methods' | nvbT\">\n @for (method of methods; track method) {\n <button\n type=\"button\"\n class=\"oat-filter oat-method\"\n [attr.data-method]=\"methodTone(method)\"\n [class.oat-filter--off]=\"hiddenMethods().has(method)\"\n [attr.aria-pressed]=\"!hiddenMethods().has(method)\"\n (click)=\"toggleMethod(method)\"\n [attr.data-testid]=\"'openapi-filter-' + methodTone(method)\"\n >\n {{ method }}\n </button>\n }\n </div>\n\n @if (specCount() > 1) {\n <label class=\"oat-spec\">\n <span>{{ 'openApi.spec' | nvbT }}</span>\n <select [value]=\"specFilter()\" (change)=\"specFilter.set($any($event.target).value)\" data-testid=\"openapi-spec-filter\">\n <option value=\"\">{{ 'openApi.spec.all' | nvbT }}</option>\n @for (catalog of state.catalogs(); track catalog.specId) {\n <option [value]=\"catalog.specId\">{{ catalog.title }}</option>\n }\n </select>\n </label>\n }\n\n <nav class=\"oat-side__scroll\">\n @for (entry of state.specs(); track entry.spec.id) {\n @if (entry.status.state === 'error') {\n <p class=\"oat-notice oat-notice--error\" data-testid=\"openapi-load-error\">\n {{ 'openApi.loadFailed' | nvbT: { title: entry.spec.title || entry.spec.id, message: entry.status.message } }}\n <button type=\"button\" class=\"oat-link\" (click)=\"reload()\">{{ 'openApi.retry' | nvbT }}</button>\n </p>\n } @else if (entry.status.state === 'loading') {\n <p class=\"oat-notice\">{{ 'openApi.loading' | nvbT }}</p>\n }\n }\n @if (!state.hasSpecs()) {\n <p class=\"oat-notice\" data-testid=\"openapi-no-specs\">{{ 'openApi.noSpecs' | nvbT }}</p>\n }\n\n @for (group of groups(); track group.tag) {\n <section class=\"oat-group\">\n <h3 class=\"oat-group__title\" [title]=\"group.description\">{{ group.tag }}</h3>\n <ul class=\"oat-group__list\" role=\"list\">\n @for (operation of group.operations; track operation.key) {\n <li>\n <button\n type=\"button\"\n class=\"oat-item\"\n [class.oat-item--active]=\"selected()?.key === operation.key\"\n [class.oat-item--deprecated]=\"operation.deprecated\"\n [attr.aria-current]=\"selected()?.key === operation.key ? 'true' : null\"\n (click)=\"select(operation)\"\n [attr.data-testid]=\"'openapi-item-' + operation.operationId\"\n >\n <span class=\"oat-method\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <span class=\"oat-item__text\">\n <span class=\"oat-item__path\">{{ operation.path }}</span>\n @if (operation.summary) {\n <span class=\"oat-item__summary\">{{ operation.summary }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </section>\n } @empty {\n @if (state.operations().length) {\n <p class=\"oat-notice\">{{ 'openApi.noMatch' | nvbT }}</p>\n }\n }\n </nav>\n </aside>\n\n <main class=\"oat-main\">\n @if (selected(); as operation) {\n <article class=\"oat-detail\" data-testid=\"openapi-detail\">\n <header class=\"oat-detail__header\">\n <div class=\"oat-detail__line\">\n <span class=\"oat-method oat-method--large\" [attr.data-method]=\"methodTone(operation.method)\">{{ operation.method }}</span>\n <code class=\"oat-detail__path\" data-testid=\"openapi-detail-path\">{{ selectedCatalog()?.baseUrl }}{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <h2 class=\"oat-detail__summary\">{{ operation.summary }}</h2>\n }\n @if (operation.deprecated) {\n <p class=\"oat-notice oat-notice--warning\">{{ 'openApi.deprecatedText' | nvbT }}</p>\n }\n @if (operation.description) {\n <p class=\"oat-detail__description\">{{ operation.description }}</p>\n }\n <div class=\"oat-detail__actions\">\n <button type=\"button\" class=\"oat-button oat-button--primary\" (click)=\"useAsDataSource()\" data-testid=\"openapi-use\">\n {{ 'openApi.use' | nvbT }}\n </button>\n <span class=\"oat-detail__id\">operationId: <code>{{ operation.operationId }}</code></span>\n </div>\n @if (lastAdded(); as added) {\n <p class=\"oat-notice oat-notice--success\" role=\"status\" data-testid=\"openapi-added\">\n {{ 'openApi.added' | nvbT: { name: added } }}\n </p>\n } @else if (linkedSources().length) {\n <p class=\"oat-notice\" data-testid=\"openapi-linked\">\n {{ 'openApi.usedBy' | nvbT: { names: linkedSources().join(', ') } }}\n </p>\n }\n </header>\n\n @if (operation.parameters.length) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">{{ 'openApi.parameters' | nvbT }}</h3>\n <table class=\"oat-table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'openApi.col.name' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.in' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.type' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.description' | nvbT }}</th>\n </tr>\n </thead>\n <tbody>\n @for (parameter of operation.parameters; track parameter.in + parameter.name) {\n <tr>\n <td>\n <code>{{ parameter.name }}</code>\n @if (parameter.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n </td>\n <td>{{ parameter.in }}</td>\n <td class=\"oat-type\">{{ schemaType(parameter.schema) }}</td>\n <td>{{ parameter.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </section>\n }\n\n @if (operation.requestBody; as body) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ 'openApi.requestBody' | nvbT }}\n <span class=\"oat-section__meta\">{{ body.contentType }}{{ body.required ? ' · ' + ('openApi.required' | nvbT) : '' }}</span>\n </h3>\n @if (body.schema) {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: body.schema, depth: 0 }\" />\n </div>\n }\n </section>\n }\n\n @if (operation.response; as response) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ 'openApi.response' | nvbT }}\n <span class=\"oat-section__meta\">{{ response.status }}{{ response.description ? ' · ' + response.description : '' }}</span>\n </h3>\n @if (response.schema) {\n <div class=\"oat-segmented\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'example'\"\n [class.oat-segmented__on]=\"responseView() === 'example'\"\n (click)=\"responseView.set('example')\"\n >\n {{ 'openApi.example' | nvbT }}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'schema'\"\n [class.oat-segmented__on]=\"responseView() === 'schema'\"\n (click)=\"responseView.set('schema')\"\n >\n {{ 'openApi.schema' | nvbT }}\n </button>\n </div>\n @if (responseView() === 'example') {\n <pre class=\"oat-code\" data-testid=\"openapi-example\">{{ exampleResponse() }}</pre>\n } @else {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: response.schema, depth: 0 }\" />\n </div>\n }\n }\n </section>\n }\n\n <section class=\"oat-section\" data-testid=\"openapi-try\">\n <h3 class=\"oat-section__title\">{{ 'openApi.try' | nvbT }}</h3>\n @if (operation.method !== 'GET') {\n <p class=\"oat-notice\">{{ 'openApi.try.getOnly' | nvbT }}</p>\n } @else {\n @if (tryParameters().length) {\n <div class=\"oat-try__fields\">\n @for (parameter of tryParameters(); track parameter.name) {\n <label class=\"oat-try__field\">\n <span>\n {{ parameter.name }}\n @if (parameter.required) {\n <span class=\"oat-required\">*</span>\n }\n </span>\n <input\n type=\"text\"\n [value]=\"tryValues()[parameter.name] ?? ''\"\n (input)=\"setTryValue(parameter.name, $any($event.target).value)\"\n (keydown.enter)=\"tryIt()\"\n [attr.data-testid]=\"'openapi-try-' + parameter.name\"\n />\n </label>\n }\n </div>\n }\n <button\n type=\"button\"\n class=\"oat-button\"\n [disabled]=\"!canTry() || trying()\"\n (click)=\"tryIt()\"\n data-testid=\"openapi-try-send\"\n >\n <nvb-icon [svg]=\"icons.play_arrow\" />\n {{ (trying() ? 'openApi.try.sending' : 'openApi.try.send') | nvbT }}\n </button>\n @if (tryResult(); as result) {\n <p class=\"oat-try__status\" [class.oat-try__status--error]=\"!result.ok\" data-testid=\"openapi-try-status\">\n {{ result.status || '' }} {{ result.statusText }} · {{ result.ms }} ms\n </p>\n <pre class=\"oat-code\" data-testid=\"openapi-try-body\">{{ result.body }}</pre>\n }\n }\n </section>\n </article>\n } @else if (state.operations().length === 0 && state.hasSpecs()) {\n <p class=\"oat-empty\">{{ 'openApi.loading' | nvbT }}</p>\n }\n </main>\n </div>\n</nvb-pro-feature-gate>\n\n<ng-template #schemaRows let-schema let-depth=\"depth\">\n <ul class=\"oat-tree\" role=\"list\">\n @for (field of childrenOf(schema); track field.name) {\n <li class=\"oat-tree__row\">\n <div class=\"oat-tree__line\">\n <code class=\"oat-tree__name\">{{ field.name }}</code>\n @if (field.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n <span class=\"oat-type\">{{ schemaType(field.schema) }}</span>\n @if (field.schema.nullable) {\n <span class=\"oat-flag\">null</span>\n }\n @if (field.schema.readOnly) {\n <span class=\"oat-flag\">{{ 'openApi.readOnly' | nvbT }}</span>\n }\n </div>\n @if (field.schema.description) {\n <p class=\"oat-tree__description\">{{ field.schema.description }}</p>\n }\n @if (field.schema.enum?.length) {\n <p class=\"oat-tree__description\">{{ 'openApi.oneOf' | nvbT }} {{ enumText(field.schema) }}</p>\n }\n @if (depth < 5 && childrenOf(field.schema).length) {\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: field.schema, depth: depth + 1 }\" />\n }\n </li>\n } @empty {\n <li class=\"oat-tree__row oat-tree__line\">\n <span class=\"oat-type\">{{ schemaType(schema) }}</span>\n </li>\n }\n </ul>\n</ng-template>\n",styles:[":host{--nvb-input-height: 32px;display:block;height:100%;min-height:0;background:color-mix(in oklab,var(--nvb-color-neutral-050) 84%,var(--nvb-color-surface-mix-target, white))}.oat{display:grid;grid-template-columns:280px minmax(0,1fr);gap:10px;height:100%;min-height:0;padding:10px;box-sizing:border-box}.oat-side,.oat-main{min-height:0;background:var(--nvb-card-bg, var(--nvb-color-neutral-000));border:1px solid var(--nvb-card-border-color, var(--nvb-color-neutral-200));border-radius:var(--nvb-card-radius, 8px);box-shadow:var(--nvb-card-shadow, none)}.oat-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oat-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oat-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oat-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oat-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oat-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oat-method--large{min-width:64px;padding:4px 8px;font-size:12px}.oat-side{display:flex;flex-direction:column;overflow:hidden}.oat-side__header{padding:12px 12px 8px}.oat-side__title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--nvb-color-neutral-900)}.oat-side__meta{margin:2px 0 0;font-size:11px;line-height:16px;color:var(--nvb-color-neutral-500);font-variant-numeric:tabular-nums}.oat-side__search{padding:0 12px 10px}.oat-filters{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 10px;border-bottom:1px solid var(--nvb-color-neutral-200)}.oat-filter{border:1px solid color-mix(in oklab,currentColor 30%,transparent);cursor:pointer}.oat-filter--off{opacity:.4;text-decoration:line-through}.oat-spec{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-bottom:1px solid var(--nvb-color-neutral-200);color:var(--nvb-color-neutral-600);font-size:12px}.oat-spec select{height:var(--nvb-input-height);border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-side__scroll{flex:1 1 auto;min-height:0;padding:0 8px 16px;overflow-y:auto}.oat-group__title{position:sticky;top:0;z-index:1;margin:8px 0 2px;padding:6px 8px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-600);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.oat-group__list{margin:0;padding:0;list-style:none}.oat-item{display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.oat-item:hover{background:color-mix(in oklab,var(--nvb-color-neutral-100) 70%,transparent)}.oat-item:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:-2px}.oat-item--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))}.oat-item--active:hover{background:color-mix(in oklab,var(--nvb-color-primary-500) 16%,var(--nvb-color-neutral-000))}.oat-item--deprecated .oat-item__path{text-decoration:line-through}.oat-item__text{display:flex;flex-direction:column;min-width:0}.oat-item__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oat-item__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oat-main{overflow-y:auto}.oat-detail{max-width:960px;padding:20px 28px 40px}.oat-detail__line{display:flex;align-items:center;gap:10px;min-width:0}.oat-detail__path{min-width:0;overflow-wrap:anywhere;font-family:Consolas,Courier New,monospace;font-size:14px;color:var(--nvb-color-neutral-900)}.oat-detail__summary{margin:10px 0 0;font-size:18px;font-weight:600;color:var(--nvb-color-neutral-900)}.oat-detail__description{margin:6px 0 0;color:var(--nvb-color-neutral-700);font-size:13px;line-height:1.5;white-space:pre-line}.oat-detail__actions{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:14px}.oat-detail__id{color:var(--nvb-color-neutral-500);font-size:12px}.oat-button{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 14px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.oat-button nvb-icon{width:18px;height:18px}.oat-button:hover:not(:disabled){border-color:var(--nvb-color-primary-400)}.oat-button:disabled{opacity:.5;cursor:default}.oat-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oat-button--primary{border-color:var(--nvb-color-primary-600);background:var(--nvb-color-primary-600);color:var(--nvb-color-neutral-000, #fff)}.oat-button--primary:hover:not(:disabled){border-color:var(--nvb-color-primary-700);background:var(--nvb-color-primary-700)}.oat-link{margin-left:6px;padding:0;border:0;background:none;color:var(--nvb-color-primary-700);font:inherit;text-decoration:underline;cursor:pointer}.oat-notice{margin:8px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.45}.oat-detail .oat-notice{margin:10px 0 0}.oat-notice--error{color:var(--nvb-color-risk-700, #b91c1c)}.oat-notice--warning{color:var(--nvb-color-warning-700, #b45309)}.oat-notice--success{color:var(--nvb-color-success-700, #15803d)}.oat-empty{padding:40px;color:var(--nvb-color-neutral-500)}.oat-section{margin-top:26px}.oat-section__title{display:flex;align-items:baseline;gap:10px;margin:0 0 8px;font-size:13px;font-weight:700;color:var(--nvb-color-neutral-800)}.oat-section__meta{color:var(--nvb-color-neutral-500);font-size:12px;font-weight:400}.oat-table{width:100%;border-collapse:collapse;font-size:12px}.oat-table th,.oat-table td{padding:6px 8px;border-bottom:1px solid var(--nvb-color-neutral-200);text-align:left;vertical-align:top}.oat-table th{color:var(--nvb-color-neutral-600);font-weight:600}.oat-table code{font-family:Consolas,Courier New,monospace}.oat-required{margin-left:2px;color:var(--nvb-color-risk-600, #dc2626);font-weight:700}.oat-type{color:var(--nvb-color-info-700, #1d4ed8);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-flag{padding:0 5px;border-radius:3px;background:var(--nvb-color-neutral-100);color:var(--nvb-color-neutral-600);font-size:11px}.oat-schema{padding:8px 12px;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000)}.oat-tree{margin:0;padding:0;list-style:none}.oat-tree .oat-tree{margin:2px 0 4px 6px;padding-left:12px;border-left:1px solid var(--nvb-color-neutral-200)}.oat-tree__row{padding:3px 0}.oat-tree__line{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.oat-tree__name{font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-900)}.oat-tree__description{margin:1px 0 0;color:var(--nvb-color-neutral-600);font-size:12px}.oat-segmented{display:inline-flex;margin-bottom:8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;overflow:hidden}.oat-segmented button{padding:4px 12px;border:0;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;cursor:pointer}.oat-segmented button+button{border-left:1px solid var(--nvb-color-neutral-300)}.oat-segmented__on{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))!important;color:var(--nvb-color-primary-700)!important;font-weight:600}.oat-code{max-height:420px;margin:0;padding:10px 12px;overflow:auto;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font-family:Consolas,Courier New,monospace;font-size:12px;line-height:1.45}.oat-try__fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-bottom:10px}.oat-try__field{display:flex;flex-direction:column;gap:4px;color:var(--nvb-color-neutral-700);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-try__field input{height:var(--nvb-input-height);padding:0 8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-try__status{margin:10px 0 6px;color:var(--nvb-color-success-700, #15803d);font-size:12px;font-weight:600}.oat-try__status--error{color:var(--nvb-color-risk-700, #b91c1c)}@media(max-width:760px){.oat{grid-template-columns:1fr;grid-template-rows:minmax(200px,40%) 1fr}.oat-detail{padding:16px}}\n"]}]}],ctorParameters:()=>[]});const le="ngx-view-builder-openapi";const $="openApi";function Re(t){ue(E);return be([{provide:z,useValue:t},xe({featurePacks:[{id:le,title:"OpenAPI",capabilities:[$],builderTabs:[{id:re,label:"tab.openApi",component:pe,order:380,icon:ae.api,description:"tab.openApi.description"}],dataSourceEditorExtensions:[{id:$,label:"openApi.picker.row",component:ie,sourceTypes:["rest"],order:10}],uiDictionaries:{en:{...de,...E}}}]}),ve(()=>{b(ye).register(Ee(b(y),b(ge),b(T)))})])}export{oe as NGX_VIEW_BUILDER_OPENAPI_AI_ID,$ as NGX_VIEW_BUILDER_OPENAPI_CAPABILITY,z as NGX_VIEW_BUILDER_OPENAPI_CONFIG,le as NGX_VIEW_BUILDER_OPENAPI_FEATURE_PACK_ID,re as NGX_VIEW_BUILDER_OPENAPI_TAB_ID,E as NGX_VIEW_BUILDER_OPENAPI_UI_EN,y as OpenApiState,J as buildOpenApiCatalog,Re as provideNgxViewBuilderOpenApi};
|
|
1
|
+
import{NGX_VIEW_BUILDER_SHARED_UI_EN as Ae,NvbPluginI18n as J,NvbProFeatureGate as S,NvbTranslatePipe as O,registerPluginEnglish as Te}from"@ngxviewbuilder/plugin-shared";import*as m from"@angular/core";import{ChangeDetectionStrategy as Q,Component as Y,Injectable as Ie,InjectionToken as Se,computed as h,effect as ee,inject as g,input as te,makeEnvironmentProviders as Oe,provideEnvironmentInitializer as Ne,signal as f,untracked as D,viewChild as Ce}from"@angular/core";import{BuilderSearchInput as ne,LicenseService as Pe,NgxViewBuilderAiPluginRegistryService as De,NgxViewBuilderApiService as E,NvbIcon as N,provideNgxViewBuilderExtensions as Ee}from"@ngxviewbuilder/designer";import{HttpClient as oe}from"@angular/common/http";import{firstValueFrom as ae}from"rxjs";import{NgTemplateOutlet as re}from"@angular/common";const $e=["GET","POST","PUT","PATCH","DELETE"];const qe=8;const d=t=>!!t&&typeof t==="object"&&!Array.isArray(t);const l=t=>typeof t==="string"?t.trim():"";function ie(t,e,n){if(!d(t))throw new Error("The API description is not a JSON object.");if(l(t["asyncapi"]))return Be(t,e,n);if(l(t["swagger"]))throw new Error("Swagger 2.0 is not supported. Convert the description to OpenAPI 3 first.");if(!l(t["openapi"]).startsWith("3."))throw new Error('Not an OpenAPI 3 document: the "openapi" field is missing.');const o=d(t["info"])?t["info"]:{};const r=new pe(t);const a=[];const i=new Set;const s=d(t["paths"])?t["paths"]:{};Object.entries(s).forEach(([c,v])=>{const u=r.deref(v);if(!d(u))return;const k=Array.isArray(u["parameters"])?u["parameters"]:[];$e.forEach(b=>{const y=r.deref(u[b.toLowerCase()]);if(!d(y))return;a.push(Me(e.id,b,c,y,k,r,i))})});const p=(Array.isArray(t["tags"])?t["tags"]:[]).filter(d).map(c=>({name:l(c["name"]),description:l(c["description"])})).filter(c=>c.name);return{specId:e.id,title:l(e.title)||l(o["title"])||e.id,version:l(o["version"]),description:l(o["description"]),baseUrl:Ue(e,t,n),tags:p,operations:a}}function Me(t,e,n,o,r,a,i){const s=new Map;[...r,...Array.isArray(o["parameters"])?o["parameters"]:[]].map(c=>a.deref(c)).filter(d).forEach(c=>{const v=l(c["in"]);const u=l(c["name"]);if(!u||!["path","query","header","cookie"].includes(v))return;s.set(`${v}:${u}`,{name:u,in:v,required:v==="path"||c["required"]===true,description:l(c["description"])||void 0,schema:c["schema"]?a.schema(c["schema"]):void 0})});let p=l(o["operationId"])||Ve(e,n);if(i.has(p))p=`${p}${e.charAt(0)}${e.slice(1).toLowerCase()}`;i.add(p);return{key:`${t} ${e} ${n}`,specId:t,operationId:p,method:e,path:n,summary:l(o["summary"]),description:l(o["description"]),tags:(Array.isArray(o["tags"])?o["tags"]:[]).map(l).filter(Boolean),deprecated:o["deprecated"]===true,parameters:Array.from(s.values()),requestBody:Re(a.deref(o["requestBody"]),a),...ze(o["responses"],a)}}function ze(t,e){const n=Le(t,e);if(n?.schema||!d(t))return{kind:"rest",response:n};const o=Object.keys(t).filter(v=>/^2(\d\d|XX)$/i.test(v)).sort()[0];const r=o?e.deref(t[o]):null;const a=(d(r)&&d(r["content"])?r["content"]:null)?.["text/event-stream"];if(!d(a))return{kind:"rest",response:n};const i=e.deref(a["itemSchema"]??a["schema"]);const s=d(i)&&d(i["properties"])?i["properties"]:null;let p=[];let c=i;if(s&&s["data"]){const v=e.deref(s["event"]);if(d(v))p=Array.isArray(v["enum"])?v["enum"].map(l).filter(Boolean):l(v["const"])?[l(v["const"])]:[];const u=e.deref(s["data"]);c=d(u)&&u["contentSchema"]?u["contentSchema"]:u}return{kind:"sse",events:p,response:{status:o,description:l(d(r)?r["description"]:""),schema:d(c)?e.schema(c):void 0}}}function Be(t,e,n){if(!l(t["asyncapi"]).startsWith("3."))throw new Error("Only AsyncAPI 3 documents are supported.");const o=d(t["info"])?t["info"]:{};const r=new pe(t);const a=d(t["channels"])?t["channels"]:{};const i=d(t["operations"])?t["operations"]:{};const s=new Map;Object.entries(i).forEach(([u,k])=>{const b=r.deref(k);if(!d(b))return;const y=(d(b["channel"])?l(b["channel"]["$ref"]):"").split("/").pop()??"";if(!y)return;const _=s.get(y)??[];_.push({id:u,action:l(b["action"]),summary:l(b["summary"])||l(b["description"]),messages:Array.isArray(b["messages"])?b["messages"]:[]});s.set(y,_)});const p=(u,k)=>{const b=u.length?u:Object.values(d(k["messages"])?k["messages"]:{});for(const y of b){const _=r.deref(y);if(d(_)&&_["payload"])return _["payload"]}};const c=[];Object.entries(a).forEach(([u,k])=>{const b=r.deref(k);if(!d(b))return;const y=l(b["address"])||`/${u}`;const _=y.startsWith("/")?y:`/${y}`;const I=s.get(u)??[];const A=I.filter(x=>x.action==="send");const _e=I.filter(x=>x.action==="receive");const X=p(A.flatMap(x=>x.messages),A.length?{}:b);const K=p(_e.flatMap(x=>x.messages),{});const ke=A[0]?.id??I[0]?.id??u;c.push({key:`${e.id} WS ${_}`,kind:"websocket",specId:e.id,operationId:ke,method:"GET",path:_,summary:l(b["title"])||A[0]?.summary||I[0]?.summary||"",description:l(b["description"]),tags:(Array.isArray(b["tags"])?b["tags"]:[]).map(x=>d(x)?l(x["name"]):"").filter(Boolean),deprecated:false,parameters:Object.entries(d(b["parameters"])?b["parameters"]:{}).map(([x,we])=>{const Z=r.deref(we);return{name:x,in:"path",required:true,description:d(Z)?l(Z["description"])||void 0:void 0}}),requestBody:K?{required:false,contentType:"application/json",schema:r.schema(K)}:void 0,response:X?{status:"message",description:A[0]?.summary??"",schema:r.schema(X)}:void 0})});const v=(Array.isArray(o["tags"])?o["tags"]:[]).filter(d).map(u=>({name:l(u["name"]),description:l(u["description"])})).filter(u=>u.name);return{specId:e.id,title:l(e.title)||l(o["title"])||e.id,version:l(o["version"]),description:l(o["description"]),baseUrl:je(e,t,n),tags:v,operations:c}}function je(t,e,n){const o=l(t.baseUrl);if(o)return o.replace(/\/+$/,"");const r=(d(e["servers"])?Object.values(e["servers"]):[]).find(s=>d(s)&&/^wss?$/i.test(l(s["protocol"])));if(!r)return"";const a=l(r["host"]);const i=l(r["pathname"]).replace(/\/+$/,"");if(typeof window!=="undefined"&&(!a||a===window.location.host))return i;return`${l(r["protocol"]).toLowerCase()||"wss"}://${a}${i}`}function Re(t,e){if(!d(t))return;const n=se(t["content"]);if(!n)return;return{required:t["required"]===true,contentType:n.contentType,schema:n.schema?e.schema(n.schema):void 0}}function Le(t,e){if(!d(t))return;const n=Object.keys(t).filter(a=>/^2(\d\d|XX)$/i.test(a)).sort()[0];if(!n)return;const o=e.deref(t[n]);if(!d(o))return;const r=se(o["content"]);return{status:n,description:l(o["description"]),schema:r?.schema?e.schema(r.schema):void 0}}function se(t){if(!d(t))return null;const e=Object.keys(t).find(o=>/^application\/json/i.test(o))??Object.keys(t).find(o=>/\+json/i.test(o));if(!e)return null;const n=t[e];return{contentType:e,schema:d(n)?n["schema"]:void 0}}function Ve(t,e){const n=e.split("/").filter(Boolean).map(o=>o.replace(/[{}]/g,"")).flatMap(o=>o.split(/[^a-zA-Z0-9]+/)).filter(Boolean);return[t.toLowerCase(),...n].map((o,r)=>r===0?o:o.charAt(0).toUpperCase()+o.slice(1)).join("")}function Ue(t,e,n){const o=l(t.baseUrl);const r=Array.isArray(e["servers"])?e["servers"]:[];const a=o||l(d(r[0])?r[0]["url"]:"");if(!a)return"";if(o||/^https?:\/\//i.test(a)||!n||typeof window==="undefined")return a.replace(/\/+$/,"");const i=new URL(a,new URL(n,window.location.href));return(i.origin===window.location.origin?i.pathname:i.href).replace(/\/+$/,"")}var pe=class{document;constructor(t){this.document=t}deref(t,e=new Set){if(!d(t)||typeof t["$ref"]!=="string")return t;const n=t["$ref"];if(e.has(n))return{};e.add(n);return this.deref(this.pointer(n),e)}schema(t,e=0,n=[]){if(!d(t))return{};let o;if(typeof t["$ref"]==="string"){const p=t["$ref"];o=p.startsWith("#/components/schemas/")?p.split("/").pop():void 0;if(n.includes(p)||e>=qe)return{type:"object",refName:o,circular:true};const c=this.pointer(p);return{...this.schema(c,e+1,[...n,p]),...o?{refName:o}:{}}}const r=p=>this.schema(p,e+1,n);const a={};const i=t["type"];if(Array.isArray(i)){const p=i.map(l).filter(Boolean);a.type=p.find(c=>c!=="null");a.nullable=p.includes("null")||void 0}else if(l(i))a.type=l(i);if(t["nullable"]===true)a.nullable=true;if(l(t["format"]))a.format=l(t["format"]);if(l(t["title"]))a.title=l(t["title"]);if(l(t["description"]))a.description=l(t["description"]);if(Array.isArray(t["enum"]))a.enum=[...t["enum"]];if(t["example"]!==void 0)a.example=t["example"];else if(Array.isArray(t["examples"])&&t["examples"].length)a.example=t["examples"][0];if(t["default"]!==void 0)a.default=t["default"];if(t["readOnly"]===true)a.readOnly=true;if(t["writeOnly"]===true)a.writeOnly=true;if(t["items"]!==void 0)a.items=r(t["items"]);if(d(t["properties"]))a.properties=Object.fromEntries(Object.entries(t["properties"]).map(([p,c])=>[p,r(c)]));if(Array.isArray(t["required"]))a.required=t["required"].map(l).filter(Boolean);if(Array.isArray(t["allOf"]))t["allOf"].map(r).forEach(p=>le(a,p));const s=Array.isArray(t["oneOf"])?t["oneOf"]:Array.isArray(t["anyOf"])?t["anyOf"]:null;if(s){const p=s.map(r);const c=p.filter(v=>v.type!=="null");if(c.length===1){le(a,c[0]);if(c.length<p.length)a.nullable=true}else a.variants=c}if(!a.type&&a.properties)a.type="object";if(!a.type&&a.items)a.type="array";return a}pointer(t){if(!t.startsWith("#/"))return{};return t.slice(2).split("/").map(e=>e.replace(/~1/g,"/").replace(/~0/g,"~")).reduce((e,n)=>d(e)?e[n]:void 0,this.document)}};function le(t,e){t.type??=e.type;t.format??=e.format;t.title??=e.title;t.description??=e.description;t.enum??=e.enum;t.example??=e.example;t.items??=e.items;t.refName??=e.refName;if(e.properties)t.properties={...t.properties??{},...e.properties};if(e.required)t.required=Array.from(new Set([...t.required??[],...e.required]))}function w(t,e=0){if(!t||e>6)return null;if(t.example!==void 0)return t.example;if(t.default!==void 0)return t.default;if(t.enum?.length)return t.enum[0];if(t.variants?.length)return w(t.variants[0],e+1);switch(t.type){case"object":return Object.fromEntries(Object.entries(t.properties??{}).filter(([,n])=>!n.writeOnly).map(([n,o])=>[n,w(o,e+1)]));case"array":return t.items?[w(t.items,e+1)]:[];case"integer":case"number":return 0;case"boolean":return false;case"string":return Fe(t.format);default:return null}}function Fe(t){switch(t){case"date":return"2026-01-31";case"date-time":return"2026-01-31T09:30:00Z";case"email":return"name@example.com";case"uuid":return"3fa85f64-5717-4562-b3fc-2c963f66afa6";case"uri":return"https://example.com";default:return"string"}}function ce(t,e,n="",o=0){if(!t||o>5)return{};if(t.type==="array")return[];if(t.type!=="object"&&!t.properties)return de(t);return Object.fromEntries(Object.entries(t.properties??{}).filter(([,r])=>!r.readOnly).map(([r,a])=>{const i=n?`${n}.${r}`:r;if(e.has(i))return[r,`{${i}}`];if(a.type==="object"||a.properties)return[r,ce(a,e,i,o+1)];return[r,de(a)]}))}function de(t){switch(t.type){case"integer":case"number":return 0;case"boolean":return false;case"array":return[];case"object":return{};default:return""}}function C(t){if(!t)return null;if(t.type==="array")return"";const e=Object.entries(t.properties??{});const n=e.filter(([,r])=>r.type==="array");const o=["items","data","results","content","records","rows","list"].map(r=>n.find(([a])=>a===r)).find(Boolean)??(n.length===1?n[0]:void 0);if(o)return o[0];for(const[r,a]of e)if(a.type==="object"){const i=C(a);if(i)return`${r}.${i}`}return null}function $(t){const e=C(t);if(e===null||!t)return;return e.split(".").filter(Boolean).reduce((n,o)=>n?.properties?.[o],t)?.items}function ue(t){const e=Object.keys(t?.properties??{});const n=o=>o.find(r=>e.includes(r))??e.find(r=>o.some(a=>r.toLowerCase()===a.toLowerCase()));return{value:n(["id","code","key","value","uuid","slug"]),label:n(["name","title","label","fullName","displayName","description","email"])}}function We(t,e){const n=e.parameters.filter(o=>o.in==="query"&&o.required).map(o=>`${encodeURIComponent(o.name)}={${o.name}}`).join("&");return`${t}${e.path}${n?`?${n}`:""}`}function q(t){const e=t.operationId.replace(/[^a-zA-Z0-9_]/g,"_").replace(/^_+|_+$/g,"");return/^[a-zA-Z]/.test(e)?e:`api_${e}`}function Ge(t){const e=new Set;const n=(o,r)=>{if(!o||typeof o!=="object")return;Object.values(o).forEach(a=>{const i=a;const s=String(i?.["name"]??"").trim();if(!i||!s||i["type"]==="page")return;const p=r?`${r}.${s}`:s;e.add(p);if(i["type"]==="objectPanel")n(i["template"],p)})};n(t?.["elements"],"");return e}function M(t,e){if(!e.length)return true;const n=[t.method,t.path,t.summary,t.operationId,...t.tags].join(" ").toLowerCase();return e.every(o=>n.includes(o))}const me=new Set(["POST","PUT","PATCH"]);function He(t){return{specId:t.specId,operationId:t.operationId,method:t.method,path:t.path}}function z(t){return t.kind}function P(t){if(t.kind==="sse")return{text:"SSE",tone:"live"};if(t.kind==="websocket")return{text:"WS",tone:"live"};return{text:t.method,tone:t.method.toLowerCase()}}function B(t,e,n){const o=We(e.baseUrl,t);const r=He(t);const a=i=>i?.schema?ce(i.schema,Ge(n)):void 0;if(t.kind==="sse")return{url:o,events:(t.events??[]).join(", "),messagePath:"",messageMode:"replaceCurrent",openApi:r,method:void 0,body:void 0,payload:void 0};if(t.kind==="websocket")return{url:o,protocols:"",message:a(t.requestBody)??"",messagePath:"",messageMode:"replaceCurrent",openApi:r,method:void 0,body:void 0,payload:void 0};return{url:o,method:t.method,openApi:r,body:me.has(t.method)?a(t.requestBody):void 0,payload:void 0,events:void 0,message:void 0,protocols:void 0}}function Xe(t){return Object.fromEntries(Object.entries(t).filter(([,e])=>e!==void 0))}function Ke(t,e,n,o){const r=t.dataSources??=[];const a=new Set(r.map(p=>p.name));const i=String(o??"").trim()||q(e);let s=i;for(let p=2;a.has(s);p+=1)s=`${i}${p}`;r.push({name:s,title:e.summary||e.operationId,type:z(e),params:Xe(B(e,n,t))});return s}const he="openApi";const j=80;function Ze(t,e,n){return{id:he,title:"API (OpenAPI)",description:"The application's REST API as its OpenAPI description lists it: endpoints with method, path, parameters, request body and response fields. Read it before adding a REST data source. Reading one operationId returns a ready `dataSource` object (URL, method, and for POST/PUT/PATCH a body with every field the API accepts, fields named like view fields bound as {name}); add it with execute([{ op: 'upsertDataSource', dataSource }]), unchanged or trimmed, and bind elements to its name. An SSE or WS endpoint gives a live source (type sse or websocket) that keeps updating the bound elements.",query:{search:"Optional words that must all appear in the method, path, summary, operationId or tag.",operationId:"Optional. Returns that one operation in full: parameters, request body fields, response fields and an example response."},available:()=>e.proAccess()==="ready",unavailableReason:()=>"The OpenAPI plugin needs an active NGX View Builder license key in the builder settings.",read:async o=>{await t.ensureLoaded();const r=String(o["operationId"]??"").trim();if(r){const s=t.operations().find(c=>c.operationId===r);if(!s)throw new Error(`No operation '${r}' in the API descriptions.`);const p=t.catalogOf(s.specId);return{...Je(s,p?.baseUrl??""),dataSource:p?{name:q(s),title:s.summary||s.operationId,type:z(s),params:B(s,p,n.getStructure())}:void 0}}const a=String(o["search"]??"").toLowerCase().split(/\s+/).filter(Boolean);const i=t.operations().filter(s=>M(s,a));return{apis:t.catalogs().map(s=>({id:s.specId,title:s.title,version:s.version,baseUrl:s.baseUrl})),errors:t.specs().filter(({status:s})=>s.state==="error").map(({spec:s,status:p})=>({id:s.id,message:p.state==="error"?p.message:""})),total:i.length,operations:i.slice(0,j).map(s=>({operationId:s.operationId,api:s.specId,method:s.method,path:s.path,summary:s.summary||void 0,tags:s.tags.length?s.tags:void 0,deprecated:s.deprecated||void 0})),...i.length>j?{note:`Showing ${j}; narrow it with search.`}:{}}}}}function Je(t,e){return{operationId:t.operationId,method:t.method,url:`${e}${t.path}`,summary:t.summary||void 0,description:t.description||void 0,deprecated:t.deprecated||void 0,parameters:t.parameters.map(n=>({name:n.name,in:n.in,required:n.required,type:R(n.schema),description:n.description})),requestBody:t.requestBody?{required:t.requestBody.required,fields:L(t.requestBody.schema)}:void 0,response:t.response?{status:t.response.status,rowsPath:C(t.response.schema),optionFields:ue($(t.response.schema)),fields:L($(t.response.schema)??t.response.schema),example:w(t.response.schema)}:void 0}}function R(t){if(!t)return;if(t.type==="array")return`${R(t.items)??"any"}[]`;return[t.refName??t.type,t.format].filter(Boolean).join(":")||void 0}function L(t,e="",n=0){if(!t?.properties||n>3)return[];const o=new Set(t.required??[]);return Object.entries(t.properties).flatMap(([r,a])=>{const i=e?`${e}.${r}`:r;return[{path:i,type:R(a),required:o.has(r)||void 0,enum:a.enum,readOnly:a.readOnly||void 0,description:a.description},...a.type==="object"?L(a,i,n+1):[]]})}const V={"tab.openApi":"API","tab.openApi.description":"Browse the API and turn its endpoints into data sources","openApi.title":"API","openApi.meta":"{count} endpoints in {specs} API descriptions","openApi.endpoints":"Endpoints","openApi.search":"Search by path, name or tag","openApi.clearSearch":"Clear search","openApi.methods":"Show methods","openApi.spec":"API","openApi.spec.all":"All APIs","openApi.untagged":"Other","openApi.loading":"Loading the API description…","openApi.loadFailed":"Could not load {title}: {message}","openApi.retry":"Try again","openApi.noSpecs":"No API description is configured. The application adds one with provideNgxViewBuilderOpenApi({ specs: [...] }).","openApi.noMatch":"No endpoint matches the search.","openApi.deprecated":"Deprecated","openApi.deprecatedText":"This endpoint is deprecated and may be removed from the API.","openApi.use":"Use as data source","openApi.added":'Data source "{name}" added to the view.',"openApi.usedBy":"Used by data source: {names}","openApi.parameters":"Parameters","openApi.col.name":"Name","openApi.col.in":"In","openApi.col.type":"Type","openApi.col.description":"Description","openApi.required":"required","openApi.readOnly":"read only","openApi.oneOf":"One of:","openApi.requestBody":"Request body","openApi.response":"Response","openApi.example":"Example","openApi.schema":"Schema","openApi.try":"Try it","openApi.try.getOnly":"Only GET requests can be sent from here, so nothing is changed on the server.","openApi.try.send":"Send request","openApi.try.sending":"Sending…","openApi.try.unreachable":"The address could not be reached","openApi.try.truncated":"the rest of the response is not shown","openApi.picker.row":"API","openApi.picker.label":"Find an API endpoint","openApi.picker.placeholder":"Find an endpoint, e.g. customers","openApi.picker.noSpecs":"No API description is configured for this application.","openApi.picker.noMatch":"No endpoint matches.","openApi.picker.listRoot":"The response is the list itself.","openApi.picker.listAt":'The rows are in "{path}": use it as the Items path of the element that shows them.',"openApi.picker.optionFields":'For options, "{value}" works as the value and "{label}" as the label.',"openApi.picker.editBody":"Choose body fields","openApi.picker.change":"Pick another endpoint","openApi.picker.show":"Show in API tab","openApi.picker.unlink":"Unlink","openApi.picker.events":"Listens to the events: {events}.","openApi.message":"Message","openApi.connectMessage":"Connect message","openApi.try.live":"A live endpoint keeps the connection open. Add it as a data source and watch it in the Preview.","openApi.liveDisabled.sse":"Event stream sources are not turned on in this builder. The application enables them with setDataSourceTypeSettings({ enableEventStream: true }).","openApi.liveDisabled.websocket":"WebSocket sources are not turned on in this builder. The application enables them with setDataSourceTypeSettings({ enableWebsocket: true }).","openApi.picker.missing":"This data source was made from {method} {path}, which the API description no longer has."};const be={api:"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 -960 960 960\"><path d=\"M480-375 375-480l105-105 105 105-105 105Zm-85-294-83-83 168-168 168 168-83 83-85-85-85 85ZM208-312 40-480l168-168 83 83-85 85 85 85-83 83Zm544 0-83-83 85-85-85-85 83-83 168 168-168 168ZM480-40 312-208l83-83 85 85 85-85 83 83L480-40Z\"/></svg>",play_arrow:"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 -960 960 960\"><path d=\"M320-203v-560l440 280-440 280Zm60-280Zm0 171 269-171-269-171v342Z\"/></svg>"};const U=new Se("NGX_VIEW_BUILDER_OPENAPI_CONFIG");const ve="openApi";var T=class W{config=g(U,{optional:true});http=g(oe,{optional:true});statuses=f({},...ngDevMode?[{debugName:"statuses"}]:[]);loading=null;selectedKey=f(null,...ngDevMode?[{debugName:"selectedKey"}]:[]);specs=h(()=>(this.config?.specs??[]).map(e=>({spec:e,status:this.statuses()[e.id]??{state:"loading"}})),...ngDevMode?[{debugName:"specs"}]:[]);catalogs=h(()=>this.specs().map(({status:e})=>e.state==="ready"?e.catalog:null).filter(e=>!!e),...ngDevMode?[{debugName:"catalogs"}]:[]);operations=h(()=>this.catalogs().flatMap(e=>e.operations),...ngDevMode?[{debugName:"operations"}]:[]);hasSpecs=h(()=>(this.config?.specs??[]).length>0,...ngDevMode?[{debugName:"hasSpecs"}]:[]);ensureLoaded(){this.loading??=this.loadAll();return this.loading}reload(){this.statuses.set({});this.loading=this.loadAll();return this.loading}catalogOf(e){return this.catalogs().find(n=>n.specId===e)??null}findOperation(e,n){return this.operations().find(o=>o.specId===e&&o.operationId===n)??null}async loadAll(){await Promise.all((this.config?.specs??[]).map(e=>this.loadOne(e.id)))}async loadOne(e){const n=this.config?.specs.find(o=>o.id===e);if(!n)return;this.setStatus(e,{state:"loading"});try{const o=ie(n.document??await this.fetchDocument(n.url),n,n.url);this.setStatus(e,{state:"ready",catalog:o})}catch(o){this.setStatus(e,{state:"error",message:Qe(o)})}}async fetchDocument(e){const n=String(e??"").trim();if(!n)throw new Error("The spec has neither a url nor a document.");if(this.http)return ae(this.http.get(n));const o=await fetch(n);if(!o.ok)throw new Error(`HTTP ${o.status}`);return o.json()}setStatus(e,n){this.statuses.update(o=>({...o,[e]:n}))}static ɵfac=m.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:W,deps:[],target:m.ɵɵFactoryTarget.Injectable});static ɵprov=m.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:W,providedIn:"root"})};m.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:T,decorators:[{type:Ie,args:[{providedIn:"root"}]}]});function Qe(t){const e=t;if(e&&typeof e.status==="number"&&e.status>0)return`HTTP ${e.status}${e.statusText?` ${e.statusText}`:""}`;if(e&&e.status===0)return"The address could not be reached.";return String(e?.message??t??"Unknown error")}const Ye=60;var fe=class G{source=te(null,...ngDevMode?[{debugName:"source"}]:[]);context=te(null,...ngDevMode?[{debugName:"context"}]:[]);state=g(T);api=g(E);i18n=g(J);searchInput=Ce("searchInput",...ngDevMode?[{debugName:"searchInput"}]:[]);query=f("",...ngDevMode?[{debugName:"query"}]:[]);open=f(false,...ngDevMode?[{debugName:"open"}]:[]);activeIndex=f(0,...ngDevMode?[{debugName:"activeIndex"}]:[]);revision=f(0,...ngDevMode?[{debugName:"revision"}]:[]);listId=`nvb-openapi-picker-${Math.random().toString(36).slice(2,8)}`;link=h(()=>{this.revision();const e=this.source()?.params?.["openApi"];return e&&e.specId&&e.operationId?e:null},...ngDevMode?[{debugName:"link"}]:[]);linkedOperation=h(()=>{const e=this.link();return e?this.state.findOperation(e.specId,e.operationId):null},...ngDevMode?[{debugName:"linkedOperation"}]:[]);multipleSpecs=h(()=>this.state.catalogs().length>1,...ngDevMode?[{debugName:"multipleSpecs"}]:[]);results=h(()=>{const e=this.query().toLowerCase().split(/\s+/).filter(Boolean);const n=this.allowedKinds();return this.state.operations().filter(o=>n.has(o.kind)).filter(o=>M(o,e)).slice(0,Ye)},...ngDevMode?[{debugName:"results"}]:[]);loadErrors=h(()=>this.state.specs().filter(({status:e})=>e.state==="error").map(({spec:e,status:n})=>({title:e.title||e.id,message:n.state==="error"?n.message:""})),...ngDevMode?[{debugName:"loadErrors"}]:[]);loading=h(()=>this.state.specs().some(({status:e})=>e.state==="loading"),...ngDevMode?[{debugName:"loading"}]:[]);responseHint=h(()=>{const e=this.linkedOperation();const n=e?.response?.schema;if(!e||!n||e.kind!=="rest")return null;const o=C(n);const r=ue($(n));return{listPath:o,value:r.value??null,label:r.label??null}},...ngDevMode?[{debugName:"responseHint"}]:[]);allowedKinds(){const e=this.api.getDataSourceTypeSettings();return new Set(["rest",...e.enableEventStream?["sse"]:[],...e.enableWebsocket?["websocket"]:[]])}badge(e){return P(e)}hasBody=h(()=>{this.revision();const e=String(this.source()?.params?.["method"]??"").toUpperCase();return this.linkedOperation()?.kind==="rest"&&me.has(e==="TABLE-POST"?"POST":e)&&!!this.linkedOperation()?.requestBody},...ngDevMode?[{debugName:"hasBody"}]:[]);constructor(){this.state.ensureLoaded()}methodTone(e){return e.toLowerCase()}onQuery(e){this.query.set(e);this.activeIndex.set(0);this.open.set(true)}onFocus(){this.open.set(true)}onBlur(e){if(e.relatedTarget?.closest(`#${this.listId}`))return;this.open.set(false)}onKeydown(e){const n=this.results().length;if(e.key==="ArrowDown"){e.preventDefault();this.open.set(true);this.activeIndex.set(n?(this.activeIndex()+1)%n:0)}else if(e.key==="ArrowUp"){e.preventDefault();this.activeIndex.set(n?(this.activeIndex()-1+n)%n:0)}else if(e.key==="Enter"){const o=this.results()[this.activeIndex()];if(this.open()&&o){e.preventDefault();this.choose(o)}}else if(e.key==="Escape"&&this.open()){e.stopPropagation();this.open.set(false)}}choose(e){const n=this.context();const o=this.source();const r=this.state.catalogOf(e.specId);if(!n||!o||!r)return;const a=B(e,r,this.api.getStructure());const i=!/^source\d+(_\d+)?$/i.test(String(o.name??"").trim());const s=!/^source \d+$/i.test(String(o.title??"").trim());n.apply({type:z(e),params:a,...i?{}:{name:q(e)},...s?{}:{title:e.summary||e.operationId}});this.revision.update(p=>p+1);this.query.set("");this.open.set(false)}change(){this.open.set(true);queueMicrotask(()=>this.searchInput()?.nativeElement.focus())}unlink(){this.context()?.apply({params:{openApi:void 0}});this.revision.update(e=>e+1)}openBodyBuilder(){this.context()?.openBodyBuilder()}showInApiTab(){const e=this.linkedOperation();if(!e)return;this.state.selectedKey.set(e.key);this.api.switchToTab("openApi")}specTitle(e){return this.state.catalogOf(e)?.title??e}placeholder(){return this.i18n.t("openApi.picker.placeholder")}static ɵfac=m.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:G,deps:[],target:m.ɵɵFactoryTarget.Component});static ɵcmp=m.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.2.1",type:G,isStandalone:true,selector:"nvb-openapi-picker",inputs:{source:{classPropertyName:"source",publicName:"source",isSignal:true,isRequired:false,transformFunction:null},context:{classPropertyName:"context",publicName:"context",isSignal:true,isRequired:false,transformFunction:null}},viewQueries:[{propertyName:"searchInput",first:true,predicate:["searchInput"],descendants:true,isSignal:true}],ngImport:m,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\" [compact]=\"true\">\n <div class=\"oap\" data-testid=\"openapi-picker\">\n @if (!state.hasSpecs()) {\n <p class=\"oap-hint\">{{ 'openApi.picker.noSpecs' | nvbT }}</p>\n } @else {\n @if (linkedOperation(); as operation) {\n <div class=\"oap-card\" data-testid=\"openapi-picker-linked\">\n <div class=\"oap-card__line\">\n <span class=\"oap-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <code class=\"oap-card__path\">{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <p class=\"oap-card__summary\">{{ operation.summary }}</p>\n }\n @if (operation.kind === 'sse' && operation.events?.length) {\n <ul class=\"oap-card__facts\">\n <li>{{ 'openApi.picker.events' | nvbT: { events: operation.events!.join(', ') } }}</li>\n </ul>\n }\n @if (responseHint(); as hint) {\n <ul class=\"oap-card__facts\">\n @if (hint.listPath === '') {\n <li>{{ 'openApi.picker.listRoot' | nvbT }}</li>\n } @else if (hint.listPath) {\n <li>{{ 'openApi.picker.listAt' | nvbT: { path: hint.listPath } }}</li>\n }\n @if (hint.value && hint.label) {\n <li>{{ 'openApi.picker.optionFields' | nvbT: { value: hint.value, label: hint.label } }}</li>\n }\n </ul>\n }\n <div class=\"oap-card__actions\">\n @if (hasBody()) {\n <button type=\"button\" class=\"oap-button oap-button--primary\" (click)=\"openBodyBuilder()\" data-testid=\"openapi-picker-body\">\n {{ 'openApi.picker.editBody' | nvbT }}\n </button>\n }\n <button type=\"button\" class=\"oap-button\" (click)=\"change()\" data-testid=\"openapi-picker-change\">\n {{ 'openApi.picker.change' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button\" (click)=\"showInApiTab()\" data-testid=\"openapi-picker-show\">\n {{ 'openApi.picker.show' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button oap-button--quiet\" (click)=\"unlink()\" data-testid=\"openapi-picker-unlink\">\n {{ 'openApi.picker.unlink' | nvbT }}\n </button>\n </div>\n </div>\n } @else if (link(); as staleLink) {\n <p class=\"oap-hint oap-hint--warning\" data-testid=\"openapi-picker-missing\">\n {{ 'openApi.picker.missing' | nvbT: { method: staleLink.method, path: staleLink.path } }}\n </p>\n }\n\n <div class=\"oap-combo\" [class.oap-combo--hidden]=\"linkedOperation() && !open()\">\n <span class=\"oap-combo__icon\" aria-hidden=\"true\"><nvb-icon name=\"search\" /></span>\n <input\n #searchInput\n type=\"text\"\n class=\"oap-combo__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"open() && results().length ? listId + '-' + activeIndex() : null\"\n [attr.aria-label]=\"'openApi.picker.label' | nvbT\"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onQuery($any($event.target).value)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur($event)\"\n (keydown)=\"onKeydown($event)\"\n data-testid=\"openapi-picker-search\"\n />\n @if (open()) {\n <ul class=\"oap-list\" role=\"listbox\" [id]=\"listId\" data-testid=\"openapi-picker-results\">\n @for (operation of results(); track operation.key; let index = $index) {\n <li\n role=\"option\"\n tabindex=\"-1\"\n class=\"oap-option\"\n [id]=\"listId + '-' + index\"\n [class.oap-option--active]=\"index === activeIndex()\"\n [attr.aria-selected]=\"index === activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mouseenter)=\"activeIndex.set(index)\"\n (click)=\"choose(operation)\"\n [attr.data-testid]=\"'openapi-picker-option-' + operation.operationId\"\n >\n <span class=\"oap-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <span class=\"oap-option__text\">\n <code class=\"oap-option__path\">{{ operation.path }}</code>\n <span class=\"oap-option__summary\">\n {{ operation.summary || operation.operationId }}\n @if (multipleSpecs()) {\n · {{ specTitle(operation.specId) }}\n }\n </span>\n </span>\n @if (operation.deprecated) {\n <span class=\"oap-option__flag\">{{ 'openApi.deprecated' | nvbT }}</span>\n }\n </li>\n } @empty {\n <li class=\"oap-list__empty\" role=\"presentation\">\n @if (loading()) {\n {{ 'openApi.loading' | nvbT }}\n } @else {\n {{ 'openApi.picker.noMatch' | nvbT }}\n }\n </li>\n }\n </ul>\n }\n </div>\n\n @for (failure of loadErrors(); track failure.title) {\n <p class=\"oap-hint oap-hint--warning\">{{ 'openApi.loadFailed' | nvbT: { title: failure.title, message: failure.message } }}</p>\n }\n }\n </div>\n</nvb-pro-feature-gate>\n",styles:[":host{display:block;min-width:0}.oap{display:flex;flex-direction:column;gap:6px;min-width:0}.oap-hint{margin:0;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.4}.oap-hint--warning{color:var(--nvb-color-warning-700, #b45309)}.oap-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oap-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oap-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oap-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oap-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oap-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oap-method[data-method=live]{color:var(--nvb-color-info-700, #1d4ed8);background:color-mix(in oklab,currentColor 14%,transparent)}.oap-card{display:flex;flex-direction:column;gap:6px;padding:8px 10px;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:color-mix(in oklab,var(--nvb-color-neutral-050) 70%,var(--nvb-color-surface-mix-target, white))}.oap-card__line{display:flex;align-items:center;gap:8px;min-width:0}.oap-card__path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-card__summary{margin:0;color:var(--nvb-color-neutral-700);font-size:12px}.oap-card__facts{margin:0;padding-left:16px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.5}.oap-card__actions{display:flex;flex-wrap:wrap;gap:6px}.oap-button{height:26px;padding:0 10px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.oap-button:hover{border-color:var(--nvb-color-primary-400);color:var(--nvb-color-primary-700)}.oap-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oap-button--primary{border-color:color-mix(in oklab,var(--nvb-color-primary-500) 40%,transparent);background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000));color:var(--nvb-color-primary-700)}.oap-button--quiet{border-color:transparent;background:transparent;color:var(--nvb-color-neutral-500)}.oap-combo{position:relative}.oap-combo--hidden{display:none}.oap-combo__icon{position:absolute;top:50%;left:8px;display:inline-flex;width:16px;height:16px;color:var(--nvb-color-neutral-500);transform:translateY(-50%);pointer-events:none}.oap-combo__icon nvb-icon{width:16px;height:16px}.oap-combo__input{box-sizing:border-box;width:100%;height:var(--nvb-control-height, 32px);padding:0 10px 0 30px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-control-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px}.oap-combo__input:focus{border-color:var(--nvb-color-primary-500);outline:2px solid color-mix(in oklab,var(--nvb-color-primary-500) 25%,transparent);outline-offset:0}.oap-list{position:absolute;z-index:20;top:calc(100% + 4px);right:0;left:0;max-height:320px;margin:0;padding:4px;overflow-y:auto;list-style:none;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:var(--nvb-color-neutral-000);box-shadow:var(--nvb-shadow-8, 0 8px 24px rgba(15, 23, 42, .14))}.oap-list__empty{padding:10px;color:var(--nvb-color-neutral-500);font-size:12px}.oap-option{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer}.oap-option--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000))}.oap-option__text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.oap-option__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-option__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oap-option__flag{flex:0 0 auto;color:var(--nvb-color-warning-700, #b45309);font-size:11px}\n"],dependencies:[{kind:"component",type:N,selector:"nvb-icon",inputs:["name","svg","ariaLabel"]},{kind:"component",type:S,selector:"nvb-pro-feature-gate",inputs:["feature","compact"]},{kind:"pipe",type:O,name:"nvbT"}],changeDetection:m.ChangeDetectionStrategy.OnPush})};m.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:fe,decorators:[{type:Y,args:[{selector:"nvb-openapi-picker",imports:[N,S,O],changeDetection:Q.OnPush,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\" [compact]=\"true\">\n <div class=\"oap\" data-testid=\"openapi-picker\">\n @if (!state.hasSpecs()) {\n <p class=\"oap-hint\">{{ 'openApi.picker.noSpecs' | nvbT }}</p>\n } @else {\n @if (linkedOperation(); as operation) {\n <div class=\"oap-card\" data-testid=\"openapi-picker-linked\">\n <div class=\"oap-card__line\">\n <span class=\"oap-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <code class=\"oap-card__path\">{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <p class=\"oap-card__summary\">{{ operation.summary }}</p>\n }\n @if (operation.kind === 'sse' && operation.events?.length) {\n <ul class=\"oap-card__facts\">\n <li>{{ 'openApi.picker.events' | nvbT: { events: operation.events!.join(', ') } }}</li>\n </ul>\n }\n @if (responseHint(); as hint) {\n <ul class=\"oap-card__facts\">\n @if (hint.listPath === '') {\n <li>{{ 'openApi.picker.listRoot' | nvbT }}</li>\n } @else if (hint.listPath) {\n <li>{{ 'openApi.picker.listAt' | nvbT: { path: hint.listPath } }}</li>\n }\n @if (hint.value && hint.label) {\n <li>{{ 'openApi.picker.optionFields' | nvbT: { value: hint.value, label: hint.label } }}</li>\n }\n </ul>\n }\n <div class=\"oap-card__actions\">\n @if (hasBody()) {\n <button type=\"button\" class=\"oap-button oap-button--primary\" (click)=\"openBodyBuilder()\" data-testid=\"openapi-picker-body\">\n {{ 'openApi.picker.editBody' | nvbT }}\n </button>\n }\n <button type=\"button\" class=\"oap-button\" (click)=\"change()\" data-testid=\"openapi-picker-change\">\n {{ 'openApi.picker.change' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button\" (click)=\"showInApiTab()\" data-testid=\"openapi-picker-show\">\n {{ 'openApi.picker.show' | nvbT }}\n </button>\n <button type=\"button\" class=\"oap-button oap-button--quiet\" (click)=\"unlink()\" data-testid=\"openapi-picker-unlink\">\n {{ 'openApi.picker.unlink' | nvbT }}\n </button>\n </div>\n </div>\n } @else if (link(); as staleLink) {\n <p class=\"oap-hint oap-hint--warning\" data-testid=\"openapi-picker-missing\">\n {{ 'openApi.picker.missing' | nvbT: { method: staleLink.method, path: staleLink.path } }}\n </p>\n }\n\n <div class=\"oap-combo\" [class.oap-combo--hidden]=\"linkedOperation() && !open()\">\n <span class=\"oap-combo__icon\" aria-hidden=\"true\"><nvb-icon name=\"search\" /></span>\n <input\n #searchInput\n type=\"text\"\n class=\"oap-combo__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"open() && results().length ? listId + '-' + activeIndex() : null\"\n [attr.aria-label]=\"'openApi.picker.label' | nvbT\"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onQuery($any($event.target).value)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur($event)\"\n (keydown)=\"onKeydown($event)\"\n data-testid=\"openapi-picker-search\"\n />\n @if (open()) {\n <ul class=\"oap-list\" role=\"listbox\" [id]=\"listId\" data-testid=\"openapi-picker-results\">\n @for (operation of results(); track operation.key; let index = $index) {\n <li\n role=\"option\"\n tabindex=\"-1\"\n class=\"oap-option\"\n [id]=\"listId + '-' + index\"\n [class.oap-option--active]=\"index === activeIndex()\"\n [attr.aria-selected]=\"index === activeIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mouseenter)=\"activeIndex.set(index)\"\n (click)=\"choose(operation)\"\n [attr.data-testid]=\"'openapi-picker-option-' + operation.operationId\"\n >\n <span class=\"oap-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <span class=\"oap-option__text\">\n <code class=\"oap-option__path\">{{ operation.path }}</code>\n <span class=\"oap-option__summary\">\n {{ operation.summary || operation.operationId }}\n @if (multipleSpecs()) {\n · {{ specTitle(operation.specId) }}\n }\n </span>\n </span>\n @if (operation.deprecated) {\n <span class=\"oap-option__flag\">{{ 'openApi.deprecated' | nvbT }}</span>\n }\n </li>\n } @empty {\n <li class=\"oap-list__empty\" role=\"presentation\">\n @if (loading()) {\n {{ 'openApi.loading' | nvbT }}\n } @else {\n {{ 'openApi.picker.noMatch' | nvbT }}\n }\n </li>\n }\n </ul>\n }\n </div>\n\n @for (failure of loadErrors(); track failure.title) {\n <p class=\"oap-hint oap-hint--warning\">{{ 'openApi.loadFailed' | nvbT: { title: failure.title, message: failure.message } }}</p>\n }\n }\n </div>\n</nvb-pro-feature-gate>\n",styles:[":host{display:block;min-width:0}.oap{display:flex;flex-direction:column;gap:6px;min-width:0}.oap-hint{margin:0;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.4}.oap-hint--warning{color:var(--nvb-color-warning-700, #b45309)}.oap-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oap-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oap-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oap-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oap-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oap-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oap-method[data-method=live]{color:var(--nvb-color-info-700, #1d4ed8);background:color-mix(in oklab,currentColor 14%,transparent)}.oap-card{display:flex;flex-direction:column;gap:6px;padding:8px 10px;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:color-mix(in oklab,var(--nvb-color-neutral-050) 70%,var(--nvb-color-surface-mix-target, white))}.oap-card__line{display:flex;align-items:center;gap:8px;min-width:0}.oap-card__path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-card__summary{margin:0;color:var(--nvb-color-neutral-700);font-size:12px}.oap-card__facts{margin:0;padding-left:16px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.5}.oap-card__actions{display:flex;flex-wrap:wrap;gap:6px}.oap-button{height:26px;padding:0 10px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.oap-button:hover{border-color:var(--nvb-color-primary-400);color:var(--nvb-color-primary-700)}.oap-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oap-button--primary{border-color:color-mix(in oklab,var(--nvb-color-primary-500) 40%,transparent);background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000));color:var(--nvb-color-primary-700)}.oap-button--quiet{border-color:transparent;background:transparent;color:var(--nvb-color-neutral-500)}.oap-combo{position:relative}.oap-combo--hidden{display:none}.oap-combo__icon{position:absolute;top:50%;left:8px;display:inline-flex;width:16px;height:16px;color:var(--nvb-color-neutral-500);transform:translateY(-50%);pointer-events:none}.oap-combo__icon nvb-icon{width:16px;height:16px}.oap-combo__input{box-sizing:border-box;width:100%;height:var(--nvb-control-height, 32px);padding:0 10px 0 30px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-control-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px}.oap-combo__input:focus{border-color:var(--nvb-color-primary-500);outline:2px solid color-mix(in oklab,var(--nvb-color-primary-500) 25%,transparent);outline-offset:0}.oap-list{position:absolute;z-index:20;top:calc(100% + 4px);right:0;left:0;max-height:320px;margin:0;padding:4px;overflow-y:auto;list-style:none;border:1px solid var(--nvb-color-neutral-200);border-radius:var(--nvb-radius-md, 4px);background:var(--nvb-color-neutral-000);box-shadow:var(--nvb-shadow-8, 0 8px 24px rgba(15, 23, 42, .14))}.oap-list__empty{padding:10px;color:var(--nvb-color-neutral-500);font-size:12px}.oap-option{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer}.oap-option--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 10%,var(--nvb-color-neutral-000))}.oap-option__text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.oap-option__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oap-option__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oap-option__flag{flex:0 0 auto;color:var(--nvb-color-warning-700, #b45309);font-size:11px}\n"]}]}],ctorParameters:()=>[],propDecorators:{source:[{type:m.Input,args:[{isSignal:true,alias:"source",required:false}]}],context:[{type:m.Input,args:[{isSignal:true,alias:"context",required:false}]}],searchInput:[{type:m.ViewChild,args:["searchInput",{isSignal:true}]}]}});const et=["GET","POST","PUT","PATCH","DELETE","SSE","WS"];const ge=2e5;var ye=class H{state=g(T);api=g(E);http=g(oe,{optional:true});i18n=g(J);methods=h(()=>{const e=new Set(this.state.operations().map(n=>P(n).text));return et.filter(n=>e.has(n))},...ngDevMode?[{debugName:"methods"}]:[]);icons=be;search=f("",...ngDevMode?[{debugName:"search"}]:[]);hiddenMethods=f(new Set,...ngDevMode?[{debugName:"hiddenMethods"}]:[]);specFilter=f("",...ngDevMode?[{debugName:"specFilter"}]:[]);responseView=f("example",...ngDevMode?[{debugName:"responseView"}]:[]);tryValues=f({},...ngDevMode?[{debugName:"tryValues"}]:[]);tryResult=f(null,...ngDevMode?[{debugName:"tryResult"}]:[]);trying=f(false,...ngDevMode?[{debugName:"trying"}]:[]);lastAdded=f(null,...ngDevMode?[{debugName:"lastAdded"}]:[]);visibleOperations=h(()=>{const e=this.search().toLowerCase().split(/\s+/).filter(Boolean);const n=this.hiddenMethods();const o=this.specFilter();return this.state.operations().filter(r=>!o||r.specId===o).filter(r=>!n.has(P(r).text)).filter(r=>M(r,e))},...ngDevMode?[{debugName:"visibleOperations"}]:[]);groups=h(()=>{const e=new Map;const n=new Map;this.state.catalogs().forEach(a=>a.tags.forEach(i=>n.set(i.name,i.description)));const o=Array.from(n.keys());this.visibleOperations().forEach(a=>{const i=a.tags[0]??this.i18n.t("openApi.untagged");if(!e.has(i))e.set(i,{tag:i,description:n.get(i)??"",operations:[]});e.get(i).operations.push(a)});const r=a=>{const i=o.indexOf(a);return i<0?o.length:i};return Array.from(e.values()).sort((a,i)=>r(a.tag)-r(i.tag))},...ngDevMode?[{debugName:"groups"}]:[]);selected=h(()=>{const e=this.state.selectedKey();return this.state.operations().find(n=>n.key===e)??null},...ngDevMode?[{debugName:"selected"}]:[]);selectedCatalog=h(()=>{const e=this.selected();return e?this.state.catalogOf(e.specId):null},...ngDevMode?[{debugName:"selectedCatalog"}]:[]);exampleResponse=h(()=>{const e=this.selected()?.response?.schema;return e?JSON.stringify(w(e),null,2):""},...ngDevMode?[{debugName:"exampleResponse"}]:[]);exampleBody=h(()=>{const e=this.selected()?.requestBody?.schema;return e?JSON.stringify(w(e),null,2):""},...ngDevMode?[{debugName:"exampleBody"}]:[]);linkedSources=h(()=>{const e=this.selected();this.lastAdded();if(!e)return[];return(this.api.getStructure()?.dataSources??[]).filter(n=>{const o=n?.params?.["openApi"];return o?.specId===e.specId&&o?.operationId===e.operationId}).map(n=>n.name)},...ngDevMode?[{debugName:"linkedSources"}]:[]);tryParameters=h(()=>(this.selected()?.parameters??[]).filter(e=>e.in==="path"||e.in==="query"),...ngDevMode?[{debugName:"tryParameters"}]:[]);canTry=h(()=>{const e=this.selected();if(!e||e.kind!=="rest"||e.method!=="GET"||!this.http)return false;const n=this.tryValues();return this.tryParameters().filter(o=>o.required).every(o=>String(n[o.name]??"").trim())},...ngDevMode?[{debugName:"canTry"}]:[]);specCount=h(()=>this.state.specs().length,...ngDevMode?[{debugName:"specCount"}]:[]);constructor(){this.state.ensureLoaded();ee(()=>{const e=this.state.operations();if(!D(()=>this.selected())&&e.length)D(()=>this.state.selectedKey.set(e[0].key))});ee(()=>{this.selected();D(()=>{this.tryValues.set({});this.tryResult.set(null)})})}select(e){this.state.selectedKey.set(e.key)}toggleMethod(e){this.hiddenMethods.update(n=>{const o=new Set(n);if(o.has(e))o.delete(e);else o.add(e);return o})}methodTone(e){return e==="SSE"||e==="WS"?"live":e.toLowerCase()}badge(e){return P(e)}canUse=h(()=>{const e=this.selected();if(!e||e.kind==="rest")return true;const n=this.api.getDataSourceTypeSettings();return e.kind==="sse"?!!n.enableEventStream:!!n.enableWebsocket},...ngDevMode?[{debugName:"canUse"}]:[]);schemaType(e){if(!e)return"";if(e.type==="array")return`${this.schemaType(e.items)||"any"}[]`;if(e.variants?.length)return e.variants.map(o=>this.schemaType(o)).join(" | ");const n=e.refName??e.type??"any";return e.format&&!e.refName?`${n} (${e.format})`:n}childrenOf(e){const n=e?.type==="array"?e.items:e;if(!n?.properties||n.circular)return[];const o=new Set(n.required??[]);return Object.entries(n.properties).map(([r,a])=>({name:r,schema:a,required:o.has(r)}))}enumText(e){return(e.enum??[]).map(n=>JSON.stringify(n)).join(", ")}setTryValue(e,n){this.tryValues.update(o=>({...o,[e]:n}))}async tryIt(){const e=this.selected();const n=this.selectedCatalog();if(!e||!n||!this.http||!this.canTry())return;const o=this.tryValues();let r=e.path;const a=new URLSearchParams;this.tryParameters().forEach(p=>{const c=String(o[p.name]??"").trim();if(p.in==="path")r=r.replace(`{${p.name}}`,encodeURIComponent(c));else if(c)a.set(p.name,c)});const i=`${n.baseUrl}${r}${a.size?`?${a}`:""}`;this.trying.set(true);const s=performance.now();try{const p=await ae(this.http.get(i,{observe:"response",responseType:"text"}));this.tryResult.set(this.toResult(p,s,true))}catch(p){const c=p;this.tryResult.set({status:c.status??0,statusText:c.status?c.statusText:this.i18n.t("openApi.try.unreachable"),ms:Math.round(performance.now()-s),body:this.pretty(typeof c.error==="string"?c.error:JSON.stringify(c.error??"")),ok:false})}finally{this.trying.set(false)}}useAsDataSource(){const e=this.selected();const n=this.selectedCatalog();if(!e||!n||!this.canUse())return;let o="";this.api.updateStructure(r=>{o=Ke(r,e,n)});if(o)this.lastAdded.set(o)}reload(){this.state.reload()}toResult(e,n,o){return{status:e.status,statusText:e.statusText,ms:Math.round(performance.now()-n),body:this.pretty(e.body??""),ok:o}}pretty(e){let n=e;try{n=JSON.stringify(JSON.parse(e),null,2)}catch{}return n.length>ge?`${n.slice(0,ge)}
|
|
2
|
+
… ${this.i18n.t("openApi.try.truncated")}`:n}static ɵfac=m.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:H,deps:[],target:m.ɵɵFactoryTarget.Component});static ɵcmp=m.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.2.1",type:H,isStandalone:true,selector:"nvb-openapi-tab",ngImport:m,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\">\n <div class=\"oat\" data-testid=\"openapi-tab\">\n <aside class=\"oat-side\" [attr.aria-label]=\"'openApi.endpoints' | nvbT\">\n <header class=\"oat-side__header\">\n <h2 class=\"oat-side__title\">{{ 'openApi.title' | nvbT }}</h2>\n <p class=\"oat-side__meta\" data-testid=\"openapi-summary\">\n {{ 'openApi.meta' | nvbT: { count: state.operations().length, specs: state.catalogs().length } }}\n </p>\n </header>\n\n <div class=\"oat-side__search\">\n <builder-search-input\n [value]=\"search()\"\n [placeholder]=\"'openApi.search' | nvbT\"\n [ariaLabel]=\"'openApi.search' | nvbT\"\n [clearAriaLabel]=\"'openApi.clearSearch' | nvbT\"\n testId=\"openapi-search\"\n (valueChanged)=\"search.set($event)\"\n (cleared)=\"search.set('')\"\n />\n </div>\n\n <div class=\"oat-filters\" role=\"group\" [attr.aria-label]=\"'openApi.methods' | nvbT\">\n @for (method of methods(); track method) {\n <button\n type=\"button\"\n class=\"oat-filter oat-method\"\n [attr.data-method]=\"methodTone(method)\"\n [class.oat-filter--off]=\"hiddenMethods().has(method)\"\n [attr.aria-pressed]=\"!hiddenMethods().has(method)\"\n (click)=\"toggleMethod(method)\"\n [attr.data-testid]=\"'openapi-filter-' + methodTone(method)\"\n >\n {{ method }}\n </button>\n }\n </div>\n\n @if (specCount() > 1) {\n <label class=\"oat-spec\">\n <span>{{ 'openApi.spec' | nvbT }}</span>\n <select [value]=\"specFilter()\" (change)=\"specFilter.set($any($event.target).value)\" data-testid=\"openapi-spec-filter\">\n <option value=\"\">{{ 'openApi.spec.all' | nvbT }}</option>\n @for (catalog of state.catalogs(); track catalog.specId) {\n <option [value]=\"catalog.specId\">{{ catalog.title }}</option>\n }\n </select>\n </label>\n }\n\n <nav class=\"oat-side__scroll\">\n @for (entry of state.specs(); track entry.spec.id) {\n @if (entry.status.state === 'error') {\n <p class=\"oat-notice oat-notice--error\" data-testid=\"openapi-load-error\">\n {{ 'openApi.loadFailed' | nvbT: { title: entry.spec.title || entry.spec.id, message: entry.status.message } }}\n <button type=\"button\" class=\"oat-link\" (click)=\"reload()\">{{ 'openApi.retry' | nvbT }}</button>\n </p>\n } @else if (entry.status.state === 'loading') {\n <p class=\"oat-notice\">{{ 'openApi.loading' | nvbT }}</p>\n }\n }\n @if (!state.hasSpecs()) {\n <p class=\"oat-notice\" data-testid=\"openapi-no-specs\">{{ 'openApi.noSpecs' | nvbT }}</p>\n }\n\n @for (group of groups(); track group.tag) {\n <section class=\"oat-group\">\n <h3 class=\"oat-group__title\" [title]=\"group.description\">{{ group.tag }}</h3>\n <ul class=\"oat-group__list\" role=\"list\">\n @for (operation of group.operations; track operation.key) {\n <li>\n <button\n type=\"button\"\n class=\"oat-item\"\n [class.oat-item--active]=\"selected()?.key === operation.key\"\n [class.oat-item--deprecated]=\"operation.deprecated\"\n [attr.aria-current]=\"selected()?.key === operation.key ? 'true' : null\"\n (click)=\"select(operation)\"\n [attr.data-testid]=\"'openapi-item-' + operation.operationId\"\n >\n <span class=\"oat-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <span class=\"oat-item__text\">\n <span class=\"oat-item__path\">{{ operation.path }}</span>\n @if (operation.summary) {\n <span class=\"oat-item__summary\">{{ operation.summary }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </section>\n } @empty {\n @if (state.operations().length) {\n <p class=\"oat-notice\">{{ 'openApi.noMatch' | nvbT }}</p>\n }\n }\n </nav>\n </aside>\n\n <main class=\"oat-main\">\n @if (selected(); as operation) {\n <article class=\"oat-detail\" data-testid=\"openapi-detail\">\n <header class=\"oat-detail__header\">\n <div class=\"oat-detail__line\">\n <span class=\"oat-method oat-method--large\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <code class=\"oat-detail__path\" data-testid=\"openapi-detail-path\">{{ selectedCatalog()?.baseUrl }}{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <h2 class=\"oat-detail__summary\">{{ operation.summary }}</h2>\n }\n @if (operation.deprecated) {\n <p class=\"oat-notice oat-notice--warning\">{{ 'openApi.deprecatedText' | nvbT }}</p>\n }\n @if (operation.description) {\n <p class=\"oat-detail__description\">{{ operation.description }}</p>\n }\n <div class=\"oat-detail__actions\">\n <button type=\"button\" class=\"oat-button oat-button--primary\" [disabled]=\"!canUse()\" (click)=\"useAsDataSource()\" data-testid=\"openapi-use\">\n {{ 'openApi.use' | nvbT }}\n </button>\n <span class=\"oat-detail__id\">operationId: <code>{{ operation.operationId }}</code></span>\n </div>\n @if (!canUse()) {\n <p class=\"oat-notice oat-notice--warning\" data-testid=\"openapi-live-disabled\">\n {{ (operation.kind === 'sse' ? 'openApi.liveDisabled.sse' : 'openApi.liveDisabled.websocket') | nvbT }}\n </p>\n }\n @if (operation.kind === 'sse' && operation.events?.length) {\n <p class=\"oat-notice\">{{ 'openApi.picker.events' | nvbT: { events: operation.events!.join(', ') } }}</p>\n }\n @if (lastAdded(); as added) {\n <p class=\"oat-notice oat-notice--success\" role=\"status\" data-testid=\"openapi-added\">\n {{ 'openApi.added' | nvbT: { name: added } }}\n </p>\n } @else if (linkedSources().length) {\n <p class=\"oat-notice\" data-testid=\"openapi-linked\">\n {{ 'openApi.usedBy' | nvbT: { names: linkedSources().join(', ') } }}\n </p>\n }\n </header>\n\n @if (operation.parameters.length) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">{{ 'openApi.parameters' | nvbT }}</h3>\n <table class=\"oat-table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'openApi.col.name' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.in' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.type' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.description' | nvbT }}</th>\n </tr>\n </thead>\n <tbody>\n @for (parameter of operation.parameters; track parameter.in + parameter.name) {\n <tr>\n <td>\n <code>{{ parameter.name }}</code>\n @if (parameter.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n </td>\n <td>{{ parameter.in }}</td>\n <td class=\"oat-type\">{{ schemaType(parameter.schema) }}</td>\n <td>{{ parameter.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </section>\n }\n\n @if (operation.requestBody; as body) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ (operation.kind === 'websocket' ? 'openApi.connectMessage' : 'openApi.requestBody') | nvbT }}\n <span class=\"oat-section__meta\">{{ body.contentType }}{{ body.required ? ' · ' + ('openApi.required' | nvbT) : '' }}</span>\n </h3>\n @if (body.schema) {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: body.schema, depth: 0 }\" />\n </div>\n }\n </section>\n }\n\n @if (operation.response; as response) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ (operation.kind === 'rest' ? 'openApi.response' : 'openApi.message') | nvbT }}\n <span class=\"oat-section__meta\">{{ response.status }}{{ response.description ? ' · ' + response.description : '' }}</span>\n </h3>\n @if (response.schema) {\n <div class=\"oat-segmented\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'example'\"\n [class.oat-segmented__on]=\"responseView() === 'example'\"\n (click)=\"responseView.set('example')\"\n >\n {{ 'openApi.example' | nvbT }}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'schema'\"\n [class.oat-segmented__on]=\"responseView() === 'schema'\"\n (click)=\"responseView.set('schema')\"\n >\n {{ 'openApi.schema' | nvbT }}\n </button>\n </div>\n @if (responseView() === 'example') {\n <pre class=\"oat-code\" data-testid=\"openapi-example\">{{ exampleResponse() }}</pre>\n } @else {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: response.schema, depth: 0 }\" />\n </div>\n }\n }\n </section>\n }\n\n <section class=\"oat-section\" data-testid=\"openapi-try\">\n <h3 class=\"oat-section__title\">{{ 'openApi.try' | nvbT }}</h3>\n @if (operation.kind !== 'rest') {\n <p class=\"oat-notice\">{{ 'openApi.try.live' | nvbT }}</p>\n } @else if (operation.method !== 'GET') {\n <p class=\"oat-notice\">{{ 'openApi.try.getOnly' | nvbT }}</p>\n } @else {\n @if (tryParameters().length) {\n <div class=\"oat-try__fields\">\n @for (parameter of tryParameters(); track parameter.name) {\n <label class=\"oat-try__field\">\n <span>\n {{ parameter.name }}\n @if (parameter.required) {\n <span class=\"oat-required\">*</span>\n }\n </span>\n <input\n type=\"text\"\n [value]=\"tryValues()[parameter.name] ?? ''\"\n (input)=\"setTryValue(parameter.name, $any($event.target).value)\"\n (keydown.enter)=\"tryIt()\"\n [attr.data-testid]=\"'openapi-try-' + parameter.name\"\n />\n </label>\n }\n </div>\n }\n <button\n type=\"button\"\n class=\"oat-button\"\n [disabled]=\"!canTry() || trying()\"\n (click)=\"tryIt()\"\n data-testid=\"openapi-try-send\"\n >\n <nvb-icon [svg]=\"icons.play_arrow\" />\n {{ (trying() ? 'openApi.try.sending' : 'openApi.try.send') | nvbT }}\n </button>\n @if (tryResult(); as result) {\n <p class=\"oat-try__status\" [class.oat-try__status--error]=\"!result.ok\" data-testid=\"openapi-try-status\">\n {{ result.status || '' }} {{ result.statusText }} · {{ result.ms }} ms\n </p>\n <pre class=\"oat-code\" data-testid=\"openapi-try-body\">{{ result.body }}</pre>\n }\n }\n </section>\n </article>\n } @else if (state.operations().length === 0 && state.hasSpecs()) {\n <p class=\"oat-empty\">{{ 'openApi.loading' | nvbT }}</p>\n }\n </main>\n </div>\n</nvb-pro-feature-gate>\n\n<ng-template #schemaRows let-schema let-depth=\"depth\">\n <ul class=\"oat-tree\" role=\"list\">\n @for (field of childrenOf(schema); track field.name) {\n <li class=\"oat-tree__row\">\n <div class=\"oat-tree__line\">\n <code class=\"oat-tree__name\">{{ field.name }}</code>\n @if (field.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n <span class=\"oat-type\">{{ schemaType(field.schema) }}</span>\n @if (field.schema.nullable) {\n <span class=\"oat-flag\">null</span>\n }\n @if (field.schema.readOnly) {\n <span class=\"oat-flag\">{{ 'openApi.readOnly' | nvbT }}</span>\n }\n </div>\n @if (field.schema.description) {\n <p class=\"oat-tree__description\">{{ field.schema.description }}</p>\n }\n @if (field.schema.enum?.length) {\n <p class=\"oat-tree__description\">{{ 'openApi.oneOf' | nvbT }} {{ enumText(field.schema) }}</p>\n }\n @if (depth < 5 && childrenOf(field.schema).length) {\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: field.schema, depth: depth + 1 }\" />\n }\n </li>\n } @empty {\n <li class=\"oat-tree__row oat-tree__line\">\n <span class=\"oat-type\">{{ schemaType(schema) }}</span>\n </li>\n }\n </ul>\n</ng-template>\n",styles:[":host{--nvb-input-height: 32px;display:block;height:100%;min-height:0;background:color-mix(in oklab,var(--nvb-color-neutral-050) 84%,var(--nvb-color-surface-mix-target, white))}.oat{display:grid;grid-template-columns:280px minmax(0,1fr);gap:10px;height:100%;min-height:0;padding:10px;box-sizing:border-box}.oat-side,.oat-main{min-height:0;background:var(--nvb-card-bg, var(--nvb-color-neutral-000));border:1px solid var(--nvb-card-border-color, var(--nvb-color-neutral-200));border-radius:var(--nvb-card-radius, 8px);box-shadow:var(--nvb-card-shadow, none)}.oat-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oat-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oat-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oat-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oat-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oat-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oat-method[data-method=live]{color:var(--nvb-color-info-700, #1d4ed8);background:color-mix(in oklab,currentColor 14%,transparent)}.oat-method--large{min-width:64px;padding:4px 8px;font-size:12px}.oat-side{display:flex;flex-direction:column;overflow:hidden}.oat-side__header{padding:12px 12px 8px}.oat-side__title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--nvb-color-neutral-900)}.oat-side__meta{margin:2px 0 0;font-size:11px;line-height:16px;color:var(--nvb-color-neutral-500);font-variant-numeric:tabular-nums}.oat-side__search{padding:0 12px 10px}.oat-filters{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 10px;border-bottom:1px solid var(--nvb-color-neutral-200)}.oat-filter{border:1px solid color-mix(in oklab,currentColor 30%,transparent);cursor:pointer}.oat-filter--off{opacity:.4;text-decoration:line-through}.oat-spec{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-bottom:1px solid var(--nvb-color-neutral-200);color:var(--nvb-color-neutral-600);font-size:12px}.oat-spec select{height:var(--nvb-input-height);border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-side__scroll{flex:1 1 auto;min-height:0;padding:0 8px 16px;overflow-y:auto}.oat-group__title{position:sticky;top:0;z-index:1;margin:8px 0 2px;padding:6px 8px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-600);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.oat-group__list{margin:0;padding:0;list-style:none}.oat-item{display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.oat-item:hover{background:color-mix(in oklab,var(--nvb-color-neutral-100) 70%,transparent)}.oat-item:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:-2px}.oat-item--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))}.oat-item--active:hover{background:color-mix(in oklab,var(--nvb-color-primary-500) 16%,var(--nvb-color-neutral-000))}.oat-item--deprecated .oat-item__path{text-decoration:line-through}.oat-item__text{display:flex;flex-direction:column;min-width:0}.oat-item__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oat-item__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oat-main{overflow-y:auto}.oat-detail{max-width:960px;padding:20px 28px 40px}.oat-detail__line{display:flex;align-items:center;gap:10px;min-width:0}.oat-detail__path{min-width:0;overflow-wrap:anywhere;font-family:Consolas,Courier New,monospace;font-size:14px;color:var(--nvb-color-neutral-900)}.oat-detail__summary{margin:10px 0 0;font-size:18px;font-weight:600;color:var(--nvb-color-neutral-900)}.oat-detail__description{margin:6px 0 0;color:var(--nvb-color-neutral-700);font-size:13px;line-height:1.5;white-space:pre-line}.oat-detail__actions{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:14px}.oat-detail__id{color:var(--nvb-color-neutral-500);font-size:12px}.oat-button{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 14px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.oat-button nvb-icon{width:18px;height:18px}.oat-button:hover:not(:disabled){border-color:var(--nvb-color-primary-400)}.oat-button:disabled{opacity:.5;cursor:default}.oat-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oat-button--primary{border-color:var(--nvb-color-primary-600);background:var(--nvb-color-primary-600);color:var(--nvb-color-neutral-000, #fff)}.oat-button--primary:hover:not(:disabled){border-color:var(--nvb-color-primary-700);background:var(--nvb-color-primary-700)}.oat-link{margin-left:6px;padding:0;border:0;background:none;color:var(--nvb-color-primary-700);font:inherit;text-decoration:underline;cursor:pointer}.oat-notice{margin:8px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.45}.oat-detail .oat-notice{margin:10px 0 0}.oat-notice--error{color:var(--nvb-color-risk-700, #b91c1c)}.oat-notice--warning{color:var(--nvb-color-warning-700, #b45309)}.oat-notice--success{color:var(--nvb-color-success-700, #15803d)}.oat-empty{padding:40px;color:var(--nvb-color-neutral-500)}.oat-section{margin-top:26px}.oat-section__title{display:flex;align-items:baseline;gap:10px;margin:0 0 8px;font-size:13px;font-weight:700;color:var(--nvb-color-neutral-800)}.oat-section__meta{color:var(--nvb-color-neutral-500);font-size:12px;font-weight:400}.oat-table{width:100%;border-collapse:collapse;font-size:12px}.oat-table th,.oat-table td{padding:6px 8px;border-bottom:1px solid var(--nvb-color-neutral-200);text-align:left;vertical-align:top}.oat-table th{color:var(--nvb-color-neutral-600);font-weight:600}.oat-table code{font-family:Consolas,Courier New,monospace}.oat-required{margin-left:2px;color:var(--nvb-color-risk-600, #dc2626);font-weight:700}.oat-type{color:var(--nvb-color-info-700, #1d4ed8);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-flag{padding:0 5px;border-radius:3px;background:var(--nvb-color-neutral-100);color:var(--nvb-color-neutral-600);font-size:11px}.oat-schema{padding:8px 12px;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000)}.oat-tree{margin:0;padding:0;list-style:none}.oat-tree .oat-tree{margin:2px 0 4px 6px;padding-left:12px;border-left:1px solid var(--nvb-color-neutral-200)}.oat-tree__row{padding:3px 0}.oat-tree__line{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.oat-tree__name{font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-900)}.oat-tree__description{margin:1px 0 0;color:var(--nvb-color-neutral-600);font-size:12px}.oat-segmented{display:inline-flex;margin-bottom:8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;overflow:hidden}.oat-segmented button{padding:4px 12px;border:0;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;cursor:pointer}.oat-segmented button+button{border-left:1px solid var(--nvb-color-neutral-300)}.oat-segmented__on{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))!important;color:var(--nvb-color-primary-700)!important;font-weight:600}.oat-code{max-height:420px;margin:0;padding:10px 12px;overflow:auto;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font-family:Consolas,Courier New,monospace;font-size:12px;line-height:1.45}.oat-try__fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-bottom:10px}.oat-try__field{display:flex;flex-direction:column;gap:4px;color:var(--nvb-color-neutral-700);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-try__field input{height:var(--nvb-input-height);padding:0 8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-try__status{margin:10px 0 6px;color:var(--nvb-color-success-700, #15803d);font-size:12px;font-weight:600}.oat-try__status--error{color:var(--nvb-color-risk-700, #b91c1c)}@media(max-width:760px){.oat{grid-template-columns:1fr;grid-template-rows:minmax(200px,40%) 1fr}.oat-detail{padding:16px}}\n"],dependencies:[{kind:"directive",type:re,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:N,selector:"nvb-icon",inputs:["name","svg","ariaLabel"]},{kind:"component",type:S,selector:"nvb-pro-feature-gate",inputs:["feature","compact"]},{kind:"component",type:ne,selector:"builder-search-input",inputs:["value","placeholder","ariaLabel","testId","clearAriaLabel","showSearchIcon"],outputs:["valueChanged","cleared"]},{kind:"pipe",type:O,name:"nvbT"}],changeDetection:m.ChangeDetectionStrategy.OnPush})};m.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.2.1",ngImport:m,type:ye,decorators:[{type:Y,args:[{selector:"nvb-openapi-tab",imports:[re,N,S,O,ne],changeDetection:Q.OnPush,template:"<nvb-pro-feature-gate [feature]=\"'openApi.title' | nvbT\">\n <div class=\"oat\" data-testid=\"openapi-tab\">\n <aside class=\"oat-side\" [attr.aria-label]=\"'openApi.endpoints' | nvbT\">\n <header class=\"oat-side__header\">\n <h2 class=\"oat-side__title\">{{ 'openApi.title' | nvbT }}</h2>\n <p class=\"oat-side__meta\" data-testid=\"openapi-summary\">\n {{ 'openApi.meta' | nvbT: { count: state.operations().length, specs: state.catalogs().length } }}\n </p>\n </header>\n\n <div class=\"oat-side__search\">\n <builder-search-input\n [value]=\"search()\"\n [placeholder]=\"'openApi.search' | nvbT\"\n [ariaLabel]=\"'openApi.search' | nvbT\"\n [clearAriaLabel]=\"'openApi.clearSearch' | nvbT\"\n testId=\"openapi-search\"\n (valueChanged)=\"search.set($event)\"\n (cleared)=\"search.set('')\"\n />\n </div>\n\n <div class=\"oat-filters\" role=\"group\" [attr.aria-label]=\"'openApi.methods' | nvbT\">\n @for (method of methods(); track method) {\n <button\n type=\"button\"\n class=\"oat-filter oat-method\"\n [attr.data-method]=\"methodTone(method)\"\n [class.oat-filter--off]=\"hiddenMethods().has(method)\"\n [attr.aria-pressed]=\"!hiddenMethods().has(method)\"\n (click)=\"toggleMethod(method)\"\n [attr.data-testid]=\"'openapi-filter-' + methodTone(method)\"\n >\n {{ method }}\n </button>\n }\n </div>\n\n @if (specCount() > 1) {\n <label class=\"oat-spec\">\n <span>{{ 'openApi.spec' | nvbT }}</span>\n <select [value]=\"specFilter()\" (change)=\"specFilter.set($any($event.target).value)\" data-testid=\"openapi-spec-filter\">\n <option value=\"\">{{ 'openApi.spec.all' | nvbT }}</option>\n @for (catalog of state.catalogs(); track catalog.specId) {\n <option [value]=\"catalog.specId\">{{ catalog.title }}</option>\n }\n </select>\n </label>\n }\n\n <nav class=\"oat-side__scroll\">\n @for (entry of state.specs(); track entry.spec.id) {\n @if (entry.status.state === 'error') {\n <p class=\"oat-notice oat-notice--error\" data-testid=\"openapi-load-error\">\n {{ 'openApi.loadFailed' | nvbT: { title: entry.spec.title || entry.spec.id, message: entry.status.message } }}\n <button type=\"button\" class=\"oat-link\" (click)=\"reload()\">{{ 'openApi.retry' | nvbT }}</button>\n </p>\n } @else if (entry.status.state === 'loading') {\n <p class=\"oat-notice\">{{ 'openApi.loading' | nvbT }}</p>\n }\n }\n @if (!state.hasSpecs()) {\n <p class=\"oat-notice\" data-testid=\"openapi-no-specs\">{{ 'openApi.noSpecs' | nvbT }}</p>\n }\n\n @for (group of groups(); track group.tag) {\n <section class=\"oat-group\">\n <h3 class=\"oat-group__title\" [title]=\"group.description\">{{ group.tag }}</h3>\n <ul class=\"oat-group__list\" role=\"list\">\n @for (operation of group.operations; track operation.key) {\n <li>\n <button\n type=\"button\"\n class=\"oat-item\"\n [class.oat-item--active]=\"selected()?.key === operation.key\"\n [class.oat-item--deprecated]=\"operation.deprecated\"\n [attr.aria-current]=\"selected()?.key === operation.key ? 'true' : null\"\n (click)=\"select(operation)\"\n [attr.data-testid]=\"'openapi-item-' + operation.operationId\"\n >\n <span class=\"oat-method\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <span class=\"oat-item__text\">\n <span class=\"oat-item__path\">{{ operation.path }}</span>\n @if (operation.summary) {\n <span class=\"oat-item__summary\">{{ operation.summary }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </section>\n } @empty {\n @if (state.operations().length) {\n <p class=\"oat-notice\">{{ 'openApi.noMatch' | nvbT }}</p>\n }\n }\n </nav>\n </aside>\n\n <main class=\"oat-main\">\n @if (selected(); as operation) {\n <article class=\"oat-detail\" data-testid=\"openapi-detail\">\n <header class=\"oat-detail__header\">\n <div class=\"oat-detail__line\">\n <span class=\"oat-method oat-method--large\" [attr.data-method]=\"badge(operation).tone\">{{ badge(operation).text }}</span>\n <code class=\"oat-detail__path\" data-testid=\"openapi-detail-path\">{{ selectedCatalog()?.baseUrl }}{{ operation.path }}</code>\n </div>\n @if (operation.summary) {\n <h2 class=\"oat-detail__summary\">{{ operation.summary }}</h2>\n }\n @if (operation.deprecated) {\n <p class=\"oat-notice oat-notice--warning\">{{ 'openApi.deprecatedText' | nvbT }}</p>\n }\n @if (operation.description) {\n <p class=\"oat-detail__description\">{{ operation.description }}</p>\n }\n <div class=\"oat-detail__actions\">\n <button type=\"button\" class=\"oat-button oat-button--primary\" [disabled]=\"!canUse()\" (click)=\"useAsDataSource()\" data-testid=\"openapi-use\">\n {{ 'openApi.use' | nvbT }}\n </button>\n <span class=\"oat-detail__id\">operationId: <code>{{ operation.operationId }}</code></span>\n </div>\n @if (!canUse()) {\n <p class=\"oat-notice oat-notice--warning\" data-testid=\"openapi-live-disabled\">\n {{ (operation.kind === 'sse' ? 'openApi.liveDisabled.sse' : 'openApi.liveDisabled.websocket') | nvbT }}\n </p>\n }\n @if (operation.kind === 'sse' && operation.events?.length) {\n <p class=\"oat-notice\">{{ 'openApi.picker.events' | nvbT: { events: operation.events!.join(', ') } }}</p>\n }\n @if (lastAdded(); as added) {\n <p class=\"oat-notice oat-notice--success\" role=\"status\" data-testid=\"openapi-added\">\n {{ 'openApi.added' | nvbT: { name: added } }}\n </p>\n } @else if (linkedSources().length) {\n <p class=\"oat-notice\" data-testid=\"openapi-linked\">\n {{ 'openApi.usedBy' | nvbT: { names: linkedSources().join(', ') } }}\n </p>\n }\n </header>\n\n @if (operation.parameters.length) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">{{ 'openApi.parameters' | nvbT }}</h3>\n <table class=\"oat-table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'openApi.col.name' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.in' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.type' | nvbT }}</th>\n <th scope=\"col\">{{ 'openApi.col.description' | nvbT }}</th>\n </tr>\n </thead>\n <tbody>\n @for (parameter of operation.parameters; track parameter.in + parameter.name) {\n <tr>\n <td>\n <code>{{ parameter.name }}</code>\n @if (parameter.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n </td>\n <td>{{ parameter.in }}</td>\n <td class=\"oat-type\">{{ schemaType(parameter.schema) }}</td>\n <td>{{ parameter.description }}</td>\n </tr>\n }\n </tbody>\n </table>\n </section>\n }\n\n @if (operation.requestBody; as body) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ (operation.kind === 'websocket' ? 'openApi.connectMessage' : 'openApi.requestBody') | nvbT }}\n <span class=\"oat-section__meta\">{{ body.contentType }}{{ body.required ? ' · ' + ('openApi.required' | nvbT) : '' }}</span>\n </h3>\n @if (body.schema) {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: body.schema, depth: 0 }\" />\n </div>\n }\n </section>\n }\n\n @if (operation.response; as response) {\n <section class=\"oat-section\">\n <h3 class=\"oat-section__title\">\n {{ (operation.kind === 'rest' ? 'openApi.response' : 'openApi.message') | nvbT }}\n <span class=\"oat-section__meta\">{{ response.status }}{{ response.description ? ' · ' + response.description : '' }}</span>\n </h3>\n @if (response.schema) {\n <div class=\"oat-segmented\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'example'\"\n [class.oat-segmented__on]=\"responseView() === 'example'\"\n (click)=\"responseView.set('example')\"\n >\n {{ 'openApi.example' | nvbT }}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"responseView() === 'schema'\"\n [class.oat-segmented__on]=\"responseView() === 'schema'\"\n (click)=\"responseView.set('schema')\"\n >\n {{ 'openApi.schema' | nvbT }}\n </button>\n </div>\n @if (responseView() === 'example') {\n <pre class=\"oat-code\" data-testid=\"openapi-example\">{{ exampleResponse() }}</pre>\n } @else {\n <div class=\"oat-schema\">\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: response.schema, depth: 0 }\" />\n </div>\n }\n }\n </section>\n }\n\n <section class=\"oat-section\" data-testid=\"openapi-try\">\n <h3 class=\"oat-section__title\">{{ 'openApi.try' | nvbT }}</h3>\n @if (operation.kind !== 'rest') {\n <p class=\"oat-notice\">{{ 'openApi.try.live' | nvbT }}</p>\n } @else if (operation.method !== 'GET') {\n <p class=\"oat-notice\">{{ 'openApi.try.getOnly' | nvbT }}</p>\n } @else {\n @if (tryParameters().length) {\n <div class=\"oat-try__fields\">\n @for (parameter of tryParameters(); track parameter.name) {\n <label class=\"oat-try__field\">\n <span>\n {{ parameter.name }}\n @if (parameter.required) {\n <span class=\"oat-required\">*</span>\n }\n </span>\n <input\n type=\"text\"\n [value]=\"tryValues()[parameter.name] ?? ''\"\n (input)=\"setTryValue(parameter.name, $any($event.target).value)\"\n (keydown.enter)=\"tryIt()\"\n [attr.data-testid]=\"'openapi-try-' + parameter.name\"\n />\n </label>\n }\n </div>\n }\n <button\n type=\"button\"\n class=\"oat-button\"\n [disabled]=\"!canTry() || trying()\"\n (click)=\"tryIt()\"\n data-testid=\"openapi-try-send\"\n >\n <nvb-icon [svg]=\"icons.play_arrow\" />\n {{ (trying() ? 'openApi.try.sending' : 'openApi.try.send') | nvbT }}\n </button>\n @if (tryResult(); as result) {\n <p class=\"oat-try__status\" [class.oat-try__status--error]=\"!result.ok\" data-testid=\"openapi-try-status\">\n {{ result.status || '' }} {{ result.statusText }} · {{ result.ms }} ms\n </p>\n <pre class=\"oat-code\" data-testid=\"openapi-try-body\">{{ result.body }}</pre>\n }\n }\n </section>\n </article>\n } @else if (state.operations().length === 0 && state.hasSpecs()) {\n <p class=\"oat-empty\">{{ 'openApi.loading' | nvbT }}</p>\n }\n </main>\n </div>\n</nvb-pro-feature-gate>\n\n<ng-template #schemaRows let-schema let-depth=\"depth\">\n <ul class=\"oat-tree\" role=\"list\">\n @for (field of childrenOf(schema); track field.name) {\n <li class=\"oat-tree__row\">\n <div class=\"oat-tree__line\">\n <code class=\"oat-tree__name\">{{ field.name }}</code>\n @if (field.required) {\n <span class=\"oat-required\" [title]=\"'openApi.required' | nvbT\">*</span>\n }\n <span class=\"oat-type\">{{ schemaType(field.schema) }}</span>\n @if (field.schema.nullable) {\n <span class=\"oat-flag\">null</span>\n }\n @if (field.schema.readOnly) {\n <span class=\"oat-flag\">{{ 'openApi.readOnly' | nvbT }}</span>\n }\n </div>\n @if (field.schema.description) {\n <p class=\"oat-tree__description\">{{ field.schema.description }}</p>\n }\n @if (field.schema.enum?.length) {\n <p class=\"oat-tree__description\">{{ 'openApi.oneOf' | nvbT }} {{ enumText(field.schema) }}</p>\n }\n @if (depth < 5 && childrenOf(field.schema).length) {\n <ng-container *ngTemplateOutlet=\"schemaRows; context: { $implicit: field.schema, depth: depth + 1 }\" />\n }\n </li>\n } @empty {\n <li class=\"oat-tree__row oat-tree__line\">\n <span class=\"oat-type\">{{ schemaType(schema) }}</span>\n </li>\n }\n </ul>\n</ng-template>\n",styles:[":host{--nvb-input-height: 32px;display:block;height:100%;min-height:0;background:color-mix(in oklab,var(--nvb-color-neutral-050) 84%,var(--nvb-color-surface-mix-target, white))}.oat{display:grid;grid-template-columns:280px minmax(0,1fr);gap:10px;height:100%;min-height:0;padding:10px;box-sizing:border-box}.oat-side,.oat-main{min-height:0;background:var(--nvb-card-bg, var(--nvb-color-neutral-000));border:1px solid var(--nvb-card-border-color, var(--nvb-color-neutral-200));border-radius:var(--nvb-card-radius, 8px);box-shadow:var(--nvb-card-shadow, none)}.oat-method{flex:0 0 auto;min-width:52px;padding:2px 6px;border-radius:3px;background:color-mix(in oklab,currentColor 10%,transparent);font-family:Consolas,Courier New,monospace;font-size:11px;font-weight:700;text-align:center}.oat-method[data-method=get]{color:var(--nvb-color-success-700, #15803d)}.oat-method[data-method=post]{color:var(--nvb-color-warning-700, #b45309)}.oat-method[data-method=put]{color:var(--nvb-color-info-700, #1d4ed8)}.oat-method[data-method=patch]{color:var(--nvb-color-primary-700)}.oat-method[data-method=delete]{color:var(--nvb-color-risk-700, #b91c1c)}.oat-method[data-method=live]{color:var(--nvb-color-info-700, #1d4ed8);background:color-mix(in oklab,currentColor 14%,transparent)}.oat-method--large{min-width:64px;padding:4px 8px;font-size:12px}.oat-side{display:flex;flex-direction:column;overflow:hidden}.oat-side__header{padding:12px 12px 8px}.oat-side__title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--nvb-color-neutral-900)}.oat-side__meta{margin:2px 0 0;font-size:11px;line-height:16px;color:var(--nvb-color-neutral-500);font-variant-numeric:tabular-nums}.oat-side__search{padding:0 12px 10px}.oat-filters{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 10px;border-bottom:1px solid var(--nvb-color-neutral-200)}.oat-filter{border:1px solid color-mix(in oklab,currentColor 30%,transparent);cursor:pointer}.oat-filter--off{opacity:.4;text-decoration:line-through}.oat-spec{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-bottom:1px solid var(--nvb-color-neutral-200);color:var(--nvb-color-neutral-600);font-size:12px}.oat-spec select{height:var(--nvb-input-height);border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-side__scroll{flex:1 1 auto;min-height:0;padding:0 8px 16px;overflow-y:auto}.oat-group__title{position:sticky;top:0;z-index:1;margin:8px 0 2px;padding:6px 8px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-600);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.oat-group__list{margin:0;padding:0;list-style:none}.oat-item{display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.oat-item:hover{background:color-mix(in oklab,var(--nvb-color-neutral-100) 70%,transparent)}.oat-item:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:-2px}.oat-item--active{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))}.oat-item--active:hover{background:color-mix(in oklab,var(--nvb-color-primary-500) 16%,var(--nvb-color-neutral-000))}.oat-item--deprecated .oat-item__path{text-decoration:line-through}.oat-item__text{display:flex;flex-direction:column;min-width:0}.oat-item__path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-800)}.oat-item__summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nvb-color-neutral-600);font-size:12px}.oat-main{overflow-y:auto}.oat-detail{max-width:960px;padding:20px 28px 40px}.oat-detail__line{display:flex;align-items:center;gap:10px;min-width:0}.oat-detail__path{min-width:0;overflow-wrap:anywhere;font-family:Consolas,Courier New,monospace;font-size:14px;color:var(--nvb-color-neutral-900)}.oat-detail__summary{margin:10px 0 0;font-size:18px;font-weight:600;color:var(--nvb-color-neutral-900)}.oat-detail__description{margin:6px 0 0;color:var(--nvb-color-neutral-700);font-size:13px;line-height:1.5;white-space:pre-line}.oat-detail__actions{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-top:14px}.oat-detail__id{color:var(--nvb-color-neutral-500);font-size:12px}.oat-button{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 14px;border:1px solid var(--nvb-color-neutral-300);border-radius:var(--nvb-button-radius, 4px);background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit;font-size:13px;font-weight:600;cursor:pointer}.oat-button nvb-icon{width:18px;height:18px}.oat-button:hover:not(:disabled){border-color:var(--nvb-color-primary-400)}.oat-button:disabled{opacity:.5;cursor:default}.oat-button:focus-visible{outline:2px solid var(--nvb-color-primary-500);outline-offset:1px}.oat-button--primary{border-color:var(--nvb-color-primary-600);background:var(--nvb-color-primary-600);color:var(--nvb-color-neutral-000, #fff)}.oat-button--primary:hover:not(:disabled){border-color:var(--nvb-color-primary-700);background:var(--nvb-color-primary-700)}.oat-link{margin-left:6px;padding:0;border:0;background:none;color:var(--nvb-color-primary-700);font:inherit;text-decoration:underline;cursor:pointer}.oat-notice{margin:8px;color:var(--nvb-color-neutral-600);font-size:12px;line-height:1.45}.oat-detail .oat-notice{margin:10px 0 0}.oat-notice--error{color:var(--nvb-color-risk-700, #b91c1c)}.oat-notice--warning{color:var(--nvb-color-warning-700, #b45309)}.oat-notice--success{color:var(--nvb-color-success-700, #15803d)}.oat-empty{padding:40px;color:var(--nvb-color-neutral-500)}.oat-section{margin-top:26px}.oat-section__title{display:flex;align-items:baseline;gap:10px;margin:0 0 8px;font-size:13px;font-weight:700;color:var(--nvb-color-neutral-800)}.oat-section__meta{color:var(--nvb-color-neutral-500);font-size:12px;font-weight:400}.oat-table{width:100%;border-collapse:collapse;font-size:12px}.oat-table th,.oat-table td{padding:6px 8px;border-bottom:1px solid var(--nvb-color-neutral-200);text-align:left;vertical-align:top}.oat-table th{color:var(--nvb-color-neutral-600);font-weight:600}.oat-table code{font-family:Consolas,Courier New,monospace}.oat-required{margin-left:2px;color:var(--nvb-color-risk-600, #dc2626);font-weight:700}.oat-type{color:var(--nvb-color-info-700, #1d4ed8);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-flag{padding:0 5px;border-radius:3px;background:var(--nvb-color-neutral-100);color:var(--nvb-color-neutral-600);font-size:11px}.oat-schema{padding:8px 12px;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000)}.oat-tree{margin:0;padding:0;list-style:none}.oat-tree .oat-tree{margin:2px 0 4px 6px;padding-left:12px;border-left:1px solid var(--nvb-color-neutral-200)}.oat-tree__row{padding:3px 0}.oat-tree__line{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.oat-tree__name{font-family:Consolas,Courier New,monospace;font-size:12px;color:var(--nvb-color-neutral-900)}.oat-tree__description{margin:1px 0 0;color:var(--nvb-color-neutral-600);font-size:12px}.oat-segmented{display:inline-flex;margin-bottom:8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;overflow:hidden}.oat-segmented button{padding:4px 12px;border:0;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-700);font:inherit;font-size:12px;cursor:pointer}.oat-segmented button+button{border-left:1px solid var(--nvb-color-neutral-300)}.oat-segmented__on{background:color-mix(in oklab,var(--nvb-color-primary-500) 12%,var(--nvb-color-neutral-000))!important;color:var(--nvb-color-primary-700)!important;font-weight:600}.oat-code{max-height:420px;margin:0;padding:10px 12px;overflow:auto;border:1px solid var(--nvb-color-neutral-200);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font-family:Consolas,Courier New,monospace;font-size:12px;line-height:1.45}.oat-try__fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-bottom:10px}.oat-try__field{display:flex;flex-direction:column;gap:4px;color:var(--nvb-color-neutral-700);font-family:Consolas,Courier New,monospace;font-size:12px}.oat-try__field input{height:var(--nvb-input-height);padding:0 8px;border:1px solid var(--nvb-color-neutral-300);border-radius:4px;background:var(--nvb-color-neutral-000);color:var(--nvb-color-neutral-800);font:inherit}.oat-try__status{margin:10px 0 6px;color:var(--nvb-color-success-700, #15803d);font-size:12px;font-weight:600}.oat-try__status--error{color:var(--nvb-color-risk-700, #b91c1c)}@media(max-width:760px){.oat{grid-template-columns:1fr;grid-template-rows:minmax(200px,40%) 1fr}.oat-detail{padding:16px}}\n"]}]}],ctorParameters:()=>[]});const xe="ngx-view-builder-openapi";const F="openApi";function tt(t){Te(V);return Oe([{provide:U,useValue:t},Ee({featurePacks:[{id:xe,title:"OpenAPI",capabilities:[F],builderTabs:[{id:ve,label:"tab.openApi",component:ye,order:380,icon:be.api,description:"tab.openApi.description"}],dataSourceEditorExtensions:[{id:F,label:"openApi.picker.row",component:fe,sourceTypes:["rest"],order:10}],uiDictionaries:{en:{...Ae,...V}}}]}),Ne(()=>{g(De).register(Ze(g(T),g(Pe),g(E)))})])}export{he as NGX_VIEW_BUILDER_OPENAPI_AI_ID,F as NGX_VIEW_BUILDER_OPENAPI_CAPABILITY,U as NGX_VIEW_BUILDER_OPENAPI_CONFIG,xe as NGX_VIEW_BUILDER_OPENAPI_FEATURE_PACK_ID,ve as NGX_VIEW_BUILDER_OPENAPI_TAB_ID,V as NGX_VIEW_BUILDER_OPENAPI_UI_EN,T as OpenApiState,ie as buildOpenApiCatalog,tt as provideNgxViewBuilderOpenApi};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngxviewbuilder/plugin-openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "OpenAPI plugin for the NGX View Builder designer: browse a REST API like Swagger and turn any endpoint into a data source in two clicks.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"homepage": "https://ngxviewbuilder.io",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@angular/common": "^22.0.0",
|
|
30
30
|
"@angular/core": "^22.0.0",
|
|
31
|
-
"@ngxviewbuilder/designer": "^0.
|
|
31
|
+
"@ngxviewbuilder/designer": "^0.15.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"tslib": "^2.6.0",
|
|
35
|
-
"@ngxviewbuilder/plugin-shared": "^0.
|
|
35
|
+
"@ngxviewbuilder/plugin-shared": "^0.15.0"
|
|
36
36
|
},
|
|
37
37
|
"module": "fesm2022/ngxviewbuilder-plugin-openapi.mjs",
|
|
38
38
|
"typings": "types/ngxviewbuilder-plugin-openapi.d.ts",
|
|
@@ -50,9 +50,15 @@ export interface IOpenApiParameter {
|
|
|
50
50
|
description?: string;
|
|
51
51
|
schema?: IOpenApiSchema;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* How the endpoint talks: a request and an answer (`rest`), a text/event-stream response
|
|
55
|
+
* the server keeps writing to (`sse`), or a WebSocket channel from an AsyncAPI document.
|
|
56
|
+
*/
|
|
57
|
+
export type OpenApiEndpointKind = 'rest' | 'sse' | 'websocket';
|
|
53
58
|
export interface IOpenApiOperation {
|
|
54
59
|
/** Unique within the catalog: `<specId> <METHOD> <path>`. */
|
|
55
60
|
key: string;
|
|
61
|
+
kind: OpenApiEndpointKind;
|
|
56
62
|
specId: string;
|
|
57
63
|
/** The spec's operationId, or one made from the method and path. */
|
|
58
64
|
operationId: string;
|
|
@@ -68,12 +74,17 @@ export interface IOpenApiOperation {
|
|
|
68
74
|
contentType: string;
|
|
69
75
|
schema?: IOpenApiSchema;
|
|
70
76
|
};
|
|
71
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* The first successful JSON response. For an event stream or a WebSocket channel, the
|
|
79
|
+
* schema of one message the client receives.
|
|
80
|
+
*/
|
|
72
81
|
response?: {
|
|
73
82
|
status: string;
|
|
74
83
|
description: string;
|
|
75
84
|
schema?: IOpenApiSchema;
|
|
76
85
|
};
|
|
86
|
+
/** Event stream: the event types the server sends, when the description lists them. */
|
|
87
|
+
events?: string[];
|
|
77
88
|
}
|
|
78
89
|
export interface IOpenApiCatalog {
|
|
79
90
|
specId: string;
|
|
@@ -154,12 +165,21 @@ export declare class OpenApiState {
|
|
|
154
165
|
static ɵfac: i0.ɵɵFactoryDeclaration<OpenApiState, never>;
|
|
155
166
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
156
167
|
}
|
|
168
|
+
type Json = Record<string, unknown>;
|
|
157
169
|
/**
|
|
158
170
|
* Turns an OpenAPI 3.0 or 3.1 document (JSON, already parsed) into the flat list of
|
|
159
171
|
* operations the builder works with. Throws with a message a person can act on when the
|
|
160
172
|
* document is not something it can read.
|
|
161
173
|
*/
|
|
162
174
|
export declare function buildOpenApiCatalog(document: unknown, spec: INgxViewBuilderOpenApiSpec, documentUrl?: string): IOpenApiCatalog;
|
|
175
|
+
/** Follows `$ref`s inside one document and flattens `allOf`. */
|
|
176
|
+
declare class SchemaResolver {
|
|
177
|
+
private readonly document;
|
|
178
|
+
constructor(document: Json);
|
|
179
|
+
deref(value: unknown, seen?: Set<string>): unknown;
|
|
180
|
+
schema(raw: unknown, depth?: number, stack?: string[]): IOpenApiSchema;
|
|
181
|
+
private pointer;
|
|
182
|
+
}
|
|
163
183
|
/** A value that looks like real data, for the example response. */
|
|
164
184
|
declare function exampleFromSchema(schema: IOpenApiSchema | undefined, depth?: number): unknown;
|
|
165
185
|
/**
|