• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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