| // Copyright (C) 2023 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import m from 'mithril'; |
| |
| import {exists} from '../base/utils'; |
| |
| import {HTMLInputAttrs} from './common'; |
| import {Menu, MenuItem} from './menu'; |
| import {scheduleFullRedraw} from './raf'; |
| import {TextInput} from './text_input'; |
| |
| export class Select implements m.ClassComponent<HTMLInputAttrs> { |
| view({attrs, children}: m.CVnode<HTMLInputAttrs>) { |
| return m('select.pf-select', attrs, children); |
| } |
| } |
| |
| export interface FilterableSelectAttrs { |
| // Whether to show a search box. Defaults to false. |
| filterable?: boolean; |
| // The values to show in the select. |
| values: string[]; |
| // Called when the user selects an option. |
| onSelected: (value: string) => void; |
| // If set, only the first maxDisplayedItems will be shown. |
| maxDisplayedItems?: number; |
| // Whether the input field should be focused when the widget is created. |
| autofocusInput?: boolean; |
| } |
| |
| // A select widget with a search box, allowing the user to filter the options. |
| export class FilterableSelect |
| implements m.ClassComponent<FilterableSelectAttrs> |
| { |
| searchText = ''; |
| |
| view({attrs}: m.CVnode<FilterableSelectAttrs>) { |
| const filteredValues = attrs.values.filter((name) => { |
| return name.toLowerCase().includes(this.searchText.toLowerCase()); |
| }); |
| |
| const displayedValues = |
| attrs.maxDisplayedItems === undefined |
| ? filteredValues |
| : filteredValues.slice(0, attrs.maxDisplayedItems); |
| |
| const extraItems = |
| exists(attrs.maxDisplayedItems) && |
| Math.max(0, filteredValues.length - attrs.maxDisplayedItems); |
| |
| // TODO(altimin): when the user presses enter and there is only one item, |
| // select the first one. |
| // MAYBE(altimin): when the user presses enter and there are multiple items, |
| // select the first one. |
| return m( |
| 'div', |
| m( |
| '.pf-search-bar', |
| m(TextInput, { |
| oninput: (event: Event) => { |
| const eventTarget = event.target as HTMLTextAreaElement; |
| this.searchText = eventTarget.value; |
| scheduleFullRedraw(); |
| }, |
| onload: (event: Event) => { |
| if (!attrs.autofocusInput) return; |
| const eventTarget = event.target as HTMLTextAreaElement; |
| eventTarget.focus(); |
| }, |
| value: this.searchText, |
| placeholder: 'Filter...', |
| className: 'pf-search-box', |
| }), |
| m( |
| Menu, |
| ...displayedValues.map((value) => |
| m(MenuItem, { |
| label: value, |
| onclick: () => attrs.onSelected(value), |
| }), |
| ), |
| Boolean(extraItems) && m('i', `+${extraItems} more`), |
| ), |
| ), |
| ); |
| } |
| } |