@ngstarter-ui/components 21.0.40 → 21.0.42

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.
Files changed (24) hide show
  1. package/ai/component-registry.json +54 -14
  2. package/fesm2022/ngstarter-ui-components-country-select.mjs +59 -16
  3. package/fesm2022/ngstarter-ui-components-country-select.mjs.map +1 -1
  4. package/fesm2022/ngstarter-ui-components-form-builder.mjs +725 -291
  5. package/fesm2022/ngstarter-ui-components-form-builder.mjs.map +1 -1
  6. package/fesm2022/{ngstarter-ui-components-form-renderer-radio-group-field-lc1V-NeL.mjs → ngstarter-ui-components-form-renderer-radio-group-field-DugkPKYU.mjs} +2 -2
  7. package/fesm2022/{ngstarter-ui-components-form-renderer-radio-group-field-lc1V-NeL.mjs.map → ngstarter-ui-components-form-renderer-radio-group-field-DugkPKYU.mjs.map} +1 -1
  8. package/fesm2022/ngstarter-ui-components-form-renderer.mjs +1 -1
  9. package/fesm2022/ngstarter-ui-components-radio.mjs +8 -5
  10. package/fesm2022/ngstarter-ui-components-radio.mjs.map +1 -1
  11. package/fesm2022/ngstarter-ui-components-scrollbar-area.mjs +2 -1
  12. package/fesm2022/ngstarter-ui-components-scrollbar-area.mjs.map +1 -1
  13. package/fesm2022/ngstarter-ui-components-slide-toggle.mjs +12 -1
  14. package/fesm2022/ngstarter-ui-components-slide-toggle.mjs.map +1 -1
  15. package/fesm2022/ngstarter-ui-components-tabs.mjs +2 -2
  16. package/fesm2022/ngstarter-ui-components-tabs.mjs.map +1 -1
  17. package/fesm2022/ngstarter-ui-components-tree.mjs +7 -5
  18. package/fesm2022/ngstarter-ui-components-tree.mjs.map +1 -1
  19. package/package.json +1 -1
  20. package/types/ngstarter-ui-components-country-select.d.ts +20 -9
  21. package/types/ngstarter-ui-components-form-builder.d.ts +114 -21
  22. package/types/ngstarter-ui-components-radio.d.ts +4 -1
  23. package/types/ngstarter-ui-components-slide-toggle.d.ts +6 -1
  24. package/types/ngstarter-ui-components-tree.d.ts +4 -2
@@ -1,14 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, input, signal, computed, viewChild, ViewContainerRef, effect, ChangeDetectionStrategy, Component, inject, model, output, ElementRef } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, input, signal, computed, viewChild, ViewContainerRef, effect, ChangeDetectionStrategy, Component, inject, model, output, DestroyRef, ElementRef } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { Validators, ReactiveFormsModule, FormControl, FormGroup, FormsModule } from '@angular/forms';
5
- import { moveItemInArray, transferArrayItem, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDragPreview, CdkDropList, CdkDropListGroup } from '@angular/cdk/drag-drop';
6
5
  import { NgTemplateOutlet } from '@angular/common';
7
6
  import { Button } from '@ngstarter-ui/components/button';
8
7
  import { Card, CardAside, CardContent, CardHeader } from '@ngstarter-ui/components/card';
9
8
  import { ConfirmManager } from '@ngstarter-ui/components/confirm';
10
9
  import { Dialog, DialogActions, DialogClose, DialogContent, DialogTitle } from '@ngstarter-ui/components/dialog';
11
- import { FormField, Hint, IconButtonSuffix, Label } from '@ngstarter-ui/components/form-field';
12
10
  import { Icon } from '@ngstarter-ui/components/icon';
13
11
  import { Input } from '@ngstarter-ui/components/input';
14
12
  import { Panel, PanelAside, PanelContent, PanelHeader, PanelSidebar } from '@ngstarter-ui/components/panel';
@@ -16,21 +14,36 @@ import { ScrollbarArea } from '@ngstarter-ui/components/scrollbar-area';
16
14
  import { Tab, TabGroup } from '@ngstarter-ui/components/tabs';
17
15
  import { Tree, TreeDragPlaceholder, TreeNode, TreeNodeDef, TreeNodePadding } from '@ngstarter-ui/components/tree';
18
16
  import { Datepicker, DatepickerInput, DatepickerToggle, provideNativeDateAdapter } from '@ngstarter-ui/components/datepicker';
17
+ import { FormField, Hint, IconButtonSuffix, Label, Error } from '@ngstarter-ui/components/form-field';
19
18
  import { Select } from '@ngstarter-ui/components/select';
20
19
  import { Option } from '@ngstarter-ui/components/option';
21
20
  import { Checkbox } from '@ngstarter-ui/components/checkbox';
22
- import { SlideToggle } from '@ngstarter-ui/components/slide-toggle';
21
+ import { SlideToggle, SlideToggleGroup } from '@ngstarter-ui/components/slide-toggle';
22
+ import { RadioButton, RadioGroup } from '@ngstarter-ui/components/radio';
23
+ import { CountrySelect } from '@ngstarter-ui/components/country-select';
24
+ import { CurrencySelect } from '@ngstarter-ui/components/currency-select';
25
+ import { TimezoneSelect } from '@ngstarter-ui/components/timezone-select';
26
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
27
 
28
+ const FORM_BUILDER_ITEMS = new InjectionToken('FORM_BUILDER_ITEMS');
24
29
  const FORM_BUILDER_FIELDS = new InjectionToken('FORM_BUILDER_FIELDS');
25
30
  const FORM_BUILDER_SETTINGS = new InjectionToken('FORM_BUILDER_SETTINGS');
26
31
  function formBuilderField(definition) {
27
32
  return definition;
28
33
  }
34
+ function formBuilderItem(definition) {
35
+ return definition;
36
+ }
29
37
  function formBuilderSettings(definition) {
30
38
  return definition;
31
39
  }
32
40
  function provideFormBuilder(config = {}) {
33
41
  return makeEnvironmentProviders([
42
+ ...(config.items ?? []).map(item => ({
43
+ provide: FORM_BUILDER_ITEMS,
44
+ useValue: item,
45
+ multi: true
46
+ })),
34
47
  ...(config.fields ?? []).map(field => ({
35
48
  provide: FORM_BUILDER_FIELDS,
36
49
  useValue: field,
@@ -87,13 +100,16 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
87
100
  }
88
101
  },
89
102
  {
90
- type: 'grid',
91
- label: 'Grid',
103
+ type: 'group',
104
+ label: 'Group',
105
+ kind: 'layout',
92
106
  group: 'Layout',
93
- icon: 'fluent:grid-24-regular',
107
+ icon: 'fluent:group-24-regular',
94
108
  description: 'Container for nested fields.',
109
+ acceptsChildren: true,
95
110
  defaults: {
96
- label: 'Grid',
111
+ kind: 'layout',
112
+ label: 'Group',
97
113
  width: 12,
98
114
  children: []
99
115
  }
@@ -106,6 +122,23 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
106
122
  defaults: {
107
123
  label: 'Select',
108
124
  placeholder: 'Choose an option',
125
+ clearable: true,
126
+ options: [
127
+ { label: 'Option 1', value: 'option_1' },
128
+ { label: 'Option 2', value: 'option_2' }
129
+ ]
130
+ }
131
+ },
132
+ {
133
+ type: 'radio',
134
+ label: 'Radio',
135
+ group: 'Choices',
136
+ icon: 'fluent:radio-button-24-regular',
137
+ defaults: {
138
+ label: 'Radio',
139
+ settings: {
140
+ orientation: 'vertical'
141
+ },
109
142
  options: [
110
143
  { label: 'Option 1', value: 'option_1' },
111
144
  { label: 'Option 2', value: 'option_2' }
@@ -142,6 +175,16 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
142
175
  placeholder: 'Select date'
143
176
  }
144
177
  },
178
+ {
179
+ type: 'timezone-select',
180
+ label: 'Timezone select',
181
+ group: 'Date and time',
182
+ icon: 'fluent:globe-clock-24-regular',
183
+ defaults: {
184
+ label: 'Timezone',
185
+ placeholder: 'Select timezone'
186
+ }
187
+ },
145
188
  {
146
189
  type: 'currency',
147
190
  label: 'Currency',
@@ -151,8 +194,41 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
151
194
  label: 'Amount',
152
195
  placeholder: '0.00'
153
196
  }
197
+ },
198
+ {
199
+ type: 'currency-select',
200
+ label: 'Currency select',
201
+ group: 'Finance',
202
+ icon: 'fluent:money-hand-24-regular',
203
+ defaults: {
204
+ label: 'Currency',
205
+ placeholder: 'Select currency'
206
+ }
207
+ },
208
+ {
209
+ type: 'country-select',
210
+ label: 'Country select',
211
+ group: 'Location',
212
+ icon: 'fluent:globe-location-24-regular',
213
+ defaults: {
214
+ label: 'Country',
215
+ placeholder: 'Select country',
216
+ clearable: true
217
+ }
154
218
  }
155
219
  ];
220
+ const DEFAULT_FORM_BUILDER_ITEMS = [
221
+ {
222
+ type: 'section',
223
+ label: 'Section',
224
+ kind: 'layout',
225
+ group: 'Layout',
226
+ icon: 'fluent:folder-24-regular',
227
+ description: 'Top-level layout container.',
228
+ acceptsChildren: true
229
+ },
230
+ ...DEFAULT_FORM_BUILDER_FIELDS
231
+ ];
156
232
  function validatorsFromRules(rules = [], field) {
157
233
  const validators = [];
158
234
  if (field?.required || rules.some(rule => rule.type === 'required')) {
@@ -189,6 +265,7 @@ class FormBuilderFieldHost {
189
265
  const type = this.field().type;
190
266
  return type === 'number' || type === 'email' ? type : 'text';
191
267
  }, ...(ngDevMode ? [{ debugName: "textInputType" }] : /* istanbul ignore next */ []));
268
+ radioOrientation = computed(() => this.field().settings?.['orientation'] === 'horizontal' ? 'horizontal' : 'vertical', ...(ngDevMode ? [{ debugName: "radioOrientation" }] : /* istanbul ignore next */ []));
192
269
  anchor = viewChild.required('anchor', { read: ViewContainerRef });
193
270
  constructor() {
194
271
  effect(async () => {
@@ -207,13 +284,14 @@ class FormBuilderFieldHost {
207
284
  componentRef.setInput('field', field);
208
285
  componentRef.setInput('control', control);
209
286
  componentRef.setInput('readonly', this.readonly());
287
+ componentRef.setInput('definition', definitions.find(definition => definition.type === field.type));
210
288
  this.customLoaded.set(true);
211
289
  });
212
290
  }
213
291
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
214
292
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderFieldHost, isStandalone: true, selector: "ngs-form-builder-field-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, editableCanvas: { classPropertyName: "editableCanvas", publicName: "editableCanvas", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-custom": "customLoaded()", "class.is-width-1": "!editableCanvas() && field().width === 1", "class.is-width-2": "!editableCanvas() && field().width === 2", "class.is-width-3": "!editableCanvas() && field().width === 3", "class.is-width-4": "!editableCanvas() && field().width === 4", "class.is-width-5": "!editableCanvas() && field().width === 5", "class.is-width-6": "!editableCanvas() && field().width === 6", "class.is-width-7": "!editableCanvas() && field().width === 7", "class.is-width-8": "!editableCanvas() && field().width === 8", "class.is-width-9": "!editableCanvas() && field().width === 9", "class.is-width-10": "!editableCanvas() && field().width === 10", "class.is-width-11": "!editableCanvas() && field().width === 11", "class.is-width-12": "!editableCanvas() && (field().width ?? 12) === 12" }, classAttribute: "ngs-form-builder-field-host" }, providers: [
215
293
  provideNativeDateAdapter()
216
- ], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderFieldHost"], ngImport: i0, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\" [placeholder]=\"field().placeholder || ''\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Datepicker, selector: "ngs-datepicker", inputs: ["startAt", "calendarHeaderComponent", "quickPresets", "showQuickPresets"], exportAs: ["ngsDatepicker"] }, { kind: "directive", type: DatepickerInput, selector: "input[ngsDatepicker]", inputs: ["ngsDatepicker"], exportAs: ["ngsDatepickerInput"] }, { kind: "component", type: DatepickerToggle, selector: "ngs-datepicker-toggle", inputs: ["for"], exportAs: ["ngsDatepickerToggle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Checkbox, selector: "ngs-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["checkedChange", "disabledChange", "indeterminateChange", "change"], exportAs: ["ngsCheckbox"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
294
+ ], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderFieldHost"], ngImport: i0, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [multiple]=\"field().multiple ?? false\"\n [clearable]=\"field().clearable ?? false\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('radio') {\n <div class=\"ngs-form-builder-choice-field\">\n <span class=\"ngs-form-builder-choice-label\">{{ field().label }}</span>\n <ngs-radio-group [formControl]=\"control()\"\n [name]=\"field().name\"\n [orientation]=\"radioOrientation()\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-radio-button [value]=\"option.value\">{{ option.label }}</ngs-radio-button>\n }\n </ngs-radio-group>\n @if (field().hint) {\n <span class=\"ngs-form-builder-choice-hint\">{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('timezone-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-timezone-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-currency-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('country-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-country-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [clearable]=\"field().clearable ?? false\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}:host .ngs-form-builder-choice-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-choice-label{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:500}:host .ngs-form-builder-choice-hint{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Datepicker, selector: "ngs-datepicker", inputs: ["startAt", "calendarHeaderComponent", "quickPresets", "showQuickPresets"], exportAs: ["ngsDatepicker"] }, { kind: "directive", type: DatepickerInput, selector: "input[ngsDatepicker]", inputs: ["ngsDatepicker"], exportAs: ["ngsDatepickerInput"] }, { kind: "component", type: DatepickerToggle, selector: "ngs-datepicker-toggle", inputs: ["for"], exportAs: ["ngsDatepickerToggle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Checkbox, selector: "ngs-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["checkedChange", "disabledChange", "indeterminateChange", "change"], exportAs: ["ngsCheckbox"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: RadioButton, selector: "ngs-radio-button", inputs: ["id", "value", "name", "checked", "disabled"], outputs: ["checkedChange", "change"] }, { kind: "component", type: RadioGroup, selector: "ngs-radio-group", inputs: ["disabled", "name", "orientation", "value"], outputs: ["disabledChange", "valueChange", "change"], exportAs: ["ngsRadioGroup"] }, { kind: "component", type: CountrySelect, selector: "ngs-country-select", inputs: ["searchTerm", "value", "id", "placeholder", "required", "disabled", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "showCountryCode"], outputs: ["searchTermChange", "valueChange", "requiredChange", "disabledChange", "opened", "closed", "selectionChange"], exportAs: ["ngsCountrySelect"] }, { kind: "component", type: CurrencySelect, selector: "ngs-currency-select", inputs: ["searchTerm", "placeholder", "required", "disabled", "showCountryName"], outputs: ["searchTermChange", "requiredChange", "disabledChange", "opened", "closed"], exportAs: ["ngsCurrencySelect"] }, { kind: "component", type: TimezoneSelect, selector: "ngs-timezone-select", inputs: ["aria-describedby", "locale", "placeholder", "required", "disabled", "value", "searchTerm"], outputs: ["valueChange", "searchTermChange", "opened", "closed"], exportAs: ["ngsTimezoneSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
217
295
  }
218
296
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, decorators: [{
219
297
  type: Component,
@@ -230,7 +308,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
230
308
  Select,
231
309
  Option,
232
310
  Checkbox,
233
- SlideToggle
311
+ SlideToggle,
312
+ RadioButton,
313
+ RadioGroup,
314
+ CountrySelect,
315
+ CurrencySelect,
316
+ TimezoneSelect
234
317
  ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
235
318
  provideNativeDateAdapter()
236
319
  ], host: {
@@ -248,11 +331,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
248
331
  '[class.is-width-10]': '!editableCanvas() && field().width === 10',
249
332
  '[class.is-width-11]': '!editableCanvas() && field().width === 11',
250
333
  '[class.is-width-12]': '!editableCanvas() && (field().width ?? 12) === 12'
251
- }, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\" [placeholder]=\"field().placeholder || ''\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
334
+ }, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [multiple]=\"field().multiple ?? false\"\n [clearable]=\"field().clearable ?? false\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('radio') {\n <div class=\"ngs-form-builder-choice-field\">\n <span class=\"ngs-form-builder-choice-label\">{{ field().label }}</span>\n <ngs-radio-group [formControl]=\"control()\"\n [name]=\"field().name\"\n [orientation]=\"radioOrientation()\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-radio-button [value]=\"option.value\">{{ option.label }}</ngs-radio-button>\n }\n </ngs-radio-group>\n @if (field().hint) {\n <span class=\"ngs-form-builder-choice-hint\">{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('timezone-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-timezone-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-currency-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('country-select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-country-select [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [clearable]=\"field().clearable ?? false\"\n [required]=\"field().required ?? false\"/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}:host .ngs-form-builder-choice-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-choice-label{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:500}:host .ngs-form-builder-choice-hint{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
252
335
  }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], editableCanvas: [{ type: i0.Input, args: [{ isSignal: true, alias: "editableCanvas", required: false }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
253
336
 
254
337
  class FormBuilderRenderer {
338
+ providedItems = inject(FORM_BUILDER_ITEMS, { optional: true }) ?? [];
255
339
  providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
340
+ orphanControls = new Map();
256
341
  schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
257
342
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
258
343
  showSubmit = input(true, ...(ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []));
@@ -261,9 +346,27 @@ class FormBuilderRenderer {
261
346
  formSubmit = output();
262
347
  formReady = output();
263
348
  definitions = computed(() => [
264
- ...DEFAULT_FORM_BUILDER_FIELDS,
265
- ...this.providedFields
266
- ], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
349
+ ...DEFAULT_FORM_BUILDER_ITEMS,
350
+ ...this.providedFields,
351
+ ...this.providedItems
352
+ ].reduce((definitions, definition) => {
353
+ const normalized = normalizeFieldDefinition$1(definition);
354
+ const index = definitions.findIndex(item => item.type === normalized.type);
355
+ if (index === -1) {
356
+ definitions.push(normalized);
357
+ }
358
+ else {
359
+ definitions[index] = {
360
+ ...definitions[index],
361
+ ...normalized,
362
+ defaults: {
363
+ ...definitions[index].defaults,
364
+ ...normalized.defaults
365
+ }
366
+ };
367
+ }
368
+ return definitions;
369
+ }, []), ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
267
370
  visibleCanvasItems = computed(() => this.resolveCanvasItems(this.schema())
268
371
  .map(item => {
269
372
  if (item.field) {
@@ -293,10 +396,28 @@ class FormBuilderRenderer {
293
396
  });
294
397
  }
295
398
  getControl(field) {
296
- return this.formGroup().get(field.name);
399
+ const control = this.formGroup().get(field.name);
400
+ if (control instanceof FormControl) {
401
+ return control;
402
+ }
403
+ const existing = this.orphanControls.get(field.id);
404
+ if (existing) {
405
+ return existing;
406
+ }
407
+ const nextControl = new FormControl({
408
+ value: this.fieldInitialValue(field),
409
+ disabled: true
410
+ });
411
+ this.orphanControls.set(field.id, nextControl);
412
+ return nextControl;
297
413
  }
298
414
  isContainerField(field) {
299
- return field.type === 'grid';
415
+ const definition = this.definitions().find(item => item.type === field.type);
416
+ return definition?.acceptsChildren === true ||
417
+ definition?.kind === 'layout' ||
418
+ field.kind === 'layout' ||
419
+ field.type === 'group' ||
420
+ field.type === 'grid';
300
421
  }
301
422
  visibleChildren(field) {
302
423
  return this.visibleFields(field.children ?? []);
@@ -317,13 +438,13 @@ class FormBuilderRenderer {
317
438
  ...this.schema().sections.flatMap(section => flattenFields(section.fields))
318
439
  ];
319
440
  for (const field of fields) {
320
- if (this.isContainerField(field)) {
441
+ const definition = this.definitions().find(item => item.type === field.type);
442
+ if (this.isContainerField(field) || definition?.kind === 'static' || field.kind === 'static') {
321
443
  continue;
322
444
  }
323
- const definition = this.definitions().find(item => item.type === field.type);
324
445
  const validators = definition?.validators?.(field) ?? validatorsFromRules(field.validation, field);
325
446
  const control = new FormControl({
326
- value: value[field.name] ?? field.defaultValue ?? null,
447
+ value: value[field.name] ?? this.fieldInitialValue(field),
327
448
  disabled: field.disabled || this.readonly()
328
449
  }, validators);
329
450
  controls[field.name] = control;
@@ -338,6 +459,18 @@ class FormBuilderRenderer {
338
459
  children: field.children ? this.visibleFields(field.children) : undefined
339
460
  }));
340
461
  }
462
+ fieldInitialValue(field) {
463
+ if (field.defaultValue !== undefined) {
464
+ return field.defaultValue;
465
+ }
466
+ const selectedValues = (field.options ?? [])
467
+ .filter(option => option.selected)
468
+ .map(option => option.value);
469
+ if (field.type === 'checkbox-list' || field.multiple) {
470
+ return selectedValues;
471
+ }
472
+ return selectedValues[0] ?? null;
473
+ }
341
474
  resolveCanvasItems(schema) {
342
475
  const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
343
476
  const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
@@ -371,6 +504,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
371
504
  'class': 'ngs-form-builder-renderer'
372
505
  }, template: "<ng-template #renderFields let-fields>\n <div class=\"ngs-form-builder-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isContainerField(field)) {\n <section class=\"ngs-form-builder-renderer-grid-field\">\n <header>\n <h4>{{ field.label }}</h4>\n @if (field.hint) {\n <p>{{ field.hint }}</p>\n }\n </header>\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: visibleChildren(field) }\"/>\n </section>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"getControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"readonly()\"/>\n }\n }\n </div>\n</ng-template>\n\n<form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"flex flex-col gap-6\">\n @for (item of visibleCanvasItems(); track item.kind + ':' + item.id) {\n @if (item.field) {\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: [item.field] }\"/>\n } @else if (item.section; as section) {\n <section class=\"ngs-form-builder-renderer-section\">\n @if (section.title || section.description) {\n <header>\n @if (section.title) {\n <h3>{{ section.title }}</h3>\n }\n @if (section.description) {\n <p>{{ section.description }}</p>\n }\n </header>\n }\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: section.fields }\"/>\n </section>\n }\n }\n\n @if (showSubmit() && !readonly()) {\n <div class=\"flex justify-end\">\n <button ngsButton=\"filled\" type=\"submit\">{{ submitLabel() }}</button>\n </div>\n }\n </div>\n</form>\n", styles: [":host{display:block}:host .ngs-form-builder-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-builder-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-builder-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-grid-field{display:flex;grid-column:1/-1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-builder-renderer-grid-field>header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
373
506
  }], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], showSubmit: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSubmit", required: false }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], formSubmit: [{ type: i0.Output, args: ["formSubmit"] }], formReady: [{ type: i0.Output, args: ["formReady"] }] } });
507
+ function normalizeFieldDefinition$1(definition) {
508
+ return {
509
+ ...definition,
510
+ kind: definition.kind ?? 'field'
511
+ };
512
+ }
374
513
  function flattenFields(fields) {
375
514
  return fields.flatMap(field => [field, ...flattenFields(field.children ?? [])]);
376
515
  }
@@ -406,102 +545,313 @@ function normalizedLayout(schema) {
406
545
  return layout;
407
546
  }
408
547
 
409
- class FormBuilderSettingsHost {
410
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
411
- schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
412
- definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
413
- settingsDefinitions = input([], ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
414
- update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
415
- customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
416
- anchor = viewChild.required('anchor', { read: ViewContainerRef });
417
- constructor() {
418
- effect(async () => {
419
- const field = this.field();
420
- const definitions = this.definitions();
421
- const settingDefinitions = this.settingsDefinitions();
422
- const viewContainer = this.anchor();
423
- const fieldDefinition = definitions.find(definition => definition.type === field.type);
424
- const customSettings = fieldDefinition?.settings ??
425
- settingDefinitions.find(definition => definition.fieldType === field.type)?.component;
426
- viewContainer.clear();
427
- this.customLoaded.set(false);
428
- if (!customSettings) {
429
- return;
430
- }
431
- const componentType = await customSettings();
432
- const componentRef = viewContainer.createComponent(componentType);
433
- componentRef.setInput('field', field);
434
- componentRef.setInput('schema', this.schema());
435
- componentRef.setInput('update', this.update());
436
- this.customLoaded.set(true);
437
- });
438
- }
439
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
440
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.4", type: FormBuilderSettingsHost, isStandalone: true, selector: "ngs-form-builder-settings-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, settingsDefinitions: { classPropertyName: "settingsDefinitions", publicName: "settingsDefinitions", isSignal: true, isRequired: false, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.is-empty": "!customLoaded()" }, classAttribute: "ngs-form-builder-settings-host" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderSettingsHost"], ngImport: i0, template: "<ng-container #anchor/>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
441
- }
442
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
443
- type: Component,
444
- args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', changeDetection: ChangeDetectionStrategy.OnPush, host: {
445
- 'class': 'ngs-form-builder-settings-host',
446
- '[class.is-empty]': '!customLoaded()'
447
- }, template: "<ng-container #anchor/>\n" }]
448
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], settingsDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingsDefinitions", required: false }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
449
-
450
548
  class BasicFormBuilderFieldSettings {
549
+ destroyRef = inject(DestroyRef);
451
550
  field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
452
551
  update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
552
+ optionsControl = new FormControl('', {
553
+ nonNullable: true,
554
+ validators: optionsTextValidator
555
+ });
453
556
  fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
557
+ radioOrientationOptions = ['vertical', 'horizontal'];
558
+ hasCheckedState = computed(() => ['checkbox', 'toggle'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasCheckedState" }] : /* istanbul ignore next */ []));
559
+ hasPlaceholder = computed(() => !['checkbox', 'toggle', 'radio'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasPlaceholder" }] : /* istanbul ignore next */ []));
454
560
  hasOptions = computed(() => ['select', 'radio', 'checkbox-list'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
561
+ hasClearable = computed(() => ['select', 'country-select'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasClearable" }] : /* istanbul ignore next */ []));
562
+ isSelect = computed(() => this.field().type === 'select', ...(ngDevMode ? [{ debugName: "isSelect" }] : /* istanbul ignore next */ []));
563
+ isRadio = computed(() => this.field().type === 'radio', ...(ngDevMode ? [{ debugName: "isRadio" }] : /* istanbul ignore next */ []));
564
+ radioOrientation = computed(() => this.field().settings?.['orientation'] === 'horizontal' ? 'horizontal' : 'vertical', ...(ngDevMode ? [{ debugName: "radioOrientation" }] : /* istanbul ignore next */ []));
455
565
  optionsText = computed(() => (this.field().options ?? [])
456
- .map(option => `${option.label}:${option.value}`)
566
+ .map(option => `${option.label}:${option.value}${this.isOptionSelected(option) ? ':selected' : ''}`)
457
567
  .join('\n'), ...(ngDevMode ? [{ debugName: "optionsText" }] : /* istanbul ignore next */ []));
568
+ constructor() {
569
+ effect(() => {
570
+ const nextValue = this.optionsText();
571
+ if (this.optionsControl.value !== nextValue) {
572
+ this.optionsControl.setValue(nextValue, { emitEvent: false });
573
+ }
574
+ });
575
+ this.optionsControl.valueChanges
576
+ .pipe(takeUntilDestroyed(this.destroyRef))
577
+ .subscribe(value => {
578
+ if (this.optionsControl.invalid) {
579
+ return;
580
+ }
581
+ this.patchOptions(value);
582
+ });
583
+ }
458
584
  patch(changes) {
459
585
  this.update()(changes);
460
586
  }
587
+ patchSelectMultiple(multiple) {
588
+ const field = this.field();
589
+ const options = multiple ? field.options ?? [] : this.normalizeSelectedOptions(field.options ?? [], false);
590
+ const selectedValues = this.selectedOptionValues(options);
591
+ let defaultValue = null;
592
+ if (multiple) {
593
+ defaultValue = Array.isArray(field.defaultValue)
594
+ ? field.defaultValue
595
+ : field.defaultValue == null
596
+ ? selectedValues
597
+ : [field.defaultValue];
598
+ }
599
+ else {
600
+ defaultValue = Array.isArray(field.defaultValue)
601
+ ? field.defaultValue[0] ?? selectedValues[0] ?? null
602
+ : field.defaultValue ?? selectedValues[0] ?? null;
603
+ }
604
+ this.patch({ multiple, options, defaultValue });
605
+ }
606
+ patchRadioOrientation(orientation) {
607
+ this.patch({
608
+ settings: {
609
+ ...this.field().settings,
610
+ orientation
611
+ }
612
+ });
613
+ }
461
614
  patchOptions(value) {
462
- const options = value
615
+ const options = this.normalizeSelectedOptions(value
463
616
  .split('\n')
464
617
  .map(line => line.trim())
465
618
  .filter(Boolean)
466
619
  .map((line, index) => {
467
- const [label, optionValue] = line.split(':');
620
+ const parsed = parseOptionLine(line);
468
621
  return {
469
- label: label?.trim() || `Option ${index + 1}`,
470
- value: (optionValue ?? label ?? `option_${index + 1}`).trim()
622
+ label: parsed?.label || `Option ${index + 1}`,
623
+ value: parsed?.value || `option_${index + 1}`,
624
+ selected: parsed?.selected || undefined
471
625
  };
626
+ }));
627
+ this.patch({
628
+ options,
629
+ defaultValue: this.resolveSelectedDefaultValue(options)
472
630
  });
473
- this.patch({ options });
631
+ }
632
+ normalizeSelectedOptions(options, multiple = this.field().multiple) {
633
+ if (this.field().type === 'checkbox-list' || multiple) {
634
+ return options;
635
+ }
636
+ let selectedSeen = false;
637
+ return options.map(option => {
638
+ if (!option.selected) {
639
+ return option;
640
+ }
641
+ if (selectedSeen) {
642
+ return {
643
+ ...option,
644
+ selected: undefined
645
+ };
646
+ }
647
+ selectedSeen = true;
648
+ return option;
649
+ });
650
+ }
651
+ resolveSelectedDefaultValue(options) {
652
+ const selectedValues = this.selectedOptionValues(options);
653
+ if (this.field().type === 'checkbox-list' || this.field().multiple) {
654
+ return selectedValues;
655
+ }
656
+ return selectedValues[0] ?? null;
657
+ }
658
+ selectedOptionValues(options) {
659
+ return options
660
+ .filter(option => option.selected)
661
+ .map(option => option.value);
662
+ }
663
+ isOptionSelected(option) {
664
+ if (option.selected) {
665
+ return true;
666
+ }
667
+ const defaultValue = this.field().defaultValue;
668
+ return Array.isArray(defaultValue)
669
+ ? defaultValue.includes(option.value)
670
+ : defaultValue === option.value;
474
671
  }
475
672
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
476
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderFieldSettings, isStandalone: true, selector: "ngs-basic-form-builder-field-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-field-settings" }, exportAs: ["ngsBasicFormBuilderFieldSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-3 py-1\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n </div>\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [ngModel]=\"optionsText()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchOptions($event)\"></textarea>\n <ngs-hint>One option per line. Use Label:value to set explicit values.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
673
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderFieldSettings, isStandalone: true, selector: "ngs-basic-form-builder-field-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-field-settings" }, exportAs: ["ngsBasicFormBuilderFieldSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (isSelect()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSelectMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Error, selector: "ngs-error", exportAs: ["ngsError"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: SlideToggleGroup, selector: "ngs-slide-toggle-group", exportAs: ["ngsSlideToggleGroup"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
477
674
  }
478
675
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, decorators: [{
479
676
  type: Component,
480
677
  args: [{ selector: 'ngs-basic-form-builder-field-settings', exportAs: 'ngsBasicFormBuilderFieldSettings', imports: [
481
678
  FormsModule,
679
+ ReactiveFormsModule,
680
+ Error,
482
681
  FormField,
483
682
  Hint,
484
683
  Label,
485
684
  Input,
486
685
  Select,
487
686
  Option,
488
- SlideToggle
687
+ SlideToggle,
688
+ SlideToggleGroup
489
689
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
490
690
  'class': 'ngs-basic-form-builder-field-settings'
491
- }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-3 py-1\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n </div>\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [ngModel]=\"optionsText()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchOptions($event)\"></textarea>\n <ngs-hint>One option per line. Use Label:value to set explicit values.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
691
+ }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (isSelect()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSelectMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
692
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
693
+ function optionsTextValidator(control) {
694
+ const hasInvalidLine = (control.value ?? '')
695
+ .split('\n')
696
+ .map(line => line.trim())
697
+ .filter(Boolean)
698
+ .some(line => !parseOptionLine(line));
699
+ return hasInvalidLine ? { formBuilderOptionsFormat: true } : null;
700
+ }
701
+ function parseOptionLine(line) {
702
+ const parts = line.split(':').map(part => part.trim());
703
+ if (parts.length < 2 || parts.length > 3) {
704
+ return null;
705
+ }
706
+ const [label, value, selected] = parts;
707
+ if (!label || !value) {
708
+ return null;
709
+ }
710
+ if (selected !== undefined && selected !== 'selected') {
711
+ return null;
712
+ }
713
+ return {
714
+ label,
715
+ value,
716
+ selected: selected === 'selected'
717
+ };
718
+ }
719
+
720
+ class BasicFormBuilderLayoutSettings {
721
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
722
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
723
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
724
+ patch(changes) {
725
+ this.update()(changes);
726
+ }
727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
728
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderLayoutSettings, isStandalone: true, selector: "ngs-basic-form-builder-layout-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-layout-settings" }, exportAs: ["ngsBasicFormBuilderLayoutSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
729
+ }
730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, decorators: [{
731
+ type: Component,
732
+ args: [{ selector: 'ngs-basic-form-builder-layout-settings', exportAs: 'ngsBasicFormBuilderLayoutSettings', imports: [
733
+ FormsModule,
734
+ FormField,
735
+ Hint,
736
+ Label,
737
+ Input,
738
+ Option,
739
+ Select
740
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
741
+ 'class': 'ngs-basic-form-builder-layout-settings'
742
+ }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
492
743
  }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
493
744
 
745
+ class BasicFormBuilderSectionSettings {
746
+ section = input.required(...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
747
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
748
+ patch(changes) {
749
+ this.update()(changes);
750
+ }
751
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
752
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: BasicFormBuilderSectionSettings, isStandalone: true, selector: "ngs-basic-form-builder-section-settings", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-section-settings" }, exportAs: ["ngsBasicFormBuilderSectionSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
753
+ }
754
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, decorators: [{
755
+ type: Component,
756
+ args: [{ selector: 'ngs-basic-form-builder-section-settings', exportAs: 'ngsBasicFormBuilderSectionSettings', imports: [
757
+ FormsModule,
758
+ FormField,
759
+ Hint,
760
+ Label,
761
+ Input,
762
+ SlideToggle
763
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
764
+ 'class': 'ngs-basic-form-builder-section-settings'
765
+ }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
766
+ }], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
767
+
768
+ class FormBuilderSettingsHost {
769
+ field = input(null, ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
770
+ section = input(null, ...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
771
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
772
+ definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
773
+ settingsDefinitions = input([], ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
774
+ update = input(...(ngDevMode ? [undefined, { debugName: "update" }] : /* istanbul ignore next */ []));
775
+ updateSection = input(...(ngDevMode ? [undefined, { debugName: "updateSection" }] : /* istanbul ignore next */ []));
776
+ itemDefinition = computed(() => {
777
+ const field = this.field();
778
+ const section = this.section();
779
+ if (section) {
780
+ return this.definitions().find(definition => definition.type === 'section');
781
+ }
782
+ return field ? this.definitions().find(definition => definition.type === field.type) : undefined;
783
+ }, ...(ngDevMode ? [{ debugName: "itemDefinition" }] : /* istanbul ignore next */ []));
784
+ fieldDefinition = this.itemDefinition;
785
+ fieldKind = computed(() => this.itemDefinition()?.kind ?? this.field()?.kind ?? 'field', ...(ngDevMode ? [{ debugName: "fieldKind" }] : /* istanbul ignore next */ []));
786
+ isLayoutField = computed(() => this.fieldKind() === 'layout' || !!this.field()?.children?.length, ...(ngDevMode ? [{ debugName: "isLayoutField" }] : /* istanbul ignore next */ []));
787
+ customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
788
+ anchor = viewChild.required('anchor', { read: ViewContainerRef });
789
+ constructor() {
790
+ effect(async () => {
791
+ const field = this.field();
792
+ const section = this.section();
793
+ const definitions = this.definitions();
794
+ const settingDefinitions = this.settingsDefinitions();
795
+ const viewContainer = this.anchor();
796
+ const type = section ? 'section' : field?.type;
797
+ const itemDefinition = type ? definitions.find(definition => definition.type === type) : undefined;
798
+ const kind = section ? 'layout' : (itemDefinition?.kind ?? field?.kind ?? 'field');
799
+ const customSettings = itemDefinition?.settings ?? settingDefinitions.find(definition => {
800
+ if (type && (definition.itemType === type || definition.fieldType === type || definition.type === type)) {
801
+ return true;
802
+ }
803
+ return !!definition.kind && definition.kind === kind;
804
+ })?.component;
805
+ viewContainer.clear();
806
+ this.customLoaded.set(false);
807
+ if (!customSettings) {
808
+ return;
809
+ }
810
+ const componentType = await customSettings();
811
+ const componentRef = viewContainer.createComponent(componentType);
812
+ componentRef.setInput('item', section ?? field);
813
+ componentRef.setInput('field', field);
814
+ componentRef.setInput('section', section);
815
+ componentRef.setInput('schema', this.schema());
816
+ componentRef.setInput('definition', itemDefinition);
817
+ componentRef.setInput('update', section ? this.updateSection() : this.update());
818
+ componentRef.setInput('updateField', this.update());
819
+ componentRef.setInput('updateSection', this.updateSection());
820
+ this.customLoaded.set(true);
821
+ });
822
+ }
823
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
824
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderSettingsHost, isStandalone: true, selector: "ngs-form-builder-settings-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: false, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, settingsDefinitions: { classPropertyName: "settingsDefinitions", publicName: "settingsDefinitions", isSignal: true, isRequired: false, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: false, transformFunction: null }, updateSection: { classPropertyName: "updateSection", publicName: "updateSection", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-empty": "!customLoaded()" }, classAttribute: "ngs-form-builder-settings-host" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderSettingsHost"], ngImport: i0, template: "@if (section(); as sectionItem) {\n <ngs-basic-form-builder-section-settings\n [section]=\"sectionItem\"\n [update]=\"updateSection()!\"/>\n} @else if (field(); as fieldItem) {\n @if (isLayoutField()) {\n <ngs-basic-form-builder-layout-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n } @else {\n <ngs-basic-form-builder-field-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n }\n}\n\n<ng-container #anchor/>\n", dependencies: [{ kind: "component", type: BasicFormBuilderFieldSettings, selector: "ngs-basic-form-builder-field-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderFieldSettings"] }, { kind: "component", type: BasicFormBuilderLayoutSettings, selector: "ngs-basic-form-builder-layout-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderLayoutSettings"] }, { kind: "component", type: BasicFormBuilderSectionSettings, selector: "ngs-basic-form-builder-section-settings", inputs: ["section", "update"], exportAs: ["ngsBasicFormBuilderSectionSettings"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
825
+ }
826
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
827
+ type: Component,
828
+ args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', imports: [
829
+ BasicFormBuilderFieldSettings,
830
+ BasicFormBuilderLayoutSettings,
831
+ BasicFormBuilderSectionSettings
832
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
833
+ 'class': 'ngs-form-builder-settings-host',
834
+ '[class.is-empty]': '!customLoaded()'
835
+ }, template: "@if (section(); as sectionItem) {\n <ngs-basic-form-builder-section-settings\n [section]=\"sectionItem\"\n [update]=\"updateSection()!\"/>\n} @else if (field(); as fieldItem) {\n @if (isLayoutField()) {\n <ngs-basic-form-builder-layout-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n } @else {\n <ngs-basic-form-builder-field-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n }\n}\n\n<ng-container #anchor/>\n" }]
836
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: false }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], settingsDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingsDefinitions", required: false }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: false }] }], updateSection: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateSection", required: false }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
837
+
494
838
  const ROOT_DROP_LIST_ID = 'ngs-form-builder-root-fields';
495
839
  const PALETTE_DRAG_TYPE = 'application/x-ngstarter-form-builder-field';
496
840
  const PALETTE_DRAG_ITEM = 'application/x-ngstarter-form-builder-item';
841
+ const ACTUAL_FIELDS_TAB_INDEX = 1;
497
842
  class FormBuilder {
498
843
  dialog = inject(Dialog);
499
844
  confirmManager = inject(ConfirmManager);
500
845
  elementRef = inject(ElementRef);
846
+ providedItems = inject(FORM_BUILDER_ITEMS, { optional: true }) ?? [];
501
847
  providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
502
848
  providedSettings = inject(FORM_BUILDER_SETTINGS, { optional: true }) ?? [];
503
849
  previewControls = new Map();
850
+ fieldTreeNodeCache = new Map();
851
+ canvasAnimationTimers = new WeakMap();
504
852
  actualFieldsTree = viewChild('actualFieldsTree', ...(ngDevMode ? [{ debugName: "actualFieldsTree" }] : /* istanbul ignore next */ []));
853
+ fieldTreeRootNodes = [];
854
+ fieldTreeStructureKey = '';
505
855
  suppressPaletteClick = false;
506
856
  schema = model(createDefaultFormBuilderSchema(), ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
507
857
  paletteTitle = input('Fields', ...(ngDevMode ? [{ debugName: "paletteTitle" }] : /* istanbul ignore next */ []));
@@ -510,6 +860,7 @@ class FormBuilder {
510
860
  fieldAdded = output();
511
861
  fieldRemoved = output();
512
862
  search = signal('', ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
863
+ fieldsTabIndex = signal(0, ...(ngDevMode ? [{ debugName: "fieldsTabIndex" }] : /* istanbul ignore next */ []));
513
864
  selectedFieldId = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldId" }] : /* istanbul ignore next */ []));
514
865
  nativeDragItem = signal(null, ...(ngDevMode ? [{ debugName: "nativeDragItem" }] : /* istanbul ignore next */ []));
515
866
  nativeDragFieldDefinition = computed(() => {
@@ -518,19 +869,35 @@ class FormBuilder {
518
869
  }, ...(ngDevMode ? [{ debugName: "nativeDragFieldDefinition" }] : /* istanbul ignore next */ []));
519
870
  nativeDragSection = computed(() => this.nativeDragItem()?.kind === 'section', ...(ngDevMode ? [{ debugName: "nativeDragSection" }] : /* istanbul ignore next */ []));
520
871
  nativeDropTarget = signal(null, ...(ngDevMode ? [{ debugName: "nativeDropTarget" }] : /* istanbul ignore next */ []));
521
- dragCollapsedSectionIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "dragCollapsedSectionIds" }] : /* istanbul ignore next */ []));
522
872
  expandedFieldTreeNodeIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedFieldTreeNodeIds" }] : /* istanbul ignore next */ []));
523
873
  definitions = computed(() => [
524
- ...DEFAULT_FORM_BUILDER_FIELDS,
525
- ...this.providedFields
526
- ], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
874
+ ...DEFAULT_FORM_BUILDER_ITEMS,
875
+ ...this.providedFields,
876
+ ...this.providedItems
877
+ ].reduce((definitions, definition) => {
878
+ const normalized = normalizeFieldDefinition(definition);
879
+ const index = definitions.findIndex(item => item.type === normalized.type);
880
+ if (index === -1) {
881
+ definitions.push(normalized);
882
+ }
883
+ else {
884
+ definitions[index] = {
885
+ ...definitions[index],
886
+ ...normalized,
887
+ defaults: {
888
+ ...definitions[index].defaults,
889
+ ...normalized.defaults
890
+ }
891
+ };
892
+ }
893
+ return definitions;
894
+ }, []), ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
527
895
  settingsDefinitions = computed(() => this.providedSettings, ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
528
- fieldContainerDropListIds = computed(() => this.collectFieldContainerDropListIds(this.schema()), ...(ngDevMode ? [{ debugName: "fieldContainerDropListIds" }] : /* istanbul ignore next */ []));
529
896
  canvasItems = computed(() => this.resolveCanvasItems(this.schema()), ...(ngDevMode ? [{ debugName: "canvasItems" }] : /* istanbul ignore next */ []));
530
897
  layoutDefinitions = computed(() => {
531
898
  const query = this.search().trim().toLowerCase();
532
899
  return this.definitions().filter(definition => {
533
- if ((definition.group || 'Other') !== 'Layout') {
900
+ if (definition.type === 'section' || (definition.group || 'Other') !== 'Layout') {
534
901
  return false;
535
902
  }
536
903
  return this.matchesPaletteQuery(definition, query);
@@ -554,23 +921,22 @@ class FormBuilder {
554
921
  }, ...(ngDevMode ? [{ debugName: "selectedField" }] : /* istanbul ignore next */ []));
555
922
  selectedSection = computed(() => {
556
923
  const selectedId = this.selectedFieldId();
557
- return selectedId ? this.findFieldLocation(this.schema(), selectedId)?.section ?? null : null;
924
+ return selectedId ? this.schema().sections.find(section => section.id === selectedId) ?? null : null;
558
925
  }, ...(ngDevMode ? [{ debugName: "selectedSection" }] : /* istanbul ignore next */ []));
559
926
  fieldTree = computed(() => {
560
- return this.resolveCanvasItems(this.schema()).map(item => {
561
- if (item.field) {
562
- return this.createFieldTreeNode(item.field);
563
- }
564
- return {
565
- id: item.section.id,
566
- label: item.section.title,
567
- type: 'section',
568
- icon: 'fluent:folder-24-regular',
569
- kind: 'section',
570
- section: item.section,
571
- children: item.section.fields.map(field => this.createFieldTreeNode(field, item.section))
572
- };
927
+ const nodes = this.resolveCanvasItems(this.schema()).map(item => {
928
+ return item.field
929
+ ? this.upsertFieldTreeNode(item.field)
930
+ : this.upsertSectionTreeNode(item.section);
573
931
  });
932
+ const structureKey = this.resolveFieldTreeStructureKey(nodes);
933
+ if (structureKey !== this.fieldTreeStructureKey) {
934
+ this.fieldTreeStructureKey = structureKey;
935
+ this.fieldTreeRootNodes = nodes;
936
+ return this.fieldTreeRootNodes;
937
+ }
938
+ replaceArrayContents(this.fieldTreeRootNodes, nodes);
939
+ return this.fieldTreeRootNodes;
574
940
  }, ...(ngDevMode ? [{ debugName: "fieldTree" }] : /* istanbul ignore next */ []));
575
941
  fieldTreeChildrenAccessor = (node) => node.children ?? [];
576
942
  hasFieldTreeChildren = (_, node) => !!node.children?.length;
@@ -580,9 +946,11 @@ class FormBuilder {
580
946
  updateSelectedField = (changes) => {
581
947
  this.patchSelectedField(changes);
582
948
  };
583
- fieldDropListEnterPredicate = (drag) => {
584
- const data = drag.data;
585
- return !(isFormBuilderLayoutItem(data) && data.kind === 'section');
949
+ updateSelectedSection = (changes) => {
950
+ const section = this.selectedSection();
951
+ if (section) {
952
+ this.updateSection(section, changes);
953
+ }
586
954
  };
587
955
  paletteDragStarted(event, definition) {
588
956
  this.suppressPaletteClick = true;
@@ -605,7 +973,7 @@ class FormBuilder {
605
973
  }
606
974
  paletteDragEnded() {
607
975
  this.nativeDragItem.set(null);
608
- this.nativeDropTarget.set(null);
976
+ this.setNativeDropTarget(null);
609
977
  window.setTimeout(() => {
610
978
  this.suppressPaletteClick = false;
611
979
  });
@@ -675,7 +1043,7 @@ class FormBuilder {
675
1043
  return;
676
1044
  }
677
1045
  if (this.nativeDropTarget()?.containerId === containerId) {
678
- this.nativeDropTarget.set(null);
1046
+ this.setNativeDropTarget(null);
679
1047
  }
680
1048
  }
681
1049
  nativeFieldDrop(event, containerId) {
@@ -697,7 +1065,7 @@ class FormBuilder {
697
1065
  this.insertNativeField(item.definition, containerId, index);
698
1066
  }
699
1067
  this.nativeDragItem.set(null);
700
- this.nativeDropTarget.set(null);
1068
+ this.setNativeDropTarget(null);
701
1069
  }
702
1070
  isNativeDropTarget(containerId, index) {
703
1071
  const target = this.nativeDropTarget();
@@ -752,7 +1120,8 @@ class FormBuilder {
752
1120
  schema.sections.push(nextSection);
753
1121
  schema.layout = this.normalizedLayout(schema);
754
1122
  }
755
- if (section.fields.some(field => field.id === this.selectedFieldId() || containsField(field, this.selectedFieldId()))) {
1123
+ if (this.selectedFieldId() === section.id ||
1124
+ section.fields.some(field => field.id === this.selectedFieldId() || containsField(field, this.selectedFieldId()))) {
756
1125
  this.selectedFieldId.set(null);
757
1126
  }
758
1127
  this.schema.set(schema);
@@ -765,134 +1134,28 @@ class FormBuilder {
765
1134
  }
766
1135
  Object.assign(nextSection, changes);
767
1136
  this.schema.set(schema);
768
- }
769
- sectionDragStarted(section) {
770
- if (section.collapsed) {
771
- return;
772
- }
773
- this.dragCollapsedSectionIds.update(ids => new Set(ids).add(section.id));
774
- }
775
- sectionDragEnded(section) {
776
- if (!this.dragCollapsedSectionIds().has(section.id)) {
777
- return;
778
- }
779
- this.dragCollapsedSectionIds.update(ids => {
780
- const next = new Set(ids);
781
- next.delete(section.id);
782
- return next;
783
- });
1137
+ this.restoreFieldTreeExpansion();
784
1138
  }
785
1139
  isSectionCollapsed(section) {
786
- return section.collapsed === true || this.dragCollapsedSectionIds().has(section.id);
787
- }
788
- dropField(event) {
789
- const schema = cloneSchema(this.schema());
790
- const targetContainer = this.findContainerLocation(schema, event.container.id);
791
- if (!targetContainer) {
792
- return;
793
- }
794
- if (event.previousContainer.id === 'ngs-form-builder-palette') {
795
- const definition = event.item.data;
796
- const field = this.createField(definition, schema);
797
- targetContainer.fields.splice(event.currentIndex, 0, field);
798
- this.schema.set(schema);
799
- this.selectField(field, targetContainer.section);
800
- this.fieldAdded.emit({ field, section: targetContainer.section });
801
- return;
802
- }
803
- if (event.previousContainer.id === ROOT_DROP_LIST_ID) {
804
- const layout = this.normalizedLayout(schema);
805
- const movingItem = this.resolveCanvasDragItem(event.item.data, layout);
806
- if (!movingItem || movingItem.kind !== 'field') {
807
- return;
808
- }
809
- const sourceIndex = (schema.fields ?? []).findIndex(field => field.id === movingItem.id);
810
- const movingField = (schema.fields ?? [])[sourceIndex];
811
- const layoutIndex = layout.findIndex(item => item.kind === 'field' && item.id === movingItem.id);
812
- if (!movingField) {
813
- return;
814
- }
815
- if (targetContainer.owner &&
816
- (targetContainer.owner.id === movingField.id || containsField(movingField, targetContainer.owner.id))) {
817
- return;
818
- }
819
- schema.fields?.splice(sourceIndex, 1);
820
- if (layoutIndex > -1) {
821
- layout.splice(layoutIndex, 1);
822
- }
823
- targetContainer.fields.splice(event.currentIndex, 0, movingField);
824
- schema.layout = layout;
825
- this.schema.set(schema);
826
- return;
827
- }
828
- const sourceContainer = this.findContainerLocation(schema, event.previousContainer.id);
829
- if (!sourceContainer) {
830
- return;
831
- }
832
- const movingField = sourceContainer.fields[event.previousIndex];
833
- if (movingField &&
834
- targetContainer.owner &&
835
- (targetContainer.owner.id === movingField.id || containsField(movingField, targetContainer.owner.id))) {
836
- return;
837
- }
838
- if (sourceContainer.id === targetContainer.id) {
839
- moveItemInArray(targetContainer.fields, event.previousIndex, event.currentIndex);
840
- }
841
- else {
842
- transferArrayItem(sourceContainer.fields, targetContainer.fields, event.previousIndex, event.currentIndex);
843
- }
844
- this.schema.set(schema);
845
- }
846
- dropCanvasItem(event) {
847
- const schema = cloneSchema(this.schema());
848
- const layout = this.normalizedLayout(schema);
849
- if (event.previousContainer.id === 'ngs-form-builder-palette') {
850
- const definition = event.item.data;
851
- const field = this.createField(definition, schema);
852
- schema.fields ??= [];
853
- schema.fields.push(field);
854
- layout.splice(event.currentIndex, 0, { kind: 'field', id: field.id });
855
- schema.layout = layout;
856
- this.schema.set(schema);
857
- this.selectField(field);
858
- this.fieldAdded.emit({ field });
859
- return;
860
- }
861
- if (event.previousContainer.id === ROOT_DROP_LIST_ID) {
862
- const movingItem = this.resolveCanvasDragItem(event.item.data, layout);
863
- const previousIndex = movingItem
864
- ? layout.findIndex(item => item.kind === movingItem.kind && item.id === movingItem.id)
865
- : event.previousIndex;
866
- if (previousIndex < 0) {
867
- return;
868
- }
869
- moveItemInArray(layout, previousIndex, event.currentIndex);
870
- schema.layout = layout;
871
- this.schema.set(schema);
872
- this.clearSectionDragCollapse();
873
- return;
874
- }
875
- const sourceContainer = this.findContainerLocation(schema, event.previousContainer.id);
876
- if (!sourceContainer) {
877
- return;
878
- }
879
- const movingField = sourceContainer.fields[event.previousIndex];
880
- if (!movingField) {
881
- return;
882
- }
883
- sourceContainer.fields.splice(event.previousIndex, 1);
884
- schema.fields ??= [];
885
- schema.fields.push(movingField);
886
- layout.splice(event.currentIndex, 0, { kind: 'field', id: movingField.id });
887
- schema.layout = layout;
888
- this.schema.set(schema);
889
- this.clearSectionDragCollapse();
1140
+ return section.collapsed === true;
890
1141
  }
891
- selectField(field, section = this.selectedSection()) {
1142
+ selectField(field, section) {
892
1143
  this.selectedFieldId.set(field.id);
893
1144
  this.fieldSelected.emit({ field, section: section ?? undefined });
894
1145
  }
1146
+ selectSection(section) {
1147
+ this.selectedFieldId.set(section.id);
1148
+ }
1149
+ selectCanvasField(field, section) {
1150
+ this.selectField(field, section);
1151
+ this.openActualFieldsTreeForField(field.id);
1152
+ }
895
1153
  selectFieldTreeNode(node) {
1154
+ if (node.section && !node.field) {
1155
+ this.selectSection(node.section);
1156
+ this.scrollCanvasSectionIntoView(node.section.id);
1157
+ return;
1158
+ }
896
1159
  if (node.field) {
897
1160
  if (node.section?.collapsed) {
898
1161
  this.updateSection(node.section, { collapsed: false });
@@ -1013,7 +1276,7 @@ class FormBuilder {
1013
1276
  return existing;
1014
1277
  }
1015
1278
  const control = new FormControl({
1016
- value: field.defaultValue ?? null,
1279
+ value: this.fieldInitialValue(field),
1017
1280
  disabled: true
1018
1281
  });
1019
1282
  this.previewControls.set(field.id, control);
@@ -1033,14 +1296,20 @@ class FormBuilder {
1033
1296
  ...location.field,
1034
1297
  ...changes
1035
1298
  };
1036
- if (changes.type === 'grid' && !nextField.children) {
1299
+ if (this.isContainerField(nextField) && !nextField.children) {
1037
1300
  nextField.children = [];
1038
1301
  }
1039
1302
  location.siblings[location.index] = nextField;
1040
1303
  this.schema.set(schema);
1304
+ this.syncPreviewControl(nextField);
1041
1305
  }
1042
1306
  isContainerField(field) {
1043
- return field.type === 'grid';
1307
+ const definition = this.definitions().find(item => item.type === field.type);
1308
+ return definition?.acceptsChildren === true ||
1309
+ definition?.kind === 'layout' ||
1310
+ field.kind === 'layout' ||
1311
+ field.type === 'group' ||
1312
+ field.type === 'grid';
1044
1313
  }
1045
1314
  fieldIcon(field) {
1046
1315
  return this.definitions().find(definition => definition.type === field.type)?.icon || 'fluent:form-24-regular';
@@ -1063,7 +1332,7 @@ class FormBuilder {
1063
1332
  if (event.dataTransfer) {
1064
1333
  event.dataTransfer.dropEffect = 'copy';
1065
1334
  }
1066
- this.nativeDropTarget.set({
1335
+ this.setNativeDropTarget({
1067
1336
  containerId,
1068
1337
  index: this.resolveNativeDropIndex(event, itemSelector)
1069
1338
  });
@@ -1077,7 +1346,68 @@ class FormBuilder {
1077
1346
  if (event.dataTransfer) {
1078
1347
  event.dataTransfer.dropEffect = 'copy';
1079
1348
  }
1080
- this.nativeDropTarget.set({ containerId, index });
1349
+ this.setNativeDropTarget({ containerId, index });
1350
+ }
1351
+ setNativeDropTarget(target) {
1352
+ const current = this.nativeDropTarget();
1353
+ if (current?.containerId === target?.containerId && current?.index === target?.index) {
1354
+ return;
1355
+ }
1356
+ const previousRects = this.captureCanvasItemRects();
1357
+ this.nativeDropTarget.set(target);
1358
+ this.afterNextPaint(() => this.animateCanvasItemMoves(previousRects));
1359
+ }
1360
+ captureCanvasItemRects() {
1361
+ const rects = new Map();
1362
+ for (const element of this.getCanvasAnimatedItems()) {
1363
+ const key = this.getCanvasAnimatedItemKey(element);
1364
+ if (key) {
1365
+ rects.set(key, element.getBoundingClientRect());
1366
+ }
1367
+ }
1368
+ return rects;
1369
+ }
1370
+ animateCanvasItemMoves(previousRects) {
1371
+ if (!previousRects.size) {
1372
+ return;
1373
+ }
1374
+ for (const element of this.getCanvasAnimatedItems()) {
1375
+ const key = this.getCanvasAnimatedItemKey(element);
1376
+ const previousRect = key ? previousRects.get(key) : undefined;
1377
+ if (!previousRect) {
1378
+ continue;
1379
+ }
1380
+ const nextRect = element.getBoundingClientRect();
1381
+ const deltaX = previousRect.left - nextRect.left;
1382
+ const deltaY = previousRect.top - nextRect.top;
1383
+ if (Math.abs(deltaX) < 0.5 && Math.abs(deltaY) < 0.5) {
1384
+ continue;
1385
+ }
1386
+ const animationTimer = this.canvasAnimationTimers.get(element);
1387
+ if (animationTimer) {
1388
+ window.clearTimeout(animationTimer);
1389
+ }
1390
+ element.style.transition = 'none';
1391
+ element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
1392
+ element.getBoundingClientRect();
1393
+ element.style.transition = 'transform 160ms cubic-bezier(0, 0, 0.2, 1)';
1394
+ element.style.transform = '';
1395
+ this.canvasAnimationTimers.set(element, window.setTimeout(() => {
1396
+ element.style.transition = '';
1397
+ element.style.transform = '';
1398
+ }, 180));
1399
+ }
1400
+ }
1401
+ getCanvasAnimatedItems() {
1402
+ return Array.from(this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-field-id], .ngs-form-builder-canvas [data-form-builder-section-id]'));
1403
+ }
1404
+ getCanvasAnimatedItemKey(element) {
1405
+ const fieldId = element.dataset['formBuilderFieldId'];
1406
+ if (fieldId) {
1407
+ return `field:${fieldId}`;
1408
+ }
1409
+ const sectionId = element.dataset['formBuilderSectionId'];
1410
+ return sectionId ? `section:${sectionId}` : null;
1081
1411
  }
1082
1412
  insertNativeField(definition, containerId, index) {
1083
1413
  const schema = cloneSchema(this.schema());
@@ -1164,10 +1494,12 @@ class FormBuilder {
1164
1494
  createField(definition, schema) {
1165
1495
  const baseLabel = definition.defaults?.label || definition.label;
1166
1496
  const name = uniqueFieldName(toFieldName(baseLabel), schema);
1167
- return {
1497
+ const kind = definition.defaults?.kind ?? definition.kind ?? 'field';
1498
+ const field = {
1168
1499
  id: uniqueId('field'),
1169
1500
  name,
1170
1501
  type: definition.type,
1502
+ kind,
1171
1503
  label: baseLabel,
1172
1504
  width: definition.defaults?.width ?? this.defaultWidth(definition.type),
1173
1505
  visibility: {
@@ -1177,6 +1509,10 @@ class FormBuilder {
1177
1509
  },
1178
1510
  ...definition.defaults
1179
1511
  };
1512
+ if (field.defaultValue === undefined) {
1513
+ field.defaultValue = this.fieldInitialValue(field);
1514
+ }
1515
+ return field;
1180
1516
  }
1181
1517
  defaultWidth(type) {
1182
1518
  return 12;
@@ -1231,30 +1567,6 @@ class FormBuilder {
1231
1567
  }
1232
1568
  return layout;
1233
1569
  }
1234
- collectFieldContainerDropListIds(schema) {
1235
- return [
1236
- ...(schema.fields ?? []).flatMap(field => this.collectFieldDropListIds(field)),
1237
- ...schema.sections.flatMap(section => [
1238
- this.sectionDropListId(section),
1239
- ...section.fields.flatMap(field => this.collectFieldDropListIds(field))
1240
- ])
1241
- ];
1242
- }
1243
- collectFieldDropListIds(field) {
1244
- return [
1245
- ...(this.isContainerField(field) ? [this.fieldDropListId(field)] : []),
1246
- ...(field.children ?? []).flatMap(child => this.collectFieldDropListIds(child))
1247
- ];
1248
- }
1249
- resolveCanvasDragItem(data, layout) {
1250
- if (isFormBuilderLayoutItem(data)) {
1251
- return data;
1252
- }
1253
- if (isFormBuilderField(data)) {
1254
- return layout.find(item => item.kind === 'field' && item.id === data.id) ?? null;
1255
- }
1256
- return null;
1257
- }
1258
1570
  detachFieldFromLocation(schema, location) {
1259
1571
  location.siblings.splice(location.index, 1);
1260
1572
  if (location.siblings === (schema.fields ?? [])) {
@@ -1330,11 +1642,6 @@ class FormBuilder {
1330
1642
  });
1331
1643
  schema.layout = layout;
1332
1644
  }
1333
- clearSectionDragCollapse() {
1334
- if (this.dragCollapsedSectionIds().size) {
1335
- this.dragCollapsedSectionIds.set(new Set());
1336
- }
1337
- }
1338
1645
  resetFieldTreeAfterRejectedDrop() {
1339
1646
  this.schema.set(cloneSchema(this.schema()));
1340
1647
  this.restoreFieldTreeExpansion();
@@ -1400,10 +1707,80 @@ class FormBuilder {
1400
1707
  flattenFieldTree(nodes) {
1401
1708
  return nodes.flatMap(node => [node, ...this.flattenFieldTree(node.children ?? [])]);
1402
1709
  }
1710
+ openActualFieldsTreeForField(fieldId) {
1711
+ const path = this.findFieldTreeNodePath(this.fieldTree(), fieldId);
1712
+ this.fieldsTabIndex.set(ACTUAL_FIELDS_TAB_INDEX);
1713
+ if (!path.length) {
1714
+ return;
1715
+ }
1716
+ const expandableIds = path
1717
+ .slice(0, -1)
1718
+ .filter(node => !!node.children?.length)
1719
+ .map(node => node.id);
1720
+ if (expandableIds.length) {
1721
+ this.expandedFieldTreeNodeIds.update(ids => {
1722
+ const next = new Set(ids);
1723
+ for (const id of expandableIds) {
1724
+ next.add(id);
1725
+ }
1726
+ return next;
1727
+ });
1728
+ }
1729
+ this.afterNextPaint(() => {
1730
+ this.afterNextPaint(() => {
1731
+ const tree = this.actualFieldsTree();
1732
+ if (tree) {
1733
+ for (const node of path.slice(0, -1)) {
1734
+ if (node.children?.length) {
1735
+ tree.expand(node);
1736
+ }
1737
+ }
1738
+ }
1739
+ this.scrollFieldTreeNodeIntoView(fieldId);
1740
+ });
1741
+ });
1742
+ }
1743
+ findFieldTreeNodePath(nodes, fieldId, path = []) {
1744
+ for (const node of nodes) {
1745
+ const nextPath = [...path, node];
1746
+ if (node.id === fieldId) {
1747
+ return nextPath;
1748
+ }
1749
+ const childPath = this.findFieldTreeNodePath(node.children ?? [], fieldId, nextPath);
1750
+ if (childPath.length) {
1751
+ return childPath;
1752
+ }
1753
+ }
1754
+ return [];
1755
+ }
1756
+ scrollFieldTreeNodeIntoView(fieldId) {
1757
+ const target = this.findFieldTreeNodeElement(fieldId);
1758
+ if (!target) {
1759
+ return;
1760
+ }
1761
+ const scrollableContent = target
1762
+ .closest('ngs-scrollbar-area')
1763
+ ?.querySelector('.scrollable-content');
1764
+ if (!scrollableContent) {
1765
+ target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
1766
+ return;
1767
+ }
1768
+ this.scrollElementFullyIntoView(target, scrollableContent);
1769
+ }
1770
+ findFieldTreeNodeElement(fieldId) {
1771
+ const nodes = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-palette [data-form-builder-tree-node-id]');
1772
+ return Array.from(nodes).find(node => node.dataset['formBuilderTreeNodeId'] === fieldId) ?? null;
1773
+ }
1403
1774
  scrollCanvasFieldIntoView(fieldId) {
1775
+ this.scrollCanvasItemIntoView(() => this.findCanvasFieldElement(fieldId));
1776
+ }
1777
+ scrollCanvasSectionIntoView(sectionId) {
1778
+ this.scrollCanvasItemIntoView(() => this.findCanvasSectionElement(sectionId));
1779
+ }
1780
+ scrollCanvasItemIntoView(resolveTarget) {
1404
1781
  this.afterNextPaint(() => {
1405
1782
  this.afterNextPaint(() => {
1406
- const target = this.findCanvasFieldElement(fieldId);
1783
+ const target = resolveTarget();
1407
1784
  if (!target) {
1408
1785
  return;
1409
1786
  }
@@ -1422,6 +1799,37 @@ class FormBuilder {
1422
1799
  const fields = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-field-id]');
1423
1800
  return Array.from(fields).find(field => field.dataset['formBuilderFieldId'] === fieldId) ?? null;
1424
1801
  }
1802
+ findCanvasSectionElement(sectionId) {
1803
+ const sections = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-section-id]');
1804
+ return Array.from(sections).find(section => section.dataset['formBuilderSectionId'] === sectionId) ?? null;
1805
+ }
1806
+ syncPreviewControl(field) {
1807
+ const control = this.previewControls.get(field.id);
1808
+ if (!control) {
1809
+ return;
1810
+ }
1811
+ queueMicrotask(() => {
1812
+ const value = this.fieldInitialValue(field);
1813
+ if (control.value !== value) {
1814
+ control.setValue(value, { emitEvent: false });
1815
+ }
1816
+ if (control.enabled) {
1817
+ control.disable({ emitEvent: false });
1818
+ }
1819
+ });
1820
+ }
1821
+ fieldInitialValue(field) {
1822
+ if (field.defaultValue !== undefined) {
1823
+ return field.defaultValue;
1824
+ }
1825
+ const selectedValues = (field.options ?? [])
1826
+ .filter(option => option.selected)
1827
+ .map(option => option.value);
1828
+ if (field.type === 'checkbox-list' || field.multiple) {
1829
+ return selectedValues;
1830
+ }
1831
+ return selectedValues[0] ?? null;
1832
+ }
1425
1833
  scrollElementFullyIntoView(target, scrollableContent) {
1426
1834
  const padding = 16;
1427
1835
  const containerRect = scrollableContent.getBoundingClientRect();
@@ -1455,19 +1863,59 @@ class FormBuilder {
1455
1863
  }
1456
1864
  window.setTimeout(callback);
1457
1865
  }
1458
- createFieldTreeNode(field, section) {
1866
+ upsertSectionTreeNode(section) {
1867
+ const node = this.getCachedFieldTreeNode(section.id);
1868
+ const children = section.fields.map(field => this.upsertFieldTreeNode(field, section));
1869
+ node.label = section.title;
1870
+ node.name = undefined;
1871
+ node.type = 'section';
1872
+ node.icon = 'fluent:folder-24-regular';
1873
+ node.kind = 'section';
1874
+ node.field = undefined;
1875
+ node.section = section;
1876
+ node.children ??= [];
1877
+ replaceArrayContents(node.children, children);
1878
+ return node;
1879
+ }
1880
+ upsertFieldTreeNode(field, section) {
1459
1881
  const definition = this.definitions().find(item => item.type === field.type);
1460
- return {
1461
- id: field.id,
1462
- label: field.label,
1463
- name: field.name,
1464
- type: field.type,
1465
- icon: definition?.icon || 'fluent:form-24-regular',
1466
- kind: 'field',
1467
- field,
1468
- section,
1469
- children: field.children?.map(child => this.createFieldTreeNode(child, section))
1882
+ const node = this.getCachedFieldTreeNode(field.id);
1883
+ const children = field.children?.map(child => this.upsertFieldTreeNode(child, section));
1884
+ node.label = field.label;
1885
+ node.name = field.name;
1886
+ node.type = field.type;
1887
+ node.icon = definition?.icon || 'fluent:form-24-regular';
1888
+ node.kind = 'field';
1889
+ node.field = field;
1890
+ node.section = section;
1891
+ if (children?.length) {
1892
+ node.children ??= [];
1893
+ replaceArrayContents(node.children, children);
1894
+ }
1895
+ else {
1896
+ node.children = undefined;
1897
+ }
1898
+ return node;
1899
+ }
1900
+ getCachedFieldTreeNode(id) {
1901
+ const cached = this.fieldTreeNodeCache.get(id);
1902
+ if (cached) {
1903
+ return cached;
1904
+ }
1905
+ const node = {
1906
+ id,
1907
+ label: '',
1908
+ type: '',
1909
+ icon: 'fluent:form-24-regular',
1910
+ kind: 'field'
1470
1911
  };
1912
+ this.fieldTreeNodeCache.set(id, node);
1913
+ return node;
1914
+ }
1915
+ resolveFieldTreeStructureKey(nodes) {
1916
+ return nodes
1917
+ .map(node => `${node.kind}:${node.id}[${this.resolveFieldTreeStructureKey(node.children ?? [])}]`)
1918
+ .join('|');
1471
1919
  }
1472
1920
  findContainerLocation(schema, containerId) {
1473
1921
  if (containerId === ROOT_DROP_LIST_ID) {
@@ -1533,19 +1981,13 @@ class FormBuilder {
1533
1981
  }
1534
1982
  }
1535
1983
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilder, isStandalone: true, selector: "ngs-form-builder", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, paletteTitle: { classPropertyName: "paletteTitle", publicName: "paletteTitle", isSignal: true, isRequired: false, transformFunction: null }, inspectorTitle: { classPropertyName: "inspectorTitle", publicName: "inspectorTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schema: "schemaChange", fieldSelected: "fieldSelected", fieldAdded: "fieldAdded", fieldRemoved: "fieldRemoved" }, host: { classAttribute: "ngs-form-builder" }, viewQueries: [{ propertyName: "actualFieldsTree", first: true, predicate: ["actualFieldsTree"], descendants: true, isSignal: true }], exportAs: ["ngsFormBuilder"], ngImport: i0, template: "<ng-template #fieldCard let-field let-section=\"section\" let-dragData=\"dragData\">\n <div\n class=\"ngs-form-builder-field\"\n cdkDrag\n [cdkDragData]=\"dragData || field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-container]=\"isContainerField(field)\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-field-handle\" cdkDragHandle aria-label=\"Move field\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n </div>\n\n @if (isContainerField(field)) {\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n }\n\n <ng-template cdkDragPlaceholder>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-line-placeholder\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n </div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-drag-preview\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n </ng-template>\n </div>\n</ng-template>\n\n<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n cdkDropList\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n [cdkDropListData]=\"fields\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n [cdkDropListEnterPredicate]=\"fieldDropListEnterPredicate\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\"\n (cdkDropListDropped)=\"dropField($event)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n section\n }\"/>\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\" cdkDropListGroup>\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-4\">\n <h3 class=\"text-base font-semibold\">{{ paletteTitle() }}</h3>\n\n <ngs-tab-group animationDuration=\"0ms\" ngs-stretch-tabs=\"false\">\n <ngs-tab label=\"Placeholders\">\n <div class=\"flex flex-col gap-4 pt-4\">\n <ngs-form-field>\n <ngs-label>Search placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"search.set($event)\">\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-5\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </div>\n </ngs-tab>\n\n <ngs-tab label=\"Actual fields\">\n <div class=\"pt-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n cdkDropList\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n [cdkDropListData]=\"canvasItems()\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\"\n (cdkDropListDropped)=\"dropCanvasItem($event)\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n dragData: item\n }\"/>\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"sectionDragStarted(section)\"\n (cdkDragEnded)=\"sectionDragEnded(section)\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-section-handle\" cdkDragHandle aria-label=\"Move section\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n\n <ng-template cdkDragPlaceholder>\n <div class=\"ngs-form-builder-section-line-placeholder\"></div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-section-drag-preview\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-section-handle\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-preview-title\">{{ section.title }}</span>\n </div>\n <ngs-card-aside>\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-5\"/>\n </ngs-card-aside>\n </ngs-card-header>\n </ngs-card>\n </ng-template>\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField(); as field) {\n <ngs-basic-form-builder-field-settings\n [field]=\"field\"\n [update]=\"updateSelectedField\"/>\n\n <ngs-form-builder-settings-host\n [field]=\"field\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"/>\n\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-primary);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1);--ngs-tree-node-drop-line-color: var(--ngs-color-primary);--ngs-tree-node-drop-target-outline: 0;--ngs-tree-node-drop-target-bg: var(--ngs-color-primary-container)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{--ngs-button-height: calc(var(--spacing, .25rem) * 6)}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-drag-preview{width:min(100%,900px);background:var(--ngs-color-surface)}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-handle{flex:none;color:var(--ngs-color-on-surface-variant);cursor:grab}:host .ngs-form-builder-section-handle:active{cursor:grabbing}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-handle{flex:none;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .cdk-drag-preview{box-sizing:border-box;border-radius:var(--ngs-radius-lg);box-shadow:var(--ngs-shadow-lg)}:host .ngs-form-builder-field-placeholder{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-field-line-placeholder{min-height:calc(var(--spacing, .25rem) * 6);justify-content:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-field-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .ngs-form-builder-field-drag-preview{width:min(360px,100vw - calc(var(--spacing, .25rem) * 8));min-height:calc(var(--spacing, .25rem) * 20);border-color:var(--ngs-color-primary);background:var(--ngs-color-surface);box-shadow:var(--ngs-shadow-lg);opacity:.94;pointer-events:none}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 18);border:1px dashed var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 6);border:0;background:transparent}:host .ngs-form-builder-section-line-placeholder{display:flex;min-height:calc(var(--spacing, .25rem) * 5);align-items:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-section-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .cdk-drag-placeholder{opacity:0}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder,:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder,:host .ngs-form-builder-canvas-list .ngs-form-builder-section-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-palette .cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-drop-list.cdk-drop-list-dragging .ngs-form-builder-field:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-canvas-list.cdk-drop-list-dragging .ngs-form-builder-section:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: DialogActions, selector: "ngs-dialog-actions, [ngs-dialog-actions], [ngsDialogActions]", inputs: ["align"] }, { kind: "directive", type: DialogClose, selector: "[ngs-dialog-close], [ngsDialogClose]", inputs: ["ngs-dialog-close", "ngsDialogClose", "ariaLabel", "type"], exportAs: ["ngsDialogClose"] }, { kind: "component", type: DialogContent, selector: "ngs-dialog-content,[ngs-dialog-content],[ngsDialogContent]" }, { kind: "component", type: DialogTitle, selector: "ngs-dialog-title, [ngs-dialog-title], [ngsDialogTitle]", inputs: ["id"], exportAs: ["ngsDialogTitle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tree, selector: "ngs-tree", inputs: ["checkable", "selectable", "draggable", "draggablePredicate", "dropPredicate", "reorderOnDrop", "dragPreview", "childrenKey", "filterValue", "filterPredicate", "filterMode"], outputs: ["checkedChange", "selectedChange", "nodeDrop"], exportAs: ["ngsTree"] }, { kind: "directive", type: TreeDragPlaceholder, selector: "ng-template[ngsTreeDragPlaceholder]" }, { kind: "component", type: TreeNode, selector: "ngs-tree-node", inputs: ["tabIndex", "value", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["ngsTreeNode"] }, { kind: "directive", type: TreeNodeDef, selector: "[ngsTreeNodeDef]", inputs: ["ngsTreeNodeDefWhen", "ngsTreeNode"] }, { kind: "directive", type: TreeNodePadding, selector: "[ngsTreeNodePadding]", inputs: ["ngsTreeNodePadding", "ngsTreeNodePaddingIndent"] }, { kind: "component", type: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas"], exportAs: ["ngsFormBuilderFieldHost"] }, { kind: "component", type: FormBuilderRenderer, selector: "ngs-form-builder-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormBuilderRenderer"] }, { kind: "component", type: FormBuilderSettingsHost, selector: "ngs-form-builder-settings-host", inputs: ["field", "schema", "definitions", "settingsDefinitions", "update"], exportAs: ["ngsFormBuilderSettingsHost"] }, { kind: "component", type: BasicFormBuilderFieldSettings, selector: "ngs-basic-form-builder-field-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderFieldSettings"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1984
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilder, isStandalone: true, selector: "ngs-form-builder", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, paletteTitle: { classPropertyName: "paletteTitle", publicName: "paletteTitle", isSignal: true, isRequired: false, transformFunction: null }, inspectorTitle: { classPropertyName: "inspectorTitle", publicName: "inspectorTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schema: "schemaChange", fieldSelected: "fieldSelected", fieldAdded: "fieldAdded", fieldRemoved: "fieldRemoved" }, host: { classAttribute: "ngs-form-builder" }, viewQueries: [{ propertyName: "actualFieldsTree", first: true, predicate: ["actualFieldsTree"], descendants: true, isSignal: true }], exportAs: ["ngsFormBuilder"], ngImport: i0, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"ps-1 pe-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField() || selectedSection()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label || selectedSection()?.title }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField() || selectedSection()) {\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-style:dashed;border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: DialogActions, selector: "ngs-dialog-actions, [ngs-dialog-actions], [ngsDialogActions]", inputs: ["align"] }, { kind: "directive", type: DialogClose, selector: "[ngs-dialog-close], [ngsDialogClose]", inputs: ["ngs-dialog-close", "ngsDialogClose", "ariaLabel", "type"], exportAs: ["ngsDialogClose"] }, { kind: "component", type: DialogContent, selector: "ngs-dialog-content,[ngs-dialog-content],[ngsDialogContent]" }, { kind: "component", type: DialogTitle, selector: "ngs-dialog-title, [ngs-dialog-title], [ngsDialogTitle]", inputs: ["id"], exportAs: ["ngsDialogTitle"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tree, selector: "ngs-tree", inputs: ["checkable", "selectable", "draggable", "draggablePredicate", "dropPredicate", "reorderOnDrop", "dragPreview", "nodePaddingIndent", "childrenKey", "filterValue", "filterPredicate", "filterMode"], outputs: ["checkedChange", "selectedChange", "nodeDrop"], exportAs: ["ngsTree"] }, { kind: "directive", type: TreeDragPlaceholder, selector: "ng-template[ngsTreeDragPlaceholder]" }, { kind: "component", type: TreeNode, selector: "ngs-tree-node", inputs: ["tabIndex", "value", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["ngsTreeNode"] }, { kind: "directive", type: TreeNodeDef, selector: "[ngsTreeNodeDef]", inputs: ["ngsTreeNodeDefWhen", "ngsTreeNode"] }, { kind: "directive", type: TreeNodePadding, selector: "[ngsTreeNodePadding]", inputs: ["ngsTreeNodePadding", "ngsTreeNodePaddingIndent"] }, { kind: "component", type: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas"], exportAs: ["ngsFormBuilderFieldHost"] }, { kind: "component", type: FormBuilderRenderer, selector: "ngs-form-builder-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormBuilderRenderer"] }, { kind: "component", type: FormBuilderSettingsHost, selector: "ngs-form-builder-settings-host", inputs: ["field", "section", "schema", "definitions", "settingsDefinitions", "update", "updateSection"], exportAs: ["ngsFormBuilderSettingsHost"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1537
1985
  }
1538
1986
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, decorators: [{
1539
1987
  type: Component,
1540
1988
  args: [{ selector: 'ngs-form-builder', exportAs: 'ngsFormBuilder', imports: [
1541
1989
  NgTemplateOutlet,
1542
1990
  FormsModule,
1543
- CdkDrag,
1544
- CdkDragHandle,
1545
- CdkDragPlaceholder,
1546
- CdkDragPreview,
1547
- CdkDropList,
1548
- CdkDropListGroup,
1549
1991
  Button,
1550
1992
  Card,
1551
1993
  CardAside,
@@ -1555,10 +1997,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
1555
1997
  DialogClose,
1556
1998
  DialogContent,
1557
1999
  DialogTitle,
1558
- FormField,
1559
2000
  Icon,
1560
2001
  Input,
1561
- Label,
1562
2002
  Panel,
1563
2003
  PanelAside,
1564
2004
  PanelContent,
@@ -1574,11 +2014,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
1574
2014
  TreeNodePadding,
1575
2015
  FormBuilderFieldHost,
1576
2016
  FormBuilderRenderer,
1577
- FormBuilderSettingsHost,
1578
- BasicFormBuilderFieldSettings
2017
+ FormBuilderSettingsHost
1579
2018
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1580
2019
  'class': 'ngs-form-builder'
1581
- }, template: "<ng-template #fieldCard let-field let-section=\"section\" let-dragData=\"dragData\">\n <div\n class=\"ngs-form-builder-field\"\n cdkDrag\n [cdkDragData]=\"dragData || field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-container]=\"isContainerField(field)\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-field-handle\" cdkDragHandle aria-label=\"Move field\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n </div>\n\n @if (isContainerField(field)) {\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n }\n\n <ng-template cdkDragPlaceholder>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-line-placeholder\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n </div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-drag-preview\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n </ng-template>\n </div>\n</ng-template>\n\n<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n cdkDropList\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n [cdkDropListData]=\"fields\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n [cdkDropListEnterPredicate]=\"fieldDropListEnterPredicate\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\"\n (cdkDropListDropped)=\"dropField($event)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n section\n }\"/>\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\" cdkDropListGroup>\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-4\">\n <h3 class=\"text-base font-semibold\">{{ paletteTitle() }}</h3>\n\n <ngs-tab-group animationDuration=\"0ms\" ngs-stretch-tabs=\"false\">\n <ngs-tab label=\"Placeholders\">\n <div class=\"flex flex-col gap-4 pt-4\">\n <ngs-form-field>\n <ngs-label>Search placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"search.set($event)\">\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-5\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </div>\n </ngs-tab>\n\n <ngs-tab label=\"Actual fields\">\n <div class=\"pt-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n cdkDropList\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n [cdkDropListData]=\"canvasItems()\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\"\n (cdkDropListDropped)=\"dropCanvasItem($event)\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n dragData: item\n }\"/>\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"sectionDragStarted(section)\"\n (cdkDragEnded)=\"sectionDragEnded(section)\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-section-handle\" cdkDragHandle aria-label=\"Move section\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n\n <ng-template cdkDragPlaceholder>\n <div class=\"ngs-form-builder-section-line-placeholder\"></div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-section-drag-preview\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-section-handle\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-preview-title\">{{ section.title }}</span>\n </div>\n <ngs-card-aside>\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-5\"/>\n </ngs-card-aside>\n </ngs-card-header>\n </ngs-card>\n </ng-template>\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField(); as field) {\n <ngs-basic-form-builder-field-settings\n [field]=\"field\"\n [update]=\"updateSelectedField\"/>\n\n <ngs-form-builder-settings-host\n [field]=\"field\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"/>\n\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-primary);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1);--ngs-tree-node-drop-line-color: var(--ngs-color-primary);--ngs-tree-node-drop-target-outline: 0;--ngs-tree-node-drop-target-bg: var(--ngs-color-primary-container)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{--ngs-button-height: calc(var(--spacing, .25rem) * 6)}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-drag-preview{width:min(100%,900px);background:var(--ngs-color-surface)}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-handle{flex:none;color:var(--ngs-color-on-surface-variant);cursor:grab}:host .ngs-form-builder-section-handle:active{cursor:grabbing}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-handle{flex:none;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .cdk-drag-preview{box-sizing:border-box;border-radius:var(--ngs-radius-lg);box-shadow:var(--ngs-shadow-lg)}:host .ngs-form-builder-field-placeholder{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-field-line-placeholder{min-height:calc(var(--spacing, .25rem) * 6);justify-content:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-field-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .ngs-form-builder-field-drag-preview{width:min(360px,100vw - calc(var(--spacing, .25rem) * 8));min-height:calc(var(--spacing, .25rem) * 20);border-color:var(--ngs-color-primary);background:var(--ngs-color-surface);box-shadow:var(--ngs-shadow-lg);opacity:.94;pointer-events:none}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 18);border:1px dashed var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 6);border:0;background:transparent}:host .ngs-form-builder-section-line-placeholder{display:flex;min-height:calc(var(--spacing, .25rem) * 5);align-items:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-section-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .cdk-drag-placeholder{opacity:0}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder,:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder,:host .ngs-form-builder-canvas-list .ngs-form-builder-section-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-palette .cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-drop-list.cdk-drop-list-dragging .ngs-form-builder-field:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-canvas-list.cdk-drop-list-dragging .ngs-form-builder-section:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
2020
+ }, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"ps-1 pe-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField() || selectedSection()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label || selectedSection()?.title }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField() || selectedSection()) {\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-style:dashed;border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
1582
2021
  }], propDecorators: { actualFieldsTree: [{ type: i0.ViewChild, args: ['actualFieldsTree', { isSignal: true }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }, { type: i0.Output, args: ["schemaChange"] }], paletteTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "paletteTitle", required: false }] }], inspectorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorTitle", required: false }] }], fieldSelected: [{ type: i0.Output, args: ["fieldSelected"] }], fieldAdded: [{ type: i0.Output, args: ["fieldAdded"] }], fieldRemoved: [{ type: i0.Output, args: ["fieldRemoved"] }] } });
1583
2022
  function createDefaultFormBuilderSchema() {
1584
2023
  const sectionId = uniqueId('section');
@@ -1619,23 +2058,18 @@ function cloneField(field) {
1619
2058
  children: field.children?.map(cloneField)
1620
2059
  };
1621
2060
  }
1622
- function isFormBuilderLayoutItem(value) {
1623
- if (!value || typeof value !== 'object') {
1624
- return false;
1625
- }
1626
- const item = value;
1627
- return (item.kind === 'field' || item.kind === 'section') && typeof item.id === 'string';
1628
- }
1629
- function isFormBuilderField(value) {
1630
- if (!value || typeof value !== 'object') {
1631
- return false;
1632
- }
1633
- const field = value;
1634
- return typeof field.id === 'string' && typeof field.type === 'string';
1635
- }
1636
2061
  function clampIndex(index, length) {
1637
2062
  return Math.max(0, Math.min(index, length));
1638
2063
  }
2064
+ function replaceArrayContents(target, source) {
2065
+ target.splice(0, target.length, ...source);
2066
+ }
2067
+ function normalizeFieldDefinition(definition) {
2068
+ return {
2069
+ ...definition,
2070
+ kind: definition.kind ?? 'field'
2071
+ };
2072
+ }
1639
2073
  function toFieldName(label) {
1640
2074
  return label
1641
2075
  .trim()
@@ -1691,5 +2125,5 @@ function containsField(field, fieldId) {
1691
2125
  * Generated bundle index. Do not edit.
1692
2126
  */
1693
2127
 
1694
- export { BasicFormBuilderFieldSettings, DEFAULT_FORM_BUILDER_FIELDS, FORM_BUILDER_FIELDS, FORM_BUILDER_SETTINGS, FormBuilder, FormBuilderFieldHost, FormBuilderRenderer, FormBuilderSettingsHost, FormBuilderRenderer as FormRenderer, createDefaultFormBuilderSchema, formBuilderField, formBuilderSettings, provideFormBuilder, validatorsFromRules };
2128
+ export { BasicFormBuilderFieldSettings, BasicFormBuilderLayoutSettings, BasicFormBuilderSectionSettings, DEFAULT_FORM_BUILDER_FIELDS, DEFAULT_FORM_BUILDER_ITEMS, FORM_BUILDER_FIELDS, FORM_BUILDER_ITEMS, FORM_BUILDER_SETTINGS, FormBuilder, FormBuilderFieldHost, FormBuilderRenderer, FormBuilderSettingsHost, FormBuilderRenderer as FormRenderer, createDefaultFormBuilderSchema, formBuilderField, formBuilderItem, formBuilderSettings, provideFormBuilder, validatorsFromRules };
1695
2129
  //# sourceMappingURL=ngstarter-ui-components-form-builder.mjs.map