javascripthtmldatalist

datalist input element loses cursor after datalist list attribute is set on ios safari


i have an html datalist element for which i am trying to only show the autocomplete list when more than two characters are entered in the field. on desktop, you can type from 0 to infinity characters with the input never losing focus and the list appearing just fine. however, for mobile (specifically- iOS safari), when the character length of the input hits 3, the cursor disappears despite the input still having focus. the user must click on the input again to continue his search. i will mention that the safari browser is presenting the user with some unrelated autocomplete results until this length of 3 is hit. here is the listener on the datalist that governs my intended behavior:

  // Only show auto-complete results for greater than 2 characters.
  searchNameInput.addEventListener('keyup', e => {
    const input = e.target;
    const list = input.getAttribute('data-list');
    input.value.length > 2 ? input.setAttribute('list', list) : input.removeAttribute('list');

    // Unrelated code (i think):
    if (!input.value.length)
      filtersProxy.searchName = { name: '', city: '', stateProvince: '' };
  });

here is what the browser looks like after 3 characters have been typed. notice the input field has no cursor. the datalist actual list hasn't appeared yet either.

example

i've tried using a setTimeout to return the focus to the element after every keyup event to no avail. this one has me stumped!


Solution

  • I have a terrible hack for you.

    First, create a fake input field:

    const fakeInput = document.createElement('input')
    fakeInput.setAttribute('type', 'text')
    fakeInput.style.position = 'absolute'
    fakeInput.style.opacity = 0
    fakeInput.style.height = 0
    fakeInput.style.fontSize = '16px'
    document.body.prepend(fakeInput)
    fakeInput.focus()
    

    Next, use setTimeout to set the focus to your input, because setting it directly won't work:

    if(input.value.length > 2) {
        if(!input.hasAttribute('list')) {
          fakeInput.focus();
          input.setAttribute('list', list);
          setTimeout(() => input.focus(), 10);
        }
    } else input.removeAttribute('list');
    

    Voila.