vendor: 21,136 bytes, lines 1-485
1/*! 2 * jQuery Pretty Dropdowns Plugin v4.13.0 by T. H. Doan (https://thdoan.github.io/pretty-dropdowns/) 3 * 4 * jQuery Pretty Dropdowns by T. H. Doan is licensed under the MIT License. 5 * Read a copy of the license in the LICENSE file or at https://choosealicense.com/licenses/mit/ 6 * 7 * Forked to remove calculation to expand to top if enough space 8 * 9 */ 10 11(function($) { 12 $.fn.prettyDropdown = function(oOptions) { 13 14 // Default options 15 oOptions = $.extend({ 16 classic: false, 17 customClass: 'arrow', 18 width: null, 19 height: 50, 20 hoverIntent: 200, 21 multiDelimiter: '; ', 22 multiVerbosity: 99, 23 selectedMarker: '✓', 24 afterLoad: function(){} 25 }, oOptions); 26 27 oOptions.selectedMarker = '<span aria-hidden="true" class="checked"> ' + oOptions.selectedMarker + '</span>'; 28 // Validate options 29 if (isNaN(oOptions.width) && !/^\d+%$/.test(oOptions.width)) oOptions.width = null; 30 if (isNaN(oOptions.height)) oOptions.height = 50; 31 else if (oOptions.height<8) oOptions.height = 8; 32 if (isNaN(oOptions.hoverIntent) || oOptions.hoverIntent<0) oOptions.hoverIntent = 200; 33 if (isNaN(oOptions.multiVerbosity)) oOptions.multiVerbosity = 99; 34 35 // Translatable strings 36 var MULTI_NONE = 'None selected', 37 MULTI_PREFIX = 'Selected: ', 38 MULTI_POSTFIX = ' selected'; 39 40 // Globals 41 var $current, 42 aKeys = [ 43 '0','1','2','3','4','5','6','7','8','9',,,,,,,, 44 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' 45 ], 46 nCount, 47 nHoverIndex, 48 nLastIndex, 49 nTimer, 50 nTimestamp, 51 52 // Initiate pretty drop-downs 53 init = function(elSel) { 54 var $select = $(elSel), 55 nSize = elSel.size, 56 sId = elSel.name || elSel.id || '', 57 sLabelId; 58 // Exit if widget has already been initiated 59 if ($select.data('loaded')) return; 60 // Remove 'size' attribute to it doesn't affect vertical alignment 61 $select.data('size', nSize).removeAttr('size'); 62 // Set <select> height to reserve space for <div> container 63 $select.css('visibility', 'hidden').outerHeight(oOptions.height); 64 nTimestamp = +new Date(); 65 // Test whether to add 'aria-labelledby' 66 if (elSel.id) { 67 // Look for <label> 68 var $label = $('label[for=' + elSel.id + ']'); 69 if ($label.length) { 70 // Add 'id' to <label> if necessary 71 if ($label.attr('id') && !/^menu\d{13,}$/.test($label.attr('id'))) sLabelId = $label.attr('id'); 72 else $label.attr('id', (sLabelId = 'menu' + nTimestamp)); 73 } 74 } 75 nCount = 0; 76 var $items = $('optgroup, option', $select), 77 $selected = $items.filter(':selected'), 78 bMultiple = elSel.multiple, 79 // Height - 2px for borders 80 sHtml = '<ul' + (elSel.disabled ? '' : ' tabindex="0"') + ' role="listbox"' 81 + (elSel.title ? ' title="' + elSel.title + '" aria-label="' + elSel.title + '"' : '') 82 + (sLabelId ? ' aria-labelledby="' + sLabelId + '"' : '') 83 + ' aria-activedescendant="item' + nTimestamp + '-1" aria-expanded="false"' 84 + ' style="max-height:' + (oOptions.height-2) + 'px;margin:' 85 // NOTE: $select.css('margin') returns an empty string in Firefox, so we have to get 86 // each margin individually. See https://github.com/jquery/jquery/issues/3383 87 + $select.css('margin-top') + ' ' 88 + $select.css('margin-right') + ' ' 89 + $select.css('margin-bottom') + ' ' 90 + $select.css('margin-left') + ';">'; 91 if (bMultiple) { 92 sHtml += renderItem(null, 'selected'); 93 $items.each(function() { 94 if (this.selected) { 95 sHtml += renderItem(this, '', true) 96 } else { 97 sHtml += renderItem(this); 98 } 99 }); 100 } else { 101 if (oOptions.classic) { 102 $items.each(function() { 103 sHtml += renderItem(this); 104 }); 105 } else { 106 sHtml += renderItem($selected[0], 'selected'); 107 $items.filter(':not(:selected)').each(function() { 108 sHtml += renderItem(this); 109 }); 110 } 111 } 112 sHtml += '</ul>'; 113 $select.wrap('<div ' + (sId ? 'id="prettydropdown-' + sId + '" ' : '') 114 + 'class="prettydropdown ' 115 + (oOptions.classic ? 'classic ' : '') 116 + (elSel.disabled ? 'disabled ' : '') 117 + (bMultiple ? 'multiple ' : '') 118 + oOptions.customClass + ' loading"' 119 // NOTE: For some reason, the container height is larger by 1px if the <select> has the 120 // 'multiple' attribute or 'size' attribute with a value larger than 1. To fix this, we 121 // have to inline the height. 122 + ((bMultiple || nSize>1) ? ' style="height:' + oOptions.height + 'px;"' : '') 123 +'></div>').before(sHtml).data('loaded', true); 124 var $dropdown = $select.parent().children('ul'), 125 nWidth = $dropdown.outerWidth(true), 126 nOuterWidth; 127 $items = $dropdown.children(); 128 // Update default selected values for multi-select menu 129 if (bMultiple) updateSelected($dropdown); 130 else if (oOptions.classic) $('[data-value="' + $selected.val() + '"]', $dropdown).addClass('selected').append(oOptions.selectedMarker); 131 // Calculate width if initially hidden 132 if ($dropdown.width()<=0) { 133 var $clone = $dropdown.parent().clone().css({ 134 position: 'absolute', 135 top: '-100%' 136 }); 137 $('body').append($clone); 138 nWidth = $clone.children('ul').outerWidth(true); 139 $('li', $clone).width(nWidth); 140 nOuterWidth = $clone.children('ul').outerWidth(true); 141 $clone.remove(); 142 } 143 // Set dropdown width and event handler 144 // NOTE: Setting width using width(), then css() because width() only can return a float, 145 // which can result in a missing right border when there is a scrollbar. 146 $items.width(nWidth).css('width', $items.css('width')); 147 if (oOptions.width) { 148 $dropdown.parent().css('min-width', $items.css('width')); 149 $dropdown.css('width', '100%'); 150 $items.css('width', '100%'); 151 } 152 $items.click(function() { 153 var $li = $(this), 154 $selected = $dropdown.children('.selected'); 155 // Ignore disabled menu 156 if ($dropdown.parent().hasClass('disabled')) return; 157 // Only update if not disabled, not a label, and a different value selected 158 if ($dropdown.hasClass('active') && !$li.hasClass('disabled') && !$li.hasClass('label') && $li.data('value')!==$selected.data('value')) { 159 // Select highlighted item 160 if (bMultiple) { 161 if ($li.children('span.checked').length) $li.children('span.checked').remove(); 162 else $li.append(oOptions.selectedMarker); 163 // Sync <select> element 164 $dropdown.children(':not(.selected)').each(function(nIndex) { 165 $('optgroup, option', $select).eq(nIndex).prop('selected', $(this).children('span.checked').length>0); 166 }); 167 // Update selected values for multi-select menu 168 updateSelected($dropdown); 169 } else { 170 $selected.removeClass('selected').children('span.checked').remove(); 171 $li.addClass('selected').append(oOptions.selectedMarker); 172 if (!oOptions.classic) $dropdown.prepend($li); 173 $dropdown.removeClass('reverse').attr('aria-activedescendant', $li.attr('id')); 174 if ($selected.data('group') && !oOptions.classic) $dropdown.children('.label').filter(function() { 175 return $(this).text()===$selected.data('group'); 176 }).after($selected); 177 // Sync <select> element 178 $('optgroup, option', $select).filter(function() { 179 // NOTE: .data('value') can return numeric, so using == comparison instead. 180 return this.value==$li.data('value') || this.text===$li.contents().filter(function() { 181 // Filter out selected marker 182 return this.nodeType===3; 183 }).text(); 184 }).prop('selected', true); 185 } 186 $select.trigger('change'); 187 } 188 if ($li.hasClass('selected') || !bMultiple) { 189 $dropdown.toggleClass('active'); 190 $dropdown.attr('aria-expanded', $dropdown.hasClass('active')); 191 } 192 // Try to keep drop-down menu within viewport 193 if ($dropdown.hasClass('active')) { 194 // Close any other open menus 195 if ($('.prettydropdown > ul.active').length>1) resetDropdown($('.prettydropdown > ul.active').not($dropdown)[0]); 196 var nWinHeight = window.innerHeight, 197 nMaxHeight, 198 nOffsetTop = $dropdown.offset().top, 199 nScrollTop = $(document).scrollTop(), 200 nDropdownHeight = $dropdown.outerHeight(); 201 if (nSize) { 202 nMaxHeight = nSize*(oOptions.height-2); 203 if (nMaxHeight<nDropdownHeight-2) nDropdownHeight = nMaxHeight+2; 204 } 205 var nDropdownBottom = nOffsetTop-nScrollTop+nDropdownHeight; 206 $dropdown.height($dropdown.height()-(nDropdownBottom-nWinHeight)); 207 if (nMaxHeight && nMaxHeight<$dropdown.height()) $dropdown.css('height', nMaxHeight + 'px'); 208 // Ensure the selected item is in view 209 if (oOptions.classic) $dropdown.scrollTop($selected.index()*(oOptions.height-2)); 210 } else { 211 $dropdown.data('clicked', true); 212 resetDropdown($dropdown[0]); 213 } 214 }); 215 $dropdown.on({ 216 focusin: function() { 217 // Unregister any existing handlers first to prevent duplicate firings 218 $(window).off('keydown', handleKeypress).on('keydown', handleKeypress); 219 }, 220 focusout: function() { 221 $(window).off('keydown', handleKeypress); 222 }, 223 mouseenter: function() { 224 $dropdown.data('hover', true); 225 }, 226 mouseleave: resetDropdown, 227 mousemove: hoverDropdownItem 228 }); 229 // Put focus on menu when user clicks on label 230 if (sLabelId) $('#' + sLabelId).off('click', handleFocus).click(handleFocus); 231 // Done with everything! 232 $dropdown.parent().width(oOptions.width||nOuterWidth||$dropdown.outerWidth(true)).removeClass('loading'); 233 oOptions.afterLoad(); 234 }, 235 236 // Manage widget focusing 237 handleFocus = function(e) { 238 $('ul[aria-labelledby=' + e.target.id + ']').focus(); 239 }, 240 241 // Manage keyboard navigation 242 handleKeypress = function(e) { 243 var $dropdown = $('.prettydropdown > ul.active, .prettydropdown > ul:focus'); 244 if (!$dropdown.length) return; 245 if (e.which===9) { // Tab 246 resetDropdown($dropdown[0]); 247 return; 248 } else { 249 // Intercept non-Tab keys only 250 e.preventDefault(); 251 e.stopPropagation(); 252 } 253 var $items = $dropdown.children(), 254 bOpen = $dropdown.hasClass('active'), 255 nItemsHeight = $dropdown.height()/(oOptions.height-2), 256 nItemsPerPage = nItemsHeight%1<0.5 ? Math.floor(nItemsHeight) : Math.ceil(nItemsHeight), 257 sKey; 258 nHoverIndex = Math.max(0, $dropdown.children('.hover').index()); 259 nLastIndex = $items.length-1; 260 $current = $items.eq(nHoverIndex); 261 $dropdown.data('lastKeypress', +new Date()); 262 switch (e.which) { 263 case 13: // Enter 264 if (!bOpen) { 265 $current = $items.filter('.selected'); 266 toggleHover($current, 1); 267 } 268 $current.click(); 269 break; 270 case 27: // Esc 271 if (bOpen) resetDropdown($dropdown[0]); 272 break; 273 case 32: // Space 274 if (bOpen) { 275 sKey = ' '; 276 } else { 277 $current = $items.filter('.selected'); 278 toggleHover($current, 1); 279 $current.click(); 280 } 281 break; 282 case 33: // Page Up 283 if (bOpen) { 284 toggleHover($current, 0); 285 toggleHover($items.eq(Math.max(nHoverIndex-nItemsPerPage-1, 0)), 1); 286 } 287 break; 288 case 34: // Page Down 289 if (bOpen) { 290 toggleHover($current, 0); 291 toggleHover($items.eq(Math.min(nHoverIndex+nItemsPerPage-1, nLastIndex)), 1); 292 } 293 break; 294 case 35: // End 295 if (bOpen) { 296 toggleHover($current, 0); 297 toggleHover($items.eq(nLastIndex), 1); 298 } 299 break; 300 case 36: // Home 301 if (bOpen) { 302 toggleHover($current, 0); 303 toggleHover($items.eq(0), 1); 304 } 305 break; 306 case 38: // Up 307 if (bOpen) { 308 toggleHover($current, 0); 309 // If not already key-navigated or first item is selected, cycle to the last item; or 310 // else select the previous item 311 toggleHover(nHoverIndex ? $items.eq(nHoverIndex-1) : $items.eq(nLastIndex), 1); 312 } 313 break; 314 case 40: // Down 315 if (bOpen) { 316 toggleHover($current, 0); 317 // If last item is selected, cycle to the first item; or else select the next item 318 toggleHover(nHoverIndex===nLastIndex ? $items.eq(0) : $items.eq(nHoverIndex+1), 1); 319 } 320 break; 321 default: 322 if (bOpen) sKey = aKeys[e.which-48]; 323 } 324 if (sKey) { // Alphanumeric key pressed 325 clearTimeout(nTimer); 326 $dropdown.data('keysPressed', $dropdown.data('keysPressed')===undefined ? sKey : $dropdown.data('keysPressed') + sKey); 327 nTimer = setTimeout(function() { 328 $dropdown.removeData('keysPressed'); 329 // NOTE: Windows keyboard repeat delay is 250-1000 ms. See 330 // https://technet.microsoft.com/en-us/library/cc978658.aspx 331 }, 300); 332 // Build index of matches 333 var aMatches = [], 334 nCurrentIndex = $current.index(); 335 $items.each(function(nIndex) { 336 if ($(this).text().toLowerCase().indexOf($dropdown.data('keysPressed'))===0) aMatches.push(nIndex); 337 }); 338 if (aMatches.length) { 339 // Cycle through items matching key(s) pressed 340 for (var i=0; i<aMatches.length; ++i) { 341 if (aMatches[i]>nCurrentIndex) { 342 toggleHover($items, 0); 343 toggleHover($items.eq(aMatches[i]), 1); 344 break; 345 } 346 if (i===aMatches.length-1) { 347 toggleHover($items, 0); 348 toggleHover($items.eq(aMatches[0]), 1); 349 } 350 } 351 } 352 } 353 }, 354 355 // Highlight menu item 356 hoverDropdownItem = function(e) { 357 var $dropdown = $(e.currentTarget); 358 if (e.target.nodeName!=='LI' || !$dropdown.hasClass('active') || new Date()-$dropdown.data('lastKeypress')<200) return; 359 toggleHover($dropdown.children(), 0, 1); 360 toggleHover($(e.target), 1, 1); 361 }, 362 363 // Construct menu item 364 // elOpt is null for first item in multi-select menus 365 renderItem = function(elOpt, sClass, bSelected) { 366 var sGroup = '', 367 sText = '', 368 sTitle; 369 sClass = sClass || ''; 370 if (elOpt) { 371 switch (elOpt.nodeName) { 372 case 'OPTION': 373 if (elOpt.parentNode.nodeName==='OPTGROUP') sGroup = elOpt.parentNode.getAttribute('label'); 374 sText = (elOpt.getAttribute('data-prefix') || '') + elOpt.text + (elOpt.getAttribute('data-suffix') || ''); 375 break; 376 case 'OPTGROUP': 377 sClass += ' label'; 378 sText = (elOpt.getAttribute('data-prefix') || '') + elOpt.getAttribute('label') + (elOpt.getAttribute('data-suffix') || ''); 379 break; 380 } 381 if (elOpt.disabled || (sGroup && elOpt.parentNode.disabled)) sClass += ' disabled'; 382 sTitle = elOpt.title; 383 if (sGroup && !sTitle) sTitle = elOpt.parentNode.title; 384 } 385 ++nCount; 386 return '<li id="item' + nTimestamp + '-' + nCount + '"' 387 + (sGroup ? ' data-group="' + sGroup + '"' : '') 388 + (elOpt && (elOpt.value||oOptions.classic) ? ' data-value="' + elOpt.value + '"' : '') 389 + (elOpt && elOpt.nodeName==='OPTION' ? ' role="option"' : '') 390 + (sTitle ? ' title="' + sTitle + '" aria-label="' + sTitle + '"' : '') 391 + (sClass ? ' class="' + $.trim(sClass) + '"' : '') 392 + ((oOptions.height!==50) ? ' style="height:' + (oOptions.height-2) 393 + 'px;line-height:' + (oOptions.height-4) + 'px;"' : '') + '>' + sText 394 + ((bSelected || sClass==='selected') ? oOptions.selectedMarker : '') + '</li>'; 395 }, 396 397 // Reset menu state 398 // @param o Event or Element object 399 resetDropdown = function(o) { 400 var $dropdown = $(o.currentTarget||o); 401 // NOTE: Sometimes it's possible for $dropdown to point to the wrong element when you 402 // quickly hover over another menu. To prevent this, we need to check for .active as a 403 // backup and manually reassign $dropdown. This also requires that it's not clicked on 404 // because in rare cases the reassignment fails and the reverse menu will not get reset. 405 if (o.type==='mouseleave' && !$dropdown.hasClass('active') && !$dropdown.data('clicked')) $dropdown = $('.prettydropdown > ul.active'); 406 $dropdown.data('hover', false); 407 clearTimeout(nTimer); 408 nTimer = setTimeout(function() { 409 if ($dropdown.data('hover')) return; 410 if ($dropdown.hasClass('reverse') && !oOptions.classic) $dropdown.prepend($dropdown.children(':last-child')); 411 $dropdown.removeClass('active reverse').removeData('clicked').attr('aria-expanded', 'false').css('height', ''); 412 $dropdown.children().removeClass('hover nohover'); 413 }, (o.type==='mouseleave' && !$dropdown.data('clicked')) ? oOptions.hoverIntent : 0); 414 }, 415 416 // Set menu item hover state 417 // bNoScroll set on hoverDropdownItem() 418 toggleHover = function($li, bOn, bNoScroll) { 419 if (bOn) { 420 $li.removeClass('nohover').addClass('hover'); 421 if ($li.length===1 && $current && !bNoScroll) { 422 // Ensure items are always in view 423 var $dropdown = $li.parent(), 424 nDropdownHeight = $dropdown.outerHeight(), 425 nItemOffset = $li.offset().top-$dropdown.offset().top-1; // -1px for top border 426 if ($li.index()===0) { 427 $dropdown.scrollTop(0); 428 } else if ($li.index()===nLastIndex) { 429 $dropdown.scrollTop($dropdown.children().length*oOptions.height); 430 } else { 431 if (nItemOffset+oOptions.height>nDropdownHeight) $dropdown.scrollTop($dropdown.scrollTop()+oOptions.height+nItemOffset-nDropdownHeight); 432 else if (nItemOffset<0) $dropdown.scrollTop($dropdown.scrollTop()+nItemOffset); 433 } 434 } 435 } else { 436 $li.removeClass('hover').addClass('nohover'); 437 } 438 }, 439 440 // Update selected values for multi-select menu 441 updateSelected = function($dropdown) { 442 var $select = $dropdown.parent().children('select'), 443 aSelected = $('option', $select).map(function() { 444 if (this.selected) return this.text; 445 }).get(), 446 sSelected; 447 if (oOptions.multiVerbosity>=aSelected.length) sSelected = aSelected.join(oOptions.multiDelimiter) || MULTI_NONE; 448 else sSelected = aSelected.length + '/' + $('option', $select).length + MULTI_POSTFIX; 449 if (sSelected) { 450 var sTitle = ($select.attr('title') ? $select.attr('title') : '') + (aSelected.length ? '\n' + MULTI_PREFIX + aSelected.join(oOptions.multiDelimiter) : ''); 451 $dropdown.children('.selected').text(sSelected); 452 $dropdown.attr({ 453 'title': sTitle, 454 'aria-label': sTitle 455 }); 456 } else { 457 $dropdown.children('.selected').empty(); 458 $dropdown.attr({ 459 'title': $select.attr('title'), 460 'aria-label': $select.attr('title') 461 }); 462 } 463 }; 464 465 /** 466 * Public Functions 467 */ 468 469 // Resync the menu with <select> to reflect state changes 470 this.refresh = function(oOptions) { 471 return this.each(function() { 472 var $select = $(this); 473 $select.prevAll('ul').remove(); 474 $select.unwrap().data('loaded', false); 475 this.size = $select.data('size'); 476 init(this); 477 }); 478 }; 479 480 return this.each(function() { 481 init(this); 482 }); 483 484 }; 485}(jQuery));
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.