@ngstarter-ui/components 21.0.39 → 21.0.41

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.
@@ -0,0 +1,1916 @@
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';
3
+ import * as i1 from '@angular/forms';
4
+ import { Validators, ReactiveFormsModule, FormControl, FormGroup, FormsModule } from '@angular/forms';
5
+ import { NgTemplateOutlet } from '@angular/common';
6
+ import { Button } from '@ngstarter-ui/components/button';
7
+ import { Card, CardAside, CardContent, CardHeader } from '@ngstarter-ui/components/card';
8
+ import { ConfirmManager } from '@ngstarter-ui/components/confirm';
9
+ import { Dialog, DialogActions, DialogClose, DialogContent, DialogTitle } from '@ngstarter-ui/components/dialog';
10
+ import { Icon } from '@ngstarter-ui/components/icon';
11
+ import { Input } from '@ngstarter-ui/components/input';
12
+ import { Panel, PanelAside, PanelContent, PanelHeader, PanelSidebar } from '@ngstarter-ui/components/panel';
13
+ import { ScrollbarArea } from '@ngstarter-ui/components/scrollbar-area';
14
+ import { Tab, TabGroup } from '@ngstarter-ui/components/tabs';
15
+ import { Tree, TreeDragPlaceholder, TreeNode, TreeNodeDef, TreeNodePadding } from '@ngstarter-ui/components/tree';
16
+ import { Datepicker, DatepickerInput, DatepickerToggle, provideNativeDateAdapter } from '@ngstarter-ui/components/datepicker';
17
+ import { FormField, Hint, IconButtonSuffix, Label } from '@ngstarter-ui/components/form-field';
18
+ import { Select } from '@ngstarter-ui/components/select';
19
+ import { Option } from '@ngstarter-ui/components/option';
20
+ import { Checkbox } from '@ngstarter-ui/components/checkbox';
21
+ import { SlideToggle } from '@ngstarter-ui/components/slide-toggle';
22
+
23
+ const FORM_BUILDER_ITEMS = new InjectionToken('FORM_BUILDER_ITEMS');
24
+ const FORM_BUILDER_FIELDS = new InjectionToken('FORM_BUILDER_FIELDS');
25
+ const FORM_BUILDER_SETTINGS = new InjectionToken('FORM_BUILDER_SETTINGS');
26
+ function formBuilderField(definition) {
27
+ return definition;
28
+ }
29
+ function formBuilderItem(definition) {
30
+ return definition;
31
+ }
32
+ function formBuilderSettings(definition) {
33
+ return definition;
34
+ }
35
+ function provideFormBuilder(config = {}) {
36
+ return makeEnvironmentProviders([
37
+ ...(config.items ?? []).map(item => ({
38
+ provide: FORM_BUILDER_ITEMS,
39
+ useValue: item,
40
+ multi: true
41
+ })),
42
+ ...(config.fields ?? []).map(field => ({
43
+ provide: FORM_BUILDER_FIELDS,
44
+ useValue: field,
45
+ multi: true
46
+ })),
47
+ ...(config.settings ?? []).map(settings => ({
48
+ provide: FORM_BUILDER_SETTINGS,
49
+ useValue: settings,
50
+ multi: true
51
+ }))
52
+ ]);
53
+ }
54
+ const DEFAULT_FORM_BUILDER_FIELDS = [
55
+ {
56
+ type: 'text',
57
+ label: 'Text',
58
+ group: 'Basic',
59
+ icon: 'fluent:text-font-24-regular',
60
+ defaults: {
61
+ label: 'Text field',
62
+ placeholder: 'Enter text'
63
+ }
64
+ },
65
+ {
66
+ type: 'number',
67
+ label: 'Number',
68
+ group: 'Basic',
69
+ icon: 'fluent:number-symbol-24-regular',
70
+ defaults: {
71
+ label: 'Number',
72
+ placeholder: '0'
73
+ }
74
+ },
75
+ {
76
+ type: 'email',
77
+ label: 'Email',
78
+ group: 'Basic',
79
+ icon: 'fluent:mail-24-regular',
80
+ defaults: {
81
+ label: 'Email',
82
+ placeholder: 'name@example.com'
83
+ },
84
+ validators: field => field.required ? [Validators.required, Validators.email] : [Validators.email]
85
+ },
86
+ {
87
+ type: 'textarea',
88
+ label: 'Textarea',
89
+ group: 'Basic',
90
+ icon: 'fluent:text-description-24-regular',
91
+ defaults: {
92
+ label: 'Description',
93
+ placeholder: 'Enter description',
94
+ width: 12
95
+ }
96
+ },
97
+ {
98
+ type: 'group',
99
+ label: 'Group',
100
+ kind: 'layout',
101
+ group: 'Layout',
102
+ icon: 'fluent:group-24-regular',
103
+ description: 'Container for nested fields.',
104
+ acceptsChildren: true,
105
+ defaults: {
106
+ kind: 'layout',
107
+ label: 'Group',
108
+ width: 12,
109
+ children: []
110
+ }
111
+ },
112
+ {
113
+ type: 'select',
114
+ label: 'Select',
115
+ group: 'Choices',
116
+ icon: 'fluent:multiselect-ltr-24-regular',
117
+ defaults: {
118
+ label: 'Select',
119
+ placeholder: 'Choose an option',
120
+ options: [
121
+ { label: 'Option 1', value: 'option_1' },
122
+ { label: 'Option 2', value: 'option_2' }
123
+ ]
124
+ }
125
+ },
126
+ {
127
+ type: 'checkbox',
128
+ label: 'Checkbox',
129
+ group: 'Choices',
130
+ icon: 'fluent:checkbox-checked-24-regular',
131
+ defaults: {
132
+ label: 'Checkbox',
133
+ defaultValue: false
134
+ }
135
+ },
136
+ {
137
+ type: 'toggle',
138
+ label: 'Toggle',
139
+ group: 'Choices',
140
+ icon: 'fluent:toggle-right-24-regular',
141
+ defaults: {
142
+ label: 'Toggle',
143
+ defaultValue: false
144
+ }
145
+ },
146
+ {
147
+ type: 'date',
148
+ label: 'Date',
149
+ group: 'Date and time',
150
+ icon: 'fluent:calendar-ltr-24-regular',
151
+ defaults: {
152
+ label: 'Date',
153
+ placeholder: 'Select date'
154
+ }
155
+ },
156
+ {
157
+ type: 'currency',
158
+ label: 'Currency',
159
+ group: 'Finance',
160
+ icon: 'fluent:money-24-regular',
161
+ defaults: {
162
+ label: 'Amount',
163
+ placeholder: '0.00'
164
+ }
165
+ }
166
+ ];
167
+ const DEFAULT_FORM_BUILDER_ITEMS = [
168
+ {
169
+ type: 'section',
170
+ label: 'Section',
171
+ kind: 'layout',
172
+ group: 'Layout',
173
+ icon: 'fluent:folder-24-regular',
174
+ description: 'Top-level layout container.',
175
+ acceptsChildren: true
176
+ },
177
+ ...DEFAULT_FORM_BUILDER_FIELDS
178
+ ];
179
+ function validatorsFromRules(rules = [], field) {
180
+ const validators = [];
181
+ if (field?.required || rules.some(rule => rule.type === 'required')) {
182
+ validators.push(Validators.required);
183
+ }
184
+ for (const rule of rules) {
185
+ if (rule.type === 'email') {
186
+ validators.push(Validators.email);
187
+ }
188
+ else if (rule.type === 'minLength') {
189
+ validators.push(Validators.minLength(Number(rule.value)));
190
+ }
191
+ else if (rule.type === 'maxLength') {
192
+ validators.push(Validators.maxLength(Number(rule.value)));
193
+ }
194
+ else if (rule.type === 'min') {
195
+ validators.push(Validators.min(Number(rule.value)));
196
+ }
197
+ else if (rule.type === 'max') {
198
+ validators.push(Validators.max(Number(rule.value)));
199
+ }
200
+ }
201
+ return validators;
202
+ }
203
+
204
+ class FormBuilderFieldHost {
205
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
206
+ control = input.required(...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
207
+ definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
208
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
209
+ editableCanvas = input(false, ...(ngDevMode ? [{ debugName: "editableCanvas" }] : /* istanbul ignore next */ []));
210
+ customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
211
+ textInputType = computed(() => {
212
+ const type = this.field().type;
213
+ return type === 'number' || type === 'email' ? type : 'text';
214
+ }, ...(ngDevMode ? [{ debugName: "textInputType" }] : /* istanbul ignore next */ []));
215
+ anchor = viewChild.required('anchor', { read: ViewContainerRef });
216
+ constructor() {
217
+ effect(async () => {
218
+ const field = this.field();
219
+ const control = this.control();
220
+ const definitions = this.definitions();
221
+ const viewContainer = this.anchor();
222
+ const renderer = definitions.find(definition => definition.type === field.type)?.renderer;
223
+ viewContainer.clear();
224
+ this.customLoaded.set(false);
225
+ if (!renderer) {
226
+ return;
227
+ }
228
+ const componentType = await renderer();
229
+ const componentRef = viewContainer.createComponent(componentType);
230
+ componentRef.setInput('field', field);
231
+ componentRef.setInput('control', control);
232
+ componentRef.setInput('readonly', this.readonly());
233
+ componentRef.setInput('definition', definitions.find(definition => definition.type === field.type));
234
+ this.customLoaded.set(true);
235
+ });
236
+ }
237
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
238
+ 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: [
239
+ provideNativeDateAdapter()
240
+ ], 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()\">\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 ('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 });
241
+ }
242
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, decorators: [{
243
+ type: Component,
244
+ args: [{ selector: 'ngs-form-builder-field-host', exportAs: 'ngsFormBuilderFieldHost', imports: [
245
+ ReactiveFormsModule,
246
+ Datepicker,
247
+ DatepickerInput,
248
+ DatepickerToggle,
249
+ FormField,
250
+ Hint,
251
+ IconButtonSuffix,
252
+ Label,
253
+ Input,
254
+ Select,
255
+ Option,
256
+ Checkbox,
257
+ SlideToggle
258
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
259
+ provideNativeDateAdapter()
260
+ ], host: {
261
+ 'class': 'ngs-form-builder-field-host',
262
+ '[class.is-custom]': 'customLoaded()',
263
+ '[class.is-width-1]': '!editableCanvas() && field().width === 1',
264
+ '[class.is-width-2]': '!editableCanvas() && field().width === 2',
265
+ '[class.is-width-3]': '!editableCanvas() && field().width === 3',
266
+ '[class.is-width-4]': '!editableCanvas() && field().width === 4',
267
+ '[class.is-width-5]': '!editableCanvas() && field().width === 5',
268
+ '[class.is-width-6]': '!editableCanvas() && field().width === 6',
269
+ '[class.is-width-7]': '!editableCanvas() && field().width === 7',
270
+ '[class.is-width-8]': '!editableCanvas() && field().width === 8',
271
+ '[class.is-width-9]': '!editableCanvas() && field().width === 9',
272
+ '[class.is-width-10]': '!editableCanvas() && field().width === 10',
273
+ '[class.is-width-11]': '!editableCanvas() && field().width === 11',
274
+ '[class.is-width-12]': '!editableCanvas() && (field().width ?? 12) === 12'
275
+ }, 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()\">\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 ('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"] }]
276
+ }], 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 }] }] } });
277
+
278
+ class FormBuilderRenderer {
279
+ providedItems = inject(FORM_BUILDER_ITEMS, { optional: true }) ?? [];
280
+ providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
281
+ orphanControls = new Map();
282
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
283
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
284
+ showSubmit = input(true, ...(ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []));
285
+ submitLabel = input('Submit', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
286
+ value = model({}, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
287
+ formSubmit = output();
288
+ formReady = output();
289
+ definitions = computed(() => [
290
+ ...DEFAULT_FORM_BUILDER_ITEMS,
291
+ ...this.providedFields,
292
+ ...this.providedItems
293
+ ].reduce((definitions, definition) => {
294
+ const normalized = normalizeFieldDefinition$1(definition);
295
+ const index = definitions.findIndex(item => item.type === normalized.type);
296
+ if (index === -1) {
297
+ definitions.push(normalized);
298
+ }
299
+ else {
300
+ definitions[index] = {
301
+ ...definitions[index],
302
+ ...normalized,
303
+ defaults: {
304
+ ...definitions[index].defaults,
305
+ ...normalized.defaults
306
+ }
307
+ };
308
+ }
309
+ return definitions;
310
+ }, []), ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
311
+ visibleCanvasItems = computed(() => this.resolveCanvasItems(this.schema())
312
+ .map(item => {
313
+ if (item.field) {
314
+ return {
315
+ ...item,
316
+ field: this.visibleFields([item.field])[0]
317
+ };
318
+ }
319
+ return {
320
+ ...item,
321
+ section: {
322
+ ...item.section,
323
+ fields: this.visibleFields(item.section.fields)
324
+ }
325
+ };
326
+ })
327
+ .filter(item => !!item.field || !!item.section?.fields.length), ...(ngDevMode ? [{ debugName: "visibleCanvasItems" }] : /* istanbul ignore next */ []));
328
+ formGroup = computed(() => this.createFormGroup(), ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
329
+ constructor() {
330
+ effect(onCleanup => {
331
+ const form = this.formGroup();
332
+ this.formReady.emit(form);
333
+ const subscription = form.valueChanges.subscribe(() => {
334
+ this.value.set(form.getRawValue());
335
+ });
336
+ onCleanup(() => subscription.unsubscribe());
337
+ });
338
+ }
339
+ getControl(field) {
340
+ const control = this.formGroup().get(field.name);
341
+ if (control instanceof FormControl) {
342
+ return control;
343
+ }
344
+ const existing = this.orphanControls.get(field.id);
345
+ if (existing) {
346
+ return existing;
347
+ }
348
+ const nextControl = new FormControl({
349
+ value: field.defaultValue ?? null,
350
+ disabled: true
351
+ });
352
+ this.orphanControls.set(field.id, nextControl);
353
+ return nextControl;
354
+ }
355
+ isContainerField(field) {
356
+ const definition = this.definitions().find(item => item.type === field.type);
357
+ return definition?.acceptsChildren === true ||
358
+ definition?.kind === 'layout' ||
359
+ field.kind === 'layout' ||
360
+ field.type === 'group' ||
361
+ field.type === 'grid';
362
+ }
363
+ visibleChildren(field) {
364
+ return this.visibleFields(field.children ?? []);
365
+ }
366
+ submit() {
367
+ const form = this.formGroup();
368
+ if (form.invalid) {
369
+ form.markAllAsTouched();
370
+ return;
371
+ }
372
+ this.formSubmit.emit(form.getRawValue());
373
+ }
374
+ createFormGroup() {
375
+ const controls = {};
376
+ const value = this.value();
377
+ const fields = [
378
+ ...flattenFields(this.schema().fields ?? []),
379
+ ...this.schema().sections.flatMap(section => flattenFields(section.fields))
380
+ ];
381
+ for (const field of fields) {
382
+ const definition = this.definitions().find(item => item.type === field.type);
383
+ if (this.isContainerField(field) || definition?.kind === 'static' || field.kind === 'static') {
384
+ continue;
385
+ }
386
+ const validators = definition?.validators?.(field) ?? validatorsFromRules(field.validation, field);
387
+ const control = new FormControl({
388
+ value: value[field.name] ?? field.defaultValue ?? null,
389
+ disabled: field.disabled || this.readonly()
390
+ }, validators);
391
+ controls[field.name] = control;
392
+ }
393
+ return new FormGroup(controls);
394
+ }
395
+ visibleFields(fields) {
396
+ return fields
397
+ .filter(field => field.visibility?.form !== false)
398
+ .map(field => ({
399
+ ...field,
400
+ children: field.children ? this.visibleFields(field.children) : undefined
401
+ }));
402
+ }
403
+ resolveCanvasItems(schema) {
404
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
405
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
406
+ const items = [];
407
+ for (const item of normalizedLayout(schema)) {
408
+ if (item.kind === 'field') {
409
+ const field = fieldsById.get(item.id);
410
+ if (field) {
411
+ items.push({ ...item, field });
412
+ }
413
+ continue;
414
+ }
415
+ const section = sectionsById.get(item.id);
416
+ if (section) {
417
+ items.push({ ...item, section });
418
+ }
419
+ }
420
+ return items;
421
+ }
422
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderRenderer, isStandalone: true, selector: "ngs-form-builder-renderer", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, showSubmit: { classPropertyName: "showSubmit", publicName: "showSubmit", isSignal: true, isRequired: false, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", formSubmit: "formSubmit", formReady: "formReady" }, host: { classAttribute: "ngs-form-builder-renderer" }, exportAs: ["ngsFormBuilderRenderer"], ngImport: i0, 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"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas"], exportAs: ["ngsFormBuilderFieldHost"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
424
+ }
425
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, decorators: [{
426
+ type: Component,
427
+ args: [{ selector: 'ngs-form-builder-renderer', exportAs: 'ngsFormBuilderRenderer', imports: [
428
+ NgTemplateOutlet,
429
+ ReactiveFormsModule,
430
+ Button,
431
+ FormBuilderFieldHost
432
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
433
+ 'class': 'ngs-form-builder-renderer'
434
+ }, 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"] }]
435
+ }], 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"] }] } });
436
+ function normalizeFieldDefinition$1(definition) {
437
+ return {
438
+ ...definition,
439
+ kind: definition.kind ?? 'field'
440
+ };
441
+ }
442
+ function flattenFields(fields) {
443
+ return fields.flatMap(field => [field, ...flattenFields(field.children ?? [])]);
444
+ }
445
+ function normalizedLayout(schema) {
446
+ const used = new Set();
447
+ const layout = [];
448
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
449
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
450
+ for (const item of schema.layout ?? []) {
451
+ const key = `${item.kind}:${item.id}`;
452
+ if (used.has(key)) {
453
+ continue;
454
+ }
455
+ if ((item.kind === 'field' && fieldsById.has(item.id)) || (item.kind === 'section' && sectionsById.has(item.id))) {
456
+ used.add(key);
457
+ layout.push(item);
458
+ }
459
+ }
460
+ for (const field of schema.fields ?? []) {
461
+ const key = `field:${field.id}`;
462
+ if (!used.has(key)) {
463
+ used.add(key);
464
+ layout.push({ kind: 'field', id: field.id });
465
+ }
466
+ }
467
+ for (const section of schema.sections) {
468
+ const key = `section:${section.id}`;
469
+ if (!used.has(key)) {
470
+ used.add(key);
471
+ layout.push({ kind: 'section', id: section.id });
472
+ }
473
+ }
474
+ return layout;
475
+ }
476
+
477
+ class BasicFormBuilderFieldSettings {
478
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
479
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
480
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
481
+ hasCheckedState = computed(() => ['checkbox', 'toggle'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasCheckedState" }] : /* istanbul ignore next */ []));
482
+ hasPlaceholder = computed(() => !['checkbox', 'toggle'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasPlaceholder" }] : /* istanbul ignore next */ []));
483
+ hasOptions = computed(() => ['select', 'radio', 'checkbox-list'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
484
+ optionsText = computed(() => (this.field().options ?? [])
485
+ .map(option => `${option.label}:${option.value}`)
486
+ .join('\n'), ...(ngDevMode ? [{ debugName: "optionsText" }] : /* istanbul ignore next */ []));
487
+ patch(changes) {
488
+ this.update()(changes);
489
+ }
490
+ patchOptions(value) {
491
+ const options = value
492
+ .split('\n')
493
+ .map(line => line.trim())
494
+ .filter(Boolean)
495
+ .map((line, index) => {
496
+ const [label, optionValue] = line.split(':');
497
+ return {
498
+ label: label?.trim() || `Option ${index + 1}`,
499
+ value: (optionValue ?? label ?? `option_${index + 1}`).trim()
500
+ };
501
+ });
502
+ this.patch({ options });
503
+ }
504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
505
+ 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 <div class=\"flex flex-col gap-5 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\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 </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 });
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, decorators: [{
508
+ type: Component,
509
+ args: [{ selector: 'ngs-basic-form-builder-field-settings', exportAs: 'ngsBasicFormBuilderFieldSettings', imports: [
510
+ FormsModule,
511
+ FormField,
512
+ Hint,
513
+ Label,
514
+ Input,
515
+ Select,
516
+ Option,
517
+ SlideToggle
518
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
519
+ 'class': 'ngs-basic-form-builder-field-settings'
520
+ }, 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 <div class=\"flex flex-col gap-5 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\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 </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"] }]
521
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
522
+
523
+ class BasicFormBuilderLayoutSettings {
524
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
525
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
526
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
527
+ patch(changes) {
528
+ this.update()(changes);
529
+ }
530
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
531
+ 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 });
532
+ }
533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, decorators: [{
534
+ type: Component,
535
+ args: [{ selector: 'ngs-basic-form-builder-layout-settings', exportAs: 'ngsBasicFormBuilderLayoutSettings', imports: [
536
+ FormsModule,
537
+ FormField,
538
+ Hint,
539
+ Label,
540
+ Input,
541
+ Option,
542
+ Select
543
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
544
+ 'class': 'ngs-basic-form-builder-layout-settings'
545
+ }, 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"] }]
546
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
547
+
548
+ class BasicFormBuilderSectionSettings {
549
+ section = input.required(...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
550
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
551
+ patch(changes) {
552
+ this.update()(changes);
553
+ }
554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
555
+ 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 });
556
+ }
557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, decorators: [{
558
+ type: Component,
559
+ args: [{ selector: 'ngs-basic-form-builder-section-settings', exportAs: 'ngsBasicFormBuilderSectionSettings', imports: [
560
+ FormsModule,
561
+ FormField,
562
+ Hint,
563
+ Label,
564
+ Input,
565
+ SlideToggle
566
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
567
+ 'class': 'ngs-basic-form-builder-section-settings'
568
+ }, 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"] }]
569
+ }], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
570
+
571
+ class FormBuilderSettingsHost {
572
+ field = input(null, ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
573
+ section = input(null, ...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
574
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
575
+ definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
576
+ settingsDefinitions = input([], ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
577
+ update = input(...(ngDevMode ? [undefined, { debugName: "update" }] : /* istanbul ignore next */ []));
578
+ updateSection = input(...(ngDevMode ? [undefined, { debugName: "updateSection" }] : /* istanbul ignore next */ []));
579
+ itemDefinition = computed(() => {
580
+ const field = this.field();
581
+ const section = this.section();
582
+ if (section) {
583
+ return this.definitions().find(definition => definition.type === 'section');
584
+ }
585
+ return field ? this.definitions().find(definition => definition.type === field.type) : undefined;
586
+ }, ...(ngDevMode ? [{ debugName: "itemDefinition" }] : /* istanbul ignore next */ []));
587
+ fieldDefinition = this.itemDefinition;
588
+ fieldKind = computed(() => this.itemDefinition()?.kind ?? this.field()?.kind ?? 'field', ...(ngDevMode ? [{ debugName: "fieldKind" }] : /* istanbul ignore next */ []));
589
+ isLayoutField = computed(() => this.fieldKind() === 'layout' || !!this.field()?.children?.length, ...(ngDevMode ? [{ debugName: "isLayoutField" }] : /* istanbul ignore next */ []));
590
+ customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
591
+ anchor = viewChild.required('anchor', { read: ViewContainerRef });
592
+ constructor() {
593
+ effect(async () => {
594
+ const field = this.field();
595
+ const section = this.section();
596
+ const definitions = this.definitions();
597
+ const settingDefinitions = this.settingsDefinitions();
598
+ const viewContainer = this.anchor();
599
+ const type = section ? 'section' : field?.type;
600
+ const itemDefinition = type ? definitions.find(definition => definition.type === type) : undefined;
601
+ const kind = section ? 'layout' : (itemDefinition?.kind ?? field?.kind ?? 'field');
602
+ const customSettings = itemDefinition?.settings ?? settingDefinitions.find(definition => {
603
+ if (type && (definition.itemType === type || definition.fieldType === type || definition.type === type)) {
604
+ return true;
605
+ }
606
+ return !!definition.kind && definition.kind === kind;
607
+ })?.component;
608
+ viewContainer.clear();
609
+ this.customLoaded.set(false);
610
+ if (!customSettings) {
611
+ return;
612
+ }
613
+ const componentType = await customSettings();
614
+ const componentRef = viewContainer.createComponent(componentType);
615
+ componentRef.setInput('item', section ?? field);
616
+ componentRef.setInput('field', field);
617
+ componentRef.setInput('section', section);
618
+ componentRef.setInput('schema', this.schema());
619
+ componentRef.setInput('definition', itemDefinition);
620
+ componentRef.setInput('update', section ? this.updateSection() : this.update());
621
+ componentRef.setInput('updateField', this.update());
622
+ componentRef.setInput('updateSection', this.updateSection());
623
+ this.customLoaded.set(true);
624
+ });
625
+ }
626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
627
+ 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 });
628
+ }
629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
630
+ type: Component,
631
+ args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', imports: [
632
+ BasicFormBuilderFieldSettings,
633
+ BasicFormBuilderLayoutSettings,
634
+ BasicFormBuilderSectionSettings
635
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
636
+ 'class': 'ngs-form-builder-settings-host',
637
+ '[class.is-empty]': '!customLoaded()'
638
+ }, 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" }]
639
+ }], 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 }] }] } });
640
+
641
+ const ROOT_DROP_LIST_ID = 'ngs-form-builder-root-fields';
642
+ const PALETTE_DRAG_TYPE = 'application/x-ngstarter-form-builder-field';
643
+ const PALETTE_DRAG_ITEM = 'application/x-ngstarter-form-builder-item';
644
+ const ACTUAL_FIELDS_TAB_INDEX = 1;
645
+ class FormBuilder {
646
+ dialog = inject(Dialog);
647
+ confirmManager = inject(ConfirmManager);
648
+ elementRef = inject(ElementRef);
649
+ providedItems = inject(FORM_BUILDER_ITEMS, { optional: true }) ?? [];
650
+ providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
651
+ providedSettings = inject(FORM_BUILDER_SETTINGS, { optional: true }) ?? [];
652
+ previewControls = new Map();
653
+ fieldTreeNodeCache = new Map();
654
+ canvasAnimationTimers = new WeakMap();
655
+ actualFieldsTree = viewChild('actualFieldsTree', ...(ngDevMode ? [{ debugName: "actualFieldsTree" }] : /* istanbul ignore next */ []));
656
+ fieldTreeRootNodes = [];
657
+ fieldTreeStructureKey = '';
658
+ suppressPaletteClick = false;
659
+ schema = model(createDefaultFormBuilderSchema(), ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
660
+ paletteTitle = input('Fields', ...(ngDevMode ? [{ debugName: "paletteTitle" }] : /* istanbul ignore next */ []));
661
+ inspectorTitle = input('Field properties', ...(ngDevMode ? [{ debugName: "inspectorTitle" }] : /* istanbul ignore next */ []));
662
+ fieldSelected = output();
663
+ fieldAdded = output();
664
+ fieldRemoved = output();
665
+ search = signal('', ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
666
+ fieldsTabIndex = signal(0, ...(ngDevMode ? [{ debugName: "fieldsTabIndex" }] : /* istanbul ignore next */ []));
667
+ selectedFieldId = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldId" }] : /* istanbul ignore next */ []));
668
+ nativeDragItem = signal(null, ...(ngDevMode ? [{ debugName: "nativeDragItem" }] : /* istanbul ignore next */ []));
669
+ nativeDragFieldDefinition = computed(() => {
670
+ const item = this.nativeDragItem();
671
+ return item?.kind === 'field' ? item.definition : null;
672
+ }, ...(ngDevMode ? [{ debugName: "nativeDragFieldDefinition" }] : /* istanbul ignore next */ []));
673
+ nativeDragSection = computed(() => this.nativeDragItem()?.kind === 'section', ...(ngDevMode ? [{ debugName: "nativeDragSection" }] : /* istanbul ignore next */ []));
674
+ nativeDropTarget = signal(null, ...(ngDevMode ? [{ debugName: "nativeDropTarget" }] : /* istanbul ignore next */ []));
675
+ expandedFieldTreeNodeIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedFieldTreeNodeIds" }] : /* istanbul ignore next */ []));
676
+ definitions = computed(() => [
677
+ ...DEFAULT_FORM_BUILDER_ITEMS,
678
+ ...this.providedFields,
679
+ ...this.providedItems
680
+ ].reduce((definitions, definition) => {
681
+ const normalized = normalizeFieldDefinition(definition);
682
+ const index = definitions.findIndex(item => item.type === normalized.type);
683
+ if (index === -1) {
684
+ definitions.push(normalized);
685
+ }
686
+ else {
687
+ definitions[index] = {
688
+ ...definitions[index],
689
+ ...normalized,
690
+ defaults: {
691
+ ...definitions[index].defaults,
692
+ ...normalized.defaults
693
+ }
694
+ };
695
+ }
696
+ return definitions;
697
+ }, []), ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
698
+ settingsDefinitions = computed(() => this.providedSettings, ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
699
+ canvasItems = computed(() => this.resolveCanvasItems(this.schema()), ...(ngDevMode ? [{ debugName: "canvasItems" }] : /* istanbul ignore next */ []));
700
+ layoutDefinitions = computed(() => {
701
+ const query = this.search().trim().toLowerCase();
702
+ return this.definitions().filter(definition => {
703
+ if (definition.type === 'section' || (definition.group || 'Other') !== 'Layout') {
704
+ return false;
705
+ }
706
+ return this.matchesPaletteQuery(definition, query);
707
+ });
708
+ }, ...(ngDevMode ? [{ debugName: "layoutDefinitions" }] : /* istanbul ignore next */ []));
709
+ paletteGroups = computed(() => {
710
+ const query = this.search().trim().toLowerCase();
711
+ const groups = new Map();
712
+ for (const definition of this.definitions()) {
713
+ const group = definition.group || 'Other';
714
+ if (group === 'Layout' || !this.matchesPaletteQuery(definition, query)) {
715
+ continue;
716
+ }
717
+ groups.set(group, [...(groups.get(group) ?? []), definition]);
718
+ }
719
+ return Array.from(groups, ([name, fields]) => ({ name, fields }));
720
+ }, ...(ngDevMode ? [{ debugName: "paletteGroups" }] : /* istanbul ignore next */ []));
721
+ selectedField = computed(() => {
722
+ const selectedId = this.selectedFieldId();
723
+ return selectedId ? this.findFieldLocation(this.schema(), selectedId)?.field ?? null : null;
724
+ }, ...(ngDevMode ? [{ debugName: "selectedField" }] : /* istanbul ignore next */ []));
725
+ selectedSection = computed(() => {
726
+ const selectedId = this.selectedFieldId();
727
+ return selectedId ? this.schema().sections.find(section => section.id === selectedId) ?? null : null;
728
+ }, ...(ngDevMode ? [{ debugName: "selectedSection" }] : /* istanbul ignore next */ []));
729
+ fieldTree = computed(() => {
730
+ const nodes = this.resolveCanvasItems(this.schema()).map(item => {
731
+ return item.field
732
+ ? this.upsertFieldTreeNode(item.field)
733
+ : this.upsertSectionTreeNode(item.section);
734
+ });
735
+ const structureKey = this.resolveFieldTreeStructureKey(nodes);
736
+ if (structureKey !== this.fieldTreeStructureKey) {
737
+ this.fieldTreeStructureKey = structureKey;
738
+ this.fieldTreeRootNodes = nodes;
739
+ return this.fieldTreeRootNodes;
740
+ }
741
+ replaceArrayContents(this.fieldTreeRootNodes, nodes);
742
+ return this.fieldTreeRootNodes;
743
+ }, ...(ngDevMode ? [{ debugName: "fieldTree" }] : /* istanbul ignore next */ []));
744
+ fieldTreeChildrenAccessor = (node) => node.children ?? [];
745
+ hasFieldTreeChildren = (_, node) => !!node.children?.length;
746
+ trackFieldTreeNode = (_, node) => node.id;
747
+ fieldTreeDraggablePredicate = (_node) => true;
748
+ fieldTreeDropPredicate = (source, target, position) => this.canDropFieldTreeNode(source, target, position);
749
+ updateSelectedField = (changes) => {
750
+ this.patchSelectedField(changes);
751
+ };
752
+ updateSelectedSection = (changes) => {
753
+ const section = this.selectedSection();
754
+ if (section) {
755
+ this.updateSection(section, changes);
756
+ }
757
+ };
758
+ paletteDragStarted(event, definition) {
759
+ this.suppressPaletteClick = true;
760
+ this.nativeDragItem.set({ kind: 'field', definition });
761
+ event.dataTransfer?.setData(PALETTE_DRAG_TYPE, definition.type);
762
+ event.dataTransfer?.setData(PALETTE_DRAG_ITEM, 'field');
763
+ if (event.dataTransfer) {
764
+ event.dataTransfer.effectAllowed = 'copy';
765
+ this.setPaletteDragImage(event);
766
+ }
767
+ }
768
+ sectionPaletteDragStarted(event) {
769
+ this.suppressPaletteClick = true;
770
+ this.nativeDragItem.set({ kind: 'section' });
771
+ event.dataTransfer?.setData(PALETTE_DRAG_ITEM, 'section');
772
+ if (event.dataTransfer) {
773
+ event.dataTransfer.effectAllowed = 'copy';
774
+ this.setPaletteDragImage(event);
775
+ }
776
+ }
777
+ paletteDragEnded() {
778
+ this.nativeDragItem.set(null);
779
+ this.setNativeDropTarget(null);
780
+ window.setTimeout(() => {
781
+ this.suppressPaletteClick = false;
782
+ });
783
+ }
784
+ paletteClicked(definition) {
785
+ if (this.suppressPaletteClick) {
786
+ return;
787
+ }
788
+ this.addField(definition);
789
+ }
790
+ nativeCanvasDragOver(event) {
791
+ this.nativeDragOver(event, ROOT_DROP_LIST_ID, '.ngs-form-builder-field:not(.ngs-form-builder-native-ghost-field), .ngs-form-builder-section:not(.ngs-form-builder-native-ghost-section)');
792
+ }
793
+ nativeFieldDragOver(event, containerId) {
794
+ if (this.nativeDragItem()?.kind === 'section') {
795
+ return;
796
+ }
797
+ event.stopPropagation();
798
+ this.nativeDragOver(event, containerId, '.ngs-form-builder-field:not(.ngs-form-builder-native-ghost-field)');
799
+ }
800
+ nativeSectionDragOver(event, section) {
801
+ if (this.nativeDragItem()?.kind === 'section') {
802
+ return;
803
+ }
804
+ event.stopPropagation();
805
+ this.nativeDragOverAtIndex(event, this.sectionDropListId(section), section.fields.length);
806
+ }
807
+ nativeSectionDrop(event, section) {
808
+ if (this.nativeDragItem()?.kind === 'section') {
809
+ return;
810
+ }
811
+ event.stopPropagation();
812
+ this.nativeFieldDrop(event, this.sectionDropListId(section));
813
+ }
814
+ nativeContainerFieldDragOver(event, field) {
815
+ if (!this.isContainerField(field)) {
816
+ return;
817
+ }
818
+ if (this.nativeDragItem()?.kind === 'section') {
819
+ return;
820
+ }
821
+ event.stopPropagation();
822
+ this.nativeDragOverAtIndex(event, this.fieldDropListId(field), field.children?.length ?? 0);
823
+ }
824
+ nativeContainerFieldDragLeave(event, field) {
825
+ if (!this.isContainerField(field)) {
826
+ return;
827
+ }
828
+ this.nativeDragLeave(event, this.fieldDropListId(field));
829
+ }
830
+ nativeContainerFieldDrop(event, field) {
831
+ if (!this.isContainerField(field)) {
832
+ return;
833
+ }
834
+ if (this.nativeDragItem()?.kind === 'section') {
835
+ return;
836
+ }
837
+ event.stopPropagation();
838
+ this.nativeFieldDrop(event, this.fieldDropListId(field));
839
+ }
840
+ nativeDragLeave(event, containerId) {
841
+ const target = event.currentTarget;
842
+ const relatedTarget = event.relatedTarget;
843
+ if (target instanceof HTMLElement &&
844
+ relatedTarget instanceof Node &&
845
+ target.contains(relatedTarget)) {
846
+ return;
847
+ }
848
+ if (this.nativeDropTarget()?.containerId === containerId) {
849
+ this.setNativeDropTarget(null);
850
+ }
851
+ }
852
+ nativeFieldDrop(event, containerId) {
853
+ const item = this.resolveNativeDragItem(event);
854
+ if (!item) {
855
+ return;
856
+ }
857
+ event.preventDefault();
858
+ event.stopPropagation();
859
+ const target = this.nativeDropTarget();
860
+ const index = target?.containerId === containerId ? target.index : 0;
861
+ if (item.kind === 'section') {
862
+ if (containerId !== ROOT_DROP_LIST_ID) {
863
+ return;
864
+ }
865
+ this.insertNativeSection(index);
866
+ }
867
+ else {
868
+ this.insertNativeField(item.definition, containerId, index);
869
+ }
870
+ this.nativeDragItem.set(null);
871
+ this.setNativeDropTarget(null);
872
+ }
873
+ isNativeDropTarget(containerId, index) {
874
+ const target = this.nativeDropTarget();
875
+ return target?.containerId === containerId && target.index === index;
876
+ }
877
+ addField(definition, sectionId) {
878
+ const schema = cloneSchema(this.schema());
879
+ const fields = sectionId
880
+ ? schema.sections.find(item => item.id === sectionId)?.fields
881
+ : schema.fields;
882
+ if (!fields) {
883
+ return;
884
+ }
885
+ const field = this.createField(definition, schema);
886
+ const section = sectionId ? schema.sections.find(item => item.id === sectionId) : undefined;
887
+ fields.push(field);
888
+ if (!sectionId) {
889
+ schema.layout = this.normalizedLayout(schema);
890
+ }
891
+ this.schema.set(schema);
892
+ this.selectField(field, section);
893
+ this.fieldAdded.emit({ field, section });
894
+ }
895
+ addSection() {
896
+ const schema = cloneSchema(this.schema());
897
+ schema.sections.push({
898
+ id: uniqueId('section'),
899
+ title: `Section ${schema.sections.length + 1}`,
900
+ fields: []
901
+ });
902
+ schema.layout = this.normalizedLayout(schema);
903
+ this.schema.set(schema);
904
+ }
905
+ openPreview(template) {
906
+ this.dialog.open(template, {
907
+ width: '760px',
908
+ maxWidth: 'calc(100vw - 48px)',
909
+ maxHeight: 'calc(100vh - 48px)',
910
+ ariaLabel: 'Form preview'
911
+ });
912
+ }
913
+ removeSection(section) {
914
+ const schema = cloneSchema(this.schema());
915
+ schema.sections = schema.sections.filter(item => item.id !== section.id);
916
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'section' && item.id === section.id));
917
+ if (schema.sections.length === 0) {
918
+ const nextSection = {
919
+ id: uniqueId('section'),
920
+ title: 'Section',
921
+ fields: []
922
+ };
923
+ schema.sections.push(nextSection);
924
+ schema.layout = this.normalizedLayout(schema);
925
+ }
926
+ if (this.selectedFieldId() === section.id ||
927
+ section.fields.some(field => field.id === this.selectedFieldId() || containsField(field, this.selectedFieldId()))) {
928
+ this.selectedFieldId.set(null);
929
+ }
930
+ this.schema.set(schema);
931
+ }
932
+ updateSection(section, changes) {
933
+ const schema = cloneSchema(this.schema());
934
+ const nextSection = schema.sections.find(item => item.id === section.id);
935
+ if (!nextSection) {
936
+ return;
937
+ }
938
+ Object.assign(nextSection, changes);
939
+ this.schema.set(schema);
940
+ this.restoreFieldTreeExpansion();
941
+ }
942
+ isSectionCollapsed(section) {
943
+ return section.collapsed === true;
944
+ }
945
+ selectField(field, section) {
946
+ this.selectedFieldId.set(field.id);
947
+ this.fieldSelected.emit({ field, section: section ?? undefined });
948
+ }
949
+ selectSection(section) {
950
+ this.selectedFieldId.set(section.id);
951
+ }
952
+ selectCanvasField(field, section) {
953
+ this.selectField(field, section);
954
+ this.openActualFieldsTreeForField(field.id);
955
+ }
956
+ selectFieldTreeNode(node) {
957
+ if (node.section && !node.field) {
958
+ this.selectSection(node.section);
959
+ this.scrollCanvasSectionIntoView(node.section.id);
960
+ return;
961
+ }
962
+ if (node.field) {
963
+ if (node.section?.collapsed) {
964
+ this.updateSection(node.section, { collapsed: false });
965
+ }
966
+ this.selectField(node.field, node.section);
967
+ this.scrollCanvasFieldIntoView(node.field.id);
968
+ }
969
+ }
970
+ toggleFieldTreeNode(tree, node, event) {
971
+ event.stopPropagation();
972
+ if (!node.children?.length) {
973
+ return;
974
+ }
975
+ if (tree.isExpanded(node)) {
976
+ tree.collapse(node);
977
+ this.expandedFieldTreeNodeIds.update(ids => {
978
+ const next = new Set(ids);
979
+ next.delete(node.id);
980
+ return next;
981
+ });
982
+ return;
983
+ }
984
+ tree.expand(node);
985
+ this.expandedFieldTreeNodeIds.update(ids => new Set(ids).add(node.id));
986
+ }
987
+ isFieldTreeNodeExpanded(tree, node) {
988
+ return tree.isExpanded(node) || this.expandedFieldTreeNodeIds().has(node.id);
989
+ }
990
+ fieldTreePlaceholderIcon(source) {
991
+ return source.kind === 'section' ? 'fluent:folder-24-regular' : source.icon;
992
+ }
993
+ fieldTreeNodeDropped(event) {
994
+ if (event.source.kind === 'section') {
995
+ this.dropFieldTreeSection(event);
996
+ return;
997
+ }
998
+ if (event.source.kind !== 'field' ||
999
+ event.source.id === event.target.id ||
1000
+ (event.target.kind === 'field' && event.position === 'inside' && !this.isContainerField(event.target.field))) {
1001
+ this.resetFieldTreeAfterRejectedDrop();
1002
+ return;
1003
+ }
1004
+ const schema = cloneSchema(this.schema());
1005
+ const sourceLocation = this.findFieldLocation(schema, event.source.id);
1006
+ if (!sourceLocation) {
1007
+ this.resetFieldTreeAfterRejectedDrop();
1008
+ return;
1009
+ }
1010
+ if (event.target.kind === 'field' &&
1011
+ (sourceLocation.field.id === event.target.id || containsField(sourceLocation.field, event.target.id))) {
1012
+ this.resetFieldTreeAfterRejectedDrop();
1013
+ return;
1014
+ }
1015
+ const movingField = sourceLocation.field;
1016
+ this.detachFieldFromLocation(schema, sourceLocation);
1017
+ if (event.target.kind === 'section') {
1018
+ const inserted = event.position === 'inside'
1019
+ ? this.insertFieldIntoTreeSection(schema, movingField, event.target.id)
1020
+ : this.insertFieldAroundTreeSection(schema, movingField, event.target.id, event.position);
1021
+ if (inserted) {
1022
+ this.schema.set(schema);
1023
+ this.restoreFieldTreeExpansion();
1024
+ this.selectField(movingField, event.position === 'inside' ? inserted.section : undefined);
1025
+ return;
1026
+ }
1027
+ }
1028
+ if (event.target.kind === 'field') {
1029
+ const target = this.resolveTreeFieldInsertTarget(schema, event.target.id, event.position);
1030
+ if (target) {
1031
+ target.fields.splice(target.index, 0, movingField);
1032
+ if (target.fields === (schema.fields ?? [])) {
1033
+ this.insertRootFieldIntoLayout(schema, movingField, event.target.id, event.position);
1034
+ }
1035
+ this.schema.set(schema);
1036
+ this.restoreFieldTreeExpansion();
1037
+ this.selectField(movingField, target.section);
1038
+ return;
1039
+ }
1040
+ }
1041
+ this.resetFieldTreeAfterRejectedDrop();
1042
+ }
1043
+ confirmRemoveField(field) {
1044
+ const confirmRef = this.confirmManager.open({
1045
+ title: 'Delete field',
1046
+ description: `Delete "${field.label}" from this form? This action cannot be undone.`
1047
+ });
1048
+ confirmRef.confirmed.subscribe(() => {
1049
+ this.removeField(field);
1050
+ });
1051
+ }
1052
+ removeField(field) {
1053
+ const schema = cloneSchema(this.schema());
1054
+ const location = this.findFieldLocation(schema, field.id);
1055
+ if (!location) {
1056
+ return;
1057
+ }
1058
+ location.siblings.splice(location.index, 1);
1059
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
1060
+ this.deletePreviewControls(field);
1061
+ if (this.selectedFieldId() === field.id || containsField(field, this.selectedFieldId())) {
1062
+ this.selectedFieldId.set(null);
1063
+ }
1064
+ this.schema.set(schema);
1065
+ this.fieldRemoved.emit({ field, section: location.section });
1066
+ }
1067
+ sectionDropListId(section) {
1068
+ return `ngs-form-builder-section-${section.id}`;
1069
+ }
1070
+ fieldDropListId(field) {
1071
+ return `ngs-form-builder-field-${field.id}`;
1072
+ }
1073
+ rootDropListId() {
1074
+ return ROOT_DROP_LIST_ID;
1075
+ }
1076
+ previewControl(field) {
1077
+ const existing = this.previewControls.get(field.id);
1078
+ if (existing) {
1079
+ return existing;
1080
+ }
1081
+ const control = new FormControl({
1082
+ value: field.defaultValue ?? null,
1083
+ disabled: true
1084
+ });
1085
+ this.previewControls.set(field.id, control);
1086
+ return control;
1087
+ }
1088
+ patchSelectedField(changes) {
1089
+ const selectedId = this.selectedFieldId();
1090
+ if (!selectedId) {
1091
+ return;
1092
+ }
1093
+ const schema = cloneSchema(this.schema());
1094
+ const location = this.findFieldLocation(schema, selectedId);
1095
+ if (!location) {
1096
+ return;
1097
+ }
1098
+ const nextField = {
1099
+ ...location.field,
1100
+ ...changes
1101
+ };
1102
+ if (this.isContainerField(nextField) && !nextField.children) {
1103
+ nextField.children = [];
1104
+ }
1105
+ location.siblings[location.index] = nextField;
1106
+ this.schema.set(schema);
1107
+ this.syncPreviewControl(nextField);
1108
+ }
1109
+ isContainerField(field) {
1110
+ const definition = this.definitions().find(item => item.type === field.type);
1111
+ return definition?.acceptsChildren === true ||
1112
+ definition?.kind === 'layout' ||
1113
+ field.kind === 'layout' ||
1114
+ field.type === 'group' ||
1115
+ field.type === 'grid';
1116
+ }
1117
+ fieldIcon(field) {
1118
+ return this.definitions().find(definition => definition.type === field.type)?.icon || 'fluent:form-24-regular';
1119
+ }
1120
+ definitionWidth(definition) {
1121
+ return definition.defaults?.width ?? this.defaultWidth(definition.type);
1122
+ }
1123
+ matchesPaletteQuery(definition, query) {
1124
+ return !query ||
1125
+ definition.label.toLowerCase().includes(query) ||
1126
+ definition.type.toLowerCase().includes(query) ||
1127
+ (definition.description?.toLowerCase().includes(query) ?? false);
1128
+ }
1129
+ nativeDragOver(event, containerId, itemSelector) {
1130
+ const item = this.nativeDragItem();
1131
+ if (!item || (item.kind === 'section' && containerId !== ROOT_DROP_LIST_ID)) {
1132
+ return;
1133
+ }
1134
+ event.preventDefault();
1135
+ if (event.dataTransfer) {
1136
+ event.dataTransfer.dropEffect = 'copy';
1137
+ }
1138
+ this.setNativeDropTarget({
1139
+ containerId,
1140
+ index: this.resolveNativeDropIndex(event, itemSelector)
1141
+ });
1142
+ }
1143
+ nativeDragOverAtIndex(event, containerId, index) {
1144
+ const item = this.nativeDragItem();
1145
+ if (!item || item.kind === 'section') {
1146
+ return;
1147
+ }
1148
+ event.preventDefault();
1149
+ if (event.dataTransfer) {
1150
+ event.dataTransfer.dropEffect = 'copy';
1151
+ }
1152
+ this.setNativeDropTarget({ containerId, index });
1153
+ }
1154
+ setNativeDropTarget(target) {
1155
+ const current = this.nativeDropTarget();
1156
+ if (current?.containerId === target?.containerId && current?.index === target?.index) {
1157
+ return;
1158
+ }
1159
+ const previousRects = this.captureCanvasItemRects();
1160
+ this.nativeDropTarget.set(target);
1161
+ this.afterNextPaint(() => this.animateCanvasItemMoves(previousRects));
1162
+ }
1163
+ captureCanvasItemRects() {
1164
+ const rects = new Map();
1165
+ for (const element of this.getCanvasAnimatedItems()) {
1166
+ const key = this.getCanvasAnimatedItemKey(element);
1167
+ if (key) {
1168
+ rects.set(key, element.getBoundingClientRect());
1169
+ }
1170
+ }
1171
+ return rects;
1172
+ }
1173
+ animateCanvasItemMoves(previousRects) {
1174
+ if (!previousRects.size) {
1175
+ return;
1176
+ }
1177
+ for (const element of this.getCanvasAnimatedItems()) {
1178
+ const key = this.getCanvasAnimatedItemKey(element);
1179
+ const previousRect = key ? previousRects.get(key) : undefined;
1180
+ if (!previousRect) {
1181
+ continue;
1182
+ }
1183
+ const nextRect = element.getBoundingClientRect();
1184
+ const deltaX = previousRect.left - nextRect.left;
1185
+ const deltaY = previousRect.top - nextRect.top;
1186
+ if (Math.abs(deltaX) < 0.5 && Math.abs(deltaY) < 0.5) {
1187
+ continue;
1188
+ }
1189
+ const animationTimer = this.canvasAnimationTimers.get(element);
1190
+ if (animationTimer) {
1191
+ window.clearTimeout(animationTimer);
1192
+ }
1193
+ element.style.transition = 'none';
1194
+ element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
1195
+ element.getBoundingClientRect();
1196
+ element.style.transition = 'transform 160ms cubic-bezier(0, 0, 0.2, 1)';
1197
+ element.style.transform = '';
1198
+ this.canvasAnimationTimers.set(element, window.setTimeout(() => {
1199
+ element.style.transition = '';
1200
+ element.style.transform = '';
1201
+ }, 180));
1202
+ }
1203
+ }
1204
+ getCanvasAnimatedItems() {
1205
+ 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]'));
1206
+ }
1207
+ getCanvasAnimatedItemKey(element) {
1208
+ const fieldId = element.dataset['formBuilderFieldId'];
1209
+ if (fieldId) {
1210
+ return `field:${fieldId}`;
1211
+ }
1212
+ const sectionId = element.dataset['formBuilderSectionId'];
1213
+ return sectionId ? `section:${sectionId}` : null;
1214
+ }
1215
+ insertNativeField(definition, containerId, index) {
1216
+ const schema = cloneSchema(this.schema());
1217
+ const field = this.createField(definition, schema);
1218
+ if (containerId === ROOT_DROP_LIST_ID) {
1219
+ const layout = this.normalizedLayout(schema);
1220
+ schema.fields ??= [];
1221
+ schema.fields.push(field);
1222
+ layout.splice(clampIndex(index, layout.length), 0, { kind: 'field', id: field.id });
1223
+ schema.layout = layout;
1224
+ this.schema.set(schema);
1225
+ this.selectField(field);
1226
+ this.fieldAdded.emit({ field });
1227
+ return;
1228
+ }
1229
+ const targetContainer = this.findContainerLocation(schema, containerId);
1230
+ if (!targetContainer) {
1231
+ return;
1232
+ }
1233
+ targetContainer.fields.splice(clampIndex(index, targetContainer.fields.length), 0, field);
1234
+ this.schema.set(schema);
1235
+ this.selectField(field, targetContainer.section);
1236
+ this.fieldAdded.emit({ field, section: targetContainer.section });
1237
+ }
1238
+ insertNativeSection(index) {
1239
+ const schema = cloneSchema(this.schema());
1240
+ const section = {
1241
+ id: uniqueId('section'),
1242
+ title: `Section ${schema.sections.length + 1}`,
1243
+ fields: []
1244
+ };
1245
+ const layout = this.normalizedLayout(schema);
1246
+ schema.sections.push(section);
1247
+ layout.splice(clampIndex(index, layout.length), 0, { kind: 'section', id: section.id });
1248
+ schema.layout = layout;
1249
+ this.schema.set(schema);
1250
+ }
1251
+ resolveNativeDragItem(event) {
1252
+ const currentItem = this.nativeDragItem();
1253
+ if (currentItem) {
1254
+ return currentItem;
1255
+ }
1256
+ if (event.dataTransfer?.getData(PALETTE_DRAG_ITEM) === 'section') {
1257
+ return { kind: 'section' };
1258
+ }
1259
+ const type = event.dataTransfer?.getData(PALETTE_DRAG_TYPE);
1260
+ const definition = type ? this.definitions().find(item => item.type === type) ?? null : null;
1261
+ return definition ? { kind: 'field', definition } : null;
1262
+ }
1263
+ resolveNativeDropIndex(event, itemSelector) {
1264
+ const container = event.currentTarget;
1265
+ if (!(container instanceof HTMLElement)) {
1266
+ return 0;
1267
+ }
1268
+ const items = Array.from(container.children).filter((child) => {
1269
+ return child instanceof HTMLElement && child.matches(itemSelector);
1270
+ });
1271
+ for (let index = 0; index < items.length; index += 1) {
1272
+ const rect = items[index].getBoundingClientRect();
1273
+ const centerY = rect.top + rect.height / 2;
1274
+ const centerX = rect.left + rect.width / 2;
1275
+ const sameRow = event.clientY >= rect.top && event.clientY <= rect.bottom;
1276
+ if (event.clientY < centerY || (sameRow && event.clientX < centerX)) {
1277
+ return index;
1278
+ }
1279
+ }
1280
+ return items.length;
1281
+ }
1282
+ setPaletteDragImage(event) {
1283
+ const source = event.currentTarget;
1284
+ if (!(source instanceof HTMLElement) || !event.dataTransfer) {
1285
+ return;
1286
+ }
1287
+ const clone = source.cloneNode(true);
1288
+ if (!(clone instanceof HTMLElement)) {
1289
+ return;
1290
+ }
1291
+ clone.classList.add('ngs-form-builder-native-drag-image');
1292
+ source.parentElement?.appendChild(clone);
1293
+ const rect = source.getBoundingClientRect();
1294
+ event.dataTransfer.setDragImage(clone, Math.min(event.offsetX, rect.width), Math.min(event.offsetY, rect.height));
1295
+ window.setTimeout(() => clone.remove());
1296
+ }
1297
+ createField(definition, schema) {
1298
+ const baseLabel = definition.defaults?.label || definition.label;
1299
+ const name = uniqueFieldName(toFieldName(baseLabel), schema);
1300
+ const kind = definition.defaults?.kind ?? definition.kind ?? 'field';
1301
+ return {
1302
+ id: uniqueId('field'),
1303
+ name,
1304
+ type: definition.type,
1305
+ kind,
1306
+ label: baseLabel,
1307
+ width: definition.defaults?.width ?? this.defaultWidth(definition.type),
1308
+ visibility: {
1309
+ form: true,
1310
+ email: true,
1311
+ pdf: true
1312
+ },
1313
+ ...definition.defaults
1314
+ };
1315
+ }
1316
+ defaultWidth(type) {
1317
+ return 12;
1318
+ }
1319
+ resolveCanvasItems(schema) {
1320
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
1321
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
1322
+ const items = [];
1323
+ for (const item of this.normalizedLayout(schema)) {
1324
+ if (item.kind === 'field') {
1325
+ const field = fieldsById.get(item.id);
1326
+ if (field) {
1327
+ items.push({ ...item, field });
1328
+ }
1329
+ continue;
1330
+ }
1331
+ const section = sectionsById.get(item.id);
1332
+ if (section) {
1333
+ items.push({ ...item, section });
1334
+ }
1335
+ }
1336
+ return items;
1337
+ }
1338
+ normalizedLayout(schema) {
1339
+ const used = new Set();
1340
+ const layout = [];
1341
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
1342
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
1343
+ for (const item of schema.layout ?? []) {
1344
+ const key = `${item.kind}:${item.id}`;
1345
+ if (used.has(key)) {
1346
+ continue;
1347
+ }
1348
+ if ((item.kind === 'field' && fieldsById.has(item.id)) || (item.kind === 'section' && sectionsById.has(item.id))) {
1349
+ used.add(key);
1350
+ layout.push(item);
1351
+ }
1352
+ }
1353
+ for (const field of schema.fields ?? []) {
1354
+ const key = `field:${field.id}`;
1355
+ if (!used.has(key)) {
1356
+ used.add(key);
1357
+ layout.push({ kind: 'field', id: field.id });
1358
+ }
1359
+ }
1360
+ for (const section of schema.sections) {
1361
+ const key = `section:${section.id}`;
1362
+ if (!used.has(key)) {
1363
+ used.add(key);
1364
+ layout.push({ kind: 'section', id: section.id });
1365
+ }
1366
+ }
1367
+ return layout;
1368
+ }
1369
+ detachFieldFromLocation(schema, location) {
1370
+ location.siblings.splice(location.index, 1);
1371
+ if (location.siblings === (schema.fields ?? [])) {
1372
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === location.field.id));
1373
+ }
1374
+ }
1375
+ insertFieldIntoTreeSection(schema, field, sectionId) {
1376
+ const section = schema.sections.find(item => item.id === sectionId);
1377
+ if (!section) {
1378
+ return null;
1379
+ }
1380
+ section.fields.push(field);
1381
+ return {
1382
+ fields: section.fields,
1383
+ index: section.fields.length - 1,
1384
+ section
1385
+ };
1386
+ }
1387
+ insertFieldAroundTreeSection(schema, field, sectionId, position) {
1388
+ if (position === 'inside') {
1389
+ return this.insertFieldIntoTreeSection(schema, field, sectionId);
1390
+ }
1391
+ const section = schema.sections.find(item => item.id === sectionId);
1392
+ if (!section) {
1393
+ return null;
1394
+ }
1395
+ schema.fields ??= [];
1396
+ if (!schema.fields.some(item => item.id === field.id)) {
1397
+ schema.fields.push(field);
1398
+ }
1399
+ const layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
1400
+ const sectionIndex = layout.findIndex(item => item.kind === 'section' && item.id === sectionId);
1401
+ layout.splice(sectionIndex === -1 ? layout.length : sectionIndex + (position === 'after' ? 1 : 0), 0, {
1402
+ kind: 'field',
1403
+ id: field.id
1404
+ });
1405
+ schema.layout = layout;
1406
+ return {
1407
+ fields: schema.fields,
1408
+ index: schema.fields.findIndex(item => item.id === field.id)
1409
+ };
1410
+ }
1411
+ resolveTreeFieldInsertTarget(schema, targetFieldId, position) {
1412
+ const targetLocation = this.findFieldLocation(schema, targetFieldId);
1413
+ if (!targetLocation) {
1414
+ return null;
1415
+ }
1416
+ if (position === 'inside' && this.isContainerField(targetLocation.field)) {
1417
+ targetLocation.field.children ??= [];
1418
+ return {
1419
+ fields: targetLocation.field.children,
1420
+ index: targetLocation.field.children.length,
1421
+ section: targetLocation.section
1422
+ };
1423
+ }
1424
+ const insertIndex = position === 'before' ? targetLocation.index : targetLocation.index + 1;
1425
+ return {
1426
+ fields: targetLocation.siblings,
1427
+ index: insertIndex,
1428
+ section: targetLocation.section
1429
+ };
1430
+ }
1431
+ insertRootFieldIntoLayout(schema, field, targetFieldId, position) {
1432
+ schema.fields ??= [];
1433
+ if (!schema.fields.some(item => item.id === field.id)) {
1434
+ schema.fields.push(field);
1435
+ }
1436
+ const layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
1437
+ const targetIndex = layout.findIndex(item => item.kind === 'field' && item.id === targetFieldId);
1438
+ layout.splice(targetIndex === -1 ? layout.length : targetIndex + (position === 'before' ? 0 : 1), 0, {
1439
+ kind: 'field',
1440
+ id: field.id
1441
+ });
1442
+ schema.layout = layout;
1443
+ }
1444
+ resetFieldTreeAfterRejectedDrop() {
1445
+ this.schema.set(cloneSchema(this.schema()));
1446
+ this.restoreFieldTreeExpansion();
1447
+ }
1448
+ canDropFieldTreeNode(source, target, position) {
1449
+ if (source.id === target.id) {
1450
+ return false;
1451
+ }
1452
+ if (source.kind === 'section') {
1453
+ return position !== 'inside' && (target.kind === 'section' || this.isRootFieldTreeNode(target));
1454
+ }
1455
+ if (target.kind === 'field' && position === 'inside') {
1456
+ return !!target.field && this.isContainerField(target.field);
1457
+ }
1458
+ return true;
1459
+ }
1460
+ dropFieldTreeSection(event) {
1461
+ if (!this.canDropFieldTreeNode(event.source, event.target, event.position)) {
1462
+ this.resetFieldTreeAfterRejectedDrop();
1463
+ return;
1464
+ }
1465
+ const schema = cloneSchema(this.schema());
1466
+ const layout = this.normalizedLayout(schema);
1467
+ const sourceIndex = layout.findIndex(item => item.kind === 'section' && item.id === event.source.id);
1468
+ const targetKind = event.target.kind === 'section' ? 'section' : 'field';
1469
+ const targetIndex = layout.findIndex(item => item.kind === targetKind && item.id === event.target.id);
1470
+ if (sourceIndex < 0 || targetIndex < 0) {
1471
+ this.resetFieldTreeAfterRejectedDrop();
1472
+ return;
1473
+ }
1474
+ const [sectionItem] = layout.splice(sourceIndex, 1);
1475
+ const adjustedTargetIndex = sourceIndex < targetIndex ? targetIndex - 1 : targetIndex;
1476
+ layout.splice(adjustedTargetIndex + (event.position === 'after' ? 1 : 0), 0, sectionItem);
1477
+ schema.layout = layout;
1478
+ this.schema.set(schema);
1479
+ this.restoreFieldTreeExpansion();
1480
+ }
1481
+ isRootFieldTreeNode(node) {
1482
+ if (node.kind !== 'field') {
1483
+ return false;
1484
+ }
1485
+ return this.resolveCanvasItems(this.schema()).some(item => item.kind === 'field' && item.id === node.id);
1486
+ }
1487
+ restoreFieldTreeExpansion() {
1488
+ const expandedIds = this.expandedFieldTreeNodeIds();
1489
+ if (!expandedIds.size) {
1490
+ return;
1491
+ }
1492
+ this.afterNextPaint(() => {
1493
+ this.afterNextPaint(() => {
1494
+ const tree = this.actualFieldsTree();
1495
+ if (!tree) {
1496
+ return;
1497
+ }
1498
+ for (const node of this.flattenFieldTree(this.fieldTree())) {
1499
+ if (node.children?.length && expandedIds.has(node.id)) {
1500
+ tree.expand(node);
1501
+ }
1502
+ }
1503
+ });
1504
+ });
1505
+ }
1506
+ flattenFieldTree(nodes) {
1507
+ return nodes.flatMap(node => [node, ...this.flattenFieldTree(node.children ?? [])]);
1508
+ }
1509
+ openActualFieldsTreeForField(fieldId) {
1510
+ const path = this.findFieldTreeNodePath(this.fieldTree(), fieldId);
1511
+ this.fieldsTabIndex.set(ACTUAL_FIELDS_TAB_INDEX);
1512
+ if (!path.length) {
1513
+ return;
1514
+ }
1515
+ const expandableIds = path
1516
+ .slice(0, -1)
1517
+ .filter(node => !!node.children?.length)
1518
+ .map(node => node.id);
1519
+ if (expandableIds.length) {
1520
+ this.expandedFieldTreeNodeIds.update(ids => {
1521
+ const next = new Set(ids);
1522
+ for (const id of expandableIds) {
1523
+ next.add(id);
1524
+ }
1525
+ return next;
1526
+ });
1527
+ }
1528
+ this.afterNextPaint(() => {
1529
+ this.afterNextPaint(() => {
1530
+ const tree = this.actualFieldsTree();
1531
+ if (tree) {
1532
+ for (const node of path.slice(0, -1)) {
1533
+ if (node.children?.length) {
1534
+ tree.expand(node);
1535
+ }
1536
+ }
1537
+ }
1538
+ this.scrollFieldTreeNodeIntoView(fieldId);
1539
+ });
1540
+ });
1541
+ }
1542
+ findFieldTreeNodePath(nodes, fieldId, path = []) {
1543
+ for (const node of nodes) {
1544
+ const nextPath = [...path, node];
1545
+ if (node.id === fieldId) {
1546
+ return nextPath;
1547
+ }
1548
+ const childPath = this.findFieldTreeNodePath(node.children ?? [], fieldId, nextPath);
1549
+ if (childPath.length) {
1550
+ return childPath;
1551
+ }
1552
+ }
1553
+ return [];
1554
+ }
1555
+ scrollFieldTreeNodeIntoView(fieldId) {
1556
+ const target = this.findFieldTreeNodeElement(fieldId);
1557
+ if (!target) {
1558
+ return;
1559
+ }
1560
+ const scrollableContent = target
1561
+ .closest('ngs-scrollbar-area')
1562
+ ?.querySelector('.scrollable-content');
1563
+ if (!scrollableContent) {
1564
+ target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
1565
+ return;
1566
+ }
1567
+ this.scrollElementFullyIntoView(target, scrollableContent);
1568
+ }
1569
+ findFieldTreeNodeElement(fieldId) {
1570
+ const nodes = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-palette [data-form-builder-tree-node-id]');
1571
+ return Array.from(nodes).find(node => node.dataset['formBuilderTreeNodeId'] === fieldId) ?? null;
1572
+ }
1573
+ scrollCanvasFieldIntoView(fieldId) {
1574
+ this.scrollCanvasItemIntoView(() => this.findCanvasFieldElement(fieldId));
1575
+ }
1576
+ scrollCanvasSectionIntoView(sectionId) {
1577
+ this.scrollCanvasItemIntoView(() => this.findCanvasSectionElement(sectionId));
1578
+ }
1579
+ scrollCanvasItemIntoView(resolveTarget) {
1580
+ this.afterNextPaint(() => {
1581
+ this.afterNextPaint(() => {
1582
+ const target = resolveTarget();
1583
+ if (!target) {
1584
+ return;
1585
+ }
1586
+ const scrollableContent = target
1587
+ .closest('ngs-scrollbar-area')
1588
+ ?.querySelector('.scrollable-content');
1589
+ if (!scrollableContent) {
1590
+ target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
1591
+ return;
1592
+ }
1593
+ this.scrollElementFullyIntoView(target, scrollableContent);
1594
+ });
1595
+ });
1596
+ }
1597
+ findCanvasFieldElement(fieldId) {
1598
+ const fields = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-field-id]');
1599
+ return Array.from(fields).find(field => field.dataset['formBuilderFieldId'] === fieldId) ?? null;
1600
+ }
1601
+ findCanvasSectionElement(sectionId) {
1602
+ const sections = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-section-id]');
1603
+ return Array.from(sections).find(section => section.dataset['formBuilderSectionId'] === sectionId) ?? null;
1604
+ }
1605
+ syncPreviewControl(field) {
1606
+ const control = this.previewControls.get(field.id);
1607
+ if (!control) {
1608
+ return;
1609
+ }
1610
+ queueMicrotask(() => {
1611
+ const value = field.defaultValue ?? null;
1612
+ if (control.value !== value) {
1613
+ control.setValue(value, { emitEvent: false });
1614
+ }
1615
+ if (control.enabled) {
1616
+ control.disable({ emitEvent: false });
1617
+ }
1618
+ });
1619
+ }
1620
+ scrollElementFullyIntoView(target, scrollableContent) {
1621
+ const padding = 16;
1622
+ const containerRect = scrollableContent.getBoundingClientRect();
1623
+ const targetRect = target.getBoundingClientRect();
1624
+ const availableHeight = Math.max(0, containerRect.height - padding * 2);
1625
+ const availableWidth = Math.max(0, containerRect.width - padding * 2);
1626
+ let top = scrollableContent.scrollTop;
1627
+ let left = scrollableContent.scrollLeft;
1628
+ if (targetRect.height > availableHeight || targetRect.top < containerRect.top + padding) {
1629
+ top += targetRect.top - containerRect.top - padding;
1630
+ }
1631
+ else if (targetRect.bottom > containerRect.bottom - padding) {
1632
+ top += targetRect.bottom - containerRect.bottom + padding;
1633
+ }
1634
+ if (targetRect.width > availableWidth || targetRect.left < containerRect.left + padding) {
1635
+ left += targetRect.left - containerRect.left - padding;
1636
+ }
1637
+ else if (targetRect.right > containerRect.right - padding) {
1638
+ left += targetRect.right - containerRect.right + padding;
1639
+ }
1640
+ scrollableContent.scrollTo({
1641
+ top: Math.max(0, top),
1642
+ left: Math.max(0, left),
1643
+ behavior: 'smooth'
1644
+ });
1645
+ }
1646
+ afterNextPaint(callback) {
1647
+ if (typeof requestAnimationFrame === 'function') {
1648
+ requestAnimationFrame(callback);
1649
+ return;
1650
+ }
1651
+ window.setTimeout(callback);
1652
+ }
1653
+ upsertSectionTreeNode(section) {
1654
+ const node = this.getCachedFieldTreeNode(section.id);
1655
+ const children = section.fields.map(field => this.upsertFieldTreeNode(field, section));
1656
+ node.label = section.title;
1657
+ node.name = undefined;
1658
+ node.type = 'section';
1659
+ node.icon = 'fluent:folder-24-regular';
1660
+ node.kind = 'section';
1661
+ node.field = undefined;
1662
+ node.section = section;
1663
+ node.children ??= [];
1664
+ replaceArrayContents(node.children, children);
1665
+ return node;
1666
+ }
1667
+ upsertFieldTreeNode(field, section) {
1668
+ const definition = this.definitions().find(item => item.type === field.type);
1669
+ const node = this.getCachedFieldTreeNode(field.id);
1670
+ const children = field.children?.map(child => this.upsertFieldTreeNode(child, section));
1671
+ node.label = field.label;
1672
+ node.name = field.name;
1673
+ node.type = field.type;
1674
+ node.icon = definition?.icon || 'fluent:form-24-regular';
1675
+ node.kind = 'field';
1676
+ node.field = field;
1677
+ node.section = section;
1678
+ if (children?.length) {
1679
+ node.children ??= [];
1680
+ replaceArrayContents(node.children, children);
1681
+ }
1682
+ else {
1683
+ node.children = undefined;
1684
+ }
1685
+ return node;
1686
+ }
1687
+ getCachedFieldTreeNode(id) {
1688
+ const cached = this.fieldTreeNodeCache.get(id);
1689
+ if (cached) {
1690
+ return cached;
1691
+ }
1692
+ const node = {
1693
+ id,
1694
+ label: '',
1695
+ type: '',
1696
+ icon: 'fluent:form-24-regular',
1697
+ kind: 'field'
1698
+ };
1699
+ this.fieldTreeNodeCache.set(id, node);
1700
+ return node;
1701
+ }
1702
+ resolveFieldTreeStructureKey(nodes) {
1703
+ return nodes
1704
+ .map(node => `${node.kind}:${node.id}[${this.resolveFieldTreeStructureKey(node.children ?? [])}]`)
1705
+ .join('|');
1706
+ }
1707
+ findContainerLocation(schema, containerId) {
1708
+ if (containerId === ROOT_DROP_LIST_ID) {
1709
+ return {
1710
+ id: containerId,
1711
+ fields: schema.fields ?? []
1712
+ };
1713
+ }
1714
+ const rootNested = this.findFieldContainerLocation(schema.fields ?? [], undefined, containerId);
1715
+ if (rootNested) {
1716
+ return rootNested;
1717
+ }
1718
+ for (const section of schema.sections) {
1719
+ if (this.sectionDropListId(section) === containerId) {
1720
+ return {
1721
+ id: containerId,
1722
+ fields: section.fields,
1723
+ section
1724
+ };
1725
+ }
1726
+ const nested = this.findFieldContainerLocation(section.fields, section, containerId);
1727
+ if (nested) {
1728
+ return nested;
1729
+ }
1730
+ }
1731
+ return null;
1732
+ }
1733
+ findFieldContainerLocation(fields, section, containerId) {
1734
+ for (const field of fields) {
1735
+ if (this.isContainerField(field) && this.fieldDropListId(field) === containerId) {
1736
+ field.children ??= [];
1737
+ return {
1738
+ id: containerId,
1739
+ fields: field.children,
1740
+ section,
1741
+ owner: field
1742
+ };
1743
+ }
1744
+ const nested = this.findFieldContainerLocation(field.children ?? [], section, containerId);
1745
+ if (nested) {
1746
+ return nested;
1747
+ }
1748
+ }
1749
+ return null;
1750
+ }
1751
+ findFieldLocation(schema, fieldId) {
1752
+ const rootLocation = findFieldLocationInFields(schema.fields ?? [], fieldId);
1753
+ if (rootLocation) {
1754
+ return rootLocation;
1755
+ }
1756
+ for (const section of schema.sections) {
1757
+ const location = findFieldLocationInFields(section.fields, fieldId, section);
1758
+ if (location) {
1759
+ return location;
1760
+ }
1761
+ }
1762
+ return null;
1763
+ }
1764
+ deletePreviewControls(field) {
1765
+ this.previewControls.delete(field.id);
1766
+ for (const child of field.children ?? []) {
1767
+ this.deletePreviewControls(child);
1768
+ }
1769
+ }
1770
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1771
+ 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=\"Placeholders\" 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=\"Actual fields\" 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 <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 }\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)}: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-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 });
1772
+ }
1773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, decorators: [{
1774
+ type: Component,
1775
+ args: [{ selector: 'ngs-form-builder', exportAs: 'ngsFormBuilder', imports: [
1776
+ NgTemplateOutlet,
1777
+ FormsModule,
1778
+ Button,
1779
+ Card,
1780
+ CardAside,
1781
+ CardContent,
1782
+ CardHeader,
1783
+ DialogActions,
1784
+ DialogClose,
1785
+ DialogContent,
1786
+ DialogTitle,
1787
+ Icon,
1788
+ Input,
1789
+ Panel,
1790
+ PanelAside,
1791
+ PanelContent,
1792
+ PanelHeader,
1793
+ PanelSidebar,
1794
+ ScrollbarArea,
1795
+ Tab,
1796
+ TabGroup,
1797
+ Tree,
1798
+ TreeDragPlaceholder,
1799
+ TreeNode,
1800
+ TreeNodeDef,
1801
+ TreeNodePadding,
1802
+ FormBuilderFieldHost,
1803
+ FormBuilderRenderer,
1804
+ FormBuilderSettingsHost
1805
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1806
+ 'class': 'ngs-form-builder'
1807
+ }, 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=\"Placeholders\" 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=\"Actual fields\" 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 <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 }\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)}: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-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"] }]
1808
+ }], 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"] }] } });
1809
+ function createDefaultFormBuilderSchema() {
1810
+ const sectionId = uniqueId('section');
1811
+ return {
1812
+ title: 'New form',
1813
+ fields: [],
1814
+ layout: [{ kind: 'section', id: sectionId }],
1815
+ sections: [
1816
+ {
1817
+ id: sectionId,
1818
+ title: 'General information',
1819
+ fields: []
1820
+ }
1821
+ ]
1822
+ };
1823
+ }
1824
+ function uniqueId(prefix) {
1825
+ return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
1826
+ }
1827
+ function cloneSchema(schema) {
1828
+ return {
1829
+ ...schema,
1830
+ fields: (schema.fields ?? []).map(cloneField),
1831
+ layout: schema.layout?.map(item => ({ ...item })),
1832
+ sections: schema.sections.map(section => ({
1833
+ ...section,
1834
+ fields: section.fields.map(cloneField)
1835
+ }))
1836
+ };
1837
+ }
1838
+ function cloneField(field) {
1839
+ return {
1840
+ ...field,
1841
+ options: field.options?.map(option => ({ ...option })),
1842
+ validation: field.validation?.map(rule => ({ ...rule })),
1843
+ visibility: field.visibility ? { ...field.visibility } : undefined,
1844
+ settings: field.settings ? { ...field.settings } : undefined,
1845
+ children: field.children?.map(cloneField)
1846
+ };
1847
+ }
1848
+ function clampIndex(index, length) {
1849
+ return Math.max(0, Math.min(index, length));
1850
+ }
1851
+ function replaceArrayContents(target, source) {
1852
+ target.splice(0, target.length, ...source);
1853
+ }
1854
+ function normalizeFieldDefinition(definition) {
1855
+ return {
1856
+ ...definition,
1857
+ kind: definition.kind ?? 'field'
1858
+ };
1859
+ }
1860
+ function toFieldName(label) {
1861
+ return label
1862
+ .trim()
1863
+ .toLowerCase()
1864
+ .replace(/[^a-z0-9]+/g, '_')
1865
+ .replace(/^_+|_+$/g, '') || 'field';
1866
+ }
1867
+ function uniqueFieldName(baseName, schema) {
1868
+ const usedNames = new Set([
1869
+ ...(schema.fields ?? []).flatMap(field => collectFieldNames(field)),
1870
+ ...schema.sections.flatMap(section => section.fields.flatMap(field => collectFieldNames(field)))
1871
+ ]);
1872
+ if (!usedNames.has(baseName)) {
1873
+ return baseName;
1874
+ }
1875
+ let index = 2;
1876
+ let nextName = `${baseName}_${index}`;
1877
+ while (usedNames.has(nextName)) {
1878
+ index += 1;
1879
+ nextName = `${baseName}_${index}`;
1880
+ }
1881
+ return nextName;
1882
+ }
1883
+ function collectFieldNames(field) {
1884
+ return [field.name, ...(field.children ?? []).flatMap(collectFieldNames)];
1885
+ }
1886
+ function findFieldLocationInFields(fields, fieldId, section) {
1887
+ const index = fields.findIndex(field => field.id === fieldId);
1888
+ if (index !== -1) {
1889
+ return {
1890
+ field: fields[index],
1891
+ section,
1892
+ siblings: fields,
1893
+ index
1894
+ };
1895
+ }
1896
+ for (const field of fields) {
1897
+ const location = findFieldLocationInFields(field.children ?? [], fieldId, section);
1898
+ if (location) {
1899
+ return location;
1900
+ }
1901
+ }
1902
+ return null;
1903
+ }
1904
+ function containsField(field, fieldId) {
1905
+ if (!fieldId) {
1906
+ return false;
1907
+ }
1908
+ return (field.children ?? []).some(child => child.id === fieldId || containsField(child, fieldId));
1909
+ }
1910
+
1911
+ /**
1912
+ * Generated bundle index. Do not edit.
1913
+ */
1914
+
1915
+ 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 };
1916
+ //# sourceMappingURL=ngstarter-ui-components-form-builder.mjs.map