ng-form-foundry 0.0.1

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 (36) hide show
  1. package/README.md +63 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +12 -0
  4. package/src/lib/core/dynamic-recursive-forms-builder.ts +129 -0
  5. package/src/lib/core/utils.ts +33 -0
  6. package/src/lib/dynamic-recursive-form/anon-leaf-renderer/anon-leaf-renderer.component.html +31 -0
  7. package/src/lib/dynamic-recursive-form/anon-leaf-renderer/anon-leaf-renderer.component.scss +11 -0
  8. package/src/lib/dynamic-recursive-form/anon-leaf-renderer/anon-leaf-renderer.component.spec.ts +22 -0
  9. package/src/lib/dynamic-recursive-form/anon-leaf-renderer/anon-leaf-renderer.component.ts +39 -0
  10. package/src/lib/dynamic-recursive-form/dynamic-recursive-form.component.html +168 -0
  11. package/src/lib/dynamic-recursive-form/dynamic-recursive-form.component.scss +128 -0
  12. package/src/lib/dynamic-recursive-form/dynamic-recursive-form.component.spec.ts +23 -0
  13. package/src/lib/dynamic-recursive-form/dynamic-recursive-form.component.ts +71 -0
  14. package/src/lib/dynamic-recursive-form/dynamic-recursive-form.stories.ts +36 -0
  15. package/src/lib/dynamic-recursive-form/leaf-enum-renderer/leaf-enum-renderer.component.html +8 -0
  16. package/src/lib/dynamic-recursive-form/leaf-enum-renderer/leaf-enum-renderer.component.scss +9 -0
  17. package/src/lib/dynamic-recursive-form/leaf-enum-renderer/leaf-enum-renderer.component.spec.ts +22 -0
  18. package/src/lib/dynamic-recursive-form/leaf-enum-renderer/leaf-enum-renderer.component.ts +21 -0
  19. package/src/lib/dynamic-recursive-form/leaf-list-renderer/leaf-list-renderer.component.html +31 -0
  20. package/src/lib/dynamic-recursive-form/leaf-list-renderer/leaf-list-renderer.component.scss +57 -0
  21. package/src/lib/dynamic-recursive-form/leaf-list-renderer/leaf-list-renderer.component.spec.ts +23 -0
  22. package/src/lib/dynamic-recursive-form/leaf-list-renderer/leaf-list-renderer.component.ts +52 -0
  23. package/src/lib/dynamic-recursive-form/leaf-renderer/leaf-renderer.component.html +17 -0
  24. package/src/lib/dynamic-recursive-form/leaf-renderer/leaf-renderer.component.scss +11 -0
  25. package/src/lib/dynamic-recursive-form/leaf-renderer/leaf-renderer.component.spec.ts +22 -0
  26. package/src/lib/dynamic-recursive-form/leaf-renderer/leaf-renderer.component.ts +41 -0
  27. package/src/lib/dynamic-recursive-form/node-group-list-renderer/node-group-list-renderer.component.html +24 -0
  28. package/src/lib/dynamic-recursive-form/node-group-list-renderer/node-group-list-renderer.component.scss +48 -0
  29. package/src/lib/dynamic-recursive-form/node-group-list-renderer/node-group-list-renderer.component.spec.ts +22 -0
  30. package/src/lib/dynamic-recursive-form/node-group-list-renderer/node-group-list-renderer.component.ts +101 -0
  31. package/src/lib/types/dynamic-recursive.types.ts +97 -0
  32. package/src/lib/types/examples/complex-oai.ts +938 -0
  33. package/src/public-api.ts +12 -0
  34. package/tsconfig.lib.json +18 -0
  35. package/tsconfig.lib.prod.json +11 -0
  36. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,71 @@
1
+ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
2
+ import { LeafRendererComponent } from './leaf-renderer/leaf-renderer.component';
3
+ import { NodeGroup, NodeType } from '../types/dynamic-recursive.types';
4
+ import {
5
+ FormArray,
6
+ FormControl,
7
+ FormGroup,
8
+ ReactiveFormsModule,
9
+ } from '@angular/forms';
10
+ import { NodeGroupListRendererComponent } from './node-group-list-renderer/node-group-list-renderer.component';
11
+ import { LeafListRendererComponent } from './leaf-list-renderer/leaf-list-renderer.component';
12
+ import { MatExpansionModule } from '@angular/material/expansion';
13
+ import { MatIconModule } from '@angular/material/icon';
14
+ import { MatButtonModule } from '@angular/material/button';
15
+ import { MatCardModule } from '@angular/material/card';
16
+ import { NgTemplateOutlet } from '@angular/common';
17
+ import { asFormArray, asFormControl, asFormGroup } from '../core/utils';
18
+
19
+ @Component({
20
+ imports: [
21
+ LeafRendererComponent,
22
+ NodeGroupListRendererComponent,
23
+ LeafListRendererComponent,
24
+ ReactiveFormsModule,
25
+ MatExpansionModule,
26
+ MatIconModule,
27
+ MatButtonModule,
28
+ NgTemplateOutlet,
29
+ MatCardModule,
30
+ MatExpansionModule,
31
+ ],
32
+ selector: 'nff-dynamic-recursive-form',
33
+ standalone: true,
34
+ styleUrl: './dynamic-recursive-form.component.scss',
35
+ templateUrl: './dynamic-recursive-form.component.html',
36
+ })
37
+ export class DynamicRecursiveFormComponent implements OnInit {
38
+ @Input({ required: true }) schema!: NodeGroup;
39
+ @Input() initialValue!: any;
40
+ @Input() formGroup = new FormGroup<any>({});
41
+ @Input() index: number | null = null;
42
+ @Input() removable: boolean = false;
43
+ @Output() remove = new EventEmitter();
44
+ @Input() title!: string;
45
+ @Input() editable = false;
46
+ root: boolean = false;
47
+
48
+ ngOnInit() {
49
+ this.root = this.schema.root ?? false;
50
+ if (this.initialValue) {
51
+ this.formGroup.patchValue(this.initialValue);
52
+ console.log(this.formGroup.value);
53
+ }
54
+ }
55
+
56
+ get nodeGroupChildrenList(): Array<{ key: string; value: NodeType }> {
57
+ const children = this.schema.children ?? {};
58
+ return Object.entries(children).map(([key, value]) => ({
59
+ key,
60
+ value: value as NodeType,
61
+ }));
62
+ }
63
+
64
+ emitRemoveEvent() {
65
+ this.remove.emit();
66
+ }
67
+
68
+ protected readonly asFormGroup = asFormGroup;
69
+ protected readonly asFormArray = asFormArray;
70
+ protected readonly asFormControl = asFormControl;
71
+ }
@@ -0,0 +1,36 @@
1
+ import { Meta, StoryObj } from '@storybook/angular';
2
+ import { DynamicRecursiveFormComponent } from './dynamic-recursive-form.component';
3
+ import { buildFormFromSchema } from '../core/dynamic-recursive-forms-builder';
4
+ import { DUConfigSchema, sampleValue } from '../types/examples/complex-oai'
5
+
6
+ const meta: Meta<DynamicRecursiveFormComponent> = {
7
+ title: 'ng-form-foundry/DynamicRecursiveFormComponent',
8
+ component:DynamicRecursiveFormComponent,
9
+ };
10
+ export default meta;
11
+
12
+ type Story = StoryObj<DynamicRecursiveFormComponent>;
13
+
14
+ export const complex: Story = ({
15
+ name: "Complex Form",
16
+ args: {
17
+ schema: DUConfigSchema,
18
+ initialValue: sampleValue,
19
+ },
20
+ render: (args) => {
21
+ const formGroup = buildFormFromSchema(args.schema);
22
+ return {
23
+ props: {
24
+ ...args,
25
+ formGroup,
26
+ },
27
+ template: `
28
+ <nff-dynamic-recursive-form
29
+ [schema]="DUConfigSchema"
30
+ [formGroup]="formGroup"
31
+ [initialValue]="sampleValue"
32
+ ></nff-dynamic-recursive-form>
33
+ `,
34
+ }
35
+ }
36
+ });
@@ -0,0 +1,8 @@
1
+ <mat-form-field [appearance]="editable ? 'fill' : 'outline'">
2
+ <mat-label>{{ leafEnum.label ?? leafEnum.name }}</mat-label>
3
+ <mat-select [formControl]="control" value="{{ control.value }}">
4
+ @for (option of leafEnum.enum; track $index) {
5
+ <mat-option [value]="option">{{ leafEnum.enumLabel?.[$index] ?? option }}</mat-option>
6
+ }
7
+ </mat-select>
8
+ </mat-form-field>
@@ -0,0 +1,9 @@
1
+ :host {
2
+ flex: 1;
3
+ min-width: 0;
4
+ width: 100%;
5
+ }
6
+
7
+ mat-form-field {
8
+ width: 100%;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { LeafEnumRendererComponent } from './leaf-enum-renderer.component';
4
+
5
+ describe('DrfLeafEnumRendererComponent', () => {
6
+ let component: LeafEnumRendererComponent;
7
+ let fixture: ComponentFixture<LeafEnumRendererComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [LeafEnumRendererComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(LeafEnumRendererComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,21 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { LeafEnum } from '../../types/dynamic-recursive.types';
3
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
4
+ import { MatFormFieldModule } from '@angular/material/form-field';
5
+ import { MatSelectModule } from '@angular/material/select';
6
+
7
+ @Component({
8
+ selector: 'nff-leaf-enum-renderer',
9
+ standalone: true,
10
+ imports: [ReactiveFormsModule, MatFormFieldModule, MatSelectModule],
11
+ templateUrl: './leaf-enum-renderer.component.html',
12
+ styleUrl: './leaf-enum-renderer.component.scss',
13
+ })
14
+ export class LeafEnumRendererComponent {
15
+ @Input() leafEnum!: LeafEnum;
16
+ @Input() initialValue!: string;
17
+ @Input() control = new FormControl();
18
+ @Input() removable: boolean = false;
19
+ @Input() remove: any;
20
+ @Input() editable: boolean = true;
21
+ }
@@ -0,0 +1,31 @@
1
+ @if (formArray && leaf_) {
2
+ <div class="fields">
3
+ @for (control of formArray.controls; track $index) {
4
+ <div class="field-item">
5
+ <nff-anon-leaf-renderer
6
+ [AnonLeaf]="leaf_" [control]="control"
7
+ [removable]="minItems == null || formArray.length > minItems"
8
+ [index]="$index"
9
+ [editable]="editable"
10
+ [label]="leaf_.label ?? leaf_.name"
11
+ (remove)="removeItem($index)"
12
+ />
13
+ <div class="actions">
14
+ @if (formArray.length > minItems && editable) {
15
+ <button class="remove-button small-icon-button" matIconButton (click)="removeItem($index)">
16
+ <mat-icon matPrefix>delete</mat-icon>
17
+ </button>
18
+ }
19
+ @if ($last) {
20
+ @if (editable) {
21
+ <button class="add-button small-icon-button" matIconButton (click)="addItem()">
22
+ <mat-icon matPrefix>add</mat-icon>
23
+ </button>
24
+ }
25
+ }
26
+ <div class="actions-spacer"></div>
27
+ </div>
28
+ </div>
29
+ }
30
+ </div>
31
+ }
@@ -0,0 +1,57 @@
1
+ @use '@angular/material' as mat;
2
+
3
+ :host {
4
+ display: flex;
5
+ flex-direction: column;
6
+ flex-wrap: wrap;
7
+ }
8
+
9
+ .fields-container {
10
+ position: relative;
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ gap: 8px;
14
+ align-items: baseline;
15
+ }
16
+
17
+ .field-item {
18
+ display: flex;
19
+ flex-direction: row;
20
+ gap: 8px;
21
+ align-items: center;
22
+ }
23
+
24
+ .actions {
25
+ z-index: 100;
26
+ display: flex;
27
+ flex-direction: column; /* stack buttons vertically */
28
+ align-items: flex-end;
29
+ gap: 4px;
30
+ height: 100%;
31
+ }
32
+
33
+ .actions-spacer {
34
+ height: 20px;
35
+ }
36
+
37
+ nff-anon-leaf-renderer {
38
+ width: 100%;
39
+ }
40
+
41
+ .add-button {
42
+ background-color: var(--mat-sys-primary-container);
43
+ @include mat.elevation(1);
44
+ }
45
+
46
+ .remove-button {
47
+ background-color: var(--mat-sys-error-container);
48
+ @include mat.elevation(1);
49
+ }
50
+
51
+ .title {
52
+ display: flex;
53
+ flex-direction: row;
54
+ gap: 8px;
55
+ align-items: center;
56
+ }
57
+
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { LeafListRendererComponent } from './leaf-list-renderer.component';
4
+
5
+ describe('DrfLeafListRendererComponent', () => {
6
+ let component: LeafListRendererComponent;
7
+ let fixture: ComponentFixture<LeafListRendererComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [LeafListRendererComponent]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(LeafListRendererComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,52 @@
1
+ import { Component, EventEmitter, inject, Input, OnInit, Output } from '@angular/core';
2
+ import { LeafList } from '../../types/dynamic-recursive.types';
3
+ import { FormControl } from '@angular/forms';
4
+ import { MatIconModule } from '@angular/material/icon';
5
+ import { AnonLeafRendererComponent } from '../anon-leaf-renderer/anon-leaf-renderer.component';
6
+ import { MatButtonModule } from '@angular/material/button';
7
+ import { MatDialog } from '@angular/material/dialog';
8
+ import { MatPrefix } from '@angular/material/input';
9
+
10
+ @Component({
11
+ selector: 'nff-leaf-list-renderer',
12
+ standalone: true,
13
+ imports: [
14
+ MatIconModule,
15
+ AnonLeafRendererComponent,
16
+ MatButtonModule,
17
+ MatPrefix,
18
+ ],
19
+ templateUrl: './leaf-list-renderer.component.html',
20
+ styleUrl: './leaf-list-renderer.component.scss',
21
+ })
22
+ export class LeafListRendererComponent implements OnInit {
23
+ @Input() leaf_!: LeafList;
24
+ @Input() initialValue!: number[] | string[] | boolean[];
25
+ @Input() formArray!: any;
26
+ @Input() editable: boolean = true;
27
+ @Input() minItems: number = 1;
28
+ @Output() message = new EventEmitter();
29
+
30
+ matDialog = inject(MatDialog);
31
+
32
+ ngOnInit() {
33
+ if (this.initialValue) {
34
+ this.formArray.patchValue(this.initialValue);
35
+ }
36
+ }
37
+
38
+ removeItem($index: number) {
39
+ if (this.formArray.length <= this.minItems) {
40
+ this.message.emit({
41
+ message: 'You cannot remove the last item!',
42
+ type: 'error',
43
+ })
44
+ return;
45
+ }
46
+ this.formArray.removeAt($index);
47
+ }
48
+
49
+ addItem() {
50
+ this.formArray.push(new FormControl());
51
+ }
52
+ }
@@ -0,0 +1,17 @@
1
+ @if ('name' in leaf_) {
2
+ @if (leaf_.type === 'string') {
3
+ <mat-form-field [appearance]="editable ? 'fill' : 'outline'">
4
+ <mat-label>{{ leaf_.label ?? leaf_.name }}</mat-label>
5
+ <input [readonly]="!editable" matInput type="text" [formControl]="control">
6
+ </mat-form-field>
7
+ } @else if (leaf_.type === 'number') {
8
+ <mat-form-field [appearance]="editable ? 'fill' : 'outline'">
9
+ <mat-label>{{ leaf_.label ?? leaf_.name }}</mat-label>
10
+ <input [readonly]="!editable" matInput type="number" [formControl]="control">
11
+ </mat-form-field>
12
+ } @else if (leaf_.type === 'boolean') {
13
+ <mat-checkbox [formControl]="control">{{ leaf_.label ?? leaf_.name }}</mat-checkbox>
14
+ } @else if (leaf_.type === 'enum') {
15
+ <nff-leaf-enum-renderer [editable]="editable" [leafEnum]="leaf_" [control]="control"></nff-leaf-enum-renderer>
16
+ }
17
+ }
@@ -0,0 +1,11 @@
1
+ :host {
2
+ display: flex;
3
+ flex: 1 1 0;
4
+ min-width: 20%;
5
+ }
6
+
7
+ mat-form-field {
8
+ flex: 1;
9
+ min-width: 0;
10
+ }
11
+
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { LeafRendererComponent } from './leaf-renderer.component';
4
+
5
+ describe('DrfLeafRendererComponent', () => {
6
+ let component: LeafRendererComponent;
7
+ let fixture: ComponentFixture<LeafRendererComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [LeafRendererComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(LeafRendererComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,41 @@
1
+ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
2
+ import { AnonLeaf, Leaf } from '../../types/dynamic-recursive.types';
3
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
4
+ import { MatFormFieldModule } from '@angular/material/form-field';
5
+ import { MatInputModule } from '@angular/material/input';
6
+ import { MatCheckboxModule } from '@angular/material/checkbox';
7
+ import { MatSelectModule } from '@angular/material/select';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import { MatButtonModule } from '@angular/material/button';
10
+ import { LeafEnumRendererComponent } from '../leaf-enum-renderer/leaf-enum-renderer.component';
11
+
12
+ @Component({
13
+ selector: 'nff-leaf-renderer',
14
+ standalone: true,
15
+ imports: [
16
+ MatFormFieldModule,
17
+ ReactiveFormsModule,
18
+ MatInputModule,
19
+ MatCheckboxModule,
20
+ MatSelectModule,
21
+ MatIconModule,
22
+ MatButtonModule,
23
+ LeafEnumRendererComponent,
24
+ ],
25
+ templateUrl: './leaf-renderer.component.html',
26
+ styleUrl: './leaf-renderer.component.scss',
27
+ })
28
+ export class LeafRendererComponent implements OnInit {
29
+ @Input() leaf_!: Leaf | AnonLeaf;
30
+ @Input() control: FormControl = new FormControl();
31
+ @Input() initialValue?: any;
32
+ @Input() removable: boolean = false;
33
+ @Input() editable = true;
34
+ @Output() remove: EventEmitter<number> = new EventEmitter();
35
+
36
+ ngOnInit(): void {
37
+ if (this.initialValue) {
38
+ this.control.patchValue(this.initialValue);
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,24 @@
1
+ @if (formArray && nodeGroupList) {
2
+ <div class="fields-container">
3
+ @for (group of formArray.controls; track $index) {
4
+ <div class="field-item">
5
+ <nff-dynamic-recursive-form
6
+ [schema]="nodeGroupList.type"
7
+ [formGroup]="asFormGroup(group)"
8
+ [title]="getTitle($index)"
9
+ [index]="$index"
10
+ [removable]="formArray.length > minItems"
11
+ (remove)="removeItem($index)"
12
+ [editable]="editable"
13
+ />
14
+ <div class="actions">
15
+ <button matIconButton class="small-icon-button"
16
+ matTooltip=" + Add new {{ nodeGroupList.type.label ?? nodeGroupList.type.name }}"
17
+ (click)="addItem()">
18
+ <mat-icon>add</mat-icon>
19
+ </button>
20
+ </div>
21
+ </div>
22
+ }
23
+ </div>
24
+ }
@@ -0,0 +1,48 @@
1
+ :host {
2
+ position: relative;
3
+ width: 100%;
4
+ flex: 1 1 0;
5
+ }
6
+
7
+ .fields {
8
+ display: flex;
9
+ flex-direction: column;
10
+ flex-wrap: wrap;
11
+ width: 100%;
12
+ }
13
+
14
+ .fields-container {
15
+ display: flex;
16
+ flex-direction: column;
17
+ }
18
+
19
+ .field-item {
20
+ display: flex;
21
+ flex-direction: row;
22
+ gap: 8px;
23
+ }
24
+
25
+ .actions {
26
+ display: flex;
27
+ flex-direction: row;
28
+ gap: 8px;
29
+ align-items: center;
30
+ margin-top: 0;
31
+ padding: 0;
32
+ min-width: 24px;
33
+ }
34
+
35
+ .title {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 8px;
39
+ align-items: center;
40
+ }
41
+
42
+ nff-dynamic-recursive-form {
43
+ flex: 1;
44
+ }
45
+
46
+ .add-button {
47
+ background-color: var(--mat-sys-primary-container);
48
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { NodeGroupListRendererComponent } from './node-group-list-renderer.component';
4
+
5
+ describe('DrfNodeListRendererComponent', () => {
6
+ let component: NodeGroupListRendererComponent;
7
+ let fixture: ComponentFixture<NodeGroupListRendererComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [NodeGroupListRendererComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(NodeGroupListRendererComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,101 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ EventEmitter,
6
+ forwardRef,
7
+ inject,
8
+ Input,
9
+ OnInit,
10
+ Output,
11
+ QueryList,
12
+ ViewChildren
13
+ } from '@angular/core';
14
+ import { NodeGroupList } from '../../types/dynamic-recursive.types';
15
+ import { FormArray, FormGroup } from '@angular/forms';
16
+ import { MatButtonModule } from '@angular/material/button';
17
+ import { MatIconModule } from '@angular/material/icon';
18
+ import { DynamicRecursiveFormComponent } from '../dynamic-recursive-form.component';
19
+ import { buildFormFromSchema } from '../../core/dynamic-recursive-forms-builder';
20
+ import { MatTooltipModule } from '@angular/material/tooltip';
21
+
22
+ @Component({
23
+ selector: 'nff-node-group-list-renderer',
24
+ standalone: true,
25
+ imports: [
26
+ forwardRef(() => DynamicRecursiveFormComponent),
27
+ MatButtonModule,
28
+ MatIconModule,
29
+ MatTooltipModule,
30
+ ],
31
+ templateUrl: './node-group-list-renderer.component.html',
32
+ styleUrl: './node-group-list-renderer.component.scss',
33
+ })
34
+ export class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
35
+ @Input() nodeGroupList!: NodeGroupList;
36
+ @Input() initialValue!: number[] | string[] | boolean[];
37
+ @Input() formArray = new FormArray<any>([]);
38
+ @Input() editable: boolean = true;
39
+ @Input() minItems: number = 1;
40
+ @Input() maxItems: number = 1;
41
+ @Output() message = new EventEmitter();
42
+ @ViewChildren(DynamicRecursiveFormComponent) items!: QueryList<DynamicRecursiveFormComponent>;
43
+
44
+ cdr = inject(ChangeDetectorRef);
45
+
46
+ ngOnInit() {
47
+ if (this.initialValue) {
48
+ this.formArray.patchValue(this.initialValue);
49
+ }
50
+ if (this.nodeGroupList.maxItems) {
51
+ this.maxItems = this.nodeGroupList.maxItems;
52
+ }
53
+ if (this.nodeGroupList.minItems) {
54
+ this.minItems = this.nodeGroupList.minItems;
55
+ }
56
+ }
57
+
58
+ ngAfterViewInit() {
59
+ this.items.changes.subscribe(() => {
60
+ this.setLastEditable();
61
+ });
62
+ }
63
+
64
+ removeItem($index: number) {
65
+ if (this.formArray.length <= this.minItems) {
66
+ this.message.emit({
67
+ message: `You cannot remove the last ${this.nodeGroupList.type.name} configuration!`,
68
+ type: 'error',
69
+ })
70
+ return;
71
+ }
72
+ this.formArray.removeAt($index);
73
+ }
74
+
75
+ addItem() {
76
+ this.formArray.push(buildFormFromSchema(this.nodeGroupList.type, null));
77
+ }
78
+
79
+ setLastEditable() {
80
+ const lastItem = this.items.last;
81
+ if (lastItem) {
82
+ lastItem.editable = this.editable;
83
+ }
84
+ this.cdr.detectChanges();
85
+ }
86
+
87
+ asFormGroup(group: any) {
88
+ return group as FormGroup;
89
+ }
90
+
91
+ getTitle($index: number) {
92
+ let title = `${this.nodeGroupList.type.label ?? this.nodeGroupList.type.name}`;
93
+ if (this.formArray.length > 1) {
94
+ return `${title} #${$index + 1}`;
95
+ }
96
+ else {
97
+ return title;
98
+ }
99
+
100
+ }
101
+ }