From 5a788ce86da57744ff9f22ff62784abd3a506af8 Mon Sep 17 00:00:00 2001 From: Herve Donner <hervedonner@gmail.com> Date: Wed, 26 Mar 2025 10:27:53 +0100 Subject: [PATCH 1/2] Update/fix active-link.js (see core issues #3464340 and #3038523). --- assets/js/misc/active-link.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/js/misc/active-link.js b/assets/js/misc/active-link.js index ff094246..027f7e85 100644 --- a/assets/js/misc/active-link.js +++ b/assets/js/misc/active-link.js @@ -25,11 +25,11 @@ // Start by finding all potentially active links. const { path } = drupalSettings; const queryString = JSON.stringify(path.currentQuery); - const querySelector = path.currentQuery - ? `[data-drupal-link-query='${queryString}']` + const querySelector = queryString + ? `[data-drupal-link-query="${CSS.escape(queryString)}"]` : ':not([data-drupal-link-query])'; const originalSelectors = [ - `[data-drupal-link-system-path="${path.currentPath}"]`, + `[data-drupal-link-system-path="${CSS.escape(path.currentPath)}"]`, `[data-drupal-active-trail=true]`, ]; let selectors; @@ -58,6 +58,7 @@ const il = activeLinks.length; for (let i = 0; i < il; i++) { activeLinks[i].classList.add(activeClass); + activeLinks[i].setAttribute('aria-current', 'page'); } }, detach(context, settings, trigger) { @@ -69,6 +70,7 @@ const il = activeLinks.length; for (let i = 0; i < il; i++) { activeLinks[i].classList.remove(activeClass); + activeLinks[i].removeAttribute('aria-current'); } } }, -- GitLab From 690adf9523af33ee6121cba3e44a209f030d53a4 Mon Sep 17 00:00:00 2001 From: Florent Torregrosa <florent.torregrosa@gmail.com> Date: Sat, 29 Mar 2025 20:25:15 +0100 Subject: [PATCH 2/2] Issue #3515452 by herved, grimreaper: Split active link by trail in a dedicated file. --- assets/js/misc/active-link-trail.js | 62 +++++++++++++++++++++++++++++ assets/js/misc/active-link.js | 2 - ui_suite_bootstrap.info.yml | 2 + ui_suite_bootstrap.libraries.yml | 7 ++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 assets/js/misc/active-link-trail.js diff --git a/assets/js/misc/active-link-trail.js b/assets/js/misc/active-link-trail.js new file mode 100644 index 00000000..1feb460a --- /dev/null +++ b/assets/js/misc/active-link-trail.js @@ -0,0 +1,62 @@ +/** + * @file + * Attaches behaviors for Drupal's active trail link marking. + */ + +((Drupal, drupalSettings) => { + const activeClass = 'active'; + + /** + * Append active class. + * + * The link is only active if it has data-drupal-active-trail=true. + * + * Does not discriminate based on element type, so allows you to set the + * active class on any element: a, li… + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.ui_suite_bootstrap_activeTrailLinks = { + attach(context) { + // Start by finding all potentially active links. + const { path } = drupalSettings; + const queryString = JSON.stringify(path.currentQuery); + const querySelector = queryString + ? `[data-drupal-link-query="${CSS.escape(queryString)}"]` + : ':not([data-drupal-link-query])'; + const originalSelectors = [`[data-drupal-active-trail=true]`]; + let selectors; + + // Add language filtering. + selectors = [].concat( + // Links without any hreflang attributes (most of them). + originalSelectors.map((selector) => `${selector}:not([hreflang])`), + // Links with hreflang equals to the current language. + originalSelectors.map( + (selector) => `${selector}[hreflang="${path.currentLanguage}"]`, + ), + ); + + // Add query string selector for pagers, exposed filters. + selectors = selectors.map((current) => current + querySelector); + + // Query the DOM. + const activeLinks = context.querySelectorAll(selectors.join(',')); + const il = activeLinks.length; + for (let i = 0; i < il; i++) { + activeLinks[i].classList.add(activeClass); + } + }, + detach(context, settings, trigger) { + if (trigger === 'unload') { + const activeLinks = context.querySelectorAll( + `[data-drupal-active-trail=true]`, + ); + const il = activeLinks.length; + for (let i = 0; i < il; i++) { + activeLinks[i].classList.remove(activeClass); + } + } + }, + }; +})(Drupal, drupalSettings); diff --git a/assets/js/misc/active-link.js b/assets/js/misc/active-link.js index 027f7e85..b40b7739 100644 --- a/assets/js/misc/active-link.js +++ b/assets/js/misc/active-link.js @@ -30,7 +30,6 @@ : ':not([data-drupal-link-query])'; const originalSelectors = [ `[data-drupal-link-system-path="${CSS.escape(path.currentPath)}"]`, - `[data-drupal-active-trail=true]`, ]; let selectors; @@ -65,7 +64,6 @@ if (trigger === 'unload') { const activeLinks = context.querySelectorAll( `[data-drupal-link-system-path].${activeClass}`, - `[data-drupal-active-trail=true]`, ); const il = activeLinks.length; for (let i = 0; i < il; i++) { diff --git a/ui_suite_bootstrap.info.yml b/ui_suite_bootstrap.info.yml index 1da0622b..45c9bb7f 100644 --- a/ui_suite_bootstrap.info.yml +++ b/ui_suite_bootstrap.info.yml @@ -101,6 +101,8 @@ libraries-override: css/icon.autocomplete.css: false libraries-extend: + core/drupal.active-link: + - ui_suite_bootstrap/drupal.active-link core/drupal.ajax: - ui_suite_bootstrap/drupal.ajax core/drupal.autocomplete: diff --git a/ui_suite_bootstrap.libraries.yml b/ui_suite_bootstrap.libraries.yml index a08104b2..0b6bdce9 100644 --- a/ui_suite_bootstrap.libraries.yml +++ b/ui_suite_bootstrap.libraries.yml @@ -23,6 +23,13 @@ component_placeholder: theme: assets/css/component/placeholder.css: {} +drupal.active-link: + js: + assets/js/misc/active-link-trail.js: { } + dependencies: + - core/drupal + - core/drupalSettings + drupal.ajax: js: assets/js/misc/ajax.js: {} -- GitLab