-
+
Copyright © Pro Angular 2022
diff --git a/src/app/layout/header.component.html b/src/app/layout/header.component.html
index a853c22..cb49f57 100644
--- a/src/app/layout/header.component.html
+++ b/src/app/layout/header.component.html
@@ -1,9 +1,7 @@
-
+
diff --git a/src/app/public/form.directive.ts b/src/app/public/form.directive.ts
index d325025..1892392 100644
--- a/src/app/public/form.directive.ts
+++ b/src/app/public/form.directive.ts
@@ -13,6 +13,8 @@ import { InputTextareaComponent } from './input-textarea/input-textarea.componen
import { InputTimepickerComponent } from './input-timepicker/input-timepicker.component';
import { InputToggleComponent } from './input-toggle/input-toggle.component';
import { InputComponent } from './input/input.component';
+import { InputDropdownComponent } from './input-dropdown/input-dropdown.component';
+import { InputChipsComponent } from './input-chips/input-chips.component';
@Directive({ selector: '[proFormDirective][formGroup]' })
export class FormDirective
@@ -25,10 +27,16 @@ export class FormDirective
@ViewChildren(InputCheckboxComponent)
private readonly inputCheckboxes!: QueryList;
+
+ @ViewChildren(InputChipsComponent)
+ private readonly inputChips!: QueryList;
@ViewChildren(InputDatepickerComponent)
private readonly inputDatepickers!: QueryList;
+ @ViewChildren(InputDropdownComponent)
+ private readonly inputDropdowns!: QueryList>;
+
@ViewChildren(InputRadioComponent)
private readonly inputRadios!: QueryList>;
@@ -44,7 +52,9 @@ export class FormDirective
private get formInputs(): ReadonlyArray<
| InputComponent
| InputCheckboxComponent
+ | InputChipsComponent
| InputDatepickerComponent
+ | InputDropdownComponent
| InputRadioComponent
| InputTextareaComponent
| InputTimepickerComponent
@@ -53,7 +63,9 @@ export class FormDirective
return [
...this.inputs.toArray(),
...this.inputCheckboxes.toArray(),
+ ...this.inputChips.toArray(),
...this.inputDatepickers.toArray(),
+ ...this.inputDropdowns.toArray(),
...this.inputRadios.toArray(),
...this.inputTextareas.toArray(),
...this.inputTimepickers.toArray(),
@@ -96,8 +108,12 @@ export class FormDirective
this.formGroup.reset();
}
- /** Scroll to the first invalid control in the form. */
- public scrollToFirstInvalidControl(): void {
+ /**
+ * Scroll to the first invalid control in the form.
+ *
+ * @returns The label of the first invalid control.
+ */
+ public scrollToFirstInvalidControl(): string | null {
if (this.formInputs.length === 0) {
throw new Error('No form inputs provided to scroll to!');
}
@@ -116,8 +132,10 @@ export class FormDirective
behavior: 'smooth',
block: 'center',
});
- break;
+ return input.label;
}
}
+
+ return null;
}
}
diff --git a/src/app/public/input-chips/input-chip.component.ts b/src/app/public/input-chips/input-chip.component.ts
new file mode 100644
index 0000000..fef77ac
--- /dev/null
+++ b/src/app/public/input-chips/input-chip.component.ts
@@ -0,0 +1,18 @@
+import { coerceElement } from '@angular/cdk/coercion';
+import { Component, ElementRef } from '@angular/core';
+import { MatChip } from '@angular/material/chips';
+
+@Component({
+ selector: 'pro-input-chip',
+ template: ` `,
+ standalone: true,
+})
+export class InputChipComponent extends MatChip {
+ public constructor(private readonly elementRef: ElementRef) {
+ super();
+ }
+
+ public get element(): HTMLElement {
+ return coerceElement(this.elementRef);
+ }
+}
diff --git a/src/app/public/input-chips/input-chips.component.html b/src/app/public/input-chips/input-chips.component.html
new file mode 100644
index 0000000..50481d5
--- /dev/null
+++ b/src/app/public/input-chips/input-chips.component.html
@@ -0,0 +1,24 @@
+{{ label }}
+
+ @for (chip of chips; track chip) {
+ {{
+ chip.element.textContent
+ }}
+ }
+
+
+ @if (formControl.hasError('required')) {
+ {{ label }} is required.
+ } @else if (formControl.hasError('maxlength')) {
+ {{ label }} cannot exceed {{ formControl.errors?.['maxlength'].requiredLength }} selections.
+ } @else if (formControl.hasError('minlength')) {
+ {{ label }} must have at least {{ formControl.errors?.['minlength'].requiredLength }} selections.
+ }
+
\ No newline at end of file
diff --git a/src/app/public/input-dropdown/.gitkeep b/src/app/public/input-chips/input-chips.component.scss
similarity index 100%
rename from src/app/public/input-dropdown/.gitkeep
rename to src/app/public/input-chips/input-chips.component.scss
diff --git a/src/app/public/input-chips/input-chips.component.ts b/src/app/public/input-chips/input-chips.component.ts
new file mode 100644
index 0000000..6eb7912
--- /dev/null
+++ b/src/app/public/input-chips/input-chips.component.ts
@@ -0,0 +1,44 @@
+import { ChangeDetectionStrategy, Component, ContentChildren, Input, QueryList, ViewChild } from '@angular/core';
+import { InputDirective } from '../input.directive';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { MatChipListbox, MatChipsModule } from '@angular/material/chips';
+import { InputChipComponent } from './input-chip.component';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatFormFieldModule } from '@angular/material/form-field';
+
+@Component({
+ selector: 'pro-input-chips',
+ templateUrl: './input-chips.component.html',
+ styleUrl: './input-chips.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ FormsModule,
+ MatChipsModule,
+ MatFormFieldModule,
+ ReactiveFormsModule,
+ ],
+ standalone: true,
+})
+export class InputChipsComponent extends InputDirective {
+ @ContentChildren(InputChipComponent)
+ public readonly chips = new QueryList();
+
+ @ViewChild(MatChipListbox) public readonly matChipListbox?: MatChipListbox;
+
+ @Input() public max: number | undefined;
+
+ @Input({ required: false, transform: coerceBooleanProperty })
+ public multiple: boolean = false;
+
+ @Input({ required: false }) public compareWith: MatChipListbox['compareWith'] = (
+ optionValue,
+ selectedValue,
+ ) => {
+ return (
+ JSON.stringify(optionValue) === JSON.stringify(selectedValue) ||
+ optionValue === selectedValue
+ );
+ };
+}
diff --git a/src/app/public/input-datepicker/input-datepicker.component.html b/src/app/public/input-datepicker/input-datepicker.component.html
index 0ee3696..0715594 100644
--- a/src/app/public/input-datepicker/input-datepicker.component.html
+++ b/src/app/public/input-datepicker/input-datepicker.component.html
@@ -1,29 +1,34 @@
-
- {{ label }}
-
-
- Loading{{ label ? ' ' + label : '' }}...
-
-
-
- {{ hint }}
-
- @if (formControl.hasError('required')) {
- {{ label }} is required.
- } @else if (formControl.hasError('min')) {
- {{ label }} must be at least
- {{ formControl.errors?.['min'].min | dateTime: 'MM/dd/yyyy' }}.
- } @else if (formControl.hasError('max')) {
- {{ label }} cannot exceed
- {{ formControl.errors?.['max'].max | dateTime: 'MM/dd/yyyy' }}.
- }
-
-
+@if (formControl) {
+
+ {{ label }}
+
+
+ Loading{{ label ? ' ' + label : '' }}...
+
+
+
+ {{ hint }}
+
+ @if (formControl.hasError('required')) {
+ {{ label }} is required.
+ } @else if (formControl.hasError('min')) {
+ {{ label }} must be at least
+ {{ formControl.errors?.['min'].min | dateTime: 'MM/dd/yyyy' }}.
+ } @else if (formControl.hasError('max')) {
+ {{ label }} cannot exceed
+ {{ formControl.errors?.['max'].max | dateTime: 'MM/dd/yyyy' }}.
+ }
+
+
+}
+@else {
+
+}
diff --git a/src/app/public/input-datepicker/input-datepicker.component.ts b/src/app/public/input-datepicker/input-datepicker.component.ts
index cb886b6..99694d3 100644
--- a/src/app/public/input-datepicker/input-datepicker.component.ts
+++ b/src/app/public/input-datepicker/input-datepicker.component.ts
@@ -11,22 +11,23 @@ import { MatInputModule } from '@angular/material/input';
import { InputDirective } from '../input.directive';
import { InputAppearance } from '../types';
+import { LoadingInputComponent } from '../utilities/loading-input.component';
@Component({
selector: 'pro-input-datepicker',
templateUrl: './input-datepicker.component.html',
- styleUrls: [],
- standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
DateTimePipe,
+ LoadingInputComponent,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
],
providers: [provideLuxonDateAdapter()],
- changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
})
export class InputDatepickerComponent extends InputDirective {
@Input({ required: false }) public appearance: InputAppearance = 'outline';
diff --git a/src/app/public/input-dropdown/input-dropdown-option-group.component.ts b/src/app/public/input-dropdown/input-dropdown-option-group.component.ts
new file mode 100644
index 0000000..b77ac74
--- /dev/null
+++ b/src/app/public/input-dropdown/input-dropdown-option-group.component.ts
@@ -0,0 +1,16 @@
+import { ChangeDetectionStrategy, Component, ContentChildren, Input, QueryList } from '@angular/core';
+import { InputDropdownOptionComponent } from './input-dropdown-option.component';
+
+@Component({
+ selector: 'pro-input-dropdown-option-group',
+ template: ` `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+})
+export class InputDropdownOptionGroupComponent {
+ @Input({ required: true }) public label!: string;
+
+ @ContentChildren(InputDropdownOptionComponent)
+ public readonly options: QueryList =
+ new QueryList();
+}
diff --git a/src/app/public/input-dropdown/input-dropdown-option.component.ts b/src/app/public/input-dropdown/input-dropdown-option.component.ts
new file mode 100644
index 0000000..aa81e3e
--- /dev/null
+++ b/src/app/public/input-dropdown/input-dropdown-option.component.ts
@@ -0,0 +1,21 @@
+import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core';
+import {
+ coerceElement,
+} from '@angular/cdk/coercion';
+import { MatOption } from '@angular/material/core';
+
+@Component({
+ selector: 'pro-input-dropdown-option',
+ template: ` `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+})
+export class InputDropdownOptionComponent extends MatOption {
+ public constructor(private readonly elementRef: ElementRef) {
+ super();
+ }
+
+ public get element(): HTMLElement {
+ return coerceElement(this.elementRef);
+ }
+}
diff --git a/src/app/public/input-dropdown/input-dropdown.component.html b/src/app/public/input-dropdown/input-dropdown.component.html
new file mode 100644
index 0000000..20f5acb
--- /dev/null
+++ b/src/app/public/input-dropdown/input-dropdown.component.html
@@ -0,0 +1,41 @@
+@if (formControl && (options || optionGroups)) {
+
+ {{ label }}
+
+ @for (option of options; track option) {
+ {{ option.element.textContent }}
+ }
+ @for (optionGroup of optionGroups; track optionGroup) {
+
+ @for (option of optionGroup.options; track option) {
+ {{ option.element.textContent }}
+ }
+
+ }
+
+ {{ hint }}
+
+ @if (formControl.hasError('required')) {
+ {{ label }} is required.
+ } @else if (formControl.hasError('maxlength')) {
+ {{ label }} cannot exceed {{ formControl.errors?.['maxlength'].requiredLength }} selections.
+ } @else if (formControl.hasError('minlength')) {
+ {{ label }} must have at least {{ formControl.errors?.['minlength'].requiredLength }} selections.
+ }
+
+
+}
+@else {
+
+}
diff --git a/src/app/public/input-dropdown/input-dropdown.component.scss b/src/app/public/input-dropdown/input-dropdown.component.scss
new file mode 100644
index 0000000..a6e8bea
--- /dev/null
+++ b/src/app/public/input-dropdown/input-dropdown.component.scss
@@ -0,0 +1,3 @@
+::ng-deep mat-form-field {
+ width: 100%;
+}
diff --git a/src/app/public/input-dropdown/input-dropdown.component.ts b/src/app/public/input-dropdown/input-dropdown.component.ts
new file mode 100644
index 0000000..8beaa4c
--- /dev/null
+++ b/src/app/public/input-dropdown/input-dropdown.component.ts
@@ -0,0 +1,90 @@
+import { ChangeDetectionStrategy, Component, ContentChildren, Input, QueryList, ViewChild } from '@angular/core';
+
+import { InputDirective } from '../input.directive';
+import { InputAppearance } from '../types';
+import { InputDropdownOptionComponent } from './input-dropdown-option.component';
+import { InputDropdownOptionGroupComponent } from './input-dropdown-option-group.component';
+import { MatOption, MatSelect, MatSelectModule } from '@angular/material/select';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { LoadingInputComponent } from '../utilities/loading-input.component';
+import { MatFormFieldModule } from '@angular/material/form-field';
+
+@Component({
+ selector: 'pro-input-dropdown',
+ templateUrl: './input-dropdown.component.html',
+ styleUrl: './input-dropdown.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ FormsModule,
+ LoadingInputComponent,
+ MatFormFieldModule,
+ MatSelectModule,
+ ReactiveFormsModule,
+ ],
+ standalone: true,
+})
+export class InputDropdownComponent extends InputDirective {
+ @ContentChildren(InputDropdownOptionComponent)
+ public readonly options: QueryList =
+ new QueryList();
+
+ @ContentChildren(InputDropdownOptionGroupComponent)
+ public readonly optionGroups: QueryList =
+ new QueryList();
+
+ @ViewChild(MatSelect) public readonly matSelect?: MatSelect;
+
+ @Input({ required: false })
+ public appearance: InputAppearance = 'outline';
+
+ @Input() public max: number | undefined;
+
+ private get exceedsMax(): boolean {
+ return this.max !== undefined && this.selectedOptions.length > this.max;
+ }
+
+ protected get selectedOptions(): readonly InputDropdownOptionComponent[] {
+ if (!this.matSelect) {
+ return [];
+ }
+
+ const allOptions = [
+ ...this.options.toArray(),
+ ...this.optionGroups.toArray().flatMap((group) => group.options.toArray()),
+ ];
+
+ // Single selection
+ if (this.matSelect.selected instanceof MatOption) {
+ const selectedValue = this.matSelect.selected.value;
+ return allOptions.filter((option) => option.value === selectedValue);
+ }
+
+ // Multi-selection
+ if (
+ Array.isArray(this.matSelect.selected) &&
+ this.matSelect.selected.every((s) => s instanceof MatOption)
+ ) {
+ const selectedValues = this.matSelect.selected.map((selected) => (selected as MatOption).value);
+ return allOptions.filter((option) => selectedValues.includes(option.value));
+ }
+
+ // Default: No option is selected.
+ return [];
+ }
+
+ @Input({ required: false, transform: coerceBooleanProperty })
+ public multiple: boolean = false;
+
+ @Input({ required: false }) public compareWith: MatSelect['compareWith'] = (
+ optionValue,
+ selectedValue,
+ ) => {
+ return (
+ JSON.stringify(optionValue) === JSON.stringify(selectedValue) ||
+ optionValue === selectedValue
+ );
+ };
+}
diff --git a/src/app/public/input-radio/input-radio-option.component.ts b/src/app/public/input-radio/input-radio-option.component.ts
index d656f03..16d13c9 100644
--- a/src/app/public/input-radio/input-radio-option.component.ts
+++ b/src/app/public/input-radio/input-radio-option.component.ts
@@ -9,8 +9,8 @@ import {
@Component({
selector: 'pro-input-radio-option',
template: ` `,
- standalone: false,
changeDetection: ChangeDetectionStrategy.Default,
+ standalone: true,
})
export class InputRadioOptionComponent {
public constructor(private readonly elementRef: ElementRef) {}
diff --git a/src/app/public/input-radio/input-radio.component.ts b/src/app/public/input-radio/input-radio.component.ts
index aba5137..3c62ea9 100644
--- a/src/app/public/input-radio/input-radio.component.ts
+++ b/src/app/public/input-radio/input-radio.component.ts
@@ -8,13 +8,23 @@ import {
import { InputDirective } from '../input.directive';
import { InputRadioOptionComponent } from './input-radio-option.component';
+import { CommonModule } from '@angular/common';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatRadioModule } from '@angular/material/radio';
+import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'pro-input-radio',
templateUrl: './input-radio.component.html',
styleUrls: ['./input-radio.component.scss'],
- standalone: false,
changeDetection: ChangeDetectionStrategy.Default,
+ imports: [
+ CommonModule,
+ MatFormFieldModule,
+ MatRadioModule,
+ ReactiveFormsModule,
+ ],
+ standalone: true,
})
export class InputRadioComponent extends InputDirective {
@ContentChildren(InputRadioOptionComponent)
diff --git a/src/app/public/input-radio/input-radio.module.ts b/src/app/public/input-radio/input-radio.module.ts
deleted file mode 100644
index 61a4cfc..0000000
--- a/src/app/public/input-radio/input-radio.module.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommonModule } from '@angular/common';
-import { NgModule } from '@angular/core';
-import { ReactiveFormsModule } from '@angular/forms';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { MatRadioModule } from '@angular/material/radio';
-
-import { InputRadioOptionComponent } from './input-radio-option.component';
-import { InputRadioComponent } from './input-radio.component';
-
-@NgModule({
- declarations: [InputRadioComponent, InputRadioOptionComponent],
- exports: [InputRadioComponent, InputRadioOptionComponent],
- imports: [
- CommonModule,
- MatFormFieldModule,
- MatRadioModule,
- ReactiveFormsModule,
- ],
-})
-export class InputRadioModule {}
diff --git a/src/app/public/input-timepicker/input-timepicker.component.html b/src/app/public/input-timepicker/input-timepicker.component.html
index ec7529d..170176d 100644
--- a/src/app/public/input-timepicker/input-timepicker.component.html
+++ b/src/app/public/input-timepicker/input-timepicker.component.html
@@ -1,29 +1,30 @@
-
- {{ label }}
-
-
- Loading{{ label ? ' ' + label : '' }}...
-
-
-
- {{ hint }}
-
- @if (formControl.hasError('required')) {
- {{ label }} is required.
- } @else if (formControl.hasError('min')) {
- {{ label }} must be at least
- {{ formControl.errors?.['min'].min | dateTime: 'h:mm a (ZZZZ)' }}.
- } @else if (formControl.hasError('max')) {
- {{ label }} cannot exceed
- {{ formControl.errors?.['max'].max | dateTime: 'h:mm a (ZZZZ)' }}.
- }
-
-
+@if (formControl) {
+
+ {{ label }}
+
+
+
+ {{ hint }}
+
+ @if (formControl.hasError('required')) {
+ {{ label }} is required.
+ } @else if (formControl.hasError('min')) {
+ {{ label }} must be at least
+ {{ formControl.errors?.['min'].min | dateTime: 'h:mm a (ZZZZ)' }}.
+ } @else if (formControl.hasError('max')) {
+ {{ label }} cannot exceed
+ {{ formControl.errors?.['max'].max | dateTime: 'h:mm a (ZZZZ)' }}.
+ }
+
+
+}
+@else {
+
+}
diff --git a/src/app/public/input-timepicker/input-timepicker.component.ts b/src/app/public/input-timepicker/input-timepicker.component.ts
index 6413be8..388a731 100644
--- a/src/app/public/input-timepicker/input-timepicker.component.ts
+++ b/src/app/public/input-timepicker/input-timepicker.component.ts
@@ -11,6 +11,7 @@ import { MatTimepickerModule } from '@angular/material/timepicker';
import { InputDirective } from '../input.directive';
import { InputAppearance } from '../types';
+import { LoadingInputComponent } from '../utilities/loading-input.component';
@Component({
selector: 'pro-input-timepicker',
@@ -20,6 +21,7 @@ import { InputAppearance } from '../types';
imports: [
CommonModule,
DateTimePipe,
+ LoadingInputComponent,
MatFormFieldModule,
MatInputModule,
MatTimepickerModule,
diff --git a/src/app/public/input/input.component.html b/src/app/public/input/input.component.html
index ea9fdf5..71331b0 100644
--- a/src/app/public/input/input.component.html
+++ b/src/app/public/input/input.component.html
@@ -1,39 +1,45 @@
-
- {{ label }}
-
-
- Loading{{ label ? ' ' + label : '' }}...
-
- {{ hint }}
-
- @if (formControl.hasError('required')) {
- {{ label }} is required.
- } @else if (formControl.hasError('min')) {
- {{ label }} must be at least {{ formControl.errors?.['min'].min }}.
- } @else if (formControl.hasError('max')) {
- {{ label }} cannot exceed {{ formControl.errors?.['max'].max }}.
- } @else if (formControl.hasError('minlength')) {
- {{ label }} must be at least
- {{ formControl.errors?.['minlength'].requiredLength }} characters.
- } @else if (formControl.hasError('maxlength')) {
- {{ label }} cannot exceed
- {{ formControl.errors?.['maxlength'].requiredLength }} characters.
- } @else if (formControl.hasError('email')) {
- {{ label }} must be a valid email address.
- } @else if (formControl.hasError('nonNumber')) {
- {{ label }} must be a number.
- } @else if (formControl.hasError('wholeNumber')) {
- {{ label }} must be a whole number.
- }
-
-
+@if (formControl) {
+
+ {{ label }}
+
+
+ Loading{{ label ? ' ' + label : '' }}...
+
+ {{ hint }}
+
+ @if (formControl.hasError('required')) {
+ {{ label }} is required.
+ } @else if (formControl.hasError('min')) {
+ {{ label }} must be at least {{ formControl.errors?.['min'].min }}.
+ } @else if (formControl.hasError('max')) {
+ {{ label }} cannot exceed {{ formControl.errors?.['max'].max }}.
+ } @else if (formControl.hasError('minlength')) {
+ {{ label }} must be at least
+ {{ formControl.errors?.['minlength'].requiredLength }} characters.
+ } @else if (formControl.hasError('maxlength')) {
+ {{ label }} cannot exceed
+ {{ formControl.errors?.['maxlength'].requiredLength }} characters.
+ } @else if (formControl.hasError('email')) {
+ {{ label }} must be a valid email address.
+ } @else if (formControl.hasError('nonNumber')) {
+ {{ label }} must be a number.
+ } @else if (formControl.hasError('wholeNumber')) {
+ {{ label }} must be a whole number.
+ }
+
+
+}
+@else {
+
+}
+
diff --git a/src/app/public/input/input.component.ts b/src/app/public/input/input.component.ts
index bc2d5d6..0de6828 100644
--- a/src/app/public/input/input.component.ts
+++ b/src/app/public/input/input.component.ts
@@ -6,14 +6,15 @@ import { MatInputModule } from '@angular/material/input';
import { InputDirective } from '../input.directive';
import { InputAppearance, InputAutocomplete, InputType } from '../types';
+import { LoadingInputComponent } from '../utilities/loading-input.component';
@Component({
selector: 'pro-input',
templateUrl: './input.component.html',
- styleUrls: [],
standalone: true,
imports: [
CommonModule,
+ LoadingInputComponent,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
diff --git a/src/app/public/public.ts b/src/app/public/public.ts
index ca2a9c4..b74a8cb 100644
--- a/src/app/public/public.ts
+++ b/src/app/public/public.ts
@@ -1,9 +1,13 @@
// Components
-export { InputComponent } from './input/input.component';
export { InputCheckboxComponent } from './input-checkbox/input-checkbox.component';
+export { InputChipComponent } from './input-chips/input-chip.component';
+export { InputChipsComponent } from './input-chips/input-chips.component';
+export { InputComponent } from './input/input.component';
export { InputDatepickerComponent } from './input-datepicker/input-datepicker.component';
+export { InputDropdownComponent } from './input-dropdown/input-dropdown.component';
+export { InputDropdownOptionComponent } from './input-dropdown/input-dropdown-option.component';
+export { InputDropdownOptionGroupComponent } from './input-dropdown/input-dropdown-option-group.component';
export { InputRadioComponent } from './input-radio/input-radio.component';
-export { InputRadioModule } from './input-radio/input-radio.module';
export { InputRadioOptionComponent } from './input-radio/input-radio-option.component';
export { InputTextareaComponent } from './input-textarea/input-textarea.component';
export { InputTimepickerComponent } from './input-timepicker/input-timepicker.component';
@@ -25,5 +29,5 @@ export type {
InputAppearance,
InputAutocomplete,
InputType,
- RadioOption,
+ Option,
} from './types';
diff --git a/src/app/public/types/index.ts b/src/app/public/types/index.ts
index 4f3b4e5..c6f20c8 100644
--- a/src/app/public/types/index.ts
+++ b/src/app/public/types/index.ts
@@ -1,3 +1,3 @@
export type { DateTimeFormat } from './date-time-format';
export type { InputAppearance, InputAutocomplete, InputType } from './input';
-export type { RadioOption } from './radio-option';
+export type { Option } from './option';
diff --git a/src/app/public/types/radio-option.ts b/src/app/public/types/option.ts
similarity index 59%
rename from src/app/public/types/radio-option.ts
rename to src/app/public/types/option.ts
index 9915ebc..bf1903f 100644
--- a/src/app/public/types/radio-option.ts
+++ b/src/app/public/types/option.ts
@@ -1,4 +1,4 @@
-export interface RadioOption {
+export interface Option {
label: string;
value: number | string;
}
diff --git a/src/app/public/utilities/loading-input.component.ts b/src/app/public/utilities/loading-input.component.ts
new file mode 100644
index 0000000..bac6856
--- /dev/null
+++ b/src/app/public/utilities/loading-input.component.ts
@@ -0,0 +1,58 @@
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
+import { interval } from 'rxjs';
+import { Component, Input, OnInit } from '@angular/core';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { InputAppearance } from '../types';
+import { CommonModule } from '@angular/common';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+
+@UntilDestroy()
+@Component({
+ selector: 'pro-loading-input',
+ template: `
+
+
+ @if (hint) {
+ {{ hint }}
+ }
+
+ `,
+ styles: [`:host { width: 100%; }`],
+ imports: [
+ CommonModule,
+ MatFormFieldModule,
+ MatInputModule,
+ ReactiveFormsModule,
+ ],
+ standalone: true,
+})
+export class LoadingInputComponent implements OnInit {
+ @Input({ required: false }) public appearance: InputAppearance = 'outline';
+ @Input({ required: false }) public hint?: string;
+ @Input({ required: false }) public label?: string;
+
+ protected readonly formControl = new FormControl(null);
+ protected loadingText = 'Loading';
+
+ public ngOnInit(): void {
+ this.formControl.disable();
+
+ interval(500)
+ .pipe(untilDestroyed(this))
+ .subscribe(() => {
+ if (this.loadingText === 'Loading') {
+ this.loadingText = this.label
+ ? `Loading "${this.label}".`
+ : 'Loading.';
+ } else if (this.loadingText.endsWith('...')) {
+ this.loadingText = this.label
+ ? `Loading "${this.label}"`
+ : 'Loading';
+ } else {
+ this.loadingText = this.loadingText + '.';
+ }
+ this.formControl.setValue(this.loadingText);
+ });
+ }
+}