@ecl/mega-menu 5.0.0-alpha.1 → 5.0.0-alpha.11

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.
package/mega-menu.js CHANGED
@@ -1,9 +1,7 @@
1
- /* eslint-disable class-methods-use-this */
2
-
3
1
  import { queryOne, queryAll } from '@ecl/dom-utils';
4
2
  import EventManager from '@ecl/event-manager';
5
- import isMobile from 'mobile-device-detect';
6
3
  import { createFocusTrap } from 'focus-trap';
4
+ import Bowser from 'bowser';
7
5
 
8
6
  /**
9
7
  * @param {HTMLElement} element DOM element for component instantiation and scope
@@ -361,9 +359,7 @@ export class MegaMenu {
361
359
  this.back.removeEventListener('click', this.handleClickOnBack);
362
360
  }
363
361
 
364
- if (this.attachClickListener) {
365
- document.removeEventListener('click', this.handleClickGlobal);
366
- }
362
+ document.removeEventListener('click', this.handleClickGlobal);
367
363
  }
368
364
 
369
365
  if (this.links) {
@@ -460,13 +456,17 @@ export class MegaMenu {
460
456
  * - not having hamburger menu on screen
461
457
  */
462
458
  useDesktopDisplay() {
459
+ const browser = Bowser.getParser(window.navigator.userAgent);
460
+ const isMobile = browser.getPlatformType() === 'mobile';
461
+ const isTablet = browser.getPlatformType() === 'tablet';
462
+
463
463
  // Detect mobile devices
464
- if (isMobile.isMobileOnly) {
464
+ if (isMobile) {
465
465
  return false;
466
466
  }
467
467
 
468
468
  // Force mobile display on tablet
469
- if (isMobile.isTablet) {
469
+ if (isTablet) {
470
470
  this.element.classList.add('ecl-mega-menu--forced-mobile');
471
471
  return false;
472
472
  }
@@ -492,14 +492,25 @@ export class MegaMenu {
492
492
 
493
493
  // Remove display:none from the sublists
494
494
  if (subLists && viewport === 'mobile') {
495
- const megaMenus = queryAll(
496
- '.ecl-mega-menu__item > .ecl-mega-menu__wrapper > .ecl-container > [data-ecl-mega-menu-mega]',
495
+ const megaMenus = queryAll('[data-ecl-mega-menu-mega]', this.element);
496
+ const featuredPanels = queryAll(
497
+ '[data-ecl-mega-menu-featured]',
497
498
  this.element,
498
499
  );
500
+ if (featuredPanels.length) {
501
+ megaMenus.push(...featuredPanels);
502
+ }
503
+
499
504
  megaMenus.forEach((menu) => {
500
505
  menu.style.height = '';
501
506
  });
502
507
 
508
+ if (subLists.length) {
509
+ subLists.forEach((list) => {
510
+ list.style.height = '';
511
+ });
512
+ }
513
+
503
514
  // Reset top position and height of the wrappers
504
515
  if (this.wrappers) {
505
516
  this.wrappers.forEach((wrapper) => {
@@ -578,7 +589,6 @@ export class MegaMenu {
578
589
  this.checkDropdownHeight(current);
579
590
  });
580
591
  } else {
581
- this.element.setAttribute('aria-expanded', 'false');
582
592
  this.element.removeAttribute('data-expanded');
583
593
  this.open.setAttribute('aria-expanded', 'false');
584
594
  this.enableScroll();
@@ -629,12 +639,12 @@ export class MegaMenu {
629
639
  screenWidth > this.breakpointL ? 'desktop' : 'mobile',
630
640
  );
631
641
  }
632
- if (this.prevScreenWidth > 1140 && screenWidth > 996) {
642
+ if (this.prevScreenWidth >= 1140 && screenWidth >= 996) {
633
643
  this.resetStyles('desktop', true);
634
644
  }
635
645
  }
636
646
  this.isDesktop = this.useDesktopDisplay();
637
- this.isLarge = window.innerWidth > 1140;
647
+ this.isLarge = window.innerWidth >= 1140;
638
648
  // Update previous screen width
639
649
  this.prevScreenWidth = screenWidth;
640
650
  this.element.classList.remove('ecl-mega-menu--forced-mobile');
@@ -663,6 +673,11 @@ export class MegaMenu {
663
673
  checkDropdownHeight(menuItem, hide = true) {
664
674
  const infoPanel = queryOne('.ecl-mega-menu__info', menuItem);
665
675
  const mainPanel = queryOne('.ecl-mega-menu__mega', menuItem);
676
+ const expanded = queryOne('.ecl-mega-menu__subitem--expanded', menuItem);
677
+ let secondPanel = null;
678
+ if (expanded) {
679
+ secondPanel = queryOne('.ecl-mega-menu__mega--level-2', expanded);
680
+ }
666
681
  // Hide the panels while calculating their heights
667
682
  if (mainPanel && this.isDesktop && hide) {
668
683
  mainPanel.style.opacity = 0;
@@ -670,6 +685,10 @@ export class MegaMenu {
670
685
  if (infoPanel && this.isDesktop && hide) {
671
686
  infoPanel.style.opacity = 0;
672
687
  }
688
+ // Second panel has already zero opacity in reality, this is for consistency
689
+ if (secondPanel && this.isDesktop && hide) {
690
+ secondPanel.opacity = 0;
691
+ }
673
692
  setTimeout(() => {
674
693
  const viewportHeight = window.innerHeight;
675
694
  let infoPanelHeight = 0;
@@ -677,11 +696,12 @@ export class MegaMenu {
677
696
  if (this.isDesktop) {
678
697
  const heights = [];
679
698
  let height = 0;
680
- let secondPanel = null;
681
699
  let featuredPanel = null;
700
+ let featuredPanelFirst = null;
682
701
  let itemsHeight = 0;
683
702
  let subItemsHeight = 0;
684
703
  let featuredHeight = 0;
704
+ let featuredFirstHeight = 0;
685
705
 
686
706
  if (infoPanel) {
687
707
  infoPanel.style.height = '';
@@ -693,6 +713,7 @@ export class MegaMenu {
693
713
  itemsHeight = infoPanelHeight;
694
714
  subItemsHeight = infoPanelHeight;
695
715
  featuredHeight = infoPanelHeight;
716
+ featuredFirstHeight = infoPanelHeight;
696
717
  }
697
718
 
698
719
  if (mainPanel) {
@@ -723,39 +744,52 @@ export class MegaMenu {
723
744
  heights.push(itemsHeight);
724
745
  }
725
746
  }
747
+ featuredPanelFirst = queryOne(
748
+ ':scope > .ecl-mega-menu__featured',
749
+ mainPanel,
750
+ );
751
+ if (featuredPanelFirst) {
752
+ // Get the elements inside the scrollable container and calculate their heights.
753
+ Array.from(featuredPanelFirst.firstElementChild.children).forEach(
754
+ (child) => {
755
+ const elStyle = window.getComputedStyle(child);
756
+ const marginHeight =
757
+ parseFloat(elStyle.marginTop) +
758
+ parseFloat(elStyle.marginBottom);
759
+ featuredFirstHeight += child.offsetHeight + marginHeight;
760
+ },
761
+ );
762
+ // Add 8px to the featured panel height to prevent scrollbar
763
+ featuredFirstHeight += 8;
764
+ heights.push(featuredFirstHeight);
765
+ }
726
766
  }
727
- const expanded = queryOne(
728
- '.ecl-mega-menu__subitem--expanded',
729
- menuItem,
730
- );
731
-
732
- if (expanded) {
733
- secondPanel = queryOne('.ecl-mega-menu__mega--level-2', expanded);
734
- if (secondPanel) {
735
- secondPanel.style.height = '';
736
- const subItems = queryAll(`${this.subItemSelector}`, secondPanel);
737
- if (subItems.length > 0) {
738
- subItems.forEach((item) => {
739
- subItemsHeight += item.getBoundingClientRect().height;
740
- });
741
- }
742
- heights.push(subItemsHeight);
743
- // Featured panel calculations.
744
- featuredPanel = queryOne('.ecl-mega-menu__featured', expanded);
745
- if (featuredPanel) {
746
- // Get the elements inside the scrollable container and calculate their heights.
747
- Array.from(featuredPanel.firstElementChild.children).forEach(
748
- (child) => {
749
- const elStyle = window.getComputedStyle(child);
750
- const marginHeight =
751
- parseFloat(elStyle.marginTop) +
752
- parseFloat(elStyle.marginBottom);
753
- featuredHeight += child.offsetHeight + marginHeight;
754
- },
755
- );
756
767
 
757
- heights.push(featuredHeight);
758
- }
768
+ if (secondPanel) {
769
+ secondPanel.style.height = '';
770
+ const subItems = queryAll(`${this.subItemSelector}`, secondPanel);
771
+ if (subItems.length > 0) {
772
+ subItems.forEach((item) => {
773
+ subItemsHeight += item.getBoundingClientRect().height;
774
+ });
775
+ }
776
+ heights.push(subItemsHeight);
777
+ // Featured panel calculations.
778
+ featuredPanel = queryOne('.ecl-mega-menu__featured', expanded);
779
+ if (featuredPanel) {
780
+ // Get the elements inside the scrollable container and calculate their heights.
781
+ Array.from(featuredPanel.firstElementChild.children).forEach(
782
+ (child) => {
783
+ const elStyle = window.getComputedStyle(child);
784
+ const marginHeight =
785
+ parseFloat(elStyle.marginTop) +
786
+ parseFloat(elStyle.marginBottom);
787
+ featuredHeight += child.offsetHeight + marginHeight;
788
+ },
789
+ );
790
+ // Add 5px to the featured panel height to prevent scrollbar on hover
791
+ featuredHeight += 5;
792
+ heights.push(featuredHeight);
759
793
  }
760
794
  }
761
795
 
@@ -775,6 +809,7 @@ export class MegaMenu {
775
809
  if (wrapper) {
776
810
  wrapper.style.height = `${height}px`;
777
811
  }
812
+
778
813
  if (mainPanel && this.isLarge) {
779
814
  mainPanel.style.height = `${height}px`;
780
815
  } else if (mainPanel && infoPanel && this.isDesktop) {
@@ -788,6 +823,11 @@ export class MegaMenu {
788
823
  } else if (secondPanel && this.isDesktop) {
789
824
  secondPanel.style.height = `${height - infoPanelHeight}px`;
790
825
  }
826
+ if (featuredPanelFirst && this.isLarge) {
827
+ featuredPanelFirst.style.height = `${height}px`;
828
+ } else if (featuredPanelFirst && this.isDesktop) {
829
+ featuredPanelFirst.style.height = `${height - infoPanelHeight}px`;
830
+ }
791
831
  if (featuredPanel && this.isLarge) {
792
832
  featuredPanel.style.height = `${height}px`;
793
833
  } else if (featuredPanel && this.isDesktop) {
@@ -800,6 +840,9 @@ export class MegaMenu {
800
840
  if (infoPanel && this.isDesktop) {
801
841
  infoPanel.style.opacity = 1;
802
842
  }
843
+ if (secondPanel && this.isDesktop) {
844
+ secondPanel.style.opacity = 1;
845
+ }
803
846
  }, 100);
804
847
  }
805
848
 
@@ -824,46 +867,63 @@ export class MegaMenu {
824
867
  const item = queryOne('.ecl-mega-menu__item--expanded', this.element);
825
868
 
826
869
  if (item) {
827
- const subList = queryOne('.ecl-mega-menu__sublist', item);
828
- if (subList && this.openPanel.num === 1) {
829
- const info = queryOne('.ecl-mega-menu__info', item);
830
- if (info) {
831
- const bottomRect = info.getBoundingClientRect();
832
- const bottomInfo = bottomRect.bottom;
833
- availableHeight = window.innerHeight - bottomInfo - 16;
870
+ const hasFeatured = queryOne(
871
+ '.ecl-mega-menu__mega--has-featured',
872
+ item,
873
+ );
874
+ const info = queryOne('.ecl-mega-menu__info', item);
875
+ if (info && this.openPanel.num === 1) {
876
+ const bottomRect = info.getBoundingClientRect();
877
+ const bottomInfo = bottomRect.bottom;
878
+ availableHeight = window.innerHeight - bottomInfo - 16;
879
+ }
880
+ if (hasFeatured) {
881
+ const hasFeaturedRect = hasFeatured.getBoundingClientRect();
882
+ const hasFeaturedTop = hasFeaturedRect.top;
883
+ availableHeight =
884
+ availableHeight || window.innerHeight - hasFeaturedTop;
885
+ hasFeatured.style.height = `${availableHeight}px`;
886
+ } else {
887
+ const subList = queryOne('.ecl-mega-menu__sublist', item);
888
+ if (subList && this.openPanel.num === 1) {
889
+ console.log('dovrei...');
890
+ const subListRect = subList.getBoundingClientRect();
891
+ const subListRectTop = subListRect.top;
834
892
  subList.classList.add('ecl-mega-menu__sublist--scrollable');
893
+ availableHeight =
894
+ availableHeight || window.innerHeight - subListRectTop;
835
895
  subList.style.height = `${availableHeight}px`;
896
+ } else if (subList) {
897
+ subList.classList.remove('ecl-mega-menu__sublist--scrollable');
898
+ subList.style.height = '';
836
899
  }
837
- } else if (subList) {
838
- subList.classList.remove('ecl-mega-menu__sublist--scrollable');
839
- subList.style.height = '';
840
- }
841
- }
842
900
 
843
- if (this.openPanel.num === 2) {
844
- const subItem = queryOne(
845
- '.ecl-mega-menu__subitem--expanded',
846
- this.element,
847
- );
848
- if (subItem) {
849
- const subMega = queryOne(
850
- '.ecl-mega-menu__mega--level-2',
851
- subItem,
852
- );
853
- if (subMega) {
854
- const subMegaRect = subMega.getBoundingClientRect();
855
- const subMegaTop = subMegaRect.top;
856
- availableHeight = window.innerHeight - subMegaTop;
857
- subMega.style.height = `${availableHeight}px`;
901
+ if (this.openPanel.num === 2) {
902
+ const subItem = queryOne(
903
+ '.ecl-mega-menu__subitem--expanded',
904
+ this.element,
905
+ );
906
+ if (subItem) {
907
+ const subMega = queryOne(
908
+ '.ecl-mega-menu__mega--level-2',
909
+ subItem,
910
+ );
911
+ if (subMega) {
912
+ const subMegaRect = subMega.getBoundingClientRect();
913
+ const subMegaTop = subMegaRect.top;
914
+ availableHeight = window.innerHeight - subMegaTop;
915
+ subMega.style.height = `${availableHeight}px`;
916
+ }
917
+ }
918
+ }
919
+ if (this.wrappers) {
920
+ this.wrappers.forEach((wrapper) => {
921
+ wrapper.style.top = '';
922
+ wrapper.style.height = '';
923
+ });
858
924
  }
859
925
  }
860
926
  }
861
- if (this.wrappers) {
862
- this.wrappers.forEach((wrapper) => {
863
- wrapper.style.top = '';
864
- wrapper.style.height = '';
865
- });
866
- }
867
927
  }
868
928
  }, 0);
869
929
  } else {
@@ -903,7 +963,7 @@ export class MegaMenu {
903
963
  handleKeyboard(e) {
904
964
  const element = e.target;
905
965
  const cList = element.classList;
906
- const menuExpanded = this.element.getAttribute('aria-expanded');
966
+ const menuExpanded = this.element.getAttribute('data-expanded');
907
967
 
908
968
  // Detect press on Escape
909
969
  if (e.key === 'Escape' || e.key === 'Esc') {
@@ -911,7 +971,7 @@ export class MegaMenu {
911
971
  element.blur();
912
972
  }
913
973
 
914
- if (menuExpanded === 'false') {
974
+ if (!menuExpanded) {
915
975
  this.closeOpenDropdown();
916
976
  }
917
977
  return;
@@ -1155,7 +1215,7 @@ export class MegaMenu {
1155
1215
  } else {
1156
1216
  e.preventDefault();
1157
1217
  this.disableScroll();
1158
- this.element.setAttribute('aria-expanded', 'true');
1218
+ this.element.setAttribute('data-expanded', true);
1159
1219
  this.element.classList.add('ecl-mega-menu--start-panel');
1160
1220
  this.element.classList.remove(
1161
1221
  'ecl-mega-menu--one-panel',
@@ -1197,7 +1257,7 @@ export class MegaMenu {
1197
1257
  * @fires Menu#onClose
1198
1258
  */
1199
1259
  handleClickOnClose(e) {
1200
- if (this.element.getAttribute('aria-expanded') === 'true') {
1260
+ if (this.element.getAttribute('data-expanded')) {
1201
1261
  this.focusTrap.deactivate();
1202
1262
  this.closeOpenDropdown();
1203
1263
  this.trigger('onClose', e);
@@ -1312,7 +1372,6 @@ export class MegaMenu {
1312
1372
  this.positionMenuOverlay();
1313
1373
  this.checkDropdownHeight(menuItem);
1314
1374
  this.element.setAttribute('data-expanded', true);
1315
- this.element.setAttribute('aria-expanded', 'true');
1316
1375
  this.element.classList.add('ecl-mega-menu--one-panel');
1317
1376
  this.element.classList.remove('ecl-mega-menu--start-panel');
1318
1377
  this.open.setAttribute('aria-expanded', 'true');
@@ -1360,19 +1419,6 @@ export class MegaMenu {
1360
1419
  };
1361
1420
  const details = { panel: 1, item: menuItem };
1362
1421
  this.trigger('OnOpenPanel', details);
1363
- if (this.isDesktop) {
1364
- const list = queryOne('.ecl-mega-menu__sublist', menuItem);
1365
- if (list) {
1366
- // Expand the item in the sublist if it contains children.
1367
- const firstExpandedChild = Array.from(list.children).find((child) =>
1368
- child.firstElementChild?.hasAttribute('aria-expanded'),
1369
- );
1370
-
1371
- if (firstExpandedChild) {
1372
- this.handleSecondPanel(firstExpandedChild, 'expand', true);
1373
- }
1374
- }
1375
- }
1376
1422
  break;
1377
1423
  }
1378
1424
 
@@ -1392,7 +1438,7 @@ export class MegaMenu {
1392
1438
  *
1393
1439
  * @fires MegaMenu#onOpenPanel
1394
1440
  */
1395
- handleSecondPanel(menuItem, op, noCheck = false) {
1441
+ handleSecondPanel(menuItem, op) {
1396
1442
  const infoPanel = queryOne(
1397
1443
  '.ecl-mega-menu__info',
1398
1444
  menuItem.closest('.ecl-container'),
@@ -1410,17 +1456,30 @@ export class MegaMenu {
1410
1456
  if (item === menuItem) {
1411
1457
  if (itemLink && itemLink.hasAttribute('aria-expanded')) {
1412
1458
  itemLink.setAttribute('aria-expanded', 'true');
1413
-
1459
+ const mega = queryOne('.ecl-mega-menu__mega', item);
1414
1460
  if (!this.isDesktop) {
1415
1461
  // We use this class mainly to recover the default behavior of the link.
1416
1462
  itemLink.classList.add('ecl-mega-menu__parent-link');
1417
1463
  if (this.back) {
1418
1464
  this.back.focus();
1419
1465
  }
1466
+ } else {
1467
+ // Hide the panel since it will be resized later.
1468
+ mega.style.opacity = 0;
1420
1469
  }
1421
1470
  item.classList.add('ecl-mega-menu__subitem--expanded');
1422
1471
  }
1423
1472
  item.classList.add('ecl-mega-menu__subitem--current');
1473
+ const hasFeatured = queryOne('.ecl-mega-menu__featured', item);
1474
+ if (hasFeatured) {
1475
+ this.element.classList.add(
1476
+ 'ecl-mega-menu--has-secondary-featured',
1477
+ );
1478
+ } else {
1479
+ this.element.classList.remove(
1480
+ 'ecl-mega-menu--has-secondary-featured',
1481
+ );
1482
+ }
1424
1483
  this.backItemLevel2 = item;
1425
1484
  } else {
1426
1485
  if (itemLink && itemLink.hasAttribute('aria-expanded')) {
@@ -1450,12 +1509,11 @@ export class MegaMenu {
1450
1509
  });
1451
1510
  }
1452
1511
  this.positionMenuOverlay();
1453
- if (!noCheck) {
1454
- this.checkDropdownHeight(
1455
- menuItem.closest('.ecl-mega-menu__item'),
1456
- false,
1457
- );
1458
- }
1512
+ this.checkDropdownHeight(
1513
+ menuItem.closest('.ecl-mega-menu__item'),
1514
+ false,
1515
+ );
1516
+
1459
1517
  const details = { panel: 2, item: menuItem };
1460
1518
  this.trigger('OnOpenPanel', details);
1461
1519
  break;
@@ -1463,6 +1521,7 @@ export class MegaMenu {
1463
1521
 
1464
1522
  case 'collapse':
1465
1523
  this.element.classList.remove('ecl-mega-menu--two-panels');
1524
+ this.element.classList.remove('ecl-mega-menu--has-secondary-featured');
1466
1525
  this.openPanel = { num: 1 };
1467
1526
  // eslint-disable-next-line no-case-declarations
1468
1527
  const itemLink = queryOne(this.subLinkSelector, menuItem);
@@ -1560,6 +1619,7 @@ export class MegaMenu {
1560
1619
  * @fires MegaMenu#onFocusTrapToggle
1561
1620
  */
1562
1621
  closeOpenDropdown(esc = false) {
1622
+ this.element.classList.remove('ecl-mega-menu--has-secondary-featured');
1563
1623
  if (this.header) {
1564
1624
  this.header.classList.remove(
1565
1625
  'ecl-site-header--open-menu',
@@ -1573,7 +1633,6 @@ export class MegaMenu {
1573
1633
  }
1574
1634
  }
1575
1635
  this.enableScroll();
1576
- this.element.setAttribute('aria-expanded', 'false');
1577
1636
  this.element.removeAttribute('data-expanded');
1578
1637
  this.element.classList.remove(
1579
1638
  'ecl-mega-menu--start-panel',
@@ -1626,7 +1685,7 @@ export class MegaMenu {
1626
1685
  sublists.forEach((sublist) => {
1627
1686
  sublist.classList.remove(
1628
1687
  'ecl-mega-menu__sublist--no-border',
1629
- '.ecl-mega-menu__sublist--scrollable',
1688
+ 'ecl-mega-menu__sublist--scrollable',
1630
1689
  );
1631
1690
  });
1632
1691
  }
@@ -1660,11 +1719,11 @@ export class MegaMenu {
1660
1719
  */
1661
1720
  handleFocusOut(e) {
1662
1721
  const element = e.target;
1663
- const menuExpanded = this.element.getAttribute('aria-expanded');
1722
+ const menuExpanded = this.element.getAttribute('data-expanded');
1664
1723
 
1665
1724
  // Specific focus action for mobile menu
1666
1725
  // Loop through the items and go back to close button
1667
- if (menuExpanded === 'true' && !this.isDesktop) {
1726
+ if (menuExpanded && !this.isDesktop) {
1668
1727
  const nextItem = element.parentElement.nextSibling;
1669
1728
 
1670
1729
  if (!nextItem) {