mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
- Added filter input mapping functionality to handle different input types (Text, Checkbox, DateRange). - Created schemas for various filter inputs including BaseFilterInput, CheckboxFilterInput, DateRangeFilterInput, and TextFilterInput. - Developed filter mapping logic to aggregate filter groups, inputs, and order by options. - Implemented unit tests for filter mapping, input mapping, and order by option mapping to ensure correctness. - Introduced a dropdown component for selecting order by options with appropriate styling and functionality.
215 lines
5.2 KiB
TypeScript
215 lines
5.2 KiB
TypeScript
import {
|
|
AfterViewInit,
|
|
ChangeDetectionStrategy,
|
|
Component,
|
|
computed,
|
|
contentChildren,
|
|
effect,
|
|
ElementRef,
|
|
inject,
|
|
input,
|
|
model,
|
|
signal,
|
|
} from '@angular/core';
|
|
|
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
|
import { isaActionChevronUp, isaActionChevronDown } from '@isa/icons';
|
|
import { ActiveDescendantKeyManager, Highlightable } from '@angular/cdk/a11y';
|
|
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
import { isEqual } from 'lodash';
|
|
import { DropdownAppearance } from './dropdown.types';
|
|
|
|
@Component({
|
|
selector: 'ui-dropdown-option',
|
|
template: '<ng-content></ng-content>',
|
|
host: {
|
|
'[class]': '["ui-dropdown-option", activeClass(), selectedClass()]',
|
|
'role': 'option',
|
|
'[attr.aria-selected]': 'selected()',
|
|
'[attr.tabindex]': '-1',
|
|
'(click)': 'select()',
|
|
},
|
|
})
|
|
export class DropdownOptionComponent<T> implements Highlightable {
|
|
private host = inject(DropdownButtonComponent<T>);
|
|
private elementRef = inject(ElementRef);
|
|
|
|
active = signal(false);
|
|
|
|
activeClass = computed(() => (this.active() ? 'active' : ''));
|
|
|
|
setActiveStyles(): void {
|
|
this.active.set(true);
|
|
}
|
|
|
|
setInactiveStyles(): void {
|
|
this.active.set(false);
|
|
}
|
|
|
|
getLabel(): string {
|
|
return this.elementRef.nativeElement.textContent.trim();
|
|
}
|
|
|
|
selected = computed(() => {
|
|
const hostValue = this.host.value();
|
|
const value = this.value();
|
|
return hostValue === value || isEqual(hostValue, value);
|
|
});
|
|
|
|
selectedClass = computed(() => (this.selected() ? 'selected' : ''));
|
|
|
|
value = input.required<T>();
|
|
|
|
select() {
|
|
this.host.select(this);
|
|
this.host.close();
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'ui-dropdown',
|
|
templateUrl: './dropdown.component.html',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
hostDirectives: [CdkOverlayOrigin],
|
|
imports: [NgIconComponent, CdkConnectedOverlay],
|
|
providers: [
|
|
provideIcons({ isaActionChevronUp, isaActionChevronDown }),
|
|
{ provide: NG_VALUE_ACCESSOR, useExisting: DropdownButtonComponent, multi: true },
|
|
],
|
|
host: {
|
|
'[class]': '["ui-dropdown", appearanceClass(), isOpenClass()]',
|
|
'role': 'combobox',
|
|
'aria-haspopup': 'listbox',
|
|
'[attr.id]': 'id()',
|
|
'[attr.tabindex]': 'disabled() ? -1 : tabIndex()',
|
|
'aria-expanded': 'isOpen()',
|
|
'(keydown)': 'keyManger?.onKeydown($event)',
|
|
'(keydown.enter)': 'select(keyManger.activeItem); close()',
|
|
'(keydown.escape)': 'close()',
|
|
'(click)': 'isOpen() ? close() : open()',
|
|
},
|
|
})
|
|
export class DropdownButtonComponent<T> implements ControlValueAccessor, AfterViewInit {
|
|
readonly init = signal(false);
|
|
private elementRef = inject(ElementRef);
|
|
|
|
get overlayMinWidth() {
|
|
return this.elementRef.nativeElement.offsetWidth;
|
|
}
|
|
|
|
appearance = input<DropdownAppearance>(DropdownAppearance.AccentOutline);
|
|
|
|
appearanceClass = computed(() => `ui-dropdown__${this.appearance()}`);
|
|
|
|
id = input<string>();
|
|
|
|
value = model<T>();
|
|
|
|
tabIndex = input<number>(0);
|
|
|
|
label = input<string>();
|
|
|
|
disabled = model<boolean>(false);
|
|
|
|
showSelectedValue = input<boolean>(true);
|
|
|
|
options = contentChildren(DropdownOptionComponent);
|
|
|
|
cdkOverlayOrigin = inject(CdkOverlayOrigin, { self: true });
|
|
|
|
selectedOption = computed(() => {
|
|
const options = this.options();
|
|
if (!options) {
|
|
return undefined;
|
|
}
|
|
|
|
return options.find((option) => option.value() === this.value());
|
|
});
|
|
|
|
private keyManger?: ActiveDescendantKeyManager<DropdownOptionComponent<T>>;
|
|
|
|
onChange?: (value: T) => void;
|
|
|
|
onTouched?: () => void;
|
|
|
|
isOpen = signal(false);
|
|
|
|
isOpenClass = computed(() => (this.isOpen() ? 'open' : ''));
|
|
|
|
isOpenIcon = computed(() => (this.isOpen() ? 'isaActionChevronUp' : 'isaActionChevronDown'));
|
|
|
|
viewLabel = computed(() => {
|
|
if (!this.showSelectedValue()) {
|
|
return this.label() ?? this.value();
|
|
}
|
|
|
|
const selectedOption = this.selectedOption();
|
|
|
|
if (!selectedOption) {
|
|
return this.label() ?? this.value();
|
|
}
|
|
|
|
return selectedOption.getLabel();
|
|
});
|
|
|
|
constructor() {
|
|
effect(() => {
|
|
if (!this.init()) {
|
|
return;
|
|
}
|
|
this.keyManger?.destroy();
|
|
this.keyManger = new ActiveDescendantKeyManager<DropdownOptionComponent<T>>(
|
|
this.options(),
|
|
).withWrap();
|
|
});
|
|
}
|
|
|
|
open() {
|
|
const selected = this.selectedOption();
|
|
if (selected) {
|
|
this.keyManger?.setActiveItem(selected);
|
|
} else {
|
|
this.keyManger?.setFirstItemActive();
|
|
}
|
|
this.isOpen.set(true);
|
|
}
|
|
|
|
close() {
|
|
this.isOpen.set(false);
|
|
}
|
|
|
|
focusout() {
|
|
// this.close();
|
|
}
|
|
|
|
ngAfterViewInit(): void {
|
|
this.init.set(true);
|
|
}
|
|
|
|
writeValue(obj: unknown): void {
|
|
this.value.set(obj as T);
|
|
}
|
|
|
|
registerOnChange(fn: unknown): void {
|
|
this.onChange = fn as (value: T) => void;
|
|
}
|
|
|
|
registerOnTouched(fn: unknown): void {
|
|
this.onTouched = fn as () => void;
|
|
}
|
|
|
|
setDisabledState?(isDisabled: boolean): void {
|
|
this.disabled.set(isDisabled);
|
|
}
|
|
|
|
select(option: DropdownOptionComponent<T>, options: { emit: boolean } = { emit: true }) {
|
|
this.value.set(option.value());
|
|
|
|
if (options.emit) {
|
|
this.onChange?.(option.value());
|
|
}
|
|
this.onTouched?.();
|
|
}
|
|
}
|