@@ -32,6 +32,7 @@ class AccessibilityCheckerHighlight {
3232 this . fixes = null ;
3333 this . currentButtonIndex = null ;
3434 this . urlParameter = this . get_url_parameter ( 'edac' ) ;
35+ this . landmarkParameter = this . get_url_parameter ( 'edac_landmark' ) ;
3536 this . currentIssueStatus = null ;
3637 this . tooltips = [ ] ;
3738 this . panelControlsFocusTrap = createFocusTrap ( '#' + this . panelControls . id , {
@@ -97,6 +98,8 @@ class AccessibilityCheckerHighlight {
9798 // Open panel if a URL parameter exists
9899 if ( this . urlParameter ) {
99100 this . panelOpen ( this . urlParameter ) ;
101+ } else if ( this . landmarkParameter ) {
102+ this . highlightLandmark ( this . landmarkParameter ) ;
100103 }
101104 }
102105
@@ -242,6 +245,9 @@ class AccessibilityCheckerHighlight {
242245 buttons . forEach ( ( button ) => {
243246 button . remove ( ) ;
244247 } ) ;
248+
249+ // Clean up any landmark labels
250+ this . removeLandmarkLabels ( ) ;
245251 }
246252
247253 /**
@@ -577,6 +583,9 @@ class AccessibilityCheckerHighlight {
577583 selectedElement . removeAttribute ( 'class' ) ;
578584 }
579585 } ) ;
586+
587+ // Clean up any landmark labels when highlights are removed
588+ this . removeLandmarkLabels ( ) ;
580589 } ;
581590
582591 /**
@@ -889,6 +898,189 @@ class AccessibilityCheckerHighlight {
889898
890899 div . textContent = textContent ;
891900 }
901+
902+ /**
903+ * This function highlights a landmark based on the selector.
904+ * @param {string } encodedSelector Base64-encoded CSS selector for the landmark
905+ */
906+ highlightLandmark ( encodedSelector ) {
907+ try {
908+ // Decode the base64 selector
909+ const selector = atob ( encodedSelector ) ;
910+
911+ // Find the landmark element using multiple strategies
912+ let landmarkElement = null ;
913+
914+ try {
915+ // Try the original selector first
916+ landmarkElement = document . querySelector ( selector ) ;
917+ } catch ( error ) {
918+ // Selector might be invalid, try fallbacks
919+ }
920+
921+ // If original selector failed, try some fallback strategies
922+ if ( ! landmarkElement ) {
923+ // Try common landmark selectors as fallbacks
924+ const fallbackSelectors = [
925+ // Remove complex pseudo-selectors and try simpler versions
926+ selector . replace ( / : n t h - c h i l d \( \d + \) / g, '' ) . replace ( / \s + > \s + / g, ' ' ) ,
927+ // Try just the last part of the selector
928+ selector . split ( ' > ' ) . pop ( ) ,
929+ // Try without classes
930+ selector . replace ( / \. [ ^ : \s > ] + / g, '' ) ,
931+ ] ;
932+
933+ for ( const fallback of fallbackSelectors ) {
934+ if ( fallback && fallback . trim ( ) ) {
935+ try {
936+ landmarkElement = document . querySelector ( fallback . trim ( ) ) ;
937+ if ( landmarkElement ) {
938+ break ;
939+ }
940+ } catch ( e ) {
941+ // Continue to next fallback
942+ }
943+ }
944+ }
945+ }
946+
947+ if ( landmarkElement ) {
948+ // Clean up any existing landmark labels first
949+ this . removeLandmarkLabels ( ) ;
950+
951+ // Add highlighting styles
952+ landmarkElement . classList . add ( 'edac-highlight-element-selected' ) ;
953+ landmarkElement . classList . add ( 'edac-landmark-highlight' ) ;
954+
955+ // Create and add landmark type label
956+ const landmarkType = this . getLandmarkType ( landmarkElement ) ;
957+ const landmarkLabel = document . createElement ( 'div' ) ;
958+ landmarkLabel . classList . add ( 'edac-landmark-label' ) ;
959+ landmarkLabel . textContent = `Landmark: ${ landmarkType } ` ;
960+ landmarkLabel . setAttribute ( 'aria-hidden' , 'true' ) ;
961+ landmarkLabel . style . cssText = `
962+ position: absolute;
963+ background: #072446;
964+ color: white;
965+ padding: 4px 8px;
966+ font-size: 12px;
967+ font-weight: bold;
968+ border-radius: 3px;
969+ z-index: 999999;
970+ pointer-events: none;
971+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
972+ line-height: 1;
973+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
974+ ` ;
975+
976+ // Position the label inside the top-left corner of the landmark
977+ const rect = landmarkElement . getBoundingClientRect ( ) ;
978+ landmarkLabel . style . left = ( rect . left + window . scrollX - 0 ) + 'px' ; // 15px inside from left edge
979+ landmarkLabel . style . top = ( rect . top + window . scrollY - 0 ) + 'px' ; // 15px inside from top edge
980+
981+ // Add label to the page
982+ document . body . appendChild ( landmarkLabel ) ;
983+
984+ // Store reference for cleanup
985+ landmarkElement . setAttribute ( 'data-edac-landmark-label-id' , Date . now ( ) ) ;
986+ landmarkLabel . setAttribute ( 'data-edac-landmark-for' , landmarkElement . getAttribute ( 'data-edac-landmark-label-id' ) ) ;
987+
988+ // Adjust for small elements
989+ if ( landmarkElement . offsetWidth < 20 ) {
990+ landmarkElement . classList . add ( 'edac-highlight-element-selected-min-width' ) ;
991+ }
992+
993+ if ( landmarkElement . offsetHeight < 5 ) {
994+ landmarkElement . classList . add ( 'edac-highlight-element-selected-min-height' ) ;
995+ }
996+
997+ // Scroll to the landmark
998+ landmarkElement . scrollIntoView ( { block : 'center' , behavior : 'smooth' } ) ;
999+
1000+ } else {
1001+ // Landmark element not found - silently fail
1002+ }
1003+ } catch ( error ) {
1004+ // Error highlighting landmark - silently fail
1005+ }
1006+ }
1007+
1008+ /**
1009+ * Determines the landmark type of an element
1010+ * @param {HTMLElement } element The element to check
1011+ * @return {string } The landmark type (e.g., "Header", "Navigation", "Main")
1012+ */
1013+ getLandmarkType ( element ) {
1014+ // Check explicit ARIA role first
1015+ const role = element . getAttribute ( 'role' ) ;
1016+ if ( role ) {
1017+ switch ( role . toLowerCase ( ) ) {
1018+ case 'banner' :
1019+ return 'Header' ;
1020+ case 'navigation' :
1021+ return 'Navigation' ;
1022+ case 'main' :
1023+ return 'Main' ;
1024+ case 'complementary' :
1025+ return 'Complementary' ;
1026+ case 'contentinfo' :
1027+ return 'Footer' ;
1028+ case 'search' :
1029+ return 'Search' ;
1030+ case 'form' :
1031+ return 'Form' ;
1032+ case 'region' :
1033+ return 'Region' ;
1034+ default :
1035+ return role . charAt ( 0 ) . toUpperCase ( ) + role . slice ( 1 ) ;
1036+ }
1037+ }
1038+
1039+ // Check semantic HTML elements
1040+ const tagName = element . tagName . toLowerCase ( ) ;
1041+ switch ( tagName ) {
1042+ case 'header' :
1043+ return 'Header' ;
1044+ case 'nav' :
1045+ return 'Navigation' ;
1046+ case 'main' :
1047+ return 'Main' ;
1048+ case 'aside' :
1049+ return 'Complementary' ;
1050+ case 'footer' :
1051+ return 'Footer' ;
1052+ case 'section' :
1053+ // Check if section has accessible name
1054+ const hasAccessibleName = element . getAttribute ( 'aria-label' ) ||
1055+ element . getAttribute ( 'aria-labelledby' ) ||
1056+ element . querySelector ( 'h1, h2, h3, h4, h5, h6' ) ;
1057+ return hasAccessibleName ? 'Region' : 'Section' ;
1058+ case 'form' :
1059+ // Check if form has accessible name
1060+ const formHasAccessibleName = element . getAttribute ( 'aria-label' ) ||
1061+ element . getAttribute ( 'aria-labelledby' ) ;
1062+ return formHasAccessibleName ? 'Form' : 'Form (unlabeled)' ;
1063+ default :
1064+ return 'Landmark' ;
1065+ }
1066+ }
1067+
1068+ /**
1069+ * Remove all landmark labels from the page
1070+ */
1071+ removeLandmarkLabels ( ) {
1072+ const landmarkLabels = document . querySelectorAll ( '.edac-landmark-label' ) ;
1073+ landmarkLabels . forEach ( ( label ) => {
1074+ label . remove ( ) ;
1075+ } ) ;
1076+
1077+ // Remove landmark highlight classes
1078+ const landmarkHighlights = document . querySelectorAll ( '.edac-landmark-highlight' ) ;
1079+ landmarkHighlights . forEach ( ( element ) => {
1080+ element . classList . remove ( 'edac-landmark-highlight' ) ;
1081+ element . removeAttribute ( 'data-edac-landmark-label-id' ) ;
1082+ } ) ;
1083+ }
8921084}
8931085
8941086// Some systems (Cloudflare Rocket Loader) defers scripts for performance but that can
0 commit comments