@ngxviewbuilder/plugin-openapi 0.0.0-stage → 0.14.1

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/LICENSE.md ADDED
@@ -0,0 +1,79 @@
1
+ # NGX View Builder Designer Commercial License Agreement
2
+
3
+ Copyright © 2026 NGX View Builder (https://ngxviewbuilder.io). All rights reserved.
4
+
5
+ This is a legal agreement between you (the **"Licensee"**) and NGX View Builder (the **"Licensor"**) covering the `@ngxviewbuilder/designer` package and the official `@ngxviewbuilder/plugin-*` packages (together the **"Software"**). By installing or using the Software you agree to these terms.
6
+
7
+ **This is commercial software. It is not open source.** The Software's source form being readable in the npm distribution does not grant any open-source rights.
8
+
9
+ The `@ngxviewbuilder/runtime` package is **not** covered by this agreement. It is free to use in production, requires no License Key, and has its own terms in that package's `LICENSE.md`. What you pay for here is the visual editor your team designs views in.
10
+
11
+ ## 1. Definitions
12
+
13
+ - **"License Key"** a cryptographically signed key issued by Licensor that identifies the customer, plan, and expiry date (the **"License Term"**).
14
+ - **"Covered Version"** any version of the Software whose release date is earlier than the expiry date of Licensee's License Key.
15
+ - **"Application"** a software product that incorporates the Software, developed by Licensee for itself **or for a client of Licensee** (contract / agency work).
16
+
17
+ ## 2. License grant
18
+
19
+ Subject to the purchase of a valid License Key and compliance with this agreement, Licensor grants Licensee a non-exclusive, non-transferable, worldwide license to:
20
+
21
+ 1. use the Software to develop Applications, within the limits of the purchased plan;
22
+ 2. deploy Covered Versions in any number of Applications and environments, unless the purchased plan states otherwise;
23
+ 3. develop Applications **for third parties (Licensee's clients)** and deliver or transfer such Applications to them the client may operate, host, and use the delivered Application without a separate License Key;
24
+ 4. keep using Covered Versions **perpetually** this right survives the end of the License Term.
25
+
26
+ End users of Licensee's Applications do **not** need their own License Key: an Application ships `@ngxviewbuilder/runtime`, which is free and shows no license UI.
27
+
28
+ For clarity, development with the Software must be covered by a License Key held by **either side of a contract-work relationship**:
29
+
30
+ - If Licensee is a development company or agency, its License Key covers the development it performs for its clients (Section 2.3).
31
+ - If Licensee is the end customer of an Application, Licensee's License Key also covers **contractors and outsourced developers** working on that Application on Licensee's behalf. Such contractor use is limited to Licensee's Applications and does not extend to the contractor's own or other clients' projects.
32
+
33
+ Any party that develops with the Software outside these two cases needs its own License Key.
34
+
35
+ ## 3. Evaluation use
36
+
37
+ The Software may be used **without** a License Key for evaluation and development trial purposes only. In that mode the builder displays a watermark. Any production use requires a valid License Key.
38
+
39
+ ## 4. Updates
40
+
41
+ Versions of the Software released **after** the end of the License Term are not Covered Versions. Using them requires renewing the License Key; without renewal they run in degraded (watermarked) mode.
42
+
43
+ ## 5. Restrictions
44
+
45
+ Licensee may not:
46
+
47
+ 1. redistribute, resell, rent, lease, or sublicense the Software on its own (i.e. other than embedded in an Application);
48
+ 2. offer the Software, or a product derived from it, as a form-builder, view-builder, or low-code SDK/service that competes with the Software;
49
+ 3. remove, disable, alter, or circumvent the license-enforcement mechanisms (License Keys, watermark, signature or server validation), nor assist or permit anyone else to do so;
50
+ 4. exceed the seat, plan, or usage limits of the purchased plan;
51
+ 5. remove or alter copyright or license notices.
52
+
53
+ ## 6. Third-party software
54
+
55
+ The Software depends on third-party open-source packages installed separately from npm, each under its own license (MIT: `codemirror`, `@codemirror/*`, `jexl`, `prettier`, `lodash-es`; 0BSD: `tslib`). Those licenses govern those packages only; this agreement governs the Software.
56
+
57
+ ## 7. Ownership
58
+
59
+ The Software is licensed, not sold. Licensor retains all right, title, and interest in and to the Software.
60
+
61
+ ## 8. Warranty disclaimer
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
64
+
65
+ ## 9. Limitation of liability
66
+
67
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, LICENSOR SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES, NOR FOR LOSS OF PROFITS, DATA, OR BUSINESS, ARISING FROM THE USE OF THE SOFTWARE. LICENSOR'S AGGREGATE LIABILITY UNDER THIS AGREEMENT SHALL NOT EXCEED THE LICENSE FEES PAID BY LICENSEE IN THE TWELVE (12) MONTHS PRECEDING THE CLAIM.
68
+
69
+ ## 10. Termination
70
+
71
+ This agreement terminates automatically if Licensee materially breaches it. Upon termination, Licensee must stop using the Software; rights to Covered Versions granted under Section 2.3 do not survive termination for breach. Sections 6–11 survive termination.
72
+
73
+ ## 11. Governing law
74
+
75
+ This agreement is governed by the laws of the Republic of Lithuania. Any disputes shall be resolved by the competent courts of the Republic of Lithuania.
76
+
77
+ ---
78
+
79
+ Questions about licensing: info@heydelabs.com · Pricing and License Keys: https://ngxviewbuilder.io/pricing
package/README.md CHANGED
@@ -1,3 +1,64 @@
1
- # Temporary Holding Version
1
+ # @ngxviewbuilder/plugin-openapi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Puts your REST API's OpenAPI description inside the [NGX View Builder](https://ngxviewbuilder.io) designer, so creators connect a form to an endpoint by picking it instead of asking for a URL.
4
+
5
+ - An **API** tab lists every endpoint, grouped by tag, with search and a method filter. Each one shows its parameters, request body and response as a field tree, an example answer, and a **Try it** button for `GET` requests. Other methods are described, never sent.
6
+ - **Use as data source** adds a REST data source for the endpoint to the open view.
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
+ - 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
+ - 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
+ The data sources it makes are plain REST sources: views keep working at runtime without the plugin.
12
+
13
+ Needs an active NGX View Builder license key in the builder settings (`licenseKey`). Without one the tab and the row explain how to add it; nothing else in the designer changes.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm i @ngxviewbuilder/plugin-openapi
19
+ ```
20
+
21
+ ```ts
22
+ import { provideNgxViewBuilderOpenApi } from '@ngxviewbuilder/plugin-openapi';
23
+
24
+ bootstrapApplication(AppComponent, {
25
+ providers: [
26
+ provideHttpClient(),
27
+ provideNgxViewBuilderRuntime(),
28
+ provideNgxViewBuilderOpenApi({
29
+ specs: [{ id: 'crm', url: '/api/openapi.json' }],
30
+ }),
31
+ ],
32
+ });
33
+ ```
34
+
35
+ | Spec option | What it does |
36
+ | --- | --- |
37
+ | `id` | Stable id, stored in the data sources made from this description. |
38
+ | `url` / `document` | Where to load the OpenAPI document from, or the document itself. |
39
+ | `title` | Shown in the API tab; defaults to `info.title`. |
40
+ | `baseUrl` | Prefix of the data source URLs, e.g. `/api`; defaults to the first `servers` entry. |
41
+
42
+ The document is loaded through your `HttpClient`, so your interceptors add any authentication it needs. OpenAPI 3.0 and 3.1 in JSON are supported; YAML, Swagger 2.0 and `$ref` to other files are not yet.
43
+
44
+ ## Translating
45
+
46
+ Every text goes through the builder's UI dictionaries. The English texts are exported as `NGX_VIEW_BUILDER_OPENAPI_UI_EN`; give the same keys in your language:
47
+
48
+ ```ts
49
+ provideNgxViewBuilderUiTranslations({
50
+ lt: { 'openApi.use': 'Naudoti kaip duomenų šaltinį' },
51
+ });
52
+ ```
53
+
54
+ ## Requirements
55
+
56
+ Angular 22 or newer and `@ngxviewbuilder/designer` at a matching version.
57
+
58
+ ## Documentation
59
+
60
+ https://ngxviewbuilder.io/developers/plugin-openapi
61
+
62
+ ## License
63
+
64
+ Terms in [LICENSE.md](./LICENSE.md).
@@ -0,0 +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};
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@ngxviewbuilder/plugin-openapi",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.14.1",
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
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "homepage": "https://ngxviewbuilder.io",
7
+ "author": "NGX View Builder (https://ngxviewbuilder.io)",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ngxviewbuilder/ngx-view-builder-community.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ngxviewbuilder/ngx-view-builder-community/issues"
14
+ },
15
+ "keywords": [
16
+ "angular",
17
+ "view-builder",
18
+ "form-builder",
19
+ "openapi",
20
+ "swagger",
21
+ "rest",
22
+ "plugin",
23
+ "low-code"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "peerDependencies": {
29
+ "@angular/common": "^22.0.0",
30
+ "@angular/core": "^22.0.0",
31
+ "@ngxviewbuilder/designer": "^0.14.0"
32
+ },
33
+ "dependencies": {
34
+ "tslib": "^2.6.0",
35
+ "@ngxviewbuilder/plugin-shared": "^0.14.0"
36
+ },
37
+ "module": "fesm2022/ngxviewbuilder-plugin-openapi.mjs",
38
+ "typings": "types/ngxviewbuilder-plugin-openapi.d.ts",
39
+ "exports": {
40
+ "./package.json": {
41
+ "default": "./package.json"
42
+ },
43
+ ".": {
44
+ "types": "./types/ngxviewbuilder-plugin-openapi.d.ts",
45
+ "default": "./fesm2022/ngxviewbuilder-plugin-openapi.mjs"
46
+ }
47
+ },
48
+ "sideEffects": false,
49
+ "type": "module"
6
50
  }
@@ -0,0 +1,197 @@
1
+ import * as i0 from "@angular/core";
2
+ import { EnvironmentProviders, InjectionToken } from "@angular/core";
3
+ import { INgxViewBuilderAiPlugin, LicenseService, NgxViewBuilderApiService } from "@ngxviewbuilder/designer";
4
+ /** One API description the host makes available to the builder. */
5
+ export interface INgxViewBuilderOpenApiSpec {
6
+ /** Stable id, stored in the data sources made from this spec. */
7
+ id: string;
8
+ /** Shown in the API tab. Defaults to the spec's `info.title`. */
9
+ title?: string;
10
+ /** Where the OpenAPI document is loaded from (JSON). Fetched through the host's HttpClient. */
11
+ url?: string;
12
+ /** The OpenAPI document itself, instead of `url`. */
13
+ document?: unknown;
14
+ /**
15
+ * Prefix for the data source URLs, e.g. `/api` or `https://api.example.com/v2`.
16
+ * Defaults to the spec's first `servers` entry.
17
+ */
18
+ baseUrl?: string;
19
+ }
20
+ export interface INgxViewBuilderOpenApiConfig {
21
+ specs: INgxViewBuilderOpenApiSpec[];
22
+ }
23
+ export type OpenApiMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
24
+ /** A schema reduced to what the builder shows and generates from. */
25
+ export interface IOpenApiSchema {
26
+ type?: string;
27
+ format?: string;
28
+ title?: string;
29
+ description?: string;
30
+ enum?: unknown[];
31
+ example?: unknown;
32
+ default?: unknown;
33
+ nullable?: boolean;
34
+ readOnly?: boolean;
35
+ writeOnly?: boolean;
36
+ items?: IOpenApiSchema;
37
+ properties?: Record<string, IOpenApiSchema>;
38
+ required?: string[];
39
+ /** `oneOf` / `anyOf` alternatives. */
40
+ variants?: IOpenApiSchema[];
41
+ /** The component name when the schema came from `#/components/schemas/<name>`. */
42
+ refName?: string;
43
+ /** True where a schema refers back to itself; it is not expanded further. */
44
+ circular?: boolean;
45
+ }
46
+ export interface IOpenApiParameter {
47
+ name: string;
48
+ in: 'path' | 'query' | 'header' | 'cookie';
49
+ required: boolean;
50
+ description?: string;
51
+ schema?: IOpenApiSchema;
52
+ }
53
+ export interface IOpenApiOperation {
54
+ /** Unique within the catalog: `<specId> <METHOD> <path>`. */
55
+ key: string;
56
+ specId: string;
57
+ /** The spec's operationId, or one made from the method and path. */
58
+ operationId: string;
59
+ method: OpenApiMethod;
60
+ path: string;
61
+ summary: string;
62
+ description: string;
63
+ tags: string[];
64
+ deprecated: boolean;
65
+ parameters: IOpenApiParameter[];
66
+ requestBody?: {
67
+ required: boolean;
68
+ contentType: string;
69
+ schema?: IOpenApiSchema;
70
+ };
71
+ /** The first successful JSON response. */
72
+ response?: {
73
+ status: string;
74
+ description: string;
75
+ schema?: IOpenApiSchema;
76
+ };
77
+ }
78
+ export interface IOpenApiCatalog {
79
+ specId: string;
80
+ title: string;
81
+ version: string;
82
+ description: string;
83
+ baseUrl: string;
84
+ tags: {
85
+ name: string;
86
+ description: string;
87
+ }[];
88
+ operations: IOpenApiOperation[];
89
+ }
90
+ export type OpenApiSpecStatus = {
91
+ state: 'loading';
92
+ } | {
93
+ state: 'ready';
94
+ catalog: IOpenApiCatalog;
95
+ } | {
96
+ state: 'error';
97
+ message: string;
98
+ };
99
+ /** Written into `params.openApi` of a data source made from an operation. */
100
+ export interface IOpenApiSourceLink {
101
+ specId: string;
102
+ operationId: string;
103
+ method: OpenApiMethod;
104
+ path: string;
105
+ }
106
+ export declare const NGX_VIEW_BUILDER_OPENAPI_FEATURE_PACK_ID = "ngx-view-builder-openapi";
107
+ export declare const NGX_VIEW_BUILDER_OPENAPI_CAPABILITY = "openApi";
108
+ /**
109
+ * Adds the API tab to the designer, a Swagger-like view of the host's OpenAPI
110
+ * descriptions, and an endpoint search to the data source editor that fills in a REST
111
+ * source's address, method and request body. Needs an active license key in the builder
112
+ * settings; without one both explain how to turn it on.
113
+ *
114
+ * ```ts
115
+ * provideNgxViewBuilderOpenApi({
116
+ * specs: [{ id: 'crm', url: '/api/openapi.json', baseUrl: '/api' }],
117
+ * });
118
+ * ```
119
+ *
120
+ * The data sources it makes are plain REST sources, so views keep working at runtime
121
+ * without the plugin.
122
+ */
123
+ export declare function provideNgxViewBuilderOpenApi(config: INgxViewBuilderOpenApiConfig): EnvironmentProviders;
124
+ export declare const NGX_VIEW_BUILDER_OPENAPI_CONFIG: InjectionToken<INgxViewBuilderOpenApiConfig>;
125
+ export declare const NGX_VIEW_BUILDER_OPENAPI_TAB_ID = "openApi";
126
+ /**
127
+ * The API descriptions the host configured, loaded once and shared by the API tab, the
128
+ * data source picker and the AI plugin.
129
+ */
130
+ export declare class OpenApiState {
131
+ private readonly config;
132
+ private readonly http;
133
+ private readonly statuses;
134
+ private loading;
135
+ /** Operation the API tab shows, set from the picker or by an AI client. */
136
+ readonly selectedKey: import("@angular/core").WritableSignal<string>;
137
+ readonly specs: import("@angular/core").Signal<{
138
+ spec: import("@ngxviewbuilder/plugin-openapi").INgxViewBuilderOpenApiSpec;
139
+ status: OpenApiSpecStatus;
140
+ }[]>;
141
+ readonly catalogs: import("@angular/core").Signal<IOpenApiCatalog[]>;
142
+ readonly operations: import("@angular/core").Signal<IOpenApiOperation[]>;
143
+ readonly hasSpecs: import("@angular/core").Signal<boolean>;
144
+ /** Loads every spec once; later calls wait for the same load. */
145
+ ensureLoaded(): Promise<void>;
146
+ /** Loads every spec again, e.g. after the API was deployed with new endpoints. */
147
+ reload(): Promise<void>;
148
+ catalogOf(specId: string): IOpenApiCatalog | null;
149
+ findOperation(specId: string, operationId: string): IOpenApiOperation | null;
150
+ private loadAll;
151
+ private loadOne;
152
+ private fetchDocument;
153
+ private setStatus;
154
+ static ɵfac: i0.ɵɵFactoryDeclaration<OpenApiState, never>;
155
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
156
+ }
157
+ /**
158
+ * Turns an OpenAPI 3.0 or 3.1 document (JSON, already parsed) into the flat list of
159
+ * operations the builder works with. Throws with a message a person can act on when the
160
+ * document is not something it can read.
161
+ */
162
+ export declare function buildOpenApiCatalog(document: unknown, spec: INgxViewBuilderOpenApiSpec, documentUrl?: string): IOpenApiCatalog;
163
+ /** A value that looks like real data, for the example response. */
164
+ declare function exampleFromSchema(schema: IOpenApiSchema | undefined, depth?: number): unknown;
165
+ /**
166
+ * The request body a data source starts with: every field the API accepts, read-only ones
167
+ * left out. A field whose name matches a field of the view becomes a `{placeholder}` for
168
+ * it; the rest get an empty value of the right type, to be filled or removed in the body
169
+ * builder.
170
+ */
171
+ declare function bodyTemplateFromSchema(schema: IOpenApiSchema | undefined, viewPaths: ReadonlySet<string>, prefix?: string, depth?: number): unknown;
172
+ /** Where the list sits in a response: '' when the response is the list itself. */
173
+ declare function findArrayPath(schema: IOpenApiSchema | undefined): string | null;
174
+ /** The schema of one row of the list a response carries, if it carries one. */
175
+ declare function rowSchema(schema: IOpenApiSchema | undefined): IOpenApiSchema | undefined;
176
+ /** Which field of a row works as an option's value and which as its label. */
177
+ declare function suggestOptionFields(row: IOpenApiSchema | undefined): {
178
+ value?: string;
179
+ label?: string;
180
+ };
181
+ /** The URL a data source stores: the base, the path with `{params}`, required query params. */
182
+ declare function dataSourceUrl(baseUrl: string, operation: IOpenApiOperation): string;
183
+ /** A data source name from an operationId, e.g. `listCustomers`. */
184
+ declare function dataSourceName(operation: IOpenApiOperation): string;
185
+ export declare const NGX_VIEW_BUILDER_OPENAPI_AI_ID = "openApi";
186
+ /**
187
+ * The host's API descriptions for AI clients, so a data source is wired to an endpoint
188
+ * that exists with the body the API expects, instead of a guessed URL.
189
+ */
190
+ declare function createOpenApiAiPlugin(state: OpenApiState, license: LicenseService, api: NgxViewBuilderApiService): INgxViewBuilderAiPlugin;
191
+ /**
192
+ * Every text of the OpenAPI plugin in English, by key. To show it in another language,
193
+ * give the same keys in that language, e.g.
194
+ * `provideNgxViewBuilderUiTranslations({ lt: { 'openApi.title': 'API' } })`.
195
+ * Placeholders in braces stay as they are.
196
+ */
197
+ export declare const NGX_VIEW_BUILDER_OPENAPI_UI_EN: Record<string, string>;