@ngxviewbuilder/plugin-templates 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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: support@ngxviewbuilder.io · Pricing and License Keys: https://ngxviewbuilder.io/pricing
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @ngxviewbuilder/plugin-templates
2
+
3
+ Adds a **Templates** tab to the [NGX View Builder](https://ngxviewbuilder.io) designer, so people editing a view can save a section as a reusable template and drop it into other views later.
4
+
5
+ Optional plugin. The designer works without it.
6
+
7
+ ## Requirements
8
+
9
+ Angular 22 or newer and [`@ngxviewbuilder/designer`](https://www.npmjs.com/package/@ngxviewbuilder/designer) at a matching version.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm i @ngxviewbuilder/plugin-templates
15
+ ```
16
+
17
+ Register the provider next to the runtime providers:
18
+
19
+ ```ts
20
+ import { bootstrapApplication } from '@angular/platform-browser';
21
+ import { provideNgxViewBuilderRuntime } from '@ngxviewbuilder/runtime';
22
+ import { provideNgxViewBuilderTemplates } from '@ngxviewbuilder/plugin-templates';
23
+ import { AppComponent } from './app/app.component';
24
+
25
+ bootstrapApplication(AppComponent, {
26
+ providers: [
27
+ provideNgxViewBuilderRuntime(),
28
+ provideNgxViewBuilderTemplates(),
29
+ ],
30
+ });
31
+ ```
32
+
33
+ That is the whole setup. The tab appears in the designer header; a host that only renders views with the runtime is unaffected.
34
+
35
+ Storage is yours: the designer raises `templateSaved`, `templateDeleted` and `templatesLoaded`, and you decide where templates live. See the [developer guide](https://ngxviewbuilder.io/developers/).
36
+
37
+ ## Upgrading from 0.5.x
38
+
39
+ The package moved to the `@ngxviewbuilder` scope in 0.6.0. Nothing else changed — selectors, class names, inputs, outputs, providers and saved view JSON are identical.
40
+
41
+ ```bash
42
+ npm rm ngx-view-builder-plugin-templates
43
+ npm i @ngxviewbuilder/plugin-templates
44
+ ```
45
+
46
+ ```diff
47
+ -import { ... } from 'ngx-view-builder-plugin-templates';
48
+ +import { ... } from '@ngxviewbuilder/plugin-templates';
49
+ ```
50
+
51
+ ## Documentation
52
+
53
+ https://ngxviewbuilder.io
54
+
55
+ ## License
56
+
57
+ Terms in [LICENSE.md](./LICENSE.md).
@@ -0,0 +1,2 @@
1
+ import{CreatorStructureService as k,TemplateEngineService as D,EventService as C,NgxViewBuilderApiService as E,NativeCodeEditor as f,NvbStyleOutlet as x,BuilderActionButton as b,BuilderIconButton as w,UiTranslatePipe as T,provideNgxViewBuilderExtensions as N,HeaderTabEnum as z,NGX_VIEW_BUILDER_TEMPLATES_CAPABILITY as I,NGX_VIEW_BUILDER_TEMPLATES_FEATURE_PACK_ID as M}from"@ngxviewbuilder/designer";import{NGX_VIEW_BUILDER_TEMPLATES_CAPABILITY as H,NGX_VIEW_BUILDER_TEMPLATES_FEATURE_PACK_ID as j}from"@ngxviewbuilder/designer";import*as l from"@angular/core";import{inject as _,input as A,signal as p,computed as m,ViewChild as F,ChangeDetectionStrategy as B,Component as R}from"@angular/core";import*as g from"@angular/forms";import{FormsModule as y}from"@angular/forms";class i{previewTemplateScopeId=`nvb-template-preview-${Math.random().toString(36).slice(2,11)}`;previewTemplateScopeSelector=`[data-nvb-template-scope="${this.previewTemplateScopeId}"]`;static SIDEBAR_WIDTH_STORAGE_KEY="nvb-templates-sidebar-width";static MIN_SIDEBAR_WIDTH=200;static MAX_SIDEBAR_WIDTH=400;static DEFAULT_SIDEBAR_WIDTH=280;sidebarRef;sidebarWidth=i.DEFAULT_SIDEBAR_WIDTH;isResizingSidebar=false;creatorStructureService=_(k);templateEngineService=_(D);eventService=_(C);ngxViewBuilderApiService=_(E);theme=A("light",...ngDevMode?[{debugName:"theme"}]:[]);templates=p([],...ngDevMode?[{debugName:"templates"}]:[]);selectedTemplateName=p("",...ngDevMode?[{debugName:"selectedTemplateName"}]:[]);draft=p(null,...ngDevMode?[{debugName:"draft"}]:[]);error=p("",...ngDevMode?[{debugName:"error"}]:[]);formatError=p("",...ngDevMode?[{debugName:"formatError"}]:[]);cssFormatError=p("",...ngDevMode?[{debugName:"cssFormatError"}]:[]);previewItemsJson=p(JSON.stringify({name:"John",lastname:"Smith",status:"Y"},null,2),...ngDevMode?[{debugName:"previewItemsJson"}]:[]);onTemplatesLoadedHandler=()=>{this.reload()};onTemplateSavedHandler=()=>{this.reload(this.selectedTemplateName())};onTemplateDeletedHandler=()=>{this.reload()};previewState=m(()=>{const e=String(this.previewItemsJson()??"").trim();if(!e){return{item:null,error:""}}try{const t=JSON.parse(e);if(t&&typeof t==="object"&&!Array.isArray(t)){return{item:t,error:""}}return{item:null,error:"templates.preview.invalidObject"}}catch{return{item:null,error:"templates.preview.invalidJson"}}},...ngDevMode?[{debugName:"previewState"}]:[]);previewItem=m(()=>this.previewState().item,...ngDevMode?[{debugName:"previewItem"}]:[]);previewJsonError=m(()=>this.previewState().error,...ngDevMode?[{debugName:"previewJsonError"}]:[]);hasTemplates=m(()=>this.templates().length>0,...ngDevMode?[{debugName:"hasTemplates"}]:[]);isDirty=m(()=>{const e=this.draft();if(!e){return false}const t=this.templates().find(a=>a.name===e.originalName);if(!t){return true}return String(e.name??"").trim()!==t.name||String(e.title??"").trim()!==String(t.title??"")||String(e.content??"")!==String(t.content??"")||String(e.css??"").trim()!==String(t.css??"")},...ngDevMode?[{debugName:"isDirty"}]:[]);previewCard=m(()=>{const e=this.draft();if(!e){return""}const t=this.composeTemplateMarkup(e);const a=this.previewItem();if(!a){return""}const r=this.ngxViewBuilderApiService.getData()??{};const n={...r,item:a,row:a,index:0,value:a};return this.templateEngineService.renderTemplate(t,n)},...ngDevMode?[{debugName:"previewCard"}]:[]);previewTemplateCss=m(()=>{const e=this.draft();if(!e){return""}const t=this.composeTemplateMarkup(e);return this.templateEngineService.resolveTemplateCss(t,this.previewTemplateScopeSelector)},...ngDevMode?[{debugName:"previewTemplateCss"}]:[]);ngOnInit(){this.ngxViewBuilderApiService.onTemplatesLoaded.add(this.onTemplatesLoadedHandler);this.ngxViewBuilderApiService.onTemplateSaved.add(this.onTemplateSavedHandler);this.ngxViewBuilderApiService.onTemplateDeleted.add(this.onTemplateDeletedHandler);this.reload()}ngAfterViewInit(){const e=parseInt(localStorage.getItem(i.SIDEBAR_WIDTH_STORAGE_KEY)??"",10);if(Number.isFinite(e)){this.sidebarWidth=this.clampSidebarWidth(e)}this.applySidebarWidth()}ngOnDestroy(){this.ngxViewBuilderApiService.onTemplatesLoaded.remove(this.onTemplatesLoadedHandler);this.ngxViewBuilderApiService.onTemplateSaved.remove(this.onTemplateSavedHandler);this.ngxViewBuilderApiService.onTemplateDeleted.remove(this.onTemplateDeletedHandler);document.removeEventListener("mousemove",this.onSidebarResizeMouseMove);document.removeEventListener("mouseup",this.onSidebarResizeMouseUp)}onSidebarResizeMouseDown(e){e.preventDefault();this.isResizingSidebar=true;document.addEventListener("mousemove",this.onSidebarResizeMouseMove);document.addEventListener("mouseup",this.onSidebarResizeMouseUp)}onSidebarResizeMouseMove=e=>{if(!this.isResizingSidebar||!this.sidebarRef){return}const t=this.sidebarRef.nativeElement.getBoundingClientRect();this.sidebarWidth=this.clampSidebarWidth(e.clientX-t.left);this.applySidebarWidth()};onSidebarResizeMouseUp=()=>{this.isResizingSidebar=false;document.removeEventListener("mousemove",this.onSidebarResizeMouseMove);document.removeEventListener("mouseup",this.onSidebarResizeMouseUp);localStorage.setItem(i.SIDEBAR_WIDTH_STORAGE_KEY,String(this.sidebarWidth))};clampSidebarWidth(e){return Math.min(i.MAX_SIDEBAR_WIDTH,Math.max(i.MIN_SIDEBAR_WIDTH,e))}applySidebarWidth(){if(this.sidebarRef){this.sidebarRef.nativeElement.style.width=`${this.sidebarWidth}px`}}trackByName(e,t){return`${e}:${t.name}`}isSelected(e){return this.selectedTemplateName()===String(e??"").trim()}selectTemplate(e){const t=String(e??"").trim();if(!t){return}const a=this.templates().find(r=>r.name===t);if(!a){return}this.selectedTemplateName.set(t);this.draft.set({...a,originalName:t});this.error.set("");this.formatError.set("");this.cssFormatError.set("")}addTemplate(){const e=this.readTemplatesFromStructure();const t=new Set(e.map(c=>c.name));const a="template";let r=1;let n=`${a}${r}`;while(t.has(n)){r+=1;n=`${a}${r}`}const o={name:n,title:`Template ${r}`,content:"",css:"",requestBodyJson:"",extraJsonSettings:""};const d=this.sortTemplates([...e,o]);this.persistTemplates(d);this.reload(n)}deleteSelectedTemplate(){const e=this.selectedTemplateName();if(!e){return}this.deleteTemplate(e)}deleteTemplate(e){const t=String(e??"").trim();if(!t){return}const a=this.readTemplatesFromStructure().filter(n=>n.name!==t);const r=this.selectedTemplateName()===t?a[0]?.name??"":this.selectedTemplateName();this.persistTemplates(a);this.ngxViewBuilderApiService.onTemplateDeleted.fire({code:t,timestamp:Date.now()});this.reload(r)}saveDraft(){const e=this.draft();if(!e){return}const t=String(e.name??"").trim();const a=String(e.title??"").trim();const r=String(e.content??"");const n=String(e.css??"").trim();const o=String(e.requestBodyJson??"").trim();const d=String(e.extraJsonSettings??"").trim();if(!t){this.error.set("templates.error.nameRequired");return}const c=this.readTemplatesFromStructure();const v=c.find(h=>h.name===t&&h.name!==e.originalName);if(v){this.error.set("templates.error.duplicateName");return}const u={name:t,title:a||void 0,content:r,css:n||void 0,requestBodyJson:o||void 0,extraJsonSettings:d||void 0};const s=String(e.originalName??"").trim();const S=c.filter(h=>{if(s&&h.name===s){return false}return h.name!==t});this.persistTemplates(this.sortTemplates([...S,u]));this.ngxViewBuilderApiService.onTemplateSaved.fire({template:{...u,code:t},previousCode:s&&s!==t?s:void 0,timestamp:Date.now()});this.reload(t)}updateDraftField(e,t){const a=this.draft();if(!a){return}this.draft.set({...a,[e]:t});if(e==="content"){this.formatError.set("");return}if(e==="css"){this.cssFormatError.set("")}}async onTemplateContentBlur(e){this.updateDraftField("content",e);await this.formatTemplateContent()}async onTemplateCssBlur(e){this.updateDraftField("css",e);await this.formatTemplateCss()}async formatTemplateContent(){const e=this.draft();if(!e){return}const t=String(e.content??"");if(!t.trim()){this.updateDraftField("content","");this.formatError.set("");return}try{const a=await this.formatHtmlWithPrettier(t);this.updateDraftField("content",a);this.formatError.set("")}catch(a){const r=a instanceof Error&&a.message?a.message:"templates.error.formatFailed";this.formatError.set(r)}}async formatTemplateCss(){const e=this.draft();if(!e){return}const t=String(e.css??"");if(!t.trim()){this.updateDraftField("css","");this.cssFormatError.set("");return}try{const a=await this.formatCssWithPrettier(t);this.updateDraftField("css",a);this.cssFormatError.set("")}catch(a){const r=a instanceof Error?a.message:"Failed to format CSS.";this.cssFormatError.set(r)}}async onPreviewTemplateClick(e,t,a){const r={...this.ngxViewBuilderApiService.getData()??{},item:t,row:t,value:t,index:a};await this.templateEngineService.handleTemplateClickEvent(e,r)}composeTemplateMarkup(e){const t=String(e?.content??"");const a=String(e?.css??"").trim();if(!a){return t}return`<style>${a}</style>
2
+ ${t}`}async formatHtmlWithPrettier(e){const[{format:t},a]=await Promise.all([import("prettier/standalone"),import("prettier/plugins/html")]);const r=a.default??a;return t(String(e??""),{parser:"angular",plugins:[r],printWidth:100,tabWidth:2})}async formatCssWithPrettier(e){const[{format:t},a]=await Promise.all([import("prettier/standalone"),import("prettier/plugins/postcss")]);const r=a.default??a;return t(String(e??""),{parser:"css",plugins:[r],printWidth:100,tabWidth:2})}reload(e){const t=this.readTemplatesFromStructure();const a=this.sortTemplates(t);this.templates.set(a);const r=o=>a.some(d=>d.name===o);const n=(e&&r(e)?e:r(this.selectedTemplateName())?this.selectedTemplateName():"")||a[0]?.name||"";if(!n){this.selectedTemplateName.set("");this.draft.set(null);this.error.set("");this.formatError.set("");this.cssFormatError.set("");return}this.selectTemplate(n)}notifyStructureChanged(){this.eventService.afterPropertyChanged.fire({payload:null});this.ngxViewBuilderApiService.notifyStructureChanged()}persistTemplates(e){const t=this.creatorStructureService.getStructure();if(!t){return}if(!t.settings||typeof t.settings!=="object"){t.settings={width:"1182",language:"lt"}}t.settings.templates=this.sortTemplates(e).map(a=>({...a}));this.notifyStructureChanged()}readTemplatesFromStructure(){const e=this.creatorStructureService.getStructure()?.settings?.templates;if(!Array.isArray(e)){return[]}return e.map(t=>this.normalizeTemplate(t)).filter(t=>t!==null)}normalizeTemplate(e){if(!e||typeof e!=="object"){return null}const t=e;const a=String(t["name"]??t["code"]??"").trim();if(!a){return null}const r=String(t["title"]??"").trim();const n=String(t["description"]??"").trim();const o=String(t["css"]??"").trim();const d=String(t["requestBodyJson"]??"").trim();const c=String(t["extraJsonSettings"]??"").trim();const v=Array.isArray(t["tags"])?t["tags"].map(s=>String(s??"").trim()).filter(s=>s.length>0):[];const u={...t,name:a,title:r||void 0,description:n||void 0,content:String(t["content"]??""),css:o||void 0,requestBodyJson:d||void 0,extraJsonSettings:c||void 0,tags:v.length>0?v:void 0};delete u["code"];return u}sortTemplates(e){return[...e].sort((t,a)=>t.name.localeCompare(a.name))}static ɵfac=l.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"22.1.4",ngImport:l,type:i,deps:[],target:l.ɵɵFactoryTarget.Component});static ɵcmp=l.ɵɵngDeclareComponent({minVersion:"17.0.0",version:"22.1.4",type:i,isStandalone:true,selector:"templates-editor",inputs:{theme:{classPropertyName:"theme",publicName:"theme",isSignal:true,isRequired:false,transformFunction:null}},viewQueries:[{propertyName:"sidebarRef",first:true,predicate:["sidebar"],descendants:true}],ngImport:l,template:"<div class=\"templates\" data-testid=\"templates-editor-root\">\n <aside class=\"templates__sidebar\" #sidebar>\n <header class=\"templates__sidebar-header\">\n <h3 class=\"templates__sidebar-title\">\n {{ 'templates.title' | uiT: 'Templates' }}\n @if (hasTemplates()) {\n <span class=\"templates__count\">{{ templates().length }}</span>\n }\n </h3>\n <builder-icon-button\n icon=\"add\"\n size=\"sm\"\n testId=\"templates-add\"\n [ariaLabel]=\"'templates.add' | uiT: 'Add template'\"\n [title]=\"'templates.add' | uiT: 'Add template'\"\n (clicked)=\"addTemplate()\"\n />\n </header>\n\n @if (!hasTemplates()) {\n <div class=\"templates__empty\">\n <span>{{ 'templates.empty' | uiT: 'No templates yet.' }}</span>\n <builder-action-button\n tone=\"primary\"\n size=\"sm\"\n icon=\"add\"\n [label]=\"'templates.add' | uiT: 'Add template'\"\n testId=\"templates-add-empty\"\n (clicked)=\"addTemplate()\"\n />\n </div>\n } @else {\n <ul class=\"templates__list\">\n @for (template of templates(); track trackByName($index, template)) {\n <li\n class=\"templates__list-row\"\n [class.templates__list-row--active]=\"isSelected(template.name)\"\n >\n <button\n type=\"button\"\n class=\"templates__item\"\n (click)=\"selectTemplate(template.name)\"\n [attr.data-testid]=\"'templates-item-' + template.name\"\n >\n <span class=\"templates__item-title\">{{ template.title || template.name }}</span>\n <span class=\"templates__item-name\">{{ template.name }}</span>\n </button>\n <builder-icon-button\n class=\"templates__item-delete\"\n [className]=\"'templates__item-delete'\"\n icon=\"delete\"\n tone=\"danger\"\n size=\"sm\"\n [ghost]=\"true\"\n (clicked)=\"deleteTemplate(template.name)\"\n [ariaLabel]=\"'templates.delete' | uiT: 'Delete'\"\n [title]=\"'templates.delete' | uiT: 'Delete'\"\n [testId]=\"'templates-delete-' + template.name\"\n />\n </li>\n }\n </ul>\n }\n\n <div\n class=\"templates__resize-handle\"\n data-testid=\"templates-sidebar-resize\"\n (mousedown)=\"onSidebarResizeMouseDown($event)\"\n ></div>\n </aside>\n\n <section class=\"templates__editor\">\n @if (draft(); as currentDraft) {\n <div class=\"templates__workspace\">\n <div class=\"templates__workspace-panel templates__workspace-panel--content\">\n <section class=\"templates__card templates__card--editor\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.content' | uiT: 'Template (Angular syntax)' }}\n </h3>\n <builder-icon-button\n icon=\"code_blocks\"\n size=\"xs\"\n [ghost]=\"true\"\n testId=\"templates-format\"\n [ariaLabel]=\"'templates.format' | uiT: 'Format template'\"\n [title]=\"'templates.format' | uiT: 'Format template'\"\n (clicked)=\"formatTemplateContent()\"\n />\n </header>\n <div class=\"templates__card-body\">\n <native-code-editor\n language=\"template\"\n [theme]=\"theme()\"\n [value]=\"currentDraft.content || ''\"\n (valueChange)=\"updateDraftField('content', $event)\"\n (editorBlur)=\"onTemplateContentBlur($event)\"\n data-testid=\"templates-content\"\n />\n </div>\n @if (formatError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ formatError() | uiT: formatError() }}\n </div>\n }\n </section>\n\n <section class=\"templates__card templates__card--editor\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">{{ 'templates.css' | uiT: 'CSS classes' }}</h3>\n <builder-icon-button\n icon=\"code_blocks\"\n size=\"xs\"\n [ghost]=\"true\"\n testId=\"templates-css-format\"\n [ariaLabel]=\"'templates.css.format' | uiT: 'Format CSS'\"\n [title]=\"'templates.css.format' | uiT: 'Format CSS'\"\n (clicked)=\"formatTemplateCss()\"\n />\n </header>\n <div class=\"templates__card-body\">\n <native-code-editor\n language=\"css\"\n [theme]=\"theme()\"\n [value]=\"currentDraft.css || ''\"\n (valueChange)=\"updateDraftField('css', $event)\"\n (editorBlur)=\"onTemplateCssBlur($event)\"\n data-testid=\"templates-css\"\n />\n </div>\n @if (cssFormatError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ cssFormatError() | uiT: cssFormatError() }}\n </div>\n }\n </section>\n </div>\n\n <div class=\"templates__workspace-panel\">\n <section class=\"templates__card templates__card--settings\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.settings' | uiT: 'Template settings' }}\n </h3>\n @if (isDirty()) {\n <span\n class=\"templates__dirty-dot\"\n [title]=\"'templates.unsaved' | uiT: 'Unsaved changes'\"\n ></span>\n }\n <builder-action-button\n tone=\"primary\"\n size=\"sm\"\n [label]=\"'templates.save' | uiT: 'Save'\"\n testId=\"templates-save\"\n (clicked)=\"saveDraft()\"\n />\n </header>\n <div class=\"templates__settings\">\n <label class=\"templates__field\">\n <span>{{ 'templates.name' | uiT: 'Name' }}</span>\n <input\n type=\"text\"\n class=\"nvb-control__text-input\"\n [ngModel]=\"currentDraft.name\"\n (ngModelChange)=\"updateDraftField('name', $event)\"\n data-testid=\"templates-name\"\n />\n </label>\n\n <label class=\"templates__field\">\n <span>{{ 'templates.titleField' | uiT: 'Title' }}</span>\n <input\n type=\"text\"\n class=\"nvb-control__text-input\"\n [ngModel]=\"currentDraft.title\"\n (ngModelChange)=\"updateDraftField('title', $event)\"\n data-testid=\"templates-title\"\n />\n </label>\n </div>\n @if (error()) {\n <div class=\"templates__alert\" role=\"alert\" data-testid=\"templates-error\">\n {{ error() | uiT: error() }}\n </div>\n }\n </section>\n\n <section class=\"templates__card templates__card--preview\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.previewTitle' | uiT: 'Preview' }}\n </h3>\n </header>\n <div class=\"templates__preview\" data-testid=\"templates-preview\">\n @if (previewTemplateCss()) {\n <nvb-style-outlet [cssText]=\"previewTemplateCss()\"></nvb-style-outlet>\n }\n @if (!previewCard()) {\n <div class=\"templates__preview-placeholder\">\n {{ 'templates.preview.empty' | uiT: 'No preview object.' }}\n </div>\n } @else {\n <article\n class=\"templates__preview-card\"\n [attr.data-nvb-template-scope]=\"previewTemplateScopeId\"\n [innerHTML]=\"previewCard()\"\n (click)=\"onPreviewTemplateClick($event, previewItem()!, 0)\"\n ></article>\n }\n </div>\n <div class=\"templates__preview-data\">\n <header class=\"templates__card-header templates__card-header--sub\">\n <h4 class=\"templates__card-title\">\n {{ 'templates.previewData' | uiT: 'Preview data (JSON)' }}\n </h4>\n </header>\n <div class=\"templates__preview-data-editor\">\n <native-code-editor\n language=\"json\"\n [theme]=\"theme()\"\n [value]=\"previewItemsJson()\"\n (valueChange)=\"previewItemsJson.set($event)\"\n data-testid=\"templates-preview-data\"\n />\n </div>\n @if (previewJsonError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ previewJsonError() | uiT: 'Preview data must be a valid JSON object.' }}\n </div>\n }\n </div>\n </section>\n </div>\n </div>\n } @else {\n <div class=\"templates__placeholder\">\n {{ 'templates.select' | uiT: 'Select a template to edit.' }}\n </div>\n }\n </section>\n</div>\n",styles:['@charset "UTF-8";:host{display:block;height:100%;overflow:hidden;background:color-mix(in oklab,var(--color-neutral-050) 84%,white)}.templates{height:100%;min-height:0;display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;overflow:hidden}.templates__card{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow);overflow:hidden}.templates__card-header{flex:0 0 auto;display:flex;align-items:center;gap:8px;min-height:36px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--color-neutral-200);background:var(--nvb-card-bg)}.templates__card-header--sub{border-top:1px solid var(--color-neutral-200);border-bottom:none;min-height:30px}.templates__card-title{flex:1 1 auto;margin:0;font-size:12px;font-weight:600;line-height:16px;color:var(--color-neutral-700)}.templates__card-header--sub .templates__card-title{color:var(--color-neutral-600)}.templates__card-body{flex:1 1 auto;min-height:0;padding:8px;overflow:hidden}.templates__sidebar{position:relative;display:flex;flex-direction:column;width:280px;min-width:200px;max-width:400px;min-height:0;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow);overflow:hidden}.templates__sidebar-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:36px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--color-neutral-200);background:var(--nvb-card-bg)}.templates__sidebar-title{display:inline-flex;align-items:center;gap:6px;margin:0;font-size:12px;font-weight:600;line-height:16px;color:var(--color-neutral-700)}.templates__count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:16px;padding:0 5px;border-radius:999px;background:var(--color-neutral-100);color:var(--color-neutral-600);font-size:10px;font-weight:600;letter-spacing:0}.templates__resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;z-index:10;cursor:ew-resize;background-color:transparent}.templates__resize-handle:after{content:"";position:absolute;top:50%;right:2px;width:2px;height:40px;transform:translateY(-50%);border-radius:999px;background-color:var(--color-neutral-300);opacity:0;transition:opacity .15s ease,background-color .15s ease}.templates__resize-handle:hover:after{opacity:1;background-color:var(--color-primary-500)}.templates__list{list-style:none;margin:0;padding:6px;display:grid;gap:2px;align-content:start;grid-auto-rows:min-content;flex:1 1 auto;min-height:0;overflow:auto}.templates__list-row{position:relative;display:flex;align-items:center;min-width:0}.templates__item{flex:1 1 auto;min-width:0;display:grid;gap:1px;text-align:left;border:none;border-left:2px solid transparent;border-radius:var(--radius-md);background:transparent;padding:6px 34px 6px 10px;cursor:pointer;font:inherit;transition:background-color .14s ease,border-color .14s ease}.templates__item-title{font-size:13px;font-weight:600;color:var(--color-neutral-700);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.templates__item-name{font-size:11px;color:var(--color-neutral-400);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.templates__item:hover{background:color-mix(in oklab,var(--color-neutral-100) 70%,var(--color-neutral-000))}.templates__list-row--active .templates__item{border-left-color:var(--color-primary-500);background:color-mix(in oklab,var(--color-primary-500) 8%,var(--color-surface-mix-target, white))}.templates__list-row--active .templates__item-title{color:var(--color-primary-700)}.templates__item-delete{position:absolute;top:50%;right:6px;transform:translateY(-50%);opacity:0;transition:opacity .14s ease}.templates__list-row:hover .templates__item-delete,.templates__list-row:focus-within .templates__item-delete,.templates__list-row--active .templates__item-delete{opacity:1}.templates__empty{flex:1 1 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:16px;color:var(--color-neutral-500);font-size:13px;text-align:center}.templates__editor{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.templates__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(340px,.82fr);gap:10px;flex:1 1 auto;min-height:0;overflow:hidden}.templates__workspace-panel{min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px;overflow:hidden}.templates__workspace-panel--content .templates__card--editor{flex:1 1 0}.templates__card--settings{flex:0 0 auto;--nvb-input-height: 36px;--nvb-input-font-size: 13px}.templates__settings{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:10px 12px 12px}.templates__field{display:flex;flex-direction:column;gap:4px;min-width:0}.templates__field span{font-size:12px;color:var(--color-neutral-600)}.templates__field .nvb-control__text-input{height:36px;min-height:36px}.templates__dirty-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--color-caution-500)}.templates__card--preview{flex:1 1 auto}.templates__preview{flex:1 1 auto;min-height:0;padding:16px;overflow:auto;background-color:color-mix(in oklab,var(--color-neutral-050) 60%,var(--color-neutral-000));background-image:radial-gradient(var(--color-neutral-200) 1px,transparent 1px);background-size:16px 16px}.templates__preview-card{all:unset;display:block;width:100%}.templates__preview-placeholder{color:var(--color-neutral-500);font-size:13px}.templates__preview-data{flex:0 0 auto;display:flex;flex-direction:column}.templates__preview-data-editor{height:120px;padding:8px;overflow:hidden}.templates__placeholder{flex:1 1 auto;display:flex;align-items:center;justify-content:center;color:var(--color-neutral-500);font-size:13px;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow)}.templates__alert{flex:0 0 auto;margin:8px;padding:8px 10px;border:1px solid color-mix(in oklab,var(--color-risk-500) 35%,transparent);border-radius:var(--radius-md);background:color-mix(in oklab,var(--color-risk-050) 70%,var(--color-surface-mix-target, white));color:var(--color-risk-700);font-size:12px;line-height:1.4}@media(max-width:1000px){.templates{grid-template-columns:1fr;grid-template-rows:minmax(220px,40%) minmax(0,1fr)}.templates__sidebar{width:auto!important;max-width:none}.templates__resize-handle{display:none}.templates__workspace{grid-template-columns:1fr;overflow:auto}.templates__workspace-panel{overflow:visible}.templates__workspace-panel--content .templates__card--editor{flex:1 1 auto;min-height:220px}.templates__card--preview .templates__preview{min-height:160px}.templates__settings{grid-template-columns:1fr}}\n'],dependencies:[{kind:"ngmodule",type:y},{kind:"directive",type:g.DefaultValueAccessor,selector:"input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"},{kind:"directive",type:g.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:g.NgModel,selector:"[ngModel]:not([formControlName]):not([formControl])",inputs:["name","disabled","ngModel","ngModelOptions"],outputs:["ngModelChange"],exportAs:["ngModel"]},{kind:"component",type:f,selector:"native-code-editor",inputs:["value","language","theme","readOnly","completions"],outputs:["valueChange","editorBlur"]},{kind:"component",type:x,selector:"nvb-style-outlet",inputs:["cssText","renderInPlace","styleKey"]},{kind:"component",type:b,selector:"builder-action-button",inputs:["label","ariaLabel","testId","title","icon","className","tone","size","align","active","fullWidth","disabled"],outputs:["clicked"]},{kind:"component",type:w,selector:"builder-icon-button",inputs:["icon","ariaLabel","title","testId","iconSet","size","tone","ghost","dragHandle","active","disabled","className"],outputs:["clicked"]},{kind:"pipe",type:T,name:"uiT"}],changeDetection:l.ChangeDetectionStrategy.Eager})}l.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"22.1.4",ngImport:l,type:i,decorators:[{type:R,args:[{selector:"templates-editor",imports:[y,T,f,x,b,w],changeDetection:B.Eager,template:"<div class=\"templates\" data-testid=\"templates-editor-root\">\n <aside class=\"templates__sidebar\" #sidebar>\n <header class=\"templates__sidebar-header\">\n <h3 class=\"templates__sidebar-title\">\n {{ 'templates.title' | uiT: 'Templates' }}\n @if (hasTemplates()) {\n <span class=\"templates__count\">{{ templates().length }}</span>\n }\n </h3>\n <builder-icon-button\n icon=\"add\"\n size=\"sm\"\n testId=\"templates-add\"\n [ariaLabel]=\"'templates.add' | uiT: 'Add template'\"\n [title]=\"'templates.add' | uiT: 'Add template'\"\n (clicked)=\"addTemplate()\"\n />\n </header>\n\n @if (!hasTemplates()) {\n <div class=\"templates__empty\">\n <span>{{ 'templates.empty' | uiT: 'No templates yet.' }}</span>\n <builder-action-button\n tone=\"primary\"\n size=\"sm\"\n icon=\"add\"\n [label]=\"'templates.add' | uiT: 'Add template'\"\n testId=\"templates-add-empty\"\n (clicked)=\"addTemplate()\"\n />\n </div>\n } @else {\n <ul class=\"templates__list\">\n @for (template of templates(); track trackByName($index, template)) {\n <li\n class=\"templates__list-row\"\n [class.templates__list-row--active]=\"isSelected(template.name)\"\n >\n <button\n type=\"button\"\n class=\"templates__item\"\n (click)=\"selectTemplate(template.name)\"\n [attr.data-testid]=\"'templates-item-' + template.name\"\n >\n <span class=\"templates__item-title\">{{ template.title || template.name }}</span>\n <span class=\"templates__item-name\">{{ template.name }}</span>\n </button>\n <builder-icon-button\n class=\"templates__item-delete\"\n [className]=\"'templates__item-delete'\"\n icon=\"delete\"\n tone=\"danger\"\n size=\"sm\"\n [ghost]=\"true\"\n (clicked)=\"deleteTemplate(template.name)\"\n [ariaLabel]=\"'templates.delete' | uiT: 'Delete'\"\n [title]=\"'templates.delete' | uiT: 'Delete'\"\n [testId]=\"'templates-delete-' + template.name\"\n />\n </li>\n }\n </ul>\n }\n\n <div\n class=\"templates__resize-handle\"\n data-testid=\"templates-sidebar-resize\"\n (mousedown)=\"onSidebarResizeMouseDown($event)\"\n ></div>\n </aside>\n\n <section class=\"templates__editor\">\n @if (draft(); as currentDraft) {\n <div class=\"templates__workspace\">\n <div class=\"templates__workspace-panel templates__workspace-panel--content\">\n <section class=\"templates__card templates__card--editor\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.content' | uiT: 'Template (Angular syntax)' }}\n </h3>\n <builder-icon-button\n icon=\"code_blocks\"\n size=\"xs\"\n [ghost]=\"true\"\n testId=\"templates-format\"\n [ariaLabel]=\"'templates.format' | uiT: 'Format template'\"\n [title]=\"'templates.format' | uiT: 'Format template'\"\n (clicked)=\"formatTemplateContent()\"\n />\n </header>\n <div class=\"templates__card-body\">\n <native-code-editor\n language=\"template\"\n [theme]=\"theme()\"\n [value]=\"currentDraft.content || ''\"\n (valueChange)=\"updateDraftField('content', $event)\"\n (editorBlur)=\"onTemplateContentBlur($event)\"\n data-testid=\"templates-content\"\n />\n </div>\n @if (formatError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ formatError() | uiT: formatError() }}\n </div>\n }\n </section>\n\n <section class=\"templates__card templates__card--editor\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">{{ 'templates.css' | uiT: 'CSS classes' }}</h3>\n <builder-icon-button\n icon=\"code_blocks\"\n size=\"xs\"\n [ghost]=\"true\"\n testId=\"templates-css-format\"\n [ariaLabel]=\"'templates.css.format' | uiT: 'Format CSS'\"\n [title]=\"'templates.css.format' | uiT: 'Format CSS'\"\n (clicked)=\"formatTemplateCss()\"\n />\n </header>\n <div class=\"templates__card-body\">\n <native-code-editor\n language=\"css\"\n [theme]=\"theme()\"\n [value]=\"currentDraft.css || ''\"\n (valueChange)=\"updateDraftField('css', $event)\"\n (editorBlur)=\"onTemplateCssBlur($event)\"\n data-testid=\"templates-css\"\n />\n </div>\n @if (cssFormatError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ cssFormatError() | uiT: cssFormatError() }}\n </div>\n }\n </section>\n </div>\n\n <div class=\"templates__workspace-panel\">\n <section class=\"templates__card templates__card--settings\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.settings' | uiT: 'Template settings' }}\n </h3>\n @if (isDirty()) {\n <span\n class=\"templates__dirty-dot\"\n [title]=\"'templates.unsaved' | uiT: 'Unsaved changes'\"\n ></span>\n }\n <builder-action-button\n tone=\"primary\"\n size=\"sm\"\n [label]=\"'templates.save' | uiT: 'Save'\"\n testId=\"templates-save\"\n (clicked)=\"saveDraft()\"\n />\n </header>\n <div class=\"templates__settings\">\n <label class=\"templates__field\">\n <span>{{ 'templates.name' | uiT: 'Name' }}</span>\n <input\n type=\"text\"\n class=\"nvb-control__text-input\"\n [ngModel]=\"currentDraft.name\"\n (ngModelChange)=\"updateDraftField('name', $event)\"\n data-testid=\"templates-name\"\n />\n </label>\n\n <label class=\"templates__field\">\n <span>{{ 'templates.titleField' | uiT: 'Title' }}</span>\n <input\n type=\"text\"\n class=\"nvb-control__text-input\"\n [ngModel]=\"currentDraft.title\"\n (ngModelChange)=\"updateDraftField('title', $event)\"\n data-testid=\"templates-title\"\n />\n </label>\n </div>\n @if (error()) {\n <div class=\"templates__alert\" role=\"alert\" data-testid=\"templates-error\">\n {{ error() | uiT: error() }}\n </div>\n }\n </section>\n\n <section class=\"templates__card templates__card--preview\">\n <header class=\"templates__card-header\">\n <h3 class=\"templates__card-title\">\n {{ 'templates.previewTitle' | uiT: 'Preview' }}\n </h3>\n </header>\n <div class=\"templates__preview\" data-testid=\"templates-preview\">\n @if (previewTemplateCss()) {\n <nvb-style-outlet [cssText]=\"previewTemplateCss()\"></nvb-style-outlet>\n }\n @if (!previewCard()) {\n <div class=\"templates__preview-placeholder\">\n {{ 'templates.preview.empty' | uiT: 'No preview object.' }}\n </div>\n } @else {\n <article\n class=\"templates__preview-card\"\n [attr.data-nvb-template-scope]=\"previewTemplateScopeId\"\n [innerHTML]=\"previewCard()\"\n (click)=\"onPreviewTemplateClick($event, previewItem()!, 0)\"\n ></article>\n }\n </div>\n <div class=\"templates__preview-data\">\n <header class=\"templates__card-header templates__card-header--sub\">\n <h4 class=\"templates__card-title\">\n {{ 'templates.previewData' | uiT: 'Preview data (JSON)' }}\n </h4>\n </header>\n <div class=\"templates__preview-data-editor\">\n <native-code-editor\n language=\"json\"\n [theme]=\"theme()\"\n [value]=\"previewItemsJson()\"\n (valueChange)=\"previewItemsJson.set($event)\"\n data-testid=\"templates-preview-data\"\n />\n </div>\n @if (previewJsonError()) {\n <div class=\"templates__alert\" role=\"alert\">\n {{ previewJsonError() | uiT: 'Preview data must be a valid JSON object.' }}\n </div>\n }\n </div>\n </section>\n </div>\n </div>\n } @else {\n <div class=\"templates__placeholder\">\n {{ 'templates.select' | uiT: 'Select a template to edit.' }}\n </div>\n }\n </section>\n</div>\n",styles:['@charset "UTF-8";:host{display:block;height:100%;overflow:hidden;background:color-mix(in oklab,var(--color-neutral-050) 84%,white)}.templates{height:100%;min-height:0;display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;overflow:hidden}.templates__card{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow);overflow:hidden}.templates__card-header{flex:0 0 auto;display:flex;align-items:center;gap:8px;min-height:36px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--color-neutral-200);background:var(--nvb-card-bg)}.templates__card-header--sub{border-top:1px solid var(--color-neutral-200);border-bottom:none;min-height:30px}.templates__card-title{flex:1 1 auto;margin:0;font-size:12px;font-weight:600;line-height:16px;color:var(--color-neutral-700)}.templates__card-header--sub .templates__card-title{color:var(--color-neutral-600)}.templates__card-body{flex:1 1 auto;min-height:0;padding:8px;overflow:hidden}.templates__sidebar{position:relative;display:flex;flex-direction:column;width:280px;min-width:200px;max-width:400px;min-height:0;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow);overflow:hidden}.templates__sidebar-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:36px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--color-neutral-200);background:var(--nvb-card-bg)}.templates__sidebar-title{display:inline-flex;align-items:center;gap:6px;margin:0;font-size:12px;font-weight:600;line-height:16px;color:var(--color-neutral-700)}.templates__count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:16px;padding:0 5px;border-radius:999px;background:var(--color-neutral-100);color:var(--color-neutral-600);font-size:10px;font-weight:600;letter-spacing:0}.templates__resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;z-index:10;cursor:ew-resize;background-color:transparent}.templates__resize-handle:after{content:"";position:absolute;top:50%;right:2px;width:2px;height:40px;transform:translateY(-50%);border-radius:999px;background-color:var(--color-neutral-300);opacity:0;transition:opacity .15s ease,background-color .15s ease}.templates__resize-handle:hover:after{opacity:1;background-color:var(--color-primary-500)}.templates__list{list-style:none;margin:0;padding:6px;display:grid;gap:2px;align-content:start;grid-auto-rows:min-content;flex:1 1 auto;min-height:0;overflow:auto}.templates__list-row{position:relative;display:flex;align-items:center;min-width:0}.templates__item{flex:1 1 auto;min-width:0;display:grid;gap:1px;text-align:left;border:none;border-left:2px solid transparent;border-radius:var(--radius-md);background:transparent;padding:6px 34px 6px 10px;cursor:pointer;font:inherit;transition:background-color .14s ease,border-color .14s ease}.templates__item-title{font-size:13px;font-weight:600;color:var(--color-neutral-700);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.templates__item-name{font-size:11px;color:var(--color-neutral-400);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.templates__item:hover{background:color-mix(in oklab,var(--color-neutral-100) 70%,var(--color-neutral-000))}.templates__list-row--active .templates__item{border-left-color:var(--color-primary-500);background:color-mix(in oklab,var(--color-primary-500) 8%,var(--color-surface-mix-target, white))}.templates__list-row--active .templates__item-title{color:var(--color-primary-700)}.templates__item-delete{position:absolute;top:50%;right:6px;transform:translateY(-50%);opacity:0;transition:opacity .14s ease}.templates__list-row:hover .templates__item-delete,.templates__list-row:focus-within .templates__item-delete,.templates__list-row--active .templates__item-delete{opacity:1}.templates__empty{flex:1 1 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:16px;color:var(--color-neutral-500);font-size:13px;text-align:center}.templates__editor{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}.templates__workspace{display:grid;grid-template-columns:minmax(0,1fr) minmax(340px,.82fr);gap:10px;flex:1 1 auto;min-height:0;overflow:hidden}.templates__workspace-panel{min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px;overflow:hidden}.templates__workspace-panel--content .templates__card--editor{flex:1 1 0}.templates__card--settings{flex:0 0 auto;--nvb-input-height: 36px;--nvb-input-font-size: 13px}.templates__settings{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:10px 12px 12px}.templates__field{display:flex;flex-direction:column;gap:4px;min-width:0}.templates__field span{font-size:12px;color:var(--color-neutral-600)}.templates__field .nvb-control__text-input{height:36px;min-height:36px}.templates__dirty-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--color-caution-500)}.templates__card--preview{flex:1 1 auto}.templates__preview{flex:1 1 auto;min-height:0;padding:16px;overflow:auto;background-color:color-mix(in oklab,var(--color-neutral-050) 60%,var(--color-neutral-000));background-image:radial-gradient(var(--color-neutral-200) 1px,transparent 1px);background-size:16px 16px}.templates__preview-card{all:unset;display:block;width:100%}.templates__preview-placeholder{color:var(--color-neutral-500);font-size:13px}.templates__preview-data{flex:0 0 auto;display:flex;flex-direction:column}.templates__preview-data-editor{height:120px;padding:8px;overflow:hidden}.templates__placeholder{flex:1 1 auto;display:flex;align-items:center;justify-content:center;color:var(--color-neutral-500);font-size:13px;background:var(--nvb-card-bg);border:1px solid var(--nvb-card-border-color);border-radius:var(--nvb-card-radius);box-shadow:var(--nvb-card-shadow)}.templates__alert{flex:0 0 auto;margin:8px;padding:8px 10px;border:1px solid color-mix(in oklab,var(--color-risk-500) 35%,transparent);border-radius:var(--radius-md);background:color-mix(in oklab,var(--color-risk-050) 70%,var(--color-surface-mix-target, white));color:var(--color-risk-700);font-size:12px;line-height:1.4}@media(max-width:1000px){.templates{grid-template-columns:1fr;grid-template-rows:minmax(220px,40%) minmax(0,1fr)}.templates__sidebar{width:auto!important;max-width:none}.templates__resize-handle{display:none}.templates__workspace{grid-template-columns:1fr;overflow:auto}.templates__workspace-panel{overflow:visible}.templates__workspace-panel--content .templates__card--editor{flex:1 1 auto;min-height:220px}.templates__card--preview .templates__preview{min-height:160px}.templates__settings{grid-template-columns:1fr}}\n']}]}],propDecorators:{sidebarRef:[{type:F,args:["sidebar"]}],theme:[{type:l.Input,args:[{isSignal:true,alias:"theme",required:false}]}]}});function L(){return N({featurePacks:[{id:M,title:"Templates",capabilities:[I],builderTabs:[{id:z.TEMPLATES,label:"tab.templates",component:i,order:350}]}]})}export{H as NGX_VIEW_BUILDER_TEMPLATES_CAPABILITY,j as NGX_VIEW_BUILDER_TEMPLATES_FEATURE_PACK_ID,L as provideNgxViewBuilderTemplates};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@ngxviewbuilder/plugin-templates",
3
+ "version": "0.6.0",
4
+ "description": "Templates plugin for the NGX View Builder designer: save a view section as a reusable template and drop it into other views.",
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": "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
+ "templates",
20
+ "plugin",
21
+ "low-code"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "peerDependencies": {
27
+ "@angular/cdk": "^22.0.0",
28
+ "@angular/common": "^22.0.0",
29
+ "@angular/core": "^22.0.0",
30
+ "@ngxviewbuilder/designer": "^0.6.0"
31
+ },
32
+ "dependencies": {
33
+ "tslib": "^2.6.0",
34
+ "prettier": "^3.6.2"
35
+ },
36
+ "module": "fesm2022/ngxviewbuilder-plugin-templates.mjs",
37
+ "typings": "types/ngxviewbuilder-plugin-templates.d.ts",
38
+ "exports": {
39
+ "./package.json": {
40
+ "default": "./package.json"
41
+ },
42
+ ".": {
43
+ "types": "./types/ngxviewbuilder-plugin-templates.d.ts",
44
+ "default": "./fesm2022/ngxviewbuilder-plugin-templates.mjs"
45
+ }
46
+ },
47
+ "sideEffects": false,
48
+ "type": "module"
49
+ }
@@ -0,0 +1,6 @@
1
+ import { EnvironmentProviders } from '@angular/core';
2
+ export { NGX_VIEW_BUILDER_TEMPLATES_CAPABILITY, NGX_VIEW_BUILDER_TEMPLATES_FEATURE_PACK_ID } from '@ngxviewbuilder/designer';
3
+
4
+ declare function provideNgxViewBuilderTemplates(): EnvironmentProviders;
5
+
6
+ export { provideNgxViewBuilderTemplates };