d11 theme
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

157 lines
6.0 KiB

/**
* @file
* JavaScript behaviors for the druid theme.
*
* ─────────────────────────────────────────────────────────────────────────
* What Drupal.behaviors solves
* ─────────────────────────────────────────────────────────────────────────
*
* Drupal swaps DOM fragments at runtime — form errors, AJAX-loaded views,
* contextual links, modal dialogs, and inline edits all inject new HTML
* into the page after the initial load. A plain `DOMContentLoaded` handler
* runs once and misses everything that arrives later.
*
* The behaviors system runs your `attach()` on every fragment Drupal
* inserts (including the initial document), so the same code initializes
* new content automatically. Use `once()` to make sure an element is only
* initialized one time even if `attach()` is invoked repeatedly.
*
* ─────────────────────────────────────────────────────────────────────────
* Pattern
* ─────────────────────────────────────────────────────────────────────────
*
* Drupal.behaviors.druidSomething = {
* attach(context, settings) {
* once('druid-something', '.selector', context).forEach((el) => {
* // initialize el — add listeners, hydrate state, etc.
* });
* },
* detach(context, settings, trigger) {
* // OPTIONAL — clean up listeners/observers when Drupal removes the
* // fragment. Skip if you don't allocate anything that leaks.
* },
* };
*
* - `context` is the DOM subtree being attached. On the initial page load
* it's `document`; on AJAX updates it's just the newly inserted fragment.
* Always scope queries to `context`, never to `document`, or you'll
* re-initialize the whole page.
* - The first arg to `once()` is a unique string ID for this behavior. The
* second is a CSS selector. `once()` returns an array of elements that
* haven't been initialized yet (it marks them with a data attribute so
* subsequent calls skip them).
* - `settings` is the JS object Drupal exposes via `drupalSettings` — use
* it to read PHP-side configuration (`drupalSettings.druid.someValue`).
*
* Naming: prefix behavior keys with `druid` so they don't collide with
* core/contrib behaviors. The `once()` ID should match the behavior name
* minus the prefix, lowercased and kebab-case.
*
* Library wiring: this file is registered in druid.libraries.yml under the
* `behaviors` library, with `core/drupal` and `core/once` as dependencies.
* If you split JS into multiple files, either add them under the same
* library or define new libraries and attach them via `libraries-extend`
* (for conditional loading) or the always-on list in druid.info.yml.
*
* Reference: https://www.drupal.org/docs/develop/standards/javascript
*/
((Drupal, once) => {
// Behaviors go here. Example skeleton — uncomment and adapt:
//
// Drupal.behaviors.druidExample = {
// attach(context) {
// once('druid-example', '[data-druid-example]', context).forEach((el) => {
// el.addEventListener('click', () => {
// el.classList.toggle('is-active');
// });
// });
// },
// };
Drupal.behaviors.mainMenuToggle = {
attach(context) {
once(
"dropdown-menu-toggle",
"nav .menu li.menu-item--expanded > a",
context
).forEach((link) => {
let touched = false;
const handler = function (e) {
// Prevent duplicate firing (touch + click)
if (e.type === "click" && touched) {
touched = false;
return;
}
if (e.type === "touchstart") {
touched = true;
}
const parent = this.parentElement;
const submenu = parent.querySelector("ul");
// Only toggle if submenu exists
if (submenu) {
e.preventDefault();
parent.classList.toggle("open");
}
};
// Mobile-first interaction
link.addEventListener("touchstart", handler, { passive: true });
// Desktop + accessibility fallback
link.addEventListener("click", handler);
});
},
};
})(Drupal, once);
((Drupal, once) => {
Drupal.behaviors.sidebarMenuDropdown = {
attach(context) {
once(
"sidebar-menu-dropdown",
".block-menu .menu-item--expanded",
context
).forEach((item) => {
const link = item.querySelector("a");
const submenu = item.querySelector("ul");
if (!submenu || !link) return;
// ✅ Create caret element
const caret = document.createElement("span");
caret.className = "menu-caret";
caret.setAttribute("aria-expanded", "false");
// Insert caret after link
link.insertAdjacentElement("afterend", caret);
let touched = false;
const toggleMenu = (e) => {
// Prevent touch + click double fire
if (e.type === "click" && touched) {
touched = false;
return;
}
if (e.type === "touchstart") {
touched = true;
}
e.preventDefault();
e.stopPropagation();
const isOpen = item.classList.toggle("open");
caret.setAttribute("aria-expanded", isOpen);
};
// ✅ Only caret toggles menu (link still navigates)
caret.addEventListener("touchstart", toggleMenu, { passive: false });
caret.addEventListener("click", toggleMenu);
});
},
};
})(Drupal, once);