In select2, if the search field is hidden for small results sets or disabled all together, the dropdown field won't navigate between different available options while typing.

Normally, this need is handled by simply filtering out options that don't match the search criteria, but when search is disabled, this no longer works.

This seems like a very basic functionality component available in every default <select> element. Is there any implementation of this feature?

Here's an example of what I'm talking about:

Type to Highlight Demo:

Type to Highlight Demo

MCVE Demo in StackSnippets & jsFiddle:

<!-- begin snippet: js hide: true console: true babel: false --> <!-- language: lang-js -->
$('#select2').select2({
  minimumResultsForSearch: 20,
});
$('#select2search').select2();
<!-- language: lang-css -->
.form-control {
  padding:15px;
}
label {
  display:inline-block;
  width:150px;
}
select {
  width: 150px;
  border: 1px solid #aaa;
  border-radius: 4px;
  height: 28px;
}
<!-- language: lang-html -->
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.js"></script>

<div class="form-control">
  <label for="normal">Normal</label>
  <select id="normal" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>

<div class="form-control">
  <label for="select2">Select2</label>
  <select id="select2" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>

<div class="form-control">
  <label for="select2search">Select2 + Search</label>
  <select id="select2search" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>
<!-- end snippet -->

You can manually implement this functionality by listening for keypress events, searching the dropdown list of items, and simulating a selection by triggering the mouseenter event on that item.

  1. Since it's hard to attach a keypress handler to the dynamic <span> that Select2 generates. We can listen to keypress events on the body that occured on a contain that has both --open and --focus
  2. Then we'll need to convert the keypress event char code into a alphanumeric character
  3. Select2 will automatically handle most special characters like <kbd>ā†‘ā†“</kbd> and <kbd>Enter</kbd>, but we'll proceed if we see a letter like this /[a-z]/i.test(char)
  4. HTML Dropdowns searches kind of work in a wonky way. As soon as any character is pressed, it'll find that element, but you can also string together multiple key in quick succession to narrow down the focus even more. So we'll set a delayed timeout that will refresh the clock every time it's called, but will clear the temporary search string after 750 ms.
  5. Then we'll crosswalk from the current element to find the $results dropdown and all of the .select2-results__option items inside it.
  6. Starting from the top, we'll test each one to see if the start of the option text is a case insensitive match for our current search string
  7. If so, we'll fire the mouseenter event, which is used elsewhere to highlight result items

All told, it'll look like this:

<!-- language: lang-js -->
// https://stackoverflow.com/a/1909508/1366033
var delay = (function () {
  var timer = 0;
  return function (callback, ms) {
    clearTimeout(timer);
    timer = setTimeout(callback, ms);
  };
})();  

// if we have focus and the container is open, it means we're not in the search field
$("body").on('keydown', '.select2-container--focus.select2-container--open',
             function(e) {
  // get char https://stackoverflow.com/a/34711175/1366033
  var char = e.key

  // only accept a range of characters
  if (/^[a-z]{1}$/i.test(char)) {
		  // select2 handles others https://github.com/select2/select2/blob/4.0.5/dist/js/select2.js#L5368
      
    var $this = $(this); // need inside delay

    // act immediately but temporarily store a run of characters
    delay(function () {
      // clear search text on delay
      $this.data("searchText", "");
    }, 750);

    // build search string with any previous characters and store new value
    var previous =  $this.data("searchText");
    var searchText = (previous === undefined ? "" : previous) + char;
    $this.data("searchText", searchText);
    console.log(searchText);

		// find available options
    var $sel2 = $this.data("element");
    var $select2 = $sel2.data("select2");
    var $results = $select2.results.$results;
		var $opts = $results.find(".select2-results__option");
    
    // find first option with matching start and select it
    $opts.each(function(i,el) {
      var $opt = $(el)
      var optText = $opt.text()
      var startsWithRegex = new RegExp('^' + searchText, 'i');
      var match = startsWithRegex.test(optText)

			// if we got one - fire event and we can leave
      if (match) {
        // mouseenter is a reliable way to highlight item in results list
        // https://github.com/select2/select2/blob/4.0.5/dist/js/select2.js#L1234
        $opt.trigger('mouseenter');
        return;
      }
      
      // if we found nothing, previous selection will stay highlighted
    })
  }
});

Working Demo in StackSnippets & jsFiddle:

<!-- begin snippet: js hide: true console: true babel: false --> <!-- language: lang-js -->
$('#select2').select2({
  minimumResultsForSearch: 20,
});
$('#select2search').select2();

// https://stackoverflow.com/a/1909508/1366033
var delay = (function () {
  var timer = 0;
  return function (callback, ms) {
    clearTimeout(timer);
    timer = setTimeout(callback, ms);
  };
})();  

// if we have focus and the container is open, it means we're not in the search field
$("body").on('keydown', '.select2-container--focus.select2-container--open',
             function(e) {
  // get char https://stackoverflow.com/a/34711175/1366033
  var char = e.key

  // only accept a range of characters
  if (/^[a-z]{1}$/i.test(char)) {
		  // select2 handles others https://github.com/select2/select2/blob/4.0.5/dist/js/select2.js#L5368
      
    var $this = $(this); // need inside delay

    // act immediately but temporarily store a run of characters
    delay(function () {
      // clear search text on delay
      $this.data("searchText", "");
    }, 750);

    // build search string with any previous characters and store new value
    var previous =  $this.data("searchText");
    var searchText = (previous === undefined ? "" : previous) + char;
    $this.data("searchText", searchText);
    console.log(searchText);

		// find available options
    var $sel2 = $this.data("element");
    var $select2 = $sel2.data("select2");
    var $results = $select2.results.$results;
		var $opts = $results.find(".select2-results__option");
    
    // find first option with matching start and select it
    $opts.each(function(i,el) {
      var $opt = $(el)
      var optText = $opt.text()
      var startsWithRegex = new RegExp('^' + searchText, 'i');
      var match = startsWithRegex.test(optText)

			// if we got one - fire event and we can leave
      if (match) {
        // mouseenter is a reliable way to highlight item in results list
        // https://github.com/select2/select2/blob/4.0.5/dist/js/select2.js#L1234
        $opt.trigger('mouseenter');
        return;
      }
      
      // if we found nothing, previous selection will stay highlighted
    })
  }
});
<!-- language: lang-css -->
.form-control {
  padding:15px;
}
label {
  display:inline-block;
  width:150px;
}
select {
  width: 150px;
  border: 1px solid #aaa;
  border-radius: 4px;
  height: 28px;
}
<!-- language: lang-html -->
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.js"></script>

<div class="form-control">
  <label for="normal">Normal</label>
  <select id="normal" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>

<div class="form-control">
  <label for="select2">Select2</label>
  <select id="select2" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>

<div class="form-control">
  <label for="select2search">Select2 + Search</label>
  <select id="select2search" >
    <option value="1">Apple</option>
    <option value="2">Banana</option>
    <option value="3">Carrot</option>
    <option value="4">Donut</option>
  </select>
</div>
<!-- end snippet -->

Working Demo

Working Demo