angular-data-mapper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/LICENSE +190 -0
  3. package/PUBLISHING.md +75 -0
  4. package/README.md +214 -0
  5. package/angular.json +121 -0
  6. package/package.json +67 -0
  7. package/projects/demo-app/public/favicon.ico +0 -0
  8. package/projects/demo-app/src/app/app.config.ts +12 -0
  9. package/projects/demo-app/src/app/app.html +36 -0
  10. package/projects/demo-app/src/app/app.routes.ts +62 -0
  11. package/projects/demo-app/src/app/app.scss +65 -0
  12. package/projects/demo-app/src/app/app.ts +11 -0
  13. package/projects/demo-app/src/app/layout/app-layout.component.ts +294 -0
  14. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.html +87 -0
  15. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.scss +202 -0
  16. package/projects/demo-app/src/app/pages/mapper-page/mapper-page.component.ts +192 -0
  17. package/projects/demo-app/src/app/pages/mappings-page/add-mapping-dialog.component.ts +163 -0
  18. package/projects/demo-app/src/app/pages/mappings-page/mappings-page.component.ts +306 -0
  19. package/projects/demo-app/src/app/pages/schema-creator-page/schema-creator-page.component.ts +88 -0
  20. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.html +108 -0
  21. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.scss +317 -0
  22. package/projects/demo-app/src/app/pages/schema-editor-page/schema-editor-page.component.ts +129 -0
  23. package/projects/demo-app/src/app/services/app-state.service.ts +233 -0
  24. package/projects/demo-app/src/app/services/sample-data.service.ts +228 -0
  25. package/projects/demo-app/src/index.html +15 -0
  26. package/projects/demo-app/src/main.ts +6 -0
  27. package/projects/demo-app/src/styles.scss +54 -0
  28. package/projects/demo-app/tsconfig.app.json +13 -0
  29. package/projects/ngx-data-mapper/ng-package.json +7 -0
  30. package/projects/ngx-data-mapper/package.json +40 -0
  31. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.html +183 -0
  32. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.scss +352 -0
  33. package/projects/ngx-data-mapper/src/lib/components/array-filter-modal/array-filter-modal.component.ts +277 -0
  34. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.html +174 -0
  35. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.scss +357 -0
  36. package/projects/ngx-data-mapper/src/lib/components/array-selector-modal/array-selector-modal.component.ts +258 -0
  37. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.html +139 -0
  38. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.scss +213 -0
  39. package/projects/ngx-data-mapper/src/lib/components/condition-builder/condition-builder.component.ts +261 -0
  40. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.html +199 -0
  41. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.scss +321 -0
  42. package/projects/ngx-data-mapper/src/lib/components/data-mapper/data-mapper.component.ts +618 -0
  43. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.html +67 -0
  44. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.scss +97 -0
  45. package/projects/ngx-data-mapper/src/lib/components/default-value-popover/default-value-popover.component.ts +105 -0
  46. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.html +552 -0
  47. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.scss +824 -0
  48. package/projects/ngx-data-mapper/src/lib/components/schema-editor/schema-editor.component.ts +730 -0
  49. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.html +82 -0
  50. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.scss +352 -0
  51. package/projects/ngx-data-mapper/src/lib/components/schema-tree/schema-tree.component.ts +225 -0
  52. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.html +346 -0
  53. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.scss +511 -0
  54. package/projects/ngx-data-mapper/src/lib/components/transformation-popover/transformation-popover.component.ts +368 -0
  55. package/projects/ngx-data-mapper/src/lib/models/json-schema.model.ts +164 -0
  56. package/projects/ngx-data-mapper/src/lib/models/schema.model.ts +173 -0
  57. package/projects/ngx-data-mapper/src/lib/services/mapping.service.ts +615 -0
  58. package/projects/ngx-data-mapper/src/lib/services/schema-parser.service.ts +270 -0
  59. package/projects/ngx-data-mapper/src/lib/services/svg-connector.service.ts +135 -0
  60. package/projects/ngx-data-mapper/src/lib/services/transformation.service.ts +453 -0
  61. package/projects/ngx-data-mapper/src/public-api.ts +22 -0
  62. package/projects/ngx-data-mapper/tsconfig.lib.json +13 -0
  63. package/projects/ngx-data-mapper/tsconfig.lib.prod.json +9 -0
  64. package/tsconfig.json +28 -0
@@ -0,0 +1,552 @@
1
+ <div class="schema-editor">
2
+ <!-- Schema Header -->
3
+ <div class="editor-header">
4
+ @if (showSchemaName) {
5
+ <div class="schema-name-section">
6
+ <mat-form-field appearance="outline" class="schema-name-field">
7
+ <mat-label>Schema Name</mat-label>
8
+ <input
9
+ #schemaNameInput
10
+ matInput
11
+ [value]="schemaName()"
12
+ (input)="onSchemaNameChange($any($event.target).value, schemaNameInput)"
13
+ placeholder="Enter schema name"
14
+ />
15
+ </mat-form-field>
16
+ </div>
17
+ }
18
+
19
+ @if (showJsonToggle) {
20
+ <mat-button-toggle-group [value]="viewMode()" (change)="setViewMode($event.value)" class="view-toggle">
21
+ <mat-button-toggle value="visual">Visual</mat-button-toggle>
22
+ <mat-button-toggle value="json">JSON</mat-button-toggle>
23
+ </mat-button-toggle-group>
24
+ }
25
+
26
+ <div class="header-actions">
27
+ @if (viewMode() === 'visual') {
28
+ <button mat-flat-button color="primary" (click)="addField()">
29
+ <mat-icon>add</mat-icon>
30
+ Add Field
31
+ </button>
32
+ } @else {
33
+ <button mat-stroked-button (click)="copyJson()" matTooltip="Copy JSON to clipboard">
34
+ <mat-icon>content_copy</mat-icon>
35
+ Copy
36
+ </button>
37
+ <button mat-stroked-button (click)="formatJson()">
38
+ <mat-icon>auto_fix_high</mat-icon>
39
+ Format
40
+ </button>
41
+ <button mat-flat-button color="primary" (click)="applyJsonChanges()" [disabled]="jsonError()">
42
+ <mat-icon>check</mat-icon>
43
+ Apply
44
+ </button>
45
+ }
46
+ </div>
47
+ </div>
48
+
49
+ <!-- JSON View -->
50
+ @if (viewMode() === 'json') {
51
+ <div class="json-view">
52
+ @if (jsonError()) {
53
+ <div class="json-error">
54
+ <mat-icon>error</mat-icon>
55
+ {{ jsonError() }}
56
+ </div>
57
+ }
58
+ <textarea
59
+ class="json-textarea"
60
+ [value]="jsonText()"
61
+ (input)="onJsonTextChange($any($event.target).value)"
62
+ spellcheck="false"
63
+ ></textarea>
64
+ </div>
65
+ }
66
+
67
+ <!-- Fields List (Visual View) -->
68
+ <div class="fields-container" [class.hidden]="viewMode() === 'json'">
69
+ @if (fields().length === 0) {
70
+ <div class="empty-state">
71
+ <mat-icon>schema</mat-icon>
72
+ <p>No fields yet. Click "Add Field" to get started.</p>
73
+ </div>
74
+ } @else {
75
+ <div
76
+ class="fields-list"
77
+ cdkDropList
78
+ [cdkDropListData]="fields()"
79
+ (cdkDropListDropped)="onFieldDrop($event)"
80
+ >
81
+ @for (field of fields(); track field.id) {
82
+ <div class="field-wrapper" cdkDrag>
83
+ <!-- Drag Placeholder -->
84
+ <div class="drag-placeholder" *cdkDragPlaceholder></div>
85
+
86
+ <!-- Drag Preview -->
87
+ <div *cdkDragPreview class="drag-preview">
88
+ <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>
89
+ {{ field.name || 'unnamed' }}
90
+ </div>
91
+
92
+ <div
93
+ class="field-item"
94
+ [class.is-editing]="field.isEditing"
95
+ [class.is-complex]="field.type === 'object' || field.type === 'array'"
96
+ >
97
+ <!-- Left section: field controls -->
98
+ <div class="field-left">
99
+ <!-- Drag Handle -->
100
+ <div class="drag-handle" cdkDragHandle matTooltip="Drag to reorder">
101
+ <mat-icon>drag_indicator</mat-icon>
102
+ </div>
103
+
104
+ <!-- Indent/Outdent Buttons -->
105
+ <div class="indent-buttons">
106
+ <button
107
+ class="indent-btn"
108
+ [disabled]="!canIndent(field, fields())"
109
+ (click)="indentField(field, fields())"
110
+ matTooltip="Move into previous object/array"
111
+ >
112
+ <mat-icon>chevron_right</mat-icon>
113
+ </button>
114
+ <button
115
+ class="indent-btn"
116
+ disabled
117
+ matTooltip="Move out of parent"
118
+ >
119
+ <mat-icon>chevron_left</mat-icon>
120
+ </button>
121
+ </div>
122
+
123
+ <!-- Expand/Collapse -->
124
+ @if (field.type === 'object' || field.type === 'array') {
125
+ <button
126
+ class="expand-btn"
127
+ (click)="toggleExpand(field)"
128
+ [matTooltip]="field.expanded ? 'Collapse' : 'Expand'"
129
+ >
130
+ <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>
131
+ </button>
132
+ } @else {
133
+ <span class="expand-placeholder"></span>
134
+ }
135
+
136
+ <!-- Type Icon -->
137
+ <mat-icon class="type-icon" [matTooltip]="field.type">{{ getTypeIcon(field.type) }}</mat-icon>
138
+
139
+ <!-- Field Name -->
140
+ @if (field.isEditing) {
141
+ <input
142
+ class="field-name-input"
143
+ [value]="field.name"
144
+ (input)="onFieldNameChange(field, $event)"
145
+ (blur)="stopEdit(field)"
146
+ (keydown)="onFieldNameKeydown($event, field)"
147
+ placeholder="Field name"
148
+ autofocus
149
+ />
150
+ } @else {
151
+ <span class="field-name" (dblclick)="startEdit(field)">
152
+ {{ field.name || 'unnamed' }}
153
+ @if (field.type === 'array') {
154
+ <span class="array-indicator">[]</span>
155
+ }
156
+ </span>
157
+ }
158
+
159
+ <!-- Required Toggle -->
160
+ <button
161
+ class="required-btn"
162
+ [class.is-required]="field.required"
163
+ (click)="toggleRequired(field)"
164
+ [matTooltip]="field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'"
165
+ >
166
+ <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>
167
+ </button>
168
+ </div>
169
+
170
+ <!-- Middle section: description (flexible) -->
171
+ <input
172
+ class="description-input"
173
+ [value]="field.description || ''"
174
+ (input)="onDescriptionChange(field, $any($event.target).value)"
175
+ placeholder="Description..."
176
+ />
177
+
178
+ <!-- Right section: type and actions (fixed position) -->
179
+ <div class="field-right">
180
+ <!-- Type Selector -->
181
+ <mat-form-field appearance="outline" class="type-selector">
182
+ <mat-select [value]="field.type" (selectionChange)="onFieldTypeChange(field, $event.value)">
183
+ @for (type of fieldTypes; track type.value) {
184
+ <mat-option [value]="type.value">
185
+ <mat-icon>{{ type.icon }}</mat-icon>
186
+ {{ type.label }}
187
+ </mat-option>
188
+ }
189
+ </mat-select>
190
+ </mat-form-field>
191
+
192
+ <!-- Actions -->
193
+ <div class="field-actions">
194
+ @if (field.type === 'object' || field.type === 'array') {
195
+ <button
196
+ mat-icon-button
197
+ (click)="addChildField(field)"
198
+ matTooltip="Add child field"
199
+ >
200
+ <mat-icon>add_circle_outline</mat-icon>
201
+ </button>
202
+ }
203
+ @if (field.type === 'string' || field.type === 'number') {
204
+ <button
205
+ mat-icon-button
206
+ (click)="toggleValuesEditor(field)"
207
+ [matTooltip]="field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues!.length + ')' : 'Add allowed values'"
208
+ [class.has-values]="field.allowedValues?.length"
209
+ >
210
+ <mat-icon>list</mat-icon>
211
+ </button>
212
+ }
213
+ @if (field.type !== 'object' && field.type !== 'array') {
214
+ <button
215
+ mat-icon-button
216
+ (click)="toggleDefaultEditor(field)"
217
+ [matTooltip]="field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'"
218
+ [class.has-default]="field.defaultValue !== undefined"
219
+ >
220
+ <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>
221
+ </button>
222
+ }
223
+ <button mat-icon-button [matMenuTriggerFor]="fieldMenu" matTooltip="More options">
224
+ <mat-icon>more_vert</mat-icon>
225
+ </button>
226
+ <mat-menu #fieldMenu="matMenu">
227
+ <button mat-menu-item (click)="startEdit(field)">
228
+ <mat-icon>edit</mat-icon>
229
+ <span>Rename</span>
230
+ </button>
231
+ <button mat-menu-item (click)="duplicateField(field, fields())">
232
+ <mat-icon>content_copy</mat-icon>
233
+ <span>Duplicate</span>
234
+ </button>
235
+ <button mat-menu-item (click)="deleteField(field, fields())" class="delete-action">
236
+ <mat-icon>delete</mat-icon>
237
+ <span>Delete</span>
238
+ </button>
239
+ </mat-menu>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Allowed Values Editor -->
245
+ @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {
246
+ <div class="allowed-values-editor">
247
+ <div class="values-header">
248
+ <span class="values-label">Allowed values:</span>
249
+ <input
250
+ #valueInput
251
+ class="value-input"
252
+ type="text"
253
+ placeholder="Type value and press Enter"
254
+ (keydown)="onAllowedValueKeydown($event, field, valueInput)"
255
+ />
256
+ <button class="add-value-btn" (click)="addAllowedValue(field, $event)" matTooltip="Add value">
257
+ <mat-icon>add</mat-icon>
258
+ </button>
259
+ </div>
260
+ @if (field.allowedValues && field.allowedValues.length > 0) {
261
+ <div class="values-list">
262
+ @for (value of field.allowedValues; track value; let vi = $index) {
263
+ <span class="value-chip">
264
+ {{ value }}
265
+ <button class="remove-value-btn" (click)="removeAllowedValue(field, vi)" matTooltip="Remove">
266
+ <mat-icon>close</mat-icon>
267
+ </button>
268
+ </span>
269
+ }
270
+ </div>
271
+ } @else {
272
+ <div class="no-values">No values defined yet</div>
273
+ }
274
+ </div>
275
+ }
276
+
277
+ <!-- Default Value Editor -->
278
+ @if (field.isEditingDefault && field.type !== 'object' && field.type !== 'array') {
279
+ <div class="default-value-editor">
280
+ <div class="default-header">
281
+ <span class="default-label">Default value:</span>
282
+ @if (field.type === 'boolean') {
283
+ <select
284
+ class="default-select"
285
+ [value]="field.defaultValue?.toString() || ''"
286
+ (change)="onDefaultValueChange(field, $any($event.target).value)"
287
+ >
288
+ <option value="">No default</option>
289
+ <option value="true">true</option>
290
+ <option value="false">false</option>
291
+ </select>
292
+ } @else {
293
+ <input
294
+ class="default-input"
295
+ [type]="field.type === 'number' ? 'number' : 'text'"
296
+ [value]="field.defaultValue ?? ''"
297
+ (input)="onDefaultValueChange(field, $any($event.target).value)"
298
+ (keydown)="onDefaultValueKeydown($event, field)"
299
+ placeholder="Enter default value"
300
+ />
301
+ }
302
+ @if (field.defaultValue !== undefined) {
303
+ <button class="clear-default-btn" (click)="clearDefaultValue(field)" matTooltip="Clear default">
304
+ <mat-icon>close</mat-icon>
305
+ </button>
306
+ }
307
+ </div>
308
+ </div>
309
+ }
310
+
311
+ <!-- Nested Children -->
312
+ @if ((field.type === 'object' || field.type === 'array') && field.expanded && field.children) {
313
+ <div
314
+ class="nested-fields"
315
+ cdkDropList
316
+ [cdkDropListData]="field.children"
317
+ (cdkDropListDropped)="onFieldDrop($event)"
318
+ >
319
+ @if (field.children.length > 0) {
320
+ @for (child of field.children; track child.id) {
321
+ <div class="field-wrapper" cdkDrag>
322
+ <div class="drag-placeholder" *cdkDragPlaceholder></div>
323
+ <div *cdkDragPreview class="drag-preview">
324
+ <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>
325
+ {{ child.name || 'unnamed' }}
326
+ </div>
327
+ <ng-container *ngTemplateOutlet="fieldItemTemplate; context: { field: child, parentList: field.children, level: 1 }"></ng-container>
328
+
329
+ <!-- Allowed Values Editor for nested field -->
330
+ @if (child.isEditingValues && (child.type === 'string' || child.type === 'number')) {
331
+ <div class="allowed-values-editor">
332
+ <div class="values-header">
333
+ <span class="values-label">Allowed values:</span>
334
+ <input
335
+ #childValueInput
336
+ class="value-input"
337
+ type="text"
338
+ placeholder="Type value and press Enter"
339
+ (keydown)="onAllowedValueKeydown($event, child, childValueInput)"
340
+ />
341
+ <button class="add-value-btn" (click)="addAllowedValue(child, $event)" matTooltip="Add value">
342
+ <mat-icon>add</mat-icon>
343
+ </button>
344
+ </div>
345
+ @if (child.allowedValues && child.allowedValues.length > 0) {
346
+ <div class="values-list">
347
+ @for (value of child.allowedValues; track value; let vi = $index) {
348
+ <span class="value-chip">
349
+ {{ value }}
350
+ <button class="remove-value-btn" (click)="removeAllowedValue(child, vi)" matTooltip="Remove">
351
+ <mat-icon>close</mat-icon>
352
+ </button>
353
+ </span>
354
+ }
355
+ </div>
356
+ } @else {
357
+ <div class="no-values">No values defined yet</div>
358
+ }
359
+ </div>
360
+ }
361
+
362
+ <!-- Default Value Editor for nested field -->
363
+ @if (child.isEditingDefault && child.type !== 'object' && child.type !== 'array') {
364
+ <div class="default-value-editor">
365
+ <div class="default-header">
366
+ <span class="default-label">Default value:</span>
367
+ @if (child.type === 'boolean') {
368
+ <select
369
+ class="default-select"
370
+ [value]="child.defaultValue?.toString() || ''"
371
+ (change)="onDefaultValueChange(child, $any($event.target).value)"
372
+ >
373
+ <option value="">No default</option>
374
+ <option value="true">true</option>
375
+ <option value="false">false</option>
376
+ </select>
377
+ } @else {
378
+ <input
379
+ class="default-input"
380
+ [type]="child.type === 'number' ? 'number' : 'text'"
381
+ [value]="child.defaultValue ?? ''"
382
+ (input)="onDefaultValueChange(child, $any($event.target).value)"
383
+ (keydown)="onDefaultValueKeydown($event, child)"
384
+ placeholder="Enter default value"
385
+ />
386
+ }
387
+ @if (child.defaultValue !== undefined) {
388
+ <button class="clear-default-btn" (click)="clearDefaultValue(child)" matTooltip="Clear default">
389
+ <mat-icon>close</mat-icon>
390
+ </button>
391
+ }
392
+ </div>
393
+ </div>
394
+ }
395
+ </div>
396
+ }
397
+ } @else {
398
+ <div class="empty-nested">
399
+ <span>No child fields</span>
400
+ <button mat-button (click)="addChildField(field)" color="primary">
401
+ <mat-icon>add</mat-icon>
402
+ Add field
403
+ </button>
404
+ </div>
405
+ }
406
+ </div>
407
+ }
408
+ </div>
409
+ }
410
+ </div>
411
+ }
412
+ </div>
413
+ </div>
414
+
415
+ <!-- Field Item Template (for nested fields) -->
416
+ <ng-template #fieldItemTemplate let-field="field" let-parentList="parentList" let-level="level">
417
+ <div
418
+ class="field-item"
419
+ [class.is-editing]="field.isEditing"
420
+ [class.is-complex]="field.type === 'object' || field.type === 'array'"
421
+ >
422
+ <!-- Left section: field controls -->
423
+ <div class="field-left">
424
+ <div class="drag-handle" cdkDragHandle matTooltip="Drag to reorder">
425
+ <mat-icon>drag_indicator</mat-icon>
426
+ </div>
427
+
428
+ <div class="indent-buttons">
429
+ <button
430
+ class="indent-btn"
431
+ [disabled]="!canIndent(field, parentList)"
432
+ (click)="indentField(field, parentList)"
433
+ matTooltip="Move into previous object/array"
434
+ >
435
+ <mat-icon>chevron_right</mat-icon>
436
+ </button>
437
+ <button
438
+ class="indent-btn"
439
+ (click)="outdentField(field, parentList, level)"
440
+ matTooltip="Move out of parent"
441
+ >
442
+ <mat-icon>chevron_left</mat-icon>
443
+ </button>
444
+ </div>
445
+
446
+ @if (field.type === 'object' || field.type === 'array') {
447
+ <button class="expand-btn" (click)="toggleExpand(field)" [matTooltip]="field.expanded ? 'Collapse' : 'Expand'">
448
+ <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>
449
+ </button>
450
+ } @else {
451
+ <span class="expand-placeholder"></span>
452
+ }
453
+
454
+ <mat-icon class="type-icon" [matTooltip]="field.type">{{ getTypeIcon(field.type) }}</mat-icon>
455
+
456
+ @if (field.isEditing) {
457
+ <input
458
+ class="field-name-input"
459
+ [value]="field.name"
460
+ (input)="onFieldNameChange(field, $event)"
461
+ (blur)="stopEdit(field)"
462
+ (keydown)="onFieldNameKeydown($event, field)"
463
+ placeholder="Field name"
464
+ autofocus
465
+ />
466
+ } @else {
467
+ <span class="field-name" (dblclick)="startEdit(field)">
468
+ {{ field.name || 'unnamed' }}
469
+ @if (field.type === 'array') {
470
+ <span class="array-indicator">[]</span>
471
+ }
472
+ </span>
473
+ }
474
+
475
+ <button
476
+ class="required-btn"
477
+ [class.is-required]="field.required"
478
+ (click)="toggleRequired(field)"
479
+ [matTooltip]="field.required ? 'Required' : 'Optional'"
480
+ >
481
+ <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>
482
+ </button>
483
+ </div>
484
+
485
+ <!-- Middle section: description (flexible) -->
486
+ <input
487
+ class="description-input"
488
+ [value]="field.description || ''"
489
+ (input)="onDescriptionChange(field, $any($event.target).value)"
490
+ placeholder="Description..."
491
+ />
492
+
493
+ <!-- Right section: type and actions (fixed position) -->
494
+ <div class="field-right">
495
+ <mat-form-field appearance="outline" class="type-selector">
496
+ <mat-select [value]="field.type" (selectionChange)="onFieldTypeChange(field, $event.value)">
497
+ @for (type of fieldTypes; track type.value) {
498
+ <mat-option [value]="type.value">
499
+ <mat-icon>{{ type.icon }}</mat-icon>
500
+ {{ type.label }}
501
+ </mat-option>
502
+ }
503
+ </mat-select>
504
+ </mat-form-field>
505
+
506
+ <div class="field-actions">
507
+ @if (field.type === 'object' || field.type === 'array') {
508
+ <button mat-icon-button (click)="addChildField(field)" matTooltip="Add child field">
509
+ <mat-icon>add_circle_outline</mat-icon>
510
+ </button>
511
+ }
512
+ @if (field.type === 'string' || field.type === 'number') {
513
+ <button
514
+ mat-icon-button
515
+ (click)="toggleValuesEditor(field)"
516
+ [matTooltip]="field.allowedValues?.length ? 'Edit allowed values' : 'Add allowed values'"
517
+ [class.has-values]="field.allowedValues?.length"
518
+ >
519
+ <mat-icon>list</mat-icon>
520
+ </button>
521
+ }
522
+ @if (field.type !== 'object' && field.type !== 'array') {
523
+ <button
524
+ mat-icon-button
525
+ (click)="toggleDefaultEditor(field)"
526
+ [matTooltip]="field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'"
527
+ [class.has-default]="field.defaultValue !== undefined"
528
+ >
529
+ <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>
530
+ </button>
531
+ }
532
+ <button mat-icon-button [matMenuTriggerFor]="nestedMenu" matTooltip="More options">
533
+ <mat-icon>more_vert</mat-icon>
534
+ </button>
535
+ <mat-menu #nestedMenu="matMenu">
536
+ <button mat-menu-item (click)="startEdit(field)">
537
+ <mat-icon>edit</mat-icon>
538
+ <span>Rename</span>
539
+ </button>
540
+ <button mat-menu-item (click)="duplicateField(field, parentList)">
541
+ <mat-icon>content_copy</mat-icon>
542
+ <span>Duplicate</span>
543
+ </button>
544
+ <button mat-menu-item (click)="deleteField(field, parentList)" class="delete-action">
545
+ <mat-icon>delete</mat-icon>
546
+ <span>Delete</span>
547
+ </button>
548
+ </mat-menu>
549
+ </div>
550
+ </div>
551
+ </div>
552
+ </ng-template>