1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16 17import {exists} from '../base/utils'; 18 19import {HTMLInputAttrs} from './common'; 20import {Menu, MenuItem} from './menu'; 21import {scheduleFullRedraw} from './raf'; 22import {TextInput} from './text_input'; 23 24export class Select implements m.ClassComponent<HTMLInputAttrs> { 25 view({attrs, children}: m.CVnode<HTMLInputAttrs>) { 26 return m('select.pf-select', attrs, children); 27 } 28} 29 30export interface FilterableSelectAttrs { 31 // Whether to show a search box. Defaults to false. 32 filterable?: boolean; 33 // The values to show in the select. 34 values: string[]; 35 // Called when the user selects an option. 36 onSelected: (value: string) => void; 37 // If set, only the first maxDisplayedItems will be shown. 38 maxDisplayedItems?: number; 39 // Whether the input field should be focused when the widget is created. 40 autofocusInput?: boolean; 41} 42 43// A select widget with a search box, allowing the user to filter the options. 44export class FilterableSelect 45 implements m.ClassComponent<FilterableSelectAttrs> 46{ 47 searchText = ''; 48 49 view({attrs}: m.CVnode<FilterableSelectAttrs>) { 50 const filteredValues = attrs.values.filter((name) => { 51 return name.toLowerCase().includes(this.searchText.toLowerCase()); 52 }); 53 54 const displayedValues = 55 attrs.maxDisplayedItems === undefined 56 ? filteredValues 57 : filteredValues.slice(0, attrs.maxDisplayedItems); 58 59 const extraItems = 60 exists(attrs.maxDisplayedItems) && 61 Math.max(0, filteredValues.length - attrs.maxDisplayedItems); 62 63 // TODO(altimin): when the user presses enter and there is only one item, 64 // select the first one. 65 // MAYBE(altimin): when the user presses enter and there are multiple items, 66 // select the first one. 67 return m( 68 'div', 69 m( 70 '.pf-search-bar', 71 m(TextInput, { 72 oninput: (event: Event) => { 73 const eventTarget = event.target as HTMLTextAreaElement; 74 this.searchText = eventTarget.value; 75 scheduleFullRedraw(); 76 }, 77 onload: (event: Event) => { 78 if (!attrs.autofocusInput) return; 79 const eventTarget = event.target as HTMLTextAreaElement; 80 eventTarget.focus(); 81 }, 82 value: this.searchText, 83 placeholder: 'Filter...', 84 className: 'pf-search-box', 85 }), 86 m( 87 Menu, 88 ...displayedValues.map((value) => 89 m(MenuItem, { 90 label: value, 91 onclick: () => attrs.onSelected(value), 92 }), 93 ), 94 Boolean(extraItems) && m('i', `+${extraItems} more`), 95 ), 96 ), 97 ); 98 } 99} 100