1/* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16import {Component, EventEmitter, Input, Output} from '@angular/core'; 17import {MatSelect, MatSelectChange} from '@angular/material/select'; 18 19@Component({ 20 selector: 'select-with-filter', 21 template: ` 22 <mat-form-field 23 [style]="getOuterFormFieldStyle()" 24 [style.text-align]="'unset'" 25 [appearance]="appearance" 26 [class]="formFieldClass" 27 [class.mat-body-2]="!select.value || select.value.length === 0"> 28 <mat-label>{{ label }}</mat-label> 29 <mat-select 30 (opened)="filter.focus()" 31 (closed)="onSelectClosed()" 32 (selectionChange)="onSelectChange($event)" 33 [multiple]="true" 34 #select> 35 <mat-form-field class="select-filter" [style]="getInnerFormFieldStyle()"> 36 <mat-label>Filter options</mat-label> 37 <input matInput #filter [(ngModel)]="filterString" /> 38 </mat-form-field> 39 <div *ngIf="(select.value?.length ?? 0) > 0" class="selected-options"> 40 <span class="mat-option mat-active">Selected:</span> 41 <div 42 class="mat-option mat-selected mat-option-multiple mat-active selected-option" 43 *ngFor="let option of selectedOptions(select)" 44 (click)="onSelectedOptionClick(option, select)"> 45 <mat-pseudo-checkbox 46 color="primary" 47 state="checked" 48 class="mat-option-pseudo-checkbox"></mat-pseudo-checkbox> 49 <div class="mat-option-text">{{option}}</div> 50 </div> 51 </div> 52 <mat-divider [vertical]="false"></mat-divider> 53 <mat-option 54 *ngFor="let option of options" 55 [value]="option" 56 class="option no-focus" 57 [class.hidden-option]="hideOption(option)">{{ option }}</mat-option> 58 </mat-select> 59 </mat-form-field> 60 `, 61 styles: [ 62 ` 63 mat-form-field { 64 width: 100%; 65 } 66 67 .hidden-option { 68 display: none; 69 } 70 71 .selected-options { 72 display: flex; 73 flex-direction: column; 74 } 75 `, 76 ], 77}) 78export class SelectWithFilterComponent { 79 @Input() label: string = ''; 80 @Input() options: string[] = []; 81 @Input() outerFilterWidth = '100px'; 82 @Input() innerFilterWidth = '100'; 83 @Input() flex = 'none'; 84 @Input() appearance = ''; 85 @Input() formFieldClass = ''; 86 87 @Output() readonly selectChange = new EventEmitter<MatSelectChange>(); 88 89 filterString: string = ''; 90 91 onSelectChange(event: MatSelectChange) { 92 this.selectChange.emit(event); 93 } 94 95 getOuterFormFieldStyle() { 96 return { 97 flex: this.flex, 98 width: this.outerFilterWidth, 99 }; 100 } 101 102 getInnerFormFieldStyle() { 103 return { 104 flex: 'none', 105 paddingTop: '2px', 106 paddingLeft: '10px', 107 paddingRight: '20px', 108 width: this.innerFilterWidth + 'px', 109 }; 110 } 111 112 onSelectClosed() { 113 this.filterString = ''; 114 } 115 116 hideOption(option: string) { 117 return !option.toLowerCase().includes(this.filterString.toLowerCase()); 118 } 119 120 selectedOptions(select: MatSelect) { 121 return this.options.filter((o) => select.value.includes(o)); 122 } 123 124 onSelectedOptionClick(option: string, select: MatSelect) { 125 select.value = select.value.filter((val: string) => val !== option); 126 this.selectChange.emit(new MatSelectChange(select, select.value)); 127 } 128} 129