diff --git a/frontend/projects/ui/src/app/components/form-object/form-object.module.ts b/frontend/projects/ui/src/app/components/form-object/form-object.module.ts
index 44715b4f7..d1ad3acee 100644
--- a/frontend/projects/ui/src/app/components/form-object/form-object.module.ts
+++ b/frontend/projects/ui/src/app/components/form-object/form-object.module.ts
@@ -19,6 +19,9 @@ import {
import { FormFileComponent } from './form-object/controls/form-file/form-file.component'
import { FormInputComponent } from './form-object/controls/form-input/form-input.component'
import { FormWarningDirective } from './form-warning.directive'
+import { FormSubformComponent } from './form-object/controls/form-subform/form-subform.component'
+import { FormEnumComponent } from './form-object/controls/form-enum/form-enum.component'
+import { FormValueComponent } from './form-object/controls/form-value/form-value.component'
@NgModule({
declarations: [
@@ -33,6 +36,9 @@ import { FormWarningDirective } from './form-warning.directive'
FormWarningDirective,
FormFileComponent,
FormInputComponent,
+ FormSubformComponent,
+ FormEnumComponent,
+ FormValueComponent,
],
imports: [
CommonModule,
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object.pipes.ts b/frontend/projects/ui/src/app/components/form-object/form-object.pipes.ts
index 97b73a583..e4f3e967d 100644
--- a/frontend/projects/ui/src/app/components/form-object/form-object.pipes.ts
+++ b/frontend/projects/ui/src/app/components/form-object/form-object.pipes.ts
@@ -14,11 +14,14 @@ import { getElementId } from './form-object/form-object.component'
name: 'getError',
})
export class GetErrorPipe implements PipeTransform {
- transform(errors: ValidationErrors, patternDesc?: string): string {
+ transform(
+ errors: ValidationErrors,
+ patternDesc: string = 'Invalid pattern',
+ ): string {
if (errors['required']) {
return 'Required'
} else if (errors['pattern']) {
- return patternDesc || 'Invalid pattern'
+ return patternDesc
} else if (errors['notNumber']) {
return 'Must be a number'
} else if (errors['numberNotInteger']) {
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.html b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.html
new file mode 100644
index 000000000..9aa3116d6
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.html
@@ -0,0 +1,21 @@
+
+
+
+ {{ control.value | toEnumListDisplay : $any(spec.spec) }}
+
+
+
+
+
+
+
+ {{ errors | getError }}
+
+
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.scss b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.scss
new file mode 100644
index 000000000..15611686c
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.scss
@@ -0,0 +1,13 @@
+.label {
+ margin: 16px 0 6px;
+}
+
+.list {
+ white-space: nowrap;
+}
+
+.error-message {
+ margin-top: 2px;
+ font-size: small;
+ color: var(--ion-color-danger);
+}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.ts b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.ts
new file mode 100644
index 000000000..d3829b6c5
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-enum/form-enum.component.ts
@@ -0,0 +1,15 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core'
+import { AbstractControl } from '@angular/forms'
+import { ValueSpecOf } from 'start-sdk/types/config-types'
+
+@Component({
+ selector: 'form-enum',
+ templateUrl: './form-enum.component.html',
+ styleUrls: ['./form-enum.component.scss'],
+})
+export class FormEnumComponent {
+ @Input() spec!: ValueSpecOf<'list'>
+ @Input() control!: AbstractControl
+
+ @Output() edit = new EventEmitter()
+}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-input/form-input.component.ts b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-input/form-input.component.ts
index c48e4aca0..c79cbada2 100644
--- a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-input/form-input.component.ts
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-input/form-input.component.ts
@@ -2,7 +2,6 @@ import { Component, Input, inject, Output, EventEmitter } from '@angular/core'
import { FormControl } from '@angular/forms'
import { ValueSpecOf } from 'start-sdk/types/config-types'
import { THEME } from '@start9labs/shared'
-import { FormObjectComponent } from '../../form-object.component'
@Component({
selector: 'form-input',
@@ -19,6 +18,4 @@ export class FormInputComponent {
unmasked = false
readonly theme$ = inject(THEME)
-
- constructor(readonly form: FormObjectComponent) {}
}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.html b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.html
new file mode 100644
index 000000000..a125e0235
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.scss b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.scss
new file mode 100644
index 000000000..de5ee20dc
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.scss
@@ -0,0 +1,25 @@
+ion-item-divider {
+ cursor: pointer;
+ text-transform: unset;
+ border-bottom: 1px solid
+ var(
+ --ion-item-border-color,
+ var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13)))
+ );
+
+ --padding-top: 18px;
+ --padding-start: 0;
+
+ &.error-border {
+ border-color: var(--ion-color-danger-shade);
+ --border-color: var(--ion-color-danger-shade);
+ }
+}
+
+.icon {
+ transition: transform 0.42s ease-out;
+
+ &_rotated {
+ transform: rotate(180deg);
+ }
+}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.ts b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.ts
new file mode 100644
index 000000000..04b44a353
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-subform/form-subform.component.ts
@@ -0,0 +1,16 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core'
+import { AbstractControl } from '@angular/forms'
+import { ValueSpecOf } from 'start-sdk/types/config-types'
+
+@Component({
+ selector: 'form-subform',
+ templateUrl: './form-subform.component.html',
+ styleUrls: ['./form-subform.component.scss'],
+})
+export class FormSubformComponent {
+ @Input() spec!: ValueSpecOf<'object'>
+ @Input() control!: AbstractControl
+ @Input() hasNewOptions = false
+
+ expanded = false
+}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.html b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.html
new file mode 100644
index 000000000..99c3834e3
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+ {{ spec['value-names'][option] }}
+
+
+
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.ts b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.ts
new file mode 100644
index 000000000..66b99181a
--- /dev/null
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/controls/form-value/form-value.component.ts
@@ -0,0 +1,15 @@
+import { Component, Input } from '@angular/core'
+import { FormControl } from '@angular/forms'
+import { ValueSpecOf } from 'start-sdk/types/config-types'
+
+@Component({
+ selector: 'form-value',
+ templateUrl: './form-value.component.html',
+})
+export class FormValueComponent {
+ @Input() spec!: ValueSpecOf<'boolean' | 'enum'>
+ @Input() control!: FormControl
+ @Input() name!: string
+
+ cancel = () => this.control.setValue(!this.control.value)
+}
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.html b/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.html
index 26406bbda..3c5b626a2 100644
--- a/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.html
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.html
@@ -1,6 +1,6 @@
-
-
+
+
-
-
-
-
-
-
-
-
- {{ spec['value-names'][option] }}
-
-
-
+ [spec]="spec"
+ [name]="entry.key"
+ [control]="$any(entry.value)"
+ >
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
- {{ formArr.value | toEnumListDisplay : $any(spec.spec) }}
-
-
-
-
-
-
-
- {{ errors | getError }}
-
-
-
-
-
-
+
+
+
diff --git a/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.ts b/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.ts
index 3e2cb815f..e5ed4e402 100644
--- a/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.ts
+++ b/frontend/projects/ui/src/app/components/form-object/form-object/form-object.component.ts
@@ -39,7 +39,6 @@ export class FormObjectComponent {
@Output() onInputChange = new EventEmitter()
@Output() hasNewOptions = new EventEmitter()
warningAck: { [key: string]: boolean } = {}
- unmasked: { [key: string]: boolean } = {}
objectDisplay: {
[key: string]: { expanded: boolean; hasNewOptions: boolean }
} = {}