UNPKG

@deepkit/desktop-ui

Version:

Library for desktop UI widgets in Angular 10+

240 lines (206 loc) 6.85 kB
/* * Deepkit Framework * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt * * This program is free software: you can redistribute it and/or modify * it under the terms of the MIT License. * * You should have received a copy of the MIT License along with this program. */ import { AfterViewInit, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, HostListener, Injector, Input, OnChanges, OnDestroy, QueryList, SimpleChanges, SkipSelf, TemplateRef, ViewChild, } from '@angular/core'; import { Subscription } from 'rxjs'; import { ngValueAccessor, ValueAccessorBase } from '../../core/form'; import { Overlay } from '@angular/cdk/overlay'; import { DropdownComponent } from '../button/dropdown.component'; import { ButtonComponent } from '../button/button.component'; /** * Necessary directive to get a dynamic rendered option. * * ```html * <dui-option> * <ng-container *dynamicOption> * {{item.fieldName | date}} * </ng-container> * </dui-option> * ``` */ @Directive({ selector: '[dynamicOption]', standalone: false, }) export class DynamicOptionDirective { constructor( public template: TemplateRef<any> ) { } } @Component({ selector: 'dui-option', standalone: false, template: ` <ng-content></ng-content>` }) export class OptionDirective { @Input() value: any; @Input() disabled: boolean = false; @ContentChild(DynamicOptionDirective, { static: false }) dynamic?: DynamicOptionDirective; constructor(public readonly element: ElementRef) { } } @Directive({ selector: 'dui-option-separator', standalone: false, providers: [{ provide: OptionDirective, useExisting: OptionSeparatorDirective }], }) export class OptionSeparatorDirective { constructor() { } } class NotSelected { } @Component({ selector: 'dui-select', standalone: false, template: ` <ng-container *ngIf="button"> <dui-button-group padding="none"> <ng-content select="dui-button"></ng-content> <dui-button class="split-knob" style="padding: 0 2px;" [openDropdown]="dropdown" tight textured icon="arrow_down"></dui-button> </dui-button-group> </ng-container> <ng-container *ngIf="!button"> <div class="placeholder" *ngIf="!isSelected">{{placeholder}}</div> <div class="value"> <ng-container *ngIf="innerValue !== undefined && optionsValueMap.get(innerValue) as option"> <ng-container *ngIf="option.dynamic" [ngTemplateOutlet]="option.dynamic.template"></ng-container> <div *ngIf="!option.dynamic"> <div [innerHTML]="option.element.nativeElement.innerHTML"></div> </div> </ng-container> </div> <div class="knob"> <dui-icon [size]="12" name="arrows"></dui-icon> </div> </ng-container> <dui-dropdown #dropdown [host]="element.nativeElement"> <ng-container *ngIf="options"> <ng-container *ngFor="let option of options.toArray()"> <dui-dropdown-separator *ngIf="isSeparator(option)"></dui-dropdown-separator> <dui-dropdown-item *ngIf="!isSeparator(option)" (click)="select(option.value)" [selected]="innerValue === option.value" > <ng-container *ngIf="option.dynamic" [ngTemplateOutlet]="option.dynamic.template"></ng-container> <div *ngIf="!option.dynamic"> <div [innerHTML]="option.element.nativeElement.innerHTML"></div> </div> </dui-dropdown-item> </ng-container> </ng-container> </dui-dropdown> `, styleUrls: ['./selectbox.component.scss'], host: { '[attr.tabIndex]': '1', '[class.split]': '!!button', '[class.textured]': 'textured !== false', '[class.small]': 'small !== false', }, providers: [ngValueAccessor(SelectboxComponent)] }) export class SelectboxComponent<T> extends ValueAccessorBase<T | NotSelected> implements AfterViewInit, OnDestroy, OnChanges { @Input() placeholder: string = ''; /** * Different textured styled. */ @Input() textured: boolean | '' = false; /** * Smaller text and height. */ @Input() small: boolean | '' = false; @ContentChild(ButtonComponent, { static: false }) button?: ButtonComponent; @ContentChildren(OptionDirective, { descendants: true }) options?: QueryList<OptionDirective>; @ViewChild('dropdown', { static: false }) dropdown!: DropdownComponent; public label: string = ''; public optionsValueMap = new Map<T | NotSelected, OptionDirective>(); protected changeSubscription?: Subscription; constructor( protected overlay: Overlay, protected injector: Injector, public element: ElementRef, public readonly cd: ChangeDetectorRef, @SkipSelf() public readonly cdParent: ChangeDetectorRef, ) { super(injector, cd, cdParent); this.innerValue = new NotSelected; } isSeparator(item: any): boolean { return item instanceof OptionSeparatorDirective; } ngAfterViewInit(): void { if (this.options) { this.changeSubscription = this.options.changes.subscribe(() => this.updateMap()); setTimeout(() => { this.updateMap(); }); } } public select(value: T) { this.innerValue = value; this.touch(); this.dropdown.close(); } @HostListener('mousedown') public onClick() { if (this.disabled) return; if (this.button) return; this.dropdown.toggle(); } ngOnChanges(changes: SimpleChanges) { } open() { if (this.dropdown) this.dropdown.open(); } writeValue(value?: T | NotSelected): void { super.writeValue(value); } @HostBinding('class.selected') get isSelected(): boolean { return !(this.innerValue instanceof NotSelected); } protected updateMap() { this.optionsValueMap.clear(); if (!this.options) return; for (const option of this.options.toArray()) { this.optionsValueMap.set(option.value, option); } this.cd.detectChanges(); } ngOnDestroy(): void { if (this.changeSubscription) { this.changeSubscription.unsubscribe(); } } }