Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <main>

  <h1>Testing <code>aria-readonly</code></h1>

  <p>
    Used in the post <cite><a href="https://adrianroselli.com/2022/11/brief-note-on-aria-readonly-support-html.html" target="_top">Brief Note on <code>aria-readonly</code> Support</a></cite>.
  </p>
  
  <p>
    These controls, with the exception of #6a (the second ARIA checkbox) in no way programmatically prevent interaction. There is no script to limit their function (again, except the ARIA checkbox).
  </p>

  <form autocomplete="off" method="get">
    <h2>1. Uses <code>aria-readonly</code> with <code>radiogroup</code> Role on a <code>&lt;fieldset&gt;</code></h2>
    <fieldset aria-readonly="true" role="radiogroup">
      <legend>Sauce</legend>
      <div>
        <input type="radio" name="sauce" id="sauce1" value="fish"> <label for="sauce1">Fish</label>
      </div>
      <div>
        <input type="radio" name="sauce" id="sauce2" value="tomato" checked> <label for="sauce2">Tomato</label>
      </div>
      <div>
        <input type="radio" name="sauce" id="sauce3" value="bearnaise"> <label for="sauce3">Béarnaise</label>
      </div>
    </fieldset>
    <input type="submit" value="Choose">
  </form>

  <form autocomplete="off" method="get">
    <h2>2. Uses <code>aria-readonly</code> with <code>radiogroup</code> Role</h2>
    <div aria-readonly="true" role="radiogroup" aria-labelledby="GrpLbl">
      <div id="GrpLbl">Cheese</div>
      <div>
        <input type="radio" name="cheese" id="cheese1" value="fish"> <label for="cheese1">Swiss</label>
      </div>
      <div>
        <input type="radio" name="cheese" id="cheese2" value="tomato" checked> <label for="cheese2">Unpasteurized English stilton</label>
      </div>
      <div>
        <input type="radio" name="cheese" id="cheese3" value="bearnaise"> <label for="cheese3">Cheddar</label>
      </div>
    </div>
    <input type="submit" value="Choose">
  </form>

  <form autocomplete="off" method="get">
    <h2>3. APG Combobox with <code>aria-readonly</code> on the <code>combobox</code> and <code>listbox</code> Roles</h2>
    <label id="combo1-label" class="combo-label">Favorite Fruit</label>
    <div class="combo js-select">
      <div aria-controls="listbox1" aria-expanded="false" aria-haspopup="listbox" aria-labelledby="combo1-label" id="combo1" class="combo-input" role="combobox" tabindex="0" aria-readonly="true">
      </div>
      <div class="combo-menu" role="listbox" id="listbox1" aria-labelledby="combo1-label" tabindex="-1" aria-readonly="true">
      </div>
    </div>
  </form>

  <form autocomplete="off" method="get">
    <h2>4. APG Listbox with <code>aria-readonly</code></h2>
    <p>Choose your favorite transuranic element (actinide or transactinide).</p>
    <div class="listbox-area">
      <div>
        <span id="ss_elem" class="listbox-label">Transuranium elements:</span>
        <ul id="ss_elem_list" tabindex="0" role="listbox" aria-labelledby="ss_elem" aria-readonly="true">
          <li id="ss_elem_Np" role="option">Neptunium</li>
          <li id="ss_elem_Pu" role="option">Plutonium</li>
          <li id="ss_elem_Am" role="option">Americium</li>
          <li id="ss_elem_Cm" role="option">Curium</li>
          <li id="ss_elem_Bk" role="option">Berkelium</li>
          <li id="ss_elem_Cf" role="option">Californium</li>
          <li id="ss_elem_Es" role="option">Einsteinium</li>
          <li id="ss_elem_Fm" role="option">Fermium</li>
          <li id="ss_elem_Md" role="option">Mendelevium</li>
          <li id="ss_elem_No" role="option">Nobelium</li>
          <li id="ss_elem_Lr" role="option">Lawrencium</li>
          <li id="ss_elem_Rf" role="option">Rutherfordium</li>
          <li id="ss_elem_Db" role="option">Dubnium</li>
          <li id="ss_elem_Sg" role="option">Seaborgium</li>
          <li id="ss_elem_Bh" role="option">Bohrium</li>
          <li id="ss_elem_Hs" role="option">Hassium</li>
          <li id="ss_elem_Mt" role="option">Meitnerium</li>
          <li id="ss_elem_Ds" role="option">Darmstadtium</li>
          <li id="ss_elem_Rg" role="option">Roentgenium</li>
          <li id="ss_elem_Cn" role="option">Copernicium</li>
          <li id="ss_elem_Nh" role="option">Nihonium</li>
          <li id="ss_elem_Fl" role="option">Flerovium</li>
          <li id="ss_elem_Mc" role="option">Moscovium</li>
          <li id="ss_elem_Lv" role="option">Livermorium</li>
          <li id="ss_elem_Ts" role="option">Tennessine</li>
          <li id="ss_elem_Og" role="option">Oganesson</li>
        </ul>
      </div>
    </div>
  </form>

  <form autocomplete="off" method="get">
    <h2>5. Native checkbox with <code>aria-readonly</code></h2>
    <input type="checkbox" id="signUp" value="yes" aria-readonly="true" checked>
    <label for="signUp">
      Please sign me up for your newsletter.
    </label>
  </form>

  <form autocomplete="off" method="get">
    <h2>6. <code>checkbox</code> Role with <code>aria-readonly</code></h2>
    <div role="checkbox" tabindex="0" aria-checked="true" id="Chk01" onclick="toggleChk(this.id);" onkeyup="if(window.event.keyCode == 32){toggleChk(this.id)};" aria-readonly="true">
      Please sign me up for your newsletter.
    </div>
  </form>

  <form autocomplete="off" method="get">
    <h2>6a. <code>checkbox</code> Role with <code>aria-readonly</code> and Checking Disallowed</h2>
    <div role="checkbox" tabindex="0" aria-checked="true" id="Chk02" onclick="toggleChk(this.id);" onkeyup="if(window.event.keyCode == 32){toggleChk(this.id)};" aria-readonly="true">
      Definitely sign me up for your other newsletter.
    </div>
  </form>

</main>
              
            
!

CSS

              
                body {
  font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto,
    Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  line-height: 1.4;
  /*   line-height: 1.5; */
  /*   letter-spacing: 0.12em; */
  /*   word-spacing: 0.16em; */
}

h1, h2, h3 {
  line-height: 1.2;
}


form {
  flex: 1 0 15em;
  margin: 1em;
}

div[role="radiogroup"] {
  padding: .25em .5em .5em .5em;
  border: .1em solid #ccc;
}

div[role="radiogroup"] > div:first-child {
  font-weight: bold;
}

div[role="checkbox"] {
  padding: .2em .5em;
  position: relative;
}

div[role="checkbox"]:focus {
  outline: .2em dotted currentColor;
}

div[role="checkbox"]::before {
  content: "";
  display: inline-block;
  margin-bottom: -0.25em;
  width: 1em;
  height: 1em;
  border: .1em solid currentColor;
  border-radius: .25em;
}

div[role="checkbox"][aria-checked="true"]::after {
  content: "";
  display: block;
  position: absolute;
  top: .1em;
  left: .75em;
  width: .5em;
  height: 1em;
  border-right: .35em solid currentColor;
  border-bottom: .35em solid currentColor;
  transform: rotate(30deg);
}

.combo-menu[role="listbox"] {
  margin-top: 0;
}




td, th {
  min-width: 12em;
}

/* Standard Tables */

table {
  margin: 1em 0;
  border-collapse: collapse;
  border: 0.1em solid #d6d6d6;
}

caption {
  text-align: left;
  font-style: italic;
  padding: 0.25em 0.5em 0.5em 0.5em;
}

th,
td {
  padding: 0.25em 0.5em 0.25em 1em;
  vertical-align: text-top;
  text-align: left;
  text-indent: -0.5em;
}

th {
  vertical-align: bottom;
  background-color: #666;
  color: #fff;
}

tr:nth-child(even) th[scope="row"] {
  background-color: #f2f2f2;
}

tr:nth-child(odd) th[scope="row"] {
  background-color: #fff;
}

tr {
  border-top: .05em solid #333;
  border-top: .05em solid #333;
}

/* Responsive Tables */

[role="region"][aria-labelledby][tabindex] {
  overflow: auto;
  border: 0.1em solid #d6d6d6;
}

[role="region"][aria-labelledby][tabindex]:focus {
  box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.5);
  outline: 0.1em solid rgba(0, 0, 0, 0.1);
}

[role="region"][aria-labelledby][tabindex] table {
  margin: 0;
  border: none;
}

/* Scrolling Visual Cue */

[role="region"][aria-labelledby][tabindex] {
  background: linear-gradient(to right, #fff 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, rgba(255, 255, 255, 0), #fff 70%) 0 100%,
    radial-gradient(
      farthest-side at 0% 50%,
      rgba(0, 0, 0, 0.2),
      rgba(0, 0, 0, 0)
    ),
    radial-gradient(
        farthest-side at 100% 50%,
        rgba(0, 0, 0, 0.2),
        rgba(0, 0, 0, 0)
      )
      0 100%;
  background-repeat: no-repeat;
  background-color: #fff;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-position: 0 0, 100%, 0 0, 100%;
  background-attachment: local, local, scroll, scroll;
}

/* Fixed Headers */

th {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 2;
}

th[scope=row] {
  position: -webkit-sticky;
  position: sticky;
  left: 0;
  z-index: 1;
}

th[scope=row] {
  vertical-align: top;
  color: inherit;
  background-color: inherit;
  background: linear-gradient(90deg, transparent 0%, transparent calc(100% - .05em), #d6d6d6 calc(100% - .05em), #d6d6d6 100%);
}

table:nth-of-type(2) th:not([scope=row]):first-child {
  left: 0;
  z-index: 3;
  background: linear-gradient(90deg, #666 0%, #666 calc(100% - .05em), #ccc calc(100% - .05em), #ccc 100%);
}


/* Confluence styles */

.good {
    background-color: #E3FCEF;
}

.good::before {
  content: "\02714\0fe0f ";
}

.caution {
    background-color: #FFFAE6;
}

.caution::before {
  content: "‽ ";
  font-weight: bold;
  font-size: 125%;
  line-height: 0;
}

.buggy {
    background-color: #FFEBE6;
}

.buggy::before {
  content: "\0274c ";
}





/* APG styles */

.listbox-area {
  display: grid;
  grid-gap: 2em;
  grid-template-columns: repeat(2, 1fr);
  padding: 20px;
  border: 1px solid #aaa;
  border-radius: 4px;
  background: #eee;
}

[role="listbox"] {
  margin: 1em 0 0;
  padding: 0;
  min-height: 18em;
  border: 1px solid #aaa;
  background: white;
}

[role="listbox"]#ss_elem_list {
  position: relative;
  max-height: 18em;
  overflow-y: auto;
}

[role="listbox"] + *,
.listbox-label + * {
  margin-top: 1em;
}

[role="group"] {
  margin: 0;
  padding: 0;
}

[role="group"] > [role="presentation"] {
  display: block;
  margin: 0;
  padding: 0 0.5em;
  font-weight: bold;
  line-height: 2;
  background-color: #ccc;
}

[role="option"] {
  position: relative;
  display: block;
  padding: 0 1em 0 1.5em;
  line-height: 1.8em;
}

[role="option"].focused {
  background: #bde4ff;
}

[role="option"][aria-selected="true"]::before {
  position: absolute;
  left: 0.5em;
  content: "✓";
}

button[aria-haspopup="listbox"] {
  position: relative;
  padding: 5px 10px;
  width: 150px;
  border-radius: 0;
  text-align: left;
}

button[aria-haspopup="listbox"]::after {
  position: absolute;
  right: 5px;
  top: 10px;
  width: 0;
  height: 0;
  border: 8px solid transparent;
  border-top-color: currentcolor;
  border-bottom: 0;
  content: "";
}

button[aria-haspopup="listbox"][aria-expanded="true"]::after {
  position: absolute;
  right: 5px;
  top: 10px;
  width: 0;
  height: 0;
  border: 8px solid transparent;
  border-top: 0;
  border-bottom-color: currentcolor;
  content: "";
}

button[aria-haspopup="listbox"] + [role="listbox"] {
  position: absolute;
  margin: 0;
  width: 9.5em;
  max-height: 10em;
  border-top: 0;
  overflow-y: auto;
}

[role="toolbar"] {
  display: flex;
}

[role="toolbar"] > * {
  border: 1px solid #aaa;
  background: #ccc;
}

[role="toolbar"] > [aria-disabled="false"]:focus {
  background-color: #eee;
}

button {
  font-size: inherit;
}

button[aria-disabled="true"] {
  opacity: 0.5;
}

.move-right-btn::after {
  content: " →";
}

.move-left-btn::before {
  content: "← ";
}

.annotate {
  color: #366ed4;
  font-style: italic;
}

.hidden {
  display: none;
}

.offscreen {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  font-size: 14px;
  white-space: nowrap;
}


/* APG combobox */

.combo *,
.combo *::before,
.combo *::after {
  box-sizing: border-box;
}

.combo {
  display: block;
  margin-bottom: 1.5em;
  max-width: 400px;
  position: relative;
}

.combo::after {
  border-bottom: 2px solid rgb(0 0 0 / 75%);
  border-right: 2px solid rgb(0 0 0 / 75%);
  content: "";
  display: block;
  height: 12px;
  pointer-events: none;
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translate(0, -65%) rotate(45deg);
  width: 12px;
}

.combo-input {
  background-color: #f5f5f5;
  border: 2px solid rgb(0 0 0 / 75%);
  border-radius: 4px;
  display: block;
  font-size: 1em;
  min-height: calc(1.4em + 26px);
  padding: 12px 16px 14px;
  text-align: left;
  width: 100%;
}

.open .combo-input {
  border-radius: 4px 4px 0 0;
}

.combo-input:focus {
  border-color: #0067b8;
  box-shadow: 0 0 4px 2px #0067b8;
  outline: 4px solid transparent;
}

.combo-label {
  display: block;
  font-size: 20px;
  font-weight: 100;
  margin-bottom: 0.25em;
}

.combo-menu {
  background-color: #f5f5f5;
  border: 1px solid rgb(0 0 0 / 75%);
  border-radius: 0 0 4px 4px;
  display: none;
  max-height: 300px;
  overflow-y: scroll;
  left: 0;
  position: absolute;
  top: 100%;
  width: 100%;
  z-index: 100;
}

.open .combo-menu {
  display: block;
}

.combo-option {
  padding: 10px 12px 12px;
}

.combo-option:hover {
  background-color: rgb(0 0 0 / 10%);
}

.combo-option.option-current {
  outline: 3px solid #0067b8;
  outline-offset: -3px;
}

.combo-option[aria-selected="true"] {
  padding-right: 30px;
  position: relative;
}

.combo-option[aria-selected="true"]::after {
  border-bottom: 2px solid #000;
  border-right: 2px solid #000;
  content: "";
  height: 16px;
  position: absolute;
  right: 15px;
  top: 50%;
  transform: translate(0, -50%) rotate(45deg);
  width: 8px;
}

              
            
!

JS

              
                function toggleChk(btnID) {
  var theButton = document.getElementById(btnID);
  // if (theButton.getAttribute("aria-readonly") == "true") {
  if (btnID == "Chk02") {
    alert("You cannot change your mind.");
  }else{
    if (theButton.getAttribute("aria-checked") == "false") {
      theButton.setAttribute("aria-checked", "true");
    } else {
      theButton.setAttribute("aria-checked", "false");
    }
  }
}












/*
 *   This content is licensed according to the W3C Software License at
 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
 */

'use strict';

/**
 * @namespace aria
 */
var aria = aria || {};

/**
 * @class
 * @description
 *  Listbox object representing the state and interactions for a listbox widget
 * @param listboxNode
 *  The DOM node pointing to the listbox
 */
aria.Listbox = function (listboxNode) {
  this.listboxNode = listboxNode;
  this.activeDescendant = this.listboxNode.getAttribute(
    'aria-activedescendant'
  );
  this.multiselectable = this.listboxNode.hasAttribute('aria-multiselectable');
  this.moveUpDownEnabled = false;
  this.siblingList = null;
  this.startRangeIndex = 0;
  this.upButton = null;
  this.downButton = null;
  this.moveButton = null;
  this.keysSoFar = '';
  this.handleFocusChange = function () {};
  this.handleItemChange = function () {};
  this.registerEvents();
};

/**
 * @description
 *  Register events for the listbox interactions
 */
aria.Listbox.prototype.registerEvents = function () {
  this.listboxNode.addEventListener('focus', this.setupFocus.bind(this));
  this.listboxNode.addEventListener('keydown', this.checkKeyPress.bind(this));
  this.listboxNode.addEventListener('click', this.checkClickItem.bind(this));

  if (this.multiselectable) {
    this.listboxNode.addEventListener(
      'mousedown',
      this.checkMouseDown.bind(this)
    );
  }
};

/**
 * @description
 *  If there is no activeDescendant, focus on the first option
 */
aria.Listbox.prototype.setupFocus = function () {
  if (this.activeDescendant) {
    return;
  }
};

/**
 * @description
 *  Focus on the first option
 */
aria.Listbox.prototype.focusFirstItem = function () {
  var firstItem = this.listboxNode.querySelector('[role="option"]');

  if (firstItem) {
    this.focusItem(firstItem);
  }
};

/**
 * @description
 *  Focus on the last option
 */
aria.Listbox.prototype.focusLastItem = function () {
  var itemList = this.listboxNode.querySelectorAll('[role="option"]');

  if (itemList.length) {
    this.focusItem(itemList[itemList.length - 1]);
  }
};

/**
 * @description
 *  Handle various keyboard controls; UP/DOWN will shift focus; SPACE selects
 *  an item.
 * @param evt
 *  The keydown event object
 */
aria.Listbox.prototype.checkKeyPress = function (evt) {
  var key = evt.which || evt.keyCode;
  var lastActiveId = this.activeDescendant;
  var allOptions = this.listboxNode.querySelectorAll('[role="option"]');
  var currentItem =
    document.getElementById(this.activeDescendant) || allOptions[0];
  var nextItem = currentItem;

  if (!currentItem) {
    return;
  }

  switch (key) {
    case aria.KeyCode.PAGE_UP:
    case aria.KeyCode.PAGE_DOWN:
      if (this.moveUpDownEnabled) {
        evt.preventDefault();

        if (key === aria.KeyCode.PAGE_UP) {
          this.moveUpItems();
        } else {
          this.moveDownItems();
        }
      }

      break;
    case aria.KeyCode.UP:
    case aria.KeyCode.DOWN:
      if (!this.activeDescendant) {
        // focus first option if no option was previously focused, and perform no other actions
        this.focusItem(currentItem);
        break;
      }

      if (this.moveUpDownEnabled && evt.altKey) {
        evt.preventDefault();
        if (key === aria.KeyCode.UP) {
          this.moveUpItems();
        } else {
          this.moveDownItems();
        }
        return;
      }

      if (key === aria.KeyCode.UP) {
        nextItem = this.findPreviousOption(currentItem);
      } else {
        nextItem = this.findNextOption(currentItem);
      }

      if (nextItem && this.multiselectable && event.shiftKey) {
        this.selectRange(this.startRangeIndex, nextItem);
      }

      if (nextItem) {
        this.focusItem(nextItem);
        evt.preventDefault();
      }

      break;
    case aria.KeyCode.HOME:
      evt.preventDefault();
      this.focusFirstItem();

      if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
        this.selectRange(this.startRangeIndex, 0);
      }
      break;
    case aria.KeyCode.END:
      evt.preventDefault();
      this.focusLastItem();

      if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
        this.selectRange(this.startRangeIndex, allOptions.length - 1);
      }
      break;
    case aria.KeyCode.SHIFT:
      this.startRangeIndex = this.getElementIndex(currentItem, allOptions);
      break;
    case aria.KeyCode.SPACE:
      evt.preventDefault();
      this.toggleSelectItem(nextItem);
      break;
    case aria.KeyCode.BACKSPACE:
    case aria.KeyCode.DELETE:
    case aria.KeyCode.RETURN:
      if (!this.moveButton) {
        return;
      }

      var keyshortcuts = this.moveButton.getAttribute('aria-keyshortcuts');
      if (key === aria.KeyCode.RETURN && keyshortcuts.indexOf('Enter') === -1) {
        return;
      }
      if (
        (key === aria.KeyCode.BACKSPACE || key === aria.KeyCode.DELETE) &&
        keyshortcuts.indexOf('Delete') === -1
      ) {
        return;
      }

      evt.preventDefault();

      var nextUnselected = nextItem.nextElementSibling;
      while (nextUnselected) {
        if (nextUnselected.getAttribute('aria-selected') != 'true') {
          break;
        }
        nextUnselected = nextUnselected.nextElementSibling;
      }
      if (!nextUnselected) {
        nextUnselected = nextItem.previousElementSibling;
        while (nextUnselected) {
          if (nextUnselected.getAttribute('aria-selected') != 'true') {
            break;
          }
          nextUnselected = nextUnselected.previousElementSibling;
        }
      }

      this.moveItems();

      if (!this.activeDescendant && nextUnselected) {
        this.focusItem(nextUnselected);
      }
      break;
    case 65:
      // handle control + A
      if (this.multiselectable && (evt.ctrlKey || evt.metaKey)) {
        evt.preventDefault();
        this.selectRange(0, allOptions.length - 1);
        break;
      }
    // fall through
    default:
      var itemToFocus = this.findItemToFocus(key);
      if (itemToFocus) {
        this.focusItem(itemToFocus);
      }
      break;
  }

  if (this.activeDescendant !== lastActiveId) {
    this.updateScroll();
  }
};

aria.Listbox.prototype.findItemToFocus = function (key) {
  var itemList = this.listboxNode.querySelectorAll('[role="option"]');
  var character = String.fromCharCode(key);
  var searchIndex = 0;

  if (!this.keysSoFar) {
    for (var i = 0; i < itemList.length; i++) {
      if (itemList[i].getAttribute('id') == this.activeDescendant) {
        searchIndex = i;
      }
    }
  }
  this.keysSoFar += character;
  this.clearKeysSoFarAfterDelay();

  var nextMatch = this.findMatchInRange(
    itemList,
    searchIndex + 1,
    itemList.length
  );
  if (!nextMatch) {
    nextMatch = this.findMatchInRange(itemList, 0, searchIndex);
  }
  return nextMatch;
};

/* Return the index of the passed element within the passed array, or null if not found */
aria.Listbox.prototype.getElementIndex = function (option, options) {
  var allOptions = Array.prototype.slice.call(options); // convert to array
  var optionIndex = allOptions.indexOf(option);

  return typeof optionIndex === 'number' ? optionIndex : null;
};

/* Return the next listbox option, if it exists; otherwise, returns null */
aria.Listbox.prototype.findNextOption = function (currentOption) {
  var allOptions = Array.prototype.slice.call(
    this.listboxNode.querySelectorAll('[role="option"]')
  ); // get options array
  var currentOptionIndex = allOptions.indexOf(currentOption);
  var nextOption = null;

  if (currentOptionIndex > -1 && currentOptionIndex < allOptions.length - 1) {
    nextOption = allOptions[currentOptionIndex + 1];
  }

  return nextOption;
};

/* Return the previous listbox option, if it exists; otherwise, returns null */
aria.Listbox.prototype.findPreviousOption = function (currentOption) {
  var allOptions = Array.prototype.slice.call(
    this.listboxNode.querySelectorAll('[role="option"]')
  ); // get options array
  var currentOptionIndex = allOptions.indexOf(currentOption);
  var previousOption = null;

  if (currentOptionIndex > -1 && currentOptionIndex > 0) {
    previousOption = allOptions[currentOptionIndex - 1];
  }

  return previousOption;
};

aria.Listbox.prototype.clearKeysSoFarAfterDelay = function () {
  if (this.keyClear) {
    clearTimeout(this.keyClear);
    this.keyClear = null;
  }
  this.keyClear = setTimeout(
    function () {
      this.keysSoFar = '';
      this.keyClear = null;
    }.bind(this),
    500
  );
};

aria.Listbox.prototype.findMatchInRange = function (
  list,
  startIndex,
  endIndex
) {
  // Find the first item starting with the keysSoFar substring, searching in
  // the specified range of items
  for (var n = startIndex; n < endIndex; n++) {
    var label = list[n].innerText;
    if (label && label.toUpperCase().indexOf(this.keysSoFar) === 0) {
      return list[n];
    }
  }
  return null;
};

/**
 * @description
 *  Check if an item is clicked on. If so, focus on it and select it.
 * @param evt
 *  The click event object
 */
aria.Listbox.prototype.checkClickItem = function (evt) {
  if (evt.target.getAttribute('role') !== 'option') {
    return;
  }

  this.focusItem(evt.target);
  this.toggleSelectItem(evt.target);
  this.updateScroll();

  if (this.multiselectable && evt.shiftKey) {
    this.selectRange(this.startRangeIndex, evt.target);
  }
};

/**
 * Prevent text selection on shift + click for multi-select listboxes
 *
 * @param evt
 */
aria.Listbox.prototype.checkMouseDown = function (evt) {
  if (
    this.multiselectable &&
    evt.shiftKey &&
    evt.target.getAttribute('role') === 'option'
  ) {
    evt.preventDefault();
  }
};

/**
 * @description
 *  Toggle the aria-selected value
 * @param element
 *  The element to select
 */
aria.Listbox.prototype.toggleSelectItem = function (element) {
  if (this.multiselectable) {
    element.setAttribute(
      'aria-selected',
      element.getAttribute('aria-selected') === 'true' ? 'false' : 'true'
    );

    this.updateMoveButton();
  }
};

/**
 * @description
 *  Defocus the specified item
 * @param element
 *  The element to defocus
 */
aria.Listbox.prototype.defocusItem = function (element) {
  if (!element) {
    return;
  }
  if (!this.multiselectable) {
    element.removeAttribute('aria-selected');
  }
  element.classList.remove('focused');
};

/**
 * @description
 *  Focus on the specified item
 * @param element
 *  The element to focus
 */
aria.Listbox.prototype.focusItem = function (element) {
  this.defocusItem(document.getElementById(this.activeDescendant));
  if (!this.multiselectable) {
    element.setAttribute('aria-selected', 'true');
  }
  element.classList.add('focused');
  this.listboxNode.setAttribute('aria-activedescendant', element.id);
  this.activeDescendant = element.id;

  if (!this.multiselectable) {
    this.updateMoveButton();
  }

  this.checkUpDownButtons();
  this.handleFocusChange(element);
};

/**
 * Helper function to check if a number is within a range; no side effects.
 *
 * @param index
 * @param start
 * @param end
 * @returns {boolean}
 */
aria.Listbox.prototype.checkInRange = function (index, start, end) {
  var rangeStart = start < end ? start : end;
  var rangeEnd = start < end ? end : start;

  return index >= rangeStart && index <= rangeEnd;
};

/**
 * Select a range of options
 *
 * @param start
 * @param end
 */
aria.Listbox.prototype.selectRange = function (start, end) {
  // get start/end indices
  var allOptions = this.listboxNode.querySelectorAll('[role="option"]');
  var startIndex =
    typeof start === 'number' ? start : this.getElementIndex(start, allOptions);
  var endIndex =
    typeof end === 'number' ? end : this.getElementIndex(end, allOptions);

  for (var index = 0; index < allOptions.length; index++) {
    var selected = this.checkInRange(index, startIndex, endIndex);
    allOptions[index].setAttribute('aria-selected', selected + '');
  }

  this.updateMoveButton();
};

/**
 * Check for selected options and update moveButton, if applicable
 */
aria.Listbox.prototype.updateMoveButton = function () {
  if (!this.moveButton) {
    return;
  }

  if (this.listboxNode.querySelector('[aria-selected="true"]')) {
    this.moveButton.setAttribute('aria-disabled', 'false');
  } else {
    this.moveButton.setAttribute('aria-disabled', 'true');
  }
};

/**
 * Check if the selected option is in view, and scroll if not
 */
aria.Listbox.prototype.updateScroll = function () {
  var selectedOption = document.getElementById(this.activeDescendant);
  if (
    selectedOption &&
    this.listboxNode.scrollHeight > this.listboxNode.clientHeight
  ) {
    var scrollBottom =
      this.listboxNode.clientHeight + this.listboxNode.scrollTop;
    var elementBottom = selectedOption.offsetTop + selectedOption.offsetHeight;
    if (elementBottom > scrollBottom) {
      this.listboxNode.scrollTop =
        elementBottom - this.listboxNode.clientHeight;
    } else if (selectedOption.offsetTop < this.listboxNode.scrollTop) {
      this.listboxNode.scrollTop = selectedOption.offsetTop;
    }
  }
};

/**
 * @description
 *  Enable/disable the up/down arrows based on the activeDescendant.
 */
aria.Listbox.prototype.checkUpDownButtons = function () {
  var activeElement = document.getElementById(this.activeDescendant);

  if (!this.moveUpDownEnabled) {
    return;
  }

  if (!activeElement) {
    this.upButton.setAttribute('aria-disabled', 'true');
    this.downButton.setAttribute('aria-disabled', 'true');
    return;
  }

  if (this.upButton) {
    if (activeElement.previousElementSibling) {
      this.upButton.setAttribute('aria-disabled', false);
    } else {
      this.upButton.setAttribute('aria-disabled', 'true');
    }
  }

  if (this.downButton) {
    if (activeElement.nextElementSibling) {
      this.downButton.setAttribute('aria-disabled', false);
    } else {
      this.downButton.setAttribute('aria-disabled', 'true');
    }
  }
};

/**
 * @description
 *  Add the specified items to the listbox. Assumes items are valid options.
 * @param items
 *  An array of items to add to the listbox
 */
aria.Listbox.prototype.addItems = function (items) {
  if (!items || !items.length) {
    return;
  }

  items.forEach(
    function (item) {
      this.defocusItem(item);
      this.toggleSelectItem(item);
      this.listboxNode.append(item);
    }.bind(this)
  );

  if (!this.activeDescendant) {
    this.focusItem(items[0]);
  }

  this.handleItemChange('added', items);
};

/**
 * @description
 *  Remove all of the selected items from the listbox; Removes the focused items
 *  in a single select listbox and the items with aria-selected in a multi
 *  select listbox.
 * @returns {Array}
 *  An array of items that were removed from the listbox
 */
aria.Listbox.prototype.deleteItems = function () {
  var itemsToDelete;

  if (this.multiselectable) {
    itemsToDelete = this.listboxNode.querySelectorAll('[aria-selected="true"]');
  } else if (this.activeDescendant) {
    itemsToDelete = [document.getElementById(this.activeDescendant)];
  }

  if (!itemsToDelete || !itemsToDelete.length) {
    return [];
  }

  itemsToDelete.forEach(
    function (item) {
      item.remove();

      if (item.id === this.activeDescendant) {
        this.clearActiveDescendant();
      }
    }.bind(this)
  );

  this.handleItemChange('removed', itemsToDelete);

  return itemsToDelete;
};

aria.Listbox.prototype.clearActiveDescendant = function () {
  this.activeDescendant = null;
  this.listboxNode.setAttribute('aria-activedescendant', null);

  this.updateMoveButton();
  this.checkUpDownButtons();
};

/**
 * @description
 *  Shifts the currently focused item up on the list. No shifting occurs if the
 *  item is already at the top of the list.
 */
aria.Listbox.prototype.moveUpItems = function () {
  if (!this.activeDescendant) {
    return;
  }

  var currentItem = document.getElementById(this.activeDescendant);
  var previousItem = currentItem.previousElementSibling;

  if (previousItem) {
    this.listboxNode.insertBefore(currentItem, previousItem);
    this.handleItemChange('moved_up', [currentItem]);
  }

  this.checkUpDownButtons();
};

/**
 * @description
 *  Shifts the currently focused item down on the list. No shifting occurs if
 *  the item is already at the end of the list.
 */
aria.Listbox.prototype.moveDownItems = function () {
  if (!this.activeDescendant) {
    return;
  }

  var currentItem = document.getElementById(this.activeDescendant);
  var nextItem = currentItem.nextElementSibling;

  if (nextItem) {
    this.listboxNode.insertBefore(nextItem, currentItem);
    this.handleItemChange('moved_down', [currentItem]);
  }

  this.checkUpDownButtons();
};

/**
 * @description
 *  Delete the currently selected items and add them to the sibling list.
 */
aria.Listbox.prototype.moveItems = function () {
  if (!this.siblingList) {
    return;
  }

  var itemsToMove = this.deleteItems();
  this.siblingList.addItems(itemsToMove);
};

/**
 * @description
 *  Enable Up/Down controls to shift items up and down.
 * @param upButton
 *   Up button to trigger up shift
 * @param downButton
 *   Down button to trigger down shift
 */
aria.Listbox.prototype.enableMoveUpDown = function (upButton, downButton) {
  this.moveUpDownEnabled = true;
  this.upButton = upButton;
  this.downButton = downButton;
  upButton.addEventListener('click', this.moveUpItems.bind(this));
  downButton.addEventListener('click', this.moveDownItems.bind(this));
};

/**
 * @description
 *  Enable Move controls. Moving removes selected items from the current
 *  list and adds them to the sibling list.
 * @param button
 *   Move button to trigger delete
 * @param siblingList
 *   Listbox to move items to
 */
aria.Listbox.prototype.setupMove = function (button, siblingList) {
  this.siblingList = siblingList;
  this.moveButton = button;
  button.addEventListener('click', this.moveItems.bind(this));
};

aria.Listbox.prototype.setHandleItemChange = function (handlerFn) {
  this.handleItemChange = handlerFn;
};

aria.Listbox.prototype.setHandleFocusChange = function (focusChangeHandler) {
  this.handleFocusChange = focusChangeHandler;
};
/*
 *   This content is licensed according to the W3C Software License at
 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
 */

/* global aria */

'use strict';

/**
 * ARIA Scrollable Listbox Example
 *
 * @function onload
 * @description Initialize the listbox example once the page has loaded
 */

window.addEventListener('load', function () {
  new aria.Listbox(document.getElementById('ss_elem_list'));
});
'use strict';
/**
 * @namespace aria
 */

var aria = aria || {};

/**
 * @description
 *  Key code constants
 */
aria.KeyCode = {
  BACKSPACE: 8,
  TAB: 9,
  RETURN: 13,
  SHIFT: 16,
  ESC: 27,
  SPACE: 32,
  PAGE_UP: 33,
  PAGE_DOWN: 34,
  END: 35,
  HOME: 36,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46,
};

aria.Utils = aria.Utils || {};

// Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
aria.Utils.matches = function (element, selector) {
  if (!Element.prototype.matches) {
    Element.prototype.matches =
      Element.prototype.matchesSelector ||
      Element.prototype.mozMatchesSelector ||
      Element.prototype.msMatchesSelector ||
      Element.prototype.oMatchesSelector ||
      Element.prototype.webkitMatchesSelector ||
      function (s) {
        var matches = element.parentNode.querySelectorAll(s);
        var i = matches.length;
        while (--i >= 0 && matches.item(i) !== this) {
          // empty
        }
        return i > -1;
      };
  }

  return element.matches(selector);
};

aria.Utils.remove = function (item) {
  if (item.remove && typeof item.remove === 'function') {
    return item.remove();
  }
  if (
    item.parentNode &&
    item.parentNode.removeChild &&
    typeof item.parentNode.removeChild === 'function'
  ) {
    return item.parentNode.removeChild(item);
  }
  return false;
};

aria.Utils.isFocusable = function (element) {
  if (element.tabIndex < 0) {
    return false;
  }

  if (element.disabled) {
    return false;
  }

  switch (element.nodeName) {
    case 'A':
      return !!element.href && element.rel != 'ignore';
    case 'INPUT':
      return element.type != 'hidden';
    case 'BUTTON':
    case 'SELECT':
    case 'TEXTAREA':
      return true;
    default:
      return false;
  }
};

aria.Utils.getAncestorBySelector = function (element, selector) {
  if (!aria.Utils.matches(element, selector + ' ' + element.tagName)) {
    // Element is not inside an element that matches selector
    return null;
  }

  // Move up the DOM tree until a parent matching the selector is found
  var currentNode = element;
  var ancestor = null;
  while (ancestor === null) {
    if (aria.Utils.matches(currentNode.parentNode, selector)) {
      ancestor = currentNode.parentNode;
    } else {
      currentNode = currentNode.parentNode;
    }
  }

  return ancestor;
};

aria.Utils.hasClass = function (element, className) {
  return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
};

aria.Utils.addClass = function (element, className) {
  if (!aria.Utils.hasClass(element, className)) {
    element.className += ' ' + className;
  }
};

aria.Utils.removeClass = function (element, className) {
  var classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)');
  element.className = element.className.replace(classRegex, ' ').trim();
};

aria.Utils.bindMethods = function (object /* , ...methodNames */) {
  var methodNames = Array.prototype.slice.call(arguments, 1);
  methodNames.forEach(function (method) {
    object[method] = object[method].bind(object);
  });
};


/*
 *   This content is licensed according to the W3C Software License at
 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
 */

'use strict';

// Save a list of named combobox actions, for future readability
const SelectActions = {
  Close: 0,
  CloseSelect: 1,
  First: 2,
  Last: 3,
  Next: 4,
  Open: 5,
  PageDown: 6,
  PageUp: 7,
  Previous: 8,
  Select: 9,
  Type: 10,
};

/*
 * Helper functions
 */

// filter an array of options against an input string
// returns an array of options that begin with the filter string, case-independent
function filterOptions(options = [], filter, exclude = []) {
  return options.filter((option) => {
    const matches = option.toLowerCase().indexOf(filter.toLowerCase()) === 0;
    return matches && exclude.indexOf(option) < 0;
  });
}

// map a key press to an action
function getActionFromKey(event, menuOpen) {
  const { key, altKey, ctrlKey, metaKey } = event;
  const openKeys = ['ArrowDown', 'ArrowUp', 'Enter', ' ']; // all keys that will do the default open action
  // handle opening when closed
  if (!menuOpen && openKeys.includes(key)) {
    return SelectActions.Open;
  }

  // home and end move the selected option when open or closed
  if (key === 'Home') {
    return SelectActions.First;
  }
  if (key === 'End') {
    return SelectActions.Last;
  }

  // handle typing characters when open or closed
  if (
    key === 'Backspace' ||
    key === 'Clear' ||
    (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)
  ) {
    return SelectActions.Type;
  }

  // handle keys when open
  if (menuOpen) {
    if (key === 'ArrowUp' && altKey) {
      return SelectActions.CloseSelect;
    } else if (key === 'ArrowDown' && !altKey) {
      return SelectActions.Next;
    } else if (key === 'ArrowUp') {
      return SelectActions.Previous;
    } else if (key === 'PageUp') {
      return SelectActions.PageUp;
    } else if (key === 'PageDown') {
      return SelectActions.PageDown;
    } else if (key === 'Escape') {
      return SelectActions.Close;
    } else if (key === 'Enter' || key === ' ') {
      return SelectActions.CloseSelect;
    }
  }
}

// return the index of an option from an array of options, based on a search string
// if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
function getIndexByLetter(options, filter, startIndex = 0) {
  const orderedOptions = [
    ...options.slice(startIndex),
    ...options.slice(0, startIndex),
  ];
  const firstMatch = filterOptions(orderedOptions, filter)[0];
  const allSameLetter = (array) => array.every((letter) => letter === array[0]);

  // first check if there is an exact match for the typed string
  if (firstMatch) {
    return options.indexOf(firstMatch);
  }

  // if the same letter is being repeated, cycle through first-letter matches
  else if (allSameLetter(filter.split(''))) {
    const matches = filterOptions(orderedOptions, filter[0]);
    return options.indexOf(matches[0]);
  }

  // if no matches, return -1
  else {
    return -1;
  }
}

// get an updated option index after performing an action
function getUpdatedIndex(currentIndex, maxIndex, action) {
  const pageSize = 10; // used for pageup/pagedown

  switch (action) {
    case SelectActions.First:
      return 0;
    case SelectActions.Last:
      return maxIndex;
    case SelectActions.Previous:
      return Math.max(0, currentIndex - 1);
    case SelectActions.Next:
      return Math.min(maxIndex, currentIndex + 1);
    case SelectActions.PageUp:
      return Math.max(0, currentIndex - pageSize);
    case SelectActions.PageDown:
      return Math.min(maxIndex, currentIndex + pageSize);
    default:
      return currentIndex;
  }
}

// check if element is visible in browser view port
function isElementInView(element) {
  var bounding = element.getBoundingClientRect();

  return (
    bounding.top >= 0 &&
    bounding.left >= 0 &&
    bounding.bottom <=
      (window.innerHeight || document.documentElement.clientHeight) &&
    bounding.right <=
      (window.innerWidth || document.documentElement.clientWidth)
  );
}

// check if an element is currently scrollable
function isScrollable(element) {
  return element && element.clientHeight < element.scrollHeight;
}

// ensure a given child element is within the parent's visible scroll area
// if the child is not visible, scroll the parent
function maintainScrollVisibility(activeElement, scrollParent) {
  const { offsetHeight, offsetTop } = activeElement;
  const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent;

  const isAbove = offsetTop < scrollTop;
  const isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight;

  if (isAbove) {
    scrollParent.scrollTo(0, offsetTop);
  } else if (isBelow) {
    scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight);
  }
}

/*
 * Select Component
 * Accepts a combobox element and an array of string options
 */
const Select = function (el, options = []) {
  // element refs
  this.el = el;
  this.comboEl = el.querySelector('[role=combobox]');
  this.listboxEl = el.querySelector('[role=listbox]');

  // data
  this.idBase = this.comboEl.id || 'combo';
  this.options = options;

  // state
  this.activeIndex = 0;
  this.open = false;
  this.searchString = '';
  this.searchTimeout = null;

  // init
  if (el && this.comboEl && this.listboxEl) {
    this.init();
  }
};

Select.prototype.init = function () {
  // select first option by default
  this.comboEl.innerHTML = this.options[0];

  // add event listeners
  this.comboEl.addEventListener('blur', this.onComboBlur.bind(this));
  this.comboEl.addEventListener('click', this.onComboClick.bind(this));
  this.comboEl.addEventListener('keydown', this.onComboKeyDown.bind(this));

  // create options
  this.options.map((option, index) => {
    const optionEl = this.createOption(option, index);
    this.listboxEl.appendChild(optionEl);
  });
};

Select.prototype.createOption = function (optionText, index) {
  const optionEl = document.createElement('div');
  optionEl.setAttribute('role', 'option');
  optionEl.id = `${this.idBase}-${index}`;
  optionEl.className =
    index === 0 ? 'combo-option option-current' : 'combo-option';
  optionEl.setAttribute('aria-selected', `${index === 0}`);
  optionEl.innerText = optionText;

  optionEl.addEventListener('click', (event) => {
    event.stopPropagation();
    this.onOptionClick(index);
  });
  optionEl.addEventListener('mousedown', this.onOptionMouseDown.bind(this));

  return optionEl;
};

Select.prototype.getSearchString = function (char) {
  // reset typing timeout and start new timeout
  // this allows us to make multiple-letter matches, like a native select
  if (typeof this.searchTimeout === 'number') {
    window.clearTimeout(this.searchTimeout);
  }

  this.searchTimeout = window.setTimeout(() => {
    this.searchString = '';
  }, 500);

  // add most recent letter to saved search string
  this.searchString += char;
  return this.searchString;
};

Select.prototype.onComboBlur = function () {
  // do not do blur action if ignoreBlur flag has been set
  if (this.ignoreBlur) {
    this.ignoreBlur = false;
    return;
  }

  // select current option and close
  if (this.open) {
    this.selectOption(this.activeIndex);
    this.updateMenuState(false, false);
  }
};

Select.prototype.onComboClick = function () {
  this.updateMenuState(!this.open, false);
};

Select.prototype.onComboKeyDown = function (event) {
  const { key } = event;
  const max = this.options.length - 1;

  const action = getActionFromKey(event, this.open);

  switch (action) {
    case SelectActions.Last:
    case SelectActions.First:
      this.updateMenuState(true);
    // intentional fallthrough
    case SelectActions.Next:
    case SelectActions.Previous:
    case SelectActions.PageUp:
    case SelectActions.PageDown:
      event.preventDefault();
      return this.onOptionChange(
        getUpdatedIndex(this.activeIndex, max, action)
      );
    case SelectActions.CloseSelect:
      event.preventDefault();
      this.selectOption(this.activeIndex);
    // intentional fallthrough
    case SelectActions.Close:
      event.preventDefault();
      return this.updateMenuState(false);
    case SelectActions.Type:
      return this.onComboType(key);
    case SelectActions.Open:
      event.preventDefault();
      return this.updateMenuState(true);
  }
};

Select.prototype.onComboType = function (letter) {
  // open the listbox if it is closed
  this.updateMenuState(true);

  // find the index of the first matching option
  const searchString = this.getSearchString(letter);
  const searchIndex = getIndexByLetter(
    this.options,
    searchString,
    this.activeIndex + 1
  );

  // if a match was found, go to it
  if (searchIndex >= 0) {
    this.onOptionChange(searchIndex);
  }
  // if no matches, clear the timeout and search string
  else {
    window.clearTimeout(this.searchTimeout);
    this.searchString = '';
  }
};

Select.prototype.onOptionChange = function (index) {
  // update state
  this.activeIndex = index;

  // update aria-activedescendant
  this.comboEl.setAttribute('aria-activedescendant', `${this.idBase}-${index}`);

  // update active option styles
  const options = this.el.querySelectorAll('[role=option]');
  [...options].forEach((optionEl) => {
    optionEl.classList.remove('option-current');
  });
  options[index].classList.add('option-current');

  // ensure the new option is in view
  if (isScrollable(this.listboxEl)) {
    maintainScrollVisibility(options[index], this.listboxEl);
  }

  // ensure the new option is visible on screen
  // ensure the new option is in view
  if (!isElementInView(options[index])) {
    options[index].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  }
};

Select.prototype.onOptionClick = function (index) {
  this.onOptionChange(index);
  this.selectOption(index);
  this.updateMenuState(false);
};

Select.prototype.onOptionMouseDown = function () {
  // Clicking an option will cause a blur event,
  // but we don't want to perform the default keyboard blur action
  this.ignoreBlur = true;
};

Select.prototype.selectOption = function (index) {
  // update state
  this.activeIndex = index;

  // update displayed value
  const selected = this.options[index];
  this.comboEl.innerHTML = selected;

  // update aria-selected
  const options = this.el.querySelectorAll('[role=option]');
  [...options].forEach((optionEl) => {
    optionEl.setAttribute('aria-selected', 'false');
  });
  options[index].setAttribute('aria-selected', 'true');
};

Select.prototype.updateMenuState = function (open, callFocus = true) {
  if (this.open === open) {
    return;
  }

  // update state
  this.open = open;

  // update aria-expanded and styles
  this.comboEl.setAttribute('aria-expanded', `${open}`);
  open ? this.el.classList.add('open') : this.el.classList.remove('open');

  // update activedescendant
  const activeID = open ? `${this.idBase}-${this.activeIndex}` : '';
  this.comboEl.setAttribute('aria-activedescendant', activeID);

  if (activeID === '' && !isElementInView(this.comboEl)) {
    this.comboEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  }

  // move focus back to the combobox, if needed
  callFocus && this.comboEl.focus();
};

// init select
window.addEventListener('load', function () {
  const options = [
    'Choose a Fruit',
    'Apple',
    'Banana',
    'Blueberry',
    'Boysenberry',
    'Cherry',
    'Cranberry',
    'Durian',
    'Eggplant',
    'Fig',
    'Grape',
    'Guava',
    'Huckleberry',
  ];
  const selectEls = document.querySelectorAll('.js-select');

  selectEls.forEach((el) => {
    new Select(el, options);
  });
});

              
            
!
999px

Console