1// UMD module from From https://github.com/umdjs/umd/blob/master/returnExports.js 2// From 'if the module has no dependencies' example. 3(function (root, factory) { 4 if (typeof define === 'function' && define.amd) { 5 // AMD. Register as an anonymous module. 6 define([], factory); 7 } else if (typeof exports === 'object') { 8 // Node. Does not work with strict CommonJS, but 9 // only CommonJS-like environments that support module.exports, 10 // like Node. 11 module.exports = factory(); 12 } else { 13 // Browser globals (root is window) 14 root.styleSelect = factory(); 15 } 16}(this, function () { 17// End of UMD module 18 19 // Quick aliases and polyfills if needed 20 var query = document.querySelector.bind(document); 21 var queryAll = document.querySelectorAll.bind(document); 22 23 var KEYCODES = { 24 SPACE: 32, 25 UP: 38, 26 DOWN: 40, 27 ENTER: 13 28 }; 29 30 if ( ! NodeList.prototype.forEach ) { 31 NodeList.prototype.forEach = Array.prototype.forEach; 32 } 33 if ( ! HTMLCollection.prototype.forEach ) { 34 HTMLCollection.prototype.forEach = Array.prototype.forEach; 35 } 36 if ( ! Element.prototype.matches ) { 37 // See https://developer.mozilla.org/en-US/docs/Web/API/Element.matches 38 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector 39 } 40 41 // IE 9-11 CustomEvent polyfill 42 // From https://developer.mozilla.org/en/docs/Web/API/CustomEvent 43 var CustomEvent = function( eventName, params ) { 44 params = params || { bubbles: false, cancelable: false, detail: undefined }; 45 var event = document.createEvent( 'CustomEvent' ); 46 event.initCustomEvent( eventName, params.bubbles, params.cancelable, params.detail ); 47 return event; 48 }; 49 CustomEvent.prototype = window.Event.prototype; 50 window.CustomEvent = CustomEvent; 51 52 // IE10 dataset polyfill 53 // From https://gist.githubusercontent.com/brettz9/4093766/raw/ba31a05e7ce21af67c6cafee9b3f439c86e95b01/html5-dataset.js 54 if (!document.documentElement.dataset && 55 // FF is empty while IE gives empty object 56 (!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset') || 57 !Object.getOwnPropertyDescriptor(Element.prototype, 'dataset').get) 58 ) { 59 var propDescriptor = { 60 enumerable: true, 61 get: function () { 62 'use strict'; 63 var i, 64 that = this, 65 HTML5_DOMStringMap, 66 attrVal, attrName, propName, 67 attribute, 68 attributes = this.attributes, 69 attsLength = attributes.length, 70 toUpperCase = function (n0) { 71 return n0.charAt(1).toUpperCase(); 72 }, 73 getter = function () { 74 return this; 75 }, 76 setter = function (attrName, value) { 77 return (typeof value !== 'undefined') ? 78 this.setAttribute(attrName, value) : 79 this.removeAttribute(attrName); 80 };
81 try { // Simulate DOMStringMap w/accessor support 82 // Test setting accessor on normal object 83 ({}).__defineGetter__('test', function () {}); 84 HTML5_DOMStringMap = {}; 85 } 86 catch (e1) { // Use a DOM object for IE8 87 HTML5_DOMStringMap = document.createElement('div'); 88 } 89 for (i = 0; i < attsLength; i++) { 90 attribute = attributes[i]; 91 // Fix: This test really should allow any XML Name without 92 // colons (and non-uppercase for XHTML) 93 if (attribute && attribute.name && 94 (/^data-\w[\w\-]*$/).test(attribute.name)) { 95 attrVal = attribute.value; 96 attrName = attribute.name; 97 // Change to CamelCase 98 propName = attrName.substr(5).replace(/-./g, toUpperCase); 99 try { 100 Object.defineProperty(HTML5_DOMStringMap, propName, { 101 enumerable: this.enumerable, 102 get: getter.bind(attrVal || ''), 103 set: setter.bind(that, attrName) 104 }); 105 } 106 catch (e2) { // if accessors are not working 107 HTML5_DOMStringMap[propName] = attrVal; 108 } 109 } 110 } 111 return HTML5_DOMStringMap; 112 } 113 }; 114 try { 115 // FF enumerates over element's dataset, but not 116 // Element.prototype.dataset; IE9 iterates over both 117 Object.defineProperty(Element.prototype, 'dataset', propDescriptor); 118 } catch (e) { 119 propDescriptor.enumerable = false; // IE8 does not allow setting to true 120 Object.defineProperty(Element.prototype, 'dataset', propDescriptor); 121 } 122 } 123 124 // Return true if any ancestor matches selector 125 // Borrowed from ancestorMatches() from agave.js (MIT) 126 var isAncestorOf = function(element, selector, includeSelf) { 127 var parent = element.parentNode; 128 if ( includeSelf && element.matches(selector) ) { 129 return true 130 } 131 // While parents are 'element' type nodes 132 // See https://developer.mozilla.org/en-US/docs/DOM/Node.nodeType 133 while ( parent && parent.nodeType && parent.nodeType === 1 ) { 134 if ( parent.matches(selector) ) { 135 return true 136 } 137 parent = parent.parentNode; 138 } 139 return false; 140 }; 141 142 143 // Used to match select boxes to their style select partners 144 var makeUUID = function(){ 145 return 'ss-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function (c) { 146 var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8; 147 return v.toString(16); 148 }) 149 }; 150 151 152 // The 'styleSelect' main function 153 // selector:String - CSS selector for the select box to style 154 return function(selector) { 155 156 // Use native selects (which pop up large native UIs to go through the options ) on iOS/Android 157 if ( navigator.userAgent.match( /iPad|iPhone|Android/i ) ) { 158 return 159 } 160 161 var realSelect = typeof selector == 'object' ? selector : query(selector), 162 realOptions = realSelect.children, 163 selectedIndex = realSelect.selectedIndex, 164 uuid = makeUUID(), 165 styleSelectHTML = '<div class="style-select" aria-hidden="true" data-ss-uuid="' + uuid + '">'; 166 167 // The index of the item that's being highlighted by the mouse or keyboard 168 var highlightedOptionIndex; 169 var highlightedOptionIndexMax = realOptions.length - 1; 170 171 realSelect.setAttribute('data-ss-uuid', uuid); 172 // Even though the element is display: none, a11y users should still see it. 173 // According to http://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden 174 // some browsers may have bugs with this but future implementation may improve 175 realSelect.setAttribute('aria-hidden', "false"); 176 177 // Build styled clones of all the real options 178 var selectedOptionHTML; 179 var optionsHTML = '<div class="ss-dropdown">'; 180 realOptions.forEach(function(realOption, index){ 181 var text = realOption.textContent, 182 value = realOption.getAttribute('value') || '', 183 cssClass = 'ss-option'; 184 185 if (index === selectedIndex) { 186 // Mark first item as selected-option - this is where we store state for the styled select box 187 // aria-hidden=true so screen readers ignore the styles selext box in favor of the real one (which is visible by default) 188 selectedOptionHTML = '<div class="ss-selected-option" tabindex="0" data-value="' + value + '">
188' + text + '</div>' 189 } 190 191 if (realOption.disabled) { 192 cssClass += ' disabled'; 193 } 194 195 // Continue building optionsHTML 196 optionsHTML += '<div class="' + cssClass + '" data-value="' + value + '">' + text + '</div>'; 197 }); 198 optionsHTML += '</div>'; 199 styleSelectHTML += selectedOptionHTML += optionsHTML += '</div>'; 200 // And add out styled select just after the real select 201 realSelect.insertAdjacentHTML('afterend', styleSelectHTML); 202 203 var styledSelect = query('.style-select[data-ss-uuid="'+uuid+'"]'); 204 var styleSelectOptions = styledSelect.querySelectorAll('.ss-option'); 205 var selectedOption = styledSelect.querySelector('.ss-selected-option'); 206 207 var changeRealSelectBox = function(newValue, newLabel) { 208 // Close styledSelect 209 styledSelect.classList.remove('open'); 210 211 // Update styled value 212 selectedOption.textContent = newLabel; 213 selectedOption.dataset.value = newValue; 214 215 // Update the 'tick' that shows the option with the current value 216 styleSelectOptions.forEach(function(styleSelectOption){ 217 if ( styleSelectOption.dataset.value === newValue) { 218 styleSelectOption.classList.add('ticked') 219 } else { 220 styleSelectOption.classList.remove('ticked') 221 } 222 }); 223 224 // Update real select box 225 realSelect.value = newValue; 226 227 // Send 'change' event to real select - to trigger any change event listeners 228 var changeEvent = new CustomEvent('change'); 229 realSelect.dispatchEvent(changeEvent); 230 }; 231 232 // Change real select box when a styled option is clicked 233 styleSelectOptions.forEach(function(unused, index){ 234 var styleSelectOption = styleSelectOptions.item(index); 235 236 if (styleSelectOption.className.match(/\bdisabled\b/)) { 237 return; 238 } 239 240 styleSelectOption.addEventListener('click', function(ev) { 241 var target = ev.target, 242 styledSelectBox = target.parentNode.parentNode, 243 uuid = styledSelectBox.getAttribute('data-ss-uuid'), 244 newValue = target.getAttribute('data-value'), 245 newLabel = target.textContent; 246 247 changeRealSelectBox(newValue, newLabel) 248 249 }); 250 251 // Tick and highlight the option that's currently in use 252 if ( styleSelectOption.dataset.value === realSelect.value ) { 253 highlightedOptionIndex = index; 254 styleSelectOption.classList.add('ticked'); 255 styleSelectOption.classList.add('highlighted') 256 } 257 258 // Important: we can't use ':hover' as the keyboard and default value can also set the highlight 259 styleSelectOption.addEventListener('mouseover', function(ev){ 260 styleSelectOption.parentNode.childNodes.forEach(function(sibling, index){ 261 if ( sibling === ev.target ) { 262 sibling.classList.add('highlighted'); 263 highlightedOptionIndex = index; 264 } else { 265 sibling.classList.remove('highlighted') 266 } 267 }) 268 }) 269 }); 270 271 272 273 var closeAllStyleSelects = function(exception){ 274 queryAll('.style-select').forEach(function(styleSelectEl) { 275 if ( styleSelectEl !== exception ) { 276 styleSelectEl.classList.remove('open'); 277 } 278 }); 279 }; 280 281 var toggleStyledSelect = function(styledSelectBox){ 282 if ( ! styledSelectBox.classList.contains('open') ) { 283 // If we're closed and about to open, close other style selects on the page 284 closeAllStyleSelects(styledSelectBox); 285 } 286 // Then toggle open/close 287 styledSelectBox.classList.toggle('open'); 288 }; 289 290 // When a styled select box is clicked 291 var styledSelectedOption = query('.style-select[data-ss-uuid="' + uuid + '"] .ss-selected-option'); 292 styledSelectedOption.addEventListener('click', function(ev) { 293 ev.preventDefault(); 294 ev.stopPropagation(); 295 toggleStyledSelect(ev.target.parentNode); 296 }); 297 298 // Keyboard handling 299 styledSelectedOption.addEventListener('keydown', function(ev) { 300 var styledSelectBox = ev.target.parentNode; 301 302 switch (ev.keyCode) { 303 case KEYCODES.SPACE: 304 // Space shows and hides styles select boxes 305 toggleStyledSelect(styledSelectBox); 306 break; 307 case KEYCODES.DOWN: 308 case KEYCODES.UP: 309 // Move the highlight up and down 310 if ( ! styledSelectBox.classList.contains('open') ) { 311 // If style select is not open, up/down should open it. 312 toggleStyledSelect(styledSelectBox); 313 } else { 314 // If style select is already open, these should change what the highlighted option is 315 if ( ev.keyCode === KEYCODES.UP ) { 316 // Up arrow moves earlier in list 317 if ( highlightedOptionIndex !== 0 ) { 318 highlightedOptionIndex = highlightedOptionIndex - 1 319 } 320 } else { 321 // Down arrow moves later in list 322 if ( highlightedOptionIndex < highlightedOptionIndexMax ) { 323 highlightedOptionIndex = highlightedOptionIndex + 1 324 } 325 } 326 styleSelectOptions.forEach(function(option, index){ 327 if ( index === highlightedOptionIndex ) { 328 option.classList.add('highlighted') 329 } else { 330 option.classList.remove('highlighted') 331 } 332 }) 333 } 334 ev.preventDefault(); 335 ev.stopPropagation(); 336 break; 337 // User has picked an item from the keyboard 338 case KEYCODES.ENTER: 339 var highlightedOption = styledSelectedOption.parentNode.querySelectorAll('.ss-option')[highlightedOptionIndex], 340 newValue = highlightedOption.dataset.value, 341 newLabel = highlightedOption.textContent; 342 343 changeRealSelectBox(newValue, newLabel); 344 ev.preventDefault(); 345 ev.stopPropagation(); 346 break; 347 } 348 }); 349 350 // Clicking outside of the styled select box closes any open styled select boxes 351 query('body').addEventListener('click', function(ev){ 352 353 if ( ! isAncestorOf(ev.target, '.style-select', true) ) { 354 closeAllStyleSelects(); 355 } 356 }) 357 358 }; 359 360// Close UMD module 361})); 362
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.