@ngstarter-ui/components 21.0.39 → 21.0.40

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,1695 @@
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 { moveItemInArray, transferArrayItem, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDragPreview, CdkDropList, CdkDropListGroup } from '@angular/cdk/drag-drop';
6
+ import { NgTemplateOutlet } from '@angular/common';
7
+ import { Button } from '@ngstarter-ui/components/button';
8
+ import { Card, CardAside, CardContent, CardHeader } from '@ngstarter-ui/components/card';
9
+ import { ConfirmManager } from '@ngstarter-ui/components/confirm';
10
+ import { Dialog, DialogActions, DialogClose, DialogContent, DialogTitle } from '@ngstarter-ui/components/dialog';
11
+ import { FormField, Hint, IconButtonSuffix, Label } from '@ngstarter-ui/components/form-field';
12
+ import { Icon } from '@ngstarter-ui/components/icon';
13
+ import { Input } from '@ngstarter-ui/components/input';
14
+ import { Panel, PanelAside, PanelContent, PanelHeader, PanelSidebar } from '@ngstarter-ui/components/panel';
15
+ import { ScrollbarArea } from '@ngstarter-ui/components/scrollbar-area';
16
+ import { Tab, TabGroup } from '@ngstarter-ui/components/tabs';
17
+ import { Tree, TreeDragPlaceholder, TreeNode, TreeNodeDef, TreeNodePadding } from '@ngstarter-ui/components/tree';
18
+ import { Datepicker, DatepickerInput, DatepickerToggle, provideNativeDateAdapter } from '@ngstarter-ui/components/datepicker';
19
+ import { Select } from '@ngstarter-ui/components/select';
20
+ import { Option } from '@ngstarter-ui/components/option';
21
+ import { Checkbox } from '@ngstarter-ui/components/checkbox';
22
+ import { SlideToggle } from '@ngstarter-ui/components/slide-toggle';
23
+
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 formBuilderSettings(definition) {
30
+ return definition;
31
+ }
32
+ function provideFormBuilder(config = {}) {
33
+ return makeEnvironmentProviders([
34
+ ...(config.fields ?? []).map(field => ({
35
+ provide: FORM_BUILDER_FIELDS,
36
+ useValue: field,
37
+ multi: true
38
+ })),
39
+ ...(config.settings ?? []).map(settings => ({
40
+ provide: FORM_BUILDER_SETTINGS,
41
+ useValue: settings,
42
+ multi: true
43
+ }))
44
+ ]);
45
+ }
46
+ const DEFAULT_FORM_BUILDER_FIELDS = [
47
+ {
48
+ type: 'text',
49
+ label: 'Text',
50
+ group: 'Basic',
51
+ icon: 'fluent:text-font-24-regular',
52
+ defaults: {
53
+ label: 'Text field',
54
+ placeholder: 'Enter text'
55
+ }
56
+ },
57
+ {
58
+ type: 'number',
59
+ label: 'Number',
60
+ group: 'Basic',
61
+ icon: 'fluent:number-symbol-24-regular',
62
+ defaults: {
63
+ label: 'Number',
64
+ placeholder: '0'
65
+ }
66
+ },
67
+ {
68
+ type: 'email',
69
+ label: 'Email',
70
+ group: 'Basic',
71
+ icon: 'fluent:mail-24-regular',
72
+ defaults: {
73
+ label: 'Email',
74
+ placeholder: 'name@example.com'
75
+ },
76
+ validators: field => field.required ? [Validators.required, Validators.email] : [Validators.email]
77
+ },
78
+ {
79
+ type: 'textarea',
80
+ label: 'Textarea',
81
+ group: 'Basic',
82
+ icon: 'fluent:text-description-24-regular',
83
+ defaults: {
84
+ label: 'Description',
85
+ placeholder: 'Enter description',
86
+ width: 12
87
+ }
88
+ },
89
+ {
90
+ type: 'grid',
91
+ label: 'Grid',
92
+ group: 'Layout',
93
+ icon: 'fluent:grid-24-regular',
94
+ description: 'Container for nested fields.',
95
+ defaults: {
96
+ label: 'Grid',
97
+ width: 12,
98
+ children: []
99
+ }
100
+ },
101
+ {
102
+ type: 'select',
103
+ label: 'Select',
104
+ group: 'Choices',
105
+ icon: 'fluent:multiselect-ltr-24-regular',
106
+ defaults: {
107
+ label: 'Select',
108
+ placeholder: 'Choose an option',
109
+ options: [
110
+ { label: 'Option 1', value: 'option_1' },
111
+ { label: 'Option 2', value: 'option_2' }
112
+ ]
113
+ }
114
+ },
115
+ {
116
+ type: 'checkbox',
117
+ label: 'Checkbox',
118
+ group: 'Choices',
119
+ icon: 'fluent:checkbox-checked-24-regular',
120
+ defaults: {
121
+ label: 'Checkbox',
122
+ defaultValue: false
123
+ }
124
+ },
125
+ {
126
+ type: 'toggle',
127
+ label: 'Toggle',
128
+ group: 'Choices',
129
+ icon: 'fluent:toggle-right-24-regular',
130
+ defaults: {
131
+ label: 'Toggle',
132
+ defaultValue: false
133
+ }
134
+ },
135
+ {
136
+ type: 'date',
137
+ label: 'Date',
138
+ group: 'Date and time',
139
+ icon: 'fluent:calendar-ltr-24-regular',
140
+ defaults: {
141
+ label: 'Date',
142
+ placeholder: 'Select date'
143
+ }
144
+ },
145
+ {
146
+ type: 'currency',
147
+ label: 'Currency',
148
+ group: 'Finance',
149
+ icon: 'fluent:money-24-regular',
150
+ defaults: {
151
+ label: 'Amount',
152
+ placeholder: '0.00'
153
+ }
154
+ }
155
+ ];
156
+ function validatorsFromRules(rules = [], field) {
157
+ const validators = [];
158
+ if (field?.required || rules.some(rule => rule.type === 'required')) {
159
+ validators.push(Validators.required);
160
+ }
161
+ for (const rule of rules) {
162
+ if (rule.type === 'email') {
163
+ validators.push(Validators.email);
164
+ }
165
+ else if (rule.type === 'minLength') {
166
+ validators.push(Validators.minLength(Number(rule.value)));
167
+ }
168
+ else if (rule.type === 'maxLength') {
169
+ validators.push(Validators.maxLength(Number(rule.value)));
170
+ }
171
+ else if (rule.type === 'min') {
172
+ validators.push(Validators.min(Number(rule.value)));
173
+ }
174
+ else if (rule.type === 'max') {
175
+ validators.push(Validators.max(Number(rule.value)));
176
+ }
177
+ }
178
+ return validators;
179
+ }
180
+
181
+ class FormBuilderFieldHost {
182
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
183
+ control = input.required(...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
184
+ definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
185
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
186
+ editableCanvas = input(false, ...(ngDevMode ? [{ debugName: "editableCanvas" }] : /* istanbul ignore next */ []));
187
+ customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
188
+ textInputType = computed(() => {
189
+ const type = this.field().type;
190
+ return type === 'number' || type === 'email' ? type : 'text';
191
+ }, ...(ngDevMode ? [{ debugName: "textInputType" }] : /* istanbul ignore next */ []));
192
+ anchor = viewChild.required('anchor', { read: ViewContainerRef });
193
+ constructor() {
194
+ effect(async () => {
195
+ const field = this.field();
196
+ const control = this.control();
197
+ const definitions = this.definitions();
198
+ const viewContainer = this.anchor();
199
+ const renderer = definitions.find(definition => definition.type === field.type)?.renderer;
200
+ viewContainer.clear();
201
+ this.customLoaded.set(false);
202
+ if (!renderer) {
203
+ return;
204
+ }
205
+ const componentType = await renderer();
206
+ const componentRef = viewContainer.createComponent(componentType);
207
+ componentRef.setInput('field', field);
208
+ componentRef.setInput('control', control);
209
+ componentRef.setInput('readonly', this.readonly());
210
+ this.customLoaded.set(true);
211
+ });
212
+ }
213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
214
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderFieldHost, isStandalone: true, selector: "ngs-form-builder-field-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, editableCanvas: { classPropertyName: "editableCanvas", publicName: "editableCanvas", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-custom": "customLoaded()", "class.is-width-1": "!editableCanvas() && field().width === 1", "class.is-width-2": "!editableCanvas() && field().width === 2", "class.is-width-3": "!editableCanvas() && field().width === 3", "class.is-width-4": "!editableCanvas() && field().width === 4", "class.is-width-5": "!editableCanvas() && field().width === 5", "class.is-width-6": "!editableCanvas() && field().width === 6", "class.is-width-7": "!editableCanvas() && field().width === 7", "class.is-width-8": "!editableCanvas() && field().width === 8", "class.is-width-9": "!editableCanvas() && field().width === 9", "class.is-width-10": "!editableCanvas() && field().width === 10", "class.is-width-11": "!editableCanvas() && field().width === 11", "class.is-width-12": "!editableCanvas() && (field().width ?? 12) === 12" }, classAttribute: "ngs-form-builder-field-host" }, providers: [
215
+ provideNativeDateAdapter()
216
+ ], viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderFieldHost"], ngImport: i0, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\" [placeholder]=\"field().placeholder || ''\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Datepicker, selector: "ngs-datepicker", inputs: ["startAt", "calendarHeaderComponent", "quickPresets", "showQuickPresets"], exportAs: ["ngsDatepicker"] }, { kind: "directive", type: DatepickerInput, selector: "input[ngsDatepicker]", inputs: ["ngsDatepicker"], exportAs: ["ngsDatepickerInput"] }, { kind: "component", type: DatepickerToggle, selector: "ngs-datepicker-toggle", inputs: ["for"], exportAs: ["ngsDatepickerToggle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "directive", type: IconButtonSuffix, selector: "[ngsIconButtonSuffix]" }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Checkbox, selector: "ngs-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["checkedChange", "disabledChange", "indeterminateChange", "change"], exportAs: ["ngsCheckbox"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
217
+ }
218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderFieldHost, decorators: [{
219
+ type: Component,
220
+ args: [{ selector: 'ngs-form-builder-field-host', exportAs: 'ngsFormBuilderFieldHost', imports: [
221
+ ReactiveFormsModule,
222
+ Datepicker,
223
+ DatepickerInput,
224
+ DatepickerToggle,
225
+ FormField,
226
+ Hint,
227
+ IconButtonSuffix,
228
+ Label,
229
+ Input,
230
+ Select,
231
+ Option,
232
+ Checkbox,
233
+ SlideToggle
234
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
235
+ provideNativeDateAdapter()
236
+ ], host: {
237
+ 'class': 'ngs-form-builder-field-host',
238
+ '[class.is-custom]': 'customLoaded()',
239
+ '[class.is-width-1]': '!editableCanvas() && field().width === 1',
240
+ '[class.is-width-2]': '!editableCanvas() && field().width === 2',
241
+ '[class.is-width-3]': '!editableCanvas() && field().width === 3',
242
+ '[class.is-width-4]': '!editableCanvas() && field().width === 4',
243
+ '[class.is-width-5]': '!editableCanvas() && field().width === 5',
244
+ '[class.is-width-6]': '!editableCanvas() && field().width === 6',
245
+ '[class.is-width-7]': '!editableCanvas() && field().width === 7',
246
+ '[class.is-width-8]': '!editableCanvas() && field().width === 8',
247
+ '[class.is-width-9]': '!editableCanvas() && field().width === 9',
248
+ '[class.is-width-10]': '!editableCanvas() && field().width === 10',
249
+ '[class.is-width-11]': '!editableCanvas() && field().width === 11',
250
+ '[class.is-width-12]': '!editableCanvas() && (field().width ?? 12) === 12'
251
+ }, template: "<ng-container #anchor/>\n\n@if (!customLoaded()) {\n @switch (field().type) {\n @case ('textarea') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\"></textarea>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('select') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <ngs-select [formControl]=\"control()\" [placeholder]=\"field().placeholder || ''\">\n @for (option of field().options ?? []; track option.value) {\n <ngs-option [value]=\"option.value\">{{ option.label }}</ngs-option>\n }\n </ngs-select>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('checkbox') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-checkbox [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-checkbox>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('toggle') {\n <div class=\"ngs-form-builder-check-field\">\n <ngs-slide-toggle [formControl]=\"control()\" [disabled]=\"readonly() || (field().disabled ?? false)\">\n {{ field().label }}\n </ngs-slide-toggle>\n @if (field().hint) {\n <span>{{ field().hint }}</span>\n }\n </div>\n }\n\n @case ('date') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [ngsDatepicker]=\"picker\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n <ngs-datepicker-toggle ngsIconButtonSuffix [for]=\"picker\"/>\n <ngs-datepicker #picker/>\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @case ('currency') {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n type=\"number\"\n inputmode=\"decimal\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n\n @default {\n <ngs-form-field>\n <ngs-label>{{ field().label }}</ngs-label>\n <input ngsInput\n [type]=\"textInputType()\"\n [formControl]=\"control()\"\n [placeholder]=\"field().placeholder || ''\"\n [readonly]=\"readonly() || field().readonly\">\n @if (field().hint) {\n <ngs-hint>{{ field().hint }}</ngs-hint>\n }\n </ngs-form-field>\n }\n }\n}\n", styles: [":host{display:block;min-width:0}:host.is-width-1{grid-column:span 1}:host.is-width-2{grid-column:span 2}:host.is-width-3{grid-column:span 3}:host.is-width-4{grid-column:span 4}:host.is-width-5{grid-column:span 5}:host.is-width-6{grid-column:span 6}:host.is-width-7{grid-column:span 7}:host.is-width-8{grid-column:span 8}:host.is-width-9{grid-column:span 9}:host.is-width-10{grid-column:span 10}:host.is-width-11{grid-column:span 11}:host.is-width-12{grid-column:span 12}:host .ngs-form-builder-check-field{display:flex;min-height:calc(var(--spacing, .25rem) * 11);flex-direction:column;justify-content:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-check-field span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
252
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], editableCanvas: [{ type: i0.Input, args: [{ isSignal: true, alias: "editableCanvas", required: false }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
253
+
254
+ class FormBuilderRenderer {
255
+ providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
256
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
257
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
258
+ showSubmit = input(true, ...(ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []));
259
+ submitLabel = input('Submit', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
260
+ value = model({}, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
261
+ formSubmit = output();
262
+ formReady = output();
263
+ definitions = computed(() => [
264
+ ...DEFAULT_FORM_BUILDER_FIELDS,
265
+ ...this.providedFields
266
+ ], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
267
+ visibleCanvasItems = computed(() => this.resolveCanvasItems(this.schema())
268
+ .map(item => {
269
+ if (item.field) {
270
+ return {
271
+ ...item,
272
+ field: this.visibleFields([item.field])[0]
273
+ };
274
+ }
275
+ return {
276
+ ...item,
277
+ section: {
278
+ ...item.section,
279
+ fields: this.visibleFields(item.section.fields)
280
+ }
281
+ };
282
+ })
283
+ .filter(item => !!item.field || !!item.section?.fields.length), ...(ngDevMode ? [{ debugName: "visibleCanvasItems" }] : /* istanbul ignore next */ []));
284
+ formGroup = computed(() => this.createFormGroup(), ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
285
+ constructor() {
286
+ effect(onCleanup => {
287
+ const form = this.formGroup();
288
+ this.formReady.emit(form);
289
+ const subscription = form.valueChanges.subscribe(() => {
290
+ this.value.set(form.getRawValue());
291
+ });
292
+ onCleanup(() => subscription.unsubscribe());
293
+ });
294
+ }
295
+ getControl(field) {
296
+ return this.formGroup().get(field.name);
297
+ }
298
+ isContainerField(field) {
299
+ return field.type === 'grid';
300
+ }
301
+ visibleChildren(field) {
302
+ return this.visibleFields(field.children ?? []);
303
+ }
304
+ submit() {
305
+ const form = this.formGroup();
306
+ if (form.invalid) {
307
+ form.markAllAsTouched();
308
+ return;
309
+ }
310
+ this.formSubmit.emit(form.getRawValue());
311
+ }
312
+ createFormGroup() {
313
+ const controls = {};
314
+ const value = this.value();
315
+ const fields = [
316
+ ...flattenFields(this.schema().fields ?? []),
317
+ ...this.schema().sections.flatMap(section => flattenFields(section.fields))
318
+ ];
319
+ for (const field of fields) {
320
+ if (this.isContainerField(field)) {
321
+ continue;
322
+ }
323
+ const definition = this.definitions().find(item => item.type === field.type);
324
+ const validators = definition?.validators?.(field) ?? validatorsFromRules(field.validation, field);
325
+ const control = new FormControl({
326
+ value: value[field.name] ?? field.defaultValue ?? null,
327
+ disabled: field.disabled || this.readonly()
328
+ }, validators);
329
+ controls[field.name] = control;
330
+ }
331
+ return new FormGroup(controls);
332
+ }
333
+ visibleFields(fields) {
334
+ return fields
335
+ .filter(field => field.visibility?.form !== false)
336
+ .map(field => ({
337
+ ...field,
338
+ children: field.children ? this.visibleFields(field.children) : undefined
339
+ }));
340
+ }
341
+ resolveCanvasItems(schema) {
342
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
343
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
344
+ const items = [];
345
+ for (const item of normalizedLayout(schema)) {
346
+ if (item.kind === 'field') {
347
+ const field = fieldsById.get(item.id);
348
+ if (field) {
349
+ items.push({ ...item, field });
350
+ }
351
+ continue;
352
+ }
353
+ const section = sectionsById.get(item.id);
354
+ if (section) {
355
+ items.push({ ...item, section });
356
+ }
357
+ }
358
+ return items;
359
+ }
360
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
361
+ 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 });
362
+ }
363
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, decorators: [{
364
+ type: Component,
365
+ args: [{ selector: 'ngs-form-builder-renderer', exportAs: 'ngsFormBuilderRenderer', imports: [
366
+ NgTemplateOutlet,
367
+ ReactiveFormsModule,
368
+ Button,
369
+ FormBuilderFieldHost
370
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
371
+ 'class': 'ngs-form-builder-renderer'
372
+ }, template: "<ng-template #renderFields let-fields>\n <div class=\"ngs-form-builder-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isContainerField(field)) {\n <section class=\"ngs-form-builder-renderer-grid-field\">\n <header>\n <h4>{{ field.label }}</h4>\n @if (field.hint) {\n <p>{{ field.hint }}</p>\n }\n </header>\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: visibleChildren(field) }\"/>\n </section>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"getControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"readonly()\"/>\n }\n }\n </div>\n</ng-template>\n\n<form [formGroup]=\"formGroup()\" (ngSubmit)=\"submit()\">\n <div class=\"flex flex-col gap-6\">\n @for (item of visibleCanvasItems(); track item.kind + ':' + item.id) {\n @if (item.field) {\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: [item.field] }\"/>\n } @else if (item.section; as section) {\n <section class=\"ngs-form-builder-renderer-section\">\n @if (section.title || section.description) {\n <header>\n @if (section.title) {\n <h3>{{ section.title }}</h3>\n }\n @if (section.description) {\n <p>{{ section.description }}</p>\n }\n </header>\n }\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: section.fields }\"/>\n </section>\n }\n }\n\n @if (showSubmit() && !readonly()) {\n <div class=\"flex justify-end\">\n <button ngsButton=\"filled\" type=\"submit\">{{ submitLabel() }}</button>\n </div>\n }\n </div>\n</form>\n", styles: [":host{display:block}:host .ngs-form-builder-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-builder-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-builder-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-grid-field{display:flex;grid-column:1/-1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-builder-renderer-grid-field>header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
373
+ }], 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"] }] } });
374
+ function flattenFields(fields) {
375
+ return fields.flatMap(field => [field, ...flattenFields(field.children ?? [])]);
376
+ }
377
+ function normalizedLayout(schema) {
378
+ const used = new Set();
379
+ const layout = [];
380
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
381
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
382
+ for (const item of schema.layout ?? []) {
383
+ const key = `${item.kind}:${item.id}`;
384
+ if (used.has(key)) {
385
+ continue;
386
+ }
387
+ if ((item.kind === 'field' && fieldsById.has(item.id)) || (item.kind === 'section' && sectionsById.has(item.id))) {
388
+ used.add(key);
389
+ layout.push(item);
390
+ }
391
+ }
392
+ for (const field of schema.fields ?? []) {
393
+ const key = `field:${field.id}`;
394
+ if (!used.has(key)) {
395
+ used.add(key);
396
+ layout.push({ kind: 'field', id: field.id });
397
+ }
398
+ }
399
+ for (const section of schema.sections) {
400
+ const key = `section:${section.id}`;
401
+ if (!used.has(key)) {
402
+ used.add(key);
403
+ layout.push({ kind: 'section', id: section.id });
404
+ }
405
+ }
406
+ return layout;
407
+ }
408
+
409
+ class FormBuilderSettingsHost {
410
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
411
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
412
+ definitions = input([], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
413
+ settingsDefinitions = input([], ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
414
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
415
+ customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
416
+ anchor = viewChild.required('anchor', { read: ViewContainerRef });
417
+ constructor() {
418
+ effect(async () => {
419
+ const field = this.field();
420
+ const definitions = this.definitions();
421
+ const settingDefinitions = this.settingsDefinitions();
422
+ const viewContainer = this.anchor();
423
+ const fieldDefinition = definitions.find(definition => definition.type === field.type);
424
+ const customSettings = fieldDefinition?.settings ??
425
+ settingDefinitions.find(definition => definition.fieldType === field.type)?.component;
426
+ viewContainer.clear();
427
+ this.customLoaded.set(false);
428
+ if (!customSettings) {
429
+ return;
430
+ }
431
+ const componentType = await customSettings();
432
+ const componentRef = viewContainer.createComponent(componentType);
433
+ componentRef.setInput('field', field);
434
+ componentRef.setInput('schema', this.schema());
435
+ componentRef.setInput('update', this.update());
436
+ this.customLoaded.set(true);
437
+ });
438
+ }
439
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
440
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.4", type: FormBuilderSettingsHost, isStandalone: true, selector: "ngs-form-builder-settings-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, settingsDefinitions: { classPropertyName: "settingsDefinitions", publicName: "settingsDefinitions", isSignal: true, isRequired: false, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.is-empty": "!customLoaded()" }, classAttribute: "ngs-form-builder-settings-host" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderSettingsHost"], ngImport: i0, template: "<ng-container #anchor/>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
441
+ }
442
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
443
+ type: Component,
444
+ args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', changeDetection: ChangeDetectionStrategy.OnPush, host: {
445
+ 'class': 'ngs-form-builder-settings-host',
446
+ '[class.is-empty]': '!customLoaded()'
447
+ }, template: "<ng-container #anchor/>\n" }]
448
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], settingsDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingsDefinitions", required: false }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
449
+
450
+ class BasicFormBuilderFieldSettings {
451
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
452
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
453
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
454
+ hasOptions = computed(() => ['select', 'radio', 'checkbox-list'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
455
+ optionsText = computed(() => (this.field().options ?? [])
456
+ .map(option => `${option.label}:${option.value}`)
457
+ .join('\n'), ...(ngDevMode ? [{ debugName: "optionsText" }] : /* istanbul ignore next */ []));
458
+ patch(changes) {
459
+ this.update()(changes);
460
+ }
461
+ patchOptions(value) {
462
+ const options = value
463
+ .split('\n')
464
+ .map(line => line.trim())
465
+ .filter(Boolean)
466
+ .map((line, index) => {
467
+ const [label, optionValue] = line.split(':');
468
+ return {
469
+ label: label?.trim() || `Option ${index + 1}`,
470
+ value: (optionValue ?? label ?? `option_${index + 1}`).trim()
471
+ };
472
+ });
473
+ this.patch({ options });
474
+ }
475
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
476
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderFieldSettings, isStandalone: true, selector: "ngs-basic-form-builder-field-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-field-settings" }, exportAs: ["ngsBasicFormBuilderFieldSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-3 py-1\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n </div>\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [ngModel]=\"optionsText()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchOptions($event)\"></textarea>\n <ngs-hint>One option per line. Use Label:value to set explicit values.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
477
+ }
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, decorators: [{
479
+ type: Component,
480
+ args: [{ selector: 'ngs-basic-form-builder-field-settings', exportAs: 'ngsBasicFormBuilderFieldSettings', imports: [
481
+ FormsModule,
482
+ FormField,
483
+ Hint,
484
+ Label,
485
+ Input,
486
+ Select,
487
+ Option,
488
+ SlideToggle
489
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
490
+ 'class': 'ngs-basic-form-builder-field-settings'
491
+ }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Field ID</ngs-label>\n <input ngsInput\n [ngModel]=\"field().name\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ name: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-3 py-1\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n </div>\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [ngModel]=\"optionsText()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchOptions($event)\"></textarea>\n <ngs-hint>One option per line. Use Label:value to set explicit values.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
492
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
493
+
494
+ const ROOT_DROP_LIST_ID = 'ngs-form-builder-root-fields';
495
+ const PALETTE_DRAG_TYPE = 'application/x-ngstarter-form-builder-field';
496
+ const PALETTE_DRAG_ITEM = 'application/x-ngstarter-form-builder-item';
497
+ class FormBuilder {
498
+ dialog = inject(Dialog);
499
+ confirmManager = inject(ConfirmManager);
500
+ elementRef = inject(ElementRef);
501
+ providedFields = inject(FORM_BUILDER_FIELDS, { optional: true }) ?? [];
502
+ providedSettings = inject(FORM_BUILDER_SETTINGS, { optional: true }) ?? [];
503
+ previewControls = new Map();
504
+ actualFieldsTree = viewChild('actualFieldsTree', ...(ngDevMode ? [{ debugName: "actualFieldsTree" }] : /* istanbul ignore next */ []));
505
+ suppressPaletteClick = false;
506
+ schema = model(createDefaultFormBuilderSchema(), ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
507
+ paletteTitle = input('Fields', ...(ngDevMode ? [{ debugName: "paletteTitle" }] : /* istanbul ignore next */ []));
508
+ inspectorTitle = input('Field properties', ...(ngDevMode ? [{ debugName: "inspectorTitle" }] : /* istanbul ignore next */ []));
509
+ fieldSelected = output();
510
+ fieldAdded = output();
511
+ fieldRemoved = output();
512
+ search = signal('', ...(ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []));
513
+ selectedFieldId = signal(null, ...(ngDevMode ? [{ debugName: "selectedFieldId" }] : /* istanbul ignore next */ []));
514
+ nativeDragItem = signal(null, ...(ngDevMode ? [{ debugName: "nativeDragItem" }] : /* istanbul ignore next */ []));
515
+ nativeDragFieldDefinition = computed(() => {
516
+ const item = this.nativeDragItem();
517
+ return item?.kind === 'field' ? item.definition : null;
518
+ }, ...(ngDevMode ? [{ debugName: "nativeDragFieldDefinition" }] : /* istanbul ignore next */ []));
519
+ nativeDragSection = computed(() => this.nativeDragItem()?.kind === 'section', ...(ngDevMode ? [{ debugName: "nativeDragSection" }] : /* istanbul ignore next */ []));
520
+ nativeDropTarget = signal(null, ...(ngDevMode ? [{ debugName: "nativeDropTarget" }] : /* istanbul ignore next */ []));
521
+ dragCollapsedSectionIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "dragCollapsedSectionIds" }] : /* istanbul ignore next */ []));
522
+ expandedFieldTreeNodeIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedFieldTreeNodeIds" }] : /* istanbul ignore next */ []));
523
+ definitions = computed(() => [
524
+ ...DEFAULT_FORM_BUILDER_FIELDS,
525
+ ...this.providedFields
526
+ ], ...(ngDevMode ? [{ debugName: "definitions" }] : /* istanbul ignore next */ []));
527
+ settingsDefinitions = computed(() => this.providedSettings, ...(ngDevMode ? [{ debugName: "settingsDefinitions" }] : /* istanbul ignore next */ []));
528
+ fieldContainerDropListIds = computed(() => this.collectFieldContainerDropListIds(this.schema()), ...(ngDevMode ? [{ debugName: "fieldContainerDropListIds" }] : /* istanbul ignore next */ []));
529
+ canvasItems = computed(() => this.resolveCanvasItems(this.schema()), ...(ngDevMode ? [{ debugName: "canvasItems" }] : /* istanbul ignore next */ []));
530
+ layoutDefinitions = computed(() => {
531
+ const query = this.search().trim().toLowerCase();
532
+ return this.definitions().filter(definition => {
533
+ if ((definition.group || 'Other') !== 'Layout') {
534
+ return false;
535
+ }
536
+ return this.matchesPaletteQuery(definition, query);
537
+ });
538
+ }, ...(ngDevMode ? [{ debugName: "layoutDefinitions" }] : /* istanbul ignore next */ []));
539
+ paletteGroups = computed(() => {
540
+ const query = this.search().trim().toLowerCase();
541
+ const groups = new Map();
542
+ for (const definition of this.definitions()) {
543
+ const group = definition.group || 'Other';
544
+ if (group === 'Layout' || !this.matchesPaletteQuery(definition, query)) {
545
+ continue;
546
+ }
547
+ groups.set(group, [...(groups.get(group) ?? []), definition]);
548
+ }
549
+ return Array.from(groups, ([name, fields]) => ({ name, fields }));
550
+ }, ...(ngDevMode ? [{ debugName: "paletteGroups" }] : /* istanbul ignore next */ []));
551
+ selectedField = computed(() => {
552
+ const selectedId = this.selectedFieldId();
553
+ return selectedId ? this.findFieldLocation(this.schema(), selectedId)?.field ?? null : null;
554
+ }, ...(ngDevMode ? [{ debugName: "selectedField" }] : /* istanbul ignore next */ []));
555
+ selectedSection = computed(() => {
556
+ const selectedId = this.selectedFieldId();
557
+ return selectedId ? this.findFieldLocation(this.schema(), selectedId)?.section ?? null : null;
558
+ }, ...(ngDevMode ? [{ debugName: "selectedSection" }] : /* istanbul ignore next */ []));
559
+ fieldTree = computed(() => {
560
+ return this.resolveCanvasItems(this.schema()).map(item => {
561
+ if (item.field) {
562
+ return this.createFieldTreeNode(item.field);
563
+ }
564
+ return {
565
+ id: item.section.id,
566
+ label: item.section.title,
567
+ type: 'section',
568
+ icon: 'fluent:folder-24-regular',
569
+ kind: 'section',
570
+ section: item.section,
571
+ children: item.section.fields.map(field => this.createFieldTreeNode(field, item.section))
572
+ };
573
+ });
574
+ }, ...(ngDevMode ? [{ debugName: "fieldTree" }] : /* istanbul ignore next */ []));
575
+ fieldTreeChildrenAccessor = (node) => node.children ?? [];
576
+ hasFieldTreeChildren = (_, node) => !!node.children?.length;
577
+ trackFieldTreeNode = (_, node) => node.id;
578
+ fieldTreeDraggablePredicate = (_node) => true;
579
+ fieldTreeDropPredicate = (source, target, position) => this.canDropFieldTreeNode(source, target, position);
580
+ updateSelectedField = (changes) => {
581
+ this.patchSelectedField(changes);
582
+ };
583
+ fieldDropListEnterPredicate = (drag) => {
584
+ const data = drag.data;
585
+ return !(isFormBuilderLayoutItem(data) && data.kind === 'section');
586
+ };
587
+ paletteDragStarted(event, definition) {
588
+ this.suppressPaletteClick = true;
589
+ this.nativeDragItem.set({ kind: 'field', definition });
590
+ event.dataTransfer?.setData(PALETTE_DRAG_TYPE, definition.type);
591
+ event.dataTransfer?.setData(PALETTE_DRAG_ITEM, 'field');
592
+ if (event.dataTransfer) {
593
+ event.dataTransfer.effectAllowed = 'copy';
594
+ this.setPaletteDragImage(event);
595
+ }
596
+ }
597
+ sectionPaletteDragStarted(event) {
598
+ this.suppressPaletteClick = true;
599
+ this.nativeDragItem.set({ kind: 'section' });
600
+ event.dataTransfer?.setData(PALETTE_DRAG_ITEM, 'section');
601
+ if (event.dataTransfer) {
602
+ event.dataTransfer.effectAllowed = 'copy';
603
+ this.setPaletteDragImage(event);
604
+ }
605
+ }
606
+ paletteDragEnded() {
607
+ this.nativeDragItem.set(null);
608
+ this.nativeDropTarget.set(null);
609
+ window.setTimeout(() => {
610
+ this.suppressPaletteClick = false;
611
+ });
612
+ }
613
+ paletteClicked(definition) {
614
+ if (this.suppressPaletteClick) {
615
+ return;
616
+ }
617
+ this.addField(definition);
618
+ }
619
+ nativeCanvasDragOver(event) {
620
+ 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)');
621
+ }
622
+ nativeFieldDragOver(event, containerId) {
623
+ if (this.nativeDragItem()?.kind === 'section') {
624
+ return;
625
+ }
626
+ event.stopPropagation();
627
+ this.nativeDragOver(event, containerId, '.ngs-form-builder-field:not(.ngs-form-builder-native-ghost-field)');
628
+ }
629
+ nativeSectionDragOver(event, section) {
630
+ if (this.nativeDragItem()?.kind === 'section') {
631
+ return;
632
+ }
633
+ event.stopPropagation();
634
+ this.nativeDragOverAtIndex(event, this.sectionDropListId(section), section.fields.length);
635
+ }
636
+ nativeSectionDrop(event, section) {
637
+ if (this.nativeDragItem()?.kind === 'section') {
638
+ return;
639
+ }
640
+ event.stopPropagation();
641
+ this.nativeFieldDrop(event, this.sectionDropListId(section));
642
+ }
643
+ nativeContainerFieldDragOver(event, field) {
644
+ if (!this.isContainerField(field)) {
645
+ return;
646
+ }
647
+ if (this.nativeDragItem()?.kind === 'section') {
648
+ return;
649
+ }
650
+ event.stopPropagation();
651
+ this.nativeDragOverAtIndex(event, this.fieldDropListId(field), field.children?.length ?? 0);
652
+ }
653
+ nativeContainerFieldDragLeave(event, field) {
654
+ if (!this.isContainerField(field)) {
655
+ return;
656
+ }
657
+ this.nativeDragLeave(event, this.fieldDropListId(field));
658
+ }
659
+ nativeContainerFieldDrop(event, field) {
660
+ if (!this.isContainerField(field)) {
661
+ return;
662
+ }
663
+ if (this.nativeDragItem()?.kind === 'section') {
664
+ return;
665
+ }
666
+ event.stopPropagation();
667
+ this.nativeFieldDrop(event, this.fieldDropListId(field));
668
+ }
669
+ nativeDragLeave(event, containerId) {
670
+ const target = event.currentTarget;
671
+ const relatedTarget = event.relatedTarget;
672
+ if (target instanceof HTMLElement &&
673
+ relatedTarget instanceof Node &&
674
+ target.contains(relatedTarget)) {
675
+ return;
676
+ }
677
+ if (this.nativeDropTarget()?.containerId === containerId) {
678
+ this.nativeDropTarget.set(null);
679
+ }
680
+ }
681
+ nativeFieldDrop(event, containerId) {
682
+ const item = this.resolveNativeDragItem(event);
683
+ if (!item) {
684
+ return;
685
+ }
686
+ event.preventDefault();
687
+ event.stopPropagation();
688
+ const target = this.nativeDropTarget();
689
+ const index = target?.containerId === containerId ? target.index : 0;
690
+ if (item.kind === 'section') {
691
+ if (containerId !== ROOT_DROP_LIST_ID) {
692
+ return;
693
+ }
694
+ this.insertNativeSection(index);
695
+ }
696
+ else {
697
+ this.insertNativeField(item.definition, containerId, index);
698
+ }
699
+ this.nativeDragItem.set(null);
700
+ this.nativeDropTarget.set(null);
701
+ }
702
+ isNativeDropTarget(containerId, index) {
703
+ const target = this.nativeDropTarget();
704
+ return target?.containerId === containerId && target.index === index;
705
+ }
706
+ addField(definition, sectionId) {
707
+ const schema = cloneSchema(this.schema());
708
+ const fields = sectionId
709
+ ? schema.sections.find(item => item.id === sectionId)?.fields
710
+ : schema.fields;
711
+ if (!fields) {
712
+ return;
713
+ }
714
+ const field = this.createField(definition, schema);
715
+ const section = sectionId ? schema.sections.find(item => item.id === sectionId) : undefined;
716
+ fields.push(field);
717
+ if (!sectionId) {
718
+ schema.layout = this.normalizedLayout(schema);
719
+ }
720
+ this.schema.set(schema);
721
+ this.selectField(field, section);
722
+ this.fieldAdded.emit({ field, section });
723
+ }
724
+ addSection() {
725
+ const schema = cloneSchema(this.schema());
726
+ schema.sections.push({
727
+ id: uniqueId('section'),
728
+ title: `Section ${schema.sections.length + 1}`,
729
+ fields: []
730
+ });
731
+ schema.layout = this.normalizedLayout(schema);
732
+ this.schema.set(schema);
733
+ }
734
+ openPreview(template) {
735
+ this.dialog.open(template, {
736
+ width: '760px',
737
+ maxWidth: 'calc(100vw - 48px)',
738
+ maxHeight: 'calc(100vh - 48px)',
739
+ ariaLabel: 'Form preview'
740
+ });
741
+ }
742
+ removeSection(section) {
743
+ const schema = cloneSchema(this.schema());
744
+ schema.sections = schema.sections.filter(item => item.id !== section.id);
745
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'section' && item.id === section.id));
746
+ if (schema.sections.length === 0) {
747
+ const nextSection = {
748
+ id: uniqueId('section'),
749
+ title: 'Section',
750
+ fields: []
751
+ };
752
+ schema.sections.push(nextSection);
753
+ schema.layout = this.normalizedLayout(schema);
754
+ }
755
+ if (section.fields.some(field => field.id === this.selectedFieldId() || containsField(field, this.selectedFieldId()))) {
756
+ this.selectedFieldId.set(null);
757
+ }
758
+ this.schema.set(schema);
759
+ }
760
+ updateSection(section, changes) {
761
+ const schema = cloneSchema(this.schema());
762
+ const nextSection = schema.sections.find(item => item.id === section.id);
763
+ if (!nextSection) {
764
+ return;
765
+ }
766
+ Object.assign(nextSection, changes);
767
+ this.schema.set(schema);
768
+ }
769
+ sectionDragStarted(section) {
770
+ if (section.collapsed) {
771
+ return;
772
+ }
773
+ this.dragCollapsedSectionIds.update(ids => new Set(ids).add(section.id));
774
+ }
775
+ sectionDragEnded(section) {
776
+ if (!this.dragCollapsedSectionIds().has(section.id)) {
777
+ return;
778
+ }
779
+ this.dragCollapsedSectionIds.update(ids => {
780
+ const next = new Set(ids);
781
+ next.delete(section.id);
782
+ return next;
783
+ });
784
+ }
785
+ isSectionCollapsed(section) {
786
+ return section.collapsed === true || this.dragCollapsedSectionIds().has(section.id);
787
+ }
788
+ dropField(event) {
789
+ const schema = cloneSchema(this.schema());
790
+ const targetContainer = this.findContainerLocation(schema, event.container.id);
791
+ if (!targetContainer) {
792
+ return;
793
+ }
794
+ if (event.previousContainer.id === 'ngs-form-builder-palette') {
795
+ const definition = event.item.data;
796
+ const field = this.createField(definition, schema);
797
+ targetContainer.fields.splice(event.currentIndex, 0, field);
798
+ this.schema.set(schema);
799
+ this.selectField(field, targetContainer.section);
800
+ this.fieldAdded.emit({ field, section: targetContainer.section });
801
+ return;
802
+ }
803
+ if (event.previousContainer.id === ROOT_DROP_LIST_ID) {
804
+ const layout = this.normalizedLayout(schema);
805
+ const movingItem = this.resolveCanvasDragItem(event.item.data, layout);
806
+ if (!movingItem || movingItem.kind !== 'field') {
807
+ return;
808
+ }
809
+ const sourceIndex = (schema.fields ?? []).findIndex(field => field.id === movingItem.id);
810
+ const movingField = (schema.fields ?? [])[sourceIndex];
811
+ const layoutIndex = layout.findIndex(item => item.kind === 'field' && item.id === movingItem.id);
812
+ if (!movingField) {
813
+ return;
814
+ }
815
+ if (targetContainer.owner &&
816
+ (targetContainer.owner.id === movingField.id || containsField(movingField, targetContainer.owner.id))) {
817
+ return;
818
+ }
819
+ schema.fields?.splice(sourceIndex, 1);
820
+ if (layoutIndex > -1) {
821
+ layout.splice(layoutIndex, 1);
822
+ }
823
+ targetContainer.fields.splice(event.currentIndex, 0, movingField);
824
+ schema.layout = layout;
825
+ this.schema.set(schema);
826
+ return;
827
+ }
828
+ const sourceContainer = this.findContainerLocation(schema, event.previousContainer.id);
829
+ if (!sourceContainer) {
830
+ return;
831
+ }
832
+ const movingField = sourceContainer.fields[event.previousIndex];
833
+ if (movingField &&
834
+ targetContainer.owner &&
835
+ (targetContainer.owner.id === movingField.id || containsField(movingField, targetContainer.owner.id))) {
836
+ return;
837
+ }
838
+ if (sourceContainer.id === targetContainer.id) {
839
+ moveItemInArray(targetContainer.fields, event.previousIndex, event.currentIndex);
840
+ }
841
+ else {
842
+ transferArrayItem(sourceContainer.fields, targetContainer.fields, event.previousIndex, event.currentIndex);
843
+ }
844
+ this.schema.set(schema);
845
+ }
846
+ dropCanvasItem(event) {
847
+ const schema = cloneSchema(this.schema());
848
+ const layout = this.normalizedLayout(schema);
849
+ if (event.previousContainer.id === 'ngs-form-builder-palette') {
850
+ const definition = event.item.data;
851
+ const field = this.createField(definition, schema);
852
+ schema.fields ??= [];
853
+ schema.fields.push(field);
854
+ layout.splice(event.currentIndex, 0, { kind: 'field', id: field.id });
855
+ schema.layout = layout;
856
+ this.schema.set(schema);
857
+ this.selectField(field);
858
+ this.fieldAdded.emit({ field });
859
+ return;
860
+ }
861
+ if (event.previousContainer.id === ROOT_DROP_LIST_ID) {
862
+ const movingItem = this.resolveCanvasDragItem(event.item.data, layout);
863
+ const previousIndex = movingItem
864
+ ? layout.findIndex(item => item.kind === movingItem.kind && item.id === movingItem.id)
865
+ : event.previousIndex;
866
+ if (previousIndex < 0) {
867
+ return;
868
+ }
869
+ moveItemInArray(layout, previousIndex, event.currentIndex);
870
+ schema.layout = layout;
871
+ this.schema.set(schema);
872
+ this.clearSectionDragCollapse();
873
+ return;
874
+ }
875
+ const sourceContainer = this.findContainerLocation(schema, event.previousContainer.id);
876
+ if (!sourceContainer) {
877
+ return;
878
+ }
879
+ const movingField = sourceContainer.fields[event.previousIndex];
880
+ if (!movingField) {
881
+ return;
882
+ }
883
+ sourceContainer.fields.splice(event.previousIndex, 1);
884
+ schema.fields ??= [];
885
+ schema.fields.push(movingField);
886
+ layout.splice(event.currentIndex, 0, { kind: 'field', id: movingField.id });
887
+ schema.layout = layout;
888
+ this.schema.set(schema);
889
+ this.clearSectionDragCollapse();
890
+ }
891
+ selectField(field, section = this.selectedSection()) {
892
+ this.selectedFieldId.set(field.id);
893
+ this.fieldSelected.emit({ field, section: section ?? undefined });
894
+ }
895
+ selectFieldTreeNode(node) {
896
+ if (node.field) {
897
+ if (node.section?.collapsed) {
898
+ this.updateSection(node.section, { collapsed: false });
899
+ }
900
+ this.selectField(node.field, node.section);
901
+ this.scrollCanvasFieldIntoView(node.field.id);
902
+ }
903
+ }
904
+ toggleFieldTreeNode(tree, node, event) {
905
+ event.stopPropagation();
906
+ if (!node.children?.length) {
907
+ return;
908
+ }
909
+ if (tree.isExpanded(node)) {
910
+ tree.collapse(node);
911
+ this.expandedFieldTreeNodeIds.update(ids => {
912
+ const next = new Set(ids);
913
+ next.delete(node.id);
914
+ return next;
915
+ });
916
+ return;
917
+ }
918
+ tree.expand(node);
919
+ this.expandedFieldTreeNodeIds.update(ids => new Set(ids).add(node.id));
920
+ }
921
+ isFieldTreeNodeExpanded(tree, node) {
922
+ return tree.isExpanded(node) || this.expandedFieldTreeNodeIds().has(node.id);
923
+ }
924
+ fieldTreePlaceholderIcon(source) {
925
+ return source.kind === 'section' ? 'fluent:folder-24-regular' : source.icon;
926
+ }
927
+ fieldTreeNodeDropped(event) {
928
+ if (event.source.kind === 'section') {
929
+ this.dropFieldTreeSection(event);
930
+ return;
931
+ }
932
+ if (event.source.kind !== 'field' ||
933
+ event.source.id === event.target.id ||
934
+ (event.target.kind === 'field' && event.position === 'inside' && !this.isContainerField(event.target.field))) {
935
+ this.resetFieldTreeAfterRejectedDrop();
936
+ return;
937
+ }
938
+ const schema = cloneSchema(this.schema());
939
+ const sourceLocation = this.findFieldLocation(schema, event.source.id);
940
+ if (!sourceLocation) {
941
+ this.resetFieldTreeAfterRejectedDrop();
942
+ return;
943
+ }
944
+ if (event.target.kind === 'field' &&
945
+ (sourceLocation.field.id === event.target.id || containsField(sourceLocation.field, event.target.id))) {
946
+ this.resetFieldTreeAfterRejectedDrop();
947
+ return;
948
+ }
949
+ const movingField = sourceLocation.field;
950
+ this.detachFieldFromLocation(schema, sourceLocation);
951
+ if (event.target.kind === 'section') {
952
+ const inserted = event.position === 'inside'
953
+ ? this.insertFieldIntoTreeSection(schema, movingField, event.target.id)
954
+ : this.insertFieldAroundTreeSection(schema, movingField, event.target.id, event.position);
955
+ if (inserted) {
956
+ this.schema.set(schema);
957
+ this.restoreFieldTreeExpansion();
958
+ this.selectField(movingField, event.position === 'inside' ? inserted.section : undefined);
959
+ return;
960
+ }
961
+ }
962
+ if (event.target.kind === 'field') {
963
+ const target = this.resolveTreeFieldInsertTarget(schema, event.target.id, event.position);
964
+ if (target) {
965
+ target.fields.splice(target.index, 0, movingField);
966
+ if (target.fields === (schema.fields ?? [])) {
967
+ this.insertRootFieldIntoLayout(schema, movingField, event.target.id, event.position);
968
+ }
969
+ this.schema.set(schema);
970
+ this.restoreFieldTreeExpansion();
971
+ this.selectField(movingField, target.section);
972
+ return;
973
+ }
974
+ }
975
+ this.resetFieldTreeAfterRejectedDrop();
976
+ }
977
+ confirmRemoveField(field) {
978
+ const confirmRef = this.confirmManager.open({
979
+ title: 'Delete field',
980
+ description: `Delete "${field.label}" from this form? This action cannot be undone.`
981
+ });
982
+ confirmRef.confirmed.subscribe(() => {
983
+ this.removeField(field);
984
+ });
985
+ }
986
+ removeField(field) {
987
+ const schema = cloneSchema(this.schema());
988
+ const location = this.findFieldLocation(schema, field.id);
989
+ if (!location) {
990
+ return;
991
+ }
992
+ location.siblings.splice(location.index, 1);
993
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
994
+ this.deletePreviewControls(field);
995
+ if (this.selectedFieldId() === field.id || containsField(field, this.selectedFieldId())) {
996
+ this.selectedFieldId.set(null);
997
+ }
998
+ this.schema.set(schema);
999
+ this.fieldRemoved.emit({ field, section: location.section });
1000
+ }
1001
+ sectionDropListId(section) {
1002
+ return `ngs-form-builder-section-${section.id}`;
1003
+ }
1004
+ fieldDropListId(field) {
1005
+ return `ngs-form-builder-field-${field.id}`;
1006
+ }
1007
+ rootDropListId() {
1008
+ return ROOT_DROP_LIST_ID;
1009
+ }
1010
+ previewControl(field) {
1011
+ const existing = this.previewControls.get(field.id);
1012
+ if (existing) {
1013
+ return existing;
1014
+ }
1015
+ const control = new FormControl({
1016
+ value: field.defaultValue ?? null,
1017
+ disabled: true
1018
+ });
1019
+ this.previewControls.set(field.id, control);
1020
+ return control;
1021
+ }
1022
+ patchSelectedField(changes) {
1023
+ const selectedId = this.selectedFieldId();
1024
+ if (!selectedId) {
1025
+ return;
1026
+ }
1027
+ const schema = cloneSchema(this.schema());
1028
+ const location = this.findFieldLocation(schema, selectedId);
1029
+ if (!location) {
1030
+ return;
1031
+ }
1032
+ const nextField = {
1033
+ ...location.field,
1034
+ ...changes
1035
+ };
1036
+ if (changes.type === 'grid' && !nextField.children) {
1037
+ nextField.children = [];
1038
+ }
1039
+ location.siblings[location.index] = nextField;
1040
+ this.schema.set(schema);
1041
+ }
1042
+ isContainerField(field) {
1043
+ return field.type === 'grid';
1044
+ }
1045
+ fieldIcon(field) {
1046
+ return this.definitions().find(definition => definition.type === field.type)?.icon || 'fluent:form-24-regular';
1047
+ }
1048
+ definitionWidth(definition) {
1049
+ return definition.defaults?.width ?? this.defaultWidth(definition.type);
1050
+ }
1051
+ matchesPaletteQuery(definition, query) {
1052
+ return !query ||
1053
+ definition.label.toLowerCase().includes(query) ||
1054
+ definition.type.toLowerCase().includes(query) ||
1055
+ (definition.description?.toLowerCase().includes(query) ?? false);
1056
+ }
1057
+ nativeDragOver(event, containerId, itemSelector) {
1058
+ const item = this.nativeDragItem();
1059
+ if (!item || (item.kind === 'section' && containerId !== ROOT_DROP_LIST_ID)) {
1060
+ return;
1061
+ }
1062
+ event.preventDefault();
1063
+ if (event.dataTransfer) {
1064
+ event.dataTransfer.dropEffect = 'copy';
1065
+ }
1066
+ this.nativeDropTarget.set({
1067
+ containerId,
1068
+ index: this.resolveNativeDropIndex(event, itemSelector)
1069
+ });
1070
+ }
1071
+ nativeDragOverAtIndex(event, containerId, index) {
1072
+ const item = this.nativeDragItem();
1073
+ if (!item || item.kind === 'section') {
1074
+ return;
1075
+ }
1076
+ event.preventDefault();
1077
+ if (event.dataTransfer) {
1078
+ event.dataTransfer.dropEffect = 'copy';
1079
+ }
1080
+ this.nativeDropTarget.set({ containerId, index });
1081
+ }
1082
+ insertNativeField(definition, containerId, index) {
1083
+ const schema = cloneSchema(this.schema());
1084
+ const field = this.createField(definition, schema);
1085
+ if (containerId === ROOT_DROP_LIST_ID) {
1086
+ const layout = this.normalizedLayout(schema);
1087
+ schema.fields ??= [];
1088
+ schema.fields.push(field);
1089
+ layout.splice(clampIndex(index, layout.length), 0, { kind: 'field', id: field.id });
1090
+ schema.layout = layout;
1091
+ this.schema.set(schema);
1092
+ this.selectField(field);
1093
+ this.fieldAdded.emit({ field });
1094
+ return;
1095
+ }
1096
+ const targetContainer = this.findContainerLocation(schema, containerId);
1097
+ if (!targetContainer) {
1098
+ return;
1099
+ }
1100
+ targetContainer.fields.splice(clampIndex(index, targetContainer.fields.length), 0, field);
1101
+ this.schema.set(schema);
1102
+ this.selectField(field, targetContainer.section);
1103
+ this.fieldAdded.emit({ field, section: targetContainer.section });
1104
+ }
1105
+ insertNativeSection(index) {
1106
+ const schema = cloneSchema(this.schema());
1107
+ const section = {
1108
+ id: uniqueId('section'),
1109
+ title: `Section ${schema.sections.length + 1}`,
1110
+ fields: []
1111
+ };
1112
+ const layout = this.normalizedLayout(schema);
1113
+ schema.sections.push(section);
1114
+ layout.splice(clampIndex(index, layout.length), 0, { kind: 'section', id: section.id });
1115
+ schema.layout = layout;
1116
+ this.schema.set(schema);
1117
+ }
1118
+ resolveNativeDragItem(event) {
1119
+ const currentItem = this.nativeDragItem();
1120
+ if (currentItem) {
1121
+ return currentItem;
1122
+ }
1123
+ if (event.dataTransfer?.getData(PALETTE_DRAG_ITEM) === 'section') {
1124
+ return { kind: 'section' };
1125
+ }
1126
+ const type = event.dataTransfer?.getData(PALETTE_DRAG_TYPE);
1127
+ const definition = type ? this.definitions().find(item => item.type === type) ?? null : null;
1128
+ return definition ? { kind: 'field', definition } : null;
1129
+ }
1130
+ resolveNativeDropIndex(event, itemSelector) {
1131
+ const container = event.currentTarget;
1132
+ if (!(container instanceof HTMLElement)) {
1133
+ return 0;
1134
+ }
1135
+ const items = Array.from(container.children).filter((child) => {
1136
+ return child instanceof HTMLElement && child.matches(itemSelector);
1137
+ });
1138
+ for (let index = 0; index < items.length; index += 1) {
1139
+ const rect = items[index].getBoundingClientRect();
1140
+ const centerY = rect.top + rect.height / 2;
1141
+ const centerX = rect.left + rect.width / 2;
1142
+ const sameRow = event.clientY >= rect.top && event.clientY <= rect.bottom;
1143
+ if (event.clientY < centerY || (sameRow && event.clientX < centerX)) {
1144
+ return index;
1145
+ }
1146
+ }
1147
+ return items.length;
1148
+ }
1149
+ setPaletteDragImage(event) {
1150
+ const source = event.currentTarget;
1151
+ if (!(source instanceof HTMLElement) || !event.dataTransfer) {
1152
+ return;
1153
+ }
1154
+ const clone = source.cloneNode(true);
1155
+ if (!(clone instanceof HTMLElement)) {
1156
+ return;
1157
+ }
1158
+ clone.classList.add('ngs-form-builder-native-drag-image');
1159
+ source.parentElement?.appendChild(clone);
1160
+ const rect = source.getBoundingClientRect();
1161
+ event.dataTransfer.setDragImage(clone, Math.min(event.offsetX, rect.width), Math.min(event.offsetY, rect.height));
1162
+ window.setTimeout(() => clone.remove());
1163
+ }
1164
+ createField(definition, schema) {
1165
+ const baseLabel = definition.defaults?.label || definition.label;
1166
+ const name = uniqueFieldName(toFieldName(baseLabel), schema);
1167
+ return {
1168
+ id: uniqueId('field'),
1169
+ name,
1170
+ type: definition.type,
1171
+ label: baseLabel,
1172
+ width: definition.defaults?.width ?? this.defaultWidth(definition.type),
1173
+ visibility: {
1174
+ form: true,
1175
+ email: true,
1176
+ pdf: true
1177
+ },
1178
+ ...definition.defaults
1179
+ };
1180
+ }
1181
+ defaultWidth(type) {
1182
+ return 12;
1183
+ }
1184
+ resolveCanvasItems(schema) {
1185
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
1186
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
1187
+ const items = [];
1188
+ for (const item of this.normalizedLayout(schema)) {
1189
+ if (item.kind === 'field') {
1190
+ const field = fieldsById.get(item.id);
1191
+ if (field) {
1192
+ items.push({ ...item, field });
1193
+ }
1194
+ continue;
1195
+ }
1196
+ const section = sectionsById.get(item.id);
1197
+ if (section) {
1198
+ items.push({ ...item, section });
1199
+ }
1200
+ }
1201
+ return items;
1202
+ }
1203
+ normalizedLayout(schema) {
1204
+ const used = new Set();
1205
+ const layout = [];
1206
+ const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
1207
+ const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
1208
+ for (const item of schema.layout ?? []) {
1209
+ const key = `${item.kind}:${item.id}`;
1210
+ if (used.has(key)) {
1211
+ continue;
1212
+ }
1213
+ if ((item.kind === 'field' && fieldsById.has(item.id)) || (item.kind === 'section' && sectionsById.has(item.id))) {
1214
+ used.add(key);
1215
+ layout.push(item);
1216
+ }
1217
+ }
1218
+ for (const field of schema.fields ?? []) {
1219
+ const key = `field:${field.id}`;
1220
+ if (!used.has(key)) {
1221
+ used.add(key);
1222
+ layout.push({ kind: 'field', id: field.id });
1223
+ }
1224
+ }
1225
+ for (const section of schema.sections) {
1226
+ const key = `section:${section.id}`;
1227
+ if (!used.has(key)) {
1228
+ used.add(key);
1229
+ layout.push({ kind: 'section', id: section.id });
1230
+ }
1231
+ }
1232
+ return layout;
1233
+ }
1234
+ collectFieldContainerDropListIds(schema) {
1235
+ return [
1236
+ ...(schema.fields ?? []).flatMap(field => this.collectFieldDropListIds(field)),
1237
+ ...schema.sections.flatMap(section => [
1238
+ this.sectionDropListId(section),
1239
+ ...section.fields.flatMap(field => this.collectFieldDropListIds(field))
1240
+ ])
1241
+ ];
1242
+ }
1243
+ collectFieldDropListIds(field) {
1244
+ return [
1245
+ ...(this.isContainerField(field) ? [this.fieldDropListId(field)] : []),
1246
+ ...(field.children ?? []).flatMap(child => this.collectFieldDropListIds(child))
1247
+ ];
1248
+ }
1249
+ resolveCanvasDragItem(data, layout) {
1250
+ if (isFormBuilderLayoutItem(data)) {
1251
+ return data;
1252
+ }
1253
+ if (isFormBuilderField(data)) {
1254
+ return layout.find(item => item.kind === 'field' && item.id === data.id) ?? null;
1255
+ }
1256
+ return null;
1257
+ }
1258
+ detachFieldFromLocation(schema, location) {
1259
+ location.siblings.splice(location.index, 1);
1260
+ if (location.siblings === (schema.fields ?? [])) {
1261
+ schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === location.field.id));
1262
+ }
1263
+ }
1264
+ insertFieldIntoTreeSection(schema, field, sectionId) {
1265
+ const section = schema.sections.find(item => item.id === sectionId);
1266
+ if (!section) {
1267
+ return null;
1268
+ }
1269
+ section.fields.push(field);
1270
+ return {
1271
+ fields: section.fields,
1272
+ index: section.fields.length - 1,
1273
+ section
1274
+ };
1275
+ }
1276
+ insertFieldAroundTreeSection(schema, field, sectionId, position) {
1277
+ if (position === 'inside') {
1278
+ return this.insertFieldIntoTreeSection(schema, field, sectionId);
1279
+ }
1280
+ const section = schema.sections.find(item => item.id === sectionId);
1281
+ if (!section) {
1282
+ return null;
1283
+ }
1284
+ schema.fields ??= [];
1285
+ if (!schema.fields.some(item => item.id === field.id)) {
1286
+ schema.fields.push(field);
1287
+ }
1288
+ const layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
1289
+ const sectionIndex = layout.findIndex(item => item.kind === 'section' && item.id === sectionId);
1290
+ layout.splice(sectionIndex === -1 ? layout.length : sectionIndex + (position === 'after' ? 1 : 0), 0, {
1291
+ kind: 'field',
1292
+ id: field.id
1293
+ });
1294
+ schema.layout = layout;
1295
+ return {
1296
+ fields: schema.fields,
1297
+ index: schema.fields.findIndex(item => item.id === field.id)
1298
+ };
1299
+ }
1300
+ resolveTreeFieldInsertTarget(schema, targetFieldId, position) {
1301
+ const targetLocation = this.findFieldLocation(schema, targetFieldId);
1302
+ if (!targetLocation) {
1303
+ return null;
1304
+ }
1305
+ if (position === 'inside' && this.isContainerField(targetLocation.field)) {
1306
+ targetLocation.field.children ??= [];
1307
+ return {
1308
+ fields: targetLocation.field.children,
1309
+ index: targetLocation.field.children.length,
1310
+ section: targetLocation.section
1311
+ };
1312
+ }
1313
+ const insertIndex = position === 'before' ? targetLocation.index : targetLocation.index + 1;
1314
+ return {
1315
+ fields: targetLocation.siblings,
1316
+ index: insertIndex,
1317
+ section: targetLocation.section
1318
+ };
1319
+ }
1320
+ insertRootFieldIntoLayout(schema, field, targetFieldId, position) {
1321
+ schema.fields ??= [];
1322
+ if (!schema.fields.some(item => item.id === field.id)) {
1323
+ schema.fields.push(field);
1324
+ }
1325
+ const layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'field' && item.id === field.id));
1326
+ const targetIndex = layout.findIndex(item => item.kind === 'field' && item.id === targetFieldId);
1327
+ layout.splice(targetIndex === -1 ? layout.length : targetIndex + (position === 'before' ? 0 : 1), 0, {
1328
+ kind: 'field',
1329
+ id: field.id
1330
+ });
1331
+ schema.layout = layout;
1332
+ }
1333
+ clearSectionDragCollapse() {
1334
+ if (this.dragCollapsedSectionIds().size) {
1335
+ this.dragCollapsedSectionIds.set(new Set());
1336
+ }
1337
+ }
1338
+ resetFieldTreeAfterRejectedDrop() {
1339
+ this.schema.set(cloneSchema(this.schema()));
1340
+ this.restoreFieldTreeExpansion();
1341
+ }
1342
+ canDropFieldTreeNode(source, target, position) {
1343
+ if (source.id === target.id) {
1344
+ return false;
1345
+ }
1346
+ if (source.kind === 'section') {
1347
+ return position !== 'inside' && (target.kind === 'section' || this.isRootFieldTreeNode(target));
1348
+ }
1349
+ if (target.kind === 'field' && position === 'inside') {
1350
+ return !!target.field && this.isContainerField(target.field);
1351
+ }
1352
+ return true;
1353
+ }
1354
+ dropFieldTreeSection(event) {
1355
+ if (!this.canDropFieldTreeNode(event.source, event.target, event.position)) {
1356
+ this.resetFieldTreeAfterRejectedDrop();
1357
+ return;
1358
+ }
1359
+ const schema = cloneSchema(this.schema());
1360
+ const layout = this.normalizedLayout(schema);
1361
+ const sourceIndex = layout.findIndex(item => item.kind === 'section' && item.id === event.source.id);
1362
+ const targetKind = event.target.kind === 'section' ? 'section' : 'field';
1363
+ const targetIndex = layout.findIndex(item => item.kind === targetKind && item.id === event.target.id);
1364
+ if (sourceIndex < 0 || targetIndex < 0) {
1365
+ this.resetFieldTreeAfterRejectedDrop();
1366
+ return;
1367
+ }
1368
+ const [sectionItem] = layout.splice(sourceIndex, 1);
1369
+ const adjustedTargetIndex = sourceIndex < targetIndex ? targetIndex - 1 : targetIndex;
1370
+ layout.splice(adjustedTargetIndex + (event.position === 'after' ? 1 : 0), 0, sectionItem);
1371
+ schema.layout = layout;
1372
+ this.schema.set(schema);
1373
+ this.restoreFieldTreeExpansion();
1374
+ }
1375
+ isRootFieldTreeNode(node) {
1376
+ if (node.kind !== 'field') {
1377
+ return false;
1378
+ }
1379
+ return this.resolveCanvasItems(this.schema()).some(item => item.kind === 'field' && item.id === node.id);
1380
+ }
1381
+ restoreFieldTreeExpansion() {
1382
+ const expandedIds = this.expandedFieldTreeNodeIds();
1383
+ if (!expandedIds.size) {
1384
+ return;
1385
+ }
1386
+ this.afterNextPaint(() => {
1387
+ this.afterNextPaint(() => {
1388
+ const tree = this.actualFieldsTree();
1389
+ if (!tree) {
1390
+ return;
1391
+ }
1392
+ for (const node of this.flattenFieldTree(this.fieldTree())) {
1393
+ if (node.children?.length && expandedIds.has(node.id)) {
1394
+ tree.expand(node);
1395
+ }
1396
+ }
1397
+ });
1398
+ });
1399
+ }
1400
+ flattenFieldTree(nodes) {
1401
+ return nodes.flatMap(node => [node, ...this.flattenFieldTree(node.children ?? [])]);
1402
+ }
1403
+ scrollCanvasFieldIntoView(fieldId) {
1404
+ this.afterNextPaint(() => {
1405
+ this.afterNextPaint(() => {
1406
+ const target = this.findCanvasFieldElement(fieldId);
1407
+ if (!target) {
1408
+ return;
1409
+ }
1410
+ const scrollableContent = target
1411
+ .closest('ngs-scrollbar-area')
1412
+ ?.querySelector('.scrollable-content');
1413
+ if (!scrollableContent) {
1414
+ target.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
1415
+ return;
1416
+ }
1417
+ this.scrollElementFullyIntoView(target, scrollableContent);
1418
+ });
1419
+ });
1420
+ }
1421
+ findCanvasFieldElement(fieldId) {
1422
+ const fields = this.elementRef.nativeElement.querySelectorAll('.ngs-form-builder-canvas [data-form-builder-field-id]');
1423
+ return Array.from(fields).find(field => field.dataset['formBuilderFieldId'] === fieldId) ?? null;
1424
+ }
1425
+ scrollElementFullyIntoView(target, scrollableContent) {
1426
+ const padding = 16;
1427
+ const containerRect = scrollableContent.getBoundingClientRect();
1428
+ const targetRect = target.getBoundingClientRect();
1429
+ const availableHeight = Math.max(0, containerRect.height - padding * 2);
1430
+ const availableWidth = Math.max(0, containerRect.width - padding * 2);
1431
+ let top = scrollableContent.scrollTop;
1432
+ let left = scrollableContent.scrollLeft;
1433
+ if (targetRect.height > availableHeight || targetRect.top < containerRect.top + padding) {
1434
+ top += targetRect.top - containerRect.top - padding;
1435
+ }
1436
+ else if (targetRect.bottom > containerRect.bottom - padding) {
1437
+ top += targetRect.bottom - containerRect.bottom + padding;
1438
+ }
1439
+ if (targetRect.width > availableWidth || targetRect.left < containerRect.left + padding) {
1440
+ left += targetRect.left - containerRect.left - padding;
1441
+ }
1442
+ else if (targetRect.right > containerRect.right - padding) {
1443
+ left += targetRect.right - containerRect.right + padding;
1444
+ }
1445
+ scrollableContent.scrollTo({
1446
+ top: Math.max(0, top),
1447
+ left: Math.max(0, left),
1448
+ behavior: 'smooth'
1449
+ });
1450
+ }
1451
+ afterNextPaint(callback) {
1452
+ if (typeof requestAnimationFrame === 'function') {
1453
+ requestAnimationFrame(callback);
1454
+ return;
1455
+ }
1456
+ window.setTimeout(callback);
1457
+ }
1458
+ createFieldTreeNode(field, section) {
1459
+ const definition = this.definitions().find(item => item.type === field.type);
1460
+ return {
1461
+ id: field.id,
1462
+ label: field.label,
1463
+ name: field.name,
1464
+ type: field.type,
1465
+ icon: definition?.icon || 'fluent:form-24-regular',
1466
+ kind: 'field',
1467
+ field,
1468
+ section,
1469
+ children: field.children?.map(child => this.createFieldTreeNode(child, section))
1470
+ };
1471
+ }
1472
+ findContainerLocation(schema, containerId) {
1473
+ if (containerId === ROOT_DROP_LIST_ID) {
1474
+ return {
1475
+ id: containerId,
1476
+ fields: schema.fields ?? []
1477
+ };
1478
+ }
1479
+ const rootNested = this.findFieldContainerLocation(schema.fields ?? [], undefined, containerId);
1480
+ if (rootNested) {
1481
+ return rootNested;
1482
+ }
1483
+ for (const section of schema.sections) {
1484
+ if (this.sectionDropListId(section) === containerId) {
1485
+ return {
1486
+ id: containerId,
1487
+ fields: section.fields,
1488
+ section
1489
+ };
1490
+ }
1491
+ const nested = this.findFieldContainerLocation(section.fields, section, containerId);
1492
+ if (nested) {
1493
+ return nested;
1494
+ }
1495
+ }
1496
+ return null;
1497
+ }
1498
+ findFieldContainerLocation(fields, section, containerId) {
1499
+ for (const field of fields) {
1500
+ if (this.isContainerField(field) && this.fieldDropListId(field) === containerId) {
1501
+ field.children ??= [];
1502
+ return {
1503
+ id: containerId,
1504
+ fields: field.children,
1505
+ section,
1506
+ owner: field
1507
+ };
1508
+ }
1509
+ const nested = this.findFieldContainerLocation(field.children ?? [], section, containerId);
1510
+ if (nested) {
1511
+ return nested;
1512
+ }
1513
+ }
1514
+ return null;
1515
+ }
1516
+ findFieldLocation(schema, fieldId) {
1517
+ const rootLocation = findFieldLocationInFields(schema.fields ?? [], fieldId);
1518
+ if (rootLocation) {
1519
+ return rootLocation;
1520
+ }
1521
+ for (const section of schema.sections) {
1522
+ const location = findFieldLocationInFields(section.fields, fieldId, section);
1523
+ if (location) {
1524
+ return location;
1525
+ }
1526
+ }
1527
+ return null;
1528
+ }
1529
+ deletePreviewControls(field) {
1530
+ this.previewControls.delete(field.id);
1531
+ for (const child of field.children ?? []) {
1532
+ this.deletePreviewControls(child);
1533
+ }
1534
+ }
1535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilder, isStandalone: true, selector: "ngs-form-builder", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, paletteTitle: { classPropertyName: "paletteTitle", publicName: "paletteTitle", isSignal: true, isRequired: false, transformFunction: null }, inspectorTitle: { classPropertyName: "inspectorTitle", publicName: "inspectorTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schema: "schemaChange", fieldSelected: "fieldSelected", fieldAdded: "fieldAdded", fieldRemoved: "fieldRemoved" }, host: { classAttribute: "ngs-form-builder" }, viewQueries: [{ propertyName: "actualFieldsTree", first: true, predicate: ["actualFieldsTree"], descendants: true, isSignal: true }], exportAs: ["ngsFormBuilder"], ngImport: i0, template: "<ng-template #fieldCard let-field let-section=\"section\" let-dragData=\"dragData\">\n <div\n class=\"ngs-form-builder-field\"\n cdkDrag\n [cdkDragData]=\"dragData || field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-container]=\"isContainerField(field)\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-field-handle\" cdkDragHandle aria-label=\"Move field\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n </div>\n\n @if (isContainerField(field)) {\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n }\n\n <ng-template cdkDragPlaceholder>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-line-placeholder\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n </div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-drag-preview\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n </ng-template>\n </div>\n</ng-template>\n\n<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n cdkDropList\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n [cdkDropListData]=\"fields\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n [cdkDropListEnterPredicate]=\"fieldDropListEnterPredicate\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\"\n (cdkDropListDropped)=\"dropField($event)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n section\n }\"/>\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\" cdkDropListGroup>\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-4\">\n <h3 class=\"text-base font-semibold\">{{ paletteTitle() }}</h3>\n\n <ngs-tab-group animationDuration=\"0ms\" ngs-stretch-tabs=\"false\">\n <ngs-tab label=\"Placeholders\">\n <div class=\"flex flex-col gap-4 pt-4\">\n <ngs-form-field>\n <ngs-label>Search placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"search.set($event)\">\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-5\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </div>\n </ngs-tab>\n\n <ngs-tab label=\"Actual fields\">\n <div class=\"pt-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n cdkDropList\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n [cdkDropListData]=\"canvasItems()\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\"\n (cdkDropListDropped)=\"dropCanvasItem($event)\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n dragData: item\n }\"/>\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"sectionDragStarted(section)\"\n (cdkDragEnded)=\"sectionDragEnded(section)\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-section-handle\" cdkDragHandle aria-label=\"Move section\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n\n <ng-template cdkDragPlaceholder>\n <div class=\"ngs-form-builder-section-line-placeholder\"></div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-section-drag-preview\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-section-handle\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-preview-title\">{{ section.title }}</span>\n </div>\n <ngs-card-aside>\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-5\"/>\n </ngs-card-aside>\n </ngs-card-header>\n </ngs-card>\n </ng-template>\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField(); as field) {\n <ngs-basic-form-builder-field-settings\n [field]=\"field\"\n [update]=\"updateSelectedField\"/>\n\n <ngs-form-builder-settings-host\n [field]=\"field\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"/>\n\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-primary);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1);--ngs-tree-node-drop-line-color: var(--ngs-color-primary);--ngs-tree-node-drop-target-outline: 0;--ngs-tree-node-drop-target-bg: var(--ngs-color-primary-container)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{--ngs-button-height: calc(var(--spacing, .25rem) * 6)}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-drag-preview{width:min(100%,900px);background:var(--ngs-color-surface)}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-handle{flex:none;color:var(--ngs-color-on-surface-variant);cursor:grab}:host .ngs-form-builder-section-handle:active{cursor:grabbing}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-handle{flex:none;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .cdk-drag-preview{box-sizing:border-box;border-radius:var(--ngs-radius-lg);box-shadow:var(--ngs-shadow-lg)}:host .ngs-form-builder-field-placeholder{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-field-line-placeholder{min-height:calc(var(--spacing, .25rem) * 6);justify-content:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-field-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .ngs-form-builder-field-drag-preview{width:min(360px,100vw - calc(var(--spacing, .25rem) * 8));min-height:calc(var(--spacing, .25rem) * 20);border-color:var(--ngs-color-primary);background:var(--ngs-color-surface);box-shadow:var(--ngs-shadow-lg);opacity:.94;pointer-events:none}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 18);border:1px dashed var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 6);border:0;background:transparent}:host .ngs-form-builder-section-line-placeholder{display:flex;min-height:calc(var(--spacing, .25rem) * 5);align-items:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-section-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .cdk-drag-placeholder{opacity:0}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder,:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder,:host .ngs-form-builder-canvas-list .ngs-form-builder-section-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-palette .cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-drop-list.cdk-drop-list-dragging .ngs-form-builder-field:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-canvas-list.cdk-drop-list-dragging .ngs-form-builder-section:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: DialogActions, selector: "ngs-dialog-actions, [ngs-dialog-actions], [ngsDialogActions]", inputs: ["align"] }, { kind: "directive", type: DialogClose, selector: "[ngs-dialog-close], [ngsDialogClose]", inputs: ["ngs-dialog-close", "ngsDialogClose", "ariaLabel", "type"], exportAs: ["ngsDialogClose"] }, { kind: "component", type: DialogContent, selector: "ngs-dialog-content,[ngs-dialog-content],[ngsDialogContent]" }, { kind: "component", type: DialogTitle, selector: "ngs-dialog-title, [ngs-dialog-title], [ngsDialogTitle]", inputs: ["id"], exportAs: ["ngsDialogTitle"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tree, selector: "ngs-tree", inputs: ["checkable", "selectable", "draggable", "draggablePredicate", "dropPredicate", "reorderOnDrop", "dragPreview", "childrenKey", "filterValue", "filterPredicate", "filterMode"], outputs: ["checkedChange", "selectedChange", "nodeDrop"], exportAs: ["ngsTree"] }, { kind: "directive", type: TreeDragPlaceholder, selector: "ng-template[ngsTreeDragPlaceholder]" }, { kind: "component", type: TreeNode, selector: "ngs-tree-node", inputs: ["tabIndex", "value", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["ngsTreeNode"] }, { kind: "directive", type: TreeNodeDef, selector: "[ngsTreeNodeDef]", inputs: ["ngsTreeNodeDefWhen", "ngsTreeNode"] }, { kind: "directive", type: TreeNodePadding, selector: "[ngsTreeNodePadding]", inputs: ["ngsTreeNodePadding", "ngsTreeNodePaddingIndent"] }, { kind: "component", type: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas"], exportAs: ["ngsFormBuilderFieldHost"] }, { kind: "component", type: FormBuilderRenderer, selector: "ngs-form-builder-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormBuilderRenderer"] }, { kind: "component", type: FormBuilderSettingsHost, selector: "ngs-form-builder-settings-host", inputs: ["field", "schema", "definitions", "settingsDefinitions", "update"], exportAs: ["ngsFormBuilderSettingsHost"] }, { kind: "component", type: BasicFormBuilderFieldSettings, selector: "ngs-basic-form-builder-field-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderFieldSettings"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1537
+ }
1538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, decorators: [{
1539
+ type: Component,
1540
+ args: [{ selector: 'ngs-form-builder', exportAs: 'ngsFormBuilder', imports: [
1541
+ NgTemplateOutlet,
1542
+ FormsModule,
1543
+ CdkDrag,
1544
+ CdkDragHandle,
1545
+ CdkDragPlaceholder,
1546
+ CdkDragPreview,
1547
+ CdkDropList,
1548
+ CdkDropListGroup,
1549
+ Button,
1550
+ Card,
1551
+ CardAside,
1552
+ CardContent,
1553
+ CardHeader,
1554
+ DialogActions,
1555
+ DialogClose,
1556
+ DialogContent,
1557
+ DialogTitle,
1558
+ FormField,
1559
+ Icon,
1560
+ Input,
1561
+ Label,
1562
+ Panel,
1563
+ PanelAside,
1564
+ PanelContent,
1565
+ PanelHeader,
1566
+ PanelSidebar,
1567
+ ScrollbarArea,
1568
+ Tab,
1569
+ TabGroup,
1570
+ Tree,
1571
+ TreeDragPlaceholder,
1572
+ TreeNode,
1573
+ TreeNodeDef,
1574
+ TreeNodePadding,
1575
+ FormBuilderFieldHost,
1576
+ FormBuilderRenderer,
1577
+ FormBuilderSettingsHost,
1578
+ BasicFormBuilderFieldSettings
1579
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1580
+ 'class': 'ngs-form-builder'
1581
+ }, template: "<ng-template #fieldCard let-field let-section=\"section\" let-dragData=\"dragData\">\n <div\n class=\"ngs-form-builder-field\"\n cdkDrag\n [cdkDragData]=\"dragData || field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-container]=\"isContainerField(field)\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-field-handle\" cdkDragHandle aria-label=\"Move field\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n </div>\n\n @if (isContainerField(field)) {\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n }\n\n <ng-template cdkDragPlaceholder>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-line-placeholder\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n </div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-drag-preview\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n </ng-template>\n </div>\n</ng-template>\n\n<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n cdkDropList\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n [cdkDropListData]=\"fields\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n [cdkDropListEnterPredicate]=\"fieldDropListEnterPredicate\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\"\n (cdkDropListDropped)=\"dropField($event)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n section\n }\"/>\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\" cdkDropListGroup>\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-4\">\n <h3 class=\"text-base font-semibold\">{{ paletteTitle() }}</h3>\n\n <ngs-tab-group animationDuration=\"0ms\" ngs-stretch-tabs=\"false\">\n <ngs-tab label=\"Placeholders\">\n <div class=\"flex flex-col gap-4 pt-4\">\n <ngs-form-field>\n <ngs-label>Search placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"search()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"search.set($event)\">\n </ngs-form-field>\n\n <div class=\"flex flex-col gap-5\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </div>\n </ngs-tab>\n\n <ngs-tab label=\"Actual fields\">\n <div class=\"pt-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n @if (node.name) {\n <small>{{ node.name }}</small>\n }\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-tab>\n </ngs-tab-group>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n cdkDropList\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n [cdkDropListData]=\"canvasItems()\"\n [cdkDropListConnectedTo]=\"fieldContainerDropListIds()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\"\n (cdkDropListDropped)=\"dropCanvasItem($event)\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n <ng-container\n [ngTemplateOutlet]=\"fieldCard\"\n [ngTemplateOutletContext]=\"{\n $implicit: field,\n dragData: item\n }\"/>\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n cdkDrag\n cdkDragLockAxis=\"y\"\n [cdkDragData]=\"item\"\n (cdkDragStarted)=\"sectionDragStarted(section)\"\n (cdkDragEnded)=\"sectionDragEnded(section)\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <button ngsIconButton type=\"button\" class=\"ngs-form-builder-section-handle\" cdkDragHandle aria-label=\"Move section\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </button>\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n\n <ng-template cdkDragPlaceholder>\n <div class=\"ngs-form-builder-section-line-placeholder\"></div>\n </ng-template>\n\n <ng-template cdkDragPreview>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-section-drag-preview\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-section-handle\">\n <ngs-icon name=\"fluent:re-order-dots-vertical-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-preview-title\">{{ section.title }}</span>\n </div>\n <ngs-card-aside>\n <ngs-icon name=\"fluent:chevron-down-24-regular\" class=\"size-5\"/>\n </ngs-card-aside>\n </ngs-card-header>\n </ngs-card>\n </ng-template>\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField(); as field) {\n <ngs-basic-form-builder-field-settings\n [field]=\"field\"\n [update]=\"updateSelectedField\"/>\n\n <ngs-form-builder-settings-host\n [field]=\"field\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"/>\n\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-builder-renderer\n [schema]=\"schema()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-primary);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1);--ngs-tree-node-drop-line-color: var(--ngs-color-primary);--ngs-tree-node-drop-target-outline: 0;--ngs-tree-node-drop-target-bg: var(--ngs-color-primary-container)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{--ngs-button-height: calc(var(--spacing, .25rem) * 6)}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-drag-preview{width:min(100%,900px);background:var(--ngs-color-surface)}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-handle{flex:none;color:var(--ngs-color-on-surface-variant);cursor:grab}:host .ngs-form-builder-section-handle:active{cursor:grabbing}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-handle{flex:none;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .cdk-drag-preview{box-sizing:border-box;border-radius:var(--ngs-radius-lg);box-shadow:var(--ngs-shadow-lg)}:host .ngs-form-builder-field-placeholder{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-field-line-placeholder{min-height:calc(var(--spacing, .25rem) * 6);justify-content:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-field-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .ngs-form-builder-field-drag-preview{width:min(360px,100vw - calc(var(--spacing, .25rem) * 8));min-height:calc(var(--spacing, .25rem) * 20);border-color:var(--ngs-color-primary);background:var(--ngs-color-surface);box-shadow:var(--ngs-shadow-lg);opacity:.94;pointer-events:none}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 18);border:1px dashed var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{min-height:calc(var(--spacing, .25rem) * 6);border:0;background:transparent}:host .ngs-form-builder-section-line-placeholder{display:flex;min-height:calc(var(--spacing, .25rem) * 5);align-items:center;padding:0;border:0;background:transparent;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-section-line-placeholder:before{content:\"\";display:block;width:100%;height:2px;border-radius:var(--ngs-radius-full);background:var(--ngs-color-primary)}:host .cdk-drag-placeholder{opacity:0}:host .ngs-form-builder-canvas-drop-list .cdk-drag-placeholder,:host .ngs-form-builder-canvas-drop-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-list .ngs-form-builder-field-line-placeholder.cdk-drag-placeholder,:host .ngs-form-builder-canvas-list .ngs-form-builder-section-line-placeholder.cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-palette .cdk-drag-placeholder{opacity:1}:host .ngs-form-builder-canvas-drop-list.cdk-drop-list-dragging .ngs-form-builder-field:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-canvas-list.cdk-drop-list-dragging .ngs-form-builder-section:not(.cdk-drag-placeholder){transition:transform .16s cubic-bezier(0,0,.2,1)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
1582
+ }], propDecorators: { actualFieldsTree: [{ type: i0.ViewChild, args: ['actualFieldsTree', { isSignal: true }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }, { type: i0.Output, args: ["schemaChange"] }], paletteTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "paletteTitle", required: false }] }], inspectorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorTitle", required: false }] }], fieldSelected: [{ type: i0.Output, args: ["fieldSelected"] }], fieldAdded: [{ type: i0.Output, args: ["fieldAdded"] }], fieldRemoved: [{ type: i0.Output, args: ["fieldRemoved"] }] } });
1583
+ function createDefaultFormBuilderSchema() {
1584
+ const sectionId = uniqueId('section');
1585
+ return {
1586
+ title: 'New form',
1587
+ fields: [],
1588
+ layout: [{ kind: 'section', id: sectionId }],
1589
+ sections: [
1590
+ {
1591
+ id: sectionId,
1592
+ title: 'General information',
1593
+ fields: []
1594
+ }
1595
+ ]
1596
+ };
1597
+ }
1598
+ function uniqueId(prefix) {
1599
+ return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
1600
+ }
1601
+ function cloneSchema(schema) {
1602
+ return {
1603
+ ...schema,
1604
+ fields: (schema.fields ?? []).map(cloneField),
1605
+ layout: schema.layout?.map(item => ({ ...item })),
1606
+ sections: schema.sections.map(section => ({
1607
+ ...section,
1608
+ fields: section.fields.map(cloneField)
1609
+ }))
1610
+ };
1611
+ }
1612
+ function cloneField(field) {
1613
+ return {
1614
+ ...field,
1615
+ options: field.options?.map(option => ({ ...option })),
1616
+ validation: field.validation?.map(rule => ({ ...rule })),
1617
+ visibility: field.visibility ? { ...field.visibility } : undefined,
1618
+ settings: field.settings ? { ...field.settings } : undefined,
1619
+ children: field.children?.map(cloneField)
1620
+ };
1621
+ }
1622
+ function isFormBuilderLayoutItem(value) {
1623
+ if (!value || typeof value !== 'object') {
1624
+ return false;
1625
+ }
1626
+ const item = value;
1627
+ return (item.kind === 'field' || item.kind === 'section') && typeof item.id === 'string';
1628
+ }
1629
+ function isFormBuilderField(value) {
1630
+ if (!value || typeof value !== 'object') {
1631
+ return false;
1632
+ }
1633
+ const field = value;
1634
+ return typeof field.id === 'string' && typeof field.type === 'string';
1635
+ }
1636
+ function clampIndex(index, length) {
1637
+ return Math.max(0, Math.min(index, length));
1638
+ }
1639
+ function toFieldName(label) {
1640
+ return label
1641
+ .trim()
1642
+ .toLowerCase()
1643
+ .replace(/[^a-z0-9]+/g, '_')
1644
+ .replace(/^_+|_+$/g, '') || 'field';
1645
+ }
1646
+ function uniqueFieldName(baseName, schema) {
1647
+ const usedNames = new Set([
1648
+ ...(schema.fields ?? []).flatMap(field => collectFieldNames(field)),
1649
+ ...schema.sections.flatMap(section => section.fields.flatMap(field => collectFieldNames(field)))
1650
+ ]);
1651
+ if (!usedNames.has(baseName)) {
1652
+ return baseName;
1653
+ }
1654
+ let index = 2;
1655
+ let nextName = `${baseName}_${index}`;
1656
+ while (usedNames.has(nextName)) {
1657
+ index += 1;
1658
+ nextName = `${baseName}_${index}`;
1659
+ }
1660
+ return nextName;
1661
+ }
1662
+ function collectFieldNames(field) {
1663
+ return [field.name, ...(field.children ?? []).flatMap(collectFieldNames)];
1664
+ }
1665
+ function findFieldLocationInFields(fields, fieldId, section) {
1666
+ const index = fields.findIndex(field => field.id === fieldId);
1667
+ if (index !== -1) {
1668
+ return {
1669
+ field: fields[index],
1670
+ section,
1671
+ siblings: fields,
1672
+ index
1673
+ };
1674
+ }
1675
+ for (const field of fields) {
1676
+ const location = findFieldLocationInFields(field.children ?? [], fieldId, section);
1677
+ if (location) {
1678
+ return location;
1679
+ }
1680
+ }
1681
+ return null;
1682
+ }
1683
+ function containsField(field, fieldId) {
1684
+ if (!fieldId) {
1685
+ return false;
1686
+ }
1687
+ return (field.children ?? []).some(child => child.id === fieldId || containsField(child, fieldId));
1688
+ }
1689
+
1690
+ /**
1691
+ * Generated bundle index. Do not edit.
1692
+ */
1693
+
1694
+ export { BasicFormBuilderFieldSettings, DEFAULT_FORM_BUILDER_FIELDS, FORM_BUILDER_FIELDS, FORM_BUILDER_SETTINGS, FormBuilder, FormBuilderFieldHost, FormBuilderRenderer, FormBuilderSettingsHost, FormBuilderRenderer as FormRenderer, createDefaultFormBuilderSchema, formBuilderField, formBuilderSettings, provideFormBuilder, validatorsFromRules };
1695
+ //# sourceMappingURL=ngstarter-ui-components-form-builder.mjs.map