diff --git a/dxpr_theme.libraries.yml b/dxpr_theme.libraries.yml index 983bdf36..3b01a017 100644 --- a/dxpr_theme.libraries.yml +++ b/dxpr_theme.libraries.yml @@ -214,3 +214,9 @@ enhanced-dropdowns: vendor/enhanced-dropdowns/dist/css/main.min.css: { minified: true } dependencies: - core/drupal + +active-trail: + js: + js/dist/dxpr-theme-active-trail.js: {} + dependencies: + - core/drupal diff --git a/dxpr_theme.theme b/dxpr_theme.theme index 48089005..5730fb8b 100644 --- a/dxpr_theme.theme +++ b/dxpr_theme.theme @@ -276,26 +276,22 @@ function dxpr_theme_preprocess_page(&$variables) { /** * Implements hook_preprocess_menu(). * - * Workaround for Drupal core regression where Views-generated menu links don't - * receive proper active trail detection. Compares menu item URLs against the - * current page path to set active states and propagates to parent items. + * Attaches client-side active trail detection for Views-generated menu links + * that core does not mark as active (core issue #3359511). This removes the + * extra url.path cache context that the previous server-side workaround added; + * the core route.menu_active_trails cache context remains on menu blocks. * * @see https://www.drupal.org/project/dxpr_theme/issues/3553914 * @see https://www.drupal.org/project/drupal/issues/3359511 - * - * @todo Remove when core issue #3359511 is resolved. */ function dxpr_theme_preprocess_menu(&$variables) { if (empty($variables['items'])) { return; } - $current_path = \Drupal::service('path.current')->getPath(); - $is_front = \Drupal::service('path.matcher')->isFrontPage(); - dxpr_theme_menu_set_active_trail($variables['items'], $current_path, $is_front); - - // Ensure menu is cached per URL to prevent wrong active states. - $variables['#cache']['contexts'][] = 'url.path'; + // Attach client-side active trail detection for edge cases (e.g. + // Views-generated menu links) not covered by core's built-in active trail. + $variables['#attached']['library'][] = 'dxpr_theme/active-trail'; } /** @@ -310,6 +306,12 @@ function dxpr_theme_preprocess_menu(&$variables) { * * @return bool * TRUE if any item in this level or below is active. + * + * @deprecated in dxpr_theme:8.2.0 and is removed from dxpr_theme:9.0.0. + * Active trail detection is now handled client-side via the active-trail + * library. This function is retained for backward compatibility but is no + * longer called by dxpr_theme_preprocess_menu(). + * @see https://www.drupal.org/project/dxpr_theme/issues/3509999 */ function dxpr_theme_menu_set_active_trail(array &$items, $current_path, $is_front) { $has_active = FALSE; diff --git a/js/dist/dxpr-theme-active-trail.js b/js/dist/dxpr-theme-active-trail.js new file mode 100644 index 00000000..3485a659 --- /dev/null +++ b/js/dist/dxpr-theme-active-trail.js @@ -0,0 +1,46 @@ +/** + * @file + * Client-side active trail detection for menu links. + * + * Supplements core's built-in active trail with client-side detection so that + * the server-rendered menu does not need a url.path cache context. This covers + * edge cases such as Views-generated menu links that core does not mark as + * active (see https://www.drupal.org/project/drupal/issues/3359511). + */ + +(function (Drupal) { + Drupal.behaviors.dxprThemeActiveTrail = { + attach(context) { + if (context !== document) { + return; + } + const currentPath = window.location.pathname; + const currentOrigin = window.location.origin; + const menuLinks = document.querySelectorAll(".menu--main a[href]"); + menuLinks.forEach((link) => { + const rawHref = link.getAttribute("href"); + if (!rawHref || rawHref.startsWith("#")) { + return; + } + const linkUrl = new URL(link.href, currentOrigin); + if (linkUrl.origin !== currentOrigin) { + return; + } + if (linkUrl.pathname === currentPath) { + link.classList.add("is-active"); + let li = link.closest("li"); + while (li) { + li.classList.add("menu-item--active-trail"); + li.classList.add("active"); + const parentUl = li.parentElement; + if (parentUl) { + li = parentUl.closest("li"); + } else { + break; + } + } + } + }); + }, + }; +})(Drupal);