1(function () { 2 var STORAGE_KEY = 'rbTheme'; 3 var MEDIA_QUERY = '(prefers-color-scheme: dark)'; 4 var THEMES = { light: true, dark: true, auto: true }; 5 var root = document.documentElement; 6 var media = window.matchMedia ? window.matchMedia(MEDIA_QUERY) : null; 7 var sessionPreference = 'auto'; 8 var mediaListenerAttached = false; 9 var mediaChangeHandler = null; 10 var config = window.RBThemeConfig || {}; 11 var canUseThemes = config.canUseThemes === true; 12 13 function readStoredTheme() { 14 try { 15 var value = window.localStorage.getItem(STORAGE_KEY); 16 if (THEMES[value]) { 17 return value; 18 } 19 } catch (error) { 20 return null; 21 } 22 23 return null; 24 } 25 26 function getSystemTheme() { 27 return media && media.matches ? 'dark' : 'light'; 28 } 29 30 function getResolvedTheme(preference) { 31 if (!canUseThemes) { 32 return 'light'; 33 } 34 35 return preference === 'auto' ? getSystemTheme() : preference; 36 } 37 38 function applyResolvedTheme(theme) { 39 if (root.dataset.bsTheme === theme) { 40 return false; 41 } 42 43 root.dataset.bsTheme = theme; 44 return true; 45 } 46 47 function syncControls(preference) { 48 var resolvedTheme = getResolvedTheme(preference); 49 var pickers = document.querySelectorAll('[data-rb-theme-picker]'); 50 var label = preference === 'light' ? 'Light' : (preference === 'dark' ? 'Dark' : 'Auto'); 51 var iconClass = preference === 'light' 52 ? 'bi bi-sun-fill' 53 : (preference === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-circle-half'); 54 55 pickers.forEach(function (picker) { 56 var current = picker.querySelector('[data-rb-theme-current]'); 57 var icon = picker.querySelector('[data-rb-theme-icon]'); 58 var toggle = picker.querySelector('[data-rb-theme-toggle]'); 59 var options = picker.querySelectorAll('[data-rb-theme-value]'); 60 61 if (current) { 62 current.textContent = label; 63 } 64 65 if (icon) { 66 icon.className = iconClass; 67 } 68 69 if (toggle) { 70 toggle.setAttribute('aria-label', 'Color theme: ' + label); 71 toggle.dataset.rbThemeActive = preference; 72 toggle.dataset.rbThemeResolved = resolvedTheme; 73 } 74 75 options.forEach(function (option) { 76 var isActive = option.dataset.rbThemeValue === preference; 77 option.setAttribute('aria-checked', isActive ? 'true' : 'false'); 78 option.classList.toggle('active', isActive); 79 80 if (isActive) { 81 option.setAttribute('aria-current', 'true'); 82 } else { 83 option.removeAttribute('aria-current'); 84 } 85 }); 86 }); 87 } 88 89 function dispatchThemeChange(preference, resolvedTheme) { 90 if (typeof window.CustomEvent !== 'function') { 91 return; 92 } 93 94 window.dispatchEvent(new CustomEvent('rb:themechange', { 95 detail: { 96 canUseThemes: canUseThemes, 97 preference: preference, 98 theme: resolvedTheme 99 } 100 })); 101 } 102 103 function persistTheme(preference) { 104 try { 105 window.localStorage.setItem(STORAGE_KEY, preference); 106 } catch (error) { 107 return false; 108 } 109 110 return true; 111 } 112 113 function detachMediaListener() { 114 if (!media || !mediaListenerAttached) { 115 return; 116 } 117 118 if (typeof media.removeEventListener === 'function') { 119 media.removeEventListener('change', mediaChangeHandler); 120 } else if (typeof media.removeListener === 'function') { 121 media.removeListener(mediaChangeHandler); 122 } 123 124 mediaListenerAttached = false; 125 } 126 127 function attachMediaListener() { 128 if (!media || mediaListenerAttached || !canUseThemes) { 129 return; 130 } 131 132 if (typeof media.addEventListener === 'function') { 133 media.addEventListener('change', mediaChangeHandler); 134 } else if (typeof media.addListener === 'function') { 135 media.addListener(mediaChangeHandler); 136 } 137 138 mediaListenerAttached = true; 139 } 140 141 function updateMediaListener(preference) { 142 if (canUseThemes && preference === 'auto') { 143 attachMediaListener(); 144 } else { 145 detachMediaListener(); 146 } 147 } 148 149 function getPreference() { 150 if (!canUseThemes) { 151 return 'light'; 152 } 153 154 return sessionPreference; 155 } 156 157 function setPreference(preference, persist) { 158 var nextPreference = canUseThemes && THEMES[preference] ? preference : 'light'; 159 var resolvedTheme = getResolvedTheme(nextPreference); 160 var didApply = applyResolvedTheme(resolvedTheme); 161 162 sessionPreference = nextPreference; 163 164 if (persist && canUseThemes) { 165 persistTheme(nextPreference); 166 } 167 168 updateMediaListener(nextPreference); 169 syncControls(nextPreference); 170 171 if (didApply) { 172 dispatchThemeChange(nextPreference, resolvedTheme); 173 } 174 } 175 176 function initializeTheme() { 177 var storedTheme = readStoredTheme(); 178 var preference = canUseThemes ? (storedTheme || 'auto') : 'light'; 179 var currentApplied = root.dataset.bsTheme; 180 var resolvedTheme = getResolvedTheme(preference); 181 182 sessionPreference = preference; 183 184 if (currentApplied !== resolvedTheme) { 185 applyResolvedTheme(resolvedTheme); 186 } 187 188 updateMediaListener(preference); 189 syncControls(preference); 190 } 191 192 function handleMediaChange() { 193 if (!canUseThemes || getPreference() !== 'auto') { 194 return; 195 } 196 197 var resolvedTheme = getResolvedTheme('auto');
198 if (applyResolvedTheme(resolvedTheme)) { 199 dispatchThemeChange('auto', resolvedTheme); 200 } 201 202 syncControls('auto'); 203 } 204 205 function bindPicker(picker) { 206 var options = picker.querySelectorAll('[data-rb-theme-value]'); 207 208 options.forEach(function (option) { 209 option.addEventListener('click', function () { 210 setPreference(option.dataset.rbThemeValue, true); 211 }); 212 }); 213 } 214 215 mediaChangeHandler = handleMediaChange; 216 217 initializeTheme(); 218 219 document.addEventListener('DOMContentLoaded', function () { 220 document.querySelectorAll('[data-rb-theme-picker]').forEach(bindPicker); 221 syncControls(getPreference()); 222 }); 223 224 window.RBTheme = { 225 canUseThemes: function () { 226 return canUseThemes; 227 }, 228 get: function () { 229 return getPreference(); 230 }, 231 set: function (theme) { 232 setPreference(theme, true); 233 return getPreference(); 234 }, 235 sync: function () { 236 syncControls(getPreference()); 237 return getPreference(); 238 } 239 }; 240})();
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.