Make WordPress Core

Ticket #30575: twentyfourteen-submenu-fix.patch

File twentyfourteen-submenu-fix.patch, 4.2 KB (added by sachinrajcp123, 7 months ago)

Fixes the submenu usability issue in the Twenty Fourteen theme on Android tablets by adding touch-friendly submenu toggles.

Line 
1*** a/wp-content/themes/twentyfourteen/js/functions.js
2--- b/wp-content/themes/twentyfourteen/js/functions.js
3@@ -1,5 +1,6 @@
4 ( function( $ ) {
5  var body    = $( 'body' ),
6+  doc     = document,
7   _window = $( window ),
8   nav, button, menu, sidebar, masthead, secondary, sidebarOffsetTop, menuTop,
9   resizeTimer;
10@@ -221,6 +222,102 @@
11   } );
12  }
13 
14+ /**
15+  * Enable touch-friendly toggles for submenu items on larger screens.
16+  *
17+  * Problem:
18+  * On some Android tablets (e.g., Samsung Galaxy Note 10.1, Nexus 7 landscape),
19+  * hover-based dropdowns appear/disappear too quickly to tap a submenu link.
20+  *
21+  * Fix:
22+  * - First tap on a parent item opens its submenu (by toggling a `.focus` class).
23+  * - Second tap on the same parent follows the link as usual.
24+  * - Tapping anywhere outside the navigation closes any open submenus.
25+  *
26+  * This mirrors the approach used by newer default themes and improves usability
27+  * without affecting the mobile menu (which engages at smaller breakpoints).
28+  */
29+ function enableSubmenuTouchToggles() {
30+  // Bail if no primary navigation is present.
31+  var container = doc.getElementById( 'primary-navigation' );
32+  if ( ! container ) {
33+   return;
34+  }
35+
36+  // Only apply on devices that support touch events and when the desktop menu is active.
37+  // (The mobile toggle handles smaller screens.)
38+  var isTouchCapable = 'ontouchstart' in window || ( window.DocumentTouch && doc instanceof window.DocumentTouch );
39+  if ( ! isTouchCapable ) {
40+   return;
41+  }
42+
43+  var parentLinks = container.querySelectorAll( '.menu-item-has-children > a, .page_item_has_children > a' );
44+  if ( ! parentLinks.length ) {
45+   return;
46+  }
47+
48+  // Helper to remove .focus from all menu items.
49+  function removeAllFocus() {
50+   var focused = container.querySelectorAll( '.menu-item-has-children.focus, .page_item_has_children.focus' );
51+   for ( var i = 0; i < focused.length; i++ ) {
52+    focused[ i ].classList.remove( 'focus' );
53+    var link = focused[ i ].querySelector( 'a' );
54+    if ( link ) {
55+     link.setAttribute( 'aria-expanded', 'false' );
56+    }
57+   }
58+  }
59+
60+  // Close on outside taps.
61+  doc.addEventListener( 'touchstart', function( e ) {
62+   if ( ! container.contains( e.target ) ) {
63+    removeAllFocus();
64+   }
65+  }, { passive: true } );
66+
67+  // Add touchstart handler to parent links.
68+  for ( var i = 0; i < parentLinks.length; i++ ) {
69+   ( function( link ) {
70+    // Ensure ARIA state exists.
71+    link.setAttribute( 'aria-haspopup', 'true' );
72+    if ( ! link.hasAttribute( 'aria-expanded' ) ) {
73+     link.setAttribute( 'aria-expanded', 'false' );
74+    }
75+
76+    link.addEventListener( 'touchstart', function( e ) {
77+     var li = link.parentNode;
78+
79+     // If submenu isn't open, open it and prevent navigation on first tap.
80+     if ( ! li.classList.contains( 'focus' ) ) {
81+      e.preventDefault();
82+
83+      // Close siblings.
84+      var siblings = li.parentNode ? li.parentNode.children : [];
85+      for ( var j = 0; j < siblings.length; j++ ) {
86+       if ( siblings[ j ] !== li && siblings[ j ].classList ) {
87+        siblings[ j ].classList.remove( 'focus' );
88+        var sLink = siblings[ j ].querySelector( ':scope > a' );
89+        if ( sLink ) {
90+         sLink.setAttribute( 'aria-expanded', 'false' );
91+        }
92+       }
93+      }
94+
95+      // Open current.
96+      li.classList.add( 'focus' );
97+      link.setAttribute( 'aria-expanded', 'true' );
98+     } else {
99+      // Already open: allow the second tap to follow the link (no preventDefault).
100+      // Also close others to avoid multiple open chains.
101+      var openSiblings = li.parentNode ? li.parentNode.children : [];
102+      for ( var k = 0; k < openSiblings.length; k++ ) {
103+       if ( openSiblings[ k ] !== li && openSiblings[ k ].classList ) {
104+        openSiblings[ k ].classList.remove( 'focus' );
105+        var oLink = openSiblings[ k ].querySelector( ':scope > a' );
106+        if ( oLink ) {
107+         oLink.setAttribute( 'aria-expanded', 'false' );
108+        }
109+       }
110+      }
111+     }
112+    }, { passive: false } );
113+   } )( parentLinks[ i ] );
114+  }
115+ }
116+
117+ // Initialize touch-friendly submenu toggles after DOM is ready.
118+ $( enableSubmenuTouchToggles );
119+
120  /**
121   * Listen for a click on the "Skip to content" link.
122   * This is necessary because the link targets an element that