How to prevent filtering the options based on the text input in a combobox in SUID?

197 Views Asked by At

I need a TextField with a dropdown menu to offer default options, and do not want to filter the options based on the text content. All options have to be offered regardless of the actual text content.

Here is the code, ready to copy into https://suid.io/tools/playground:

import { TextField } from '@suid/material';
    
export default function App() {
  const remotes: string[] = ['First', 'Second'];
  return (
    <div>
      <TextField
        id="ipaddr"
        label="IP address"
        type="text"
        variant="filled"
        clearOnEscape
        InputProps={{
          endAdornment: (
            <datalist id="remotelist">
              {remotes.map((r) => (
                <option value={r}></option>
              ))}
            </datalist>
          ),
          inputProps: {
            list: 'remotelist',
          },
        }}
      />
    </div>
  );
}

What's happening is that after selecting First from the dropdown, it's no more possible to select Second. Typing in arbitrary text also removes the non-matching options.

Another issue is that the clearOnEscape option does not seem to be working.

Attempted to configure the filter functions etc... to no avail. Any ideas?

1

There are 1 best solutions below

1
snnsnn On

The problem is you are using adornment prop outside its purpose. If you check the documentaiton, https://mui.com/material-ui/react-text-field/#input-adornments, it is used to add prefix or suffix pertaining to the input text and the value is rendered as an inline element.

The functionality you asked is not offered out of the box, so you need to implement it yourself. If you need to keep the look and feel of MUI, you need to use built-in components.

import { List, ListItemButton, ListItemText, TextField } from '@suid/material';
import { createSignal, Show } from 'solid-js';

export const App = () => {
  const [show, setShow] = createSignal(false);
  const [ip, setIp] = createSignal('127.0.0.1');

  const remotes: Array<{ ip: string, label: string }> = [
    {
      ip: '127.0.0.1',
      label: 'localhost',
    },
    {
      ip: '192.168.0.10',
      label: 'LAN'
    },
    {
      ip: '84.8.23.218',
      label: 'Remote'
    },
  ];

  const handleChange = (event: any) => {
    setIp(event.target.value);
  };

  const handleFocus = () => setShow(true);

  const handleBlur = () => setShow(false);

  const handleButtonClick = (ip: string) => (event: any) => {
    setIp(ip);
  };

  return (
    <div>
      <TextField
        label="IP address"
        type="text"
        value={ip()}
        onchange={handleChange}
        onFocus={handleFocus}
        // onBlur={handleBlur}
      />
      <Show when={show()}>
        <List>
          {remotes.map(item => (
            <ListItemButton onClick={handleButtonClick(item.ip)} >
              <ListItemText primary={item.label} />
            </ListItemButton>
          ))}
        </List>
      </Show>
    </div>
  );
}

There are few problems with @suid event handling for TextField, only delegated events are supported, meaning you can use event handlers like onFocus and onBlur, problem is, you have no control over the event sequence. Try enabling onBlur, you will see, the list disappears before the click event gets registered.

Solid provides all lowercase event handler syntax, like onblur, to tap into native event handling but it is not bound for TextField component. So, onblur={handleBlur} does not work.

What we are trying to achive is, when user clicks one of the ip button, we need to change the api first then hide the list. So, click event should be handled before the blur event. We need to use bubling phase.

https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase

So, you need to resort to ref to access native event binding:

Create a variable first:

let ref: HTMLInputElement;

Then add to the element as a prop:

// Snipped
<TextField
  ref={ref!}
/>

But, ref does not point to actual input element but the div wrapper around it, so is no use:

onMount(() => {
  console.log(ref);
});

So, library has some issues, you need find a workaround to achive:

  1. Show list when input focuses.
  2. Hides list when input loses focus.
  3. When user clicks the buttons, set the IP first than hide the list.

Solution will not be hard to find, but ugly and subpar.

Alternatively you can use plain jsx elements, add classes from @suid.