@ngstarter-ui/components 21.0.45 → 21.0.47

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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, input, signal, computed, viewChild, ViewContainerRef, effect, ChangeDetectionStrategy, Component, model, output, DestroyRef, ElementRef } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, inject, input, signal, computed, viewChild, ViewContainerRef, effect, ChangeDetectionStrategy, Component, model, output, ElementRef, DestroyRef } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
- import { Validators, ReactiveFormsModule, FormControl, FormGroup, FormsModule } from '@angular/forms';
4
+ import { Validators, ReactiveFormsModule, FormControl, FormArray, FormGroup, FormsModule } from '@angular/forms';
5
5
  import { NgTemplateOutlet } from '@angular/common';
6
6
  import { Button } from '@ngstarter-ui/components/button';
7
7
  import { Card, CardAside, CardContent, CardHeader } from '@ngstarter-ui/components/card';
@@ -12,6 +12,7 @@ import { Input } from '@ngstarter-ui/components/input';
12
12
  import { Panel, PanelAside, PanelContent, PanelHeader, PanelSidebar } from '@ngstarter-ui/components/panel';
13
13
  import { ScrollbarArea } from '@ngstarter-ui/components/scrollbar-area';
14
14
  import { Tab, TabGroup } from '@ngstarter-ui/components/tabs';
15
+ import { Toolbar, ToolbarItem, ToolbarSpacer, ToolbarTitle, ToolbarSubtitle } from '@ngstarter-ui/components/toolbar';
15
16
  import { Tree, TreeDragPlaceholder, TreeNode, TreeNodeDef, TreeNodePadding } from '@ngstarter-ui/components/tree';
16
17
  import { DateRange, Datepicker, DatepickerInput, DatepickerToggle, DateRangeInput, DateRangePicker, StartDate, EndDate, provideNativeDateAdapter } from '@ngstarter-ui/components/datepicker';
17
18
  import { FormField, Hint, IconButtonSuffix, Label, Error } from '@ngstarter-ui/components/form-field';
@@ -40,6 +41,16 @@ function formBuilderItem(definition) {
40
41
  function formBuilderSettings(definition) {
41
42
  return definition;
42
43
  }
44
+ function provideFormBuilderField(definition) {
45
+ return {
46
+ provide: FORM_BUILDER_FIELDS,
47
+ useValue: definition,
48
+ multi: true
49
+ };
50
+ }
51
+ function provideFormBuilderFields(definitions) {
52
+ return definitions.map(definition => provideFormBuilderField(definition));
53
+ }
43
54
  function provideFormBuilder(config = {}) {
44
55
  return makeEnvironmentProviders([
45
56
  ...(config.uploadCallback ? [{
@@ -63,6 +74,130 @@ function provideFormBuilder(config = {}) {
63
74
  }))
64
75
  ]);
65
76
  }
77
+ const FIELD_WIDTH_OPTIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
78
+ .map(width => ({ label: `${width}/12`, value: width }));
79
+ const SPACER_HEIGHT_OPTIONS = [8, 16, 24, 32, 48, 64]
80
+ .map(height => ({ label: `${height}px`, value: height }));
81
+ const ORIENTATION_OPTIONS = [
82
+ { label: 'Vertical', value: 'vertical' },
83
+ { label: 'Horizontal', value: 'horizontal' }
84
+ ];
85
+ const FORM_BUILDER_FIELD_BASE_SETTINGS_SCHEMA = {
86
+ sections: [
87
+ {
88
+ id: 'field-base-settings',
89
+ title: 'Field',
90
+ fields: [
91
+ { id: 'field-label', name: 'label', type: 'text', label: 'Label' },
92
+ { id: 'field-name', name: 'name', type: 'text', label: 'Field ID' },
93
+ { id: 'field-hint', name: 'hint', type: 'text', label: 'Hint' },
94
+ { id: 'field-width', name: 'width', type: 'select', label: 'Width', defaultValue: 12, options: FIELD_WIDTH_OPTIONS },
95
+ { id: 'field-required', name: 'required', type: 'toggle', label: 'Required field', defaultValue: false },
96
+ { id: 'field-readonly', name: 'readonly', type: 'toggle', label: 'Readonly', defaultValue: false },
97
+ { id: 'field-disabled', name: 'disabled', type: 'toggle', label: 'Disabled', defaultValue: false }
98
+ ]
99
+ }
100
+ ]
101
+ };
102
+ const FORM_BUILDER_INPUT_FIELD_BASE_SETTINGS_SCHEMA = {
103
+ sections: [
104
+ {
105
+ id: 'input-field-base-settings',
106
+ title: 'Field',
107
+ fields: [
108
+ { id: 'input-field-label', name: 'label', type: 'text', label: 'Label' },
109
+ { id: 'input-field-name', name: 'name', type: 'text', label: 'Field ID' },
110
+ { id: 'input-field-placeholder', name: 'placeholder', type: 'text', label: 'Placeholder' },
111
+ { id: 'input-field-hint', name: 'hint', type: 'text', label: 'Hint' },
112
+ { id: 'input-field-width', name: 'width', type: 'select', label: 'Width', defaultValue: 12, options: FIELD_WIDTH_OPTIONS },
113
+ { id: 'input-field-required', name: 'required', type: 'toggle', label: 'Required field', defaultValue: false },
114
+ { id: 'input-field-readonly', name: 'readonly', type: 'toggle', label: 'Readonly', defaultValue: false },
115
+ { id: 'input-field-disabled', name: 'disabled', type: 'toggle', label: 'Disabled', defaultValue: false }
116
+ ]
117
+ }
118
+ ]
119
+ };
120
+ const FORM_BUILDER_LAYOUT_BASE_SETTINGS_SCHEMA = {
121
+ sections: [
122
+ {
123
+ id: 'layout-base-settings',
124
+ title: 'Layout',
125
+ fields: [
126
+ { id: 'layout-label', name: 'label', type: 'text', label: 'Label' },
127
+ { id: 'layout-hint', name: 'hint', type: 'text', label: 'Hint' },
128
+ { id: 'layout-width', name: 'width', type: 'select', label: 'Width', defaultValue: 12, options: FIELD_WIDTH_OPTIONS }
129
+ ]
130
+ }
131
+ ]
132
+ };
133
+ const FORM_BUILDER_LAYOUT_CONTAINER_BASE_SETTINGS_SCHEMA = {
134
+ sections: [
135
+ {
136
+ id: 'layout-container-base-settings',
137
+ title: 'Layout',
138
+ fields: [
139
+ { id: 'layout-container-label', name: 'label', type: 'text', label: 'Label' },
140
+ { id: 'layout-container-width', name: 'width', type: 'select', label: 'Width', defaultValue: 12, options: FIELD_WIDTH_OPTIONS }
141
+ ]
142
+ }
143
+ ]
144
+ };
145
+ const FORM_BUILDER_STATIC_BASE_SETTINGS_SCHEMA = {
146
+ sections: [
147
+ {
148
+ id: 'static-base-settings',
149
+ title: 'Static block',
150
+ fields: [
151
+ { id: 'static-label', name: 'label', type: 'text', label: 'Label' },
152
+ { id: 'static-width', name: 'width', type: 'select', label: 'Width', defaultValue: 12, options: FIELD_WIDTH_OPTIONS }
153
+ ]
154
+ }
155
+ ]
156
+ };
157
+ const FORM_BUILDER_SECTION_BASE_SETTINGS_SCHEMA = {
158
+ sections: [
159
+ {
160
+ id: 'section-base-settings',
161
+ title: 'Section',
162
+ fields: [
163
+ { id: 'section-title', name: 'title', type: 'text', label: 'Title' },
164
+ { id: 'section-description', name: 'description', type: 'textarea', label: 'Description', hint: 'Optional helper text rendered under the section title.' },
165
+ { id: 'section-collapsed', name: 'collapsed', type: 'toggle', label: 'Collapsed', defaultValue: false }
166
+ ]
167
+ }
168
+ ]
169
+ };
170
+ const CHECKED_SETTINGS_SCHEMA = {
171
+ sections: [
172
+ {
173
+ id: 'checked-settings',
174
+ title: 'Default state',
175
+ fields: [
176
+ { id: 'default-checked', name: 'defaultValue', type: 'toggle', label: 'Checked', defaultValue: false }
177
+ ]
178
+ }
179
+ ]
180
+ };
181
+ const OPTIONS_SETTINGS_SCHEMA = {
182
+ sections: [
183
+ {
184
+ id: 'options-settings',
185
+ title: 'Options',
186
+ fields: [
187
+ {
188
+ id: 'options',
189
+ name: 'options',
190
+ type: 'textarea',
191
+ label: 'Options',
192
+ hint: 'One option per line. Use Label:value or Label:value:selected.',
193
+ settings: {
194
+ valueAdapter: 'optionsText'
195
+ }
196
+ }
197
+ ]
198
+ }
199
+ ]
200
+ };
66
201
  const DEFAULT_FORM_BUILDER_FIELDS = [
67
202
  {
68
203
  type: 'text',
@@ -72,6 +207,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
72
207
  defaults: {
73
208
  label: 'Text field',
74
209
  placeholder: 'Enter text'
210
+ },
211
+ settings: {
212
+ extends: 'input-field'
75
213
  }
76
214
  },
77
215
  {
@@ -82,6 +220,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
82
220
  defaults: {
83
221
  label: 'Number',
84
222
  placeholder: '0'
223
+ },
224
+ settings: {
225
+ extends: 'input-field'
85
226
  }
86
227
  },
87
228
  {
@@ -93,6 +234,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
93
234
  label: 'Email',
94
235
  placeholder: 'name@example.com'
95
236
  },
237
+ settings: {
238
+ extends: 'input-field'
239
+ },
96
240
  validators: field => field.required ? [Validators.required, Validators.email] : [Validators.email]
97
241
  },
98
242
  {
@@ -104,6 +248,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
104
248
  label: 'Description',
105
249
  placeholder: 'Enter description',
106
250
  width: 12
251
+ },
252
+ settings: {
253
+ extends: 'input-field'
107
254
  }
108
255
  },
109
256
  {
@@ -119,6 +266,56 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
119
266
  label: 'Group',
120
267
  width: 12,
121
268
  children: []
269
+ },
270
+ settings: {
271
+ extends: 'layout-container'
272
+ }
273
+ },
274
+ {
275
+ type: 'repeater',
276
+ label: 'Repeater',
277
+ kind: 'layout',
278
+ group: 'Layout',
279
+ icon: 'fluent:copy-add-24-regular',
280
+ description: 'Repeat a group of nested fields as a FormArray.',
281
+ acceptsChildren: true,
282
+ defaults: {
283
+ kind: 'layout',
284
+ label: 'Repeater',
285
+ name: 'items',
286
+ width: 12,
287
+ settings: {
288
+ allowNullValue: false,
289
+ emptyText: 'No items added yet.'
290
+ },
291
+ children: []
292
+ },
293
+ settings: {
294
+ extends: 'layout-container',
295
+ schema: {
296
+ sections: [
297
+ {
298
+ id: 'repeater-settings',
299
+ title: 'Repeater',
300
+ fields: [
301
+ {
302
+ id: 'repeater-allow-null-value',
303
+ name: 'settings.allowNullValue',
304
+ type: 'toggle',
305
+ label: 'Allow null value',
306
+ defaultValue: false
307
+ },
308
+ {
309
+ id: 'repeater-empty-text',
310
+ name: 'settings.emptyText',
311
+ type: 'textarea',
312
+ label: 'Empty text',
313
+ defaultValue: 'No items added yet.'
314
+ }
315
+ ]
316
+ }
317
+ ]
318
+ }
122
319
  }
123
320
  },
124
321
  {
@@ -135,6 +332,20 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
135
332
  settings: {
136
333
  height: 24
137
334
  }
335
+ },
336
+ settings: {
337
+ extends: 'static',
338
+ schema: {
339
+ sections: [
340
+ {
341
+ id: 'spacer-settings',
342
+ title: 'Spacer',
343
+ fields: [
344
+ { id: 'spacer-height', name: 'settings.height', type: 'select', label: 'Height', defaultValue: 24, options: SPACER_HEIGHT_OPTIONS }
345
+ ]
346
+ }
347
+ ]
348
+ }
138
349
  }
139
350
  },
140
351
  {
@@ -150,6 +361,31 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
150
361
  { label: 'Option 1', value: 'option_1' },
151
362
  { label: 'Option 2', value: 'option_2' }
152
363
  ]
364
+ },
365
+ settings: {
366
+ extends: 'input-field',
367
+ schema: {
368
+ sections: [
369
+ {
370
+ id: 'select-behavior-settings',
371
+ title: 'Select',
372
+ fields: [
373
+ {
374
+ id: 'select-multiple',
375
+ name: 'multiple',
376
+ type: 'toggle',
377
+ label: 'Multiple',
378
+ defaultValue: false,
379
+ settings: {
380
+ valueAdapter: 'selectMultiple'
381
+ }
382
+ },
383
+ { id: 'select-clearable', name: 'clearable', type: 'toggle', label: 'Clearable', defaultValue: true }
384
+ ]
385
+ },
386
+ ...OPTIONS_SETTINGS_SCHEMA.sections
387
+ ]
388
+ }
153
389
  }
154
390
  },
155
391
  {
@@ -166,6 +402,21 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
166
402
  { label: 'Option 1', value: 'option_1' },
167
403
  { label: 'Option 2', value: 'option_2' }
168
404
  ]
405
+ },
406
+ settings: {
407
+ extends: 'field',
408
+ schema: {
409
+ sections: [
410
+ {
411
+ id: 'radio-settings',
412
+ title: 'Radio',
413
+ fields: [
414
+ { id: 'radio-orientation', name: 'settings.orientation', type: 'select', label: 'Orientation', defaultValue: 'vertical', options: ORIENTATION_OPTIONS }
415
+ ]
416
+ },
417
+ ...OPTIONS_SETTINGS_SCHEMA.sections
418
+ ]
419
+ }
169
420
  }
170
421
  },
171
422
  {
@@ -176,6 +427,10 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
176
427
  defaults: {
177
428
  label: 'Checkbox',
178
429
  defaultValue: false
430
+ },
431
+ settings: {
432
+ extends: 'field',
433
+ schema: CHECKED_SETTINGS_SCHEMA
179
434
  }
180
435
  },
181
436
  {
@@ -186,6 +441,10 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
186
441
  defaults: {
187
442
  label: 'Toggle',
188
443
  defaultValue: false
444
+ },
445
+ settings: {
446
+ extends: 'field',
447
+ schema: CHECKED_SETTINGS_SCHEMA
189
448
  }
190
449
  },
191
450
  {
@@ -196,6 +455,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
196
455
  defaults: {
197
456
  label: 'Date',
198
457
  placeholder: 'Select date'
458
+ },
459
+ settings: {
460
+ extends: 'input-field'
199
461
  }
200
462
  },
201
463
  {
@@ -206,6 +468,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
206
468
  defaults: {
207
469
  label: 'Time',
208
470
  placeholder: 'Select time'
471
+ },
472
+ settings: {
473
+ extends: 'input-field'
209
474
  }
210
475
  },
211
476
  {
@@ -217,6 +482,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
217
482
  label: 'Date range',
218
483
  placeholder: 'Start date',
219
484
  width: 12
485
+ },
486
+ settings: {
487
+ extends: 'input-field'
220
488
  }
221
489
  },
222
490
  {
@@ -232,6 +500,37 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
232
500
  settings: {
233
501
  accept: '*/*'
234
502
  }
503
+ },
504
+ settings: {
505
+ extends: 'input-field',
506
+ schema: {
507
+ sections: [
508
+ {
509
+ id: 'upload-settings',
510
+ title: 'Upload',
511
+ fields: [
512
+ {
513
+ id: 'upload-multiple',
514
+ name: 'multiple',
515
+ type: 'toggle',
516
+ label: 'Multiple',
517
+ defaultValue: false,
518
+ settings: {
519
+ valueAdapter: 'multipleDefaultValue'
520
+ }
521
+ },
522
+ {
523
+ id: 'upload-accept',
524
+ name: 'settings.accept',
525
+ type: 'text',
526
+ label: 'Accepted file types',
527
+ defaultValue: '*/*',
528
+ hint: 'Use MIME types separated by commas, for example image/*,application/pdf.'
529
+ }
530
+ ]
531
+ }
532
+ ]
533
+ }
235
534
  }
236
535
  },
237
536
  {
@@ -242,6 +541,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
242
541
  defaults: {
243
542
  label: 'Timezone',
244
543
  placeholder: 'Select timezone'
544
+ },
545
+ settings: {
546
+ extends: 'input-field'
245
547
  }
246
548
  },
247
549
  {
@@ -252,6 +554,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
252
554
  defaults: {
253
555
  label: 'Amount',
254
556
  placeholder: '0.00'
557
+ },
558
+ settings: {
559
+ extends: 'input-field'
255
560
  }
256
561
  },
257
562
  {
@@ -262,6 +567,9 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
262
567
  defaults: {
263
568
  label: 'Currency',
264
569
  placeholder: 'Select currency'
570
+ },
571
+ settings: {
572
+ extends: 'input-field'
265
573
  }
266
574
  },
267
575
  {
@@ -273,6 +581,20 @@ const DEFAULT_FORM_BUILDER_FIELDS = [
273
581
  label: 'Country',
274
582
  placeholder: 'Select country',
275
583
  clearable: true
584
+ },
585
+ settings: {
586
+ extends: 'input-field',
587
+ schema: {
588
+ sections: [
589
+ {
590
+ id: 'country-select-settings',
591
+ title: 'Country select',
592
+ fields: [
593
+ { id: 'country-clearable', name: 'clearable', type: 'toggle', label: 'Clearable', defaultValue: true }
594
+ ]
595
+ }
596
+ ]
597
+ }
276
598
  }
277
599
  }
278
600
  ];
@@ -284,7 +606,11 @@ const DEFAULT_FORM_BUILDER_ITEMS = [
284
606
  group: 'Layout',
285
607
  icon: 'fluent:folder-24-regular',
286
608
  description: 'Top-level layout container.',
287
- acceptsChildren: true
609
+ acceptsChildren: true,
610
+ settings: {
611
+ extends: 'none',
612
+ schema: FORM_BUILDER_SECTION_BASE_SETTINGS_SCHEMA
613
+ }
288
614
  },
289
615
  ...DEFAULT_FORM_BUILDER_FIELDS
290
616
  ];
@@ -550,8 +876,8 @@ class FormBuilderRenderer {
550
876
  onCleanup(() => subscription.unsubscribe());
551
877
  });
552
878
  }
553
- getControl(field) {
554
- const control = this.formGroup().get(field.name);
879
+ getControl(field, formGroup = this.formGroup()) {
880
+ const control = formGroup.controls[field.name];
555
881
  if (control instanceof FormControl) {
556
882
  return control;
557
883
  }
@@ -577,6 +903,44 @@ class FormBuilderRenderer {
577
903
  visibleChildren(field) {
578
904
  return this.visibleFields(field.children ?? []);
579
905
  }
906
+ isRepeaterField(field) {
907
+ return field.type === 'repeater';
908
+ }
909
+ repeaterArray(field, formGroup = this.formGroup()) {
910
+ const control = formGroup.controls[field.name];
911
+ return control instanceof FormArray
912
+ ? control
913
+ : new FormArray([]);
914
+ }
915
+ repeaterGroups(field, formGroup = this.formGroup()) {
916
+ return this.repeaterArray(field, formGroup).controls;
917
+ }
918
+ repeaterEmptyText(field) {
919
+ const emptyText = field.settings?.['emptyText'];
920
+ return typeof emptyText === 'string' ? emptyText.trim() : '';
921
+ }
922
+ addRepeaterItem(field, formGroup = this.formGroup()) {
923
+ if (this.readonly()) {
924
+ return;
925
+ }
926
+ const array = this.repeaterArray(field, formGroup);
927
+ array.push(this.createRepeaterGroup(field));
928
+ array.updateValueAndValidity();
929
+ }
930
+ removeRepeaterItem(field, index, formGroup = this.formGroup()) {
931
+ if (this.readonly()) {
932
+ return;
933
+ }
934
+ if (!this.canRemoveRepeaterItem(field, formGroup)) {
935
+ return;
936
+ }
937
+ const array = this.repeaterArray(field, formGroup);
938
+ array.removeAt(index);
939
+ array.updateValueAndValidity();
940
+ }
941
+ canRemoveRepeaterItem(field, formGroup = this.formGroup()) {
942
+ return this.allowsNullValue(field) || this.repeaterArray(field, formGroup).length > 1;
943
+ }
580
944
  submit() {
581
945
  const form = this.formGroup();
582
946
  if (form.invalid) {
@@ -589,12 +953,24 @@ class FormBuilderRenderer {
589
953
  const controls = {};
590
954
  const value = this.value();
591
955
  const fields = [
592
- ...flattenFields(this.schema().fields ?? []),
593
- ...this.schema().sections.flatMap(section => flattenFields(section.fields))
956
+ ...(this.schema().fields ?? []),
957
+ ...this.schema().sections.flatMap(section => section.fields)
594
958
  ];
959
+ this.addFieldsToControls(controls, fields, value);
960
+ return new FormGroup(controls);
961
+ }
962
+ addFieldsToControls(controls, fields, value) {
595
963
  for (const field of fields) {
596
964
  const definition = this.definitions().find(item => item.type === field.type);
597
- if (this.isContainerField(field) || definition?.kind === 'static' || field.kind === 'static') {
965
+ if (this.isRepeaterField(field)) {
966
+ controls[field.name] = this.createRepeaterArray(field, value[field.name]);
967
+ continue;
968
+ }
969
+ if (this.isContainerField(field)) {
970
+ this.addFieldsToControls(controls, field.children ?? [], value);
971
+ continue;
972
+ }
973
+ if (definition?.kind === 'static' || field.kind === 'static') {
598
974
  continue;
599
975
  }
600
976
  const validators = definition?.validators?.(field) ?? validatorsFromRules(field.validation, field);
@@ -604,6 +980,31 @@ class FormBuilderRenderer {
604
980
  }, validators);
605
981
  controls[field.name] = control;
606
982
  }
983
+ }
984
+ createRepeaterArray(field, value) {
985
+ const allowNullValue = this.allowsNullValue(field);
986
+ const rows = Array.isArray(value) && (allowNullValue || value.length > 0)
987
+ ? value
988
+ : allowNullValue
989
+ ? []
990
+ : [{}];
991
+ const validators = allowNullValue ? [] : [repeaterRequiredValidator];
992
+ return new FormArray(rows.map(row => this.createRepeaterGroup(field, row)), validators);
993
+ }
994
+ createRepeaterGroup(field, value = {}) {
995
+ const controls = {};
996
+ const rowValue = isRecord(value) ? value : {};
997
+ for (const child of flattenFields(field.children ?? [])) {
998
+ const definition = this.definitions().find(item => item.type === child.type);
999
+ if (this.isContainerField(child) || definition?.kind === 'static' || child.kind === 'static') {
1000
+ continue;
1001
+ }
1002
+ const validators = definition?.validators?.(child) ?? validatorsFromRules(child.validation, child);
1003
+ controls[child.name] = new FormControl({
1004
+ value: rowValue[child.name] ?? this.fieldInitialValue(child),
1005
+ disabled: child.disabled || this.readonly()
1006
+ }, validators);
1007
+ }
607
1008
  return new FormGroup(controls);
608
1009
  }
609
1010
  visibleFields(fields) {
@@ -629,6 +1030,9 @@ class FormBuilderRenderer {
629
1030
  }
630
1031
  return selectedValues[0] ?? null;
631
1032
  }
1033
+ allowsNullValue(field) {
1034
+ return field.settings?.['allowNullValue'] === true;
1035
+ }
632
1036
  resolveCanvasItems(schema) {
633
1037
  const fieldsById = new Map((schema.fields ?? []).map(field => [field.id, field]));
634
1038
  const sectionsById = new Map(schema.sections.map(section => [section.id, section]));
@@ -649,7 +1053,7 @@ class FormBuilderRenderer {
649
1053
  return items;
650
1054
  }
651
1055
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
652
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderRenderer, isStandalone: true, selector: "ngs-form-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 }, uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", 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-renderer" }, exportAs: ["ngsFormRenderer"], ngImport: i0, template: "<ng-template #renderFields let-fields>\n <div class=\"ngs-form-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isContainerField(field)) {\n <section class=\"ngs-form-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 [uploadCallback]=\"uploadCallback()\"\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-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-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-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-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-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", "uploadCallback"], exportAs: ["ngsFormBuilderFieldHost"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1056
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderRenderer, isStandalone: true, selector: "ngs-form-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 }, uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", 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-renderer" }, exportAs: ["ngsFormRenderer"], ngImport: i0, template: "<ng-template #renderFields let-fields let-controlGroup=\"controlGroup\">\n <div class=\"ngs-form-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isRepeaterField(field)) {\n <section class=\"ngs-form-renderer-grid-field ngs-form-renderer-repeater\">\n <header>\n <div>\n <h4>{{ field.label }}</h4>\n @if (field.hint) {\n <p>{{ field.hint }}</p>\n }\n </div>\n\n @if (!readonly()) {\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"addRepeaterItem(field, controlGroup)\">\n +\n </button>\n }\n </header>\n\n <div class=\"ngs-form-renderer-repeater-items\">\n @if (!repeaterGroups(field, controlGroup).length && repeaterEmptyText(field)) {\n <p class=\"ngs-form-renderer-repeater-empty\">{{ repeaterEmptyText(field) }}</p>\n }\n\n @for (group of repeaterGroups(field, controlGroup); track $index) {\n <div class=\"ngs-form-renderer-repeater-item\" [formGroup]=\"group\">\n <div class=\"ngs-form-renderer-repeater-item-header\">\n <span>{{ field.label }} {{ $index + 1 }}</span>\n @if (!readonly() && canRemoveRepeaterItem(field, controlGroup)) {\n <button ngsButton=\"text\" type=\"button\" (click)=\"removeRepeaterItem(field, $index, controlGroup)\">\n Remove\n </button>\n }\n </div>\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: visibleChildren(field), controlGroup: group }\"/>\n </div>\n }\n </div>\n </section>\n } @else if (isContainerField(field)) {\n <section class=\"ngs-form-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), controlGroup: controlGroup }\"/>\n </section>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"getControl(field, controlGroup)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\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], controlGroup: formGroup() }\"/>\n } @else if (item.section; as section) {\n <section class=\"ngs-form-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, controlGroup: formGroup() }\"/>\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-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-grid-field{display:flex;grid-column:1/-1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px solid;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-grid-field>header{display:flex;align-items:flex-start;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-renderer-grid-field>header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-repeater-items{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-repeater-item{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px dashed var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-repeater-item-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-renderer-repeater-item-header span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);font-weight:500}:host .ngs-form-renderer-repeater-empty{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);margin:0}\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", "uploadCallback"], exportAs: ["ngsFormBuilderFieldHost"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
653
1057
  }
654
1058
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderRenderer, decorators: [{
655
1059
  type: Component,
@@ -660,7 +1064,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
660
1064
  FormBuilderFieldHost
661
1065
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
662
1066
  'class': 'ngs-form-renderer'
663
- }, template: "<ng-template #renderFields let-fields>\n <div class=\"ngs-form-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isContainerField(field)) {\n <section class=\"ngs-form-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 [uploadCallback]=\"uploadCallback()\"\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-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-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-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-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-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"] }]
1067
+ }, template: "<ng-template #renderFields let-fields let-controlGroup=\"controlGroup\">\n <div class=\"ngs-form-renderer-grid\">\n @for (field of fields; track field.id) {\n @if (isRepeaterField(field)) {\n <section class=\"ngs-form-renderer-grid-field ngs-form-renderer-repeater\">\n <header>\n <div>\n <h4>{{ field.label }}</h4>\n @if (field.hint) {\n <p>{{ field.hint }}</p>\n }\n </div>\n\n @if (!readonly()) {\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"addRepeaterItem(field, controlGroup)\">\n +\n </button>\n }\n </header>\n\n <div class=\"ngs-form-renderer-repeater-items\">\n @if (!repeaterGroups(field, controlGroup).length && repeaterEmptyText(field)) {\n <p class=\"ngs-form-renderer-repeater-empty\">{{ repeaterEmptyText(field) }}</p>\n }\n\n @for (group of repeaterGroups(field, controlGroup); track $index) {\n <div class=\"ngs-form-renderer-repeater-item\" [formGroup]=\"group\">\n <div class=\"ngs-form-renderer-repeater-item-header\">\n <span>{{ field.label }} {{ $index + 1 }}</span>\n @if (!readonly() && canRemoveRepeaterItem(field, controlGroup)) {\n <button ngsButton=\"text\" type=\"button\" (click)=\"removeRepeaterItem(field, $index, controlGroup)\">\n Remove\n </button>\n }\n </div>\n\n <ng-container\n [ngTemplateOutlet]=\"renderFields\"\n [ngTemplateOutletContext]=\"{ $implicit: visibleChildren(field), controlGroup: group }\"/>\n </div>\n }\n </div>\n </section>\n } @else if (isContainerField(field)) {\n <section class=\"ngs-form-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), controlGroup: controlGroup }\"/>\n </section>\n } @else {\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"getControl(field, controlGroup)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\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], controlGroup: formGroup() }\"/>\n } @else if (item.section; as section) {\n <section class=\"ngs-form-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, controlGroup: formGroup() }\"/>\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-renderer-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-section header{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-renderer-section header h3{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-lg);font-weight:600}:host .ngs-form-renderer-section header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-grid-field{display:flex;grid-column:1/-1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px solid;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-grid-field>header{display:flex;align-items:flex-start;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-renderer-grid-field>header h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600}:host .ngs-form-renderer-grid-field>header p{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm)}:host .ngs-form-renderer-repeater-items{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-repeater-item{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4);border:1px dashed var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);padding:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-renderer-repeater-item-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-renderer-repeater-item-header span{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);font-weight:500}:host .ngs-form-renderer-repeater-empty{color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);margin:0}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
664
1068
  }], 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 }] }], uploadCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadCallback", 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"] }] } });
665
1069
  function normalizeFieldDefinition$1(definition) {
666
1070
  return {
@@ -671,6 +1075,14 @@ function normalizeFieldDefinition$1(definition) {
671
1075
  function flattenFields(fields) {
672
1076
  return fields.flatMap(field => [field, ...flattenFields(field.children ?? [])]);
673
1077
  }
1078
+ function isRecord(value) {
1079
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1080
+ }
1081
+ function repeaterRequiredValidator(control) {
1082
+ return control instanceof FormArray && control.length === 0
1083
+ ? { repeaterRequired: true }
1084
+ : null;
1085
+ }
674
1086
  function normalizedLayout(schema) {
675
1087
  const used = new Set();
676
1088
  const layout = [];
@@ -703,249 +1115,6 @@ function normalizedLayout(schema) {
703
1115
  return layout;
704
1116
  }
705
1117
 
706
- class BasicFormBuilderFieldSettings {
707
- destroyRef = inject(DestroyRef);
708
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
709
- update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
710
- optionsControl = new FormControl('', {
711
- nonNullable: true,
712
- validators: optionsTextValidator
713
- });
714
- fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
715
- radioOrientationOptions = ['vertical', 'horizontal'];
716
- spacerHeightOptions = [8, 16, 24, 32, 48, 64];
717
- hasCheckedState = computed(() => ['checkbox', 'toggle'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasCheckedState" }] : /* istanbul ignore next */ []));
718
- hasPlaceholder = computed(() => !['checkbox', 'toggle', 'radio', 'spacer'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasPlaceholder" }] : /* istanbul ignore next */ []));
719
- hasOptions = computed(() => ['select', 'radio', 'checkbox-list'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
720
- hasClearable = computed(() => ['select', 'country-select'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasClearable" }] : /* istanbul ignore next */ []));
721
- hasMultiple = computed(() => ['select', 'upload'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasMultiple" }] : /* istanbul ignore next */ []));
722
- isSelect = computed(() => this.field().type === 'select', ...(ngDevMode ? [{ debugName: "isSelect" }] : /* istanbul ignore next */ []));
723
- isUpload = computed(() => this.field().type === 'upload', ...(ngDevMode ? [{ debugName: "isUpload" }] : /* istanbul ignore next */ []));
724
- isRadio = computed(() => this.field().type === 'radio', ...(ngDevMode ? [{ debugName: "isRadio" }] : /* istanbul ignore next */ []));
725
- isSpacer = computed(() => this.field().type === 'spacer', ...(ngDevMode ? [{ debugName: "isSpacer" }] : /* istanbul ignore next */ []));
726
- hasBehaviorToggles = computed(() => !this.isSpacer(), ...(ngDevMode ? [{ debugName: "hasBehaviorToggles" }] : /* istanbul ignore next */ []));
727
- radioOrientation = computed(() => this.field().settings?.['orientation'] === 'horizontal' ? 'horizontal' : 'vertical', ...(ngDevMode ? [{ debugName: "radioOrientation" }] : /* istanbul ignore next */ []));
728
- optionsText = computed(() => (this.field().options ?? [])
729
- .map(option => `${option.label}:${option.value}${this.isOptionSelected(option) ? ':selected' : ''}`)
730
- .join('\n'), ...(ngDevMode ? [{ debugName: "optionsText" }] : /* istanbul ignore next */ []));
731
- constructor() {
732
- effect(() => {
733
- const nextValue = this.optionsText();
734
- if (this.optionsControl.value !== nextValue) {
735
- this.optionsControl.setValue(nextValue, { emitEvent: false });
736
- }
737
- });
738
- this.optionsControl.valueChanges
739
- .pipe(takeUntilDestroyed(this.destroyRef))
740
- .subscribe(value => {
741
- if (this.optionsControl.invalid) {
742
- return;
743
- }
744
- this.patchOptions(value);
745
- });
746
- }
747
- patch(changes) {
748
- this.update()(changes);
749
- }
750
- patchSettings(changes) {
751
- this.patch({
752
- settings: {
753
- ...this.field().settings,
754
- ...changes
755
- }
756
- });
757
- }
758
- patchMultiple(multiple) {
759
- if (this.field().type === 'select') {
760
- this.patchSelectMultiple(multiple);
761
- return;
762
- }
763
- this.patch({
764
- multiple,
765
- defaultValue: multiple ? [] : null
766
- });
767
- }
768
- patchSelectMultiple(multiple) {
769
- const field = this.field();
770
- const options = multiple ? field.options ?? [] : this.normalizeSelectedOptions(field.options ?? [], false);
771
- const selectedValues = this.selectedOptionValues(options);
772
- let defaultValue = null;
773
- if (multiple) {
774
- defaultValue = Array.isArray(field.defaultValue)
775
- ? field.defaultValue
776
- : field.defaultValue == null
777
- ? selectedValues
778
- : [field.defaultValue];
779
- }
780
- else {
781
- defaultValue = Array.isArray(field.defaultValue)
782
- ? field.defaultValue[0] ?? selectedValues[0] ?? null
783
- : field.defaultValue ?? selectedValues[0] ?? null;
784
- }
785
- this.patch({ multiple, options, defaultValue });
786
- }
787
- patchRadioOrientation(orientation) {
788
- this.patch({
789
- settings: {
790
- ...this.field().settings,
791
- orientation
792
- }
793
- });
794
- }
795
- patchOptions(value) {
796
- const options = this.normalizeSelectedOptions(value
797
- .split('\n')
798
- .map(line => line.trim())
799
- .filter(Boolean)
800
- .map((line, index) => {
801
- const parsed = parseOptionLine(line);
802
- return {
803
- label: parsed?.label || `Option ${index + 1}`,
804
- value: parsed?.value || `option_${index + 1}`,
805
- selected: parsed?.selected || undefined
806
- };
807
- }));
808
- this.patch({
809
- options,
810
- defaultValue: this.resolveSelectedDefaultValue(options)
811
- });
812
- }
813
- normalizeSelectedOptions(options, multiple = this.field().multiple) {
814
- if (this.field().type === 'checkbox-list' || multiple) {
815
- return options;
816
- }
817
- let selectedSeen = false;
818
- return options.map(option => {
819
- if (!option.selected) {
820
- return option;
821
- }
822
- if (selectedSeen) {
823
- return {
824
- ...option,
825
- selected: undefined
826
- };
827
- }
828
- selectedSeen = true;
829
- return option;
830
- });
831
- }
832
- resolveSelectedDefaultValue(options) {
833
- const selectedValues = this.selectedOptionValues(options);
834
- if (this.field().type === 'checkbox-list' || this.field().multiple) {
835
- return selectedValues;
836
- }
837
- return selectedValues[0] ?? null;
838
- }
839
- selectedOptionValues(options) {
840
- return options
841
- .filter(option => option.selected)
842
- .map(option => option.value);
843
- }
844
- isOptionSelected(option) {
845
- if (option.selected) {
846
- return true;
847
- }
848
- const defaultValue = this.field().defaultValue;
849
- return Array.isArray(defaultValue)
850
- ? defaultValue.includes(option.value)
851
- : defaultValue === option.value;
852
- }
853
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
854
- 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 @if (!isSpacer()) {\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\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n @if (!isSpacer()) {\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\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 @if (hasBehaviorToggles()) {\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (hasMultiple()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n }\n\n @if (isUpload()) {\n <ngs-form-field>\n <ngs-label>Accepted file types</ngs-label>\n <input ngsInput\n [ngModel]=\"field().settings?.['accept'] || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ accept: $event })\">\n <ngs-hint>Use MIME types separated by commas, for example image/*,application/pdf.</ngs-hint>\n </ngs-form-field>\n }\n\n @if (isSpacer()) {\n <ngs-form-field>\n <ngs-label>Height</ngs-label>\n <ngs-select [ngModel]=\"field().settings?.['height'] ?? 24\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ height: $event })\">\n @for (height of spacerHeightOptions; track height) {\n <ngs-option [value]=\"height\">{{ height }}px</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Error, selector: "ngs-error", exportAs: ["ngsError"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: SlideToggleGroup, selector: "ngs-slide-toggle-group", exportAs: ["ngsSlideToggleGroup"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
855
- }
856
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, decorators: [{
857
- type: Component,
858
- args: [{ selector: 'ngs-basic-form-builder-field-settings', exportAs: 'ngsBasicFormBuilderFieldSettings', imports: [
859
- FormsModule,
860
- ReactiveFormsModule,
861
- Error,
862
- FormField,
863
- Hint,
864
- Label,
865
- Input,
866
- Select,
867
- Option,
868
- SlideToggle,
869
- SlideToggleGroup
870
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
871
- 'class': 'ngs-basic-form-builder-field-settings'
872
- }, 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 @if (!isSpacer()) {\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\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n @if (!isSpacer()) {\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\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 @if (hasBehaviorToggles()) {\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (hasMultiple()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n }\n\n @if (isUpload()) {\n <ngs-form-field>\n <ngs-label>Accepted file types</ngs-label>\n <input ngsInput\n [ngModel]=\"field().settings?.['accept'] || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ accept: $event })\">\n <ngs-hint>Use MIME types separated by commas, for example image/*,application/pdf.</ngs-hint>\n </ngs-form-field>\n }\n\n @if (isSpacer()) {\n <ngs-form-field>\n <ngs-label>Height</ngs-label>\n <ngs-select [ngModel]=\"field().settings?.['height'] ?? 24\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ height: $event })\">\n @for (height of spacerHeightOptions; track height) {\n <ngs-option [value]=\"height\">{{ height }}px</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
873
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
874
- function optionsTextValidator(control) {
875
- const hasInvalidLine = (control.value ?? '')
876
- .split('\n')
877
- .map(line => line.trim())
878
- .filter(Boolean)
879
- .some(line => !parseOptionLine(line));
880
- return hasInvalidLine ? { formBuilderOptionsFormat: true } : null;
881
- }
882
- function parseOptionLine(line) {
883
- const parts = line.split(':').map(part => part.trim());
884
- if (parts.length < 2 || parts.length > 3) {
885
- return null;
886
- }
887
- const [label, value, selected] = parts;
888
- if (!label || !value) {
889
- return null;
890
- }
891
- if (selected !== undefined && selected !== 'selected') {
892
- return null;
893
- }
894
- return {
895
- label,
896
- value,
897
- selected: selected === 'selected'
898
- };
899
- }
900
-
901
- class BasicFormBuilderLayoutSettings {
902
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
903
- update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
904
- fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
905
- patch(changes) {
906
- this.update()(changes);
907
- }
908
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
909
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderLayoutSettings, isStandalone: true, selector: "ngs-basic-form-builder-layout-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-layout-settings" }, exportAs: ["ngsBasicFormBuilderLayoutSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
910
- }
911
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, decorators: [{
912
- type: Component,
913
- args: [{ selector: 'ngs-basic-form-builder-layout-settings', exportAs: 'ngsBasicFormBuilderLayoutSettings', imports: [
914
- FormsModule,
915
- FormField,
916
- Hint,
917
- Label,
918
- Input,
919
- Option,
920
- Select
921
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
922
- 'class': 'ngs-basic-form-builder-layout-settings'
923
- }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
924
- }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
925
-
926
- class BasicFormBuilderSectionSettings {
927
- section = input.required(...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
928
- update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
929
- patch(changes) {
930
- this.update()(changes);
931
- }
932
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
933
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: BasicFormBuilderSectionSettings, isStandalone: true, selector: "ngs-basic-form-builder-section-settings", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-section-settings" }, exportAs: ["ngsBasicFormBuilderSectionSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
934
- }
935
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, decorators: [{
936
- type: Component,
937
- args: [{ selector: 'ngs-basic-form-builder-section-settings', exportAs: 'ngsBasicFormBuilderSectionSettings', imports: [
938
- FormsModule,
939
- FormField,
940
- Hint,
941
- Label,
942
- Input,
943
- SlideToggle
944
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
945
- 'class': 'ngs-basic-form-builder-section-settings'
946
- }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
947
- }], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
948
-
949
1118
  class FormBuilderSettingsHost {
950
1119
  field = input(null, ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
951
1120
  section = input(null, ...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
@@ -962,10 +1131,29 @@ class FormBuilderSettingsHost {
962
1131
  }
963
1132
  return field ? this.definitions().find(definition => definition.type === field.type) : undefined;
964
1133
  }, ...(ngDevMode ? [{ debugName: "itemDefinition" }] : /* istanbul ignore next */ []));
965
- fieldDefinition = this.itemDefinition;
966
- fieldKind = computed(() => this.itemDefinition()?.kind ?? this.field()?.kind ?? 'field', ...(ngDevMode ? [{ debugName: "fieldKind" }] : /* istanbul ignore next */ []));
967
- isLayoutField = computed(() => this.fieldKind() === 'layout' || !!this.field()?.children?.length, ...(ngDevMode ? [{ debugName: "isLayoutField" }] : /* istanbul ignore next */ []));
968
- customLoaded = signal(false, ...(ngDevMode ? [{ debugName: "customLoaded" }] : /* istanbul ignore next */ []));
1134
+ settingsSchema = computed(() => {
1135
+ const definition = this.itemDefinition();
1136
+ const config = this.settingsConfig(definition);
1137
+ const inheritance = config?.extends ?? this.inferSettingsInheritance(definition);
1138
+ const schemas = [
1139
+ this.baseSettingsSchema(inheritance),
1140
+ this.resolveOwnSettingsSchema(config, definition)
1141
+ ].filter((schema) => !!schema);
1142
+ return mergeSettingsSchemas(schemas);
1143
+ }, ...(ngDevMode ? [{ debugName: "settingsSchema" }] : /* istanbul ignore next */ []));
1144
+ settingsValue = computed(() => {
1145
+ const item = this.section() ?? this.field();
1146
+ const schema = this.settingsSchema();
1147
+ const value = {};
1148
+ if (!item || !schema) {
1149
+ return value;
1150
+ }
1151
+ for (const settingField of flattenSettingsFields(schema)) {
1152
+ value[settingField.name] = this.readSettingValue(item, settingField);
1153
+ }
1154
+ return value;
1155
+ }, ...(ngDevMode ? [{ debugName: "settingsValue" }] : /* istanbul ignore next */ []));
1156
+ legacyLoaded = signal(false, ...(ngDevMode ? [{ debugName: "legacyLoaded" }] : /* istanbul ignore next */ []));
969
1157
  anchor = viewChild.required('anchor', { read: ViewContainerRef });
970
1158
  constructor() {
971
1159
  effect(async () => {
@@ -977,18 +1165,18 @@ class FormBuilderSettingsHost {
977
1165
  const type = section ? 'section' : field?.type;
978
1166
  const itemDefinition = type ? definitions.find(definition => definition.type === type) : undefined;
979
1167
  const kind = section ? 'layout' : (itemDefinition?.kind ?? field?.kind ?? 'field');
980
- const customSettings = itemDefinition?.settings ?? settingDefinitions.find(definition => {
1168
+ const legacySettings = this.legacySettingsImporter(itemDefinition) ?? settingDefinitions.find(definition => {
981
1169
  if (type && (definition.itemType === type || definition.fieldType === type || definition.type === type)) {
982
1170
  return true;
983
1171
  }
984
1172
  return !!definition.kind && definition.kind === kind;
985
1173
  })?.component;
986
1174
  viewContainer.clear();
987
- this.customLoaded.set(false);
988
- if (!customSettings) {
1175
+ this.legacyLoaded.set(false);
1176
+ if (!legacySettings) {
989
1177
  return;
990
1178
  }
991
- const componentType = await customSettings();
1179
+ const componentType = await legacySettings();
992
1180
  const componentRef = viewContainer.createComponent(componentType);
993
1181
  componentRef.setInput('item', section ?? field);
994
1182
  componentRef.setInput('field', field);
@@ -998,23 +1186,286 @@ class FormBuilderSettingsHost {
998
1186
  componentRef.setInput('update', section ? this.updateSection() : this.update());
999
1187
  componentRef.setInput('updateField', this.update());
1000
1188
  componentRef.setInput('updateSection', this.updateSection());
1001
- this.customLoaded.set(true);
1189
+ this.legacyLoaded.set(true);
1190
+ });
1191
+ }
1192
+ applySettingsValue(value) {
1193
+ const schema = this.settingsSchema();
1194
+ const section = this.section();
1195
+ const field = this.field();
1196
+ if (!schema) {
1197
+ return;
1198
+ }
1199
+ if (section) {
1200
+ const patch = this.createSettingsPatch(section, schema, value);
1201
+ if (Object.keys(patch).length) {
1202
+ this.updateSection()?.(patch);
1203
+ }
1204
+ return;
1205
+ }
1206
+ if (field) {
1207
+ const patch = this.createSettingsPatch(field, schema, value);
1208
+ if (Object.keys(patch).length) {
1209
+ this.update()?.(patch);
1210
+ }
1211
+ }
1212
+ }
1213
+ createSettingsPatch(item, schema, value) {
1214
+ const patch = {};
1215
+ for (const settingField of flattenSettingsFields(schema)) {
1216
+ if (settingField.settings?.['valueAdapter'] === 'optionsText' && isFormBuilderField(item)) {
1217
+ if (value[settingField.name] === this.readSettingValue(item, settingField)) {
1218
+ continue;
1219
+ }
1220
+ const nextOptions = parseOptionsText(String(value[settingField.name] ?? ''), item);
1221
+ patch['options'] = nextOptions;
1222
+ patch['defaultValue'] = resolveSelectedDefaultValue(nextOptions, item);
1223
+ continue;
1224
+ }
1225
+ if (settingField.settings?.['valueAdapter'] === 'selectMultiple' && isFormBuilderField(item)) {
1226
+ const multiple = value[settingField.name] === true;
1227
+ if (multiple === (item.multiple === true)) {
1228
+ continue;
1229
+ }
1230
+ const options = multiple ? item.options ?? [] : normalizeSelectedOptions(item.options ?? [], { ...item, multiple });
1231
+ patch['multiple'] = multiple;
1232
+ patch['options'] = options;
1233
+ patch['defaultValue'] = resolveMultipleDefaultValue(item, options, multiple);
1234
+ continue;
1235
+ }
1236
+ if (settingField.settings?.['valueAdapter'] === 'multipleDefaultValue' && isFormBuilderField(item)) {
1237
+ const multiple = value[settingField.name] === true;
1238
+ if (multiple === (item.multiple === true)) {
1239
+ continue;
1240
+ }
1241
+ patch['multiple'] = multiple;
1242
+ patch['defaultValue'] = multiple ? [] : null;
1243
+ continue;
1244
+ }
1245
+ const nextValue = value[settingField.name];
1246
+ if (nextValue === this.readSettingValue(item, settingField)) {
1247
+ continue;
1248
+ }
1249
+ seedPathPatch(patch, item, settingField.name);
1250
+ setPathValue(patch, settingField.name, nextValue);
1251
+ }
1252
+ return patch;
1253
+ }
1254
+ readSettingValue(item, settingField) {
1255
+ if (settingField.settings?.['valueAdapter'] === 'optionsText' && isFormBuilderField(item)) {
1256
+ return optionsToText(item.options ?? [], item.defaultValue);
1257
+ }
1258
+ const value = getPathValue(item, settingField.name);
1259
+ return value === undefined ? settingField.defaultValue ?? null : value;
1260
+ }
1261
+ settingsConfig(definition) {
1262
+ return definition?.settings && typeof definition.settings !== 'function'
1263
+ ? definition.settings
1264
+ : undefined;
1265
+ }
1266
+ legacySettingsImporter(definition) {
1267
+ return typeof definition?.settings === 'function'
1268
+ ? definition.settings
1269
+ : undefined;
1270
+ }
1271
+ resolveOwnSettingsSchema(config, definition) {
1272
+ const schema = config?.schema;
1273
+ if (!schema) {
1274
+ return null;
1275
+ }
1276
+ if (typeof schema !== 'function') {
1277
+ return schema;
1278
+ }
1279
+ const section = this.section();
1280
+ if (section) {
1281
+ return schema({
1282
+ item: section,
1283
+ section,
1284
+ schema: this.schema(),
1285
+ definition,
1286
+ update: this.updateSection(),
1287
+ updateSection: this.updateSection()
1288
+ });
1289
+ }
1290
+ const field = this.field();
1291
+ return schema({
1292
+ item: field,
1293
+ field,
1294
+ schema: this.schema(),
1295
+ definition,
1296
+ update: this.update(),
1297
+ updateField: this.update()
1002
1298
  });
1003
1299
  }
1004
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
1005
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderSettingsHost, isStandalone: true, selector: "ngs-form-builder-settings-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: false, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, settingsDefinitions: { classPropertyName: "settingsDefinitions", publicName: "settingsDefinitions", isSignal: true, isRequired: false, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: false, transformFunction: null }, updateSection: { classPropertyName: "updateSection", publicName: "updateSection", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-empty": "!customLoaded()" }, classAttribute: "ngs-form-builder-settings-host" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderSettingsHost"], ngImport: i0, template: "@if (section(); as sectionItem) {\n <ngs-basic-form-builder-section-settings\n [section]=\"sectionItem\"\n [update]=\"updateSection()!\"/>\n} @else if (field(); as fieldItem) {\n @if (isLayoutField()) {\n <ngs-basic-form-builder-layout-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n } @else {\n <ngs-basic-form-builder-field-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n }\n}\n\n<ng-container #anchor/>\n", dependencies: [{ kind: "component", type: BasicFormBuilderFieldSettings, selector: "ngs-basic-form-builder-field-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderFieldSettings"] }, { kind: "component", type: BasicFormBuilderLayoutSettings, selector: "ngs-basic-form-builder-layout-settings", inputs: ["field", "update"], exportAs: ["ngsBasicFormBuilderLayoutSettings"] }, { kind: "component", type: BasicFormBuilderSectionSettings, selector: "ngs-basic-form-builder-section-settings", inputs: ["section", "update"], exportAs: ["ngsBasicFormBuilderSectionSettings"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1300
+ inferSettingsInheritance(definition) {
1301
+ const field = this.field();
1302
+ if (this.section()) {
1303
+ return 'none';
1304
+ }
1305
+ const kind = definition?.kind ?? field?.kind ?? 'field';
1306
+ if (kind === 'static') {
1307
+ return 'static';
1308
+ }
1309
+ if (kind === 'layout' || definition?.acceptsChildren || field?.children?.length) {
1310
+ return 'layout';
1311
+ }
1312
+ return 'field';
1313
+ }
1314
+ baseSettingsSchema(inheritance) {
1315
+ if (this.section()) {
1316
+ return inheritance === 'none' ? null : FORM_BUILDER_SECTION_BASE_SETTINGS_SCHEMA;
1317
+ }
1318
+ switch (inheritance) {
1319
+ case 'field':
1320
+ return FORM_BUILDER_FIELD_BASE_SETTINGS_SCHEMA;
1321
+ case 'input-field':
1322
+ return FORM_BUILDER_INPUT_FIELD_BASE_SETTINGS_SCHEMA;
1323
+ case 'layout':
1324
+ return FORM_BUILDER_LAYOUT_BASE_SETTINGS_SCHEMA;
1325
+ case 'layout-container':
1326
+ return FORM_BUILDER_LAYOUT_CONTAINER_BASE_SETTINGS_SCHEMA;
1327
+ case 'static':
1328
+ return FORM_BUILDER_STATIC_BASE_SETTINGS_SCHEMA;
1329
+ default:
1330
+ return null;
1331
+ }
1332
+ }
1333
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
1334
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: FormBuilderSettingsHost, isStandalone: true, selector: "ngs-form-builder-settings-host", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: false, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, definitions: { classPropertyName: "definitions", publicName: "definitions", isSignal: true, isRequired: false, transformFunction: null }, settingsDefinitions: { classPropertyName: "settingsDefinitions", publicName: "settingsDefinitions", isSignal: true, isRequired: false, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: false, transformFunction: null }, updateSection: { classPropertyName: "updateSection", publicName: "updateSection", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-empty": "!settingsSchema() && !legacyLoaded()" }, classAttribute: "ngs-form-builder-settings-host" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, read: ViewContainerRef, isSignal: true }], exportAs: ["ngsFormBuilderSettingsHost"], ngImport: i0, template: "@if (settingsSchema(); as schema) {\n <ngs-form-renderer\n [schema]=\"schema\"\n [value]=\"settingsValue()\"\n [showSubmit]=\"false\"\n (valueChange)=\"applySettingsValue($event)\"/>\n}\n\n<ng-container #anchor/>\n", dependencies: [{ kind: "component", type: FormBuilderRenderer, selector: "ngs-form-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "uploadCallback", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormRenderer"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1335
+ }
1336
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
1337
+ type: Component,
1338
+ args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', imports: [
1339
+ FormBuilderRenderer
1340
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1341
+ 'class': 'ngs-form-builder-settings-host',
1342
+ '[class.is-empty]': '!settingsSchema() && !legacyLoaded()'
1343
+ }, template: "@if (settingsSchema(); as schema) {\n <ngs-form-renderer\n [schema]=\"schema\"\n [value]=\"settingsValue()\"\n [showSubmit]=\"false\"\n (valueChange)=\"applySettingsValue($event)\"/>\n}\n\n<ng-container #anchor/>\n" }]
1344
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: false }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], settingsDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingsDefinitions", required: false }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: false }] }], updateSection: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateSection", required: false }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
1345
+ function mergeSettingsSchemas(schemas) {
1346
+ if (!schemas.length) {
1347
+ return null;
1348
+ }
1349
+ return {
1350
+ sections: schemas.flatMap(schema => schema.sections.map(section => ({
1351
+ ...section,
1352
+ fields: section.fields.map(field => ({ ...field }))
1353
+ })))
1354
+ };
1355
+ }
1356
+ function flattenSettingsFields(schema) {
1357
+ return [
1358
+ ...(schema.fields ?? []),
1359
+ ...schema.sections.flatMap(section => section.fields)
1360
+ ].flatMap(field => [field, ...flattenSettingsFields({ sections: [], fields: field.children ?? [] })]);
1361
+ }
1362
+ function getPathValue(source, path) {
1363
+ return path.split('.').reduce((value, key) => value?.[key], source);
1364
+ }
1365
+ function setPathValue(target, path, value) {
1366
+ const keys = path.split('.');
1367
+ let cursor = target;
1368
+ keys.slice(0, -1).forEach(key => {
1369
+ cursor[key] = cursor[key] ?? {};
1370
+ cursor = cursor[key];
1371
+ });
1372
+ cursor[keys[keys.length - 1]] = value;
1373
+ }
1374
+ function seedPathPatch(target, source, path) {
1375
+ const [rootKey] = path.split('.');
1376
+ const sourceValue = source[rootKey];
1377
+ if (!path.includes('.') || target[rootKey] !== undefined || !isPlainObject(sourceValue)) {
1378
+ return;
1379
+ }
1380
+ target[rootKey] = { ...sourceValue };
1381
+ }
1382
+ function isPlainObject(value) {
1383
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1384
+ }
1385
+ function isFormBuilderField(item) {
1386
+ return 'type' in item;
1387
+ }
1388
+ function optionsToText(options, defaultValue) {
1389
+ return options
1390
+ .map(option => `${option.label}:${option.value}${isOptionSelected(option, defaultValue) ? ':selected' : ''}`)
1391
+ .join('\n');
1392
+ }
1393
+ function parseOptionsText(value, field) {
1394
+ return normalizeSelectedOptions(value
1395
+ .split('\n')
1396
+ .map(line => line.trim())
1397
+ .filter(Boolean)
1398
+ .map((line, index) => {
1399
+ const parsed = parseOptionLine$1(line);
1400
+ return {
1401
+ label: parsed?.label || `Option ${index + 1}`,
1402
+ value: parsed?.value || `option_${index + 1}`,
1403
+ selected: parsed?.selected || undefined
1404
+ };
1405
+ }), field);
1406
+ }
1407
+ function parseOptionLine$1(line) {
1408
+ const parts = line.split(':').map(part => part.trim());
1409
+ if (parts.length < 2 || !parts[0] || !parts[1]) {
1410
+ return null;
1411
+ }
1412
+ return {
1413
+ label: parts[0],
1414
+ value: parts[1],
1415
+ selected: parts[2] === 'selected'
1416
+ };
1417
+ }
1418
+ function normalizeSelectedOptions(options, field) {
1419
+ if (field.type === 'checkbox-list' || field.multiple) {
1420
+ return options;
1421
+ }
1422
+ let selectedSeen = false;
1423
+ return options.map(option => {
1424
+ if (!option.selected) {
1425
+ return option;
1426
+ }
1427
+ if (selectedSeen) {
1428
+ return {
1429
+ ...option,
1430
+ selected: undefined
1431
+ };
1432
+ }
1433
+ selectedSeen = true;
1434
+ return option;
1435
+ });
1436
+ }
1437
+ function resolveSelectedDefaultValue(options, field) {
1438
+ const selectedValues = options
1439
+ .filter(option => option.selected)
1440
+ .map(option => option.value);
1441
+ if (field.type === 'checkbox-list' || field.multiple) {
1442
+ return selectedValues;
1443
+ }
1444
+ return selectedValues[0] ?? null;
1445
+ }
1446
+ function resolveMultipleDefaultValue(field, options, multiple) {
1447
+ const selectedValues = options
1448
+ .filter(option => option.selected)
1449
+ .map(option => option.value);
1450
+ if (multiple) {
1451
+ return Array.isArray(field.defaultValue)
1452
+ ? field.defaultValue
1453
+ : field.defaultValue == null
1454
+ ? selectedValues
1455
+ : [field.defaultValue];
1456
+ }
1457
+ return Array.isArray(field.defaultValue)
1458
+ ? field.defaultValue[0] ?? selectedValues[0] ?? null
1459
+ : field.defaultValue ?? selectedValues[0] ?? null;
1460
+ }
1461
+ function isOptionSelected(option, defaultValue) {
1462
+ if (option.selected) {
1463
+ return true;
1464
+ }
1465
+ return Array.isArray(defaultValue)
1466
+ ? defaultValue.includes(option.value)
1467
+ : defaultValue === option.value;
1006
1468
  }
1007
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilderSettingsHost, decorators: [{
1008
- type: Component,
1009
- args: [{ selector: 'ngs-form-builder-settings-host', exportAs: 'ngsFormBuilderSettingsHost', imports: [
1010
- BasicFormBuilderFieldSettings,
1011
- BasicFormBuilderLayoutSettings,
1012
- BasicFormBuilderSectionSettings
1013
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1014
- 'class': 'ngs-form-builder-settings-host',
1015
- '[class.is-empty]': '!customLoaded()'
1016
- }, template: "@if (section(); as sectionItem) {\n <ngs-basic-form-builder-section-settings\n [section]=\"sectionItem\"\n [update]=\"updateSection()!\"/>\n} @else if (field(); as fieldItem) {\n @if (isLayoutField()) {\n <ngs-basic-form-builder-layout-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n } @else {\n <ngs-basic-form-builder-field-settings\n [field]=\"fieldItem\"\n [update]=\"update()!\"/>\n }\n}\n\n<ng-container #anchor/>\n" }]
1017
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: false }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], definitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "definitions", required: false }] }], settingsDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "settingsDefinitions", required: false }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: false }] }], updateSection: [{ type: i0.Input, args: [{ isSignal: true, alias: "updateSection", required: false }] }], anchor: [{ type: i0.ViewChild, args: ['anchor', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
1018
1469
 
1019
1470
  const ROOT_DROP_LIST_ID = 'ngs-form-builder-root-fields';
1020
1471
  const PALETTE_DRAG_TYPE = 'application/x-ngstarter-form-builder-field';
@@ -1121,7 +1572,7 @@ class FormBuilder {
1121
1572
  return this.fieldTreeRootNodes;
1122
1573
  }, ...(ngDevMode ? [{ debugName: "fieldTree" }] : /* istanbul ignore next */ []));
1123
1574
  fieldTreeChildrenAccessor = (node) => node.children ?? [];
1124
- hasFieldTreeChildren = (_, node) => !!node.children?.length;
1575
+ hasFieldTreeChildren = (_, node) => !!node.children;
1125
1576
  trackFieldTreeNode = (_, node) => node.id;
1126
1577
  fieldTreeDraggablePredicate = (_node) => true;
1127
1578
  fieldTreeDropPredicate = (source, target, position) => this.canDropFieldTreeNode(source, target, position);
@@ -1293,15 +1744,6 @@ class FormBuilder {
1293
1744
  const schema = cloneSchema(this.schema());
1294
1745
  schema.sections = schema.sections.filter(item => item.id !== section.id);
1295
1746
  schema.layout = this.normalizedLayout(schema).filter(item => !(item.kind === 'section' && item.id === section.id));
1296
- if (schema.sections.length === 0) {
1297
- const nextSection = {
1298
- id: uniqueId('section'),
1299
- title: 'Section',
1300
- fields: []
1301
- };
1302
- schema.sections.push(nextSection);
1303
- schema.layout = this.normalizedLayout(schema);
1304
- }
1305
1747
  if (this.selectedFieldId() === section.id ||
1306
1748
  section.fields.some(field => field.id === this.selectedFieldId() || containsField(field, this.selectedFieldId()))) {
1307
1749
  this.selectedFieldId.set(null);
@@ -1348,7 +1790,7 @@ class FormBuilder {
1348
1790
  }
1349
1791
  toggleFieldTreeNode(tree, node, event) {
1350
1792
  event.stopPropagation();
1351
- if (!node.children?.length) {
1793
+ if (!node.children) {
1352
1794
  return;
1353
1795
  }
1354
1796
  if (tree.isExpanded(node)) {
@@ -1411,6 +1853,7 @@ class FormBuilder {
1411
1853
  if (target.fields === (schema.fields ?? [])) {
1412
1854
  this.insertRootFieldIntoLayout(schema, movingField, event.target.id, event.position);
1413
1855
  }
1856
+ this.expandFieldTreeOwner(target.owner);
1414
1857
  this.schema.set(schema);
1415
1858
  this.restoreFieldTreeExpansion();
1416
1859
  this.selectField(movingField, target.section);
@@ -1610,6 +2053,7 @@ class FormBuilder {
1610
2053
  return;
1611
2054
  }
1612
2055
  targetContainer.fields.splice(clampIndex(index, targetContainer.fields.length), 0, field);
2056
+ this.expandFieldTreeOwner(targetContainer.owner);
1613
2057
  this.schema.set(schema);
1614
2058
  this.selectField(field, targetContainer.section);
1615
2059
  this.fieldAdded.emit({ field, section: targetContainer.section });
@@ -1801,7 +2245,8 @@ class FormBuilder {
1801
2245
  return {
1802
2246
  fields: targetLocation.field.children,
1803
2247
  index: targetLocation.field.children.length,
1804
- section: targetLocation.section
2248
+ section: targetLocation.section,
2249
+ owner: targetLocation.field
1805
2250
  };
1806
2251
  }
1807
2252
  const insertIndex = position === 'before' ? targetLocation.index : targetLocation.index + 1;
@@ -1879,13 +2324,19 @@ class FormBuilder {
1879
2324
  return;
1880
2325
  }
1881
2326
  for (const node of this.flattenFieldTree(this.fieldTree())) {
1882
- if (node.children?.length && expandedIds.has(node.id)) {
2327
+ if (node.children && expandedIds.has(node.id)) {
1883
2328
  tree.expand(node);
1884
2329
  }
1885
2330
  }
1886
2331
  });
1887
2332
  });
1888
2333
  }
2334
+ expandFieldTreeOwner(owner) {
2335
+ if (!owner) {
2336
+ return;
2337
+ }
2338
+ this.expandedFieldTreeNodeIds.update(ids => new Set(ids).add(owner.id));
2339
+ }
1889
2340
  flattenFieldTree(nodes) {
1890
2341
  return nodes.flatMap(node => [node, ...this.flattenFieldTree(node.children ?? [])]);
1891
2342
  }
@@ -1897,7 +2348,7 @@ class FormBuilder {
1897
2348
  }
1898
2349
  const expandableIds = path
1899
2350
  .slice(0, -1)
1900
- .filter(node => !!node.children?.length)
2351
+ .filter(node => !!node.children)
1901
2352
  .map(node => node.id);
1902
2353
  if (expandableIds.length) {
1903
2354
  this.expandedFieldTreeNodeIds.update(ids => {
@@ -1913,7 +2364,7 @@ class FormBuilder {
1913
2364
  const tree = this.actualFieldsTree();
1914
2365
  if (tree) {
1915
2366
  for (const node of path.slice(0, -1)) {
1916
- if (node.children?.length) {
2367
+ if (node.children) {
1917
2368
  tree.expand(node);
1918
2369
  }
1919
2370
  }
@@ -2073,9 +2524,9 @@ class FormBuilder {
2073
2524
  node.kind = 'field';
2074
2525
  node.field = field;
2075
2526
  node.section = section;
2076
- if (children?.length) {
2527
+ if (children?.length || this.isContainerField(field)) {
2077
2528
  node.children ??= [];
2078
- replaceArrayContents(node.children, children);
2529
+ replaceArrayContents(node.children, children ?? []);
2079
2530
  }
2080
2531
  else {
2081
2532
  node.children = undefined;
@@ -2166,7 +2617,7 @@ class FormBuilder {
2166
2617
  }
2167
2618
  }
2168
2619
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
2169
- 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 }, uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schema: "schemaChange", fieldSelected: "fieldSelected", fieldAdded: "fieldAdded", fieldRemoved: "fieldRemoved" }, host: { classAttribute: "ngs-form-builder" }, viewQueries: [{ propertyName: "actualFieldsTree", first: true, predicate: ["actualFieldsTree"], descendants: true, isSignal: true }], exportAs: ["ngsFormBuilder"], ngImport: i0, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"ps-1 pe-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField() || selectedSection()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label || selectedSection()?.title }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField() || selectedSection()) {\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-renderer\n [schema]=\"schema()\"\n [uploadCallback]=\"uploadCallback()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-style:dashed;border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: DialogActions, selector: "ngs-dialog-actions, [ngs-dialog-actions], [ngsDialogActions]", inputs: ["align"] }, { kind: "directive", type: DialogClose, selector: "[ngs-dialog-close], [ngsDialogClose]", inputs: ["ngs-dialog-close", "ngsDialogClose", "ariaLabel", "type"], exportAs: ["ngsDialogClose"] }, { kind: "component", type: DialogContent, selector: "ngs-dialog-content,[ngs-dialog-content],[ngsDialogContent]" }, { kind: "component", type: DialogTitle, selector: "ngs-dialog-title, [ngs-dialog-title], [ngsDialogTitle]", inputs: ["id"], exportAs: ["ngsDialogTitle"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Tree, selector: "ngs-tree", inputs: ["checkable", "selectable", "draggable", "draggablePredicate", "dropPredicate", "reorderOnDrop", "dragPreview", "nodePaddingIndent", "childrenKey", "filterValue", "filterPredicate", "filterMode"], outputs: ["checkedChange", "selectedChange", "nodeDrop"], exportAs: ["ngsTree"] }, { kind: "directive", type: TreeDragPlaceholder, selector: "ng-template[ngsTreeDragPlaceholder]" }, { kind: "component", type: TreeNode, selector: "ngs-tree-node", inputs: ["tabIndex", "value", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["ngsTreeNode"] }, { kind: "directive", type: TreeNodeDef, selector: "[ngsTreeNodeDef]", inputs: ["ngsTreeNodeDefWhen", "ngsTreeNode"] }, { kind: "directive", type: TreeNodePadding, selector: "[ngsTreeNodePadding]", inputs: ["ngsTreeNodePadding", "ngsTreeNodePaddingIndent"] }, { kind: "component", type: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas", "uploadCallback"], exportAs: ["ngsFormBuilderFieldHost"] }, { kind: "component", type: FormBuilderRenderer, selector: "ngs-form-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "uploadCallback", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormRenderer"] }, { kind: "component", type: FormBuilderSettingsHost, selector: "ngs-form-builder-settings-host", inputs: ["field", "section", "schema", "definitions", "settingsDefinitions", "update", "updateSection"], exportAs: ["ngsFormBuilderSettingsHost"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2620
+ 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 }, uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schema: "schemaChange", fieldSelected: "fieldSelected", fieldAdded: "fieldAdded", fieldRemoved: "fieldRemoved" }, host: { classAttribute: "ngs-form-builder" }, viewQueries: [{ propertyName: "actualFieldsTree", first: true, predicate: ["actualFieldsTree"], descendants: true, isSignal: true }], exportAs: ["ngsFormBuilder"], ngImport: i0, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-border px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-border\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"px-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-border\">\n @if (selectedField() || selectedSection()) {\n <ngs-panel-header class=\"ps-5 pe-2 border-b border-border\">\n <ngs-toolbar>\n <ngs-toolbar-title class=\"truncate\">\n {{ inspectorTitle() }}\n </ngs-toolbar-title>\n <ngs-toolbar-subtitle>\n {{ selectedField()?.label || selectedSection()?.title }}\n </ngs-toolbar-subtitle>\n <ngs-toolbar-spacer/>\n <ngs-toolbar-item>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </ngs-toolbar-item>\n </ngs-toolbar>\n </ngs-panel-header>\n }\n\n <ngs-panel-content>\n @if (selectedField() || selectedSection()) {\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n </div>\n </ngs-scrollbar-area>\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 </ngs-panel-content>\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-renderer\n [schema]=\"schema()\"\n [uploadCallback]=\"uploadCallback()\"\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;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{flex:none;width:calc(var(--spacing, .25rem) * 10)}: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;border-color: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{height:100%;min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:transparent;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) 97%)}}:host .ngs-form-builder-canvas-list{display:grid;flex:1;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:calc(var(--spacing, .25rem) * 4);min-height:100%}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid;border-color:transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid;border-color: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-panel-aside.ngs-form-builder-inspector{display:flex;flex-direction:column}:host ngs-panel-aside.ngs-form-builder-inspector>ngs-panel-header{flex:none}:host ngs-panel-aside.ngs-form-builder-inspector>ngs-panel-content{min-height:0;flex:1 1 auto;overflow:hidden}:host .ngs-form-builder-empty-inspector{display:flex;min-height:100%;align-items:center;justify-content:center;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-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-color:transparent;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) 97%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: " button[ngsButton], button[ngsIconButton], a[ngsButton], a[ngsIconButton] ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }, { kind: "component", type: Card, selector: "ngs-card", inputs: ["appearance"], exportAs: ["ngsCard"] }, { kind: "component", type: CardAside, selector: "ngs-card-aside, [ngs-card-aside], ngsCardAside" }, { kind: "component", type: CardContent, selector: "ngs-card-content, [ngs-card-content], [ngsCardContent]", inputs: ["withoutPadding"], exportAs: ["ngsCardContent"] }, { kind: "component", type: CardHeader, selector: "ngs-card-header", exportAs: ["ngsCardHeader"] }, { kind: "component", type: DialogActions, selector: "ngs-dialog-actions, [ngs-dialog-actions], [ngsDialogActions]", inputs: ["align"] }, { kind: "directive", type: DialogClose, selector: "[ngs-dialog-close], [ngsDialogClose]", inputs: ["ngs-dialog-close", "ngsDialogClose", "ariaLabel", "type"], exportAs: ["ngsDialogClose"] }, { kind: "component", type: DialogContent, selector: "ngs-dialog-content,[ngs-dialog-content],[ngsDialogContent]" }, { kind: "component", type: DialogTitle, selector: "ngs-dialog-title, [ngs-dialog-title], [ngsDialogTitle]", inputs: ["id"], exportAs: ["ngsDialogTitle"] }, { kind: "component", type: Icon, selector: "ngs-icon", inputs: ["name"], exportAs: ["ngsIcon"] }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Panel, selector: "ngs-panel", inputs: ["absolute"], exportAs: ["ngsPanel"] }, { kind: "component", type: PanelAside, selector: "ngs-panel-aside" }, { kind: "component", type: PanelContent, selector: "ngs-panel-content", exportAs: ["ngsPanelContent"] }, { kind: "component", type: PanelHeader, selector: "ngs-panel-header", inputs: ["flex", "autoHeight"], exportAs: ["ngsPanelHeader"] }, { kind: "component", type: PanelSidebar, selector: "ngs-panel-sidebar" }, { kind: "component", type: ScrollbarArea, selector: "ngs-scrollbar-area", inputs: ["scrollbarWidth", "autoHide", "absolute"], outputs: ["scrolled"], exportAs: ["ngsScrollbarArea"] }, { kind: "component", type: Tab, selector: "ngs-tab", inputs: ["label", "aria-label", "aria-labelledby", "disabled"], exportAs: ["ngsTab"] }, { kind: "component", type: TabGroup, selector: "ngs-tab-group", inputs: ["selectedIndex", "headerPosition", "preserveContent", "ngs-stretch-tabs", "ngs-align-tabs", "disableRipple", "animationDuration", "animate.enter", "animate.leave"], outputs: ["selectedIndexChange", "selectedTabChange", "focusChange"] }, { kind: "component", type: Toolbar, selector: "ngs-toolbar", exportAs: ["ngsToolbar"] }, { kind: "component", type: ToolbarItem, selector: "ngs-toolbar-item", inputs: ["hidden"], outputs: ["hiddenChange"] }, { kind: "component", type: ToolbarSpacer, selector: "ngs-toolbar-spacer" }, { kind: "component", type: ToolbarTitle, selector: "ngs-toolbar-title", exportAs: ["ngsToolbarTitle"] }, { kind: "component", type: Tree, selector: "ngs-tree", inputs: ["checkable", "selectable", "draggable", "draggablePredicate", "dropPredicate", "reorderOnDrop", "dragPreview", "nodePaddingIndent", "childrenKey", "filterValue", "filterPredicate", "filterMode"], outputs: ["checkedChange", "selectedChange", "nodeDrop"], exportAs: ["ngsTree"] }, { kind: "directive", type: TreeDragPlaceholder, selector: "ng-template[ngsTreeDragPlaceholder]" }, { kind: "component", type: TreeNode, selector: "ngs-tree-node", inputs: ["tabIndex", "value", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["ngsTreeNode"] }, { kind: "directive", type: TreeNodeDef, selector: "[ngsTreeNodeDef]", inputs: ["ngsTreeNodeDefWhen", "ngsTreeNode"] }, { kind: "directive", type: TreeNodePadding, selector: "[ngsTreeNodePadding]", inputs: ["ngsTreeNodePadding", "ngsTreeNodePaddingIndent"] }, { kind: "component", type: FormBuilderFieldHost, selector: "ngs-form-builder-field-host", inputs: ["field", "control", "definitions", "readonly", "editableCanvas", "uploadCallback"], exportAs: ["ngsFormBuilderFieldHost"] }, { kind: "component", type: FormBuilderRenderer, selector: "ngs-form-renderer", inputs: ["schema", "readonly", "showSubmit", "submitLabel", "uploadCallback", "value"], outputs: ["valueChange", "formSubmit", "formReady"], exportAs: ["ngsFormRenderer"] }, { kind: "component", type: FormBuilderSettingsHost, selector: "ngs-form-builder-settings-host", inputs: ["field", "section", "schema", "definitions", "settingsDefinitions", "update", "updateSection"], exportAs: ["ngsFormBuilderSettingsHost"] }, { kind: "component", type: ToolbarSubtitle, selector: "ngs-toolbar-subtitle", exportAs: ["ngsToolbarSubtitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2170
2621
  }
2171
2622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FormBuilder, decorators: [{
2172
2623
  type: Component,
@@ -2192,6 +2643,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
2192
2643
  ScrollbarArea,
2193
2644
  Tab,
2194
2645
  TabGroup,
2646
+ Toolbar,
2647
+ ToolbarItem,
2648
+ ToolbarSpacer,
2649
+ ToolbarTitle,
2195
2650
  Tree,
2196
2651
  TreeDragPlaceholder,
2197
2652
  TreeNode,
@@ -2199,24 +2654,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
2199
2654
  TreeNodePadding,
2200
2655
  FormBuilderFieldHost,
2201
2656
  FormBuilderRenderer,
2202
- FormBuilderSettingsHost
2657
+ FormBuilderSettingsHost,
2658
+ ToolbarSubtitle
2203
2659
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
2204
2660
  'class': 'ngs-form-builder'
2205
- }, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-b-subtle px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-e-subtle\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"ps-1 pe-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-s-subtle\">\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <div class=\"flex items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <h3 class=\"text-base font-semibold\">{{ inspectorTitle() }}</h3>\n @if (selectedField() || selectedSection()) {\n <p class=\"truncate text-sm text-secondary\">{{ selectedField()?.label || selectedSection()?.title }}</p>\n }\n </div>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </div>\n\n @if (selectedField() || selectedSection()) {\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n } @else {\n <div class=\"ngs-form-builder-empty-inspector\">\n Select a field on the canvas or in the fields tree to edit its settings.\n </div>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-aside>\n</ngs-panel>\n\n<ng-template #previewDialog>\n <h3 ngs-dialog-title>{{ schema().title || 'Form preview' }}</h3>\n <ngs-dialog-content class=\"ngs-form-builder-preview-dialog-content\">\n <ngs-form-renderer\n [schema]=\"schema()\"\n [uploadCallback]=\"uploadCallback()\"\n [showSubmit]=\"false\"/>\n </ngs-dialog-content>\n <ngs-dialog-actions align=\"end\">\n <button ngsButton=\"outlined\" type=\"button\" ngs-dialog-close>Close</button>\n </ngs-dialog-actions>\n</ng-template>\n", styles: [":host{display:block;min-height:640px}:host .ngs-form-builder-shell{height:100%;min-height:640px;overflow:hidden}:host .ngs-form-builder-palette-item{display:flex;width:100%;min-height:calc(var(--spacing, .25rem) * 12);align-items:center;border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-item{min-width:0;flex:1;justify-content:flex-start;overflow:hidden}:host .ngs-form-builder-tree-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-tree-item small{margin-inline-start:auto;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);font-weight:400}:host .ngs-form-builder-tree-item.is-selected{background:var(--ngs-color-primary-container);color:var(--ngs-color-on-primary-container)}:host .ngs-form-builder-tree-item.is-selected small{color:currentColor}:host .ngs-form-builder-tree-drag-placeholder{display:flex;min-width:0;width:100%;min-height:calc(var(--spacing, .25rem) * 10);align-items:center;gap:calc(var(--spacing, .25rem) * 2);border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-md);background:var(--ngs-color-primary);color:var(--ngs-color-primary);font-size:var(--ngs-font-size-sm);font-weight:500;padding:calc(var(--spacing, .25rem) * 2) calc(var(--spacing, .25rem) * 3);pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-tree-drag-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-tree-drag-placeholder ngs-icon{flex:none}:host .ngs-form-builder-tree-drag-placeholder span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drag-icon{display:inline-flex;flex:none;align-items:center;color:var(--ngs-color-on-surface-variant)}:host .ngs-form-builder-canvas{min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-native-ghost-section{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 94%)}}:host .ngs-form-builder-canvas-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4)}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface);padding:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header p{overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-grid-field-header span{display:block;overflow:hidden;color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-xs);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-empty-inspector{display:flex;min-height:calc(var(--spacing, .25rem) * 14);align-items:center;justify-content:center;border:1px dashed var(--ngs-color-primary);border-radius:var(--ngs-radius-lg);color:var(--ngs-color-on-surface-variant);font-size:var(--ngs-font-size-sm);grid-column:1/-1;padding:calc(var(--spacing, .25rem) * 4);text-align:center}:host .ngs-form-builder-empty-inspector{min-height:calc(var(--spacing, .25rem) * 28);border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-style:dashed;border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);box-shadow:none;opacity:1;pointer-events:none}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-field-placeholder{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 92%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
2661
+ }, template: "<ng-template #nativeFieldGhost>\n @if (nativeDragFieldDefinition(); as definition) {\n <div\n class=\"ngs-form-builder-field ngs-form-builder-field-placeholder ngs-form-builder-native-ghost-field\"\n [class.is-width-1]=\"definitionWidth(definition) === 1\"\n [class.is-width-2]=\"definitionWidth(definition) === 2\"\n [class.is-width-3]=\"definitionWidth(definition) === 3\"\n [class.is-width-4]=\"definitionWidth(definition) === 4\"\n [class.is-width-5]=\"definitionWidth(definition) === 5\"\n [class.is-width-6]=\"definitionWidth(definition) === 6\"\n [class.is-width-7]=\"definitionWidth(definition) === 7\"\n [class.is-width-8]=\"definitionWidth(definition) === 8\"\n [class.is-width-9]=\"definitionWidth(definition) === 9\"\n [class.is-width-10]=\"definitionWidth(definition) === 10\"\n [class.is-width-11]=\"definitionWidth(definition) === 11\"\n [class.is-width-12]=\"definitionWidth(definition) === 12\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.defaults?.label || definition.label }}</span>\n </div>\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:arrow-move-24-regular\"/>\n </span>\n </div>\n <div class=\"ngs-form-builder-ghost-control\"></div>\n </div>\n }\n</ng-template>\n\n<ng-template #nativeSectionGhost>\n <ngs-card class=\"ngs-form-builder-section ngs-form-builder-native-ghost-section\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <span class=\"ngs-form-builder-drag-icon\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n </span>\n <span class=\"ngs-form-builder-section-title\">Section</span>\n </div>\n </ngs-card-header>\n </ngs-card>\n</ng-template>\n\n<ng-template #fieldList let-fields let-containerId=\"containerId\" let-section=\"section\">\n <div\n class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\"\n [id]=\"containerId\"\n (dragover)=\"nativeFieldDragOver($event, containerId)\"\n (dragleave)=\"nativeDragLeave($event, containerId)\"\n (drop)=\"nativeFieldDrop($event, containerId)\">\n @for (field of fields; track field.id; let fieldIndex = $index) {\n @if (isNativeDropTarget(containerId, fieldIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field),\n section\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field, section)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n }\n\n @if (isNativeDropTarget(containerId, fields.length)) {\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n }\n </div>\n</ng-template>\n\n<ngs-panel class=\"ngs-form-builder-shell\">\n <ngs-panel-header autoHeight class=\"flex items-center justify-between gap-4 border-b border-border px-5 py-3\">\n <div class=\"min-w-0\">\n <p class=\"text-sm text-secondary\">Form builder</p>\n <h2 class=\"truncate text-lg font-semibold\">{{ schema().title || 'Untitled form' }}</h2>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button ngsButton=\"outlined\" type=\"button\" (click)=\"openPreview(previewDialog)\">\n <ngs-icon name=\"fluent:eye-24-regular\"/>\n Preview\n </button>\n </div>\n </ngs-panel-header>\n\n <ngs-panel-sidebar class=\"ngs-form-builder-palette w-80 border-e border-border\">\n <ngs-tab-group\n animationDuration=\"0ms\"\n [selectedIndex]=\"fieldsTabIndex()\"\n (selectedIndexChange)=\"fieldsTabIndex.set($event)\">\n <ngs-tab label=\"Fields\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"flex flex-col gap-5 p-4\">\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">Layout</h4>\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"sectionPaletteDragStarted($event)\"\n (dragend)=\"paletteDragEnded()\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon name=\"fluent:folder-add-24-regular\"/>\n <span>Section</span>\n </span>\n </span>\n </button>\n\n @for (definition of layoutDefinitions(); track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </section>\n\n @for (group of paletteGroups(); track group.name) {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-xs font-semibold uppercase tracking-normal text-secondary\">{{ group.name }}</h4>\n <div class=\"flex flex-col gap-2\">\n @for (definition of group.fields; track definition.type) {\n <button\n type=\"button\"\n class=\"ngs-form-builder-palette-item\"\n draggable=\"true\"\n (dragstart)=\"paletteDragStarted($event, definition)\"\n (dragend)=\"paletteDragEnded()\"\n (click)=\"paletteClicked(definition)\">\n <span class=\"ngs-form-builder-palette-item-content\">\n <span class=\"ngs-form-builder-palette-item-label\">\n <ngs-icon [name]=\"definition.icon || 'fluent:form-24-regular'\"/>\n <span>{{ definition.label }}</span>\n </span>\n </span>\n </button>\n }\n </div>\n </section>\n }\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n <ngs-tab label=\"Layers\" class=\"relative\">\n <ngs-scrollbar-area>\n <div class=\"px-4 py-4\">\n <ngs-tree\n #actualFieldsTree=\"ngsTree\"\n [dataSource]=\"fieldTree()\"\n [childrenAccessor]=\"fieldTreeChildrenAccessor\"\n [trackBy]=\"trackFieldTreeNode\"\n [draggablePredicate]=\"fieldTreeDraggablePredicate\"\n [dropPredicate]=\"fieldTreeDropPredicate\"\n [nodePaddingIndent]=\"80\"\n [reorderOnDrop]=\"false\"\n (nodeDrop)=\"fieldTreeNodeDropped($event)\"\n draggable>\n <ng-template ngsTreeDragPlaceholder let-source=\"source\">\n <div class=\"ngs-form-builder-tree-drag-placeholder\">\n <ngs-icon [name]=\"fieldTreePlaceholderIcon(source)\"/>\n <span>{{ source.label }}</span>\n </div>\n </ng-template>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node; when: hasFieldTreeChildren\"\n ngsTreeNodePadding\n [value]=\"node.id\"\n [cdkTreeNodeTypeaheadLabel]=\"node.label\">\n <button\n ngsIconButton\n class=\"ngs-form-builder-tree-toggle\"\n [attr.aria-label]=\"'Toggle ' + node.label\"\n (click)=\"toggleFieldTreeNode(actualFieldsTree, node, $event)\">\n <ngs-icon [name]=\"isFieldTreeNodeExpanded(actualFieldsTree, node) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-right-24-regular'\" class=\"size-4\"/>\n </button>\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n <span>{{ node.label }}</span>\n <!-- @if (node.name) {-->\n <!-- <small>{{ node.name }}</small>-->\n <!-- }-->\n </button>\n </ngs-tree-node>\n\n <ngs-tree-node *ngsTreeNodeDef=\"let node\" ngsTreeNodePadding [value]=\"node.id\">\n <button\n ngsButton=\"text\"\n type=\"button\"\n class=\"ngs-form-builder-tree-item\"\n [class.is-selected]=\"selectedFieldId() === node.id\"\n [attr.data-form-builder-tree-node-id]=\"node.id\"\n (click)=\"selectFieldTreeNode(node)\">\n <ngs-icon [name]=\"node.icon\"/>\n {{ node.label }}\n<!-- <span class=\"inline-flex flex-col\">-->\n<!-- -->\n<!-- @if (node.name) {-->\n<!-- <small>{{ node.name }}</small>-->\n<!-- }-->\n<!-- </span>-->\n </button>\n </ngs-tree-node>\n </ngs-tree>\n </div>\n </ngs-scrollbar-area>\n </ngs-tab>\n </ngs-tab-group>\n </ngs-panel-sidebar>\n\n <ngs-panel-content>\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"ngs-form-builder-canvas flex flex-col gap-4 p-5\">\n <div\n class=\"ngs-form-builder-canvas-list\"\n [id]=\"rootDropListId()\"\n (dragover)=\"nativeCanvasDragOver($event)\"\n (dragleave)=\"nativeDragLeave($event, rootDropListId())\"\n (drop)=\"nativeFieldDrop($event, rootDropListId())\">\n @for (item of canvasItems(); track item.kind + ':' + item.id; let itemIndex = $index) {\n @if (isNativeDropTarget(rootDropListId(), itemIndex)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n @if (item.field; as field) {\n @if (isContainerField(field)) {\n <div\n class=\"ngs-form-builder-field is-container\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (dragover)=\"nativeContainerFieldDragOver($event, field)\"\n (dragleave)=\"nativeContainerFieldDragLeave($event, field)\"\n (drop)=\"nativeContainerFieldDrop($event, field)\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <div class=\"ngs-form-builder-grid-field\">\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: field.children ?? [],\n containerId: fieldDropListId(field)\n }\"/>\n </div>\n </div>\n } @else {\n <div\n class=\"ngs-form-builder-field\"\n [class.is-width-1]=\"(field.width ?? 12) === 1\"\n [class.is-width-2]=\"field.width === 2\"\n [class.is-width-3]=\"field.width === 3\"\n [class.is-width-4]=\"field.width === 4\"\n [class.is-width-5]=\"field.width === 5\"\n [class.is-width-6]=\"field.width === 6\"\n [class.is-width-7]=\"field.width === 7\"\n [class.is-width-8]=\"field.width === 8\"\n [class.is-width-9]=\"field.width === 9\"\n [class.is-width-10]=\"field.width === 10\"\n [class.is-width-11]=\"field.width === 11\"\n [class.is-width-12]=\"(field.width ?? 12) === 12\"\n [class.is-selected]=\"selectedFieldId() === field.id\"\n [attr.data-form-builder-field-id]=\"field.id\"\n (click)=\"$event.stopPropagation(); selectCanvasField(field)\">\n <div class=\"ngs-form-builder-field-header\">\n <div class=\"ngs-form-builder-field-label\">\n <ngs-icon [name]=\"fieldIcon(field)\"/>\n <span>{{ field.label }}</span>\n </div>\n </div>\n\n <ngs-form-builder-field-host\n [field]=\"field\"\n [control]=\"previewControl(field)\"\n [definitions]=\"definitions()\"\n [uploadCallback]=\"uploadCallback()\"\n [readonly]=\"true\"\n [editableCanvas]=\"true\"/>\n </div>\n }\n } @else if (item.section; as section) {\n <ngs-card class=\"ngs-form-builder-section\"\n [attr.data-form-builder-section-id]=\"section.id\"\n (dragover)=\"nativeSectionDragOver($event, section)\"\n (dragleave)=\"nativeDragLeave($event, sectionDropListId(section))\"\n (drop)=\"nativeSectionDrop($event, section)\"\n (click)=\"selectSection(section)\">\n <ngs-card-header>\n <div class=\"ngs-form-builder-section-heading\">\n <input ngsInput\n class=\"ngs-form-builder-section-title\"\n [ngModel]=\"section.title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateSection(section, { title: $event })\"\n aria-label=\"Section title\">\n </div>\n @if (section.description) {\n <p class=\"truncate text-sm text-secondary\">{{ section.description }}</p>\n }\n\n <ngs-card-aside>\n <div class=\"ngs-form-builder-section-actions\">\n <button ngsIconButton type=\"button\" aria-label=\"Collapse section\" (click)=\"updateSection(section, { collapsed: !section.collapsed })\">\n <ngs-icon [name]=\"isSectionCollapsed(section) ? 'fluent:chevron-down-24-regular' : 'fluent:chevron-up-24-regular'\"/>\n </button>\n <button ngsIconButton type=\"button\" aria-label=\"Delete section\" (click)=\"removeSection(section)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n </button>\n </div>\n </ngs-card-aside>\n </ngs-card-header>\n\n @if (!isSectionCollapsed(section)) {\n <ngs-card-content>\n <ng-container\n [ngTemplateOutlet]=\"fieldList\"\n [ngTemplateOutletContext]=\"{\n $implicit: section.fields,\n containerId: sectionDropListId(section),\n section\n }\"/>\n </ngs-card-content>\n } @else if (isNativeDropTarget(sectionDropListId(section), section.fields.length)) {\n <ngs-card-content>\n <div class=\"ngs-form-builder-drop-list ngs-form-builder-canvas-drop-list\">\n <ng-container [ngTemplateOutlet]=\"nativeFieldGhost\"/>\n </div>\n </ngs-card-content>\n }\n </ngs-card>\n }\n }\n\n @if (isNativeDropTarget(rootDropListId(), canvasItems().length)) {\n <ng-container [ngTemplateOutlet]=\"nativeDragSection() ? nativeSectionGhost : nativeFieldGhost\"/>\n }\n\n </div>\n </div>\n </ngs-scrollbar-area>\n </ngs-panel-content>\n\n <ngs-panel-aside class=\"ngs-form-builder-inspector w-88 border-s border-border\">\n @if (selectedField() || selectedSection()) {\n <ngs-panel-header class=\"ps-5 pe-2 border-b border-border\">\n <ngs-toolbar>\n <ngs-toolbar-title class=\"truncate\">\n {{ inspectorTitle() }}\n </ngs-toolbar-title>\n <ngs-toolbar-subtitle>\n {{ selectedField()?.label || selectedSection()?.title }}\n </ngs-toolbar-subtitle>\n <ngs-toolbar-spacer/>\n <ngs-toolbar-item>\n <button ngsIconButton type=\"button\" aria-label=\"Clear selection\" (click)=\"selectedFieldId.set(null)\">\n <ngs-icon name=\"fluent:dismiss-24-regular\"/>\n </button>\n </ngs-toolbar-item>\n </ngs-toolbar>\n </ngs-panel-header>\n }\n\n <ngs-panel-content>\n @if (selectedField() || selectedSection()) {\n <ngs-scrollbar-area [absolute]=\"true\">\n <div class=\"flex flex-col gap-4 p-5\">\n <ngs-form-builder-settings-host\n [field]=\"selectedField()\"\n [section]=\"selectedSection()\"\n [schema]=\"schema()\"\n [definitions]=\"definitions()\"\n [settingsDefinitions]=\"settingsDefinitions()\"\n [update]=\"updateSelectedField\"\n [updateSection]=\"updateSelectedSection\"/>\n\n @if (selectedField(); as field) {\n <div class=\"mt-3\">\n <button ngsButton=\"text\" type=\"button\" class=\"ngs-form-builder-delete-button\" (click)=\"confirmRemoveField(field)\">\n <ngs-icon name=\"fluent:delete-24-regular\"/>\n Delete field\n </button>\n </div>\n }\n </div>\n </ngs-scrollbar-area>\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 </ngs-panel-content>\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-renderer\n [schema]=\"schema()\"\n [uploadCallback]=\"uploadCallback()\"\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;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-lg);background:var(--ngs-color-surface);color:var(--ngs-color-on-surface);cursor:grab;font-size:var(--ngs-font-size-sm);font-weight:500;padding:0 calc(var(--spacing, .25rem) * 3);text-align:start;transition:background-color .16s cubic-bezier(0,0,.2,1),border-color .16s cubic-bezier(0,0,.2,1),box-shadow .16s cubic-bezier(0,0,.2,1)}:host .ngs-form-builder-palette-item:hover{border-color:var(--ngs-color-primary);background:var(--ngs-color-primary);color:var(--ngs-color-primary)}@supports (color: color-mix(in lab,red,red)){:host .ngs-form-builder-palette-item:hover{background:color-mix(in srgb,var(--ngs-color-primary),var(--ngs-color-surface) 96%)}}:host .ngs-form-builder-palette-item:focus-visible{outline:0;box-shadow:0 0 0 3px var(--ngs-state-focus-ring)}:host .ngs-form-builder-palette-item:active{cursor:grabbing}:host .ngs-form-builder-palette-item-preview{width:calc(var(--spacing, .25rem) * 64)}:host .ngs-form-builder-native-drag-image{position:fixed;inset-block-start:0;inset-inline-start:0;width:calc(var(--spacing, .25rem) * 64);pointer-events:none;transform:translate(-200vw,-200vh);z-index:-1}:host .ngs-form-builder-palette-item-placeholder{width:100%;box-shadow:none;opacity:1;pointer-events:none}:host .ngs-form-builder-palette{background:var(--ngs-color-surface-container-low)}:host .ngs-form-builder-palette-item-content{display:inline-flex;min-width:0;width:100%;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-palette-item-label{display:inline-flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}:host .ngs-form-builder-palette-item-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ngs-tab-group{--ngs-tab-label-height: calc(var(--spacing, .25rem) * 10)}:host ngs-tree{--ngs-tree-node-gap: calc(var(--spacing, .25rem) * 1)}:host ngs-tree ngs-tree-node{cursor:grab}:host ngs-tree ngs-tree-node:active{cursor:grabbing}:host .ngs-form-builder-tree-toggle{flex:none;width:calc(var(--spacing, .25rem) * 10)}: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;border-color: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{height:100%;min-height:100%}:host .ngs-form-builder-section{--ngs-card-padding: calc(var(--spacing, .25rem) * 4);grid-column:span 12}:host .ngs-form-builder-native-ghost-section{border-color:transparent;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) 97%)}}:host .ngs-form-builder-canvas-list{display:grid;flex:1;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:calc(var(--spacing, .25rem) * 4);min-height:100%}:host .ngs-form-builder-section-heading{display:flex;min-width:0;flex:1;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-section-actions{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1)}:host .ngs-form-builder-section-title{width:100%;min-width:0;height:auto;min-height:0;padding:0;border:0;background:transparent;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);outline:0}:host .ngs-form-builder-section-title:focus{outline:2px solid var(--ngs-color-primary);outline-offset:2px;border-radius:var(--ngs-radius-sm)}:host .ngs-form-builder-section-preview-title{min-width:0;overflow:hidden;color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-base);font-weight:600;line-height:var(--ngs-line-height-base);text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-drop-list{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:calc(var(--spacing, .25rem) * 4);min-height:calc(var(--spacing, .25rem) * 16)}:host .ngs-form-builder-field{position:relative;display:flex;flex-direction:column;grid-column:span 12;gap:calc(var(--spacing, .25rem) * 3);border:1px solid;border-color:transparent;border-radius:var(--ngs-radius-lg);padding:calc(var(--spacing, .25rem) * 3);background:var(--ngs-color-surface);cursor:pointer}:host .ngs-form-builder-field.is-container{align-items:stretch;background:var(--ngs-color-surface-container-lowest)}:host .ngs-form-builder-field.is-width-1{grid-column:span 1}:host .ngs-form-builder-field.is-width-2{grid-column:span 2}:host .ngs-form-builder-field.is-width-3{grid-column:span 3}:host .ngs-form-builder-field.is-width-4{grid-column:span 4}:host .ngs-form-builder-field.is-width-5{grid-column:span 5}:host .ngs-form-builder-field.is-width-6{grid-column:span 6}:host .ngs-form-builder-field.is-width-7{grid-column:span 7}:host .ngs-form-builder-field.is-width-8{grid-column:span 8}:host .ngs-form-builder-field.is-width-9{grid-column:span 9}:host .ngs-form-builder-field.is-width-10{grid-column:span 10}:host .ngs-form-builder-field.is-width-11{grid-column:span 11}:host .ngs-form-builder-field.is-width-12{grid-column:span 12}:host .ngs-form-builder-field:hover{border-color:var(--ngs-color-outline-variant)}:host .ngs-form-builder-field.is-selected{border-color:var(--ngs-color-primary);box-shadow:0 0 0 1px var(--ngs-color-primary)}:host .ngs-form-builder-field-header{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-field-label{display:flex;min-width:0;align-items:center;gap:calc(var(--spacing, .25rem) * 2);color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}:host .ngs-form-builder-field-label ngs-icon{flex:none;color:var(--ngs-color-primary)}:host .ngs-form-builder-field-label span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .ngs-form-builder-field-host{min-width:0;width:100%;grid-column:auto}:host .ngs-form-builder-grid-field{display:flex;min-width:0;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3)}:host .ngs-form-builder-grid-field-header{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 3);border:1px solid;border-color: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-panel-aside.ngs-form-builder-inspector{display:flex;flex-direction:column}:host ngs-panel-aside.ngs-form-builder-inspector>ngs-panel-header{flex:none}:host ngs-panel-aside.ngs-form-builder-inspector>ngs-panel-content{min-height:0;flex:1 1 auto;overflow:hidden}:host .ngs-form-builder-empty-inspector{display:flex;min-height:100%;align-items:center;justify-content:center;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-delete-button{justify-content:flex-start;color:var(--ngs-color-danger)}:host .ngs-form-builder-preview-dialog-content{width:min(100%,760px)}:host .ngs-form-builder-field-placeholder{border-color:transparent;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) 97%)}}:host .ngs-form-builder-ghost-control{height:calc(var(--spacing, .25rem) * 10);border:1px solid;border-color:var(--ngs-color-outline-variant);border-radius:var(--ngs-radius-md);background:var(--ngs-color-surface)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
2206
2662
  }], 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 }] }], uploadCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadCallback", required: false }] }], fieldSelected: [{ type: i0.Output, args: ["fieldSelected"] }], fieldAdded: [{ type: i0.Output, args: ["fieldAdded"] }], fieldRemoved: [{ type: i0.Output, args: ["fieldRemoved"] }] } });
2207
2663
  function createDefaultFormBuilderSchema() {
2208
- const sectionId = uniqueId('section');
2209
2664
  return {
2210
2665
  title: 'New form',
2211
2666
  fields: [],
2212
- layout: [{ kind: 'section', id: sectionId }],
2213
- sections: [
2214
- {
2215
- id: sectionId,
2216
- title: 'General information',
2217
- fields: []
2218
- }
2219
- ]
2667
+ layout: [],
2668
+ sections: []
2220
2669
  };
2221
2670
  }
2222
2671
  function uniqueId(prefix) {
@@ -2306,9 +2755,253 @@ function containsField(field, fieldId) {
2306
2755
  return (field.children ?? []).some(child => child.id === fieldId || containsField(child, fieldId));
2307
2756
  }
2308
2757
 
2758
+ class BasicFormBuilderFieldSettings {
2759
+ destroyRef = inject(DestroyRef);
2760
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
2761
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
2762
+ optionsControl = new FormControl('', {
2763
+ nonNullable: true,
2764
+ validators: optionsTextValidator
2765
+ });
2766
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2767
+ radioOrientationOptions = ['vertical', 'horizontal'];
2768
+ spacerHeightOptions = [8, 16, 24, 32, 48, 64];
2769
+ hasCheckedState = computed(() => ['checkbox', 'toggle'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasCheckedState" }] : /* istanbul ignore next */ []));
2770
+ hasPlaceholder = computed(() => !['checkbox', 'toggle', 'radio', 'spacer'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasPlaceholder" }] : /* istanbul ignore next */ []));
2771
+ hasOptions = computed(() => ['select', 'radio', 'checkbox-list'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
2772
+ hasClearable = computed(() => ['select', 'country-select'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasClearable" }] : /* istanbul ignore next */ []));
2773
+ hasMultiple = computed(() => ['select', 'upload'].includes(this.field().type), ...(ngDevMode ? [{ debugName: "hasMultiple" }] : /* istanbul ignore next */ []));
2774
+ isSelect = computed(() => this.field().type === 'select', ...(ngDevMode ? [{ debugName: "isSelect" }] : /* istanbul ignore next */ []));
2775
+ isUpload = computed(() => this.field().type === 'upload', ...(ngDevMode ? [{ debugName: "isUpload" }] : /* istanbul ignore next */ []));
2776
+ isRadio = computed(() => this.field().type === 'radio', ...(ngDevMode ? [{ debugName: "isRadio" }] : /* istanbul ignore next */ []));
2777
+ isSpacer = computed(() => this.field().type === 'spacer', ...(ngDevMode ? [{ debugName: "isSpacer" }] : /* istanbul ignore next */ []));
2778
+ hasBehaviorToggles = computed(() => !this.isSpacer(), ...(ngDevMode ? [{ debugName: "hasBehaviorToggles" }] : /* istanbul ignore next */ []));
2779
+ radioOrientation = computed(() => this.field().settings?.['orientation'] === 'horizontal' ? 'horizontal' : 'vertical', ...(ngDevMode ? [{ debugName: "radioOrientation" }] : /* istanbul ignore next */ []));
2780
+ optionsText = computed(() => (this.field().options ?? [])
2781
+ .map(option => `${option.label}:${option.value}${this.isOptionSelected(option) ? ':selected' : ''}`)
2782
+ .join('\n'), ...(ngDevMode ? [{ debugName: "optionsText" }] : /* istanbul ignore next */ []));
2783
+ constructor() {
2784
+ effect(() => {
2785
+ const nextValue = this.optionsText();
2786
+ if (this.optionsControl.value !== nextValue) {
2787
+ this.optionsControl.setValue(nextValue, { emitEvent: false });
2788
+ }
2789
+ });
2790
+ this.optionsControl.valueChanges
2791
+ .pipe(takeUntilDestroyed(this.destroyRef))
2792
+ .subscribe(value => {
2793
+ if (this.optionsControl.invalid) {
2794
+ return;
2795
+ }
2796
+ this.patchOptions(value);
2797
+ });
2798
+ }
2799
+ patch(changes) {
2800
+ this.update()(changes);
2801
+ }
2802
+ patchSettings(changes) {
2803
+ this.patch({
2804
+ settings: {
2805
+ ...this.field().settings,
2806
+ ...changes
2807
+ }
2808
+ });
2809
+ }
2810
+ patchMultiple(multiple) {
2811
+ if (this.field().type === 'select') {
2812
+ this.patchSelectMultiple(multiple);
2813
+ return;
2814
+ }
2815
+ this.patch({
2816
+ multiple,
2817
+ defaultValue: multiple ? [] : null
2818
+ });
2819
+ }
2820
+ patchSelectMultiple(multiple) {
2821
+ const field = this.field();
2822
+ const options = multiple ? field.options ?? [] : this.normalizeSelectedOptions(field.options ?? [], false);
2823
+ const selectedValues = this.selectedOptionValues(options);
2824
+ let defaultValue = null;
2825
+ if (multiple) {
2826
+ defaultValue = Array.isArray(field.defaultValue)
2827
+ ? field.defaultValue
2828
+ : field.defaultValue == null
2829
+ ? selectedValues
2830
+ : [field.defaultValue];
2831
+ }
2832
+ else {
2833
+ defaultValue = Array.isArray(field.defaultValue)
2834
+ ? field.defaultValue[0] ?? selectedValues[0] ?? null
2835
+ : field.defaultValue ?? selectedValues[0] ?? null;
2836
+ }
2837
+ this.patch({ multiple, options, defaultValue });
2838
+ }
2839
+ patchRadioOrientation(orientation) {
2840
+ this.patch({
2841
+ settings: {
2842
+ ...this.field().settings,
2843
+ orientation
2844
+ }
2845
+ });
2846
+ }
2847
+ patchOptions(value) {
2848
+ const options = this.normalizeSelectedOptions(value
2849
+ .split('\n')
2850
+ .map(line => line.trim())
2851
+ .filter(Boolean)
2852
+ .map((line, index) => {
2853
+ const parsed = parseOptionLine(line);
2854
+ return {
2855
+ label: parsed?.label || `Option ${index + 1}`,
2856
+ value: parsed?.value || `option_${index + 1}`,
2857
+ selected: parsed?.selected || undefined
2858
+ };
2859
+ }));
2860
+ this.patch({
2861
+ options,
2862
+ defaultValue: this.resolveSelectedDefaultValue(options)
2863
+ });
2864
+ }
2865
+ normalizeSelectedOptions(options, multiple = this.field().multiple) {
2866
+ if (this.field().type === 'checkbox-list' || multiple) {
2867
+ return options;
2868
+ }
2869
+ let selectedSeen = false;
2870
+ return options.map(option => {
2871
+ if (!option.selected) {
2872
+ return option;
2873
+ }
2874
+ if (selectedSeen) {
2875
+ return {
2876
+ ...option,
2877
+ selected: undefined
2878
+ };
2879
+ }
2880
+ selectedSeen = true;
2881
+ return option;
2882
+ });
2883
+ }
2884
+ resolveSelectedDefaultValue(options) {
2885
+ const selectedValues = this.selectedOptionValues(options);
2886
+ if (this.field().type === 'checkbox-list' || this.field().multiple) {
2887
+ return selectedValues;
2888
+ }
2889
+ return selectedValues[0] ?? null;
2890
+ }
2891
+ selectedOptionValues(options) {
2892
+ return options
2893
+ .filter(option => option.selected)
2894
+ .map(option => option.value);
2895
+ }
2896
+ isOptionSelected(option) {
2897
+ if (option.selected) {
2898
+ return true;
2899
+ }
2900
+ const defaultValue = this.field().defaultValue;
2901
+ return Array.isArray(defaultValue)
2902
+ ? defaultValue.includes(option.value)
2903
+ : defaultValue === option.value;
2904
+ }
2905
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
2906
+ 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 @if (!isSpacer()) {\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\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n @if (!isSpacer()) {\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\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 @if (hasBehaviorToggles()) {\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (hasMultiple()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n }\n\n @if (isUpload()) {\n <ngs-form-field>\n <ngs-label>Accepted file types</ngs-label>\n <input ngsInput\n [ngModel]=\"field().settings?.['accept'] || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ accept: $event })\">\n <ngs-hint>Use MIME types separated by commas, for example image/*,application/pdf.</ngs-hint>\n </ngs-form-field>\n }\n\n @if (isSpacer()) {\n <ngs-form-field>\n <ngs-label>Height</ngs-label>\n <ngs-select [ngModel]=\"field().settings?.['height'] ?? 24\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ height: $event })\">\n @for (height of spacerHeightOptions; track height) {\n <ngs-option [value]=\"height\">{{ height }}px</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Error, selector: "ngs-error", exportAs: ["ngsError"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }, { kind: "component", type: SlideToggleGroup, selector: "ngs-slide-toggle-group", exportAs: ["ngsSlideToggleGroup"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2907
+ }
2908
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderFieldSettings, decorators: [{
2909
+ type: Component,
2910
+ args: [{ selector: 'ngs-basic-form-builder-field-settings', exportAs: 'ngsBasicFormBuilderFieldSettings', imports: [
2911
+ FormsModule,
2912
+ ReactiveFormsModule,
2913
+ Error,
2914
+ FormField,
2915
+ Hint,
2916
+ Label,
2917
+ Input,
2918
+ Select,
2919
+ Option,
2920
+ SlideToggle,
2921
+ SlideToggleGroup
2922
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
2923
+ 'class': 'ngs-basic-form-builder-field-settings'
2924
+ }, 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 @if (!isSpacer()) {\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\n @if (hasPlaceholder()) {\n <ngs-form-field>\n <ngs-label>Placeholder</ngs-label>\n <input ngsInput\n [ngModel]=\"field().placeholder || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ placeholder: $event })\">\n </ngs-form-field>\n }\n\n @if (!isSpacer()) {\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\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 @if (hasBehaviorToggles()) {\n <ngs-slide-toggle-group class=\"mb-4\">\n <ngs-slide-toggle [ngModel]=\"field().required ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ required: $event })\">\n Required field\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().readonly ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ readonly: $event })\">\n Readonly\n </ngs-slide-toggle>\n\n <ngs-slide-toggle [ngModel]=\"field().disabled ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ disabled: $event })\">\n Disabled\n </ngs-slide-toggle>\n\n @if (hasCheckedState()) {\n <ngs-slide-toggle [ngModel]=\"field().defaultValue === true\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ defaultValue: $event })\">\n Checked\n </ngs-slide-toggle>\n }\n\n @if (hasMultiple()) {\n <ngs-slide-toggle [ngModel]=\"field().multiple ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchMultiple($event)\">\n Multiple\n </ngs-slide-toggle>\n }\n\n @if (hasClearable()) {\n <ngs-slide-toggle [ngModel]=\"field().clearable ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ clearable: $event })\">\n Clearable\n </ngs-slide-toggle>\n }\n </ngs-slide-toggle-group>\n }\n\n @if (isUpload()) {\n <ngs-form-field>\n <ngs-label>Accepted file types</ngs-label>\n <input ngsInput\n [ngModel]=\"field().settings?.['accept'] || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ accept: $event })\">\n <ngs-hint>Use MIME types separated by commas, for example image/*,application/pdf.</ngs-hint>\n </ngs-form-field>\n }\n\n @if (isSpacer()) {\n <ngs-form-field>\n <ngs-label>Height</ngs-label>\n <ngs-select [ngModel]=\"field().settings?.['height'] ?? 24\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchSettings({ height: $event })\">\n @for (height of spacerHeightOptions; track height) {\n <ngs-option [value]=\"height\">{{ height }}px</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (isRadio()) {\n <ngs-form-field>\n <ngs-label>Orientation</ngs-label>\n <ngs-select [ngModel]=\"radioOrientation()\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patchRadioOrientation($event)\">\n @for (orientation of radioOrientationOptions; track orientation) {\n <ngs-option [value]=\"orientation\">{{ orientation }}</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n }\n\n @if (hasOptions()) {\n <ngs-form-field>\n <ngs-label>Options</ngs-label>\n <textarea ngsInput\n rows=\"4\"\n [formControl]=\"optionsControl\"></textarea>\n @if (optionsControl.hasError('formBuilderOptionsFormat')) {\n <ngs-error>Use Label:value or Label:value:selected.</ngs-error>\n }\n <ngs-hint>One option per line. Use Label:value or Label:value:selected.</ngs-hint>\n </ngs-form-field>\n }\n</div>\n", styles: [":host{display:block}:host .settings-section{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);border-top:1px solid var(--ngs-color-outline-variant);padding-top:calc(var(--spacing, .25rem) * 4)}:host .settings-section h4{color:var(--ngs-color-on-surface);font-size:var(--ngs-font-size-sm);font-weight:600}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
2925
+ }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
2926
+ function optionsTextValidator(control) {
2927
+ const hasInvalidLine = (control.value ?? '')
2928
+ .split('\n')
2929
+ .map(line => line.trim())
2930
+ .filter(Boolean)
2931
+ .some(line => !parseOptionLine(line));
2932
+ return hasInvalidLine ? { formBuilderOptionsFormat: true } : null;
2933
+ }
2934
+ function parseOptionLine(line) {
2935
+ const parts = line.split(':').map(part => part.trim());
2936
+ if (parts.length < 2 || parts.length > 3) {
2937
+ return null;
2938
+ }
2939
+ const [label, value, selected] = parts;
2940
+ if (!label || !value) {
2941
+ return null;
2942
+ }
2943
+ if (selected !== undefined && selected !== 'selected') {
2944
+ return null;
2945
+ }
2946
+ return {
2947
+ label,
2948
+ value,
2949
+ selected: selected === 'selected'
2950
+ };
2951
+ }
2952
+
2953
+ class BasicFormBuilderLayoutSettings {
2954
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
2955
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
2956
+ fieldWidthOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2957
+ showHintSetting = computed(() => this.field().type !== 'group', ...(ngDevMode ? [{ debugName: "showHintSetting" }] : /* istanbul ignore next */ []));
2958
+ patch(changes) {
2959
+ this.update()(changes);
2960
+ }
2961
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
2962
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: BasicFormBuilderLayoutSettings, isStandalone: true, selector: "ngs-basic-form-builder-layout-settings", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-layout-settings" }, exportAs: ["ngsBasicFormBuilderLayoutSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Label</ngs-label>\n <input ngsInput\n [ngModel]=\"field().label\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ label: $event })\">\n </ngs-form-field>\n\n @if (showHintSetting()) {\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n }\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: Option, selector: "ngs-option", inputs: ["value", "data", "disabled", "selected"], outputs: ["onSelectionChange"], exportAs: ["ngsOption"] }, { kind: "component", type: Select, selector: "ngs-select", inputs: ["id", "placeholder", "disabled", "required", "multiple", "hideCheckIcon", "clearable", "aria-label", "tabIndex", "aria-describedby", "value"], outputs: ["selectionChange", "opened", "closed", "valueChange"], exportAs: ["ngsSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2963
+ }
2964
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderLayoutSettings, decorators: [{
2965
+ type: Component,
2966
+ args: [{ selector: 'ngs-basic-form-builder-layout-settings', exportAs: 'ngsBasicFormBuilderLayoutSettings', imports: [
2967
+ FormsModule,
2968
+ FormField,
2969
+ Hint,
2970
+ Label,
2971
+ Input,
2972
+ Option,
2973
+ Select
2974
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
2975
+ 'class': 'ngs-basic-form-builder-layout-settings'
2976
+ }, 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 @if (showHintSetting()) {\n <ngs-form-field>\n <ngs-label>Hint</ngs-label>\n <input ngsInput\n [ngModel]=\"field().hint || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ hint: $event })\">\n <ngs-hint>Shown below the layout title in preview and renderer.</ngs-hint>\n </ngs-form-field>\n }\n\n <ngs-form-field>\n <ngs-label>Width</ngs-label>\n <ngs-select [ngModel]=\"field().width ?? 12\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ width: $event })\">\n @for (width of fieldWidthOptions; track width) {\n <ngs-option [value]=\"width\">{{ width }}/12</ngs-option>\n }\n </ngs-select>\n </ngs-form-field>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
2977
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
2978
+
2979
+ class BasicFormBuilderSectionSettings {
2980
+ section = input.required(...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
2981
+ update = input.required(...(ngDevMode ? [{ debugName: "update" }] : /* istanbul ignore next */ []));
2982
+ patch(changes) {
2983
+ this.update()(changes);
2984
+ }
2985
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, deps: [], target: i0.ɵɵFactoryTarget.Component });
2986
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: BasicFormBuilderSectionSettings, isStandalone: true, selector: "ngs-basic-form-builder-section-settings", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, update: { classPropertyName: "update", publicName: "update", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "ngs-basic-form-builder-section-settings" }, exportAs: ["ngsBasicFormBuilderSectionSettings"], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormField, selector: "ngs-form-field", inputs: ["subscriptHiddenIfEmpty", "sameHeightAsButton"], exportAs: ["ngsFormField"] }, { kind: "component", type: Hint, selector: "ngs-hint", inputs: ["align"], exportAs: ["ngsHint"] }, { kind: "component", type: Label, selector: "ngs-label" }, { kind: "directive", type: Input, selector: "input[ngsInput], textarea[ngsInput]", inputs: ["id", "placeholder", "required", "disabled", "readonly", "errorStateMatcher"], exportAs: ["ngsInput"] }, { kind: "component", type: SlideToggle, selector: "ngs-slide-toggle", inputs: ["id", "name", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "disabled", "disableRipple", "tabIndex", "hideIcon", "color", "checked"], outputs: ["disabledChange", "checkedChange", "change", "toggleChange"], exportAs: ["ngsSlideToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2987
+ }
2988
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: BasicFormBuilderSectionSettings, decorators: [{
2989
+ type: Component,
2990
+ args: [{ selector: 'ngs-basic-form-builder-section-settings', exportAs: 'ngsBasicFormBuilderSectionSettings', imports: [
2991
+ FormsModule,
2992
+ FormField,
2993
+ Hint,
2994
+ Label,
2995
+ Input,
2996
+ SlideToggle
2997
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
2998
+ 'class': 'ngs-basic-form-builder-section-settings'
2999
+ }, template: "<div class=\"flex flex-col gap-4\">\n <ngs-form-field>\n <ngs-label>Title</ngs-label>\n <input ngsInput\n [ngModel]=\"section().title\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ title: $event })\">\n </ngs-form-field>\n\n <ngs-form-field>\n <ngs-label>Description</ngs-label>\n <textarea ngsInput\n rows=\"3\"\n [ngModel]=\"section().description || ''\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ description: $event })\"></textarea>\n <ngs-hint>Optional helper text rendered under the section title.</ngs-hint>\n </ngs-form-field>\n\n <ngs-slide-toggle [ngModel]=\"section().collapsed ?? false\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"patch({ collapsed: $event })\">\n Collapsed\n </ngs-slide-toggle>\n</div>\n", styles: [":host{display:block}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
3000
+ }], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], update: [{ type: i0.Input, args: [{ isSignal: true, alias: "update", required: true }] }] } });
3001
+
2309
3002
  /**
2310
3003
  * Generated bundle index. Do not edit.
2311
3004
  */
2312
3005
 
2313
- export { BasicFormBuilderFieldSettings, BasicFormBuilderLayoutSettings, BasicFormBuilderSectionSettings, DEFAULT_FORM_BUILDER_FIELDS, DEFAULT_FORM_BUILDER_ITEMS, FORM_BUILDER_FIELDS, FORM_BUILDER_ITEMS, FORM_BUILDER_SETTINGS, FORM_BUILDER_UPLOAD_CALLBACK, FormBuilder, FormBuilderFieldHost, FormBuilderRenderer, FormBuilderSettingsHost, createDefaultFormBuilderSchema, formBuilderField, formBuilderItem, formBuilderSettings, provideFormBuilder, validatorsFromRules };
3006
+ export { BasicFormBuilderFieldSettings, BasicFormBuilderLayoutSettings, BasicFormBuilderSectionSettings, DEFAULT_FORM_BUILDER_FIELDS, DEFAULT_FORM_BUILDER_ITEMS, FORM_BUILDER_FIELDS, FORM_BUILDER_FIELD_BASE_SETTINGS_SCHEMA, FORM_BUILDER_INPUT_FIELD_BASE_SETTINGS_SCHEMA, FORM_BUILDER_ITEMS, FORM_BUILDER_LAYOUT_BASE_SETTINGS_SCHEMA, FORM_BUILDER_LAYOUT_CONTAINER_BASE_SETTINGS_SCHEMA, FORM_BUILDER_SECTION_BASE_SETTINGS_SCHEMA, FORM_BUILDER_SETTINGS, FORM_BUILDER_STATIC_BASE_SETTINGS_SCHEMA, FORM_BUILDER_UPLOAD_CALLBACK, FormBuilder, FormBuilderFieldHost, FormBuilderRenderer, FormBuilderSettingsHost, createDefaultFormBuilderSchema, formBuilderField, formBuilderItem, formBuilderSettings, provideFormBuilder, provideFormBuilderField, provideFormBuilderFields, validatorsFromRules };
2314
3007
  //# sourceMappingURL=ngstarter-ui-components-form-builder.mjs.map