1/** 2 * Accessibility Fixer â Around the Mountain Dental 3 * Fixes all DOM-level WCAG issues detected by accessScan. 4 */ 5(function () { 6 'use strict'; 7 8 var hashJumpGuardUntil = 0; 9 var hashJumpGuardScroll = null; 10 var hadKeyboardFocusEvent = false; 11 12 onPageReady(function () { 13 runWhenIdle(runAccessibilityFixes); 14 }); 15 16 function onPageReady(callback) { 17 if (document.readyState === 'complete') { 18 callback(); 19 return; 20 } 21 22 window.addEventListener('load', function onLoad() { 23 window.removeEventListener('load', onLoad); 24 callback(); 25 }); 26 } 27 28 function runWhenIdle(callback) { 29 if ('requestIdleCallback' in window) { 30 window.requestIdleCallback(callback, { timeout: 1500 }); 31 return; 32 } 33 34 window.setTimeout(callback, 250); 35 } 36 37 function runAccessibilityFixes() { 38 if (isFixEnabled('focus_visibility')) enableFocusVisibility(); 39 if (isFixEnabled('image_alt_text')) fixImageAltText(); 40 if (isFixEnabled('active_nav_aria_current')) fixCurrentPageLinks(); 41 if (isFixEnabled('skip_link')) fixSkipLinks(); 42 if (isFixEnabled('icon_only_links')) fixIconOnlyLinks(); 43 if (isFixEnabled('lightbox_links')) fixEmptyLightboxLinks(); 44 if (isFixEnabled('read_more_links')) { 45 fixReadMoreLinks(); 46 watchReadMoreLinks(); 47 } 48 if (isFixEnabled('book_online_links')) fixBookOnlineLinks(); 49 if (isFixEnabled('positive_tabindex_forms')) { 50 fixPositiveTabindexInForms(); 51 watchPositiveTabindexInForms(); 52 } 53 if (isFixEnabled('button_like_links')) { 54 fixButtonLikeLinks(); 55 watchButtonLikeLinks(); 56 } 57 if (isFixEnabled('x_anchor_button_accessibility')) { 58 fixXAnchorButtons(); 59 watchXAnchorButtons(); 60 enableRoleButtonKeyboardSupport(); 61 watchRoleButtonKeyboardSupport(); 62 } 63 if (isFixEnabled('x_anchor_toggle_remove_aria_label')) { 64 removeXAnchorToggleAriaLabels(); 65 watchXAnchorToggleAriaLabels(); 66 scheduleXAnchorToggleAriaLabelCleanup(); 67 } 68 if (isFixEnabled('empty_hash_links')) { 69 fixEmptyHashLinks(); 70 watchEmptyHashLinks(); 71 } 72 if (isFixEnabled('x_menu_navigation_landmark')) { 73 wrapXMenuNavigation(); 74 watchXMenuNavigation(); 75 } 76 if (isFixEnabled('x_acc_header_remove_role')) { 77 removeXAccHeaderRoles(); 78 watchXAccHeaderRoles(); 79 } 80 if (isFixEnabled('x_acc_item_child_div_remove_role')) { 81 removeXAccItemChildDivRoles(); 82 watchXAccItemChildDivRoles(); 83 } 84 if (isFixEnabled('x_acc_item_remove_tab_aria')) { 85 removeXAccItemTabAria(); 86 watchXAccItemTabAria(); 87 } 88 if (isFixEnabled('x_rating_graphic_remove_role')) { 89 removeXRatingGraphicRoles(); 90 watchXRatingGraphicRoles(); 91 } 92 if (isFixEnabled('cornerstone_accordion_headers')) { 93 fixCornerstoneAccordionHeaders(); 94 watchCornerstoneAccordionHeaders(); 95 } 96 if (isFixEnabled('nav_menu_button_roles')) { 97 removeNavMenuButtonRoles(); 98 watchNavMenuButtonRoles(); 99 } 100 if (isFixEnabled('modal_links')) fixModalLinks(); 101 if (isFixEnabled('slick_carousel')) fixSlickCarousel(); 102 if (isFixEnabled('mejs_video_remove_application_role')) { 103 removeMejsVideoApplicationRoles(); 104 watchMejsVideoApplicationRoles(); 105 } 106 if (isFixEnabled('hidden_modals')) fixHiddenModals(); 107 if (isFixEnabled('decorative_icons')) fixIconElements(); 108 if (isFixEnabled('frame_names')) { 109 fixFrameAccessibleNames(); 110 watchFrameAccessibleNames(); 111 } 112 if (isFixEnabled('title_only_info')) fixTitleOnlyInformation(); 113 if (isFixEnabled('aria_label_visible_text')) fixAriaLabelsOverridingVisibleText(); 114 if (isFixEnabled('article_page_tags')) { 115 fixArticlePageTags(); 116 watchArticlePageTags(); 117 } 118 if (isFixEnabled('article_regions')) fixArticleRegions(); 119 if (isFixEnabled('main_content_div_to_main')) { 120 changeMainContentDivToMain(); 121 watchMainContentDivToMain(); 122 } 123 if (isFixEnabled('main_landmark') || isFixEnabled('landmark_nesting') || isFixEnabled('contentinfo_landmark')) fixPageLandmarks(); 124 if (isFixEnabled('secondary_nav_role')) fixSecondaryNavRole(); 125 if (isFixEnabled('minor_navigation_landmarks')) { 126 fixMinorNavigationLandmarks(); 127 watchMinorNavigationLandmarks(); 128 } 129 if (isFixEnabled('links_discernible_text')) { 130 fixLinksWithoutDiscernibleText(); 131 watchLinksWithoutDiscernibleText(); 132 } 133 if (isFixEnabled('pdf_link_warning')) { 134 fixPdfLinkWarnings(); 135 watchPdfLinkWarnings(); 136 } 137 if (isFixEnabled('yes_cookies_ada_button_fix')) { 138 fixCookieYesAccessibility(); 139 watchCookieYesAccessibility(); 140 } 141 142 // Re-run carousel fix after Slick initialises (it may run after DOMContentLoaded) 143 if (isFixEnabled('slick_carousel')) waitForSlick(); 144 enableEmptyHashJumpGuard(); 145 } 146 147 function isFixEnabled(key) { 148 if (!window.atmA11ySettings || !window.atmA11ySettings.enabledFixes) return true; 149 return window.atmA11ySettings.enabledFixes[key] !== 0 && window.atmA11ySettings.enabledFixes[key] !== '0'; 150 } 151 152 function enableFocusVisibility() { 153 document.documentElement.classList.add('atm-a11y-focus-enabled'); 154 updateStickyHeaderOffset(); 155 protectFocusedElementsFromStickyHeader(); 156 157 window.addEventListener('resize', debounce(updateStickyHeaderOffset, 150)); 158 window.addEventListener('orientationchange', debounce(updateStickyHeaderOffset, 150)); 159 } 160 161 function protectFocusedElementsFromStickyHeader() { 162 if (document.documentElement.getAttribute('data-atm-a11y-focus-protection') === '1') return; 163 document.documentElement.setAttribute('data-atm-a11y-focus-protection', '1'); 164 165 document.addEventListener('keydown', function (event) { 166 if (event.key === 'Tab' || event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') { 167 hadKeyboardFocusEvent = true; 168 } 169 }, true); 170 171 document.addEventListener('mousedown', function () { 172 hadKeyboardFocusEvent = false;
173 }, true); 174 175 document.addEventListener('touchstart', function () { 176 hadKeyboardFocusEvent = false; 177 }, true); 178 179 document.addEventListener('pointerdown', function () { 180 hadKeyboardFocusEvent = false; 181 }, true); 182 183 document.addEventListener('focusin', function (event) { 184 var target = event.target; 185 if (!target || target === document.body || target === document.documentElement) return; 186 if (!hadKeyboardFocusEvent) return; 187 188 window.setTimeout(function () { 189 scrollFocusedElementBelowStickyHeader(target); 190 }, 0); 191 }); 192 193 document.addEventListener('click', function (event) { 194 var link = event.target && event.target.closest ? event.target.closest('a[href^="#"]') : null; 195 if (!link) return; 196 197 window.setTimeout(function () { 198 var id = decodeURIComponent((link.getAttribute('href') || '').replace(/^#/, '')); 199 var target = id ? document.getElementById(id) : null; 200 if (target) scrollFocusedElementBelowStickyHeader(target); 201 }, 50); 202 }); 203 } 204 205 function scrollFocusedElementBelowStickyHeader(el) { 206 var offset = getStickyHeaderOffset(); 207 if (!offset) return; 208 209 var rect = el.getBoundingClientRect(); 210 var buffer = 12; 211 212 if (rect.top < offset + buffer) { 213 window.scrollBy({ 214 top: rect.top - offset - buffer, 215 left: 0, 216 behavior: 'auto' 217 }); 218 } 219 } 220 221 function updateStickyHeaderOffset() { 222 document.documentElement.style.setProperty('--atm-header-height', getStickyHeaderOffset() + 'px'); 223 } 224 225 function getStickyHeaderOffset() { 226 var maxBottom = 0; 227 var selectors = [ 228 'header', 229 '.site-header', 230 '#site-header', 231 '#masthead', 232 '.sticky-header', 233 '.fixed-header', 234 '.et-l--header', 235 '.et-fixed-header', 236 '.elementor-location-header', 237 '.elementor-sticky--active', 238 '.x-navbar', 239 '.x-bar-fixed', 240 '.x-masthead', 241 '.cs-header' 242 ]; 243 244 document.querySelectorAll(selectors.join(',')).forEach(function (el) { 245 var style = window.getComputedStyle(el); 246 var rect = el.getBoundingClientRect(); 247 var isStickyOrFixed = style.position === 'fixed' || style.position === 'sticky'; 248 249 if (!isStickyOrFixed || style.display === 'none' || style.visibility === 'hidden') return; 250 if (rect.height <= 0 || rect.bottom <= 0 || rect.top > 80) return; 251 252 maxBottom = Math.max(maxBottom, rect.bottom); 253 }); 254 255 return Math.ceil(maxBottom); 256 } 257 258 function debounce(fn, delay) { 259 var timer = null; 260 return function () { 261 window.clearTimeout(timer); 262 timer = window.setTimeout(fn, delay); 263 }; 264 } 265 266 function fixTitleOnlyInformation() { 267 var elements = document.querySelectorAll('[title]'); 268 elements.forEach(function (el) { 269 var title = (el.getAttribute('title') || '').trim(); 270 if (title === '') return; 271 if ((el.getAttribute('aria-label') || '').trim() !== '') return; 272 if ((el.getAttribute('aria-labelledby') || '').trim() !== '') return; 273 if (!isFormOrFrameElement(el) && getVisibleText(el) !== '') return; 274 275 el.setAttribute('aria-label', title); 276 }); 277 } 278 279 function fixAriaLabelsOverridingVisibleText() { 280 var elements = document.querySelectorAll('a[aria-label], button[aria-label], [role="button"][aria-label], [role="link"][aria-label]'); 281 elements.forEach(function (el) { 282 fixAriaLabelForVisibleText(el); 283 }); 284 } 285 286 function fixAriaLabelForVisibleText(el) { 287 if (isLandmark(el)) return; 288 289 var visibleText = getVisibleText(el); 290 if (visibleText === '') return; 291 292 var label = (el.getAttribute('aria-label') || '').trim(); 293 if (label === '' || labelContainsVisibleText(label, visibleText)) return; 294 295 el.setAttribute('aria-label', visibleText); 296 } 297 298 function labelContainsVisibleText(label, visibleText) { 299 return normalizeAccessibleText(label).indexOf(normalizeAccessibleText(visibleText)) !== -1; 300 } 301 302 function normalizeAccessibleText(text) { 303 return (text || '').replace(/\s+/g, ' ').trim().toLowerCase(); 304 } 305 306 function isFormOrFrameElement(el) {
307 return /^(input|textarea|select|iframe|frame)$/i.test(el.tagName); 308 } 309 310 function fixCurrentPageLinks() { 311 var links = document.querySelectorAll('a[href], area[href], [role="link"]'); 312 links.forEach(function (link) { 313 if ((link.getAttribute('aria-current') || '').trim() !== '') return; 314 if (!pointsToCurrentPage(link)) return; 315 316 link.setAttribute('aria-current', 'page'); 317 }); 318 } 319 320 function pointsToCurrentPage(link) { 321 var href = link.getAttribute('href'); 322 if (!href || href.charAt(0) === '#') return false; 323 324 try { 325 var target = new URL(href, window.location.href); 326 var current = new URL(window.location.href); 327 328 return normalizeUrlPath(target) === normalizeUrlPath(current) && 329 target.hostname === current.hostname && 330 target.protocol === current.protocol; 331 } catch (e) { 332 return false; 333 } 334 } 335 336 function normalizeUrlPath(url) { 337 var path = url.pathname.replace(/\/+$/, ''); 338 return path === '' ? '/' : path; 339 } 340 341 function fixSkipLinks() { 342 var skipLink = document.querySelector('.atm-skip-link[href^="#"]'); 343 if (!skipLink) return; 344 345 var targetId = (skipLink.getAttribute('href') || '').replace(/^#/, ''); 346 var target = targetId ? document.getElementById(targetId) : null; 347 348 if (!target) { 349 target = findSkipLinkTarget(); 350 if (!target) return; 351 352 if (!target.id) { 353 target.id = 'main-content'; 354 } 355 356 skipLink.setAttribute('href', '#' + target.id); 357 } 358 359 if (!isFocusable(target)) { 360 target.setAttribute('tabindex', '-1'); 361 } 362 } 363 364 function findSkipLinkTarget() { 365 var selectors = [ 366 'main', 367 '[role="main"]', 368 '#main-content', 369 '#content', 370 '#primary', 371 '.site-content', 372 '.content-area', 373 '#main', 374 '.main-content', 375 '.entry-content', 376 '.x-main' 377 ]; 378 379 for (var i = 0; i < selectors.length; i++) { 380 var el = document.querySelector(selectors[i]); 381 if (el && hasMeaningfulContent(el)) { 382 return el; 383 } 384 } 385 386 return null; 387 } 388 389 function isFocusable(el) { 390 if (!el || !el.matches) return false; 391 if (el.matches('a[href], area[href], button, input, select, textarea, iframe, object, embed')) return true; 392 var tabindex = el.getAttribute('tabindex'); 393 return tabindex !== null && !isNaN(parseInt(tabindex, 10)); 394 } 395 396 function fixArticleRegions() { 397 var articles = document.querySelectorAll('article'); 398 articles.forEach(function (article) { 399 if (!shouldDemoteArticle(article)) return; 400 401 demoteArticle(article); 402 }); 403 } 404 405 function shouldDemoteArticle(article) { 406 if (article.hasAttribute('data-atm-keep-article')) return false; 407 408 var className = getClassString(article).toLowerCase(); 409 if (/\b(page|type-page|et_pb_post|et-l|x-section|cs-content|elementor|fl-builder|vc_row)\b/.test(className)) { 410 return true; 411 } 412 413 if (/\b(post|type-post|single-post|blog|news|article|hentry)\b/.test(className) && !/\bpage\b/.test(className)) { 414 return false; 415 } 416 417 return false; 418 } 419 420 function fixArticlePageTags(root) { 421 var scope = root || document; 422 var articles = Array.prototype.slice.call(scope.querySelectorAll('article[class]')).filter(isArticlePageTag); 423 424 if (isArticlePageTag(scope)) { 425 articles.unshift(scope); 426 } 427 428 articles.forEach(function (article) { 429 demoteArticle(article); 430 }); 431 } 432 433 function isArticlePageTag(el) { 434 return !!el && el.matches && el.matches('article') && el.classList && el.classList.contains('page'); 435 } 436 437 function watchArticlePageTags() { 438 if (!window.MutationObserver) return; 439 440 var observer = new MutationObserver(function (mutations) { 441 mutations.forEach(function (mutation) { 442 if (mutation.type === 'attributes') { 443 fixArticlePageTags(mutation.target); 444 return; 445 } 446 447 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 448 if (!node || node.nodeType !== 1) return; 449 fixArticlePageTags(node); 450 }); 451 }); 452 }); 453 454 observer.observe(document.body, { 455 attributes: true, 456 attributeFilter: ['class'], 457 childList: true, 458 subtree: true 459 }); 460 } 461 462 function demoteArticle(article) { 463 var replacement = document.createElement('div'); 464 copyAttributes(article, replacement, []); 465 replacement.setAttribute('data-atm-a11y-demoted-article', 'true'); 466 467 while (article.firstChild) { 468 replacement.appendChild(article.firstChild); 469 } 470 471 article.parentNode.replaceChild(replacement, article); 472 } 473 474 // ------------------------------------------------------------------------- 475 // 1. Images without alt text â default "Image" 476 // ------------------------------------------------------------------------- 477 function fixImageAltText() { 478 var imgs = document.querySelectorAll('img');
479 imgs.forEach(function (img) { 480 // Skip images already marked decorative (role="presentation" or aria-hidden) 481 if ( 482 img.getAttribute('role') === 'presentation' || 483 img.getAttribute('aria-hidden') === 'true' || 484 img.hasAttribute('data-uw-rm-ignore') 485 ) { 486 return; 487 } 488 if (!img.hasAttribute('alt') || img.getAttribute('alt') === '') { 489 img.setAttribute('alt', 'Image'); 490 } 491 }); 492 } 493 494 // ------------------------------------------------------------------------- 495 // 2. Social media / icon-only links â aria-label 496 // Handles <a><i class="fa fa-google"></i></a> pattern 497 // ------------------------------------------------------------------------- 498 var iconLabelMap = { 499 'fa-google': 'Visit us on Google', 500 'fa-facebook-f': 'Visit us on Facebook', 501 'fa-facebook': 'Visit us on Facebook', 502 'fa-instagram': 'Visit us on Instagram', 503 'fa-twitter': 'Visit us on Twitter', 504 'fa-youtube': 'Visit us on YouTube', 505 'fa-linkedin': 'Visit us on LinkedIn', 506 'fa-yelp': 'Visit us on Yelp', 507 }; 508 509 function fixIconOnlyLinks() { 510 var links = document.querySelectorAll('a'); 511 links.forEach(function (a) { 512 // Only target links whose visible text is empty 513 if (a.textContent.trim() !== '') return; 514 if (a.getAttribute('aria-label')) return; 515 516 var icon = a.querySelector('i[class]'); 517 if (!icon) return; 518 519 var classes = icon.className.split(' '); 520 for (var c = 0; c < classes.length; c++) { 521 if (iconLabelMap[classes[c]]) { 522 a.setAttribute('aria-label', iconLabelMap[classes[c]]); 523 break; 524 } 525 } 526 527 // Fallback for any unmatched icon link â make at least non-empty 528 if (!a.getAttribute('aria-label')) { 529 a.setAttribute('aria-label', 'External link'); 530 } 531 }); 532 } 533 534 // ------------------------------------------------------------------------- 535 // 3. Empty gallery lightbox links â label + role 536 // <a class="dm-lightbox" style="background-image: url(...)"></a> 537 // ------------------------------------------------------------------------- 538 function fixEmptyLightboxLinks() { 539 var lightboxLinks = document.querySelectorAll('a.dm-lightbox'); 540 lightboxLinks.forEach(function (a, index) { 541 if (!a.getAttribute('aria-label')) { 542 // Try to extract filename for a better label 543 var href = a.getAttribute('href') || ''; 544 var filename = href.split('/').pop().replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ') || ''; 545 var label = filename 546 ? 'View gallery image: ' + filename + ' (opens image)' 547 : 'View gallery image ' + (index + 1) + ' (opens image)'; 548 a.setAttribute('aria-label', label); 549 } 550 if (anchorHasRealHref(a)) { 551 removeRoleButtonFromRealLink(a); 552 return; 553 } 554 if (!a.getAttribute('role')) { 555 a.setAttribute('role', 'button'); 556 } 557 }); 558 } 559 560 // ------------------------------------------------------------------------- 561 // 4. Ambiguous "Read More" links â context-aware aria-label 562 // ------------------------------------------------------------------------- 563 function fixReadMoreLinks() { 564 var links = document.querySelectorAll('a'); 565 links.forEach(function (link, index) { 566 fixGenericLinkContext(link, index); 567 }); 568 } 569 570 function watchReadMoreLinks() { 571 if (!window.MutationObserver) return; 572 573 var observer = new MutationObserver(function (mutations) { 574 mutations.forEach(function (mutation) { 575 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 576 if (!node || node.nodeType !== 1) return; 577 578 if (isLinkElement(node)) { 579 fixGenericLinkContext(node, 0); 580 } 581 582 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link, index) { 583 fixGenericLinkContext(link, index); 584 }); 585 }); 586 }); 587 }); 588 589 observer.observe(document.body, { childList: true, subtree: true }); 590 } 591 592 function fixGenericLinkContext(link, index) { 593 var visibleText = getVisibleText(link); 594 var currentLabel = (link.getAttribute('aria-label') || '').trim(); 595 596 if (!isGenericLinkText(visibleText) && !isGenericLinkText(currentLabel)) return; 597 598 var context = getLinkContextText(link); 599 if (context === '') return; 600 601 link.setAttribute('aria-label', getContextualLinkAction(link, index) + ' ' + context); 602 } 603 604 function isGenericLinkText(text) { 605 var normalized = normalizeAccessibleText(text).replace(/^[\s.:\-|»âº>â¦]+|[\s.:\-|»âº>â¦]+$/g, ''); 606 return /^(read more|learn more|more|view more|find out more|discover more|see more|show more|click here|here|details|view details|more details|continue reading|keep reading)$/i.test(normalized); 607 } 608 609 function getLinkContextText(link) { 610 var title = (link.getAttribute('title') || '').trim(); 611 if (title !== '' && !isGenericLinkText(title)) return normalizeContextText(title); 612 613 var imageLabel = getLabelFromLinkedImage(link); 614 if (imageLabel !== '' && !isGenericLinkText(imageLabel)) return normalizeContextText(imageLabel); 615 616 var svgLabel = getLabelFromSvg(link); 617 if (svgLabel !== '' && !isGenericLinkText(svgLabel)) return normalizeContextText(svgLabel); 618 619 var heading = findPrecedingHeading(link); 620 if (heading) return normalizeContextText(heading); 621 622 var hrefLabel = getContextFromHref(link.getAttribute('href') || ''); 623 if (hrefLabel !== '') return normalizeContextText(hrefLabel); 624 625 var classLabel = getContextFromClassName(link); 626 if (classLabel !== '') return normalizeContextText(classLabel); 627 628 return ''; 629 } 630 631 function getContextualLinkAction(link, index) { 632 var visibleText = normalizeAccessibleText(getVisibleText(link)); 633 var actions = [ 634 'Read more about', 635 'Learn more about', 636 'View details about', 637 'Explore', 638 'Discover', 639 'Find out more about', 640 'See more about', 641 'Open details for', 642 'Continue reading about', 643 'Get more information about', 644 'Review details for' 645 ]; 646 647 if (visibleText === 'learn more') return 'Learn more about'; 648 if (visibleText === 'read more') return 'Read more about'; 649 return actions[Math.abs(hashString((link.getAttribute('href') || '') + ' ' + index)) % actions.length]; 650 } 651 652 function hashString(text) { 653 var hash = 0; 654 for (var i = 0; i < text.length; i++) { 655 hash = ((hash << 5) - hash) + text.charCodeAt(i); 656 hash |= 0; 657 } 658 return hash; 659 } 660 661 function getContextFromHref(href) { 662 if (!href || href === '#') return ''; 663 if (href.indexOf('tel:') === 0) return 'phone number ' + href.replace('tel:', '').replace(/[^\d+]/g, ''); 664 if (href.indexOf('mailto:') === 0) return href.replace('mailto:', '').split('?')[0]; 665 666 try { 667 var url = new URL(href, window.location.href);
668 var segments = url.pathname.split('/').filter(Boolean); 669 var segment = segments.pop() || ''; 670 return segment.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 671 } catch (e) { 672 return href.split('#')[0].split('?')[0].replace(/\/$/, '').split('/').pop().replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 673 } 674 } 675 676 function getContextFromClassName(link) { 677 var classText = (link.className && typeof link.className === 'string') ? link.className : ''; 678 classText = classText.replace(/\b(button|btn|link|read|more|learn|cta|primary|secondary)\b/gi, '').replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim(); 679 return isGenericLinkText(classText) ? '' : classText; 680 } 681 682 function normalizeContextText(text) { 683 return (text || '').replace(/\s+/g, ' ').trim(); 684 } 685 686 function findPrecedingHeading(el) { 687 // Walk up the DOM tree looking for a sibling or ancestor heading 688 var node = el; 689 while (node && node !== document.body) { 690 node = node.parentElement; 691 if (!node) break; 692 var headings = node.querySelectorAll('h1, h2, h3, h4'); 693 if (headings.length) { 694 return headings[headings.length - 1].textContent.trim(); 695 } 696 } 697 return null; 698 } 699 700 // ------------------------------------------------------------------------- 701 // 5. "Book Online" anchor acting as button 702 // ------------------------------------------------------------------------- 703 function fixBookOnlineLinks() { 704 var links = document.querySelectorAll('a'); 705 links.forEach(function (a) { 706 var text = a.textContent.trim().toLowerCase(); 707 if (text !== 'book online') return; 708 709 if (anchorHasRealHref(a)) { 710 removeRoleButtonFromRealLink(a); 711 return; 712 } 713 714 var href = a.getAttribute('href'); 715 if (href === null || href === '' || href === '#') { 716 a.setAttribute('role', 'button'); 717 if (!a.getAttribute('tabindex')) { 718 a.setAttribute('tabindex', '0'); 719 } 720 // Keyboard activation support 721 a.addEventListener('keydown', function (e) { 722 if (e.key === 'Enter' || e.key === ' ') { 723 e.preventDefault(); 724 a.click(); 725 } 726 }); 727 preventHashJumpForButtonLikeLink(a); 728 } 729 }); 730 } 731 732 function fixPositiveTabindexInForms(root) { 733 var scope = root || document; 734 var elements = Array.prototype.slice.call(scope.querySelectorAll('form[tabindex], form [tabindex], input[tabindex], select[tabindex], textarea[tabindex], button[tabindex]')); 735 736 if (scope.matches && scope.matches('form[tabindex], form [tabindex], input[tabindex], select[tabindex], textarea[tabindex], button[tabindex]')) { 737 elements.unshift(scope); 738 } 739 740 elements.forEach(function (el) { 741 normalizePositiveTabindex(el); 742 }); 743 } 744 745 function watchPositiveTabindexInForms() { 746 if (!window.MutationObserver) return; 747 748 var observer = new MutationObserver(function (mutations) { 749 mutations.forEach(function (mutation) { 750 if (mutation.type === 'attributes') { 751 normalizePositiveTabindex(mutation.target); 752 return; 753 } 754 755 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 756 if (!node || node.nodeType !== 1) return; 757 fixPositiveTabindexInForms(node); 758 }); 759 }); 760 }); 761 762 observer.observe(document.body, { 763 attributes: true, 764 attributeFilter: ['tabindex'], 765 childList: true, 766 subtree: true 767 }); 768 } 769 770 function normalizePositiveTabindex(el) { 771 if (!isFormTabindexElement(el)) return; 772 773 var tabindex = parseInt(el.getAttribute('tabindex'), 10); 774 if (!isNaN(tabindex) && tabindex > 0) { 775 el.setAttribute('tabindex', '0'); 776 } 777 } 778 779 function isFormTabindexElement(el) { 780 if (!el || !el.matches || !el.hasAttribute('tabindex')) return false; 781 return el.matches('form, input, select, textarea, button') || !!el.closest('form'); 782 } 783 784 // ------------------------------------------------------------------------- 785 // 6. Button-like anchors used by builders, sliders, popups, and Cornerstone 786 // ------------------------------------------------------------------------- 787 function fixButtonLikeLinks() { 788 var links = document.querySelectorAll('a'); 789 links.forEach(function (link) { 790 fixButtonLikeLink(link); 791 }); 792 } 793 794 function watchButtonLikeLinks() { 795 if (!window.MutationObserver) return; 796 797 var observer = new MutationObserver(function (mutations) { 798 mutations.forEach(function (mutation) {
799 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 800 if (!node || node.nodeType !== 1) return; 801 802 if (node.matches && node.matches('a')) { 803 fixButtonLikeLink(node); 804 } 805 806 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link) { 807 fixButtonLikeLink(link); 808 }); 809 }); 810 }); 811 }); 812 813 observer.observe(document.body, { childList: true, subtree: true }); 814 } 815 816 function fixButtonLikeLink(link) { 817 if (isFixEnabled('nav_menu_button_roles') && isNavMenuLink(link)) { 818 removeRoleButtonFromNavMenuLink(link); 819 return; 820 } 821 822 if (anchorHasRealHref(link)) { 823 removeRoleButtonFromRealLink(link); 824 return; 825 } 826 827 if (!isButtonLikeLink(link)) return; 828 829 link.setAttribute('role', 'button'); 830 if (!link.getAttribute('tabindex')) { 831 link.setAttribute('tabindex', '0'); 832 } 833 834 var currentLabel = (link.getAttribute('aria-label') || '').trim(); 835 if (currentLabel === '' || currentLabel.toLowerCase() === 'read more') { 836 link.setAttribute('aria-label', getButtonLikeLinkLabel(link)); 837 } 838 839 if (isFixEnabled('aria_label_visible_text')) { 840 fixAriaLabelForVisibleText(link); 841 } 842 843 if (link.getAttribute('data-atm-a11y-button-keydown') === '1') return; 844 link.setAttribute('data-atm-a11y-button-keydown', '1'); 845 846 preventHashJumpForButtonLikeLink(link); 847 848 link.addEventListener('keydown', function (e) { 849 if (e.key !== 'Enter' && e.key !== ' ') return; 850 851 e.preventDefault(); 852 link.click(); 853 }); 854 } 855 856 function fixXAnchorButtons(root) { 857 var scope = root || document; 858 var selector = [ 859 '.x-anchor-button', 860 '[class*="x-anchor-button"]' 861 ].join(','); 862 var buttons = Array.prototype.slice.call(scope.querySelectorAll(selector)); 863 864 if (scope.matches && scope.matches(selector)) { 865 buttons.unshift(scope); 866 } 867 868 buttons.forEach(function (button) { 869 if (anchorHasRealHref(button)) { 870 removeRoleButtonFromRealLink(button); 871 return; 872 } 873 874 button.setAttribute('role', 'button'); 875 876 if (!button.getAttribute('tabindex')) { 877 button.setAttribute('tabindex', '0'); 878 } 879 880 if ((button.getAttribute('aria-label') || '').trim() === '' && getVisibleText(button) === '') { 881 button.setAttribute('aria-label', getButtonLikeLinkLabel(button)); 882 } 883 884 preventHashJumpForButtonLikeLink(button); 885 addRoleButtonKeyboardSupport(button); 886 }); 887 } 888 889 function watchXAnchorButtons() { 890 if (!window.MutationObserver) return; 891 892 var observer = new MutationObserver(function (mutations) { 893 mutations.forEach(function (mutation) { 894 if (mutation.type === 'attributes') { 895 fixXAnchorButtons(mutation.target); 896 return; 897 } 898 899 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 900 if (!node || node.nodeType !== 1) return; 901 fixXAnchorButtons(node); 902 }); 903 }); 904 }); 905 906 observer.observe(document.body, { 907 attributes: true, 908 attributeFilter: ['class'], 909 childList: true, 910 subtree: true 911 }); 912 } 913 914 function enableRoleButtonKeyboardSupport(root) { 915 var scope = root || document; 916 var buttons = Array.prototype.slice.call(scope.querySelectorAll('[role="button"]')); 917 918 if (scope.matches && scope.matches('[role="button"]')) { 919 buttons.unshift(scope); 920 } 921 922 buttons.forEach(addRoleButtonKeyboardSupport); 923 } 924 925 function watchRoleButtonKeyboardSupport() { 926 if (!window.MutationObserver) return; 927 928 var observer = new MutationObserver(function (mutations) { 929 mutations.forEach(function (mutation) { 930 if (mutation.type === 'attributes') { 931 enableRoleButtonKeyboardSupport(mutation.target); 932 return; 933 } 934 935 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 936 if (!node || node.nodeType !== 1) return; 937 enableRoleButtonKeyboardSupport(node); 938 }); 939 }); 940 }); 941 942 observer.observe(document.body, { 943 attributes: true, 944 attributeFilter: ['role'], 945 childList: true, 946 subtree: true 947 }); 948 } 949 950 function addRoleButtonKeyboardSupport(button) { 951 if (button.getAttribute('data-atm-a11y-role-button-keydown') === '1') return; 952 if (button.getAttribute('data-atm-a11y-button-keydown') === '1') { 953 button.setAttribute('data-atm-a11y-role-button-keydown', '1'); 954 return; 955 } 956 957 button.setAttribute('data-atm-a11y-role-button-keydown', '1'); 958 959 button.addEventListener('keydown', function (e) { 960 if (e.key !== 'Enter' && e.key !== ' ') return; 961 962 e.preventDefault(); 963 button.click(); 964 }); 965 } 966 967 function removeXAnchorToggleAriaLabels(root) { 968 var scope = root || document; 969 var selector = [ 970 '.x-anchor-toggle[aria-label]', 971 '.x-anchor-toggle [aria-label]', 972 '[class*="x-anchor-toggle"][aria-label]', 973 '[class*="x-anchor-toggle"] [aria-label]' 974 ].join(','); 975 var toggles = Array.prototype.slice.call(scope.querySelectorAll(selector)); 976 977 if (scope.matches && scope.matches(selector)) { 978 toggles.unshift(scope); 979 } 980
981 toggles.forEach(function (toggle) { 982 toggle.removeAttribute('aria-label'); 983 }); 984 } 985 986 function scheduleXAnchorToggleAriaLabelCleanup() { 987 [100, 500, 1500, 3000].forEach(function (delay) { 988 window.setTimeout(removeXAnchorToggleAriaLabels, delay); 989 }); 990 } 991 992 function watchXAnchorToggleAriaLabels() { 993 if (!window.MutationObserver) return; 994 995 var observer = new MutationObserver(function (mutations) { 996 mutations.forEach(function (mutation) { 997 if (mutation.type === 'attributes') { 998 removeXAnchorToggleAriaLabels(mutation.target); 999 return; 1000 } 1001 1002 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1003 if (!node || node.nodeType !== 1) return; 1004 removeXAnchorToggleAriaLabels(node); 1005 }); 1006 }); 1007 }); 1008 1009 observer.observe(document.body, { 1010 attributes: true, 1011 attributeFilter: ['aria-label', 'class'], 1012 childList: true, 1013 subtree: true 1014 }); 1015 } 1016 1017 function fixEmptyHashLinks(root) { 1018 var scope = root || document; 1019 var links = Array.prototype.slice.call(scope.querySelectorAll('a[href="#"]')); 1020 1021 if (scope.matches && scope.matches('a[href="#"]')) { 1022 links.unshift(scope); 1023 } 1024 1025 links.forEach(function (link) { 1026 link.setAttribute('href', 'javascript:void(0)'); 1027 }); 1028 } 1029 1030 function watchEmptyHashLinks() { 1031 if (!window.MutationObserver) return; 1032 1033 var observer = new MutationObserver(function (mutations) { 1034 mutations.forEach(function (mutation) { 1035 if (mutation.type === 'attributes') { 1036 fixEmptyHashLinks(mutation.target); 1037 return; 1038 } 1039 1040 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1041 if (!node || node.nodeType !== 1) return; 1042 fixEmptyHashLinks(node); 1043 }); 1044 }); 1045 }); 1046 1047 observer.observe(document.body, { 1048 childList: true, 1049 subtree: true, 1050 attributes: true, 1051 attributeFilter: ['href'] 1052 }); 1053 } 1054 1055 function wrapXMenuNavigation(root) { 1056 var scope = root || document; 1057 var menus = Array.prototype.slice.call(scope.querySelectorAll('ul.x-menu')); 1058 1059 if (scope.matches && scope.matches('ul.x-menu')) { 1060 menus.unshift(scope); 1061 } 1062 1063 menus.forEach(function (menu) { 1064 if (menu.closest('nav')) return; 1065 if (!menu.parentNode) return; 1066 1067 var nav = document.createElement('nav'); 1068 nav.setAttribute('aria-label', 'Main Navigation'); 1069 menu.parentNode.insertBefore(nav, menu); 1070 nav.appendChild(menu); 1071 }); 1072 } 1073 1074 function watchXMenuNavigation() { 1075 if (!window.MutationObserver) return; 1076 1077 var observer = new MutationObserver(function (mutations) { 1078 mutations.forEach(function (mutation) { 1079 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1080 if (!node || node.nodeType !== 1) return; 1081 wrapXMenuNavigation(node); 1082 }); 1083 }); 1084 }); 1085 1086 observer.observe(document.body, { childList: true, subtree: true }); 1087 } 1088 1089 function fixCornerstoneAccordionHeaders(root) { 1090 var scope = root || document; 1091 var headers = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-header')); 1092 1093 if (scope.matches && scope.matches('.x-acc-header')) { 1094 headers.unshift(scope); 1095 } 1096 1097 headers.forEach(function (header) { 1098 if ((header.getAttribute('role') || '').trim().toLowerCase() === 'button') { 1099 header.removeAttribute('role'); 1100 } 1101 1102 if ((header.getAttribute('type') || '').trim().toLowerCase() === 'button') { 1103 header.removeAttribute('type'); 1104 } 1105 1106 header.removeAttribute('aria-expanded'); 1107 header.removeAttribute('aria-controls'); 1108 }); 1109 } 1110 1111 function removeXAccHeaderRoles(root) { 1112 var scope = root || document; 1113 var headers = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-header[role]')); 1114 1115 if (scope.matches && scope.matches('.x-acc-header[role]')) { 1116 headers.unshift(scope); 1117 } 1118 1119 headers.forEach(function (header) { 1120 header.removeAttribute('role'); 1121 }); 1122 } 1123 1124 function watchXAccHeaderRoles() { 1125 if (!window.MutationObserver) return; 1126 1127 var observer = new MutationObserver(function (mutations) { 1128 mutations.forEach(function (mutation) { 1129 if (mutation.type === 'attributes') { 1130 removeXAccHeaderRoles(mutation.target); 1131 return; 1132 } 1133
1134 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1135 if (!node || node.nodeType !== 1) return; 1136 removeXAccHeaderRoles(node); 1137 }); 1138 }); 1139 }); 1140 1141 observer.observe(document.body, { 1142 attributes: true, 1143 attributeFilter: ['role', 'class'], 1144 childList: true, 1145 subtree: true 1146 }); 1147 } 1148 1149 function removeXAccItemChildDivRoles(root) { 1150 var scope = root || document; 1151 var childDivs = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-item > div[role]')); 1152 1153 if (scope.matches && scope.matches('.x-acc-item > div[role]')) { 1154 childDivs.unshift(scope); 1155 } 1156 1157 childDivs.forEach(function (childDiv) { 1158 childDiv.removeAttribute('role'); 1159 }); 1160 } 1161 1162 function watchXAccItemChildDivRoles() { 1163 if (!window.MutationObserver) return; 1164 1165 var observer = new MutationObserver(function (mutations) { 1166 mutations.forEach(function (mutation) { 1167 if (mutation.type === 'attributes') { 1168 removeXAccItemChildDivRoles(mutation.target); 1169 if (mutation.target.parentElement) { 1170 removeXAccItemChildDivRoles(mutation.target.parentElement); 1171 } 1172 return; 1173 } 1174 1175 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1176 if (!node || node.nodeType !== 1) return; 1177 removeXAccItemChildDivRoles(node); 1178 if (node.parentElement) { 1179 removeXAccItemChildDivRoles(node.parentElement); 1180 } 1181 }); 1182 }); 1183 }); 1184 1185 observer.observe(document.body, { 1186 attributes: true, 1187 attributeFilter: ['role', 'class'], 1188 childList: true, 1189 subtree: true 1190 }); 1191 } 1192 1193 function removeXRatingGraphicRoles(root) { 1194 var scope = root || document; 1195 var graphics = Array.prototype.slice.call(scope.querySelectorAll('.x-rating-graphic[role]')); 1196 1197 if (scope.matches && scope.matches('.x-rating-graphic[role]')) { 1198 graphics.unshift(scope); 1199 } 1200 1201 graphics.forEach(function (graphic) { 1202 graphic.removeAttribute('role'); 1203 }); 1204 } 1205 1206 function watchXRatingGraphicRoles() { 1207 if (!window.MutationObserver) return; 1208 1209 var observer = new MutationObserver(function (mutations) { 1210 mutations.forEach(function (mutation) { 1211 if (mutation.type === 'attributes') { 1212 removeXRatingGraphicRoles(mutation.target); 1213 return; 1214 } 1215 1216 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1217 if (!node || node.nodeType !== 1) return; 1218 removeXRatingGraphicRoles(node); 1219 }); 1220 }); 1221 }); 1222 1223 observer.observe(document.body, { 1224 attributes: true, 1225 attributeFilter: ['role', 'class'], 1226 childList: true, 1227 subtree: true 1228 }); 1229 } 1230 1231 function removeXAccItemTabAria(root) { 1232 var scope = root || document; 1233 var items = []; 1234 var tabRoles = { 1235 tablist: true, 1236 tab: true, 1237 tabpanel: true 1238 }; 1239 1240 if (scope.matches && scope.matches('.x-acc-item')) { 1241 items.push(scope); 1242 } else if (scope.closest) { 1243 var parentItem = scope.closest('.x-acc-item'); 1244 if (parentItem) items.push(parentItem); 1245 } 1246 1247 if (scope.querySelectorAll) { 1248 Array.prototype.slice.call(scope.querySelectorAll('.x-acc-item')).forEac
1248h(function (item) { 1249 if (items.indexOf(item) === -1) items.push(item); 1250 }); 1251 } 1252 1253 items.forEach(function (item) { 1254 var targets = [item].concat(Array.prototype.slice.call(item.querySelectorAll('[role], [aria-selected], [aria-controls], [aria-labelledby]'))); 1255 1256 targets.forEach(function (target) { 1257 var role = (target.getAttribute('role') || '').trim().toLowerCase(); 1258 1259 if (tabRoles[role]) { 1260 target.removeAttribute('role'); 1261 } 1262 1263 target.removeAttribute('aria-selected'); 1264 target.removeAttribute('aria-controls'); 1265 target.removeAttribute('aria-labelledby'); 1266 }); 1267 }); 1268 } 1269 1270 function watchXAccItemTabAria() { 1271 if (!window.MutationObserver) return; 1272 1273 var observer = new MutationObserver(function (mutations) { 1274 mutations.forEach(function (mutation) { 1275 if (mutation.type === 'attributes') { 1276 removeXAccItemTabAria(mutation.target); 1277 return; 1278 } 1279 1280 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1281 if (!node || node.nodeType !== 1) return; 1282 removeXAccItemTabAria(node); 1283 }); 1284 }); 1285 }); 1286 1287 observer.observe(document.body, { 1288 attributes: true, 1289 attributeFilter: ['role', 'aria-selected', 'aria-controls', 'aria-labelledby', 'class'], 1290 childList: true, 1291 subtree: true 1292 }); 1293 } 1294 1295 function watchCornerstoneAccordionHeaders() { 1296 if (!window.MutationObserver) return; 1297 1298 var observer = new MutationObserver(function (mutations) { 1299 mutations.forEach(function (mutation) { 1300 if (mutation.type === 'attributes') { 1301 fixCornerstoneAccordionHeaders(mutation.target); 1302 return; 1303 } 1304 1305 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1306 if (!node || node.nodeType !== 1) return; 1307 fixCornerstoneAccordionHeaders(node); 1308 }); 1309 }); 1310 }); 1311 1312 observer.observe(document.body, { 1313 childList: true, 1314 subtree: true, 1315 attributes: true, 1316 attributeFilter: ['role', 'type', 'aria-expanded', 'aria-controls', 'class'] 1317 }); 1318 } 1319 1320 function isButtonLikeLink(link) { 1321 var href = ((link.getAttribute('href') || '') + '').trim().toLowerCase(); 1322 1323 return href === '' || href === '#' || href.indexOf('javascript:') === 0; 1324 } 1325 1326 function anchorHasRealHref(link) { 1327 if (!link || !link.matches || !link.matches('a')) return false; 1328 1329 var href = link.getAttribute('href'); 1330 if (href === null) return false; 1331 1332 href = (href + '').trim().toLowerCase(); 1333 return href !== '' && href !== '#' && href.indexOf('javascript:') !== 0; 1334 } 1335 1336 function removeRoleButtonFromRealLink(link) { 1337 if (!anchorHasRealHref(link)) return; 1338 if ((link.getAttribute('role') || '').trim().toLowerCase() === 'button') { 1339 link.removeAttribute('role'); 1340 } 1341 } 1342 1343 function removeNavMenuButtonRoles() { 1344 var links = document.querySelectorAll('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]'); 1345 links.forEach(function (link) { 1346 removeRoleButtonFromNavMenuLink(link); 1347 }); 1348 } 1349 1350 function watchNavMenuButtonRoles() { 1351 if (!window.MutationObserver) return; 1352 1353 var observer = new MutationObserver(function (mutations) { 1354 mutations.forEach(function (mutation) { 1355 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1356 if (!node || node.nodeType !== 1) return; 1357 1358 if (node.matches && node.matches('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]')) { 1359 removeRoleButtonFromNavMenuLink(node); 1360 } 1361 1362 Array.prototype.slice.call(node.querySelectorAll('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]')).forEach(function (link) { 1363 removeRoleButtonFromNavMenuLink(link); 1364 }); 1365 }); 1366 }); 1367 }); 1368 1369 observer.observe(document.body, { childList: true, subtree: true }); 1370 } 1371 1372 function isNavMenuLink(link) { 1373 return !!(link.closest && link.closest('.site-menu, .nav-menu, .x-menu')); 1374 } 1375 1376 function removeRoleButtonFromNavMenuLink(link) { 1377 if (!isNavMenuLink(link)) return; 1378 if ((link.getAttribute('role') || '').trim().toLowerCase() === 'button') { 1379 link.removeAttribute('role'); 1380 } 1381 } 1382 1383 function preventHashJumpForButtonLikeLink(link) { 1384 if (link.getAttribute('data-atm-a11y-prevent-hash-jump') === '1') return; 1385 1386 var href = ((link.getAttribute('href') || '') + '').trim().toLowerCase(); 1387 if (href !== '#') return; 1388 1389 link.setAttribute('data-atm-a11y-prevent-hash-jump', '1'); 1390 link.addEventListener('click', function () { 1391 var scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0; 1392 var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0; 1393 1394 armEmptyHashJumpGuard(scrollX, scrollY); 1395 restoreHashLinkScroll(scrollX, scrollY); 1396 }); 1397 } 1398 1399 function enableEmptyHashJumpGuard() { 1400 if (document.documentElement.getAttribute('data-atm-a11y-empty-hash-guard') === '1') return; 1401 document.documentElement.setAttribute('data-atm-a11y-empty-hash-guard', '1'); 1402 1403 document.addEventListener('click', function () { 1404 if (!hashJumpGuardScroll || Date.now() > hashJumpGuardUntil) return; 1405 1406 var scrollX = hashJumpGuardScroll.x; 1407 var scrollY = hashJumpGuardScroll.y; 1408 window.setTimeout(function () { 1409 restoreUnexpectedTopJump(scrollX, scrollY); 1410 }, 0); 1411 window.setTimeout(function () { 1412 restoreUnexpectedTopJump(scrollX, scrollY); 1413 }, 80); 1414 window.setTimeout(function () { 1415 restoreUnexpectedTopJump(scrollX, scrollY); 1416 }, 300); 1417 }, true); 1418 } 1419 1420 function armEmptyHashJumpGuard(scrollX, scrollY) { 1421 hashJumpGuardScroll = { x: scrollX, y: scrollY }; 1422 hashJumpGuardUntil = Date.now() + 5000; 1423 } 1424 1425 function restoreUnexpectedTopJump(scrollX, scrollY) { 1426 var currentY = window.pageYOffset || document.documentElement.scrollTop || 0; 1427 if (scrollY < 80 || currentY > 5) return; 1428 if (window.location.hash !== '' && window.location.hash !== '#') return; 1429 1430 window.scrollTo(scrollX, scrollY); 1431 removeEmptyHashFromUrl(); 1432 } 1433 1434 function restoreHashLinkScroll(scrollX, scrollY) { 1435 var restore = function () { 1436 if (window.location.hash === '' || window.location.hash === '#') { 1437 window.scrollTo(scrollX, scrollY); 1438 removeEmptyHashFromUrl(); 1439 } 1440 }; 1441 1442 window.setTimeout(restore, 0); 1443 window.setTimeout(restore, 50); 1444 window.setTimeout(restore, 250); 1445 1446 if (window.requestAnimationFrame) { 1447 window.requestAnimationFrame(restore); 1448 } 1449 } 1450 1451 function removeEmptyHashFromUrl() { 1452 if (!window.history || !window.history.replaceState) return; 1453 if (window.location.hash !== '' && window.location.hash !== '#') return; 1454 1455 var cleanUrl = window.location.href.replace(/#$/, ''); 1456 if (cleanUrl !== window.location.href) { 1457 window.history.replaceState(null, document.title, cleanUrl); 1458 } 1459 } 1460 1461 function getButtonLikeLinkLabel(link) { 1462 var className = getClassString(link).toLowerCase(); 1463 var text = link.textContent.replace(/\s+/g, ' ').trim(); 1464 1465 if (/(?:next|arrow-next|slick-next|swiper-button-next|splide__arrow--next|owl-next)/i.test(className)) { 1466 return 'Next Slide'; 1467 } 1468 if (/(?:prev|previous|arrow-prev|slick-prev|swiper-button-prev|splide__arrow--prev|owl-prev)/i.test(className)) { 1469 return 'Previous Slide'; 1470 } 1471 if (/(?:close|dismiss)/i.test(className + ' ' + text)) { 1472 return 'Close'; 1473 } 1474 if (/(?:menu|nav-toggle|hamburger|elementor-menu-toggle)/i.test(className)) { 1475 return 'Toggle menu'; 1476 } 1477 if (text) { 1478 return text; 1479 } 1480 if (/(?:popup|modal|book|appointment|newpopup)/i.test(className + ' ' + text)) { 1481 return 'Book Appointment'; 1482 } 1483 1484 return 'Button'; 1485 } 1486 1487 function getClassString(el) { 1488 return (el.getAttribute('class') || '').toString(); 1489 } 1490 1491 // ------------------------------------------------------------------------- 1492 // 7. Modal trigger links with tabindex="-1" and href="#" 1493 // <a href="#" class="dm_classic_modal" tabindex="-1">read more</a> 1494 // -------------------------------------------------------------------------
1495 function fixModalLinks() { 1496 var modals = document.querySelectorAll('a.dm_classic_modal'); 1497 modals.forEach(function (a) { 1498 if (anchorHasRealHref(a)) { 1499 removeRoleButtonFromRealLink(a); 1500 return; 1501 } 1502 1503 a.setAttribute('role', 'button'); 1504 if (a.getAttribute('tabindex') === '-1') { 1505 a.setAttribute('tabindex', '0'); 1506 } 1507 a.addEventListener('keydown', function (e) { 1508 if (e.key === 'Enter' || e.key === ' ') { 1509 e.preventDefault(); 1510 a.click(); 1511 } 1512 }); 1513 preventHashJumpForButtonLikeLink(a); 1514 }); 1515 } 1516 1517 // ------------------------------------------------------------------------- 1518 // 7. Slick carousel â fix broken ARIA tab roles 1519 // The Slick slider incorrectly uses role="tablist/tab/tabpanel". 1520 // Replace with the ARIA carousel pattern (group + roledescription). 1521 // ------------------------------------------------------------------------- 1522 function fixSlickCarousel() { 1523 // Fix dot navigation (was role="tablist") 1524 var dotLists = document.querySelectorAll('ul.slick-dots[role="tablist"]'); 1525 dotLists.forEach(function (ul) { 1526 ul.removeAttribute('role'); 1527 ul.setAttribute('aria-label', 'Slide navigation'); 1528 }); 1529 1530 // Fix dot buttons (was role="tab") 1531 var dotButtons = document.querySelectorAll('ul.slick-dots button[role="tab"]'); 1532 dotButtons.forEach(function (btn) { 1533 btn.removeAttribute('role'); 1534 btn.removeAttribute('aria-selected'); 1535 }); 1536 1537 // Fix slides (was role="tabpanel") 1538 var slides = document.querySelectorAll('.slick-slide[role="tabpanel"]'); 1539 slides.forEach(function (slide, index) { 1540 slide.removeAttribute('role'); 1541 slide.setAttribute('aria-roledescription', 'slide'); 1542 if (!slide.getAttribute('aria-label')) { 1543 slide.setAttribute('aria-label', 'Slide ' + (index + 1)); 1544 } 1545 }); 1546 1547 // Wrap the slider track in a group if not already 1548 var sliders = document.querySelectorAll('.slick-slider'); 1549 sliders.forEach(function (slider) { 1550 if (!slider.getAttribute('role')) { 1551 slider.setAttribute('role', 'region'); 1552 slider.setAttribute('aria-roledescription', 'carousel'); 1553 slider.setAttribute('aria-label', 'Image slideshow'); 1554 } 1555 }); 1556 } 1557 1558 function waitForSlick() { 1559 if (typeof jQuery === 'undefined') return; 1560 jQuery(document).on('init.slick', function () { 1561 fixSlickCarousel(); 1562 }); 1563 } 1564 1565 function removeMejsVideoApplicationRoles(root) { 1566 var scope = root || document; 1567 var players = Array.prototype.slice.call(scope.querySelectorAll('.mejs-video[role="application"]')); 1568 1569 if (scope.matches && scope.matches('.mejs-video[role="application"]')) { 1570 players.unshift(scope); 1571 } 1572 1573 players.forEach(function (player) { 1574 player.removeAttribute('role'); 1575 }); 1576 } 1577 1578 function watchMejsVideoApplicationRoles() { 1579 if (!window.MutationObserver) return; 1580 1581 var observer = new MutationObserver(function (mutations) { 1582 mutations.forEach(function (mutation) { 1583 if (mutation.type === 'attributes') { 1584 removeMejsVideoApplicationRoles(mutation.target); 1585 return; 1586 } 1587 1588 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1589 if (!node || node.nodeType !== 1) return; 1590 removeMejsVideoApplicationRoles(node); 1591 }); 1592 }); 1593 }); 1594 1595 observer.observe(document.body, { 1596 attributes: true, 1597 attributeFilter: ['role', 'class'], 1598 childList: true, 1599 subtree: true 1600 }); 1601 } 1602 1603 // ------------------------------------------------------------------------- 1604 // 8. Hidden modals â add aria-hidden when visually hidden 1605 // ------------------------------------------------------------------------- 1606 function fixHiddenModals() { 1607 var hiddenModals = document.querySelectorAll('.hidde-modals'); 1608 hiddenModals.forEach(function (modal) { 1609 var style = window.getComputedStyle(modal); 1610 if (style.display === 'none' || style.visibility === 'hidden') { 1611 modal.setAttribute('aria-hidden', 'true'); 1612 } 1613 1614 // Watch for visibility changes (e.g. when modal opens via JS) 1615 var observer = new MutationObserver(function () { 1616 var s = window.getComputedStyle(modal); 1617 if (s.display === 'none' || s.visibility === 'hidden') { 1618 modal.setAttribute('aria-hidden', 'true'); 1619 } else { 1620 modal.removeAttribute('aria-hidden'); 1621 // Move focus into the modal 1622 var focusable = modal.querySelector('button, [href], input, [tabindex]:not
1622([tabindex="-1"])'); 1623 if (focusable) focusable.focus(); 1624 } 1625 }); 1626 1627 observer.observe(modal, { attributes: true, attributeFilter: ['style', 'class'] }); 1628 }); 1629 } 1630 1631 // ------------------------------------------------------------------------- 1632 // 9. Decorative icon <i> elements inside links â aria-hidden 1633 // ------------------------------------------------------------------------- 1634 function fixIconElements() { 1635 var icons = document.querySelectorAll('a i[class*="fa "], a i[class*=" fa-"]'); 1636 icons.forEach(function (icon) { 1637 if (!icon.getAttribute('aria-hidden')) { 1638 icon.setAttribute('aria-hidden', 'true'); 1639 } 1640 }); 1641 } 1642 1643 // ------------------------------------------------------------------------- 1644 // 10. Secondary navigation div â remove incorrect role="navigation" 1645 // (PHP handles this on first render; JS handles cache/CDN edge cases) 1646 // ------------------------------------------------------------------------- 1647 function fixPageLandmarks() { 1648 if (isFixEnabled('main_landmark')) { 1649 ensureLandmarkRole('header, .site-header, #masthead', 'banner'); 1650 } 1651 if (isFixEnabled('contentinfo_landmark')) { 1652 fixContentinfoLandmarks(); 1653 } 1654 1655 var main = findMainLandmark(); 1656 if (!main && isFixEnabled('main_landmark')) { 1657 main = findMainContentContainer(); 1658 if (main) { 1659 main.setAttribute('role', 'main'); 1660 } 1661 } 1662 1663 if (main) { 1664 if (isFixEnabled('main_landmark')) { 1665 main = repairMainLandmarkContent(main); 1666 } 1667 if (isFixEnabled('landmark_nesting')) { 1668 removeParentLandmarks(main); 1669 } 1670 if (isFixEnabled('main_landmark') && !main.id) { 1671 main.id = 'main-content'; 1672 } 1673 updateSkipLinkTarget(main.id); 1674 } 1675 1676 if (isFixEnabled('main_landmark')) { 1677 wrapUnlandmarkedBodyContent(); 1678 } 1679 } 1680 1681 function changeMainContentDivToMain(root) { 1682 var scope = root || document; 1683 var mainContent = null; 1684 1685 if (scope.matches && scope.matches('div#main-content')) { 1686 mainContent = scope; 1687 } else if (scope.querySelector) { 1688 mainContent = scope.querySelector('div#main-content'); 1689 } 1690 1691 if (!mainContent) return; 1692 1693 var replacement = replaceElementTag(mainContent, 'main', []); 1694 updateSkipLinkTarget(replacement.id); 1695 } 1696 1697 function watchMainContentDivToMain() { 1698 if (!window.MutationObserver) return; 1699 1700 var observer = new MutationObserver(function (mutations) { 1701 mutations.forEach(function (mutation) { 1702 if (mutation.type === 'attributes') { 1703 changeMainContentDivToMain(mutation.target); 1704 return; 1705 } 1706 1707 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1708 if (!node || node.nodeType !== 1) return; 1709 changeMainContentDivToMain(node); 1710 }); 1711 }); 1712 }); 1713 1714 observer.observe(document.body, { 1715 attributes: true, 1716 attributeFilter: ['id'], 1717 childList: true, 1718 subtree: true 1719 }); 1720 } 1721 1722 function updateSkipLinkTarget(mainId) { 1723 var skipLink = document.querySelector('.atm-skip-link'); 1724 if (skipLink && mainId) { 1725 skipLink.setAttribute('href', '#' + mainId); 1726 } 1727 } 1728 1729 function repairMainLandmarkContent(main) { 1730 if (!mainContainsRepeatedLandmarks(main)) { 1731 return main; 1732 } 1733 1734 var betterMain = findUniqueContentInsideMain(main); 1735 if (betterMain && betterMain !== main) { 1736 moveMainLandmark(main, betterMain); 1737 return betterMain; 1738 } 1739 1740 demoteRepeatedLandmarksInsideMain(main); 1741 return main; 1742 } 1743 1744 function mainContainsRepeatedLandmarks(main) { 1745 return !!main.querySelector('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1746 } 1747 1748 function findUniqueContentInsideMain(main) { 1749 var selectors = [ 1750 '.entry-content', 1751 '.page-content', 1752 '.post-content', 1753 '.site-main', 1754 '#primary', 1755 '.content-area', 1756 '.et_builder_inner_content', 1757 '.elementor', 1758 '.elementor-section-wrap', 1759 '.wp-block-post-content' 1760 ]; 1761 1762 for (var i = 0; i < selectors.length; i++) { 1763 var candidate = main.querySelector(selectors[i]); 1764 if (!candidate || candidate === main) continue; 1765 if (isInsideRepeatedLandmark(candidate)) continue; 1766 if (mainContainsRepeatedLandmarks(candidate)) continue; 1767 if (!hasMeaningfulContent(candidate)) continue; 1768 1769 return candidate; 1770 } 1771 1772 return null; 1773 } 1774 1775 function moveMainLandmark(oldMain, newMain) { 1776 var oldId = oldMain.id; 1777 oldMain.removeAttribute('role'); 1778 1779 if (oldMain.tagName.toLowerCase() === 'main') { 1780 replaceElementTag(oldMain, 'div', ['role', 'id']); 1781 } 1782 1783 if (newMain.tagName.toLowerCase() !== 'main') { 1784 newMain.setAttribute('role', 'main'); 1785 } 1786 1787 if (!newMain.id) { 1788 newMain.id = oldId || 'main-content'; 1789 } 1790 } 1791 1792 function demoteRepeatedLandmarksInsideMain(main) { 1793 var repeated = main.querySelectorAll('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1794 repeated.forEach(function (el) { 1795 if (el.closest('[data-atm-a11y-preserve-landmark]')) return; 1796 if (!isBuilderOrThemeRepeatedElement(el)) return; 1797 1798 el.removeAttribute('role'); 1799 if (/^(header|footer|nav)$/i.test(el.tagName)) { 1800 replaceElementTag(el, 'div', ['role']); 1801 } 1802 }); 1803 } 1804 1805 function isBuilderOrThemeRepeatedElement(el) { 1806 var className = getClassString(el).toLowerCase(); 1807 var id = (el.id || '').toLowerCase(); 1808 var text = (id + ' ' + className); 1809 1810 return /(?:header|footer|nav|menu|masthead|site-header|site-footer|et-l--header|et-l--footer|elementor-location-header|elementor-location-footer|x-masthead|x-navbar|cs-header)/i.test(text); 1811 } 1812 1813 function isInsideRepeatedLandmark(el) { 1814 return !!el.closest('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1815 } 1816 1817 function replaceElementTag(el, tagName, excludedAttrs) { 1818 var replacement = document.createElement(tagName); 1819 copyAttributes(el, replacement, excludedAttrs || []); 1820 1821 while (el.firstChild) { 1822 replacement.appendChild(el.firstChild); 1823 } 1824 1825 el.parentNode.replaceChild(replacement, el); 1826 return replacement; 1827 } 1828 1829 function removeParentLandmarks(main) { 1830 var parentLandmark = getParentLandmark(main); 1831 1832 while (parentLandmark) { 1833 if (!canDemoteLandmark(parentLandmark)) { 1834 break; 1835 } 1836 1837 demoteLandmark(parentLandmark); 1838 parentLandmark = getParentLandmark(main); 1839 } 1840 } 1841 1842 function getParentLandmark(el) { 1843 if (!el.parentElement) return null; 1844 return el.parentElement.closest('main, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="complementary"], [role="region"]'); 1845 } 1846 1847 function canDemoteLandmark(el) { 1848 if (!el || el === document.body) return false;
1849 if (el.matches('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"], [role="search"]')) return false; 1850 return true; 1851 } 1852 1853 function demoteLandmark(el) { 1854 if (el.tagName.toLowerCase() === 'main') { 1855 var replacement = document.createElement('div'); 1856 copyAttributes(el, replacement, ['role']); 1857 1858 while (el.firstChild) { 1859 replacement.appendChild(el.firstChild); 1860 } 1861 1862 el.parentNode.replaceChild(replacement, el); 1863 return; 1864 } 1865 1866 el.removeAttribute('role'); 1867 if (el.tagName.toLowerCase() === 'section') { 1868 el.removeAttribute('aria-label'); 1869 el.removeAttribute('aria-labelledby'); 1870 } 1871 } 1872 1873 function copyAttributes(from, to, excludedAttrs) { 1874 Array.prototype.slice.call(from.attributes).forEach(function (attr) { 1875 if (excludedAttrs.indexOf(attr.name) === -1) { 1876 to.setAttribute(attr.name, attr.value); 1877 } 1878 }); 1879 } 1880 1881 function ensureLandmarkRole(selector, role) { 1882 var el = document.querySelector(selector); 1883 if (!el || isInsideLandmark(el)) return; 1884 1885 if (!isNativeLandmark(el, role) && !el.getAttribute('role')) { 1886 el.setAttribute('role', role); 1887 } 1888 } 1889 1890 function fixContentinfoLandmarks() { 1891 var globalFooter = findGlobalFooterElement(); 1892 var contentinfos = document.querySelectorAll('footer, [role="contentinfo"]'); 1893 1894 contentinfos.forEach(function (el) { 1895 if (el === globalFooter) return; 1896 1897 if (el.getAttribute('role') === 'contentinfo') { 1898 el.removeAttribute('role'); 1899 } 1900 1901 if (el.tagName.toLowerCase() === 'footer') { 1902 var replacement = replaceElementTag(el, 'div', ['role']); 1903 replacement.setAttribute('data-atm-a11y-demoted-footer', 'true'); 1904 } 1905 }); 1906 1907 if (!globalFooter) return; 1908 if (globalFooter.tagName.toLowerCase() !== 'footer' && !globalFooter.getAttribute('role')) { 1909 globalFooter.setAttribute('role', 'contentinfo'); 1910 } 1911 } 1912 1913 function findGlobalFooterElement() { 1914 var selectors = [ 1915 '#site-footer', 1916 '#colophon', 1917 '.site-footer', 1918 '.footer-main', 1919 '.footer-wrapper', 1920 '.elementor-location-footer', 1921 '.et-l--footer', 1922 '.x-colophon', 1923 '.x-site-footer', 1924 '.cs-footer', 1925 'body > footer' 1926 ]; 1927 1928 for (var i = 0; i < selectors.length; i++) { 1929 var footer = document.querySelector(selectors[i]); 1930 if (footer && isGlobalFooterCandidate(footer)) { 1931 return footer; 1932 } 1933 } 1934 1935 return null; 1936 } 1937 1938 function isGlobalFooterCandidate(el) { 1939 if (el.closest('main, article, aside, section, [role="main"], [role="article"], [role="complementary"], [role="region"]')) { 1940 return false; 1941 } 1942 1943 var classAndId = ((el.id || '') + ' ' + getClassString(el)).toLowerCase(); 1944 if (/(?:site-footer|colophon|global-footer|footer-main|footer-wrapper|elementor-location-footer|et-l--footer|x-colophon|x-site-footer|cs-footer)/i.test(classAndId)) { 1945 return true; 1946 } 1947 1948 return el.parentElement === document.body && el.tagName.toLowerCase() === 'footer'; 1949 } 1950 1951 function findMainLandmark() { 1952 return document.querySelector('main, [role="main"]'); 1953 } 1954 1955 function findMainContentContainer() { 1956 var selectors = [ 1957 '#main-content', 1958 '#content', 1959 '.site-content', 1960 '#primary', 1961 '.content-area', 1962 '#main', 1963 '.main-content' 1964 ]; 1965 1966 for (var i = 0; i < selectors.length; i++) { 1967 var el = document.querySelector(selectors[i]); 1968 if (el && !isInsideLandmark(el)) { 1969 return el; 1970 } 1971 } 1972 1973 return null; 1974 } 1975 1976 function wrapUnlandmarkedBodyContent() { 1977 var wrapper = null; 1978 var children = Array.prototype.slice.call(document.body.children); 1979 1980 children.forEach(function (child) { 1981 if (!needsLandmarkWrapper(child)) { 1982 wrapper = null; 1983 return; 1984 } 1985 1986 if (!wrapper) { 1987 wrapper = document.createElement('div'); 1988 wrapper.setAttribute('role', 'region'); 1989 wrapper.setAttribute('aria-label', 'Page content'); 1990 wrapper.className = 'atm-a11y-landmark-region'; 1991 child.parentNode.insertBefore(wrapper, child); 1992 } 1993 1994 wrapper.appendChild(child); 1995 }); 1996 } 1997 1998 function needsLandmarkWrapper(el) { 1999 if (isIgnoredBodyChild(el)) return false;
2000 if (isSkipLinkElement(el)) return false; 2001 if (isLandmark(el) || isInsideLandmark(el)) return false; 2002 if (hasLandmarkDescendant(el)) return false; 2003 if (el.id === 'wpadminbar') return false; 2004 if (el.classList.contains('atm-a11y-landmark-region')) return false; 2005 return hasMeaningfulContent(el); 2006 } 2007 2008 function isIgnoredBodyChild(el) { 2009 return ['SCRIPT', 'STYLE', 'NOSCRIPT', 'LINK', 'META', 'TEMPLATE'].indexOf(el.tagName) !== -1; 2010 } 2011 2012 function isSkipLinkElement(el) { 2013 return el.classList.contains('atm-skip-link') || el.classList.contains('atm-skip-links'); 2014 } 2015 2016 function hasMeaningfulContent(el) { 2017 if (el.hasAttribute('hidden') || el.getAttribute('aria-hidden') === 'true') return false; 2018 if (el.textContent.trim() !== '') return true; 2019 return !!el.querySelector('img, svg, canvas, iframe, video, audio, input, button, select, textarea, a[href]'); 2020 } 2021 2022 function isInsideLandmark(el) { 2023 var selector = 'main, header, footer, nav, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'; 2024 return !!el.parentElement && !!el.parentElement.closest(selector); 2025 } 2026 2027 function hasLandmarkDescendant(el) { 2028 var selector = 'main, header, footer, nav, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'; 2029 return !!el.querySelector(selector); 2030 } 2031 2032 function isLandmark(el) { 2033 return isNativeLandmark(el) || !!el.matches('[role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'); 2034 } 2035 2036 function isNativeLandmark(el, expectedRole) { 2037 var tag = el.tagName.toLowerCase(); 2038 var roleMap = { 2039 main: 'main', 2040 header: 'banner', 2041 footer: 'contentinfo', 2042 nav: 'navigation', 2043 aside: 'complementary' 2044 }; 2045 2046 if (tag === 'section') { 2047 return !expectedRole && (!!el.getAttribute('aria-label') || !!el.getAttribute('aria-labelledby')); 2048 } 2049 2050 return expectedRole ? roleMap[tag] === expectedRole : !!roleMap[tag]; 2051 } 2052 2053 function fixFrameAccessibleNames() { 2054 var frames = document.querySelectorAll('iframe, frame'); 2055 frames.forEach(function (frame, index) { 2056 fixSingleFrameAccessibleName(frame, index); 2057 }); 2058 } 2059 2060 function frameHasPresentationRole(frame) { 2061 var role = (frame.getAttribute('role') || '').trim().toLowerCase(); 2062 return role === 'none' || role === 'presentation'; 2063 } 2064 2065 function hasFrameAccessibleNameOrPresentationRole(frame) { 2066 if (frameHasPresentationRole(frame)) return true; 2067 if ((frame.getAttribute('title') || '').trim() !== '') return true; 2068 if ((frame.getAttribute('aria-label') || '').trim() !== '') return true; 2069 return getFrameLabelledByText(frame) !== ''; 2070 } 2071 2072 function watchFrameAccessibleNames() { 2073 if (!window.MutationObserver) return; 2074 2075 var observer = new MutationObserver(function (mutations) { 2076 mutations.forEach(function (mutation) { 2077 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2078 if (!node || node.nodeType !== 1) return; 2079 2080 if (node.matches && node.matches('iframe, frame')) { 2081 fixSingleFrameAccessibleName(node); 2082 } 2083 2084 Array.prototype.slice.call(node.querySelectorAll('iframe, frame')).forEach(function (frame) { 2085 fixSingleFrameAccessibleName(frame); 2086 }); 2087 }); 2088 }); 2089 }); 2090 2091 observer.observe(document.body, { childList: true, subtree: true }); 2092 } 2093 2094 function fixSingleFrameAccessibleName(frame, index) { 2095 if (frameHasPresentationRole(frame)) return; 2096 2097 var title = (frame.getAttribute('title') || '').trim(); 2098 if (title !== '') { 2099 if ((frame.getAttribute('aria-label') || '').trim() !== title) { 2100 frame.setAttribute('aria-label', title); 2101 } 2102 return; 2103 } 2104 2105 if ((frame.getAttribute('aria-label') || '').trim() !== '') return; 2106 if (getFrameLabelledByText(frame) !== '') return; 2107 2108 title = getFrameTitle(frame, index || 0); 2109 frame.setAttribute('title', title); 2110 frame.setAttribute('aria-label', title); 2111 } 2112 2113 function getFrameLabelledByText(frame) { 2114 var labelledBy = (frame.getAttribute('aria-labelledby') || '').trim(); 2115 if (labelledBy === '') return ''; 2116 2117 return labelledBy.split(/\s+/).map(function (id) { 2118 var el = document.getElementById(id); 2119 return el ? el.textContent.trim() : ''; 2120 }
2120).filter(Boolean).join(' ').trim(); 2121 } 2122 2123 function getFrameTitle(frame, index) { 2124 var frameText = [ 2125 frame.getAttribute('src') || '', 2126 frame.getAttribute('data-src') || '', 2127 frame.getAttribute('data-lazy-src') || '', 2128 frame.getAttribute('data-original-src') || '', 2129 frame.getAttribute('allow') || '', 2130 frame.getAttribute('class') || '', 2131 frame.getAttribute('id') || '', 2132 frame.getAttribute('name') || '' 2133 ].join(' ').toLowerCase(); 2134 2135 if (frameText.indexOf('google.com/maps') !== -1 || frameText.indexOf('maps.google') !== -1) { 2136 return 'Map'; 2137 } 2138 if (frameText.indexOf('youtube.com') !== -1 || frameText.indexOf('youtu.be') !== -1) { 2139 return 'Video'; 2140 } 2141 if (frameText.indexOf('vimeo.com') !== -1) { 2142 return 'Video'; 2143 } 2144 if (frameText.indexOf('picture-in-picture') !== -1 || frameText.indexOf('fullscreen') !== -1 || frameText.indexOf('fitvid') !== -1 || frameText.indexOf('video') !== -1) { 2145 return 'Video'; 2146 } 2147 if (frameText.indexOf('recaptcha') !== -1 || frameText.indexOf('captcha') !== -1) { 2148 return 'Verification challenge'; 2149 } 2150 2151 return 'Embedded content ' + (index + 1); 2152 } 2153 2154 function fixSecondaryNavRole() { 2155 var secondaryNav = document.getElementById('secondary-navigation'); 2156 if (secondaryNav && secondaryNav.tagName.toLowerCase() !== 'nav') { 2157 secondaryNav.removeAttribute('role'); 2158 } 2159 } 2160 2161 function fixMinorNavigationLandmarks() { 2162 var navs = document.querySelectorAll('nav, [role="navigation"]'); 2163 navs.forEach(function (el) { 2164 demoteMinorNavigationLandmark(el); 2165 }); 2166 } 2167 2168 function watchMinorNavigationLandmarks() { 2169 if (!window.MutationObserver) return; 2170 2171 var observer = new MutationObserver(function (mutations) { 2172 mutations.forEach(function (mutation) { 2173 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2174 if (!node || node.nodeType !== 1) return; 2175 2176 if (node.matches && node.matches('nav, [role="navigation"]')) { 2177 demoteMinorNavigationLandmark(node); 2178 } 2179 2180 Array.prototype.slice.call(node.querySelectorAll('nav, [role="navigation"]')).forEach(function (el) { 2181 demoteMinorNavigationLandmark(el); 2182 }); 2183 }); 2184 }); 2185 }); 2186 2187 observer.observe(document.body, { childList: true, subtree: true }); 2188 } 2189 2190 function demoteMinorNavigationLandmark(el) { 2191 if (!isMinorNavigationLandmark(el)) return; 2192 2193 el.removeAttribute('role'); 2194 el.setAttribute('data-atm-a11y-demoted-navigation', 'true'); 2195 2196 if (el.tagName.toLowerCase() === 'nav') { 2197 replaceElementTag(el, 'div', ['role']); 2198 } 2199 } 2200 2201 function isMinorNavigationLandmark(el) { 2202 if (el.hasAttribute('data-atm-a11y-preserve-navigation')) return false; 2203 2204 var text = ( 2205 el.tagName + ' ' + 2206 (el.id || '') + ' ' + 2207 getClassString(el) + ' ' + 2208 (el.getAttribute('aria-label') || '') + ' ' + 2209 getLabelledByText(el) + ' ' + 2210 getVisibleText(el) 2211 ).toLowerCase(); 2212 2213 if (/(?:secondary|utility|top-bar|topbar|footer|social|share|sharing|breadcrumb|breadcrumbs|pagination|pager|post-navigation|posts-navigation|image-navigation|comment-navigation|legal|privacy|terms|copyright|related|tag|tags|categor|archive|sidebar|widget|quick-links|quicklinks)/i.test(text)) { 2214 return true; 2215 } 2216 2217 if (isKeyNavigationLandmark(text)) { 2218 return false; 2219 } 2220 2221 return el.tagName.toLowerCase() === 'nav' && el.querySelectorAll('a[href]').length > 0; 2222 } 2223 2224 function isKeyNavigationLandmark(text) { 2225 return /(?:primary|main|site-navigation|main-navigation|header-navigation|primary-menu|main-menu|mobile-menu|mega-menu|site-menu|menu-primary|menu-main|nav-menu|menu-item|current-menu-item|\bhome\b|\babout\b|\bservices?\b|\bcontact\b|\blocations?\b|\bpatients?\b|\bdentistry\b|\bdental\b)/i.test(text); 2226 } 2227 2228 // ------------------------------------------------------------------------- 2229 // 11. Links without discernible text 2230 // ------------------------------------------------------------------------- 2231 function fixLinksWithoutDiscernibleText() { 2232 var links = document.querySelectorAll('a'); 2233 links.forEach(function (link, index) { 2234 fixLinkWithoutDiscernibleText(link, index); 2235 }); 2236 } 2237 2238 function watchLinksWithoutDiscernibleText() { 2239 if (!window.MutationObserver) return; 2240 2241 var observer = new MutationObserver(function (mutations) { 2242 mutations.forEach(function (mutation) {
2243 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2244 if (!node || node.nodeType !== 1) return; 2245 2246 if (isLinkElement(node)) { 2247 fixLinkWithoutDiscernibleText(node, 0); 2248 } 2249 2250 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link, index) { 2251 fixLinkWithoutDiscernibleText(link, index); 2252 }); 2253 }); 2254 }); 2255 }); 2256 2257 observer.observe(document.body, { childList: true, subtree: true }); 2258 } 2259 2260 function fixLinkWithoutDiscernibleText(link, index) { 2261 if ((link.getAttribute('aria-label') || '').trim() !== '') return; 2262 if (getVisibleText(link) !== '') return; 2263 2264 var context = getLinkContextText(link); 2265 link.setAttribute('aria-label', context ? getContextualLinkAction(link, index) + ' ' + context : getFallbackLinkLabel(link, index)); 2266 2267 if (isFixEnabled('aria_label_visible_text')) { 2268 fixAriaLabelForVisibleText(link); 2269 } 2270 } 2271 2272 function getFallbackLinkLabel(link, index) { 2273 var labels = [ 2274 'Related link', 2275 'Additional information', 2276 'More information', 2277 'Details link', 2278 'Learn more link', 2279 'Read more link', 2280 'Continue link', 2281 'Open related content', 2282 'View additional details', 2283 'Explore related content', 2284 'More details' 2285 ]; 2286 2287 return labels[Math.abs(hashString((link.getAttribute('href') || '') + ' ' + index)) % labels.length]; 2288 } 2289 2290 function isLinkElement(el) { 2291 if (!el.matches) return false; 2292 return el.matches('a'); 2293 } 2294 2295 function hasDiscernibleText(link) { 2296 if ((link.getAttribute('aria-label') || '').trim() !== '') return true; 2297 if ((link.getAttribute('title') || '').trim() !== '') return true; 2298 if (getLabelledByText(link) !== '') return true; 2299 if (getLabelFromLinkedImage(link) !== '') return true; 2300 if (getLabelFromSvg(link) !== '') return true; 2301 return getVisibleText(link) !== '' && !isIconOnlyLink(link); 2302 } 2303 2304 function getLabelledByText(link) { 2305 var labelledBy = (link.getAttribute('aria-labelledby') || '').trim(); 2306 if (labelledBy === '') return ''; 2307 2308 var text = labelledBy.split(/\s+/).map(function (id) { 2309 var el = document.getElementById(id); 2310 return el ? el.textContent.trim() : ''; 2311 }).filter(Boolean).join(' '); 2312 2313 return text.trim(); 2314 } 2315 2316 function getVisibleText(link) { 2317 return link.textContent.replace(/\s+/g, ' ').trim(); 2318 } 2319 2320 function isIconOnlyLink(link) { 2321 if (link.querySelector('img[alt], svg[aria-label], svg title')) return false; 2322 if (link.querySelector('i[class], span[class*="icon"], span[class*="fa-"], span[class*="dashicons"]')) return true; 2323 2324 var text = getVisibleText(link); 2325 return text !== '' && /^[\uE000-\uF8FF\s]+$/.test(text); 2326 } 2327 2328 function getLabelFromLinkedImage(link) { 2329 var img = link.querySelector('img[alt]'); 2330 return img ? img.getAttribute('alt').trim() : ''; 2331 } 2332 2333 function getLabelFromSvg(link) { 2334 var labelledSvg = link.querySelector('svg[aria-label], svg title'); 2335 if (!labelledSvg) return ''; 2336 2337 if (labelledSvg.getAttribute && labelledSvg.getAttribute('aria-label')) { 2338 return labelledSvg.getAttribute('aria-label').trim(); 2339 } 2340 2341 return labelledSvg.textContent.trim(); 2342 } 2343 2344 function getLabelFromNearbyHeading(link) { 2345 var heading = findPrecedingHeading(link); 2346 return heading ? 'Learn more about ' + heading : ''; 2347 } 2348 2349 function getLabelFromButtonLikeClasses(link) { 2350 var classText = (link.className && typeof link.className === 'string') ? link.className : ''; 2351 classText = classText.replace(/\b(button|btn|link|icon|fa|fas|far|fab)\b/g, '').replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim(); 2352 return classText ? 'Open ' + classText : ''; 2353 } 2354 2355 function getLabelFromHref(href) { 2356 if (!href || href === '#') return ''; 2357 if (href.indexOf('tel:') === 0) return 'Call ' + href.replace('tel:', '').replace(/[^\d+]/g, ''); 2358 if (href.indexOf('mailto:') === 0) return 'Email ' + href.replace('mailto:', '').split('?')[0]; 2359 2360 var cleaned = href.split('#')[0].split('?')[0].replace(/\/$/, ''); 2361 var segment = cleaned.split('/').pop(); 2362 if (!segment) return ''; 2363 2364 return 'Open ' + segment.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 2365 } 2366 2367 // ------------------------------------------------------------------------- 2368 // 12. PDF links should warn users before opening a PDF reader 2369 // ------------------------------------------------------------------------- 2370 function fixPdfLinkWarnings() { 2371 var links = document.querySelectorAll('a[href]'); 2372 links.forEach(function (link) { 2373 fixPdfLinkWarning(link); 2374 }); 2375 } 2376 2377 function watchPdfLinkWarnings() { 2378 if (!window.MutationObserver) return; 2379 2380 var observer = new MutationObserver(function (mutations) { 2381 mutations.forEach(function (mutation) {
2382 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2383 if (!node || node.nodeType !== 1) return; 2384 2385 if (isLinkElement(node)) { 2386 fixPdfLinkWarning(node); 2387 } 2388 2389 Array.prototype.slice.call(node.querySelectorAll('a[href]')).forEach(function (link) { 2390 fixPdfLinkWarning(link); 2391 }); 2392 }); 2393 }); 2394 }); 2395 2396 observer.observe(document.body, { childList: true, subtree: true }); 2397 } 2398 2399 function fixPdfLinkWarning(link) { 2400 var href = link.getAttribute('href') || ''; 2401 if (!isPdfHref(href)) return; 2402 2403 var label = (link.getAttribute('aria-label') || '').trim() || 2404 getVisibleText(link) || 2405 (link.getAttribute('title') || '').trim() || 2406 getLabelFromHref(href) || 2407 'Open document'; 2408 2409 if (!textMentionsPdf(label)) { 2410 label += ' (opens a PDF file)'; 2411 } 2412 2413 link.setAttribute('aria-label', label); 2414 appendPdfWarningText(link); 2415 2416 var title = (link.getAttribute('title') || '').trim(); 2417 if (title === '') { 2418 link.setAttribute('title', 'Opens a PDF file'); 2419 } else if (!textMentionsPdf(title)) { 2420 link.setAttribute('title', title + ' - opens a PDF file'); 2421 } 2422 } 2423 2424 function isPdfHref(href) { 2425 try { 2426 return /\.pdf$/i.test(new URL(href, window.location.href).pathname); 2427 } catch (e) { 2428 return /\.pdf(?:$|[?#])/i.test(href); 2429 } 2430 } 2431 2432 function textMentionsPdf(text) { 2433 return /\bpdf\b/i.test(text || ''); 2434 } 2435 2436 function appendPdfWarningText(link) { 2437 if (link.querySelector('.atm-a11y-pdf-warning')) return; 2438 if (textMentionsPdf(getVisibleText(link))) return; 2439 2440 var warning = document.createElement('span'); 2441 warning.className = 'atm-a11y-sr-only atm-a11y-pdf-warning'; 2442 warning.textContent = ' (opens a PDF file)'; 2443 link.appendChild(warning); 2444 } 2445 2446 // ------------------------------------------------------------------------- 2447 // 13. CookieYes banner buttons should expose tab semantics 2448 // ------------------------------------------------------------------------- 2449 function fixCookieYesAccessibility() { 2450 var buttonWrapper = document.querySelector('.cky-notice-btn-wrapper'); 2451 2452 if (buttonWrapper && !buttonWrapper.hasAttribute('role')) { 2453 buttonWrapper.setAttribute('role', 'tablist'); 2454 buttonWrapper.setAttribute('aria-label', 'Cookie Consent Options'); 2455 } 2456 2457 var buttons = document.querySelectorAll('.cky-btn-customize, .cky-btn-reject, .cky-btn-accept'); 2458 2459 buttons.forEach(function (button, index) { 2460 button.setAttribute('role', 'tab'); 2461 button.setAttribute('aria-selected', 'false'); 2462 button.setAttribute('tabindex', '-1'); 2463 2464 if (!button.id) { 2465 button.id = 'cky-tab-' + index; 2466 } 2467 }); 2468 2469 if (buttons.length) { 2470 buttons[0].setAttribute('aria-selected', 'true'); 2471 buttons[0].setAttribute('tabindex', '0'); 2472 } 2473 2474 var revisitBtn = document.querySelector('.cky-btn-revisit'); 2475 2476 if (revisitBtn) { 2477 revisitBtn.setAttribute('role', 'tab'); 2478 revisitBtn.setAttribute('aria-selected', 'false'); 2479 } 2480 } 2481 2482 function watchCookieYesAccessibility() { 2483 if (!window.MutationObserver) return; 2484 2485 var observer = new MutationObserver(function () { 2486 fixCookieYesAccessibility(); 2487 }); 2488 2489 observer.observe(document.body, { 2490 childList: true, 2491 subtree: true 2492 }); 2493 } 2494 2495})();
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.