1function compareHighlightFirst(a, b) { 2 if (a.highlight && !b.highlight) return -1; 3 if (!a.highlight && b.highlight) return 1; 4 return 0; 5} 6 7function fdGetValue(id) { 8 return document.getElementById(id)?.dataset.value || ''; 9} 10 11const fdValueModes = { 12 sortHasValue: Object.freeze({ sortHasValue: true }) 13}; 14 15function fdSetValue(id, value, label, options = {}) { 16 const { 17 labelMode = 'auto', 18 selectFallback = false, 19 syncAria = true, 20 sortHasValue = false 21 } = options; 22 23 const el = document.getElementById(id); 24 if (!el) return; 25 26 const optionList = Array.from(el.querySelectorAll('.fd-option')); 27 const selectedOption = 28 optionList.find(option => (option.dataset.value || '') === value) || 29 (selectFallback ? optionList[0] : null); 30 const nextValue = selectedOption ? (selectedOption.dataset.value || '') : value; 31 32 el.dataset.value = nextValue; 33 34 const triggerText = el.querySelector('.fd-trigger-text'); 35 const optionLabel = selectedOption ? selectedOption.innerHTML.trim() : undefined; 36 const nextLabel = label !== undefined ? label : optionLabel; 37 38 if (triggerText && nextLabel !== undefined) { 39 const shouldRenderHtml = 40 labelMode === 'html' || 41 (labelMode === 'auto' && ( 42 /<[^>]+>/.test(nextLabel) || 43 /&(?:[a-zA-Z]+|#\d+|#x[\da-fA-F]+);/.test(nextLabel) || 44 Boolean(selectedOption?.children.length) 45 )); 46 47 if (shouldRenderHtml) triggerText.innerHTML = nextLabel; 48 else triggerText.textContent = nextLabel; 49 } 50 51 optionList.forEach(option => { 52 const selected = selectedOption ? option === selectedOption : (option.dataset.value || '') === nextValue; 53 option.classList.toggle('selected', selected); 54 if (syncAria) option.setAttribute('aria-selected', selected ? 'true' : 'false'); 55 }); 56 57 el.classList.toggle('has-value', nextValue !== '' && (sortHasValue || id !== 'fd-sort')); 58} 59 60function fdCloseDropdowns(except = null) { 61 document.querySelectorAll('.fd.open').forEach(fd => { 62 if (fd === except) return; 63 fd.classList.remove('open'); 64 fd.querySelector('.fd-trigger')?.setAttribute('aria-expanded', 'false'); 65 }); 66} 67 68function fdInitControls(options = {}) { 69 const { 70 selector = '.fd', 71 label, 72 valueOptions, 73 onSort, 74 onFilter, 75 onChange 76 } = options; 77 78 document.querySelectorAll(selector).forEach(fd => { 79 const trigger = fd.querySelector('.fd-trigger'); 80 const menu = fd.querySelector('.fd-menu'); 81 if (!trigger || !menu) return; 82 83 trigger.addEventListener('click', event => { 84 event.stopPropagation(); 85 const willOpen = !fd.classList.contains('open'); 86 fdCloseDropdowns(fd); 87 fd.classList.toggle('open', willOpen); 88 trigger.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); 89 }); 90 91 menu.querySelectorAll('.fd-option').forEach(option => { 92 option.addEventListener('click', event => { 93 event.stopPropagation(); 94 const nextValue = option.dataset.value || ''; 95 const nextLabel = typeof label === 'function' ? label(option, fd) : undefined; 96 const nextOptions = typeof valueOptions === 'function' ? valueOptions(fd, option) : valueOptions; 97 98 fdSetValue(fd.id, nextValue, nextLabel, nextOptions || undefined); 99 fd.classList.remove('open'); 100 trigger.setAttribute('aria-expanded', 'false'); 101 102 const detail = { fd, option, value: fd.dataset.value || '', isSort: fd.id === 'fd-sort' }; 103 if (typeof onChange === 'function') { 104 onChange(detail); 105 } else if (detail.isSort && typeof onSort === 'function') { 106 onSort(detail); 107 } else if (!detail.isSort && typeof onFilter === 'function') { 108 onFilter(detail); 109 } 110 }); 111 }); 112 }); 113 114 if (document.documentElement.dataset.fdControlsBound === 'true') return; 115 document.documentElement.dataset.fdControlsBound = 'true'; 116 117 document.addEventListener('click', event => { 118 if (event.target.closest('.fd')) return; 119 fdCloseDropdowns(); 120 }); 121 122 document.addEventListener('keydown', event => { 123 if (event.key === 'Escape') fdCloseDropdowns(); 124 }); 125} 126
127function updateTableScrollState(options = {}) { 128 const { 129 tableId = 'tableWrap', 130 shellId = 'tableScrollWrap', 131 hintId = 'scrollHint', 132 scrollThreshold = 2, 133 startOffset = 2, 134 endOffset = 8, 135 trackEnd = true, 136 endWhenNotScrollable = false, 137 hintMode = 'until-end' 138 } = options; 139 140 const tableWrap = document.getElementById(tableId); 141 const scrollWrap = document.getElementById(shellId); 142 const scrollHint = document.getElementById(hintId); 143 if (!tableWrap || !scrollWrap) return; 144 145 const scrollable = tableWrap.scrollWidth > tableWrap.clientWidth + scrollThreshold; 146 const atStart = tableWrap.scrollLeft <= startOffset; 147 const atEnd = tableWrap.scrollLeft + tableWrap.clientWidth >= tableWrap.scrollWidth - endOffset; 148 149 scrollWrap.classList.toggle('scrollable', scrollable); 150 scrollWrap.classList.toggle('at-start', endWhenNotScrollable ? (!scrollable || atStart) : atStart); 151 if (trackEnd) scrollWrap.classList.toggle('at-end', endWhenNotScrollable ? (!scrollable || atEnd) : atEnd); 152 153 if (!scrollHint) return; 154 const hintVisible = 155 hintMode === 'while-scrollable' ? scrollable : 156 hintMode === 'at-start' ? (scrollable && atStart) : 157 (scrollable && !atEnd); 158 scrollHint.classList.toggle('visible', hintVisible); 159}
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.