1import { g as getCookie, c as countries, s as setCookie, d as defaultExpiration, a as BaseCustomSelect, B as BaseAutocomplete } from "./BaseAutocomplete.js"; 2const userTypes = { 3 business: "business", 4 consumer: "consumer", 5 both: "both" 6}; 7class MyAccount extends HTMLElement { 8 constructor() { 9 super(); 10 const params = new Proxy(new URLSearchParams(window.location.search), { 11 get: (searchParams, prop) => searchParams.get(prop) 12 }); 13 this.useRedesign = window.headerFooterData.isMyBbbRedesignEnabled || params.useMenuRedesign === "true"; 14 this.render = () => { 15 this.setButtonOnClick(); 16 try { 17 this.initData(); 18 } catch { 19 this.user = null; 20 } 21 this.updateButtonLabel(); 22 clearInterval(this.cookieInterval); 23 this.cookieInterval = window.setInterval(this.checkAuthState, 5e3); 24 }; 25 } 26 connectedCallback() { 27 this.render(); 28 } 29 disconnectedCallback() { 30 clearInterval(this.cookieInterval); 31 } 32 checkAuthState = () => { 33 const isLoggedIn = getCookie("iabbb_auth_login_hint") === "true"; 34 if (!this.user && isLoggedIn) { 35 this.initData(); 36 } else if (this.user && !isLoggedIn) { 37 this.user = null; 38 } 39 this.updateButtonLabel(); 40 }; 41 initData = () => { 42 if (this.user) { 43 return; 44 } 45 if (getCookie("iabbb_auth_login_hint") !== "true") { 46 this.user = null; 47 return; 48 } 49 const userTypeCode = getCookie("iabbb_auth_user_type"); 50 let userType; 51 if (userTypeCode === "b") { 52 userType = userTypes.business; 53 } else if (userTypeCode === "bc") { 54 userType = userTypes.both; 55 } else { 56 userType = userTypes.consumer; 57 } 58 this.user = { userType }; 59 }; 60 buildAndOpenRedesignDialog = () => { 61 const closeIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="currentColor" aria-hidden="true" focusable="false" width="1em" height="1em"><path d="M7.5 105c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l151 151 151-151c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-151 151 151 151c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-151-151-151 151c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l151-151-151-151z"/></svg>`; 62 const visuallyHiddenLabel = `<span class="visually-hidden">close account dialog</span>`; 63 const closeButton = document.createElement("button"); 64 closeButton.className = "account-close-button bds-button-unstyled"; 65 closeButton.innerHTML = `${closeIcon}${visuallyHiddenLabel}`; 66 const dialog = document.createElement("dialog"); 67 dialog.className = "account-dialog-redesign"; 68 dialog.setAttribute("data-has-user", this.user ? "true" : "false"); 69 closeButton.onclick = () => { 70 this.destroyRedesignDialog(); 71 this.querySelector("button").focus(); 72 }; 73 dialog.appendChild(closeButton); 74 const chevronRightIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" fill="currentColor" aria-hidden="true" focusable="false" width="1em" height="1em"><path d="M311.1 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L243.2 256 73.9 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg>`; 75 if (this.user) { 76 const loggedInSection = document.createElement("div"); 77 loggedInSection.className = "account-links-section"; 78 dialog.appendChild(loggedInSection); 79 const bbbAccountHeading = document.createElement("h3"); 80 bbbAccountHeading.innerText = "BBB Account"; 81 bbbAccountHeading.className = "bbb-account-heading"; 82 loggedInSection.appendChild(bbbAccountHeading); 83 const accountDivider = document.createElement("div"); 84 accountDivider.className = "account-dialog-slim-divider"; 85 loggedInSection.appendChild(accountDivider); 86 const linksSection = document.createElement("div"); 87 linksSection.className = "account-links-list"; 88 loggedInSection.appendChild(linksSection); 89 if (this.user.userType === userTypes.business || this.user.userType === userTypes.both) { 90 const businessPortalLink = document.createElement("a"); 91 businessPortalLink.href = `${window.headerFooterData.baseUrl}/account/bid-selector`; 92 businessPortalLink.className = "account-dialog-link"; 93 businessPortalLink.innerHTML = `
94 <span class="account-dialog-link-text">Business Portal</span> 95 <span class="account-dialog-link-chevron" aria-hidden="true">${chevronRightIcon}</span> 96 `; 97 linksSection.appendChild(businessPortalLink); 98 } 99 if (this.user.userType === userTypes.consumer || this.user.userType === userTypes.both) { 100 const reviewsLink = document.createElement("a"); 101 reviewsLink.href = `${window.headerFooterData.baseUrl}/account/reviews`; 102 reviewsLink.className = "account-dialog-link"; 103 reviewsLink.innerHTML = ` 104 <span class="account-dialog-link-text">Reviews</span> 105 <span class="account-dialog-link-chevron" aria-hidden="true">${chevronRightIcon}</span> 106 `; 107 linksSection.appendChild(reviewsLink); 108 const complaintsLink = document.createElement("a"); 109 complaintsLink.href = `${window.headerFooterData.baseUrl}/account/complaints`; 110 complaintsLink.className = "account-dialog-link"; 111 complaintsLink.innerHTML = ` 112 <span class="account-dialog-link-text">Complaints</span> 113 <span class="account-dialog-link-chevron" aria-hidden="true">${chevronRightIcon}</span> 114 `; 115 linksSection.appendChild(complaintsLink); 116 const scamsLink = document.createElement("a"); 117 scamsLink.href = `${window.headerFooterData.baseUrl}/account/scams`; 118 scamsLink.className = "account-dialog-link"; 119 scamsLink.innerHTML = ` 120 <span class="account-dialog-link-text">Scams</span> 121 <span class="account-dialog-link-chevron" aria-hidden="true">${chevronRightIcon}</span> 122 `; 123 linksSection.appendChild(scamsLink); 124 } 125 const myAccountLink = document.createElement("a"); 126 myAccountLink.href = `${window.headerFooterData.baseUrl}/account/home`; 127 myAccountLink.className = "account-dialog-link"; 128 myAccountLink.innerHTML = ` 129 <span class="account-dialog-link-text">Profile</span> 130 <span class="account-dialog-link-chevron" aria-hidden="true">${chevronRightIcon}</span> 131 `; 132 linksSection.appendChild(myAccountLink); 133 if (this.user.userType === userTypes.consumer) { 134 const registerBusinessLink = document.createElement("a"); 135 registerBusinessLink.className = "register-account-link"; 136 registerBusinessLink.innerText = "Register for a Business Account"; 137 registerBusinessLink.href = `${window.headerFooterData.baseUrl}/account/register/business`; 138 linksSection.appendChild(registerBusinessLink); 139 } else if (this.user.userType === userTypes.business) { 140 const registerConsumerLink = document.createElement("a"); 141 registerConsumerLink.className = "register-account-link"; 142 registerConsumerLink.innerText = "Register for a Personal Account"; 143 registerConsumerLink.href = `${window.headerFooterData.baseUrl}/account/register/personal`; 144 linksSection.appendChild(registerConsumerLink); 145 } 146 } else { 147 const notLoggedInSection = document.createElement("div"); 148 notLoggedInSection.className = "access-your-account-section"; 149 dialog.appendChild(notLoggedInSection); 150 const accessAccountHeading = document.createElement("h3"); 151 accessAccountHeading.innerText = "Access Your BBB Account"; 152 accessAccountHeading.className = "access-account-heading"; 153 notLoggedInSection.appendChild(accessAccountHeading); 154 const accessDivider = document.createElement("div"); 155 accessDivider.className = "account-dialog-divider"; 156 notLoggedInSection.appendChild(accessDivider); 157 const signInOrRegisterSection = document.createElement("div"); 158 signInOrRegisterSection.className = "sign-in-or-register-section"; 159 notLoggedInSection.appendChild(signInOrRegisterSection); 160 const signInBlurb = document.createElement("div"); 161 signInBlurb.className = "sign-in-blurb"; 162 signInBlurb.innerHTML = `<span class="sign-in-emphasis">Sign in</span> or <span class="sign-in-emphasis">register</span> for an <span class="sign-in-emphasis">Account</span> to manage your business or personal interactions with BBB.`; 163 signInOrRegisterSection.appendChild(signInBlurb);
164 const signInLink = document.createElement("a"); 165 signInLink.innerText = "Sign In"; 166 signInLink.className = "bds-cta"; 167 signInLink.setAttribute("data-type", "featured"); 168 signInLink.href = `${window.headerFooterData.baseUrl}/account/login`; 169 signInOrRegisterSection.appendChild(signInLink); 170 const signInDivider = document.createElement("div"); 171 signInDivider.className = "account-dialog-divider"; 172 signInOrRegisterSection.appendChild(signInDivider); 173 const registerLink = document.createElement("a"); 174 registerLink.innerText = "Register for an account"; 175 registerLink.className = "register-link"; 176 registerLink.href = `${window.headerFooterData.baseUrl}/account/register/personal`; 177 signInOrRegisterSection.appendChild(registerLink); 178 } 179 if (window.headerFooterData?.bbb?.name) { 180 const localBbbDivider = document.createElement("div"); 181 localBbbDivider.className = "account-dialog-slim-divider"; 182 dialog.appendChild(localBbbDivider); 183 const localBbbSection = document.createElement("div"); 184 localBbbSection.className = "account-local-bbb-section"; 185 const localBbbHeading = document.createElement("h3"); 186 localBbbHeading.id = "localBbbHeading"; 187 localBbbHeading.innerText = "Local BBB"; 188 localBbbSection.appendChild(localBbbHeading); 189 const houseBuildingIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" aria-hidden="true" focusable="false" width="1em" height="1em" fill="currentColor"><path d="M368 0c-26.5 0-48 21.5-48 48V153.9l64 58.7V208c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H413.2c1.9 6.8 2.8 13.9 2.8 21.1V288h16c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H416V464c0 18-6 34.6-16 48H592c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H368zM512 208c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H528c-8.8 0-16-7.2-16-16V208zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H528c-8.8 0-16-7.2-16-16V304c0-8.8 7.2-16 16-16zM400 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H400c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16zm112 16c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H528c-8.8 0-16-7.2-16-16V112zM0 277.1V464c0 26.5 21.5 48 48 48H336c26.5 0 48-21.5 48-48V277.1c0-13.5-5.6-26.3-15.6-35.4l-144-132c-18.4-16.8-46.5-16.8-64.9 0l-144 132C5.6 250.8 0 263.7 0 277.1zM144 296c0-13.3 10.7-24 24-24h48c13.3 0 24 10.7 24 24v48c0 13.3-10.7 24-24 24H168c-13.3 0-24-10.7-24-24V296z"/></svg>`; 190 const localBbbCard = document.createElement("div"); 191 localBbbCard.className = "account-local-bbb-card"; 192 const localBbbLink = document.createElement("a"); 193 localBbbLink.className = "account-local-bbb-link"; 194 localBbbLink.href = window.headerFooterData?.bbb?.url || "/bbb-directory"; 195 localBbbLink.innerHTML = ` 196 <span class="account-local-bbb-label"> 197 <span class="account-local-bbb-icon" aria-hidden="true">${houseBuildingIcon}</span> 198 <span class="account-local-bbb-text"> 199 <span class="account-local-bbb-name">${window.headerFooterData.bbb.name}</span> 200 </span> 201 </span> 202 <span class="account-local-bbb-chevron" aria-hidden="true">${chevronRightIcon}</span> 203 `; 204 const changeLink = document.createElement("a"); 205 changeLink.href = `${window.headerFooterData.baseUrl}/manage-location`; 206 changeLink.id = "changeBbbLink"; 207 changeLink.className = "change-bbb-link"; 208 changeLink.setAttribute("aria-labelledby", "changeBbbLink localBbbHeading"); 209 changeLink.innerText = "Change"; 210 localBbbCard.appendChild(localBbbLink); 211 localBbbCard.appendChild(changeLink); 212 localBbbSection.appendChild(localBbbCard); 213 dialog.appendChild(localBbbSection); 214 } 215 if (this.user) { 216 const signOutButton = document.createElement("a"); 217 signOutButton.className = "sign-out-button-redesign"; 218 signOutButton.innerText = "Sign Out"; 219 const signOutReturnUrl = window.location.pathname.startsWith("/account") ? `${window.headerFooterData.baseUrl}/account/login` : window.location.href; 220 signOutButton.href = `${window.headerFooterData.baseUrl}/account/logout?returnUrl=${encodeURIComponent(signOutReturnUrl)}`; 221 dialog.appendChild(signOutButton); 222 } 223 document.body.appendChild(dialog); 224 this.updateDialogPosition(dialog); 225 dialog.showModal(); 226 dialog.addEventListener("click", (e) => { 227 const dialogDimensions = dialog.getBoundingClientRect(); 228 if (e.clientX < dialogDimensions.left || e.clientX > dialogDimensions.right || e.clientY < dialogDimensions.top || e.clientY > dialogDimensions.bottom) { 229 dialog.close(); 230 this.destroyRedesignDialog(); 231 } 232 }); 233 dialog.addEventListener("close", () => { 234 if (document.querySelector(".account-dialog-redesign")) { 235 this.destroyRedesignDialog(); 236 } 237 this.querySelector("button").focus(); 238 }); 239 window.addEventListener("resize", this.handleRedesignDialogPositionOnResize, { passive: true }); 240 }; 241 handleLegacyDialogPositionOnResize = () => { 242 const dialogToListen = document.querySelector(".account-dialog"); 243 this.updateDialogPosition(dialogToListen); 244 }; 245 handleRedesignDialogPositionOnResize = () => { 246 const dialogToListen = document.querySelector(".account-dialog-redesign"); 247 this.updateDialogPosition(dialogToListen); 248 }; 249 updateDialogPosition = (dialog) => { 250 const buttonBoundingClientRect = this.querySelector("button").getBoundingClientRect(); 251 if (!dialog || buttonBoundingClientRect.left === 0) { 252 return; 253 } 254 const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); 255 if (vw < 768) { 256 dialog.style.left = "revert"; 257 } else { 258 const desktopDialogWidth = this.user ? 300 : 500; 259 dialog.style.left = `${buttonBoundingClientRect.right - desktopDialogWidth}px`; 260 } 261 dialog.style.top = `${buttonBoundingClientRect.top}px`; 262 }; 263 destroyRedesignDialog = () => { 264 const dialog = document.querySelector(".account-dialog-redesign"); 265 if (dialog) { 266 dialog.remove(); 267 } 268 window.removeEventListener("resize", this.handleRedesignDialogPositionOnResize); 269 }; 270 /** 271 * Emit a custom event 272 * @param {string} name - The event name 273 * @param {object} detail - Any details to pass along with the event 274 */ 275 emit = (name, detail = {}) => { 276 const event = new CustomEvent(name, { 277 bubbles: true, 278 cancelable: true, 279 composed: true, 280 detail 281 }); 282 return this.dispatchEvent(event); 283 }; 284 setButtonOnClick = () => { 285 const btn = this.querySelector("button"); 286 btn.onclick = () => { 287 this.buildAndOpenRedesignDialog(); 288 }; 289 }; 290 updateButtonLabel = () => { 291 const label = this.querySelector(".my-account-label"); 292 if (!label) { 293 return; 294 } 295 label.textContent = this.user ? "My account" : "Sign in"; 296 }; 297} 298customElements.define("iabbb-my-account", MyAccount); 299function waitForSatellite(callback, onTimeout, timeout = 5e3, interval = 200) { 300 const start = Date.now(); 301 (function check() { 302 if (window._satellite) { 303 callback(); 304 } else if (Date.now() - start < timeout) { 305 setTimeout(check, interval); 306 } else { 307 onTimeout(); 308 } 309 })(); 310} 311const queue = []; 312let waitingForSatellite = false;
313function fire({ name, dataLayerProperties }) { 314 if (dataLayerProperties !== null && window.webDigitalData) { 315 window.webDigitalData = { 316 ...window.webDigitalData, 317 direct_call: dataLayerProperties 318 }; 319 } 320 window._satellite.track(name); 321} 322function drainQueue() { 323 waitingForSatellite = false; 324 while (queue.length > 0) { 325 fire(queue.shift()); 326 } 327} 328const aaTrackEvent = (name, dataLayerProperties = null) => { 329 queue.push({ 330 name, 331 dataLayerProperties: dataLayerProperties !== null ? { ...dataLayerProperties } : null 332 }); 333 if (window._satellite) { 334 drainQueue(); 335 return; 336 } 337 if (!waitingForSatellite) { 338 waitingForSatellite = true; 339 waitForSatellite(drainQueue, () => { 340 waitingForSatellite = false; 341 }); 342 } 343}; 344const HEADER_LANGUAGE_DROPDOWN_OPEN = "header_language_dropdown_open"; 345const HEADER_LANGUAGE_SWITCH = "header_language_switch"; 346const trackLanguageDropdownOpen = () => { 347 aaTrackEvent(HEADER_LANGUAGE_DROPDOWN_OPEN); 348}; 349const gtranslateScriptUrl = "https://cdn.gtranslate.net/widgets/latest/ln.js"; 350const defaultSettings = { 351 default_language: "en", 352 native_language_names: true, 353 languages: ["en", "es", "fr", "zh-CN", "tr", "ru", "ja", "vi", "pt"], 354 wrapper_selector: ".gtranslate_wrapper", 355 alt_flags: { en: "usa", es: "mexico", pt: "brazil" } 356}; 357let windowLoadPromise; 358let widgetLoadPromise; 359const waitForWindowLoad = () => { 360 if (document.readyState === "complete") { 361 return Promise.resolve(); 362 } 363 windowLoadPromise ??= new Promise((resolve) => { 364 window.addEventListener("load", resolve, { once: true }); 365 }); 366 return windowLoadPromise; 367}; 368const hasLanguageLinks = () => document.querySelector(".gtranslate_wrapper a[data-gt-lang]") !== null; 369const loadWidget = () => new Promise((resolve) => { 370 const script = document.createElement("script"); 371 script.src = gtranslateScriptUrl; 372 script.dataset.iabbbGtranslateLoader = "true"; 373 script.addEventListener("load", resolve, { once: true }); 374 script.addEventListener("error", resolve, { once: true }); 375 document.head.append(script); 376}); 377const ensureGTranslate = async () => { 378 await waitForWindowLoad(); 379 if (hasLanguageLinks()) { 380 return; 381 } 382 window.gtranslateSettings ??= defaultSettings; 383 widgetLoadPromise ??= loadWidget(); 384 await widgetLoadPromise; 385 widgetLoadPromise = void 0; 386}; 387const trackLanguageSwitch = ({ languageCode }) => { 388 if (document.documentElement.lang === languageCode) { 389 return; 390 } 391 aaTrackEvent(HEADER_LANGUAGE_SWITCH, { 392 language_code: languageCode 393 }); 394}; 395const languageDisplayMap = { 396 en: "EN", 397 es: "ES", 398 fr: "FR", 399 "zh-CN": "ZH", 400 tr: "TR", 401 ru: "RU", 402 ja: "JA", 403 vi: "VI", 404 pt: "PT" 405}; 406const updateLangLink = (link, closeFunc) => { 407 const langCode = link.getAttribute("data-gt-lang"); 408 if (!languageDisplayMap[langCode] || link.dataset.iabbbLanguageLinkEnhanced === "true") { 409 return; 410 } 411 link.dataset.iabbbLanguageLinkEnhanced = "true"; 412 const languageName = link.textContent; 413 const languageCode = document.createElement("span"); 414 languageCode.className = "language-list-code"; 415 languageCode.textContent = languageDisplayMap[langCode]; 416 link.replaceChildren(languageCode, languageName); 417 link.addEventListener("click", () => { 418 trackLanguageSwitch({ languageCode: langCode }); 419 closeFunc(); 420 }); 421}; 422class NavMenu extends HTMLElement { 423 connectedCallback() { 424 this.moveHeaderMenu(); 425 this.replaceLinkWithButton(); 426 document.addEventListener("keydown", this.escapeHandler); 427 this.addEventListener("click", this.clickEventHandler); 428 this.collapseSubnavs(); 429 this.setUpLanguageMenu(); 430 this.languageLinksObserver = new MutationObserver(this.setUpLanguageMenu); 431 this.languageLinksObserver.observe(this, { childList: true, subtree: true }); 432 } 433 disconnectedCallback() { 434 document.removeEventListener("keydown", this.escapeHandler); 435 document.removeEventListener("click", this.lightDismissHandler); 436 this.removeEventListener("click", this.clickEventHandler); 437 this.languageLinksObserver?.disconnect(); 438 } 439 clickEventHandler = (event) => { 440 const subnavButton = event.target.closest(".has-subnav button"); 441 if (subnavButton) { 442 this.toggleSubnav(subnavButton); 443 return; 444 } 445 const languageButton = event.target.closest(".language-dropdown-link"); 446 if (languageButton) {
447 if (languageButton.getAttribute("aria-expanded") === "false") { 448 trackLanguageDropdownOpen(); 449 } 450 this.toggleSubnav(languageButton); 451 } 452 }; 453 /** 454 * Emit a custom event 455 * @param {string} type - The event type 456 * @param {object} detail - Any details to pass along with the event 457 */ 458 emit = (type, detail = {}) => { 459 const event = new CustomEvent(`${this.localName}:${type}`, { 460 bubbles: true, 461 cancelable: true, 462 detail 463 }); 464 return this.dispatchEvent(event); 465 }; 466 collapseSubnavs = () => { 467 for (const liWithSubnav of this.querySelectorAll(".has-subnav")) { 468 const linkId = liWithSubnav.querySelector("a").getAttribute("id"); 469 const expandBtn = document.createElement("button"); 470 expandBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" aria-hidden="true" focusable="false" height="1em" width="1em"><path d="M241 337c-9.4 9.4-24.6 9.4-33.9 0L47 177c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l143 143L367 143c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9L241 337z"></path></svg>`; 471 expandBtn.setAttribute("aria-expanded", "false"); 472 expandBtn.setAttribute("aria-labelledby", linkId); 473 liWithSubnav.insertBefore(expandBtn, liWithSubnav.querySelector("ul")); 474 } 475 for (const mainNavLink of this.querySelectorAll("#headerNavMenu ul > li a")) { 476 mainNavLink.addEventListener("click", (e) => { 477 this.emit("click-main-link", { label: e.currentTarget.innerText }); 478 }); 479 } 480 for (const subNavLink of this.querySelectorAll("#headerNavMenu ul > li ul a")) { 481 subNavLink.addEventListener("click", (e) => { 482 const parentElement = e.currentTarget.closest("li").closest("ul").closest("li"); 483 if (!parentElement) { 484 return; 485 } 486 this.emit("click-sub-link", { label: e.currentTarget.innerText, parentLabel: parentElement.innerText }); 487 }); 488 } 489 }; 490 replaceLinkWithButton = () => { 491 const link = this.querySelector('[href="#headerNavMenu"]'); 492 if (!link) { 493 return; 494 } 495 const btn = document.createElement("button"); 496 btn.innerHTML = link.innerHTML; 497 btn.className = link.className; 498 btn.setAttribute("aria-expanded", "false"); 499 link.replaceWith(btn); 500 btn.addEventListener("click", this.toggleMenu); 501 }; 502 moveHeaderMenu() { 503 const navMenu = document.getElementById("headerNavMenu"); 504 this.querySelector("nav").append(navMenu); 505 } 506 toggleMenu = () => { 507 const isOpen = this.querySelector("button").getAttribute("aria-expanded") === "true"; 508 if (isOpen) { 509 this.closeMenu(); 510 } else { 511 document.addEventListener("keydown", this.escapeHandler); 512 this.addEventListener("focusout", this.focusOutHandler); 513 document.addEventListener("click", this.lightDismissHandler); 514 this.querySelector("nav").setAttribute("data-open", "true"); 515 this.querySelector("button").setAttribute("aria-expanded", "true"); 516 this.trackMenuOpen(); 517 } 518 }; 519 closeMenu = () => { 520 document.removeEventListener("keydown", this.escapeHandler); 521 this.removeEventListener("focusout", this.focusOutHandler); 522 document.removeEventListener("click", this.lightDismissHandler); 523 this.querySelector("nav").removeAttribute("data-open"); 524 this.querySelector("button").setAttribute("aria-expanded", "false"); 525 for (const subnavButton of this.querySelectorAll('.nav-menu button[aria-expanded="true"]')) { 526 subnavButton.setAttribute("aria-expanded", "false"); 527 } 528 this.trackMenuClose(); 529 }; 530 trackMenuOpen = () => { 531 this.querySelector("button").classList.remove("dtm-header-menu-button-closed"); 532 this.querySelector("button").classList.add("dtm-header-menu-button-opened"); 533 this.emit("expanded"); 534 }; 535 trackMenuClose = () => { 536 this.querySelector("button").classList.remove("dtm-header-menu-button-opened"); 537 this.querySelector("button").classList.add("dtm-header-menu-button-closed"); 538 this.emit("collapsed"); 539 }; 540 toggleSubnav = (node) => { 541 const isCurrentlyExpanded = node.getAttribute("aria-expanded");
542 this.emit("toggle-subnav", { 543 label: node.innerText, 544 isCurrentlyExpanded 545 }); 546 node.setAttribute("aria-expanded", isCurrentlyExpanded === "false" ? "true" : "false"); 547 }; 548 escapeHandler = (e) => { 549 if (e.isComposing) { 550 return; 551 } 552 const isOpen = this.querySelector("button").getAttribute("aria-expanded") === "true"; 553 if (e.key === "Escape" && isOpen) { 554 this.toggleMenu(); 555 } 556 }; 557 lightDismissHandler = (event) => { 558 if (!event.composedPath().includes(this)) { 559 this.toggleMenu(); 560 } 561 }; 562 focusOutHandler = (event) => { 563 if (!event.relatedTarget) { 564 return; 565 } 566 const menuContainsFocus = this.contains(event.relatedTarget); 567 if (!menuContainsFocus) { 568 this.closeMenu(); 569 } 570 }; 571 setUpLanguageMenu = () => { 572 ensureGTranslate(); 573 const languageLinks = this.querySelectorAll(".language-subnav-list .gtranslate_wrapper a"); 574 for (const link of languageLinks) { 575 updateLangLink(link, this.closeMenu); 576 } 577 }; 578} 579customElements.define("iabbb-nav-menu", NavMenu); 580class DesktopNav { 581 constructor() { 582 this.nav = document.querySelector(".desktop-nav"); 583 if (!this.nav) { 584 return; 585 } 586 this.buttons = this.nav.querySelectorAll(".desktop-nav-button"); 587 this.init(); 588 } 589 init() { 590 this.buttons.forEach((button) => { 591 button.addEventListener("click", (e) => this.toggleDropdown(e.currentTarget)); 592 }); 593 document.addEventListener("click", (e) => { 594 if (!e.target.closest(".desktop-nav-item")) { 595 this.closeAllDropdowns(); 596 } 597 }); 598 document.addEventListener("keydown", (e) => { 599 if (e.key === "Escape") { 600 this.closeAllDropdowns(); 601 } 602 }); 603 } 604 toggleDropdown(button) { 605 const isExpanded = button.getAttribute("aria-expanded") === "true"; 606 this.closeAllDropdowns(); 607 if (!isExpanded) { 608 button.setAttribute("aria-expanded", "true"); 609 } 610 } 611 closeAllDropdowns() { 612 this.buttons.forEach((button) => { 613 button.setAttribute("aria-expanded", "false"); 614 }); 615 } 616} 617if (document.readyState === "loading") { 618 document.addEventListener("DOMContentLoaded", () => new DesktopNav()); 619} else { 620 new DesktopNav(); 621} 622class DesktopLanguageSelector extends HTMLElement { 623 connectedCallback() { 624 this.button = this.querySelector("button"); 625 if (!this.button) { 626 return; 627 } 628 this.button.addEventListener("click", this.openDialog); 629 this.dialogRoot = this.closest("iabbb-header") || document.body; 630 const dialog = this.dialogRoot.querySelector(".language-dialog"); 631 if (dialog) { 632 this.setupDialog(dialog); 633 } else { 634 this.dialogObserver = new MutationObserver(() => { 635 const dialog2 = this.dialogRoot.querySelector(".language-dialog"); 636 if (dialog2) { 637 this.dialogObserver.disconnect(); 638 this.setupDialog(dialog2); 639 } 640 }); 641 this.dialogObserver.observe(this.dialogRoot, { childList: true, subtree: true }); 642 } 643 } 644 disconnectedCallback() { 645 this.button?.removeEventListener("click", this.openDialog); 646 this.dialogObserver?.disconnect(); 647 this.languageLinksObserver?.disconnect(); 648 this.dialog?.removeEventListener("click", this.handleDialogClick); 649 this.dialog?.removeEventListener("cancel", this.handleDialogCancel); 650 this.dialog?.removeEventListener("close", this.handleDialogClose); 651 } 652 setupDialog = (dialog) => { 653 this.dialog = dialog; 654 this.setupCloseHandlers(); 655 this.setupLanguageLinks(); 656 this.languageLinksObserver = new MutationObserver(this.setupLanguageLinks); 657 this.languageLinksObserver.observe(dialog, { childList: true, subtree: true }); 658 }; 659 openDialog = () => { 660 if (!this.dialog || this.dialog.open) { 661 return; 662 } 663 trackLanguageDropdownOpen(); 664 this.positionDialog(); 665 this.dialog.showModal(); 666 this.button.setAttribute("aria-expanded", "true"); 667 }; 668 closeDialog = () => { 669 if (this.dialog?.open) { 670 this.dialog.close(); 671 } 672 this.resetDialogTrigger(); 673 }; 674 setupLanguageLinks = () => { 675 ensureGTranslate(); 676 const languageLinks = this.dialog.querySelectorAll(".gtranslate_wrapper a"); 677 for (const link of languageLinks) { 678 updateLangLink(link, this.closeDialog); 679 } 680 }; 681 positionDialog = () => { 682 const buttonRect = this.button.getBoundingClientRect(); 683 this.dialog.style.position = "fixed"; 684 this.dialog.style.left = `${buttonRect.left}px`; 685 this.dialog.style.top = `${buttonRect.bottom + 8}px`; 686 this.dialog.style.margin = "0"; 687 }; 688 handleDialogClick = (event) => { 689 const dialogDimensions = this.dialog.getBoundingClientRect(); 690 if (event.clientX < dialogDimensions.left || event.clientX > dialogDimensions.right || event.clientY < dialogDimensions.top || event.clientY > dialogDimensions.bottom) { 691 this.closeDialog(); 692 } 693 }; 694 handleDialogCancel = (event) => { 695 event.preventDefault(); 696 this.closeDialog(); 697 }; 698 handleDialogClose = () => { 699 if (!this.dialog.open) { 700 this.resetDialogTrigger(); 701 } 702 }; 703 resetDialogTrigger = () => { 704 this.button.setAttribute("aria-expanded", "false"); 705 this.button.focus(); 706 }; 707 setupCloseHandlers = () => { 708 this.dialog.addEventListener("click", this.handleDialogClick); 709 this.dialog.addEventListener("cancel", this.handleDialogCancel); 710 this.dialog.addEventListener("close", this.handleDialogClose); 711 }; 712} 713customElements.define("iabbb-language-selector", DesktopLanguageSelector); 714class LocaleSelector extends HTMLElement { 715 constructor() { 716 super(); 717 this.render = () => { 718 this.showChangeLocationView = false;
719 this.initData(); 720 this.useDialogElement = false; 721 if (this.useDialogElement) { 722 this.addDialogElement(); 723 } 724 this.replaceLinkWithButton(); 725 }; 726 } 727 connectedCallback() { 728 this.render(); 729 } 730 initData = () => { 731 const rawCookie = getCookie("iabbb_user_location"); 732 const userLocationId = rawCookie ? decodeURIComponent(rawCookie) : null; 733 if (userLocationId) { 734 const userLocationIdParts = decodeURIComponent(userLocationId).split("_"); 735 const userCountry = userLocationIdParts[userLocationIdParts.length - 1]; 736 const userState = userLocationIdParts[userLocationIdParts.length - 2]; 737 const userCity = userLocationIdParts.slice(0, userLocationIdParts.length - 2).join(" "); 738 if (userCountry && userState && userCity) { 739 this.userLocationName = userCountry && userState && userCity ? `${userCity}, ${userState}, ${userCountry}` : null; 740 this.userLocationUrl = `/${[ 741 `${countries.find((x) => x.value === userCountry).shortLabel.toLowerCase()}`, 742 `${userState.toLowerCase()}`, 743 `${userCity.toLowerCase().replace(" ", "-")}` 744 ].join("/")}`; 745 } 746 } 747 if (window.headerFooterData?.bbb) { 748 this.bbbName = window.headerFooterData.bbb.name; 749 this.bbbUrl = window.headerFooterData.bbb.url; 750 } 751 this.hasUserLocation = this.bbbName && this.userLocationName; 752 }; 753 /** 754 * Emit a custom event 755 * @param {string} name - The event name 756 * @param {object} detail - Any details to pass along with the event 757 */ 758 emit = (name, detail = {}) => { 759 const event = new CustomEvent(name, { 760 bubbles: true, 761 cancelable: true, 762 composed: true, 763 detail 764 }); 765 return this.dispatchEvent(event); 766 }; 767 addDialogElement = () => { 768 const closeIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" aria-hidden="true" fill="currentColor" focusable="false" height="1em" width="1em"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256l105.3-105.4z"></path></svg>`; 769 const visuallyHiddenLabel = `<span class="visually-hidden">close outage disclaimer</span>`; 770 const closeButton = document.createElement("button"); 771 closeButton.className = "ls-close-button bds-button-unstyled"; 772 closeButton.innerHTML = `${closeIcon}${visuallyHiddenLabel}`; 773 const dialog = document.createElement("dialog"); 774 dialog.className = "locale-selector-dialog"; 775 dialog.tabIndex = -1; 776 const dialogContent = document.createElement("div"); 777 const formMethodDialog = document.createElement("form"); 778 formMethodDialog.method = "dialog"; 779 formMethodDialog.appendChild(closeButton); 780 dialogContent.appendChild(formMethodDialog); 781 dialog.appendChild(dialogContent); 782 const screen = document.createElement("div"); 783 screen.id = "locale-selector-screen"; 784 if (this.hasUserLocation) { 785 screen.appendChild(this.buildShowLocationDialogContent()); 786 } else { 787 screen.appendChild(this.buildChangeLocationDialogContent()); 788 } 789 dialogContent.appendChild(screen); 790 dialog.addEventListener("click", ({ target: dialog2 }) => { 791 if (dialog2.nodeName === "DIALOG") { 792 dialog2.close("dismiss"); 793 } 794 }); 795 dialog.addEventListener("close", () => { 796 if (this.showChangeLocationView) { 797 screen.innerHTML = ""; 798 screen.appendChild(this.buildShowLocationDialogContent()); 799 this.showChangeLocationView = false; 800 } 801 }); 802 this.appendChild(dialog); 803 }; 804 buildAndOpenLegacyDialog = () => { 805 const closeIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" aria-hidden="true" fill="currentColor" focusable="false" height="1em" width="1em"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256l105.3-105.4z"></path></svg>`; 806 const visuallyHiddenLabel = `<span class="visually-hidden">close outage disclaimer</span>`;
807 const closeButton = document.createElement("button"); 808 closeButton.className = "ls-close-button bds-button-unstyled"; 809 closeButton.innerHTML = `${closeIcon}${visuallyHiddenLabel}`; 810 const dialog = document.createElement("div"); 811 dialog.role = "dialog"; 812 dialog.className = "locale-selector-dialog"; 813 dialog.tabIndex = -1; 814 const dialogContent = document.createElement("div"); 815 closeButton.onclick = () => { 816 this.destroyLegacyDialog(); 817 this.querySelector("button").focus(); 818 }; 819 dialogContent.appendChild(closeButton); 820 dialog.appendChild(dialogContent); 821 const screen = document.createElement("div"); 822 screen.id = "locale-selector-screen"; 823 if (this.hasUserLocation) { 824 screen.appendChild(this.buildShowLocationDialogContent()); 825 } else { 826 screen.appendChild(this.buildChangeLocationDialogContent()); 827 } 828 dialogContent.appendChild(screen); 829 this.updateDialogPosition(dialog); 830 const dialogBackdrop = document.createElement("div"); 831 dialogBackdrop.className = "locale-selector-dialog-backdrop"; 832 document.body.appendChild(dialogBackdrop); 833 dialogBackdrop.addEventListener("click", () => { 834 this.destroyLegacyDialog(); 835 }); 836 document.body.appendChild(dialog); 837 dialog.focus(); 838 dialog.addEventListener("keydown", (e) => { 839 if (e.key === "Escape") { 840 this.destroyLegacyDialog(); 841 this.querySelector("button").focus(); 842 } 843 }); 844 window.addEventListener("resize", this.handleLegacyDialogPositionOnResize, { passive: true }); 845 }; 846 handleLegacyDialogPositionOnResize = () => { 847 const dialogToListen = document.querySelector(".locale-selector-dialog"); 848 this.updateDialogPosition(dialogToListen); 849 }; 850 destroyLegacyDialog = () => { 851 document.querySelector(".locale-selector-dialog-backdrop").remove(); 852 document.querySelector(".locale-selector-dialog").remove(); 853 window.removeEventListener("resize", this.handleLegacyDialogPositionOnResize); 854 for (const nonDialogElement of this.nonDialogElements) { 855 nonDialogElement.removeAttribute("inert"); 856 } 857 }; 858 // Use only when we rely solely on native dialog/anchor positioning 859 // updateLinkIfUserHasNoLocation = () => { 860 // const link = this.querySelector('a'); 861 // if (!this.hasUserLocation) { 862 // link.innerHTML = link.innerHTML.replace('My BBB', 'Set location'); 863 // } 864 // }; 865 replaceLinkWithButton = () => { 866 const link = this.querySelector("a"); 867 if (!link) { 868 return; 869 } 870 const btn = document.createElement("button"); 871 btn.innerHTML = this.hasUserLocation ? link.innerHTML : link.innerHTML.replace("My BBB", "Set location"); 872 btn.className = `${link.className} bds-button-unstyled`; 873 btn.setAttribute("aria-expanded", "false"); 874 btn.onclick = (e) => { 875 this.emit(`${this.localName}:open-dialog`, { 876 label: e.target.innerText 877 }); 878 if (this.useDialogElement) { 879 this.querySelector("dialog").showModal(); 880 this.querySelector("dialog").focus(); 881 } else { 882 this.nonDialogElements = document.querySelectorAll("body > :not(script)"); 883 for (const nonDialogElement of this.nonDialogElements) { 884 nonDialogElement.setAttribute("inert", "inert"); 885 } 886 this.buildAndOpenLegacyDialog(); 887 } 888 }; 889 link.replaceWith(btn); 890 }; 891 updateDialogPosition = (dialog) => { 892 const buttonBoundingClientRect = this.querySelector("button").getBoundingClientRect(); 893 dialog.style.left = `${buttonBoundingClientRect.left}px`; 894 dialog.style.top = `${buttonBoundingClientRect.top}px`; 895 }; 896 buildShowLocationDialogContent = () => { 897 const linkIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true" fill="currentColor" focusable="false" height="1em" width="1em"><path d="M0 256a256 256 0 1 0 512 0 256 256 0 1 0-512 0zm241 121c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l87-87-87-87c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0L345 239c9.4 9.4 9.4 24.6 0 33.9L241 377z"></path></svg>`; 898 const locationHeading = document.createElement("h2"); 899 locationHeading.tabIndex = -1; 900 locationHeading.innerText = "My Home Location"; 901 const locationLink = document.createElement("a"); 902 locationLink.className = "active-underline"; 903 locationLink.href = this.userLocationUrl; 904 locationLink.innerHTML = `${this.userLocationName}${linkIcon}`; 905 locationLink.onclick = () => {
906 this.emit(`${this.localName}:click-location-link`, { 907 location: this.userLocationName 908 }); 909 }; 910 const changeLocationButton = document.createElement("button"); 911 changeLocationButton.className = "bds-button-unstyled ls-change-home-location"; 912 changeLocationButton.innerText = "Change home location"; 913 changeLocationButton.onclick = () => { 914 this.showChangeLocationView = true; 915 document.querySelector("#locale-selector-screen").innerHTML = ""; 916 document.querySelector("#locale-selector-screen").appendChild(this.buildChangeLocationDialogContent()); 917 document.querySelector("#locale-selector-screen h2").focus(); 918 }; 919 const bbbHeading = document.createElement("h2"); 920 bbbHeading.className = "my-bbb-heading"; 921 bbbHeading.innerText = "My BBB"; 922 const bbbLink = document.createElement("a"); 923 bbbLink.className = "active-underline dtm-locale-selector-location-name"; 924 bbbLink.href = this.bbbUrl; 925 bbbLink.innerHTML = `${this.bbbName}${linkIcon}`; 926 const container = document.createElement("div"); 927 container.appendChild(locationHeading); 928 container.appendChild(locationLink); 929 container.appendChild(changeLocationButton); 930 container.appendChild(bbbHeading); 931 container.appendChild(bbbLink); 932 return container; 933 }; 934 buildChangeLocationDialogContent = () => { 935 const legend = document.createElement("legend"); 936 const heading = document.createElement("h2"); 937 heading.tabIndex = -1; 938 heading.innerText = "Set Your Home Location:"; 939 legend.appendChild(heading); 940 const customSelect = document.createElement("iabbb-ls-country-custom-select"); 941 customSelect.className = "enhanced-select"; 942 customSelect.innerHTML = ` 943 <label class="visually-hidden" for="ls_country">Country</label> 944 <select id="ls_country" name="ls_country"> 945 <option value="USA">United States</option> 946 <option value="CAN">Canada</option> 947 </select> 948 `; 949 const nearAutocomplete = document.createElement("iabbb-ls-autocomplete"); 950 nearAutocomplete.innerHTML = ` 951 <div class="input-group"> 952 <label class="dtm-locale-selector-location-pin" for="ls_loc"> 953 <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 384 512" aria-hidden="true" focusable="false" height="1em" width="1em"> 954 <path d="M215.7 499.2C267 435 384 279.4 384 192 384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2 12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"></path> 955 </svg> 956 <span class="visually-hidden">Near</span> 957 </label> 958 <input id="ls_loc" name="ls_loc" placeholder="city, state or zip" autocomplete="off" autocapitalize="none"> 959 </div> 960 `; 961 nearAutocomplete.className = "autocomplete"; 962 const button = document.createElement("button"); 963 button.className = "bds-button dtm-locale-selector-set-location"; 964 button.innerText = "Set Location"; 965 const fieldset = document.createElement("fieldset"); 966 fieldset.className = "change-location"; 967 fieldset.appendChild(legend); 968 fieldset.appendChild(customSelect); 969 fieldset.appendChild(nearAutocomplete); 970 fieldset.appendChild(button); 971 const form = document.createElement("form"); 972 form.onsubmit = this.onLocationChangeFormSubmit; 973 form.appendChild(fieldset); 974 return form; 975 }; 976 showLocationError = () => { 977 const locationError = "Location must be a valid city (e.g. Los Angeles, CA) or postal code (e.g. 90210)"; 978 const warningIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true" height="1em" width="1em" fill="currentColor"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224c0-17.7-14.3-32-32-32s-32 14.3-32 32 14.3 32 32 32 32-14.3 32-32z"></path></svg>`; 979 const inputId = document.querySelector("iabbb-ls-autocomplete input").id; 980 const error = document.createElement("span"); 981 error.className = "error"; 982 error.id = `${inputId}-error`; 983 error.innerHTML = `${warningIcon}${locationError}`; 984 const autocomplete = document.querySelector("iabbb-ls-autocomplete"); 985 autocomplete.parentNode.insertBefore(error, autocomplete); 986 const autocompleteInput = autocomplete.querySelector("input"); 987 autocompleteInput.setAttribute("aria-describedby", error.id); 988 autocompleteInput.setAttribute("aria-invalid", true); 989 autocompleteInput.focus(); 990 }; 991 clearLocationError = () => { 992 const error = document.querySelector(".error"); 993 if (error) { 994 error.remove(); 995 const autocomplete = document.querySelector("iabbb-ls-autocomplete"); 996 const autocompleteInput = autocomplete.querySelector("input"); 997 autocompleteInput.removeAttribute("aria-describedby"); 998 autocompleteInput.removeAttribute("aria-invalid"); 999 } 1000 }; 1001 onLocationChangeFormSubmit = async (e) => { 1002 e.preventDefault(); 1003 this.clearLocationError(); 1004 const initialUserLocationId = getCookie("iabbb_user_location"); 1005 const initialUserBbbId = getCookie("iabbb_user_bbb"); 1006 const formValues = Object.fromEntries(new FormData(e.target)); 1007 const selectedCountryName = formValues.ls_country; 1008 const selectedLocation = formValues.ls_loc; 1009 const locationId = await fetch( 1010 `${window.headerFooterData.baseUrl}/api/suggest/location?country=${selectedCountryName}&input=${selectedLocation}&maxMatches=1`, 1011 { credentials: "omit" } 1012 ).then((res) => { 1013 return res.json(); 1014 }).then((matches) => { 1015 return matches.length > 0 ? matches[0].id : null; 1016 }); 1017 if (locationId) { 1018 fetch(`${window.headerFooterData.baseUrl}/api/location/bbblocation?locationId=${locationId}`).then((res) => { 1019 if (!res.ok) { 1020 throw new Error(`${res.statusText} (status: ${res.status})`); 1021 } 1022 return res.json(); 1023 }).then((location) => { 1024 const locationId2 = `${location.city}_${location.state}_${location.threeLetterIsoCountryCode}`;
1025 setCookie("iabbb_user_location", locationId2, defaultExpiration); 1026 setCookie("iabbb_user_postalcode", location.postalCode, defaultExpiration); 1027 if (location.bbbInfo?.bbbId) { 1028 setCookie("iabbb_user_bbb", location.bbbInfo.bbbId, defaultExpiration); 1029 window.headerFooterData = { 1030 ...window.headerFooterData || {}, 1031 bbb: { 1032 name: location.bbbInfo.bbbName, 1033 url: location.bbbInfo.urls.bbbUrl 1034 } 1035 }; 1036 this.bbbName = window.headerFooterData?.bbb.name; 1037 this.bbbUrl = window.headerFooterData?.bbb.url; 1038 this.userLocationUrl = `/${location.country.toLowerCase()}/${location.state.toLowerCase()}/${location.citySeo}`; 1039 this.userLocationName = `${location.city}, ${location.state}, ${location.threeLetterIsoCountryCode}`; 1040 } 1041 const nearInput = document.getElementById("find_loc"); 1042 if (nearInput && !nearInput.value) { 1043 nearInput.value = location.displayText; 1044 setCookie("iabbb_find_location", locationId2, defaultExpiration); 1045 } 1046 const savedCulture = getCookie("iabbb_user_culture"); 1047 const selectedCountry = countries.find((x) => x.value === selectedCountryName); 1048 if (selectedCountry.culture !== savedCulture) { 1049 setCookie("iabbb_user_culture", selectedCountry.culture, defaultExpiration); 1050 setCookie("iabbb_find_location", locationId2, defaultExpiration); 1051 } 1052 this.emit(`${this.localName}:set-location`, { 1053 county: selectedCountry.fullLabel, 1054 culture: selectedCountry.culture, 1055 location: locationId2 1056 }); 1057 if (decodeURIComponent(initialUserLocationId) !== locationId2) { 1058 this.emit("iabbb:localeselector.setlocation", { 1059 id: location.id, 1060 city: location.city, 1061 state: location.state, 1062 country: location.country, 1063 postalCode: location.postalCode, 1064 latLng: location.latLng, 1065 bbbInfo: { 1066 bbbId: location.bbbInfo.bbbId, 1067 bbbName: location.bbbInfo.bbbName, 1068 primaryCountry: location.bbbInfo.primaryCountry, 1069 primaryLanguage: location.bbbInfo.primaryLanguage 1070 } 1071 }); 1072 if (initialUserBbbId !== location.bbbInfo.bbbId) { 1073 fetch(`${window.headerFooterData.baseUrl}/header-footer/api/bbb-data?id=${location.bbbInfo.bbbId}`).then((res) => { 1074 if (!res.ok) { 1075 throw new Error(`${res.statusText} (status: ${res.status})`); 1076 } 1077 return res.json(); 1078 }).then((location2) => { 1079 this.emit("hf:set-bbb", location2); 1080 }); 1081 } 1082 } 1083 if (!initialUserLocationId) { 1084 this.querySelector("button").innerHTML = this.querySelector("button").innerHTML.replace( 1085 "Set location", 1086 "My BBB" 1087 ); 1088 } 1089 const screen = document.querySelector("#locale-selector-screen"); 1090 screen.innerHTML = ""; 1091 screen.appendChild(this.buildShowLocationDialogContent()); 1092 this.showChangeLocationView = false; 1093 document.querySelector("#locale-selector-screen h2").focus(); 1094 }).catch((err) => { 1095 console.error(err); 1096 }); 1097 } else { 1098 this.showLocationError(); 1099 } 1100 }; 1101} 1102customElements.define("iabbb-locale-selector", LocaleSelector); 1103class LocaleSelectorCountryCustomSelect extends BaseCustomSelect { 1104 getInitialValue = () => { 1105 const savedCulture = getCookie("iabbb_user_culture"); 1106 if (!savedCulture) { 1107 return this.select.value; 1108 } 1109 return countries.find((x) => x.culture === savedCulture)?.value || this.select.value; 1110 }; 1111 getOptionHtml = (val) => { 1112 const country = countries.find((x) => x.value === val); 1113 return `<img src="${country.flag}" alt="" /><span class="option-label">${country.fullLabel}</span>`; 1114 }; 1115 optionClassName = "dtm-locale-selector-country"; 1116} 1117customElements.define("iabbb-ls-country-custom-select", LocaleSelectorCountryCustomSelect); 1118class LocaleSelectorAutocomplete extends BaseAutocomplete { 1119 connectedCallback() { 1120 this.render(); 1121 self.addEventListener("change-ls-language", this.changeLanguageEventListener); 1122 const savedNearLocationId = decodeURIComponent(getCookie("iabbb_user_location")); 1123 if (savedNearLocationId) { 1124 const nearLocationIdParts = decodeURIComponent(savedNearLocationId).split("_"); 1125 const nearState = nearLocationIdParts[nearLocationIdParts.length - 2]; 1126 const nearCity = nearLocationIdParts.slice(0, nearLocationIdParts.length - 2).join(" "); 1127 if (nearState && nearCity) { 1128 this.querySelector("input").value = `${nearCity}, ${nearState}`; 1129 this.showClearButton(); 1130 } 1131 } 1132 } 1133 disconnectedCallback() { 1134 self.removeEventListener("change-ls-language", this.changeLanguageEventListener); 1135 } 1136 getOptionAttributes = (result, inputText) => { 1137 return { 1138 html: inputText.trim().length > 1 ? result.displayText.replaceAll(new RegExp(inputText, "ig"), (match) => `<span>${match}</span>`) : result.displayText, 1139 attributes: result.isCurrentLocation ? [{ name: "data-value", value: result.value }] : null 1140 }; 1141 }; 1142 getOptions = (query) => { 1143 const userLocation = decodeURIComponent(getCookie("iabbb_user_physical_location")); 1144 const country = document.querySelector('[name="ls_country"]').value;
1145 let currentLocationOption; 1146 if (userLocation) { 1147 const [city, state, userCountry] = userLocation.split(", "); 1148 if (country === userCountry) { 1149 currentLocationOption = { 1150 displayText: "Current location", 1151 value: `${city}, ${state}`, 1152 isCurrentLocation: true 1153 }; 1154 } 1155 } 1156 if (query.trim().length === 0) { 1157 return currentLocationOption ? Promise.resolve([currentLocationOption]) : Promise.resolve([]); 1158 } 1159 return fetch( 1160 `${window.headerFooterData.baseUrl}/api/suggest/location?country=${country}&input=${query}&maxMatches=6`, 1161 { credentials: "omit" } 1162 ).then((res) => { 1163 if (res.ok) { 1164 return res.json(); 1165 } 1166 return res.text().then((msg) => { 1167 throw msg; 1168 }); 1169 }).then((results) => currentLocationOption ? [currentLocationOption, ...results] : results).catch((error) => { 1170 console.error(error); 1171 return Promise.resolve([]); 1172 }); 1173 }; 1174 changeLanguageEventListener = () => { 1175 this.querySelector("input").value = ""; 1176 }; 1177} 1178customElements.define("iabbb-ls-autocomplete", LocaleSelectorAutocomplete); 1179class Footer extends HTMLElement { 1180 connectedCallback() { 1181 this.addAnalyticsEventListeners(); 1182 } 1183 disconnectedCallback() { 1184 this.removeAnalyticsEventListeners(); 1185 } 1186 addAnalyticsEventListeners = () => { 1187 const logo = this.querySelector("[data-logo-link]"); 1188 logo.addEventListener("click", this.handleLogoClick); 1189 const mainLinks = this.querySelectorAll(".main .dtm-footer-link"); 1190 for (const mainLink of mainLinks) { 1191 mainLink.addEventListener("click", this.handleMainLinkClick); 1192 } 1193 const bottomLinks = this.querySelectorAll(".bottom .dtm-footer-link"); 1194 for (const bottomLink of bottomLinks) { 1195 bottomLink.addEventListener("click", this.handleBottomLinkClick); 1196 } 1197 const socialLinks = this.querySelectorAll(".main .dtm-footer-social-link"); 1198 for (const socialLink of socialLinks) { 1199 socialLink.addEventListener("click", this.handleSocialLinkClick); 1200 } 1201 }; 1202 removeAnalyticsEventListeners = () => { 1203 const logo = this.querySelector("[data-logo-link]"); 1204 logo.removeEventListener("click", this.handleLogoClick); 1205 const mainLinks = this.querySelectorAll(".main .dtm-footer-link"); 1206 for (const mainLink of mainLinks) { 1207 mainLink.removeEventListener("click", this.handleMainLinkClick); 1208 } 1209 const bottomLinks = this.querySelectorAll(".bottom .dtm-footer-link"); 1210 for (const bottomLink of bottomLinks) { 1211 bottomLink.removeEventListener("click", this.handleBottomLinkClick); 1212 } 1213 const socialLinks = this.querySelectorAll(".main .dtm-footer-social-link"); 1214 for (const socialLink of socialLinks) { 1215 socialLink.removeEventListener("click", this.handleSocialLinkClick); 1216 } 1217 }; 1218} 1219customElements.define("iabbb-footer", Footer); 1220class Header extends HTMLElement { 1221 connectedCallback() { 1222 self.addEventListener("hf:set-bbb", this.handleSetBbb); 1223 self.addEventListener("iabbb-country-custom-select:select", this.syncCountryCustomSelects); 1224 this.addAnalyticsEventListeners(); 1225 } 1226 disconnectedCallback() { 1227 self.removeEventListener("hf:set-bbb", this.handleSetBbb); 1228 self.addEventListener("iabbb-country-custom-select:select", this.syncCountryCustomSelects); 1229 this.removeAnalyticsEventListeners(); 1230 } 1231 /** 1232 * Emit a custom event 1233 * @param {string} name - The event name 1234 * @param {object} detail - Any details to pass along with the event 1235 */ 1236 emit = (name, detail = {}) => { 1237 const event = new CustomEvent(name, { 1238 bubbles: true, 1239 cancelable: true, 1240 composed: true, 1241 detail 1242 }); 1243 return this.dispatchEvent(event); 1244 }; 1245 handleSetBbb = (e) => { 1246 this.updatePopularCategories(e.detail.popularCategories); 1247 }; 1248 syncCountryCustomSelects = (e) => { 1249 this.emit("iabbb-country-custom-select:manual-set-option", { value: e.detail.value }); 1250 }; 1251 addAnalyticsEventListeners = () => { 1252 self.addEventListener("iabbb-locale-selector:click-location-link", this.handleClickLocaleSelectorLocationLink); 1253 self.addEventListener("iabbb-search-bar:execute", this.handleSearchExecute); 1254 self.addEventListener("iabbb-find-autocomplete:type", this.handleFindAutocompleteType); 1255 self.addEventListener("iabbb-near-autocomplete:type", this.handleNearAutocompleteType); 1256 self.addEventListener("iabbb-find-autocomplete:select-option", this.handleFindAutocompleteSelect); 1257 self.addEventListener("iabbb-near-autocomplete:select-option", this.handleNearAutocompleteSelect); 1258 }; 1259 removeAnalyticsEventListeners = () => { 1260 self.removeEventListener( 1261 "iabbb-locale-selector:click-location-link", 1262 this.handleClickLocaleSelectorLocationLink 1263 ); 1264 self.removeEventListener("iabbb-search-bar:execute", this.handleSearchExecute); 1265 self.removeEventListener("iabbb-find-autocomplete:type", this.handleFindAutocompleteType); 1266 self.removeEventListener("iabbb-near-autocomplete:type", this.handleNearAutocompleteType); 1267 self.removeEventListener("iabbb-find-autocomplete:select-option", this.handleFindAutocompleteSelect); 1268 self.removeEventListener("iabbb-near-autocomplete:select-option", this.handleNearAutocompleteSelect); 1269 }; 1270 updatePopularCategories = (popularCategories) => { 1271 window.headerFooterData = { 1272 ...window.headerFooterData || {}, 1273 bbb: { 1274 ...window.headerFooterData?.bbb || {}, 1275 popularCategories 1276 } 1277 }; 1278 }; 1279 handleSearchExecute = () => { 1280 aaTrackEvent("searchCompleted"); 1281 aaTrackEvent("searchExecuted"); 1282 }; 1283 handleFindAutocompleteType = (e) => { 1284 this.updateSearchDataLayer({ 1285 find_query: e.detail.query, 1286 find_method: "type",
1287 used_type_aheads: "false" 1288 }); 1289 }; 1290 handleNearAutocompleteType = (e) => { 1291 this.updateSearchDataLayer({ 1292 near_query: e.detail.query, 1293 near_method: "type", 1294 used_type_aheads: "false" 1295 }); 1296 }; 1297 handleFindAutocompleteSelect = (e) => { 1298 this.updateSearchDataLayer({ 1299 find_query: e.detail.value, 1300 find_method: "selected", 1301 used_type_aheads: "true" 1302 }); 1303 }; 1304 handleNearAutocompleteSelect = (e) => { 1305 this.updateSearchDataLayer({ 1306 near_query: e.detail.value, 1307 near_method: "selected", 1308 used_type_aheads: "true" 1309 }); 1310 }; 1311 updateSearchDataLayer = (updatedSearchDataLayer) => { 1312 if (window.webDigitalData?.search_info) { 1313 window.webDigitalData.search_info = { 1314 ...window.webDigitalData.search_info, 1315 updatedSearchDataLayer 1316 }; 1317 } 1318 }; 1319} 1320customElements.define("iabbb-header", Header); 1321const cookieGroups = [ 1322 { 1323 name: "Necessary Cookies", 1324 description: "These cookies are necessary for the site to function and cannot be switched off in our systems. They are usually only set in response to actions made by you that amount to a request for services, such as setting your privacy preferences, logging in or filling in forms. You can set your browser to block or alert you about these cookies, but some parts of the site will not work. These cookies do not store any personally identifiable information.", 1325 isReadOnly: true 1326 }, 1327 { 1328 name: "Functional Cookies", 1329 description: "These cookies enable the site to provide enhanced functionality and personalization. They may be set by us or by third party providers whose services we have added to our pages. If you do not allow these cookies, some or all of these services may not function properly." 1330 }, 1331 { 1332 name: "Performance Cookies", 1333 description: "These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site, and will not be able to monitor its performance." 1334 }, 1335 { 1336 name: "Marketing Cookies", 1337 description: "These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant content on other sites. They do not store personal information directly, but are based on uniquely identifying your browser or device. If you do not allow these cookies, you will experience less targeted advertising." 1338 } 1339]; 1340class CookiesMessage extends HTMLElement { 1341 constructor() { 1342 super(); 1343 this.render = () => { 1344 this.enhanceAcceptAllCookies(); 1345 if (typeof HTMLDialogElement === "function") { 1346 this.createPolicyDialog(); 1347 this.replaceManageCookiesLinkWithButton(); 1348 } 1349 this.addScrollPadding(); 1350 }; 1351 } 1352 connectedCallback() { 1353 this.render(); 1354 } 1355 createPolicyDialog = () => { 1356 const dialog = document.createElement("dialog"); 1357 dialog.tabIndex = -1; 1358 const dialogContent = document.createElement("div"); 1359 const heading = document.createElement("h2"); 1360 heading.className = "bds-h2"; 1361 heading.innerText = "Cookie Preferences"; 1362 const paragraph = document.createElement("p"); 1363 paragraph.innerText = "Many websites use cookies or similar tools to store information on your browser or device. We use cookies on BBB websites to remember your preferences, improve website performance and enhance user experience, and to recommend content we believe will be most relevant to you. Most cookies collect anonymous information such as how users arrive at and use the website. Some cookies are necessary to allow the website to function properly, but you may choose to not allow other types of cookies below."; 1364 dialogContent.appendChild(heading); 1365 dialogContent.appendChild(paragraph); 1366 const form = document.createElement("form"); 1367 form.action = "/manage-cookies"; 1368 form.method = "post"; 1369 form.onsubmit = (e) => { 1370 if ("FormData" in window) { 1371 e.preventDefault(); 1372 const formValues = Object.fromEntries(new FormData(e.target)); 1373 this.setCookiesPolicy({ 1374 necessary: true, 1375 functional: formValues.functional === "true", 1376 performance: formValues.performance === "true", 1377 marketing: formValues.marketing === "true" 1378 }); 1379 this.setCookiesPreferencesSet(); 1380 this.querySelector("dialog").close(); 1381 this.showSavedPreferencesMessage(); 1382 } 1383 }; 1384 for (const cookieGroup of cookieGroups) { 1385 const group = this.createGroup(cookieGroup.name, cookieGroup.description, cookieGroup.isReadOnly); 1386 form.appendChild(group); 1387 } 1388 const actions = document.createElement("div"); 1389 actions.className = "actions"; 1390 const saveChangesButton = document.createElement("button"); 1391 saveChangesButton.innerText = "Save Changes"; 1392 saveChangesButton.className = "bds-button"; 1393 const acceptAllButton = document.createElement("button"); 1394 acceptAllButton.className = "bds-button-unstyled"; 1395 acceptAllButton.name = "allow-all"; 1396 acceptAllButton.value = "true"; 1397 acceptAllButton.type = "button"; 1398 acceptAllButton.innerText = "Accept All Cookies"; 1399 acceptAllButton.onclick = () => { 1400 this.setAcceptAllCookiesPolicy(); 1401 this.setCookiesPreferencesSet(); 1402 this.querySelector("dialog").close(); 1403 this.showSavedPreferencesMessage(); 1404 }; 1405 actions.appendChild(saveChangesButton); 1406 actions.appendChild(acceptAllButton); 1407 form.appendChild(actions); 1408 dialogContent.appendChild(form); 1409 dialog.appendChild(dialogContent); 1410 dialog.addEventListener("click", ({ target: dialog2 }) => { 1411 if (dialog2.nodeName === "DIALOG") { 1412 dialog2.close("dismiss"); 1413 } 1414 }); 1415 this.appendChild(dialog); 1416 }; 1417 replaceManageCookiesLinkWithButton = () => { 1418 const link = this.querySelector(".actions a"); 1419 if (!link) { 1420 return; 1421 } 1422 const btn = document.createElement("button"); 1423 btn.innerHTML = link.innerHTML; 1424 btn.className = `${link.className || ""} bds-button-unstyled`; 1425 btn.onclick = () => { 1426 this.querySelector("dialog").showModal(); 1427 this.querySelector("dialog").focus(); 1428 }; 1429 link.replaceWith(btn); 1430 }; 1431 createGroup = (name, description, isReadOnly = false) => {
1432 const id = name.replace(" ", "").replace("Cookies", "").toLowerCase(); 1433 const container = document.createElement("div"); 1434 container.className = "cookies-group"; 1435 const heading = document.createElement("h3"); 1436 heading.className = "bds-h4"; 1437 heading.id = `${id}-heading`; 1438 heading.innerText = name; 1439 const details = document.createElement("details"); 1440 const summary = document.createElement("summary"); 1441 summary.innerText = `What are ${name.toLowerCase()}?`; 1442 const detailsFull = document.createElement("div"); 1443 detailsFull.innerText = description; 1444 details.appendChild(summary); 1445 details.appendChild(detailsFull); 1446 container.appendChild(heading); 1447 container.appendChild(details); 1448 if (isReadOnly) { 1449 const disclaimer = document.createElement("p"); 1450 disclaimer.innerText = `${name} must always be enabled`; 1451 container.appendChild(disclaimer); 1452 } else { 1453 const fields = document.createElement("fieldset"); 1454 fields.setAttribute("aria-labelledby", `${id}-heading`); 1455 const iconRadio = `<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false"> 1456 <circle 1457 class="background" 1458 cx="12" 1459 cy="12" 1460 r="11.5" 1461 stroke="currentColor" 1462 fill="none" 1463 stroke-width="1" 1464 vector-effect="non-scaling-stroke" 1465 /> 1466 <circle class="radio" cx="12" cy="12" fill="currentColor" /> 1467 </svg>`; 1468 const radioContainerTrue = document.createElement("div"); 1469 radioContainerTrue.className = "bds-form-group"; 1470 radioContainerTrue.innerHTML = ` 1471 <div class="bds-radio" data-enhanced="true"> 1472 <input type="radio" id="${id}-input-0" name="${id}" value="true" checked="" /> 1473 ${iconRadio} 1474 <label for="${id}-input-0">Use ${name.toLowerCase()}</label> 1475 </div> 1476 `; 1477 const radioContainerFalse = document.createElement("div"); 1478 radioContainerFalse.className = "bds-form-group"; 1479 radioContainerFalse.innerHTML = ` 1480 <div class="bds-radio" data-enhanced="true"> 1481 <input type="radio" id="${id}-input-1" name="${id}" value="false" /> 1482 ${iconRadio} 1483 <label for="${id}-input-1">Do not use ${name.toLowerCase()}</label> 1484 </div> 1485 `; 1486 fields.appendChild(radioContainerTrue); 1487 fields.appendChild(radioContainerFalse); 1488 container.appendChild(fields); 1489 } 1490 return container; 1491 }; 1492 enhanceAcceptAllCookies = () => { 1493 this.querySelector("form").addEventListener("submit", (e) => { 1494 e.preventDefault(); 1495 this.setAcceptAllCookiesPolicy(); 1496 this.setCookiesPreferencesSet(); 1497 this.showSavedPreferencesMessage(); 1498 }); 1499 }; 1500 setCookiesPolicy = (cookiesPolicy) => { 1501 setCookie("iabbb_cookies_policy", JSON.stringify(cookiesPolicy), defaultExpiration); 1502 }; 1503 setAcceptAllCookiesPolicy = () => { 1504 const allCookiesPolicy = { 1505 necessary: true, 1506 functional: true, 1507 performance: true, 1508 marketing: true 1509 }; 1510 this.setCookiesPolicy(allCookiesPolicy); 1511 }; 1512 setCookiesPreferencesSet = () => { 1513 setCookie("iabbb_cookies_preferences_set", "true", defaultExpiration); 1514 }; 1515 showSavedPreferencesMessage = () => { 1516 const message = this.querySelector(".layout p"); 1517 message.innerHTML = `Your cookie settings have been saved. You can <a href="${window.headerFooterData.baseUrl}/manage-cookies">change your cookie settings</a> at any time.`; 1518 message.tabIndex = -1; 1519 const hideThisMessageButton = document.createElement("button"); 1520 hideThisMessageButton.innerText = "Hide This Message"; 1521 hideThisMessageButton.className = "bds-button"; 1522 hideThisMessageButton.onclick = () => { 1523 this.innerHTML = ""; 1524 }; 1525 this.querySelector(".layout .actions").innerHTML = ""; 1526 this.querySelector(".layout .actions").appendChild(hideThisMessageButton); 1527 message.focus(); 1528 }; 1529 // Add scroll padding to prevent focused elements from being obscured by this message. 1530 // Credit: https://codepen.io/TPG/pen/QWVMXMg 1531 // Changed: Added overflow: auto to inline styles, as scroll-padding was otherwise not taking effect 1532 // Changed: references to header were switched to cookies message container, and scroll-padding-top was switched to scroll-padding-bottom 1533 addScrollPadding = () => { 1534 const MEDIA_MATCH = "(min-width: 20em)"; 1535 const EXTRA_PADDING = 6; 1536 const style = document.head.appendChild(document.createElement("style")); 1537 const scrollFocusedElementIntoView = (height) => { 1538 const focused = document.activeElement || document.body; 1539 if (focused !== document.body && !this.contains(focused)) { 1540 if (focused.getBoundingClientRect().top < height) { 1541 focused.scrollIntoView(); 1542 } 1543 } 1544 }; 1545 const applyScrollPadding = () => { 1546 if (!style) { 1547 return; 1548 } 1549 const height = this.offsetHeight + EXTRA_PADDING; 1550 if ("CSS" in window && CSS.supports("selector(:has(*))")) { 1551 style.innerHTML = `html:has(:not(.cookies-message *, .cookies-policy-dialog *):focus) { scroll-padding-bottom:${height}px; overflow: auto; }`; 1552 } else { 1553 style.innerHTML = `html { scroll-padding-bottom:${height}px; overflow: auto; }`; 1554 } 1555 scrollFocusedElementIntoView(height); 1556 }; 1557 if ("ResizeObserver" in window) { 1558 const observer = new ResizeObserver(applyScrollPadding); 1559 const enableResizeMonitor = () => { 1560 observer.observe(this); 1561 }; 1562 const disableResizeMonitor = () => { 1563 if (!style) { 1564 return; 1565 } 1566 observer.unobserve(this); 1567 style.innerHTML = ""; 1568 }; 1569 const query = window.matchMedia(MEDIA_MATCH); 1570 query.addEventListener("change", (e) => { 1571 if (e.matches) { 1572 enableResizeMonitor(); 1573 } else { 1574 disableResizeMonitor(); 1575 } 1576 }); 1577 if (query.matches) { 1578 enableResizeMonitor(); 1579 } 1580 } 1581 }; 1582}
1583customElements.define("iabbb-cookies-message", CookiesMessage); 1584class OutageDisclaimer extends HTMLElement { 1585 constructor() { 1586 super(); 1587 this.render = () => { 1588 this.addCloseButton(); 1589 }; 1590 } 1591 connectedCallback() { 1592 this.render(); 1593 } 1594 addCloseButton = () => { 1595 const closeIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" aria-hidden="true" fill="currentColor" focusable="false" height="1em" width="1em"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256l105.3-105.4z"></path></svg>`; 1596 const visuallyHiddenLabel = `<span class="visually-hidden">close outage disclaimer</span>`; 1597 const closeButton = document.createElement("button"); 1598 closeButton.className = "bds-button-unstyled"; 1599 closeButton.innerHTML = `${closeIcon}${visuallyHiddenLabel}`; 1600 closeButton.onclick = () => { 1601 this.addDisclaimerAcknowledgedCookie(); 1602 this.style.display = "none"; 1603 }; 1604 this.appendChild(closeButton); 1605 }; 1606 addDisclaimerAcknowledgedCookie = () => { 1607 setCookie("iabbb_outage_disclaimer", "true"); 1608 }; 1609} 1610customElements.define("iabbb-outage-disclaimer", OutageDisclaimer); 1611function autoDetectLocation() { 1612 const baseUrl = window.headerFooterData?.baseUrl; 1613 if (!baseUrl) { 1614 if (document.readyState !== "complete") { 1615 window.addEventListener("load", autoDetectLocation, { once: true }); 1616 } 1617 return; 1618 } 1619 const rawCookie = getCookie("iabbb_user_location"); 1620 if (rawCookie) { 1621 return; 1622 } 1623 fetch(`${baseUrl}/api/geoip/`).then((res) => { 1624 if (!res.ok) { 1625 throw new Error(`GeoIP failed (status: ${res.status})`); 1626 } 1627 return res.json(); 1628 }).then(({ location, geoIpInfo }) => { 1629 const postalCode = location?.postalCode || geoIpInfo?.postalCode; 1630 if (!postalCode || !location?.city || !location?.state) { 1631 return; 1632 } 1633 const locationId = location.id || `${location.city}_${location.state}_${location.threeLetterIsoCountryCode}`; 1634 setCookie("iabbb_user_location", locationId, defaultExpiration);
1635 setCookie("iabbb_user_postalcode", postalCode, defaultExpiration); 1636 if (location.latLng?.lat != null && location.latLng?.lng != null) { 1637 setCookie("iabbb_user_latlng", `${location.latLng.lat},${location.latLng.lng}`, defaultExpiration); 1638 } 1639 if (location.bbbInfo?.bbbId) { 1640 setCookie("iabbb_user_bbb", location.bbbInfo.bbbId, defaultExpiration); 1641 window.headerFooterData = { 1642 ...window.headerFooterData || {}, 1643 bbb: { 1644 name: location.bbbInfo.bbbName, 1645 url: location.bbbInfo.urls?.bbbUrl 1646 } 1647 }; 1648 } 1649 document.dispatchEvent( 1650 new CustomEvent("iabbb:localeselector.setlocation", { 1651 bubbles: true, 1652 detail: { 1653 id: location.id, 1654 city: location.city, 1655 state: location.state, 1656 country: location.country, 1657 postalCode, 1658 latLng: location.latLng, 1659 bbbInfo: location.bbbInfo ? { 1660 bbbId: location.bbbInfo.bbbId, 1661 bbbName: location.bbbInfo.bbbName, 1662 primaryCountry: location.bbbInfo.primaryCountry, 1663 primaryLanguage: location.bbbInfo.primaryLanguage 1664 } : null 1665 } 1666 }) 1667 ); 1668 }).catch((err) => { 1669 console.warn("[initGeoLocation] auto-detect failed:", err); 1670 }); 1671} 1672autoDetectLocation(); 1673//# sourceMappingURL=scripts.js.map
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.