@openeuropa/bcl-theme-default 1.10.5 → 1.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/css/oe-bcl-ckeditor5.min.css +1 -1
  2. package/css/oe-bcl-ckeditor5.min.css.map +1 -1
  3. package/css/oe-bcl-default.css +581 -91
  4. package/css/oe-bcl-default.css.map +1 -1
  5. package/css/oe-bcl-default.min.css +1 -1
  6. package/css/oe-bcl-default.min.css.map +1 -1
  7. package/js/oe-bcl-default.bundle.js +187 -0
  8. package/js/oe-bcl-default.bundle.js.map +1 -1
  9. package/js/oe-bcl-default.bundle.min.js +1 -1
  10. package/js/oe-bcl-default.bundle.min.js.map +1 -1
  11. package/js/oe-bcl-default.esm.js +186 -1
  12. package/js/oe-bcl-default.esm.js.map +1 -1
  13. package/js/oe-bcl-default.esm.min.js +1 -1
  14. package/js/oe-bcl-default.esm.min.js.map +1 -1
  15. package/js/oe-bcl-default.umd.js +187 -0
  16. package/js/oe-bcl-default.umd.js.map +1 -1
  17. package/js/oe-bcl-default.umd.min.js +1 -1
  18. package/js/oe-bcl-default.umd.min.js.map +1 -1
  19. package/package.json +5 -5
  20. package/src/js/index.esm.js +4 -0
  21. package/src/js/index.umd.js +4 -0
  22. package/src/js/main-navigation/main-navigation.js +42 -0
  23. package/src/js/mega-menu/mega-menu.js +177 -0
  24. package/src/scss/_header.scss +212 -111
  25. package/src/scss/_mega-menu.scss +432 -0
  26. package/src/scss/base/_colors.scss +3 -0
  27. package/src/scss/oe-bcl-default.scss +1 -0
  28. package/templates/bcl-button/button.html.twig +3 -2
  29. package/templates/bcl-header/header.html.twig +37 -6
  30. package/templates/bcl-mega-menu/mega-menu-items.html.twig +35 -0
  31. package/templates/bcl-mega-menu/mega-menu-submenu.html.twig +65 -0
  32. package/templates/bcl-mega-menu/mega-menu.html.twig +115 -0
  33. package/templates/bcl-navigation/navigation.html.twig +3 -1
  34. package/templates/bcl-offcanvas/offcanvas.html.twig +9 -6
@@ -2673,6 +2673,191 @@ enableDismissTrigger(Modal);
2673
2673
 
2674
2674
  defineJQueryPlugin$1(Modal);
2675
2675
 
2676
+ class MainNavigation {
2677
+ constructor(toggler) {
2678
+ this.toggler = toggler;
2679
+ this.top = 0;
2680
+ this.target = this.getTargetFromToggler(toggler);
2681
+ if (!this.target) return;
2682
+ this.addListeners();
2683
+ }
2684
+ getTargetFromToggler(toggler) {
2685
+ const selector = toggler.getAttribute("data-bs-target");
2686
+ if (!selector) return null;
2687
+ try {
2688
+ return document.querySelector(selector);
2689
+ } catch {
2690
+ return null;
2691
+ }
2692
+ }
2693
+ addListeners() {
2694
+ EventHandler.on(this.target, "show.bs.collapse", () => {
2695
+ window.scrollTo(0, this.top);
2696
+ });
2697
+ }
2698
+ static init(selector = ".bcl-toggler", options) {
2699
+ const togglers = SelectorEngine.find(selector);
2700
+ togglers.forEach(toggler => new MainNavigation(toggler, options));
2701
+ }
2702
+ }
2703
+ document.addEventListener("DOMContentLoaded", () => {
2704
+ // Run for all .bcl-toggler buttons
2705
+ MainNavigation.init(".bcl-navbar-toggler");
2706
+ });
2707
+
2708
+ class MegaMenu {
2709
+ constructor(root) {
2710
+ this.root = root;
2711
+ this.backButton = SelectorEngine.findOne(".back-button", this.root);
2712
+ this.trigger = SelectorEngine.findOne(':scope > .dropdown-toggle[data-bs-toggle="dropdown"]', this.root);
2713
+ this.ul = SelectorEngine.findOne('.bcl-mega-menu__items.__level-1', this.root);
2714
+ this.addSubmenuTriggerListeners();
2715
+ this.addBackButtonListener();
2716
+ this.addTriggerListeners();
2717
+ this.addEscapeKeyHandler();
2718
+ }
2719
+ getPanelForTrigger(trigger) {
2720
+ const id = trigger.getAttribute('aria-controls');
2721
+ return id ? document.getElementById(id) : null;
2722
+ }
2723
+ getFocusableChildren(container) {
2724
+ if (!container) return [];
2725
+ return Array.from(container.querySelectorAll('a[href], button:not([disabled]):not(.back-button), [tabindex]:not([tabindex="-1"])')).filter(el => !el.hasAttribute('disabled') && el.tabIndex !== -1);
2726
+ }
2727
+ focusFirstItemInPanel(trigger) {
2728
+ const panel = this.getPanelForTrigger(trigger);
2729
+ if (!panel) return;
2730
+ const list = SelectorEngine.findOne('ul.bcl-mega-menu__items', panel);
2731
+ if (!list) return;
2732
+ const first = this.getFocusableChildren(list)[0];
2733
+ if (first) first.focus();
2734
+ }
2735
+ addEscapeKeyHandler() {
2736
+ // Bootstrap attaches its dropdown keydown listener on `document` in the capture phase.
2737
+ // By listening on `window` in the capture phase, our handler runs *before* Bootstrap’s.
2738
+ // This lets us intercept Esc inside mega menu submenus and stop Bootstrap from closing
2739
+ // the entire dropdown.
2740
+ window.addEventListener('keydown', e => {
2741
+ if (e.key !== 'Escape') return;
2742
+
2743
+ // Only act if Esc originated inside THIS mega menu's open submenu
2744
+ const panel = e.target.closest('.bcl-mega-menu__submenu');
2745
+ if (!panel || panel.hidden || !this.root.contains(panel)) {
2746
+ // Not our submenu: let Bootstrap handle it normally
2747
+ return;
2748
+ }
2749
+
2750
+ // Stop the event BEFORE it reaches Bootstrap's document-capture handler
2751
+ e.preventDefault();
2752
+ e.stopImmediatePropagation();
2753
+ e.stopPropagation();
2754
+
2755
+ // Close only this submenu and focus its trigger
2756
+ const triggerId = panel.getAttribute('aria-labelledby');
2757
+ const trigger = triggerId ? document.getElementById(triggerId) : null;
2758
+ if (trigger) {
2759
+ this.closeSubmenu(trigger);
2760
+ trigger.focus();
2761
+ }
2762
+ }, true);
2763
+ }
2764
+ addTriggerListeners() {
2765
+ if (!this.trigger) return;
2766
+
2767
+ // When the mega menu is opened, focus the first item in the menu.
2768
+ EventHandler.on(this.trigger, 'shown.bs.dropdown', () => {
2769
+ const panelId = this.trigger.getAttribute('aria-controls');
2770
+ const panel = panelId ? document.getElementById(panelId) : null;
2771
+ const firstFocusable = panel ? this.getFocusableChildren(panel)[0] : null;
2772
+ if (firstFocusable) firstFocusable.focus();
2773
+ });
2774
+
2775
+ // When the mega menu is closed, close all submenus.
2776
+ EventHandler.on(this.trigger, 'hide.bs.dropdown', () => {
2777
+ this.closeAllSubmenus();
2778
+ });
2779
+ }
2780
+ addSubmenuTriggerListeners() {
2781
+ // Clicking/activating a parent item button toggles the submenu.
2782
+ SelectorEngine.find(':scope li > button[aria-expanded]', this.root).forEach(trigger => {
2783
+ EventHandler.on(trigger, "click", () => {
2784
+ const expanded = trigger.getAttribute('aria-expanded') === 'true';
2785
+ if (expanded) {
2786
+ // Close this submenu.
2787
+ this.closeSubmenu(trigger);
2788
+ } else {
2789
+ this.openSubmenu(trigger);
2790
+ // The back button is only visible in mobile / narrow viewport.
2791
+ if (this.backButton && this.backButton.offsetParent !== null) {
2792
+ this.backButton.focus();
2793
+ } else {
2794
+ this.focusFirstItemInPanel(trigger);
2795
+ }
2796
+ }
2797
+ });
2798
+ });
2799
+ }
2800
+ addBackButtonListener() {
2801
+ // Clicking a back button closes the submenu or the menu itself.
2802
+ if (!this.backButton) {
2803
+ return;
2804
+ }
2805
+ EventHandler.on(this.backButton, "click", () => {
2806
+ const submenusThatWereOpen = this.closeAllSubmenus();
2807
+ if (submenusThatWereOpen.length > 0) {
2808
+ // Focus the submenu trigger, to allow quick reopen by keystroke.
2809
+ submenusThatWereOpen[0].focus();
2810
+ return;
2811
+ }
2812
+ // Close the mega menu itself.
2813
+ if (this.trigger) {
2814
+ // Close using the Bootstrap dropdown API.
2815
+ Dropdown.getOrCreateInstance(this.trigger).hide();
2816
+ // Focus the main trigger, to allow quick reopen by keystroke.
2817
+ this.trigger.focus();
2818
+ }
2819
+ });
2820
+ }
2821
+ openSubmenu(trigger) {
2822
+ // Close all submenus, then open the current submenu.
2823
+ this.closeAllSubmenus();
2824
+ trigger.setAttribute('aria-expanded', 'true');
2825
+ const panel = this.getPanelForTrigger(trigger);
2826
+ if (panel) panel.hidden = false;
2827
+ }
2828
+
2829
+ /**
2830
+ * Closes all submenus.
2831
+ *
2832
+ * This is simple while there is only one submenu level.
2833
+ *
2834
+ * @returns {HTMLElement[]}
2835
+ * Triggers for submenus that were closed.
2836
+ * Usually this is either exactly one, or none.
2837
+ */
2838
+ closeAllSubmenus() {
2839
+ if (!this.ul) {
2840
+ return;
2841
+ }
2842
+ const triggers = SelectorEngine.find(':scope > li > button[aria-expanded="true"]', this.ul);
2843
+ // Use arrow fn to keep `this` bound.
2844
+ triggers.forEach(t => this.closeSubmenu(t));
2845
+ return triggers;
2846
+ }
2847
+ closeSubmenu(trigger) {
2848
+ trigger.setAttribute('aria-expanded', 'false');
2849
+ const panel = this.getPanelForTrigger(trigger);
2850
+ if (panel) panel.hidden = true;
2851
+ }
2852
+ static init(selector = ".bcl-mega-menu") {
2853
+ const megaMenus = SelectorEngine.find(selector);
2854
+ megaMenus.forEach(menuEl => new MegaMenu(menuEl));
2855
+ }
2856
+ }
2857
+ document.addEventListener("DOMContentLoaded", () => {
2858
+ MegaMenu.init();
2859
+ });
2860
+
2676
2861
  /**
2677
2862
  * --------------------------------------------------------------------------
2678
2863
  * Bootstrap offcanvas.js
@@ -5066,5 +5251,5 @@ class AccessibleToggle {
5066
5251
  }
5067
5252
  }
5068
5253
 
5069
- export { AccessibleToggle, AccordionToggle, Alert, Button, Carousel, Collapse, Dropdown, Gallery, Modal, Offcanvas, Popover, ScrollSpy, ScrollSpy$1 as ScrollSpyV2, Tab, Toast, Tooltip };
5254
+ export { AccessibleToggle, AccordionToggle, Alert, Button, Carousel, Collapse, Dropdown, Gallery, MainNavigation, MegaMenu, Modal, Offcanvas, Popover, ScrollSpy, ScrollSpy$1 as ScrollSpyV2, Tab, Toast, Tooltip };
5070
5255
  //# sourceMappingURL=oe-bcl-default.esm.js.map