@gerandon/ngx-widgets 17.0.0 → 18.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +9 -19
  3. package/src/lib/basic-chips/basic-chips.component.html +42 -0
  4. package/src/lib/basic-chips/basic-chips.component.scss +31 -0
  5. package/src/lib/basic-chips/basic-chips.component.ts +83 -0
  6. package/src/lib/basic-input/basic-input.component.html +47 -0
  7. package/src/lib/basic-input/basic-input.component.scss +26 -0
  8. package/src/lib/basic-input/basic-input.component.ts +41 -0
  9. package/src/lib/core/base-input.ts +69 -0
  10. package/src/lib/core/base-text-input.ts +13 -0
  11. package/src/lib/core/base-value-accessor.ts +80 -0
  12. package/src/lib/core/component-unsubscribe.ts +58 -0
  13. package/src/lib/select/select.component.html +39 -0
  14. package/src/lib/select/select.component.scss +7 -0
  15. package/src/lib/select/select.component.ts +70 -0
  16. package/src/lib/textarea-input/textarea-input.component.html +48 -0
  17. package/src/lib/textarea-input/textarea-input.component.scss +27 -0
  18. package/src/lib/textarea-input/textarea-input.component.ts +30 -0
  19. package/{public-api.d.ts → src/public-api.ts} +5 -0
  20. package/tsconfig.lib.json +14 -0
  21. package/tsconfig.lib.prod.json +10 -0
  22. package/tsconfig.spec.json +14 -0
  23. package/esm2022/gerandon-ngx-widgets.mjs +0 -5
  24. package/esm2022/lib/basic-chips/basic-chips.component.mjs +0 -80
  25. package/esm2022/lib/basic-input/basic-input.component.mjs +0 -41
  26. package/esm2022/lib/core/base-input.mjs +0 -81
  27. package/esm2022/lib/core/base-text-input.mjs +0 -20
  28. package/esm2022/lib/core/base-value-accessor.mjs +0 -63
  29. package/esm2022/lib/core/component-unsubscribe.mjs +0 -54
  30. package/esm2022/lib/select/select.component.mjs +0 -61
  31. package/esm2022/lib/textarea-input/textarea-input.component.mjs +0 -39
  32. package/esm2022/public-api.mjs +0 -12
  33. package/fesm2022/gerandon-ngx-widgets.mjs +0 -409
  34. package/fesm2022/gerandon-ngx-widgets.mjs.map +0 -1
  35. package/index.d.ts +0 -5
  36. package/lib/basic-chips/basic-chips.component.d.ts +0 -17
  37. package/lib/basic-input/basic-input.component.d.ts +0 -9
  38. package/lib/core/base-input.d.ts +0 -38
  39. package/lib/core/base-text-input.d.ts +0 -8
  40. package/lib/core/base-value-accessor.d.ts +0 -28
  41. package/lib/core/component-unsubscribe.d.ts +0 -8
  42. package/lib/select/select.component.d.ts +0 -26
  43. package/lib/textarea-input/textarea-input.component.d.ts +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/ngx-widgets",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerandon/ngx-widgets",
3
- "version": "17.0.0",
3
+ "version": "18.0.1",
4
4
  "description": "Angular widget (components) collection using CVA (ControlValueAccessor)",
5
5
  "keywords": [
6
6
  "CVA",
@@ -28,29 +28,19 @@
28
28
  "url": "https://github.com/Gerandon/ngx-widgets"
29
29
  },
30
30
  "peerDependencies": {
31
- "@angular/common": "^17.3.0",
32
- "@angular/core": "^17.3.0",
33
- "@angular/material": "^17.3.6",
31
+ "@angular/common": "^18.0.0",
32
+ "@angular/core": "^18.0.0",
33
+ "@angular/material": "^18.0.0",
34
34
  "lodash-es": "^4.17.21"
35
35
  },
36
36
  "dependencies": {
37
37
  "tslib": "^2.3.0"
38
38
  },
39
+ "devDependencies": {
40
+ "@types/lodash-es": "^4.17.12"
41
+ },
39
42
  "publishConfig": {
40
43
  "access": "public"
41
44
  },
42
- "sideEffects": false,
43
- "module": "fesm2022/gerandon-ngx-widgets.mjs",
44
- "typings": "index.d.ts",
45
- "exports": {
46
- "./package.json": {
47
- "default": "./package.json"
48
- },
49
- ".": {
50
- "types": "./index.d.ts",
51
- "esm2022": "./esm2022/gerandon-ngx-widgets.mjs",
52
- "esm": "./esm2022/gerandon-ngx-widgets.mjs",
53
- "default": "./fesm2022/gerandon-ngx-widgets.mjs"
54
- }
55
- }
56
- }
45
+ "sideEffects": false
46
+ }
@@ -0,0 +1,42 @@
1
+ <mat-form-field appearance="outline" [subscriptSizing]="subscriptSizing" [floatLabel]="floatLabel">
2
+ @if (label) {
3
+ <mat-label [class.disabled]="isDisabled">{{ label }}</mat-label>
4
+ }
5
+ <mat-chip-grid #chipGrid class="w-100">
6
+ @for(item of control.value; track item) {
7
+ <mat-chip-row (removed)="remove(item)" color="primary" highlighted>
8
+ {{ labelProperty ? item[labelProperty] : item}}
9
+ <button matChipRemove [attr.aria-label]="(labelProperty ? item[labelProperty] : item) + ' eltávolítása'">
10
+ <mat-icon>cancel</mat-icon>
11
+ </button>
12
+ </mat-chip-row>
13
+ }
14
+ <input #inputElement
15
+ matInput
16
+ [placeholder]="placeholder || label"
17
+ [matAutocomplete]="auto"
18
+ [matChipInputFor]="chipGrid"
19
+ [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
20
+ (matChipInputTokenEnd)="!labelProperty && add($event)"/>
21
+ <mat-autocomplete #auto="matAutocomplete"
22
+ (optionSelected)="selected($event)">
23
+ @for (filterItem of asyncOptions | async; track filterItem) {
24
+ <mat-option [value]="filterItem">
25
+ {{labelProperty ? filterItem[labelProperty] : filterItem}}
26
+ </mat-option>
27
+ }
28
+ </mat-autocomplete>
29
+ </mat-chip-grid>
30
+ <input #input="ngForm" [style.display]="'none'" [formControl]="control" />
31
+ @if (control.errors?.['server']) {
32
+ <mat-error>{{ control.errors?.['server'] }}</mat-error>
33
+ } @else if (control.errors?.['required']) {
34
+ @if (validationTranslations.required) {
35
+ <mat-error>{{ validationTranslations.required }}</mat-error>
36
+ } @else {
37
+ @for (error of validatorMessagesArray; track error) {
38
+ <mat-error>{{ error.value }}</mat-error>
39
+ }
40
+ }
41
+ }
42
+ </mat-form-field>
@@ -0,0 +1,31 @@
1
+ gerandon-basic-chips {
2
+ display: block;
3
+ .mat-mdc-standard-chip {
4
+ height: 28px !important;
5
+ }
6
+ mat-form-field {
7
+ width: 100%;
8
+ .mat-mdc-text-field-wrapper {
9
+ .mat-mdc-form-field-infix {
10
+ display: flex;
11
+ align-items: center;
12
+ min-height: 40px !important;
13
+ padding: unset !important;
14
+ }
15
+ .mat-mdc-floating-label {
16
+ &:not(.mdc-floating-label--float-above) {
17
+ top: 1.2rem !important;
18
+ }
19
+ }
20
+ }
21
+ mat-chip-row {
22
+ margin-top: 8px !important;
23
+ margin-bottom: 8px !important;
24
+ }
25
+ .mat-mdc-standard-chip .mdc-evolution-chip__cell--primary,
26
+ .mat-mdc-standard-chip .mdc-evolution-chip__action--primary,
27
+ .mat-mdc-standard-chip .mat-mdc-chip-action-label {
28
+ overflow: hidden !important;
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,83 @@
1
+ import { COMMA, ENTER } from '@angular/cdk/keycodes';
2
+ import { AsyncPipe, JsonPipe } from '@angular/common';
3
+ import { Component, forwardRef, Input, ViewEncapsulation } from '@angular/core';
4
+ import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
5
+ import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
6
+ import { MatChipInputEvent, MatChipsModule } from '@angular/material/chips';
7
+ import { MatIconModule } from '@angular/material/icon';
8
+
9
+ import { Observable } from 'rxjs';
10
+ import {BaseInput} from "../core/base-input";
11
+ import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
12
+ import {MatInput} from "@angular/material/input";
13
+
14
+ @Component({
15
+ selector: 'gerandon-basic-chips',
16
+ templateUrl: 'basic-chips.component.html',
17
+ styleUrls: ['basic-chips.component.scss'],
18
+ standalone: true,
19
+ encapsulation: ViewEncapsulation.None,
20
+ providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BasicChipsComponent), multi: true }],
21
+ imports: [
22
+ MatChipsModule,
23
+ MatIconModule,
24
+ ReactiveFormsModule,
25
+ MatAutocompleteModule,
26
+ AsyncPipe,
27
+ JsonPipe,
28
+ MatFormField,
29
+ MatInput,
30
+ MatLabel,
31
+ MatError,
32
+ ],
33
+ })
34
+ export class BasicChipsComponent<T> extends BaseInput<T[]> {
35
+
36
+ @Input() public asyncOptions?: Observable<T[]>;
37
+ @Input() public labelProperty?: keyof T;
38
+ public readonly separatorKeysCodes = [ENTER, COMMA] as const;
39
+
40
+ remove(item: T) {
41
+ const values: T[] = this.control.value;
42
+ const index = values.indexOf(item);
43
+ if (index >= 0) {
44
+ values.splice(index, 1);
45
+ this.control.setValue(values);
46
+ }
47
+
48
+ this.mark();
49
+ }
50
+
51
+ add(event: MatChipInputEvent): void {
52
+ const value = (event.value || '').trim();
53
+ if (value) {
54
+ this.updateValue(value as T);
55
+ }
56
+ event.chipInput!.clear();
57
+
58
+ this.mark();
59
+ }
60
+
61
+ selected(event: MatAutocompleteSelectedEvent): void {
62
+ if (!this.control.value?.includes(event.option.value)) {
63
+ this.updateValue(<T>event.option.value);
64
+ }
65
+ this.inputElement.nativeElement.value = '';
66
+
67
+ this.mark();
68
+ }
69
+
70
+ private updateValue(value: T) {
71
+ this.control.setValue([
72
+ ...(this.control.value || []),
73
+ value,
74
+ ]);
75
+ }
76
+
77
+ private mark() {
78
+ if (!this.control.touched) {
79
+ this.control.markAsTouched();
80
+ this.control.markAsDirty();
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,47 @@
1
+ <div class="basic-input cva-input">
2
+ <mat-form-field appearance="outline" [subscriptSizing]="subscriptSizing" [hintLabel]="hintLabel" [floatLabel]="floatLabel">
3
+ @if (label) {
4
+ <mat-label [class.disabled]="isDisabled">{{label}}</mat-label>
5
+ }
6
+ <input
7
+ [id]="id"
8
+ #inputElement
9
+ #input="ngForm"
10
+ matInput
11
+ [style.padding-right]="(suffix || prefixIcon) && '35px'"
12
+ [type]="type"
13
+ [attr.disabled]="isDisabled || control.disabled ? '' : null"
14
+ [readonly]="isDisabled"
15
+ [placeholder]="placeholder"
16
+ [formControl]="control"
17
+ [maxLength]="maxLength"
18
+ [name]="name"
19
+ [required]="!!control.errors?.['required']"/>
20
+ @if (prefixIcon) {
21
+ <mat-icon matPrefix color="accent">
22
+ {{prefixIcon}}
23
+ </mat-icon>
24
+ }
25
+ @if (suffixIcon) {
26
+ <mat-icon matSuffix color="accent">
27
+ {{suffixIcon}}
28
+ </mat-icon>
29
+ }
30
+ @if (suffix) {
31
+ <span matSuffix style="margin-right: 10px">{{suffix}}</span>
32
+ }
33
+ @if (control.errors?.['server']) {
34
+ <mat-error>{{ control.errors?.['server'] }}</mat-error>
35
+ } @else if (control.errors?.['required']) {
36
+ @if (validationTranslations.required) {
37
+ <mat-error>{{ validationTranslations.required }}</mat-error>
38
+ } @else {
39
+ @for (error of validatorMessagesArray; track error) {
40
+ @if (control.errors?.[error.key]) {
41
+ <mat-error>{{ error.value }}</mat-error>
42
+ }
43
+ }
44
+ }
45
+ }
46
+ </mat-form-field>
47
+ </div>
@@ -0,0 +1,26 @@
1
+ gerandon-basic-input {
2
+ display: block;
3
+ .basic-input {
4
+ height: inherit;
5
+ .disabled {
6
+ color: #CED4DAFF;
7
+ }
8
+ mat-form-field {
9
+ width: 100%;
10
+ .mat-icon {
11
+ padding: unset;
12
+ margin-left: 10px;
13
+ margin-right: 10px;
14
+ }
15
+ input {
16
+ &::placeholder {
17
+ color: #ADB5BDFF;
18
+ font-style: italic;
19
+ }
20
+ &:disabled {
21
+ cursor: not-allowed;
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ Component,
3
+ EventEmitter,
4
+ forwardRef,
5
+ OnInit,
6
+ Output,
7
+ ViewEncapsulation,
8
+ } from '@angular/core';
9
+ import { NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
10
+ import { MatFormFieldModule } from '@angular/material/form-field';
11
+ import { MatIconModule } from '@angular/material/icon';
12
+ import { MatInputModule } from '@angular/material/input';
13
+
14
+ import { BaseTextInput } from '../core/base-text-input';
15
+
16
+ @Component({
17
+ selector: 'gerandon-basic-input',
18
+ templateUrl: './basic-input.component.html',
19
+ styleUrls: ['./basic-input.component.scss'],
20
+ encapsulation: ViewEncapsulation.None,
21
+ standalone: true,
22
+ imports: [
23
+ ReactiveFormsModule,
24
+ MatIconModule,
25
+ MatFormFieldModule,
26
+ MatInputModule,
27
+ ],
28
+ providers: [
29
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BasicInputComponent), multi: true },
30
+ { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => BasicInputComponent), multi: true },
31
+ ],
32
+ })
33
+ export class BasicInputComponent extends BaseTextInput<string> implements OnInit {
34
+
35
+ @Output() iconClick = new EventEmitter();
36
+
37
+ override ngOnInit() {
38
+ super.ngOnInit();
39
+ this.id = this.id || this.name;
40
+ }
41
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ AfterViewInit,
3
+ Directive, Inject, inject, InjectionToken,
4
+ Input, OnChanges,
5
+ OnInit, Optional, SimpleChanges,
6
+ } from '@angular/core';
7
+ import { FloatLabelType, SubscriptSizing } from '@angular/material/form-field';
8
+
9
+ import { BaseValueAccessor } from './base-value-accessor';
10
+ import { isEmpty, keys } from 'lodash-es';
11
+
12
+ export interface NgxWidgetsValidationErrorTypes {
13
+ required?: string;
14
+ selectGlobalPlaceholder?: string;
15
+ }
16
+ export const NGX_WIDGETS_VALIDATION_TRANSLATIONS = new InjectionToken<NgxWidgetsValidationErrorTypes>('NGX_WIDGETS_VALIDATION_TRANSLATIONS');
17
+
18
+ @Directive()
19
+ export class BaseInput<T> extends BaseValueAccessor<T> implements OnInit, AfterViewInit, OnChanges {
20
+
21
+ @Input() public id!: string;
22
+ @Input() public name!: string;
23
+ @Input() public label!: string;
24
+ @Input() public translateParams?: unknown;
25
+ @Input() public placeholder!: string;
26
+ @Input() public isDisabled? = false;
27
+ @Input() public floatLabel: FloatLabelType = 'auto';
28
+ @Input() public prefixIcon?: string;
29
+ @Input() public suffixIcon?: string;
30
+ @Input() public suffix?: string;
31
+ @Input() public formControlName?: string;
32
+ @Input() public validatorMessages?: { [key: string]: string };
33
+ @Input() public subscriptSizing: SubscriptSizing = 'fixed';
34
+ @Input() public hintLabel = '';
35
+ public validatorMessagesArray: { key: string, value: unknown }[] = [];
36
+
37
+ constructor(@Optional() @Inject(NGX_WIDGETS_VALIDATION_TRANSLATIONS) protected readonly validationTranslations: NgxWidgetsValidationErrorTypes) {
38
+ super();
39
+ }
40
+
41
+ ngOnInit() {
42
+ this.placeholder = this.placeholder === undefined ? this.label : this.placeholder;
43
+ if (!this.name) {
44
+ this.name = this.formControlName!;
45
+ /*
46
+ console.warn(`name attribute is not defined for ${this.formControlName}! Please beware, that using this control multiple
47
+ times with the same control name could result in wrong focus, clicking on the label!`);
48
+ */
49
+ }
50
+ // *ngIf seems like does not re-render component when label is used with dynamic value (e.g.: translate pipe). Strange
51
+ this.label = this.label || ' ';
52
+ }
53
+
54
+ ngOnChanges(changes: SimpleChanges) {
55
+ if (changes['validatorMessages']) {
56
+ if (!isEmpty(this.validatorMessages)) {
57
+ this.validatorMessagesArray = keys(this.validatorMessages).map((key) => ({
58
+ key,
59
+ value: this.validatorMessages![key],
60
+ }));
61
+ }
62
+ }
63
+ }
64
+
65
+ override ngAfterViewInit() {
66
+ super.ngAfterViewInit();
67
+ this.cdr.detectChanges();
68
+ }
69
+ }
@@ -0,0 +1,13 @@
1
+ import {
2
+ Directive,
3
+ Input,
4
+ } from '@angular/core';
5
+
6
+ import { BaseInput } from './base-input';
7
+
8
+ @Directive()
9
+ export class BaseTextInput<T> extends BaseInput<T> {
10
+
11
+ @Input() public type: ('text' | 'password' | 'number' | 'email' | 'tel') = 'text';
12
+ @Input() public maxLength? = 512;
13
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef, Directive,
4
+ ElementRef, inject,
5
+ Injector, Input, OnDestroy, Type,
6
+ ViewChild,
7
+ } from '@angular/core';
8
+ import {
9
+ AbstractControl,
10
+ ControlValueAccessor, FormControl,
11
+ NgControl,
12
+ ValidationErrors,
13
+ Validator, ValidatorFn,
14
+ } from '@angular/forms';
15
+
16
+ import {Observable, of, Subject} from 'rxjs';
17
+
18
+ @Directive()
19
+ export class BaseValueAccessor<T> implements ControlValueAccessor, AfterViewInit, Validator, OnDestroy {
20
+
21
+ @Input() public validator: Observable<ValidationErrors> = of({});
22
+ @ViewChild('inputElement') inputElement!: ElementRef;
23
+ @ViewChild('input') input!: NgControl;
24
+
25
+ public control: FormControl;
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ private onChange = (value: T) => {
29
+ };
30
+ private onTouched = () => {
31
+ };
32
+ private readonly injector: Injector = inject(Injector);
33
+ protected controlDir!: NgControl;
34
+ protected readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef);
35
+ protected _validate: ValidatorFn;
36
+ protected readonly _defaultValidate: ValidatorFn = () => null;
37
+
38
+ protected readonly destroy$ = new Subject<void>();
39
+
40
+ constructor() {
41
+ this._validate = this._defaultValidate;
42
+ // Temporarily, AfterViewInit will handle the correct setting
43
+ this.control = new FormControl();
44
+ }
45
+
46
+ validate(control: AbstractControl): Observable<ValidationErrors> {
47
+ control.setErrors({ ...control.errors, pending: true });
48
+ return this.validator;
49
+ }
50
+
51
+ ngAfterViewInit() {
52
+ this.controlDir = this.injector.get<NgControl>(NgControl as Type<NgControl>);
53
+ this.control = <FormControl>this.controlDir.control;
54
+ // For ng-valid expression changed error workaround purposes
55
+ this.cdr.detectChanges();
56
+ }
57
+
58
+ writeValue(obj: T): void {
59
+ this.valueAccessor?.writeValue(obj);
60
+ }
61
+
62
+ registerOnChange(fn: (value: T) => unknown): void {
63
+ this.onChange = fn;
64
+ this.valueAccessor?.registerOnChange(fn);
65
+ }
66
+
67
+ registerOnTouched(fn: () => unknown) {
68
+ this.onTouched = fn;
69
+ this.valueAccessor?.registerOnTouched(fn);
70
+ }
71
+
72
+ protected get valueAccessor(): ControlValueAccessor | null {
73
+ return this.input ? this.input.valueAccessor : null;
74
+ }
75
+
76
+ ngOnDestroy() {
77
+ this.destroy$.next();
78
+ this.destroy$.complete();
79
+ }
80
+ }
@@ -0,0 +1,58 @@
1
+ import { isDevMode } from '@angular/core';
2
+
3
+ import { Observable, Subject, takeUntil } from 'rxjs';
4
+ import { SafeSubscriber } from 'rxjs/internal/Subscriber';
5
+
6
+ /**
7
+ * Automatically unsubscribe from an Observable when the view is destroyed
8
+ * Tested with checking the "complete" event of a subscribe method
9
+ * @description
10
+ * An Annotation that should be used with an Observable typed variable to handle its subscriptions
11
+ * @author gergo.asztalos
12
+ */
13
+ export function UnsubscribeOnDestroy<ObservableType>(): PropertyDecorator {
14
+ return function (target: any, propertyKey: string | symbol) {
15
+ const ngOnDestroy = target.ngOnDestroy;
16
+
17
+ const secretKey = `_${<string>propertyKey}$`;
18
+ // Probably with function we could use own context
19
+ const destroyKey = (_this: any) =>
20
+ _this.hasOwnProperty('destroy$') ? 'destroy$' : `${_this.constructor.name}_destroy$`;
21
+ Object.defineProperty(target, secretKey, { enumerable: false, writable: true });
22
+ Object.defineProperty(target, propertyKey, {
23
+ configurable: true,
24
+ enumerable: true,
25
+ get: function() {
26
+ return this[secretKey];
27
+ },
28
+ set: function(newValue: Observable<ObservableType> | SafeSubscriber<ObservableType>) {
29
+ if (!this[destroyKey(this)]) {
30
+ this[destroyKey(this)] = new Subject();
31
+ }
32
+ if (newValue instanceof Observable) {
33
+ this[secretKey] = newValue.pipe(
34
+ takeUntil(this[destroyKey(this)]),
35
+ );
36
+ } else {
37
+ this[secretKey] = newValue;
38
+ }
39
+ },
40
+ });
41
+
42
+ target.ngOnDestroy = function () {
43
+ if (this[propertyKey] instanceof SafeSubscriber) {
44
+ this[propertyKey].unsubscribe();
45
+ this[secretKey].unsubscribe();
46
+ } else if (this.hasOwnProperty(destroyKey(this))) {
47
+ this[destroyKey(this)].next();
48
+ this[destroyKey(this)].complete();
49
+ }
50
+ delete this[secretKey];
51
+ if (isDevMode()) {
52
+ // eslint-disable-next-line no-console,max-len
53
+ console.debug(`<UnsubscribeOnDestroy> - Observable/Subscription <${<string>propertyKey}> completed in class: ${this.constructor.name}`);
54
+ }
55
+ ngOnDestroy && ngOnDestroy.call(this);
56
+ };
57
+ };
58
+ }
@@ -0,0 +1,39 @@
1
+ <mat-form-field appearance="outline" [subscriptSizing]="subscriptSizing" [floatLabel]="floatLabel">
2
+ @if (label) {
3
+ <mat-label>{{ label }}</mat-label>
4
+ }
5
+ <mat-select #inputElement
6
+ #input="ngForm"
7
+ [multiple]="multiple"
8
+ [placeholder]="!floatLabel ? label : placeholder"
9
+ [formControl]="control"
10
+ [id]="id"
11
+ [class.input-disabled]="isDisabled || control.disabled"
12
+ [compareWith]="_isEqual"
13
+ [attr.disabled]="isDisabled || control.disabled ? '' : null">
14
+ @if (emptyOptionLabel) {
15
+ <mat-option (click)="control.reset()">
16
+ {{ emptyOptionLabel }}
17
+ </mat-option>
18
+ }
19
+ @for(option of options; track option) {
20
+ <mat-option [value]="option.value">
21
+ {{ option.label }}
22
+ </mat-option>
23
+ }
24
+ </mat-select>
25
+ @if (suffix) {
26
+ <span matSuffix>{{suffix}}</span>
27
+ }
28
+ @if (control.errors?.['server']) {
29
+ <mat-error>{{ control.errors?.['server'] }}</mat-error>
30
+ } @else if (control.errors?.['required']) {
31
+ @if (validationTranslations.required) {
32
+ <mat-error>{{ validationTranslations.required }}</mat-error>
33
+ } @else {
34
+ @for (error of validatorMessagesArray; track error) {
35
+ <mat-error>{{ error.value }}</mat-error>
36
+ }
37
+ }
38
+ }
39
+ </mat-form-field>
@@ -0,0 +1,7 @@
1
+ gerandon-select {
2
+ mat-form-field {
3
+ width: 100%;
4
+ .mat-mdc-text-field-wrapper {
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ Component,
3
+ ElementRef,
4
+ forwardRef,
5
+ Input,
6
+ OnInit,
7
+ QueryList,
8
+ ViewChildren,
9
+ ViewEncapsulation,
10
+ } from '@angular/core';
11
+ import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
12
+ import { MatInputModule } from '@angular/material/input';
13
+ import { MatSelectModule } from '@angular/material/select';
14
+ import { MatTooltipModule } from '@angular/material/tooltip';
15
+
16
+ import {BaseInput} from '../core/base-input';
17
+ import { isEqual } from 'lodash-es';
18
+ import { Observable } from 'rxjs';
19
+ import { takeUntil } from 'rxjs/operators';
20
+
21
+ export interface SelectOptionType {
22
+ label: string;
23
+ value: string | number | null | unknown;
24
+ }
25
+
26
+ @Component({
27
+ selector: 'gerandon-select',
28
+ templateUrl: './select.component.html',
29
+ styleUrls: ['./select.component.scss'],
30
+ encapsulation: ViewEncapsulation.None,
31
+ standalone: true,
32
+ providers: [
33
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SelectComponent), multi: true }
34
+ ],
35
+ imports: [
36
+ MatInputModule,
37
+ MatSelectModule,
38
+ ReactiveFormsModule,
39
+ MatTooltipModule,
40
+ ],
41
+ })
42
+ export class SelectComponent extends BaseInput<unknown> implements OnInit {
43
+
44
+ /**
45
+ * In this case, an empty option appears that resets the control, to an empty value state
46
+ */
47
+ @Input() public emptyOptionLabel?: string;
48
+ @Input() public multiple?: boolean;
49
+ @Input() public options!: SelectOptionType[];
50
+ @Input() public asyncOptions!: Observable<SelectOptionType[]>;
51
+ @ViewChildren('optionElements') public optionElements!: QueryList<ElementRef>;
52
+
53
+ /**
54
+ * Angular Material - Select component comparsion is only '===', does not work with Array values
55
+ * https://github.com/angular/components/blob/a07c0758a5ec2eb4de1bb822354be08178c66aa4/src/lib/select/select.ts#L242C48-L242C58
56
+ */
57
+ public readonly _isEqual = isEqual;
58
+
59
+ override ngOnInit() {
60
+ this.placeholder = !this.placeholder ? (this.validationTranslations.selectGlobalPlaceholder || this.label) : this.placeholder;
61
+ super.ngOnInit();
62
+ this.id = this.id || this.formControlName || this.name;
63
+ if (this.asyncOptions) {
64
+ this.asyncOptions.pipe(takeUntil(this.destroy$)).subscribe((resp) => {
65
+ this.options = resp;
66
+ this.cdr.detectChanges();
67
+ });
68
+ }
69
+ }
70
+ }