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