From 002d7a479e45b445c313b4e4b7d2fdef693e6f05 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 8 Mar 2026 09:27:18 +0100 Subject: [PATCH] Fix validation. --- .../forms/editors/autocomplete.component.html | 3 +- .../forms/editors/autocomplete.component.ts | 2 + .../editors/date-time-editor.component.html | 1 + .../editors/date-time-editor.component.ts | 2 + .../forms/editors/dropdown.component.html | 3 +- .../forms/editors/dropdown.component.ts | 2 + .../forms/editors/tag-editor.component.html | 1 + .../forms/editors/tag-editor.component.ts | 2 + .../angular/forms/form-row.component.ts | 11 +++++- .../forms/forward-form-classes.directive.ts | 37 +++++++++++++++++++ .../src/app/shared/state/schemas.forms.ts | 2 +- 11 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 frontend/src/app/framework/angular/forms/forward-form-classes.directive.ts diff --git a/frontend/src/app/framework/angular/forms/editors/autocomplete.component.html b/frontend/src/app/framework/angular/forms/editors/autocomplete.component.html index c514a90d5..6fdf61d71 100644 --- a/frontend/src/app/framework/angular/forms/editors/autocomplete.component.html +++ b/frontend/src/app/framework/angular/forms/editors/autocomplete.component.html @@ -22,7 +22,8 @@ [placeholder]="placeholder" rows="2" sqxAutosize - [sqxFocusOnInit]="autoFocus"> + [sqxFocusOnInit]="autoFocus" + sqxForwardFormClasses> } @else { >; @@ -77,6 +78,7 @@ const RANGE_LIMIT = 60; DropdownMenuComponent, FocusOnInitDirective, FormsModule, + ForwardFormClassesDirective, LoaderComponent, ModalDirective, ModalPlacementDirective, diff --git a/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.html b/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.html index 5c2dac959..73db8c4df 100644 --- a/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.html +++ b/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.html @@ -28,6 +28,7 @@ (ngModelChange)="updateValue($event)" [ngModelOptions]="{ standalone: true }" placeholder="{{ 'common.date' | sqxTranslate }}" + sqxForwardFormClasses [type]="isDateTimeMode ? 'datetime-local' : 'date'" /> diff --git a/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.ts b/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.ts index 1dee0de9b..49f09a690 100644 --- a/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.ts +++ b/frontend/src/app/framework/angular/forms/editors/date-time-editor.component.ts @@ -13,6 +13,7 @@ import { TooltipDirective } from '../../modals/tooltip.directive'; import { TranslatePipe } from '../../pipes/translate.pipe'; import { ResizedDirective } from '../../resized.directive'; import { FocusComponent } from '../forms-helper'; +import { ForwardFormClassesDirective } from '../forward-form-classes.directive'; export const SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateTimeEditorComponent), multi: true, @@ -39,6 +40,7 @@ interface State { changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FormsModule, + ForwardFormClassesDirective, ReactiveFormsModule, ResizedDirective, TooltipDirective, diff --git a/frontend/src/app/framework/angular/forms/editors/dropdown.component.html b/frontend/src/app/framework/angular/forms/editors/dropdown.component.html index 1f24539f9..126349711 100644 --- a/frontend/src/app/framework/angular/forms/editors/dropdown.component.html +++ b/frontend/src/app/framework/angular/forms/editors/dropdown.component.html @@ -10,7 +10,8 @@ [disabled]="snapshot.isDisabled" (keydown)="onKeyDown($event)" [name]="formName" - readonly /> + readonly + sqxForwardFormClasses /> @if (snapshot.selectedItem; as selectedItem) {
diff --git a/frontend/src/app/framework/angular/forms/editors/dropdown.component.ts b/frontend/src/app/framework/angular/forms/editors/dropdown.component.ts index 3eadacfa3..7a67208a5 100644 --- a/frontend/src/app/framework/angular/forms/editors/dropdown.component.ts +++ b/frontend/src/app/framework/angular/forms/editors/dropdown.component.ts @@ -18,6 +18,7 @@ import { TranslatePipe } from '../../pipes/translate.pipe'; import { ScrollActiveDirective } from '../../scroll-active.directive'; import { TemplateWrapperDirective } from '../../template-wrapper.directive'; import { FocusOnInitDirective } from '../focus-on-init.directive'; +import { ForwardFormClassesDirective } from '../forward-form-classes.directive'; export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true, @@ -49,6 +50,7 @@ interface State { DropdownMenuComponent, FocusOnInitDirective, FormsModule, + ForwardFormClassesDirective, LoaderComponent, ModalDirective, ModalPlacementDirective, diff --git a/frontend/src/app/framework/angular/forms/editors/tag-editor.component.html b/frontend/src/app/framework/angular/forms/editors/tag-editor.component.html index 1513b38df..0690c4d62 100644 --- a/frontend/src/app/framework/angular/forms/editors/tag-editor.component.html +++ b/frontend/src/app/framework/angular/forms/editors/tag-editor.component.html @@ -12,6 +12,7 @@ [class.suggested]="itemsSorted.length > 0" (focus)="focusInput($event)" (mousedown)="focusInput($event)" + sqxForwardFormClasses tabindex="0"> @for (tag of snapshot.tags; track tag; let i = $index) { {{ tag }} diff --git a/frontend/src/app/framework/angular/forms/editors/tag-editor.component.ts b/frontend/src/app/framework/angular/forms/editors/tag-editor.component.ts index 96a3edd5b..9434d17de 100644 --- a/frontend/src/app/framework/angular/forms/editors/tag-editor.component.ts +++ b/frontend/src/app/framework/angular/forms/editors/tag-editor.component.ts @@ -18,6 +18,7 @@ import { TooltipDirective } from '../../modals/tooltip.directive'; import { TranslatePipe } from '../../pipes/translate.pipe'; import { ScrollActiveDirective } from '../../scroll-active.directive'; import { StopClickDirective } from '../../stop-click.directive'; +import { ForwardFormClassesDirective } from '../forward-form-classes.directive'; export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagEditorComponent), multi: true, @@ -49,6 +50,7 @@ interface State { DropdownMenuComponent, FormsModule, LoaderComponent, + ForwardFormClassesDirective, ModalDirective, ModalPlacementDirective, ReactiveFormsModule, diff --git a/frontend/src/app/framework/angular/forms/form-row.component.ts b/frontend/src/app/framework/angular/forms/form-row.component.ts index 8fa79e017..6f8929abf 100644 --- a/frontend/src/app/framework/angular/forms/form-row.component.ts +++ b/frontend/src/app/framework/angular/forms/form-row.component.ts @@ -6,8 +6,8 @@ */ -import { booleanAttribute, ChangeDetectionStrategy, Component, HostBinding, Input } from '@angular/core'; -import { AbstractControl } from '@angular/forms'; +import { booleanAttribute, ChangeDetectionStrategy, Component, HostBinding, Input, Optional, SkipSelf } from '@angular/core'; +import { AbstractControl, FormGroupDirective } from '@angular/forms'; import { Types } from '@app/framework/internal'; import { MarkdownDirective } from '../markdown.directive'; import { TranslatePipe } from '../pipes/translate.pipe'; @@ -26,6 +26,13 @@ import { FormHintComponent } from './form-hint.component'; '[class.row]': '!vertical', '[class.form-group-aligned]': 'centered', }, + viewProviders: [ + { + provide: FormGroupDirective, + useFactory: (directive: FormGroupDirective) => directive, + deps: [[new Optional(), new SkipSelf(), FormGroupDirective]], + }, + ], imports: [ ControlErrorsComponent, FormAlertComponent, diff --git a/frontend/src/app/framework/angular/forms/forward-form-classes.directive.ts b/frontend/src/app/framework/angular/forms/forward-form-classes.directive.ts new file mode 100644 index 000000000..06ae1f9bd --- /dev/null +++ b/frontend/src/app/framework/angular/forms/forward-form-classes.directive.ts @@ -0,0 +1,37 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Directive, DoCheck, ElementRef, Injector, OnInit } from "@angular/core"; +import { NgControl } from "@angular/forms"; + +@Directive({ + selector: '[sqxForwardFormClasses]', +}) +export class ForwardFormClassesDirective implements OnInit, DoCheck { + private ngControl: NgControl | null = null; + + constructor( + private readonly injector: Injector, + private readonly host: ElementRef, + ) { + } + + public ngOnInit() { + this.ngControl = this.injector.get(NgControl, null); + } + + public ngDoCheck() { + const classes = this.host.nativeElement.classList; + classes.toggle('ng-touched', !!this.ngControl?.touched); + classes.toggle('ng-untouched', !!this.ngControl?.untouched); + classes.toggle('ng-dirty', !!this.ngControl?.dirty); + classes.toggle('ng-pristine', !!this.ngControl?.pristine); + classes.toggle('ng-valid', !!this.ngControl?.valid); + classes.toggle('ng-invalid', !!this.ngControl?.invalid); + classes.toggle('ng-pending', !!this.ngControl?.pending); + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/state/schemas.forms.ts b/frontend/src/app/shared/state/schemas.forms.ts index 7bf97af26..19feffae7 100644 --- a/frontend/src/app/shared/state/schemas.forms.ts +++ b/frontend/src/app/shared/state/schemas.forms.ts @@ -422,7 +422,7 @@ export class EditSchemaForm extends Form