how to create an Autocomplete element?

399 Views Asked by At

I'm looking for the Autocomplete element of Material UI and React for SolidJS. Many of the elements are available in Solid through https://suid.io/, but https://mui.com/material-ui/react-autocomplete/ seems to be missing. Is there any library available for this purpose? The goal is having a nicely styled SUID TextField that can be edited by the user, and it should be possible to select predefined options from a dropdown list.

It's also possible to specify the "select" attribute for the TextField, or specify an "input" element for Select. Is it possible to combine the two elements into one TextField that also has the dropdown list? What is the purpose of input for Select and select for TextField? I played with the attributes and only managed to get a normal TextField without the Select functionality. Any example or clarification would be welcome.

1

There are 1 best solutions below

3
snnsnn On

Not Material UI but there are some select libraries:

But you can create your own if you like:

  1. Create an options list below a text input field.
  2. Tie dropdown's visibility to input field's focus and blur events.
  3. Filter and show items on the list that partially matches with the input text.
  4. Create selected signal and use it to control selected element on dropdown list.
  5. Tie UpArrow and DownArrow keydown events to increment and decrement selected value.
  6. Tie Enter keydown to accept selected item and use it as input value.
  7. Hide options after accepting selected element.

Here is a working demo, you can improve on it. You can implement the mouse events on the list element for efficiency: Update the selected item on mousemove and update the text value on mousedown.

import { render } from 'solid-js/web';
import { Accessor, batch, Component, createEffect, createMemo, createSignal, For, JSX, on, Setter, Show } from 'solid-js';

const Select: Component<{
  text: Accessor<string>,
  setText: Setter<string>,
  options: Array<string>
}> = (props) => {
  const [show, setShow] = createSignal(false);
  const [selected, setSelected] = createSignal(0);

  const filteredOptions = () => props.options.filter(el => el.includes(props.text()));

  const isVisible = createMemo(() => {
    return show() && (filteredOptions().length > 1 || filteredOptions()[0] !== props.text());
  });

  createEffect(on(props.text, () => {
    setSelected(0);
  }));

  const handleInput: JSX.EventHandlerUnion<HTMLInputElement, InputEvent> = (event) => {
    props.setText(event.currentTarget.value);
  };

  const handleKeydown: JSX.EventHandler<HTMLInputElement, KeyboardEvent> = (event) => {
    if (event.code === 'ArrowUp') {
      setSelected(prev => prev === 0 ? (filteredOptions().length - 1) : prev - 1);
    } else if (event.code === 'ArrowDown') {
      setSelected(prev => prev + 1 === filteredOptions().length ? 0 : prev + 1);
    } else if (event.code === 'Enter') {
      props.setText(filteredOptions()[selected()]);
    };
  }

  return (
    <div>
      <input
        type="text"
        value={props.text()}
        onInput={handleInput}
        onKeyDown={handleKeydown}
        onFocus={() => setShow(true)}
        onBlur={() => setShow(false)}
      />
      <Show when={isVisible()}>
        <ul style={`display: block; width: 100%; list-style: none; margin: 0; padding: 0`}>
          <For each={filteredOptions()}>
            {(item, i) => <li style={{ color: selected() === i() ? 'red': 'inherit'}}>{item}</li>}
          </For>
        </ul>
      </Show>
    </div>
  );
};

export const App = () => {
  const [text, setText] = createSignal("");
  const options = ['Apple', 'Orange', 'Banana', 'Mango', 'Pear'];

  return (
    <div>
      <Select text={text} setText={setText} options={options} />
    </div>
  );
};

render(() => <App />, document.body);

https://playground.solidjs.com/anonymous/e58974e7-287f-4f56-8ab3-33787d93c629