PageSourceSearch

https://www.gresb.com/wp-content/themes/framework/assets/js/components/_1_custom-select.js?ver=1790907449

js gresb.com collected 2026-10-02 02:36:15 UTC 11,570 bytes, 259 lines download raw bytes

1// File#: _1_custom-select
2// Usage: codyhouse.co/license
3(function() {
4  // NOTE: you need the js code only when using the --custom-dropdown variation of the Custom Select component. Default version does nor require JS.
5  
6  var CustomSelect = function(element) {
7    this.element = element;
8    this.select = this.element.getElementsByTagName('select')[0];
9    this.optGroups = this.select.getElementsByTagName('optgroup');
10    this.options = this.select.getElementsByTagName('option');
11    this.selectedOption = getSelectedOptionText(this);
12    this.selectId = this.select.getAttribute('id');
13    this.trigger = false;
14    this.dropdown = false;
15    this.customOptions = false;
16    this.arrowIcon = this.element.getElementsByTagName('svg');
17    this.label = document.querySelector('[for="'+this.selectId+'"]');
18
19    this.optionIndex = 0; // used while building the custom dropdown
20
21    initCustomSelect(this); // init markup
22    initCustomSelectEvents(this); // init event listeners
23  };
24  
25  function initCustomSelect(select) {
26    // create the HTML for the custom dropdown element
27    select.element.insertAdjacentHTML('beforeend', initButtonSelect(select) + initListSelect(select));
28    
29    // save custom elements
30    select.dropdown = select.element.getElementsByClassName('js-select__dropdown')[0];
31    select.trigger = select.element.getElementsByClassName('js-select__button')[0];
32    select.customOptions = select.dropdown.getElementsByClassName('js-select__item');
33    
34    // hide default select
35    Util.addClass(select.select, 'is-hidden');
36    if(select.arrowIcon.length > 0 ) select.arrowIcon[0].style.display = 'none';
37
38    // place dropdown
39    placeDropdown(select);
40  };
41
42  function initCustomSelectEvents(select) {
43    // option selection in dropdown
44    initSelection(select);
45
46    // click events
47    select.trigger.addEventListener('click', function(){
48      toggleCustomSelect(select, false);
49    });
50    if(select.label) {
51      // move focus to custom trigger when clicking on <select> label
52      select.label.addEventListener('click', function(){
53        Util.moveFocus(select.trigger);
54      });
55    }
56    // keyboard navigation
57    select.dropdown.addEventListener('keydown', function(event){
58      if(event.keyCode && event.keyCode == 38 || event.key && event.key.toLowerCase() == 'arrowup') {
59        keyboardCustomSelect(select, 'prev', event);
60      } else if(event.keyCode && event.keyCode == 40 || event.key && event.key.toLowerCase() == 'arrowdown') {
61        keyboardCustomSelect(select, 'next', event);
62      }
63    });
64    // native <select> element has been updated -> update custom select as well
65    select.element.addEventListener('select-updated', function(event){
66      resetCustomSelect(select);
67    });
68  };
69
70  function toggleCustomSelect(select, bool) {
71    var ariaExpanded;
72    if(bool) {
73      ariaExpanded = bool;
74    } else {
75      ariaExpanded = select.trigger.getAttribute('aria-expanded') == 'true' ? 'false' : 'true';
76    }
77    select.trigger.setAttribute('aria-expanded', ariaExpanded);
78    if(ariaExpanded == 'true') {
79      var selectedOption = getSelectedOption(select);
80      Util.moveFocus(selectedOption); // fallback if transition is not supported
81      select.dropdown.addEventListener('transitionend', function cb(){
82        Util.moveFocus(selectedOption);
83        select.dropdown.removeEventListener('transitionend', cb);
84      });
85      placeDropdown(select); // place dropdown based on available space
86    }
87  };
88
89  function placeDropdown(select) {
90    // remove placement classes to reset position
91    Util.removeClass(select.dropdown, 'select__dropdown--right select__dropdown--up');
92    var triggerBoundingRect = select.trigger.getBoundingClientRect();
93    Util.toggleClass(select.dropdown, 'select__dropdown--right', (document.documentElement.clientWidth - 5 < triggerBoundingRect.left + select.dropdown.offsetWidth));
94    // check if there's enough space up or down
95    var moveUp = (window.innerHeight - triggerBoundingRect.bottom - 5) < triggerBoundingRect.top;
96    Util.toggleClass(select.dropdown, 'select__dropdown--up', moveUp);
97    // check if we need to set a max width
98    var maxHeight = moveUp ? triggerBoundingRect.top - 20 : window.innerHeight - triggerBoundingRect.bottom - 20;
99    // set max-height based on available space
100    select.dropdown.setAttribute('style', 'max-height: '+maxHeight+'px; width: '+triggerBoundingRect.width+'px;');
101  };
102
103  function keyboardCustomSelect(select, direction, event) { // navigate custom dropdown with keyboard
104    event.preventDefault();
105    var index = Util.getIndexInArray(select.customOptions, document.activeElement);
106    index = (direction == 'next') ? index + 1 : index - 1;
107    if(index < 0) index = select.customOptions.length - 1;
108    if(index >= select.customOptions.length) index = 0;
109    Util.moveFocus(select.customOptions[index]);
110  };
111
112  function initSelection(select) { // option selection
113    select.dropdown.addEventListener('click', function(event){
114      var option = event.target.closest('.js-select__item');
115      if(!option) return;
116      selectOption(select, option);
117    });
118  };
119  
120  function selectOption(select, option) {
121    if(option.hasAttribute('aria-selected') && option.getAttribute('aria-selected') == 'true') {
122      // selecting the same option
123      select.trigger.setAttribute('aria-expanded', 'false'); // hide dropdown
124    } else { 
125      var selectedOption = select.dropdown.querySelector('[aria-selected="true"]');
126      if(selectedOption) selectedOption.setAttribute('aria-selected', 'false');
127      option.setAttribute('aria-selected', 'true');
128      select.trigger.getElementsByClassName('js-select__label')[0].textContent = option.textContent;
129      select.trigger.setAttribute('aria-expanded', 'false');
130      // new option has been selected -> update native <select> element _ arai-label of trigger <button>
131      updateNativeSelect(select, option.getAttribute('data-index'));
132      updateTriggerAria(select); 
133    }
134    // move focus back to trigger
135    select.trigger.focus();
136  };
137
138  function updateNativeSelect(select, index) {
139    select.select.selectedIndex = index;
140    select.select.dispatchEvent(new CustomEvent('change', {bubbles: true})); // trigger change event
141  };
142
143  function updateTriggerAria(select) {
144    select.trigger.setAttribute('aria-label', select.options[select.select.selectedIndex].innerHTML+', '+select.label.textContent);
145  };
146
147  function getSelectedOptionText(select) {// used to initialize the label of the custom select button
148    var label = '';
149    if('selectedIndex' in select.select) {
150      label = select.options[select.select.selectedIndex].text;
151    } else {
152      label = select.select.querySelector('option[selected]').text;
153    }
154    return label;
155
156  };
157  
158  function initButtonSelect(select) { // create the button element -> custom select trigger
159    // check if we need to add custom classes to the button trigger
160    var customClasses = select.element.getAttribute('data-trigger-class') ? ' '+select.element.getAttribute('data-trigger-class') : '';
161
162    var label = select.options[select.select.selectedIndex].innerHTML+', '+select.label.textContent;
163  
164    var button = '<button type="button" class="js-select__button select__button'+customClasses+'" aria-label="'+label
164+'" aria-expanded="false" aria-controls="'+select.selectId+'-dropdown"><span aria-hidden="true" class="js-select__label select__label">'+select.selectedOption+'</span>';
165    if(select.arrowIcon.length > 0 && select.arrowIcon[0].outerHTML) {
166      var clone = select.arrowIcon[0].cloneNode(true);
167      Util.removeClass(clone, 'select__icon');
168      button = button +clone.outerHTML;
169    }
170    
171    return button+'</button>';
172
173  };
174
175  function initListSelect(select) { // create custom select dropdown
176    var list = '<div class="js-select__dropdown select__dropdown" aria-describedby="'+select.selectId+'-description" id="'+select.selectId+'-dropdown">';
177    list = list + getSelectLabelSR(select);
178    if(select.optGroups.length > 0) {
179      for(var i = 0; i < select.optGroups.length; i++) {
180        var optGroupList = select.optGroups[i].getElementsByTagName('option'),
181          optGroupLabel = '<li><span class="select__item select__item--optgroup">'+select.optGroups[i].getAttribute('label')+'</span></li>';
182        list = list + '<ul class="select__list" role="listbox">'+optGroupLabel+getOptionsList(select, optGroupList) + '</ul>';
183      }
184    } else {
185      list = list + '<ul class="select__list" role="listbox">'+getOptionsList(select, select.options) + '</ul>';
186    }
187    return list;
188  };
189
190  function getSelectLabelSR(select) {
191    if(select.label) {
192      return '<p class="sr-only" id="'+select.selectId+'-description">'+select.label.textContent+'</p>'
193    } else {
194      return '';
195    }
196  };
197  
198  function resetCustomSelect(select) {
199    // <select> element has been updated (using an external control) - update custom select
200    var selectedOption = select.dropdown.querySelector('[aria-selected="true"]');
201    if(selectedOption) selectedOption.setAttribute('aria-selected', 'false');
202    var option = select.dropdown.querySelector('.js-select__item[data-index="'+select.select.selectedIndex+'"]');
203    option.setAttribute('aria-selected', 'true');
204    select.trigger.getElementsByClassName('js-select__label')[0].textContent = option.textContent;
205    select.trigger.setAttribute('aria-expanded', 'false');
206    updateTriggerAria(select); 
207  };
208
209  function getOptionsList(select, options) {
210    var list = '';
211    for(var i = 0; i < options.length; i++) {
212      var selected = options[i].hasAttribute('selected') ? ' aria-selected="true"' : ' aria-selected="false"';
213      list = list + '<li><button type="button" class="reset js-select__item select__item select__item--option" role="option" data-value="'+options[i].value+'" '+selected+' data-index="'+select.optionIndex+'">'+options[i].text+'</button></li>';
214      select.optionIndex = select.optionIndex + 1;
215    };
216    return list;
217  };
218
219  function getSelectedOption(select) {
220    var option = select.dropdown.querySelector('[aria-selected="true"]');
221    if(option) return option;
222    else return select.dropdown.getElementsByClassName('js-select__item')[0];
223  };
224
225  function moveFocusToSelectTrigger(select) {
226    if(!document.activeElement.closest('.js-select')) return
227    select.trigger.focus();
228  };
229  
230  function checkCustomSelectClick(select, target) { // close select when clicking outside it
231    if( !select.element.contains(target) ) toggleCustomSelect(select, 'false');
232  };
233  
234  //initialize the CustomSelect objects
235  var customSelect = document.getElementsByClassName('js-select');
236  if( customSelect.length > 0 ) {
237    var selectArray = [];
238    for( var i = 0; i < customSelect.length; i++) {
239      (function(i){selectArray.push(new CustomSelect(customSelect[i]));})(i);
240    }
241
242    // listen for key events
243    window.addEventListener('keyup', function(event){
244      if( event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape' ) {
245        // close custom select on 'Esc'
246        selectArray.forEach(function(element){
247          moveFocusToSelectTrigger(element); // if focus is within dropdown, move it to dropdown trigger
248          toggleCustomSelect(element, 'false'); // close dropdown
249        });
250      } 
251    });
252    // close custom select when clicking outside it
253    window.addEventListener('click', function(event){
254      selectArray.forEach(function(element){
255        checkCustomSelectClick(element, event.target);
256      });
257    });
258  }
259}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.