Feature: Create Event - First Step Frontend #17
|
@ -1,15 +1,16 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
HostListener,
|
||||
input,
|
||||
InputSignal,
|
||||
output,
|
||||
OutputEmitterRef,
|
||||
signal,
|
||||
WritableSignal,
|
||||
OnInit,
|
||||
HostListener,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
|
@ -39,29 +40,79 @@ import {
|
|||
},
|
||||
],
|
||||
})
|
||||
export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||
export class DropdownComponent
|
||||
implements ControlValueAccessor, Validator, OnInit
|
||||
{
|
||||
// Input properties
|
||||
/**
|
||||
* The label text for the dropdown.
|
||||
*/
|
||||
public label: InputSignal<string> = input.required<string>();
|
||||
/**
|
||||
* The error message to display when the dropdown selection is invalid.
|
||||
*/
|
||||
public errorMessage: InputSignal<string> = input.required<string>();
|
||||
/**
|
||||
* The placeholder text for the dropdown input.
|
||||
*/
|
||||
public placeholder: InputSignal<string> = input.required<string>();
|
||||
/**
|
||||
* The list of items to display in the dropdown.
|
||||
*/
|
||||
public items: InputSignal<string[]> = input.required<string[]>();
|
||||
/**
|
||||
* The text to display when the dropdown list is empty.
|
||||
*/
|
||||
public emptyStateText: InputSignal<string> = input<string>('');
|
||||
/**
|
||||
* The text to display as a divider in the dropdown list.
|
||||
*/
|
||||
public dividerText: InputSignal<string> = input<string>('');
|
||||
/**
|
||||
* The text to display for adding a new item to the dropdown list.
|
||||
*/
|
||||
public newItemText: InputSignal<string> = input<string>('');
|
||||
/**
|
||||
* Whether the dropdown selection is required.
|
||||
*/
|
||||
public required: InputSignal<boolean> = input<boolean>(false);
|
||||
/**
|
||||
* Hint text to display in the top right of the dropdown.
|
||||
*/
|
||||
public hintTopRight: InputSignal<string> = input<string>('');
|
||||
/**
|
||||
* Hint text to display in the bottom left of the dropdown.
|
||||
*/
|
||||
public hintBottomLeft: InputSignal<string> = input<string>('');
|
||||
/**
|
||||
* Hint text to display in the bottom right of the dropdown.
|
||||
*/
|
||||
public hintBottomRight: InputSignal<string> = input<string>('');
|
||||
// Output properties
|
||||
/**
|
||||
* Event emitted when an item is selected from the dropdown.
|
||||
*/
|
||||
public itemSelected: OutputEmitterRef<string> = output<string>();
|
||||
/**
|
||||
* Event emitted when a new item is submitted to be added to the dropdown.
|
||||
*/
|
||||
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
||||
/**
|
||||
* Event emitted when an item is selected for editing.
|
||||
*/
|
||||
public itemEdit: OutputEmitterRef<string> = output<string>();
|
||||
// Internal state
|
||||
public searchTerm: WritableSignal<string> = signal('');
|
||||
public showDropdown: WritableSignal<boolean> = signal<boolean>(false);
|
||||
public filteredItems: WritableSignal<string[]> = signal<string[]>([]);
|
||||
public selectedItem: WritableSignal<string | null> = signal<string | null>(
|
||||
null
|
||||
);
|
||||
public itemSelected: OutputEmitterRef<string> = output<string>();
|
||||
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
||||
public itemEdit: OutputEmitterRef<string> = output<string>();
|
||||
public touched: WritableSignal<boolean> = signal(false);
|
||||
private isMouseInDropdown: WritableSignal<boolean> = signal<boolean>(false);
|
||||
private internalValue: WritableSignal<string | null> = signal<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
public constructor(private readonly elementRef: ElementRef) {}
|
||||
|
||||
|
@ -72,15 +123,13 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
}
|
||||
}
|
||||
|
||||
public writeValue(value: string | null): void {
|
||||
if (value && this.isValidOption(value)) {
|
||||
this.searchTerm.set(value);
|
||||
this.selectedItem.set(value);
|
||||
} else {
|
||||
this.searchTerm.set('');
|
||||
this.selectedItem.set(null);
|
||||
public ngOnInit(): void {
|
||||
this.updateStateFromInternalValue();
|
||||
}
|
||||
this.touched.set(false);
|
||||
|
||||
public writeValue(value: string | null): void {
|
||||
this.internalValue.set(value);
|
||||
this.updateStateFromInternalValue();
|
||||
}
|
||||
|
||||
public registerOnChange(fn: (value: string) => void): void {
|
||||
|
@ -106,19 +155,6 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
return null;
|
||||
}
|
||||
|
||||
public onDropdownMouseEnter(): void {
|
||||
this.isMouseInDropdown.set(true);
|
||||
}
|
||||
|
||||
public onDropdownMouseLeave(): void {
|
||||
this.isMouseInDropdown.set(false);
|
||||
}
|
||||
|
||||
public closeDropdown(): void {
|
||||
this.showDropdown.set(false);
|
||||
this.isMouseInDropdown.set(false);
|
||||
}
|
||||
|
||||
public toggleDropdown(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
@ -127,10 +163,8 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
this.showDropdown.set(newDropdownState);
|
||||
|
||||
if (newDropdownState) {
|
||||
// Wenn das Dropdown geöffnet wird, aktualisieren wir die filteredItems
|
||||
this.updateFilteredItems();
|
||||
} else {
|
||||
// Wenn wir das Dropdown schließen, verhindern wir, dass das Eingabefeld den Fokus erhält
|
||||
(event.target as HTMLElement).blur();
|
||||
}
|
||||
}
|
||||
|
@ -147,32 +181,43 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
|
||||
if (exactMatch) {
|
||||
this.selectedItem.set(exactMatch);
|
||||
this.internalValue.set(exactMatch);
|
||||
this.onChange(exactMatch);
|
||||
this.itemSelected.emit(exactMatch);
|
||||
} else {
|
||||
this.selectedItem.set(null);
|
||||
this.internalValue.set(null);
|
||||
this.onChange('');
|
||||
this.itemSelected.emit('');
|
||||
}
|
||||
|
||||
this.showDropdown.set(true);
|
||||
this.onTouched();
|
||||
this.markAsTouched();
|
||||
}
|
||||
|
||||
public getInputClass(): string {
|
||||
if (this.touched()) {
|
||||
if (this.selectedItem()) {
|
||||
return 'input-success';
|
||||
} else if (
|
||||
this.searchTerm().trim() !== '' &&
|
||||
!this.isValidOption(this.searchTerm())
|
||||
) {
|
||||
return 'input-error';
|
||||
} else if (this.required() && this.searchTerm().trim() === '') {
|
||||
return 'input-error';
|
||||
public selectItem(item: string): void {
|
||||
if (this.isValidOption(item)) {
|
||||
this.searchTerm.set(item);
|
||||
this.selectedItem.set(item);
|
||||
this.internalValue.set(item);
|
||||
this.closeDropdown();
|
||||
this.itemSelected.emit(item);
|
||||
this.onChange(item);
|
||||
this.markAsTouched();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
|
||||
public submitNewItem(): void {
|
||||
this.closeDropdown();
|
||||
this.submitNewItems.emit(true);
|
||||
}
|
||||
|
||||
public onInputClear(): void {
|
||||
this.searchTerm.set('');
|
||||
this.selectedItem.set(null);
|
||||
this.internalValue.set(null);
|
||||
this.onChange('');
|
||||
this.markAsTouched();
|
||||
}
|
||||
|
||||
public markAsTouched(): void {
|
||||
|
@ -185,31 +230,27 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
public editItem(item: string, event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// TODO: Implement edit item functionality
|
||||
this.itemEdit.emit(item);
|
||||
}
|
||||
|
||||
public selectItem(item: string): void {
|
||||
if (this.isValidOption(item)) {
|
||||
this.searchTerm.set(item);
|
||||
this.selectedItem.set(item);
|
||||
this.closeDropdown();
|
||||
this.itemSelected.emit(item);
|
||||
this.onChange(item);
|
||||
this.onTouched();
|
||||
}
|
||||
public getInputClass(): string {
|
||||
if (!this.touched() && !this.hasValidValue()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
public submitNewItem(): void {
|
||||
this.closeDropdown();
|
||||
this.submitNewItems.emit(true);
|
||||
}
|
||||
const validationResult = this.validate({
|
||||
value: this.internalValue(),
|
||||
} as AbstractControl);
|
||||
|
||||
public onInputClear(): void {
|
||||
this.searchTerm.set('');
|
||||
this.selectedItem.set(null);
|
||||
this.onChange('');
|
||||
this.onTouched();
|
||||
if (validationResult === null) {
|
||||
return 'input-success';
|
||||
} else if (
|
||||
validationResult['required'] ||
|
||||
validationResult['invalidOption']
|
||||
) {
|
||||
return 'input-error';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public onFocus(): void {
|
||||
|
@ -227,6 +268,14 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
}, 1);
|
||||
}
|
||||
|
||||
public onDropdownMouseEnter(): void {
|
||||
this.isMouseInDropdown.set(true);
|
||||
}
|
||||
|
||||
public onDropdownMouseLeave(): void {
|
||||
this.isMouseInDropdown.set(false);
|
||||
}
|
||||
|
||||
private isValidOption(value: string): boolean {
|
||||
return this.items().includes(value);
|
||||
}
|
||||
|
@ -240,6 +289,30 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
this.filteredItems.set(matchingItems);
|
||||
}
|
||||
|
||||
private updateStateFromInternalValue(): void {
|
||||
const value = this.internalValue();
|
||||
|
||||
if (value && this.isValidOption(value)) {
|
||||
this.searchTerm.set(value);
|
||||
this.selectedItem.set(value);
|
||||
} else {
|
||||
this.searchTerm.set('');
|
||||
this.selectedItem.set(null);
|
||||
}
|
||||
this.updateFilteredItems();
|
||||
}
|
||||
|
||||
private hasValidValue(): boolean {
|
||||
const value = this.internalValue();
|
||||
|
||||
return value !== null && this.isValidOption(value);
|
||||
}
|
||||
|
||||
private closeDropdown(): void {
|
||||
this.showDropdown.set(false);
|
||||
this.isMouseInDropdown.set(false);
|
||||
}
|
||||
|
||||
private onChange: (value: string) => void = () => {};
|
||||
private onTouched: () => void = () => {};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue