Files
zitadel/console/src/app/modules/form-field/field/form-field.component.ts
T
Max PeintnerGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aa805d18a2 chore(npm): dependencies (#7141)
* chore(deps): bump flag-icons from 6.9.3 to 7.1.0 in /console (#7092)

Bumps [flag-icons](https://github.com/lipis/flag-icons) from 6.9.3 to 7.1.0.
- [Release notes](https://github.com/lipis/flag-icons/releases)
- [Changelog](https://github.com/lipis/flag-icons/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lipis/flag-icons/compare/v6.9.3...v7.1.0)

---
updated-dependencies:
- dependency-name: flag-icons
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump prettier from 3.0.3 to 3.1.1 in /console (#7058)

Bumps [prettier](https://github.com/prettier/prettier) from 3.0.3 to 3.1.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.0.3...3.1.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump uuid and @types/uuid in /console (#6993)

Bumps [uuid](https://github.com/uuidjs/uuid) and [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid). These dependencies needed to be updated together.

Updates `uuid` from 9.0.0 to 9.0.1
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

Updates `@types/uuid` from 9.0.2 to 9.0.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/uuid"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* zonejs

* chore(deps-dev): bump @types/jasmine from 4.3.6 to 5.1.4 in /console (#6991)

Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.3.6 to 5.1.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine)

---
updated-dependencies:
- dependency-name: "@types/jasmine"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump i18n-iso-countries from 7.6.0 to 7.7.0 in /console (#6990)

Bumps [i18n-iso-countries](https://github.com/michaelwittig/node-i18n-iso-countries) from 7.6.0 to 7.7.0.
- [Release notes](https://github.com/michaelwittig/node-i18n-iso-countries/releases)
- [Commits](https://github.com/michaelwittig/node-i18n-iso-countries/compare/v7.6.0...v7.7.0)

---
updated-dependencies:
- dependency-name: i18n-iso-countries
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* jasmine

* prettier plugin

* chore(deps-dev): bump @types/opentype.js from 1.3.4 to 1.3.8 in /console (#6985)

Bumps [@types/opentype.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/opentype.js) from 1.3.4 to 1.3.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/opentype.js)

---
updated-dependencies:
- dependency-name: "@types/opentype.js"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump postcss from 8.4.21 to 8.4.31 in /docs (#6674)

Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump axios and wait-on in /e2e (#7073)

Bumps [axios](https://github.com/axios/axios) to 1.6.2 and updates ancestor dependency [wait-on](https://github.com/jeffbski/wait-on). These dependencies need to be updated together.


Updates `axios` from 0.25.0 to 1.6.2
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.25.0...v1.6.2)

Updates `wait-on` from 6.0.1 to 7.2.0
- [Release notes](https://github.com/jeffbski/wait-on/releases)
- [Commits](https://github.com/jeffbski/wait-on/compare/v6.0.1...v7.2.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
- dependency-name: wait-on
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* lint changes

* lint

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 10:25:21 +00:00

225 lines
7.1 KiB
TypeScript

import {
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChildren,
ElementRef,
HostListener,
Inject,
InjectionToken,
Input,
OnDestroy,
QueryList,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { NgControl } from '@angular/forms';
import { MatFormFieldControl } from '@angular/material/form-field';
import { combineLatest, map, Observable, of, startWith, Subject, takeUntil } from 'rxjs';
import { CnslErrorDirective, CNSL_ERROR } from '../error/error.directive';
import { cnslFormFieldAnimations } from './animations';
export const CNSL_FORM_FIELD = new InjectionToken<CnslFormFieldComponent>('CnslFormFieldComponent');
class CnslFormFieldBase {
constructor(public _elementRef: ElementRef) {}
}
interface Help {
type: 'hints' | 'errors';
validationErrors?: Array<ValidationError>;
}
interface ValidationError {
i18nKey: string;
params: any;
}
@Component({
selector: 'cnsl-form-field',
templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'],
providers: [{ provide: CNSL_FORM_FIELD, useExisting: CnslFormFieldComponent }],
host: {
'[class.ng-untouched]': '_shouldForward("untouched")',
'[class.ng-touched]': '_shouldForward("touched")',
'[class.ng-pristine]': '_shouldForward("pristine")',
'[class.ng-dirty]': '_shouldForward("dirty")',
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
'[class.ng-required]': '_control.required',
'[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused',
'[class.cnsl-form-field-invalid]': '_control.errorState',
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [cnslFormFieldAnimations.transitionMessages],
})
export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestroy, AfterContentInit, AfterViewInit {
focused: boolean = false;
private _destroyed: Subject<void> = new Subject<void>();
@ViewChild('connectionContainer', { static: true }) _connectionContainerRef!: ElementRef;
@ViewChild('inputContainer') _inputContainerRef!: ElementRef;
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
@Input() public disableValidationErrors = false;
@Input() public hideRequiredMarker = false;
get _control(): MatFormFieldControl<any> {
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
}
set _control(value: MatFormFieldControl<any>) {
this._explicitFormFieldControl = value;
}
private _explicitFormFieldControl!: MatFormFieldControl<any>;
readonly stateChanges: Subject<void> = new Subject<void>();
public help$?: Observable<Help>;
_subscriptAnimationState: string = '';
@ContentChildren(CNSL_ERROR as any, { descendants: true }) _errorChildren!: QueryList<CnslErrorDirective>;
// TODO: Remove?
@HostListener('blur', ['false'])
_focusChanged(isFocused: boolean): void {
if (isFocused !== this.focused && !isFocused) {
this.focused = isFocused;
this.stateChanges.next();
}
}
constructor(
public override _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
@Inject(ElementRef)
_labelOptions: // Use `ElementRef` here so Angular has something to inject.
any,
) {
super(_elementRef);
}
public ngAfterViewInit(): void {
this._changeDetectorRef.detectChanges();
}
public ngOnDestroy(): void {
this._destroyed.next();
this._destroyed.complete();
}
public ngAfterContentInit(): void {
this._validateControlChild();
this.mapHelp();
const control = this._control;
control.stateChanges.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
// Run change detection if the value changes.
// TODO: Is that not redundant (see immediately above)?
if (control.ngControl && control.ngControl.valueChanges) {
control.ngControl.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(() => this._changeDetectorRef.markForCheck());
}
}
/** Throws an error if the form field's control is missing. */
protected _validateControlChild(): void {
if (!this._control) {
throw Error('cnsl-form-field must contain a MatFormFieldControl.');
}
}
private _syncDescribedByIds(): void {
if (this._control) {
const ids: string[] = [];
if (this._control.userAriaDescribedBy && typeof this._control.userAriaDescribedBy === 'string') {
ids.push(...this._control.userAriaDescribedBy.split(' '));
}
if (this._errorChildren) {
ids.push(...this._errorChildren.map((error) => error.id));
}
this._control.setDescribedByIds(ids);
}
}
private mapHelp(): void {
const validationErrors$: Observable<Array<ValidationError>> = this.disableValidationErrors
? of([])
: this._control.stateChanges?.pipe(
map(() => this.currentValidationErrors()),
startWith([]),
) || of([]);
const childrenErrors$: Observable<boolean> = this._errorChildren.changes.pipe(
map(() => {
return this._errorChildren.length > 0;
}),
startWith(false),
);
this.help$ = combineLatest([validationErrors$, childrenErrors$]).pipe(
map((combined) => {
return combined[0].length >= 1
? <Help>{
type: 'errors',
validationErrors: combined[0],
}
: combined[1]
? <Help>{
type: 'errors',
}
: <Help>{
type: 'hints',
validationErrors: undefined,
};
}),
);
this._changeDetectorRef.markForCheck();
}
private currentValidationErrors(): Array<ValidationError> {
return (
Object.entries(this._control.ngControl?.control?.errors || [])
?.filter(this.filterErrorsProperties)
.map(this.mapToValidationError)
.filter(this.distinctFilter) || []
);
}
private filterErrorsProperties(kv: [string, any]): boolean {
return typeof kv[1] == 'object' && (kv[1] as { valid: boolean }).valid === false;
}
private mapToValidationError(kv: [string, any]): ValidationError {
return {
i18nKey: 'ERRORS.INVALID_FORMAT',
...(kv[1] as ValidationError | any),
};
}
private distinctFilter(_: ValidationError, index: number, arr: Array<ValidationError>): boolean {
return arr.findIndex((item) => item.i18nKey) === index;
}
/** Determines whether a class from the NgControl should be forwarded to the host element. */
_shouldForward(prop: keyof NgControl): boolean {
const ngControl: any = this._control ? this._control.ngControl : null;
return ngControl && ngControl[prop];
}
}