@eui/ecl 22.0.0-alpha.45 → 22.0.0-alpha.46

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 (159) hide show
  1. package/docs/changelog.html +38 -0
  2. package/docs/components/EclMenuItemComponent.html +203 -161
  3. package/docs/components/EclMenuMegaItemComponent.html +3 -3
  4. package/docs/interfaces/EclMenuItem.html +4 -4
  5. package/docs/js/search/search_index.js +2 -2
  6. package/docs/json/documentation.json +152 -124
  7. package/docs/llms.txt +13 -7
  8. package/docs/properties.html +1 -1
  9. package/fesm2022/eui-ecl-components-ecl-accordion.mjs.map +1 -1
  10. package/fesm2022/eui-ecl-components-ecl-add-to-calendar.mjs.map +1 -1
  11. package/fesm2022/eui-ecl-components-ecl-animated-numbers.mjs.map +1 -1
  12. package/fesm2022/eui-ecl-components-ecl-app.mjs.map +1 -1
  13. package/fesm2022/eui-ecl-components-ecl-banner.mjs.map +1 -1
  14. package/fesm2022/eui-ecl-components-ecl-blockquote.mjs.map +1 -1
  15. package/fesm2022/eui-ecl-components-ecl-breadcrumb.mjs.map +1 -1
  16. package/fesm2022/eui-ecl-components-ecl-button.mjs.map +1 -1
  17. package/fesm2022/eui-ecl-components-ecl-card.mjs.map +1 -1
  18. package/fesm2022/eui-ecl-components-ecl-carousel.mjs.map +1 -1
  19. package/fesm2022/eui-ecl-components-ecl-category-filter.mjs.map +1 -1
  20. package/fesm2022/eui-ecl-components-ecl-checkbox.mjs.map +1 -1
  21. package/fesm2022/eui-ecl-components-ecl-content-block.mjs.map +1 -1
  22. package/fesm2022/eui-ecl-components-ecl-content-item.mjs.map +1 -1
  23. package/fesm2022/eui-ecl-components-ecl-date-block.mjs.map +1 -1
  24. package/fesm2022/eui-ecl-components-ecl-date-picker.mjs.map +1 -1
  25. package/fesm2022/eui-ecl-components-ecl-divider.mjs.map +1 -1
  26. package/fesm2022/eui-ecl-components-ecl-expandable.mjs.map +1 -1
  27. package/fesm2022/eui-ecl-components-ecl-fact-figures.mjs.map +1 -1
  28. package/fesm2022/eui-ecl-components-ecl-featured.mjs.map +1 -1
  29. package/fesm2022/eui-ecl-components-ecl-feedback-message.mjs.map +1 -1
  30. package/fesm2022/eui-ecl-components-ecl-file-upload.mjs.map +1 -1
  31. package/fesm2022/eui-ecl-components-ecl-file.mjs.map +1 -1
  32. package/fesm2022/eui-ecl-components-ecl-form-group.mjs.map +1 -1
  33. package/fesm2022/eui-ecl-components-ecl-form-label.mjs.map +1 -1
  34. package/fesm2022/eui-ecl-components-ecl-gallery.mjs.map +1 -1
  35. package/fesm2022/eui-ecl-components-ecl-help-block.mjs.map +1 -1
  36. package/fesm2022/eui-ecl-components-ecl-highlight-box.mjs.map +1 -1
  37. package/fesm2022/eui-ecl-components-ecl-highlighted-search.mjs.map +1 -1
  38. package/fesm2022/eui-ecl-components-ecl-icon.mjs.map +1 -1
  39. package/fesm2022/eui-ecl-components-ecl-inpage-navigation.mjs.map +1 -1
  40. package/fesm2022/eui-ecl-components-ecl-label.mjs.map +1 -1
  41. package/fesm2022/eui-ecl-components-ecl-layout-wrapper.mjs.map +1 -1
  42. package/fesm2022/eui-ecl-components-ecl-link.mjs.map +1 -1
  43. package/fesm2022/eui-ecl-components-ecl-list-illustration.mjs.map +1 -1
  44. package/fesm2022/eui-ecl-components-ecl-list.mjs.map +1 -1
  45. package/fesm2022/eui-ecl-components-ecl-loading-indicator.mjs.map +1 -1
  46. package/fesm2022/eui-ecl-components-ecl-media-container.mjs.map +1 -1
  47. package/fesm2022/eui-ecl-components-ecl-mega-menu.mjs.map +1 -1
  48. package/fesm2022/eui-ecl-components-ecl-menu.mjs +67 -49
  49. package/fesm2022/eui-ecl-components-ecl-menu.mjs.map +1 -1
  50. package/fesm2022/eui-ecl-components-ecl-modal.mjs.map +1 -1
  51. package/fesm2022/eui-ecl-components-ecl-multiselect.mjs.map +1 -1
  52. package/fesm2022/eui-ecl-components-ecl-navigation-list.mjs.map +1 -1
  53. package/fesm2022/eui-ecl-components-ecl-news-ticker.mjs.map +1 -1
  54. package/fesm2022/eui-ecl-components-ecl-notification.mjs.map +1 -1
  55. package/fesm2022/eui-ecl-components-ecl-page-header.mjs.map +1 -1
  56. package/fesm2022/eui-ecl-components-ecl-page-information.mjs.map +1 -1
  57. package/fesm2022/eui-ecl-components-ecl-page-summary.mjs.map +1 -1
  58. package/fesm2022/eui-ecl-components-ecl-pagination.mjs.map +1 -1
  59. package/fesm2022/eui-ecl-components-ecl-popover.mjs.map +1 -1
  60. package/fesm2022/eui-ecl-components-ecl-quiz.mjs.map +1 -1
  61. package/fesm2022/eui-ecl-components-ecl-radio.mjs.map +1 -1
  62. package/fesm2022/eui-ecl-components-ecl-range.mjs.map +1 -1
  63. package/fesm2022/eui-ecl-components-ecl-rating-field.mjs.map +1 -1
  64. package/fesm2022/eui-ecl-components-ecl-search-form.mjs.map +1 -1
  65. package/fesm2022/eui-ecl-components-ecl-select.mjs.map +1 -1
  66. package/fesm2022/eui-ecl-components-ecl-site-footer.mjs.map +1 -1
  67. package/fesm2022/eui-ecl-components-ecl-site-header.mjs.map +1 -1
  68. package/fesm2022/eui-ecl-components-ecl-slogan-ticker.mjs.map +1 -1
  69. package/fesm2022/eui-ecl-components-ecl-social-media-follow.mjs.map +1 -1
  70. package/fesm2022/eui-ecl-components-ecl-splash-page.mjs.map +1 -1
  71. package/fesm2022/eui-ecl-components-ecl-spotlight.mjs.map +1 -1
  72. package/fesm2022/eui-ecl-components-ecl-sticky-container.mjs.map +1 -1
  73. package/fesm2022/eui-ecl-components-ecl-story-card.mjs.map +1 -1
  74. package/fesm2022/eui-ecl-components-ecl-table.mjs.map +1 -1
  75. package/fesm2022/eui-ecl-components-ecl-tabs.mjs.map +1 -1
  76. package/fesm2022/eui-ecl-components-ecl-tag.mjs.map +1 -1
  77. package/fesm2022/eui-ecl-components-ecl-text-area.mjs.map +1 -1
  78. package/fesm2022/eui-ecl-components-ecl-text-input.mjs.map +1 -1
  79. package/fesm2022/eui-ecl-components-ecl-text-media.mjs.map +1 -1
  80. package/fesm2022/eui-ecl-components-ecl-timeline.mjs.map +1 -1
  81. package/fesm2022/eui-ecl-components-ecl-tooltip.mjs.map +1 -1
  82. package/fesm2022/eui-ecl-core.mjs.map +1 -1
  83. package/fesm2022/eui-ecl-shared.mjs.map +1 -1
  84. package/package.json +1 -1
  85. package/types/eui-ecl-components-ecl-accordion.d.ts.map +1 -1
  86. package/types/eui-ecl-components-ecl-add-to-calendar.d.ts.map +1 -1
  87. package/types/eui-ecl-components-ecl-animated-numbers.d.ts.map +1 -1
  88. package/types/eui-ecl-components-ecl-app.d.ts.map +1 -1
  89. package/types/eui-ecl-components-ecl-banner.d.ts.map +1 -1
  90. package/types/eui-ecl-components-ecl-blockquote.d.ts.map +1 -1
  91. package/types/eui-ecl-components-ecl-breadcrumb.d.ts.map +1 -1
  92. package/types/eui-ecl-components-ecl-button.d.ts.map +1 -1
  93. package/types/eui-ecl-components-ecl-card.d.ts.map +1 -1
  94. package/types/eui-ecl-components-ecl-carousel.d.ts.map +1 -1
  95. package/types/eui-ecl-components-ecl-category-filter.d.ts.map +1 -1
  96. package/types/eui-ecl-components-ecl-checkbox.d.ts.map +1 -1
  97. package/types/eui-ecl-components-ecl-content-block.d.ts.map +1 -1
  98. package/types/eui-ecl-components-ecl-content-item.d.ts.map +1 -1
  99. package/types/eui-ecl-components-ecl-date-block.d.ts.map +1 -1
  100. package/types/eui-ecl-components-ecl-date-picker.d.ts.map +1 -1
  101. package/types/eui-ecl-components-ecl-divider.d.ts.map +1 -1
  102. package/types/eui-ecl-components-ecl-expandable.d.ts.map +1 -1
  103. package/types/eui-ecl-components-ecl-fact-figures.d.ts.map +1 -1
  104. package/types/eui-ecl-components-ecl-featured.d.ts.map +1 -1
  105. package/types/eui-ecl-components-ecl-feedback-message.d.ts.map +1 -1
  106. package/types/eui-ecl-components-ecl-file-upload.d.ts.map +1 -1
  107. package/types/eui-ecl-components-ecl-file.d.ts.map +1 -1
  108. package/types/eui-ecl-components-ecl-form-group.d.ts.map +1 -1
  109. package/types/eui-ecl-components-ecl-form-label.d.ts.map +1 -1
  110. package/types/eui-ecl-components-ecl-gallery.d.ts.map +1 -1
  111. package/types/eui-ecl-components-ecl-help-block.d.ts.map +1 -1
  112. package/types/eui-ecl-components-ecl-highlight-box.d.ts.map +1 -1
  113. package/types/eui-ecl-components-ecl-highlighted-search.d.ts.map +1 -1
  114. package/types/eui-ecl-components-ecl-icon.d.ts.map +1 -1
  115. package/types/eui-ecl-components-ecl-inpage-navigation.d.ts.map +1 -1
  116. package/types/eui-ecl-components-ecl-label.d.ts.map +1 -1
  117. package/types/eui-ecl-components-ecl-layout-wrapper.d.ts.map +1 -1
  118. package/types/eui-ecl-components-ecl-link.d.ts.map +1 -1
  119. package/types/eui-ecl-components-ecl-list-illustration.d.ts.map +1 -1
  120. package/types/eui-ecl-components-ecl-list.d.ts.map +1 -1
  121. package/types/eui-ecl-components-ecl-loading-indicator.d.ts.map +1 -1
  122. package/types/eui-ecl-components-ecl-media-container.d.ts.map +1 -1
  123. package/types/eui-ecl-components-ecl-mega-menu.d.ts.map +1 -1
  124. package/types/eui-ecl-components-ecl-menu.d.ts +16 -11
  125. package/types/eui-ecl-components-ecl-menu.d.ts.map +1 -1
  126. package/types/eui-ecl-components-ecl-modal.d.ts.map +1 -1
  127. package/types/eui-ecl-components-ecl-multiselect.d.ts.map +1 -1
  128. package/types/eui-ecl-components-ecl-navigation-list.d.ts.map +1 -1
  129. package/types/eui-ecl-components-ecl-news-ticker.d.ts.map +1 -1
  130. package/types/eui-ecl-components-ecl-notification.d.ts.map +1 -1
  131. package/types/eui-ecl-components-ecl-page-header.d.ts.map +1 -1
  132. package/types/eui-ecl-components-ecl-page-information.d.ts.map +1 -1
  133. package/types/eui-ecl-components-ecl-page-summary.d.ts.map +1 -1
  134. package/types/eui-ecl-components-ecl-pagination.d.ts.map +1 -1
  135. package/types/eui-ecl-components-ecl-popover.d.ts.map +1 -1
  136. package/types/eui-ecl-components-ecl-quiz.d.ts.map +1 -1
  137. package/types/eui-ecl-components-ecl-radio.d.ts.map +1 -1
  138. package/types/eui-ecl-components-ecl-range.d.ts.map +1 -1
  139. package/types/eui-ecl-components-ecl-rating-field.d.ts.map +1 -1
  140. package/types/eui-ecl-components-ecl-search-form.d.ts.map +1 -1
  141. package/types/eui-ecl-components-ecl-select.d.ts.map +1 -1
  142. package/types/eui-ecl-components-ecl-site-footer.d.ts.map +1 -1
  143. package/types/eui-ecl-components-ecl-site-header.d.ts.map +1 -1
  144. package/types/eui-ecl-components-ecl-slogan-ticker.d.ts.map +1 -1
  145. package/types/eui-ecl-components-ecl-social-media-follow.d.ts.map +1 -1
  146. package/types/eui-ecl-components-ecl-splash-page.d.ts.map +1 -1
  147. package/types/eui-ecl-components-ecl-spotlight.d.ts.map +1 -1
  148. package/types/eui-ecl-components-ecl-sticky-container.d.ts.map +1 -1
  149. package/types/eui-ecl-components-ecl-story-card.d.ts.map +1 -1
  150. package/types/eui-ecl-components-ecl-table.d.ts.map +1 -1
  151. package/types/eui-ecl-components-ecl-tabs.d.ts.map +1 -1
  152. package/types/eui-ecl-components-ecl-tag.d.ts.map +1 -1
  153. package/types/eui-ecl-components-ecl-text-area.d.ts.map +1 -1
  154. package/types/eui-ecl-components-ecl-text-input.d.ts.map +1 -1
  155. package/types/eui-ecl-components-ecl-text-media.d.ts.map +1 -1
  156. package/types/eui-ecl-components-ecl-timeline.d.ts.map +1 -1
  157. package/types/eui-ecl-components-ecl-tooltip.d.ts.map +1 -1
  158. package/types/eui-ecl-core.d.ts.map +1 -1
  159. package/types/eui-ecl-shared.d.ts.map +1 -1
@@ -969,12 +969,12 @@
969
969
  },
970
970
  {
971
971
  "name": "EclMenuItem",
972
- "id": "interface-EclMenuItem-3f0db6beeae4f21f699be9afa5bc6c72676e2d850124076ac93351fc2f221ad3c15131dc064c4f05e5d5e80b5d4b1281295f15b422c40832f56bbcefba4a7f17",
972
+ "id": "interface-EclMenuItem-038ddd566420e281249dfca3a6c6928695914e0974e185099601d5531b2e428d07710350bbce601a3e985dc8921b18d8f6510f82f2d7f7fc25f1fe2ec5403f61",
973
973
  "file": "packages/ecl/components/ecl-menu/models/ecl-menu-item.model.ts",
974
974
  "deprecated": false,
975
975
  "deprecationMessage": "",
976
976
  "type": "interface",
977
- "sourceCode": "import { Signal, WritableSignal } from '@angular/core';\n\nexport interface EclMenuItem {\n id?: Signal<string>;\n label: Signal<string>;\n isHovered?: WritableSignal<boolean>;\n isCurrent?: WritableSignal<boolean>;\n isExpanded?: WritableSignal<boolean>;\n isVisible?: WritableSignal<boolean>;\n hasChildren?: boolean;\n children?: EclMenuItem[];\n}\n",
977
+ "sourceCode": "import { Signal, WritableSignal } from '@angular/core';\n\nexport interface EclMenuItem {\n id?: Signal<string>;\n label: Signal<string>;\n isHovered?: WritableSignal<boolean>;\n isCurrent?: WritableSignal<boolean>;\n isExpanded?: WritableSignal<boolean>;\n isVisible?: WritableSignal<boolean>;\n hasChildren?: Signal<boolean>;\n children?: Signal<EclMenuItem[]>;\n}\n",
978
978
  "displayName": "EclMenuItem",
979
979
  "properties": [
980
980
  {
@@ -982,7 +982,7 @@
982
982
  "coverageIgnore": false,
983
983
  "deprecated": false,
984
984
  "deprecationMessage": "",
985
- "type": "EclMenuItem[]",
985
+ "type": "Signal<EclMenuItem[]>",
986
986
  "indexKey": "",
987
987
  "optional": true,
988
988
  "description": "",
@@ -994,7 +994,7 @@
994
994
  "coverageIgnore": false,
995
995
  "deprecated": false,
996
996
  "deprecationMessage": "",
997
- "type": "boolean",
997
+ "type": "Signal<boolean>",
998
998
  "indexKey": "",
999
999
  "optional": true,
1000
1000
  "description": "",
@@ -66110,7 +66110,7 @@
66110
66110
  },
66111
66111
  {
66112
66112
  "name": "EclMenuComponent",
66113
- "id": "component-EclMenuComponent-1be26f68226dd336abd2507c1f4c4575750683e5ac225e165d36ed645bbe4ec65b630bf3d7bcb1cafb5a0f6eac86058efcbfb5d6df2e838726ad524e612ddf4b",
66113
+ "id": "component-EclMenuComponent-c44b0b6baa089c4bbf8b6873f1838c3b89c3bcf9ab1e00cdfaa2f3ced517dffe832485aeb553be9779f41e34b68c521f51566e20e0db0511437fe0ee3e879e83",
66114
66114
  "file": "packages/ecl/components/ecl-menu/ecl-menu/ecl-menu.component.ts",
66115
66115
  "encapsulation": [],
66116
66116
  "entryComponents": [],
@@ -67025,7 +67025,7 @@
67025
67025
  "description": "<p>The horizontal menu is used as a primary navigation and it can display up to 2 levels.\nIt directs users to the pages where they need to visit.</p>\n",
67026
67026
  "rawdescription": "\n\nThe horizontal menu is used as a primary navigation and it can display up to 2 levels.\nIt directs users to the pages where they need to visit.\n",
67027
67027
  "type": "component",
67028
- "sourceCode": "import { isPlatformBrowser } from '@angular/common';\nimport { AfterContentInit, ChangeDetectorRef, Component, DOCUMENT, ElementRef, OnDestroy, OnInit, PLATFORM_ID, afterNextRender, computed, effect, inject, numberAttribute, output, input, signal, contentChildren, viewChild } from '@angular/core';\nimport { outputToObservable } from '@angular/core/rxjs-interop';\nimport { EuiAppShellService } from '@eui/core';\nimport { EUI_ECL_BUTTON, EclButtonComponent } from '@eui/ecl/components/ecl-button';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { ECLBaseDirective, EclRtlService, KeyCode } from '@eui/ecl/core';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport type Hammer from 'hammerjs';\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { EclMenuItemCaretEvent, EclMenuItemHoverEvent, EclMenuItemKeydownEvent, EclMenuItemSelectEvent } from '../events';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\nimport { EclMenuItemComponent } from './ecl-menu-item.component';\n\n/**\n * The horizontal menu is used as a primary navigation and it can display up to 2 levels.\n * It directs users to the pages where they need to visit.\n */\n@Component({\n selector: 'ecl-menu',\n templateUrl: './ecl-menu.component.html',\n imports: [TranslatePipe, ...EUI_ECL_BUTTON, ...EUI_ECL_ICON],\n styles: [\n `\n :host {\n display: block;\n }\n `,\n ],\n host: {\n role: 'navigation',\n '[class]': 'cssClasses()',\n '[class.ecl-menu--forced-mobile]': 'isTablet()',\n '[class.ecl-menu--forced-close]': 'isMenuHidden()',\n '[attr.data-expanded]': 'isHamburgerMenuOpened() ? \"\" : null',\n '(document:click)': 'onDocumentClick($event)',\n '(keydown.escape)': 'onEscapePress($event)',\n },\n})\nexport class EclMenuComponent extends ECLBaseDirective implements OnInit, AfterContentInit, OnDestroy {\n\n /**\n * Adds some additional styling for the menu items' labels.\n * Depending on that value, the labels can drop on 2-3 lines, if they are longer.\n */\n readonly maxLines = input(0, { transform: numberAttribute });\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly openHamburgerMenu = output();\n readonly closeHamburgerMenu = output();\n\n readonly isHamburgerMenuOpened = signal(false);\n readonly isMobileBreakpoint = signal(false);\n readonly isTablet = signal(false);\n readonly isMenuHidden = signal(false);\n readonly hasOverflow = signal(false);\n /** Controls the delayed application of CSS transition class (prevents Chrome flash on load) */\n readonly hasTransition = signal(false);\n /**\n * Show/hide the specific class for the 'previous' and 'next' buttons.\n */\n readonly isPrevBtnCurrentClass = signal(false);\n readonly menuListStyleRight = signal('auto');\n readonly menuListStyleLeft = signal('auto');\n readonly overlayStyleTop = signal<number | null>(null);\n readonly innerStyleTop = signal<number | null>(null);\n\n /**\n * Represents the current viewport breakpoint states.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n breakpointsValue: any = {\n isMobile: false,\n isTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n };\n\n /**\n * The main items of the menu.\n */\n protected readonly eclRootMenuItems = contentChildren(EclMenuItemComponent);\n /**\n * The element, that wraps the main menu items and the back and forth button.\n */\n protected readonly innerSection = viewChild<ElementRef>('innerSection');\n /**\n * The element that wraps the main menu items.\n */\n protected readonly itemsList = viewChild<ElementRef>('itemsList');\n /**\n * When there is an overflow of the menu, the 'previous' button to navigate back to first hidden items.\n */\n protected readonly btnPrevious = viewChild<EclButtonComponent>('btnPrevious');\n /**\n * When there is an overflow of the menu, the 'next' button to navigate to next hidden items.\n */\n protected readonly btnNext = viewChild<EclButtonComponent>('btnNext');\n /**\n * The button to open/close menu in mobile mode.\n */\n protected readonly openCloseButton = viewChild<EclButtonComponent>('openCloseButton');\n\n protected readonly isShowPrevBtn = signal(false);\n protected readonly isShowNextBtn = signal(false);\n\n protected readonly cssClasses = computed<string>(() =>\n [\n super.getCssClasses('ecl-menu'),\n this.hasTransition() ? 'ecl-menu--transition' : '',\n this.isRtl() ? 'ecl-menu--rtl' : '',\n ].join(' '));\n\n /** Controls whether hover events should be processed (suppressed briefly after touch interactions) */\n private ignoreHover = false;\n private readonly isRtl = signal(false);\n private currentItem: EclMenuItem;\n private destroy$: Subject<boolean> = new Subject<boolean>();\n private connectedItems = new WeakSet<EclMenuItemComponent>();\n private totalItemsWidth = 0;\n private offsetLeft = 0;\n private lastVisibleItem: EclMenuItemComponent;\n private hammertime: Hammer;\n private resizeTimer: ReturnType<typeof setTimeout>;\n private TIMEOUT_INTERVAL = 150;\n private el = inject(ElementRef);\n private eclRtlService = inject(EclRtlService);\n private euiAppShellService = inject(EuiAppShellService);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private cdr = inject(ChangeDetectorRef);\n\n constructor() {\n super();\n // Subscribe to each projected root menu item's events as the set of items changes.\n effect(() => {\n this.eclRootMenuItems().forEach((rootItem) => this.connectMenuItemEvents(rootItem));\n });\n afterNextRender(() => {\n clearTimeout(this.resizeTimer);\n this.observeElementSizeChanges();\n this.resizeTimer = setTimeout(() => {\n this.checkMenuItemsWidth();\n this.checkMenuOverflow();\n }, this.TIMEOUT_INTERVAL);\n this.subscribeToAppShellBreakpoints();\n\n // Delay transition class to prevent CSS transitions from playing on page load (Chrome fix)\n setTimeout(() => {\n this.hasTransition.set(true);\n }, 500);\n });\n }\n\n /**\n * On outside click - close/hide any open menus.\n * @param event\n */\n onDocumentClick(event: MouseEvent): void {\n if (!this.isHamburgerMenuOpened()) return;\n\n const target = event.target as HTMLElement;\n if (!this.el.nativeElement.contains(target) ||\n target.classList.contains('ecl-menu__overlay')) {\n this.closeRootMenu();\n this.onCloseHamburgerMenu();\n }\n }\n\n /**\n * On 'esc' key clicked - close/hide any open menus.\n * @param event\n */\n onEscapePress(event: Event): void {\n if (!this.isHamburgerMenuOpened()) return;\n\n this.closeRootMenu();\n this.onCloseHamburgerMenu();\n }\n\n ngOnInit(): void {\n if (isPlatformBrowser(this.platformId)) {\n this.handleRtlChangeState();\n this.observeSwipeGestures();\n }\n }\n\n ngAfterContentInit(): void {\n // Event wiring for projected items is handled by an effect set up in the constructor.\n }\n\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n\n this.hammertime?.off('swipeleft');\n this.hammertime?.off('swiperight');\n }\n\n /**\n * Swipe left gesture handling\n */\n onSwipeLeft(): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.onOpenHamburgerMenu();\n }\n }\n\n /**\n * Swipe right gesture handling\n */\n onSwipeRight(): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.onCloseHamburgerMenu();\n }\n }\n\n /**\n * Manages state on open/close button click, used in mobile view.\n */\n onMenuOpenCloseClick(evt: MouseEvent): void {\n if (this.isHamburgerMenuOpened()) {\n this.onCloseHamburgerMenu();\n } else {\n this.onOpenHamburgerMenu();\n }\n evt.preventDefault();\n }\n\n /**\n * Manages state on close button click, used in mobile view, in EU theme.\n */\n onMenuCloseClick(evt: MouseEvent): void {\n this.onCloseHamburgerMenu();\n this.closeRootMenu();\n evt.preventDefault();\n }\n\n /**\n * Manages state on back button click, used in mobile view.\n */\n onMenuBackClick(evt: MouseEvent): void {\n this.closeRootMenu();\n evt.preventDefault();\n }\n\n /**\n * Manages state on 'previous' button click. Used in desktop view. Slides the items and shows the previous hidden items.\n */\n onPrevBtnClick(): void {\n this.slideMenuInitialState();\n this.refreshDisplay();\n }\n\n /**\n * Manages state on 'next' button click. Used in desktop view. Slides the items and shows the next hidden items.\n */\n onNextBtnClick(): void {\n this.isShowPrevBtn.set(true);\n this.isShowNextBtn.set(false);\n\n if (this.isRtl()) {\n this.showNextSlideRtl();\n } else {\n this.showNextSlideLtr();\n }\n\n this.refreshDisplay();\n }\n\n /**\n * Postions overlay, depending on parent header position.\n * This method is called from parent header, which uses the menu for navigation.\n * @param bottomSpaceY\n */\n positionMenuOverlay(bottomSpaceY: number): void {\n if ((this.isMobileBreakpoint() || this.isTablet()) && bottomSpaceY) {\n this.overlayStyleTop.set(bottomSpaceY);\n this.innerStyleTop.set(bottomSpaceY);\n } else {\n this.overlayStyleTop.set(null);\n this.innerStyleTop.set(null);\n }\n }\n\n private observeElementSizeChanges(): void {\n new ResizeObserver(() => {\n clearTimeout(this.resizeTimer);\n this.resizeTimer = setTimeout(() => {\n // Scroll to top when menu is open during resize (e.g. orientation change)\n if (this.isHamburgerMenuOpened()) {\n this.document.documentElement.scrollTop = 0;\n this.document.body.scrollTop = 0;\n }\n\n this.totalItemsWidth = 0;\n this.checkMenuItemsWidth();\n this.checkMenuOverflow();\n this.cdr.detectChanges();\n\n // Re-enable hover after resize completes\n this.ignoreHover = false;\n }, this.TIMEOUT_INTERVAL);\n }).observe(this.innerSection().nativeElement);\n }\n\n private slideMenuInitialState(): void {\n this.offsetLeft = 0;\n if (this.isRtl()) {\n this.menuListStyleRight.set('0px');\n this.menuListStyleLeft.set('auto');\n } else {\n this.menuListStyleLeft.set('0px');\n this.menuListStyleRight.set('auto');\n }\n\n this.isShowPrevBtn.set(false);\n this.isShowNextBtn.set(true);\n }\n\n private showNextSlideLtr(): void {\n const btnOffsetWidth = this.btnNext().el.nativeElement.offsetWidth;\n this.offsetLeft =\n this.lastVisibleItem.el.nativeElement.getBoundingClientRect().left -\n this.itemsList().nativeElement.getBoundingClientRect().left -\n btnOffsetWidth;\n\n this.menuListStyleLeft.set(`-${this.offsetLeft}px`);\n this.menuListStyleRight.set('auto');\n }\n\n private showNextSlideRtl(): void {\n const btnOffsetWidth = this.btnNext().el.nativeElement.offsetWidth;\n this.offsetLeft =\n this.itemsList().nativeElement.getBoundingClientRect().right -\n this.lastVisibleItem.el.nativeElement.getBoundingClientRect().right -\n btnOffsetWidth;\n\n this.menuListStyleRight.set(`-${this.offsetLeft}px`);\n this.menuListStyleLeft.set('auto');\n }\n\n private refreshDisplay(): void {\n this.eclRootMenuItems().forEach((item) => {\n item.checkMenuItem(this.maxLines(), this.isMobileBreakpoint());\n item.isVisible.set(!item.isVisible());\n });\n }\n\n private checkMenuOverflow(): void {\n this.hasOverflow.set(this.totalItemsWidth > this.innerSection().nativeElement.offsetWidth + 16);\n if (!this.hasOverflow() || this.isMobileBreakpoint() || this.isTablet()) {\n this.resetOverflowValues();\n return;\n }\n\n this.setClassForPrevNextButtons();\n this.resetVisibilityIndicator();\n\n if (this.offsetLeft === 0) {\n this.manageOverflowEnd();\n } else {\n this.manageOverflowBeginning();\n }\n }\n\n /**\n * Manage items visibility when using overlfow. For both rtl and ltr cases.\n */\n private manageOverflowBeginning(): void {\n if (this.isRtl()) {\n this.eclRootMenuItems()\n .filter(\n (item) =>\n item.el.nativeElement.getBoundingClientRect().right <=\n this.innerSection().nativeElement.getBoundingClientRect().right,\n )\n .forEach((el) => el.isVisible.set(true));\n } else {\n this.eclRootMenuItems()\n .filter(\n (item) =>\n item.el.nativeElement.getBoundingClientRect().left >= this.innerSection().nativeElement.getBoundingClientRect().left,\n )\n .forEach((el) => el.isVisible.set(true));\n }\n }\n\n /**\n * Manage items visibility when using overlfow, with the 'next' button. For both rtl and ltr cases.\n */\n private manageOverflowEnd(): void {\n this.isShowNextBtn.set(true);\n this.menuListStyleLeft.set('0px');\n this.menuListStyleRight.set('auto');\n if (this.isRtl()) {\n for (const item of this.eclRootMenuItems()) {\n if (item.el.nativeElement.getBoundingClientRect().left < this.itemsList().nativeElement.getBoundingClientRect().left) {\n this.lastVisibleItem = item;\n break;\n }\n item.isVisible.set(true);\n }\n } else {\n for (const item of this.eclRootMenuItems()) {\n if (item.el.nativeElement.getBoundingClientRect().right > this.itemsList().nativeElement.getBoundingClientRect().right) {\n this.lastVisibleItem = item;\n break;\n }\n item.isVisible.set(true);\n }\n }\n }\n\n private resetVisibilityIndicator(): void {\n this.eclRootMenuItems().forEach((item) => item.isVisible.set(false));\n }\n\n private resetOverflowValues(): void {\n this.isShowPrevBtn.set(false);\n this.isShowNextBtn.set(false);\n this.hasOverflow.set(false);\n this.menuListStyleLeft.set('0px');\n this.offsetLeft = 0;\n this.totalItemsWidth = 0;\n this.lastVisibleItem = null;\n }\n\n private setClassForPrevNextButtons(): void {\n this.currentItem = this.eclRootMenuItems().find((item) => item.isCurrent());\n if (this.currentItem) {\n if ((this.currentItem as EclMenuItemComponent).isVisible()) {\n this.isPrevBtnCurrentClass.set(true);\n } else {\n this.isPrevBtnCurrentClass.set(false);\n }\n }\n }\n\n private checkMenuItemsWidth(): void {\n this.eclRootMenuItems().forEach((item) => {\n item.checkMenuItem(this.maxLines(), this.isMobileBreakpoint());\n this.totalItemsWidth += item.el.nativeElement.offsetWidth;\n });\n }\n\n public get isMenuExpandedInMobile(): boolean {\n return (this.isMobileBreakpoint() || this.isTablet()) && this.isAnyRootMenuItemExpanded();\n }\n\n private isAnyRootMenuItemExpanded(): boolean {\n return this.eclRootMenuItems().some((item) => item.isExpanded());\n }\n\n private connectMenuItemEvents(item: EclMenuItemComponent): void {\n if (this.connectedItems.has(item)) {\n return;\n }\n this.connectedItems.add(item);\n outputToObservable(item.menuItemSelect).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemSelected(evt);\n });\n outputToObservable(item.menuItemHover).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemHovered(evt);\n });\n outputToObservable(item.menuItemHoverOut).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemHoveredOut(evt);\n });\n outputToObservable(item.menuItemCaret).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemCaret(evt);\n });\n outputToObservable(item.menuMegaItemSelect).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onMenuMegaItemSelected(item, evt);\n });\n outputToObservable(item.menuItemFocus).pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.onRootMenuItemFocused(item);\n });\n outputToObservable(item.menuItemKeydown).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemKeydown(item, evt);\n });\n outputToObservable(item.menuMegaLastItemTab).pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.openCloseButton().onFocus();\n });\n }\n\n private onRootMenuItemFocused(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (!this.isMobileBreakpoint() && !this.isTablet()) {\n this.closeRootMenu();\n }\n if (this.isMovePrevOverflow(index)){\n this.onPrevBtnClick();\n } else if (this.isMoveNextOverflow(index)){\n this.onNextBtnClick();\n }\n }\n\n private onRootMenuItemKeydown(item: EclMenuItemComponent, evt: EclMenuItemKeydownEvent): void {\n switch (evt.domEvent.key) {\n case KeyCode.ARROW_DOWN: {\n this.onRootMenuItemEnter(item);\n break;\n }\n case KeyCode.ARROW_RIGHT: {\n this.onNextMenuItemFocus(item);\n break;\n }\n case KeyCode.ARROW_UP: {\n this.onPrevMenuItemFocus(item);\n break;\n }\n case KeyCode.ARROW_LEFT: {\n this.onPrevMenuItemFocus(item);\n break;\n }\n case KeyCode.ENTER: {\n this.onRootMenuItemEnter(item);\n break;\n }\n case KeyCode.ESCAPE: {\n this.onRootMenuItemEscape(item, evt.domEvent);\n break;\n }\n case KeyCode.TAB: {\n if ((this.isMobileBreakpoint() || this.isTablet()) && this.isLastItem(item)) {\n evt.preventDefault();\n this.openCloseButton().onFocus();\n }\n break;\n }\n default: {\n break;\n }\n }\n }\n\n private isLastItem(item: EclMenuItemComponent): boolean {\n const items = this.eclRootMenuItems();\n return items[items.length - 1] === item;\n }\n\n private onRootMenuItemEscape(item: EclMenuItem, evt: Event): void {\n if (item.isHovered()) {\n this.closeRootMenu();\n }\n evt.stopPropagation();\n }\n\n private onPrevMenuItemFocus(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (index !== 0) {\n this.getRootMenuItemsArray()[index - 1].onFocusFromNext();\n }\n }\n\n private onNextMenuItemFocus(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (index !== this.getRootMenuItemsArray().length - 1) {\n this.getRootMenuItemsArray()[index + 1].onFocusFromPrev();\n }\n }\n\n private findIndexInArray(item: EclMenuItem): number {\n return this.getRootMenuItemsArray().findIndex(el => item === el);\n }\n\n private isMoveNextOverflow(index: number): boolean {\n return !this.getRootMenuItemsArray()[index].isVisible() && this.isShowNextBtn();\n }\n\n private isMovePrevOverflow(index: number): boolean {\n return !this.getRootMenuItemsArray()[index].isVisible() && this.isShowPrevBtn();\n }\n\n private onRootMenuItemSelected(evt: EclMenuItemSelectEvent): void {\n const requestedRootMenuItem: EclMenuItem = evt.menuItem;\n\n this.closeRootMenu();\n this.deselectAllRootMenuItems();\n this.deselectAllSubmenusItems();\n this.selectMenuItem(requestedRootMenuItem);\n this.onCloseHamburgerMenu();\n if (this.hasOverflow) {\n this.managePrevNextButtonsClassOnItemSelected();\n }\n this.sendMenuItemSelectEvent(evt);\n }\n\n private managePrevNextButtonsClassOnItemSelected(): void {\n if (this.isShowNextBtn()) {\n this.isPrevBtnCurrentClass.set(true);\n } else {\n this.isPrevBtnCurrentClass.set(false);\n }\n }\n\n private onRootMenuItemHovered(evt: EclMenuItemHoverEvent): void {\n if (this.ignoreHover) return;\n this.openSubmenu(evt.menuItem);\n }\n\n private onRootMenuItemEnter(item: EclMenuItem): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.closeRootMenuButOneItem(item);\n this.toggleExpandedForMenuItem(item);\n } else {\n if (!item.isHovered()) {\n this.openSubmenu(item);\n } else {\n this.closeRootMenu();\n }\n }\n }\n\n private openSubmenu(item: EclMenuItem): void {\n if (item.isVisible()) {\n if (!this.isMobileBreakpoint() && !this.isTablet() && (item as EclMenuItemComponent).submenu()) {\n this.checkMegaMenu(item as EclMenuItemComponent);\n }\n item.isHovered.set(true);\n this.closeRootMenuButOneItem(item);\n this.showMenu();\n }\n }\n\n private getRootMenuItemsArray(): readonly EclMenuItemComponent[] {\n return this.eclRootMenuItems();\n }\n\n private checkMegaMenu(menuItem: EclMenuItemComponent): void {\n if (menuItem.isFull()) {\n if (this.isRtl()) {\n menuItem.submenu().offsetRight.set(this.offsetLeft);\n } else {\n menuItem.submenu().offsetLeft.set(this.offsetLeft);\n }\n return;\n }\n this.checkEnoughSpaceForMegaMenu(menuItem);\n }\n\n private checkEnoughSpaceForMegaMenu(menuItem: EclMenuItemComponent): void {\n const megaBounding = menuItem.submenu().el.nativeElement.getBoundingClientRect();\n const containerBounding = this.innerSection().nativeElement.getBoundingClientRect();\n const menuItemBounding = menuItem.el.nativeElement.getBoundingClientRect();\n\n const megaWidth = megaBounding.width;\n const containerWidth = containerBounding.width;\n const menuItemPosition = menuItemBounding.left - containerBounding.left;\n\n if (menuItemPosition + megaWidth > containerWidth) {\n menuItem.submenu().isRtlDisplay.set(true);\n } else {\n menuItem.submenu().isRtlDisplay.set(false);\n }\n }\n\n private onRootMenuItemHoveredOut(evt: EclMenuItemHoverEvent): void {\n if (this.ignoreHover) return;\n if (!this.isMobileBreakpoint()) {\n this.closeRootMenu();\n }\n }\n\n private onRootMenuItemCaret(evt: EclMenuItemCaretEvent): void {\n const requestedRootMenuItem: EclMenuItem = evt.menuItem;\n\n // Suppress hover events briefly after touch/caret interaction (prevents double-trigger on hybrid devices)\n this.ignoreHover = true;\n\n this.closeRootMenuButOneItem(requestedRootMenuItem);\n this.toggleExpandedForMenuItem(requestedRootMenuItem);\n }\n\n private onMenuMegaItemSelected(rootMenuItem: EclMenuItem, evt: EclMenuItemSelectEvent): void {\n const requestedSubmenuItem = evt.menuItem;\n\n this.closeRootMenu();\n this.deselectAllRootMenuItems();\n this.deselectAllSubmenusItems();\n this.selectMenuItem(rootMenuItem);\n this.selectMenuItem(requestedSubmenuItem);\n this.onCloseHamburgerMenu();\n if (this.hasOverflow()) {\n this.managePrevNextButtonsClassOnItemSelected();\n }\n this.sendMenuItemSelectEvent(evt);\n\n this.hideMenu();\n }\n\n private deselectAllRootMenuItems(): void {\n this.deselectMenuItems(this.eclRootMenuItems());\n }\n\n private deselectAllSubmenusItems(): void {\n this.eclRootMenuItems().forEach((rootItem) => {\n this.deselectMenuItems(rootItem.children);\n });\n }\n\n private deselectMenuItems(items: ReadonlyArray<EclMenuItem>): void {\n items.forEach((it) => {\n it.isCurrent.set(false);\n });\n }\n\n private selectMenuItem(item: EclMenuItem): void {\n item.isCurrent.set(true);\n this.currentItem = item;\n }\n\n private hideMenu(): void {\n this.isMenuHidden.set(true);\n }\n\n private showMenu(): void {\n this.isMenuHidden.set(false);\n }\n\n private closeRootMenu(): void {\n this.eclRootMenuItems().forEach((it) => {\n it.isExpanded.set(false);\n it.isHovered.set(false);\n });\n }\n\n private closeRootMenuButOneItem(item: EclMenuItem): void {\n this.eclRootMenuItems()\n .filter((it) => it !== item)\n .forEach((it) => {\n it.isExpanded.set(false);\n it.isHovered.set(false);\n });\n }\n\n private handleRtlChangeState(): void {\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((rtlState) => {\n if (rtlState !== undefined && this.isRtl() !== rtlState) {\n this.isRtl.set(rtlState);\n if (this.hasOverflow()) {\n this.slideMenuInitialState();\n setTimeout(() => this.checkMenuOverflow());\n }\n }\n });\n }\n\n private toggleExpandedForMenuItem(item: EclMenuItem): void {\n item.isExpanded.set(!item.isExpanded());\n }\n\n private onOpenHamburgerMenu(): void {\n this.disableScroll();\n this.isHamburgerMenuOpened.set(true);\n this.openHamburgerMenu.emit();\n this.focusOnFirstItem();\n }\n\n private onCloseHamburgerMenu(): void {\n this.isHamburgerMenuOpened.set(false);\n this.enableScroll();\n this.closeHamburgerMenu.emit();\n }\n\n private disableScroll(): void {\n this.document.body.classList.add('no-scroll');\n }\n\n private enableScroll(): void {\n this.document.body.classList.remove('no-scroll');\n }\n\n private focusOnFirstItem(): void {\n this.eclRootMenuItems()[0]?.onFocusFromPrev();\n }\n\n private sendMenuItemSelectEvent(evt: EclMenuItemSelectEvent): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(evt.menuItem, evt.domEvent));\n }\n\n private observeSwipeGestures(): void {\n // load hammerjs only on browser\n if(isPlatformBrowser(this.platformId)) {\n import('hammerjs').then((Hammer: Hammer) => {\n this.hammertime = new Hammer.default(this.document, { inputClass: Hammer.TouchInput });\n this.hammertime.on('swipeleft', this.onSwipeLeft.bind(this));\n this.hammertime.on('swiperight', this.onSwipeRight.bind(this));\n }).catch((error) => {\n console.error('Error loading HammerJS', error);\n });\n }\n }\n\n private subscribeToAppShellBreakpoints(): void {\n this.euiAppShellService.breakpoints$\n .pipe(takeUntil(this.destroy$))\n .subscribe((bkps: any) => {\n if (!bkps) return;\n\n this.breakpointsValue = bkps;\n this.isMobileBreakpoint.set(!!bkps.isMobile);\n this.isTablet.set(!!bkps.isTablet || !!bkps.isLtLargeTablet);\n this.cdr.detectChanges();\n\n if (!this.isMobileBreakpoint() && !this.isTablet()) {\n this.overlayStyleTop.set(null);\n this.innerStyleTop.set(null);\n\n // Ensure hamburger menu is closed when switching to desktop\n if (this.isHamburgerMenuOpened()) {\n this.onCloseHamburgerMenu();\n this.closeRootMenu();\n }\n }\n });\n }\n}\n",
67028
+ "sourceCode": "import { isPlatformBrowser } from '@angular/common';\nimport { AfterContentInit, ChangeDetectorRef, Component, DOCUMENT, ElementRef, OnDestroy, OnInit, PLATFORM_ID, afterNextRender, computed, effect, inject, numberAttribute, output, input, signal, contentChildren, viewChild } from '@angular/core';\nimport { outputToObservable } from '@angular/core/rxjs-interop';\nimport { EuiAppShellService } from '@eui/core';\nimport { EUI_ECL_BUTTON, EclButtonComponent } from '@eui/ecl/components/ecl-button';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { ECLBaseDirective, EclRtlService, KeyCode } from '@eui/ecl/core';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport type Hammer from 'hammerjs';\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { EclMenuItemCaretEvent, EclMenuItemHoverEvent, EclMenuItemKeydownEvent, EclMenuItemSelectEvent } from '../events';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\nimport { EclMenuItemComponent } from './ecl-menu-item.component';\n\n/**\n * The horizontal menu is used as a primary navigation and it can display up to 2 levels.\n * It directs users to the pages where they need to visit.\n */\n@Component({\n selector: 'ecl-menu',\n templateUrl: './ecl-menu.component.html',\n imports: [TranslatePipe, ...EUI_ECL_BUTTON, ...EUI_ECL_ICON],\n styles: [\n `\n :host {\n display: block;\n }\n `,\n ],\n host: {\n role: 'navigation',\n '[class]': 'cssClasses()',\n '[class.ecl-menu--forced-mobile]': 'isTablet()',\n '[class.ecl-menu--forced-close]': 'isMenuHidden()',\n '[attr.data-expanded]': 'isHamburgerMenuOpened() ? \"\" : null',\n '(document:click)': 'onDocumentClick($event)',\n '(keydown.escape)': 'onEscapePress($event)',\n },\n})\nexport class EclMenuComponent extends ECLBaseDirective implements OnInit, AfterContentInit, OnDestroy {\n\n /**\n * Adds some additional styling for the menu items' labels.\n * Depending on that value, the labels can drop on 2-3 lines, if they are longer.\n */\n readonly maxLines = input(0, { transform: numberAttribute });\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly openHamburgerMenu = output();\n readonly closeHamburgerMenu = output();\n\n readonly isHamburgerMenuOpened = signal(false);\n readonly isMobileBreakpoint = signal(false);\n readonly isTablet = signal(false);\n readonly isMenuHidden = signal(false);\n readonly hasOverflow = signal(false);\n /** Controls the delayed application of CSS transition class (prevents Chrome flash on load) */\n readonly hasTransition = signal(false);\n /**\n * Show/hide the specific class for the 'previous' and 'next' buttons.\n */\n readonly isPrevBtnCurrentClass = signal(false);\n readonly menuListStyleRight = signal('auto');\n readonly menuListStyleLeft = signal('auto');\n readonly overlayStyleTop = signal<number | null>(null);\n readonly innerStyleTop = signal<number | null>(null);\n\n /**\n * Represents the current viewport breakpoint states.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n breakpointsValue: any = {\n isMobile: false,\n isTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n };\n\n /**\n * The main items of the menu.\n */\n protected readonly eclRootMenuItems = contentChildren(EclMenuItemComponent);\n /**\n * The element, that wraps the main menu items and the back and forth button.\n */\n protected readonly innerSection = viewChild<ElementRef>('innerSection');\n /**\n * The element that wraps the main menu items.\n */\n protected readonly itemsList = viewChild<ElementRef>('itemsList');\n /**\n * When there is an overflow of the menu, the 'previous' button to navigate back to first hidden items.\n */\n protected readonly btnPrevious = viewChild<EclButtonComponent>('btnPrevious');\n /**\n * When there is an overflow of the menu, the 'next' button to navigate to next hidden items.\n */\n protected readonly btnNext = viewChild<EclButtonComponent>('btnNext');\n /**\n * The button to open/close menu in mobile mode.\n */\n protected readonly openCloseButton = viewChild<EclButtonComponent>('openCloseButton');\n\n protected readonly isShowPrevBtn = signal(false);\n protected readonly isShowNextBtn = signal(false);\n\n protected readonly cssClasses = computed<string>(() =>\n [\n super.getCssClasses('ecl-menu'),\n this.hasTransition() ? 'ecl-menu--transition' : '',\n this.isRtl() ? 'ecl-menu--rtl' : '',\n ].join(' '));\n\n /** Controls whether hover events should be processed (suppressed briefly after touch interactions) */\n private ignoreHover = false;\n private readonly isRtl = signal(false);\n private currentItem: EclMenuItem;\n private destroy$: Subject<boolean> = new Subject<boolean>();\n private connectedItems = new WeakSet<EclMenuItemComponent>();\n private totalItemsWidth = 0;\n private offsetLeft = 0;\n private lastVisibleItem: EclMenuItemComponent;\n private hammertime: Hammer;\n private resizeTimer: ReturnType<typeof setTimeout>;\n private TIMEOUT_INTERVAL = 150;\n private el = inject(ElementRef);\n private eclRtlService = inject(EclRtlService);\n private euiAppShellService = inject(EuiAppShellService);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private cdr = inject(ChangeDetectorRef);\n\n constructor() {\n super();\n // Subscribe to each projected root menu item's events as the set of items changes.\n effect(() => {\n this.eclRootMenuItems().forEach((rootItem) => this.connectMenuItemEvents(rootItem));\n });\n afterNextRender(() => {\n clearTimeout(this.resizeTimer);\n this.observeElementSizeChanges();\n this.resizeTimer = setTimeout(() => {\n this.checkMenuItemsWidth();\n this.checkMenuOverflow();\n }, this.TIMEOUT_INTERVAL);\n this.subscribeToAppShellBreakpoints();\n\n // Delay transition class to prevent CSS transitions from playing on page load (Chrome fix)\n setTimeout(() => {\n this.hasTransition.set(true);\n }, 500);\n });\n }\n\n /**\n * On outside click - close/hide any open menus.\n * @param event\n */\n onDocumentClick(event: MouseEvent): void {\n if (!this.isHamburgerMenuOpened()) return;\n\n const target = event.target as HTMLElement;\n if (!this.el.nativeElement.contains(target) ||\n target.classList.contains('ecl-menu__overlay')) {\n this.closeRootMenu();\n this.onCloseHamburgerMenu();\n }\n }\n\n /**\n * On 'esc' key clicked - close/hide any open menus.\n * @param event\n */\n onEscapePress(event: Event): void {\n if (!this.isHamburgerMenuOpened()) return;\n\n this.closeRootMenu();\n this.onCloseHamburgerMenu();\n }\n\n ngOnInit(): void {\n if (isPlatformBrowser(this.platformId)) {\n this.handleRtlChangeState();\n this.observeSwipeGestures();\n }\n }\n\n ngAfterContentInit(): void {\n // Event wiring for projected items is handled by an effect set up in the constructor.\n }\n\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n\n this.hammertime?.off('swipeleft');\n this.hammertime?.off('swiperight');\n }\n\n /**\n * Swipe left gesture handling\n */\n onSwipeLeft(): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.onOpenHamburgerMenu();\n }\n }\n\n /**\n * Swipe right gesture handling\n */\n onSwipeRight(): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.onCloseHamburgerMenu();\n }\n }\n\n /**\n * Manages state on open/close button click, used in mobile view.\n */\n onMenuOpenCloseClick(evt: MouseEvent): void {\n if (this.isHamburgerMenuOpened()) {\n this.onCloseHamburgerMenu();\n } else {\n this.onOpenHamburgerMenu();\n }\n evt.preventDefault();\n }\n\n /**\n * Manages state on close button click, used in mobile view, in EU theme.\n */\n onMenuCloseClick(evt: MouseEvent): void {\n this.onCloseHamburgerMenu();\n this.closeRootMenu();\n evt.preventDefault();\n }\n\n /**\n * Manages state on back button click, used in mobile view.\n */\n onMenuBackClick(evt: MouseEvent): void {\n this.closeRootMenu();\n evt.preventDefault();\n }\n\n /**\n * Manages state on 'previous' button click. Used in desktop view. Slides the items and shows the previous hidden items.\n */\n onPrevBtnClick(): void {\n this.slideMenuInitialState();\n this.refreshDisplay();\n }\n\n /**\n * Manages state on 'next' button click. Used in desktop view. Slides the items and shows the next hidden items.\n */\n onNextBtnClick(): void {\n this.isShowPrevBtn.set(true);\n this.isShowNextBtn.set(false);\n\n if (this.isRtl()) {\n this.showNextSlideRtl();\n } else {\n this.showNextSlideLtr();\n }\n\n this.refreshDisplay();\n }\n\n /**\n * Postions overlay, depending on parent header position.\n * This method is called from parent header, which uses the menu for navigation.\n * @param bottomSpaceY\n */\n positionMenuOverlay(bottomSpaceY: number): void {\n if ((this.isMobileBreakpoint() || this.isTablet()) && bottomSpaceY) {\n this.overlayStyleTop.set(bottomSpaceY);\n this.innerStyleTop.set(bottomSpaceY);\n } else {\n this.overlayStyleTop.set(null);\n this.innerStyleTop.set(null);\n }\n }\n\n private observeElementSizeChanges(): void {\n new ResizeObserver(() => {\n clearTimeout(this.resizeTimer);\n this.resizeTimer = setTimeout(() => {\n // Scroll to top when menu is open during resize (e.g. orientation change)\n if (this.isHamburgerMenuOpened()) {\n this.document.documentElement.scrollTop = 0;\n this.document.body.scrollTop = 0;\n }\n\n this.totalItemsWidth = 0;\n this.checkMenuItemsWidth();\n this.checkMenuOverflow();\n this.cdr.detectChanges();\n\n // Re-enable hover after resize completes\n this.ignoreHover = false;\n }, this.TIMEOUT_INTERVAL);\n }).observe(this.innerSection().nativeElement);\n }\n\n private slideMenuInitialState(): void {\n this.offsetLeft = 0;\n if (this.isRtl()) {\n this.menuListStyleRight.set('0px');\n this.menuListStyleLeft.set('auto');\n } else {\n this.menuListStyleLeft.set('0px');\n this.menuListStyleRight.set('auto');\n }\n\n this.isShowPrevBtn.set(false);\n this.isShowNextBtn.set(true);\n }\n\n private showNextSlideLtr(): void {\n const btnOffsetWidth = this.btnNext().el.nativeElement.offsetWidth;\n this.offsetLeft =\n this.lastVisibleItem.el.nativeElement.getBoundingClientRect().left -\n this.itemsList().nativeElement.getBoundingClientRect().left -\n btnOffsetWidth;\n\n this.menuListStyleLeft.set(`-${this.offsetLeft}px`);\n this.menuListStyleRight.set('auto');\n }\n\n private showNextSlideRtl(): void {\n const btnOffsetWidth = this.btnNext().el.nativeElement.offsetWidth;\n this.offsetLeft =\n this.itemsList().nativeElement.getBoundingClientRect().right -\n this.lastVisibleItem.el.nativeElement.getBoundingClientRect().right -\n btnOffsetWidth;\n\n this.menuListStyleRight.set(`-${this.offsetLeft}px`);\n this.menuListStyleLeft.set('auto');\n }\n\n private refreshDisplay(): void {\n this.eclRootMenuItems().forEach((item) => {\n item.checkMenuItem(this.maxLines(), this.isMobileBreakpoint());\n item.isVisible.set(!item.isVisible());\n });\n }\n\n private checkMenuOverflow(): void {\n this.hasOverflow.set(this.totalItemsWidth > this.innerSection().nativeElement.offsetWidth + 16);\n if (!this.hasOverflow() || this.isMobileBreakpoint() || this.isTablet()) {\n this.resetOverflowValues();\n return;\n }\n\n this.setClassForPrevNextButtons();\n this.resetVisibilityIndicator();\n\n if (this.offsetLeft === 0) {\n this.manageOverflowEnd();\n } else {\n this.manageOverflowBeginning();\n }\n }\n\n /**\n * Manage items visibility when using overlfow. For both rtl and ltr cases.\n */\n private manageOverflowBeginning(): void {\n if (this.isRtl()) {\n this.eclRootMenuItems()\n .filter(\n (item) =>\n item.el.nativeElement.getBoundingClientRect().right <=\n this.innerSection().nativeElement.getBoundingClientRect().right,\n )\n .forEach((el) => el.isVisible.set(true));\n } else {\n this.eclRootMenuItems()\n .filter(\n (item) =>\n item.el.nativeElement.getBoundingClientRect().left >= this.innerSection().nativeElement.getBoundingClientRect().left,\n )\n .forEach((el) => el.isVisible.set(true));\n }\n }\n\n /**\n * Manage items visibility when using overlfow, with the 'next' button. For both rtl and ltr cases.\n */\n private manageOverflowEnd(): void {\n this.isShowNextBtn.set(true);\n this.menuListStyleLeft.set('0px');\n this.menuListStyleRight.set('auto');\n if (this.isRtl()) {\n for (const item of this.eclRootMenuItems()) {\n if (item.el.nativeElement.getBoundingClientRect().left < this.itemsList().nativeElement.getBoundingClientRect().left) {\n this.lastVisibleItem = item;\n break;\n }\n item.isVisible.set(true);\n }\n } else {\n for (const item of this.eclRootMenuItems()) {\n if (item.el.nativeElement.getBoundingClientRect().right > this.itemsList().nativeElement.getBoundingClientRect().right) {\n this.lastVisibleItem = item;\n break;\n }\n item.isVisible.set(true);\n }\n }\n }\n\n private resetVisibilityIndicator(): void {\n this.eclRootMenuItems().forEach((item) => item.isVisible.set(false));\n }\n\n private resetOverflowValues(): void {\n this.isShowPrevBtn.set(false);\n this.isShowNextBtn.set(false);\n this.hasOverflow.set(false);\n this.menuListStyleLeft.set('0px');\n this.offsetLeft = 0;\n this.totalItemsWidth = 0;\n this.lastVisibleItem = null;\n }\n\n private setClassForPrevNextButtons(): void {\n this.currentItem = this.eclRootMenuItems().find((item) => item.isCurrent());\n if (this.currentItem) {\n if ((this.currentItem as EclMenuItemComponent).isVisible()) {\n this.isPrevBtnCurrentClass.set(true);\n } else {\n this.isPrevBtnCurrentClass.set(false);\n }\n }\n }\n\n private checkMenuItemsWidth(): void {\n this.eclRootMenuItems().forEach((item) => {\n item.checkMenuItem(this.maxLines(), this.isMobileBreakpoint());\n this.totalItemsWidth += item.el.nativeElement.offsetWidth;\n });\n }\n\n public get isMenuExpandedInMobile(): boolean {\n return (this.isMobileBreakpoint() || this.isTablet()) && this.isAnyRootMenuItemExpanded();\n }\n\n private isAnyRootMenuItemExpanded(): boolean {\n return this.eclRootMenuItems().some((item) => item.isExpanded());\n }\n\n private connectMenuItemEvents(item: EclMenuItemComponent): void {\n if (this.connectedItems.has(item)) {\n return;\n }\n this.connectedItems.add(item);\n outputToObservable(item.menuItemSelect).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemSelected(evt);\n });\n outputToObservable(item.menuItemHover).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemHovered(evt);\n });\n outputToObservable(item.menuItemHoverOut).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemHoveredOut(evt);\n });\n outputToObservable(item.menuItemCaret).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemCaret(evt);\n });\n outputToObservable(item.menuMegaItemSelect).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onMenuMegaItemSelected(item, evt);\n });\n outputToObservable(item.menuItemFocus).pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.onRootMenuItemFocused(item);\n });\n outputToObservable(item.menuItemKeydown).pipe(takeUntil(this.destroy$)).subscribe((evt) => {\n this.onRootMenuItemKeydown(item, evt);\n });\n outputToObservable(item.menuMegaLastItemTab).pipe(takeUntil(this.destroy$)).subscribe(() => {\n this.openCloseButton().onFocus();\n });\n }\n\n private onRootMenuItemFocused(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (!this.isMobileBreakpoint() && !this.isTablet()) {\n this.closeRootMenu();\n }\n if (this.isMovePrevOverflow(index)){\n this.onPrevBtnClick();\n } else if (this.isMoveNextOverflow(index)){\n this.onNextBtnClick();\n }\n }\n\n private onRootMenuItemKeydown(item: EclMenuItemComponent, evt: EclMenuItemKeydownEvent): void {\n switch (evt.domEvent.key) {\n // At the first-level row, arrows navigate through the items (ECL parity):\n // ArrowRight/ArrowDown move forward, ArrowLeft/ArrowUp move backward.\n // Opening a submenu via ArrowDown is handled on the caret button itself\n // (onButtonKeydown), not here.\n case KeyCode.ARROW_RIGHT:\n case KeyCode.ARROW_DOWN: {\n this.onNextMenuItemFocus(item);\n break;\n }\n case KeyCode.ARROW_UP:\n case KeyCode.ARROW_LEFT: {\n this.onPrevMenuItemFocus(item);\n break;\n }\n case KeyCode.SPACE:\n case KeyCode.ENTER: {\n this.onRootMenuItemEnter(item);\n break;\n }\n case KeyCode.ESCAPE: {\n this.onRootMenuItemEscape(item, evt.domEvent);\n break;\n }\n case KeyCode.TAB: {\n if ((this.isMobileBreakpoint() || this.isTablet()) && this.isLastItem(item)) {\n evt.preventDefault();\n this.openCloseButton().onFocus();\n }\n break;\n }\n default: {\n break;\n }\n }\n }\n\n private isLastItem(item: EclMenuItemComponent): boolean {\n const items = this.eclRootMenuItems();\n return items[items.length - 1] === item;\n }\n\n private onRootMenuItemEscape(item: EclMenuItem, evt: Event): void {\n if (item.isHovered()) {\n this.closeRootMenu();\n }\n evt.stopPropagation();\n }\n\n private onPrevMenuItemFocus(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (index !== 0) {\n this.getRootMenuItemsArray()[index - 1].onFocusFromNext();\n }\n }\n\n private onNextMenuItemFocus(item: EclMenuItem): void {\n const index = this.findIndexInArray(item);\n if (index !== this.getRootMenuItemsArray().length - 1) {\n this.getRootMenuItemsArray()[index + 1].onFocusFromPrev();\n }\n }\n\n private findIndexInArray(item: EclMenuItem): number {\n return this.getRootMenuItemsArray().findIndex(el => item === el);\n }\n\n private isMoveNextOverflow(index: number): boolean {\n return !this.getRootMenuItemsArray()[index].isVisible() && this.isShowNextBtn();\n }\n\n private isMovePrevOverflow(index: number): boolean {\n return !this.getRootMenuItemsArray()[index].isVisible() && this.isShowPrevBtn();\n }\n\n private onRootMenuItemSelected(evt: EclMenuItemSelectEvent): void {\n const requestedRootMenuItem: EclMenuItem = evt.menuItem;\n\n this.closeRootMenu();\n this.deselectAllRootMenuItems();\n this.deselectAllSubmenusItems();\n this.selectMenuItem(requestedRootMenuItem);\n this.onCloseHamburgerMenu();\n if (this.hasOverflow()) {\n this.managePrevNextButtonsClassOnItemSelected();\n }\n this.sendMenuItemSelectEvent(evt);\n }\n\n private managePrevNextButtonsClassOnItemSelected(): void {\n if (this.isShowNextBtn()) {\n this.isPrevBtnCurrentClass.set(true);\n } else {\n this.isPrevBtnCurrentClass.set(false);\n }\n }\n\n private onRootMenuItemHovered(evt: EclMenuItemHoverEvent): void {\n if (this.ignoreHover) return;\n this.openSubmenu(evt.menuItem);\n }\n\n private onRootMenuItemEnter(item: EclMenuItem): void {\n if (this.isMobileBreakpoint() || this.isTablet()) {\n this.closeRootMenuButOneItem(item);\n this.toggleExpandedForMenuItem(item);\n } else {\n if (!item.isHovered()) {\n this.openSubmenu(item);\n } else {\n this.closeRootMenu();\n }\n }\n }\n\n private openSubmenu(item: EclMenuItem): void {\n if (item.isVisible()) {\n if (!this.isMobileBreakpoint() && !this.isTablet() && (item as EclMenuItemComponent).submenu()) {\n this.checkMegaMenu(item as EclMenuItemComponent);\n }\n item.isHovered.set(true);\n this.closeRootMenuButOneItem(item);\n this.showMenu();\n }\n }\n\n private getRootMenuItemsArray(): readonly EclMenuItemComponent[] {\n return this.eclRootMenuItems();\n }\n\n private checkMegaMenu(menuItem: EclMenuItemComponent): void {\n if (menuItem.isFull()) {\n if (this.isRtl()) {\n menuItem.submenu().offsetRight.set(this.offsetLeft);\n } else {\n menuItem.submenu().offsetLeft.set(this.offsetLeft);\n }\n return;\n }\n this.checkEnoughSpaceForMegaMenu(menuItem);\n }\n\n private checkEnoughSpaceForMegaMenu(menuItem: EclMenuItemComponent): void {\n const megaBounding = menuItem.submenu().el.nativeElement.getBoundingClientRect();\n const containerBounding = this.innerSection().nativeElement.getBoundingClientRect();\n const menuItemBounding = menuItem.el.nativeElement.getBoundingClientRect();\n\n const megaWidth = megaBounding.width;\n const containerWidth = containerBounding.width;\n const menuItemPosition = menuItemBounding.left - containerBounding.left;\n\n if (menuItemPosition + megaWidth > containerWidth) {\n menuItem.submenu().isRtlDisplay.set(true);\n } else {\n menuItem.submenu().isRtlDisplay.set(false);\n }\n }\n\n private onRootMenuItemHoveredOut(evt: EclMenuItemHoverEvent): void {\n if (this.ignoreHover) return;\n if (!this.isMobileBreakpoint()) {\n this.closeRootMenu();\n }\n }\n\n private onRootMenuItemCaret(evt: EclMenuItemCaretEvent): void {\n const requestedRootMenuItem: EclMenuItem = evt.menuItem;\n // Desktop: the caret click is a click-to-open/close affordance (ECL parity with\n // handleClickOnCaret). Toggle the submenu the same way Enter/Space does on desktop,\n // without touching the mobile accordion state or suppressing hover.\n if (!this.isMobileBreakpoint() && !this.isTablet()) {\n if (!requestedRootMenuItem.isHovered()) {\n this.openSubmenu(requestedRootMenuItem);\n } else {\n this.closeRootMenu();\n }\n return;\n }\n\n // Mobile/tablet: suppress hover events briefly after touch/caret interaction\n // (prevents double-trigger on hybrid devices), then toggle the accordion.\n this.ignoreHover = true;\n\n this.closeRootMenuButOneItem(requestedRootMenuItem);\n this.toggleExpandedForMenuItem(requestedRootMenuItem);\n }\n\n private onMenuMegaItemSelected(rootMenuItem: EclMenuItem, evt: EclMenuItemSelectEvent): void {\n const requestedSubmenuItem = evt.menuItem;\n\n this.closeRootMenu();\n this.deselectAllRootMenuItems();\n this.deselectAllSubmenusItems();\n this.selectMenuItem(rootMenuItem);\n this.selectMenuItem(requestedSubmenuItem);\n this.onCloseHamburgerMenu();\n if (this.hasOverflow()) {\n this.managePrevNextButtonsClassOnItemSelected();\n }\n this.sendMenuItemSelectEvent(evt);\n\n this.hideMenu();\n }\n\n private deselectAllRootMenuItems(): void {\n this.deselectMenuItems(this.eclRootMenuItems());\n }\n\n private deselectAllSubmenusItems(): void {\n this.eclRootMenuItems().forEach((rootItem) => {\n this.deselectMenuItems(rootItem.children());\n });\n }\n\n private deselectMenuItems(items: ReadonlyArray<EclMenuItem>): void {\n items.forEach((it) => {\n it.isCurrent.set(false);\n });\n }\n\n private selectMenuItem(item: EclMenuItem): void {\n item.isCurrent.set(true);\n this.currentItem = item;\n }\n\n private hideMenu(): void {\n this.isMenuHidden.set(true);\n }\n\n private showMenu(): void {\n this.isMenuHidden.set(false);\n }\n\n private closeRootMenu(): void {\n this.eclRootMenuItems().forEach((it) => {\n it.isExpanded.set(false);\n it.isHovered.set(false);\n });\n }\n\n private closeRootMenuButOneItem(item: EclMenuItem): void {\n this.eclRootMenuItems()\n .filter((it) => it !== item)\n .forEach((it) => {\n it.isExpanded.set(false);\n it.isHovered.set(false);\n });\n }\n\n private handleRtlChangeState(): void {\n this.eclRtlService.currentRtlState$.pipe(takeUntil(this.destroy$)).subscribe((rtlState) => {\n if (rtlState !== undefined && this.isRtl() !== rtlState) {\n this.isRtl.set(rtlState);\n if (this.hasOverflow()) {\n this.slideMenuInitialState();\n setTimeout(() => this.checkMenuOverflow());\n }\n }\n });\n }\n\n private toggleExpandedForMenuItem(item: EclMenuItem): void {\n item.isExpanded.set(!item.isExpanded());\n }\n\n private onOpenHamburgerMenu(): void {\n this.disableScroll();\n this.isHamburgerMenuOpened.set(true);\n this.openHamburgerMenu.emit();\n this.focusOnFirstItem();\n }\n\n private onCloseHamburgerMenu(): void {\n this.isHamburgerMenuOpened.set(false);\n this.enableScroll();\n this.closeHamburgerMenu.emit();\n }\n\n private disableScroll(): void {\n this.document.body.classList.add('no-scroll');\n }\n\n private enableScroll(): void {\n this.document.body.classList.remove('no-scroll');\n }\n\n private focusOnFirstItem(): void {\n this.eclRootMenuItems()[0]?.onFocusFromPrev();\n }\n\n private sendMenuItemSelectEvent(evt: EclMenuItemSelectEvent): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(evt.menuItem, evt.domEvent));\n }\n\n private observeSwipeGestures(): void {\n // load hammerjs only on browser\n if(isPlatformBrowser(this.platformId)) {\n import('hammerjs').then((Hammer: Hammer) => {\n this.hammertime = new Hammer.default(this.document, { inputClass: Hammer.TouchInput });\n this.hammertime.on('swipeleft', this.onSwipeLeft.bind(this));\n this.hammertime.on('swiperight', this.onSwipeRight.bind(this));\n }).catch((error) => {\n console.error('Error loading HammerJS', error);\n });\n }\n }\n\n private subscribeToAppShellBreakpoints(): void {\n this.euiAppShellService.breakpoints$\n .pipe(takeUntil(this.destroy$))\n .subscribe((bkps: any) => {\n if (!bkps) return;\n\n this.breakpointsValue = bkps;\n this.isMobileBreakpoint.set(!!bkps.isMobile);\n this.isTablet.set(!!bkps.isTablet || !!bkps.isLtLargeTablet);\n this.cdr.detectChanges();\n\n if (!this.isMobileBreakpoint() && !this.isTablet()) {\n this.overlayStyleTop.set(null);\n this.innerStyleTop.set(null);\n\n // Ensure hamburger menu is closed when switching to desktop\n if (this.isHamburgerMenuOpened()) {\n this.onCloseHamburgerMenu();\n this.closeRootMenu();\n }\n }\n });\n }\n}\n",
67029
67029
  "assetsDirs": [],
67030
67030
  "styleUrlsData": "",
67031
67031
  "stylesData": "\n :host {\n display: block;\n }\n \n",
@@ -67108,7 +67108,7 @@
67108
67108
  },
67109
67109
  {
67110
67110
  "name": "EclMenuItemComponent",
67111
- "id": "component-EclMenuItemComponent-2dddaee94d6d368f9ba8cee26c98de87fe654523bc53fdab11a0003fc6f6610dab0d34b6e3cfdf21ba1dc77ff570d9caf5df17d3b94bfea184e4a8cd8c6b596c",
67111
+ "id": "component-EclMenuItemComponent-bf7fb5cc215964bf24b0fc0108e132d0b2cfe8ae25ae194dca2be309928c2ab302b876fe5ee2db45b6bc482dd00bf5131f8bef1e5af8c1a8e727daf51df1743a",
67112
67112
  "file": "packages/ecl/components/ecl-menu/ecl-menu/ecl-menu-item.component.ts",
67113
67113
  "encapsulation": [
67114
67114
  "ViewEncapsulation.None"
@@ -67508,6 +67508,38 @@
67508
67508
  }
67509
67509
  ],
67510
67510
  "propertiesClass": [
67511
+ {
67512
+ "name": "ariaCurrent",
67513
+ "coverageIgnore": false,
67514
+ "defaultValue": "computed<string | null>(() => this.isCurrent() && !this.hasCurrentChild() ? 'page' : null)",
67515
+ "deprecated": false,
67516
+ "deprecationMessage": "",
67517
+ "type": "unknown",
67518
+ "indexKey": "",
67519
+ "optional": false,
67520
+ "description": "<p>&#39;aria-current&#39; is set to &#39;page&#39; only when this item itself is the current page, and not when it is\nmarked current merely because one of its children is the active item (in that case the child link\ncarries &#39;aria-current=&quot;page&quot;&#39;). Derived from live state so it stays correct whether &#39;isCurrent&#39; is set\nat render time or updated on selection.</p>\n",
67521
+ "line": 165,
67522
+ "rawdescription": "\n\n'aria-current' is set to 'page' only when this item itself is the current page, and not when it is\nmarked current merely because one of its children is the active item (in that case the child link\ncarries 'aria-current=\"page\"'). Derived from live state so it stays correct whether 'isCurrent' is set\nat render time or updated on selection.\n",
67523
+ "modifierKind": [
67524
+ 148
67525
+ ]
67526
+ },
67527
+ {
67528
+ "name": "children",
67529
+ "coverageIgnore": false,
67530
+ "defaultValue": "computed<EclMenuItem[]>(() => this.hasChildren() ? this.submenu().eclMenuMegaItems() : [])",
67531
+ "deprecated": false,
67532
+ "deprecationMessage": "",
67533
+ "type": "unknown",
67534
+ "indexKey": "",
67535
+ "optional": false,
67536
+ "description": "",
67537
+ "line": 174,
67538
+ "rawdescription": "\n",
67539
+ "modifierKind": [
67540
+ 148
67541
+ ]
67542
+ },
67511
67543
  {
67512
67544
  "name": "cssClasses",
67513
67545
  "coverageIgnore": false,
@@ -67538,6 +67570,54 @@
67538
67570
  "line": 67,
67539
67571
  "rawdescription": "\n"
67540
67572
  },
67573
+ {
67574
+ "name": "hasChildren",
67575
+ "coverageIgnore": false,
67576
+ "defaultValue": "computed<boolean>(() => !!this.submenu())",
67577
+ "deprecated": false,
67578
+ "deprecationMessage": "",
67579
+ "type": "unknown",
67580
+ "indexKey": "",
67581
+ "optional": false,
67582
+ "description": "",
67583
+ "line": 172,
67584
+ "rawdescription": "\n",
67585
+ "modifierKind": [
67586
+ 148
67587
+ ]
67588
+ },
67589
+ {
67590
+ "name": "hasCurrentChild",
67591
+ "coverageIgnore": false,
67592
+ "defaultValue": "computed<boolean>(() => this.children().some((child) => child.isCurrent()))",
67593
+ "deprecated": false,
67594
+ "deprecationMessage": "",
67595
+ "type": "unknown",
67596
+ "indexKey": "",
67597
+ "optional": false,
67598
+ "description": "<p>Whether any child (sub-item) is the current item.</p>\n",
67599
+ "line": 170,
67600
+ "rawdescription": "\n\nWhether any child (sub-item) is the current item.\n",
67601
+ "modifierKind": [
67602
+ 148
67603
+ ]
67604
+ },
67605
+ {
67606
+ "name": "hasEclChildrenDataAttribute",
67607
+ "coverageIgnore": false,
67608
+ "defaultValue": "computed<boolean | undefined>(() => this.hasChildren() || undefined)",
67609
+ "deprecated": false,
67610
+ "deprecationMessage": "",
67611
+ "type": "unknown",
67612
+ "indexKey": "",
67613
+ "optional": false,
67614
+ "description": "",
67615
+ "line": 155,
67616
+ "rawdescription": "\n",
67617
+ "modifierKind": [
67618
+ 148
67619
+ ]
67620
+ },
67541
67621
  {
67542
67622
  "name": "isCurrent",
67543
67623
  "coverageIgnore": false,
@@ -67554,6 +67634,22 @@
67554
67634
  148
67555
67635
  ]
67556
67636
  },
67637
+ {
67638
+ "name": "isDataExpanded",
67639
+ "coverageIgnore": false,
67640
+ "defaultValue": "computed<boolean | null>(() => this.hasChildren() ? this.isHovered() : null)",
67641
+ "deprecated": false,
67642
+ "deprecationMessage": "",
67643
+ "type": "unknown",
67644
+ "indexKey": "",
67645
+ "optional": false,
67646
+ "description": "",
67647
+ "line": 157,
67648
+ "rawdescription": "\n",
67649
+ "modifierKind": [
67650
+ 148
67651
+ ]
67652
+ },
67557
67653
  {
67558
67654
  "name": "isExpanded",
67559
67655
  "coverageIgnore": false,
@@ -67573,7 +67669,7 @@
67573
67669
  {
67574
67670
  "name": "isFull",
67575
67671
  "coverageIgnore": false,
67576
- "defaultValue": "computed<boolean>(() => this.hasChildren && this.submenu().eclMenuMegaItems().length >= ColTrigger.COL3_TRIGGER)",
67672
+ "defaultValue": "computed<boolean>(() => this.hasChildren() && this.submenu().eclMenuMegaItems().length >= ColTrigger.COL3_TRIGGER)",
67577
67673
  "deprecated": false,
67578
67674
  "deprecationMessage": "",
67579
67675
  "type": "unknown",
@@ -67710,7 +67806,7 @@
67710
67806
  "optional": false,
67711
67807
  "returnType": "void",
67712
67808
  "typeParameters": [],
67713
- "line": 374,
67809
+ "line": 372,
67714
67810
  "deprecated": false,
67715
67811
  "deprecationMessage": "",
67716
67812
  "rawdescription": "\n\nThis method calculates height and width of an item, depending the maximum of lines(rows) allowed.\nDepends on the 'maxLines' property of the parent menu component.\n",
@@ -67718,8 +67814,8 @@
67718
67814
  "jsdoctags": [
67719
67815
  {
67720
67816
  "name": {
67721
- "pos": 13018,
67722
- "end": 13026,
67817
+ "pos": 13616,
67818
+ "end": 13624,
67723
67819
  "kind": 80,
67724
67820
  "id": 0,
67725
67821
  "flags": 16842752,
@@ -67732,8 +67828,8 @@
67732
67828
  "deprecated": false,
67733
67829
  "deprecationMessage": "",
67734
67830
  "tagName": {
67735
- "pos": 13012,
67736
- "end": 13017,
67831
+ "pos": 13610,
67832
+ "end": 13615,
67737
67833
  "kind": 80,
67738
67834
  "id": 0,
67739
67835
  "flags": 16842752,
@@ -67744,8 +67840,8 @@
67744
67840
  },
67745
67841
  {
67746
67842
  "name": {
67747
- "pos": 13105,
67748
- "end": 13123,
67843
+ "pos": 13703,
67844
+ "end": 13721,
67749
67845
  "kind": 80,
67750
67846
  "id": 0,
67751
67847
  "flags": 16842752,
@@ -67758,8 +67854,8 @@
67758
67854
  "deprecated": false,
67759
67855
  "deprecationMessage": "",
67760
67856
  "tagName": {
67761
- "pos": 13099,
67762
- "end": 13104,
67857
+ "pos": 13697,
67858
+ "end": 13702,
67763
67859
  "kind": 80,
67764
67860
  "id": 0,
67765
67861
  "flags": 16842752,
@@ -67770,8 +67866,8 @@
67770
67866
  },
67771
67867
  {
67772
67868
  "tagName": {
67773
- "pos": 13132,
67774
- "end": 13139,
67869
+ "pos": 13730,
67870
+ "end": 13737,
67775
67871
  "kind": 80,
67776
67872
  "id": 0,
67777
67873
  "flags": 16842752,
@@ -67789,7 +67885,7 @@
67789
67885
  "optional": false,
67790
67886
  "returnType": "void",
67791
67887
  "typeParameters": [],
67792
- "line": 188,
67888
+ "line": 176,
67793
67889
  "deprecated": false,
67794
67890
  "deprecationMessage": "",
67795
67891
  "rawdescription": "\n",
@@ -67802,7 +67898,7 @@
67802
67898
  "optional": false,
67803
67899
  "returnType": "void",
67804
67900
  "typeParameters": [],
67805
- "line": 212,
67901
+ "line": 200,
67806
67902
  "deprecated": false,
67807
67903
  "deprecationMessage": "",
67808
67904
  "rawdescription": "\n",
@@ -67824,7 +67920,7 @@
67824
67920
  "optional": false,
67825
67921
  "returnType": "void",
67826
67922
  "typeParameters": [],
67827
- "line": 280,
67923
+ "line": 275,
67828
67924
  "deprecated": false,
67829
67925
  "deprecationMessage": "",
67830
67926
  "rawdescription": "\n\nManages the keyboard navigation when the current element is the button of the menu item.\n",
@@ -67832,8 +67928,8 @@
67832
67928
  "jsdoctags": [
67833
67929
  {
67834
67930
  "name": {
67835
- "pos": 9897,
67836
- "end": 9900,
67931
+ "pos": 10305,
67932
+ "end": 10308,
67837
67933
  "kind": 80,
67838
67934
  "id": 0,
67839
67935
  "flags": 16842752,
@@ -67846,8 +67942,8 @@
67846
67942
  "deprecated": false,
67847
67943
  "deprecationMessage": "",
67848
67944
  "tagName": {
67849
- "pos": 9891,
67850
- "end": 9896,
67945
+ "pos": 10299,
67946
+ "end": 10304,
67851
67947
  "kind": 80,
67852
67948
  "id": 0,
67853
67949
  "flags": 16842752,
@@ -67865,7 +67961,7 @@
67865
67961
  "optional": false,
67866
67962
  "returnType": "void",
67867
67963
  "typeParameters": [],
67868
- "line": 260,
67964
+ "line": 255,
67869
67965
  "deprecated": false,
67870
67966
  "deprecationMessage": "",
67871
67967
  "rawdescription": "\n\nManages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n the next root menu item.\n",
@@ -67878,7 +67974,7 @@
67878
67974
  "optional": false,
67879
67975
  "returnType": "void",
67880
67976
  "typeParameters": [],
67881
- "line": 272,
67977
+ "line": 267,
67882
67978
  "deprecated": false,
67883
67979
  "deprecationMessage": "",
67884
67980
  "rawdescription": "\n\nManages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n the previous root menu item.\n",
@@ -67891,7 +67987,7 @@
67891
67987
  "optional": false,
67892
67988
  "returnType": "void",
67893
67989
  "typeParameters": [],
67894
- "line": 250,
67990
+ "line": 245,
67895
67991
  "deprecated": false,
67896
67992
  "deprecationMessage": "",
67897
67993
  "rawdescription": "\n\nManages the state when the item is focused, when navigating with keyboard and the navigation is coming from the submenu.\n",
@@ -67913,16 +68009,16 @@
67913
68009
  "optional": false,
67914
68010
  "returnType": "void",
67915
68011
  "typeParameters": [],
67916
- "line": 243,
68012
+ "line": 236,
67917
68013
  "deprecated": false,
67918
68014
  "deprecationMessage": "",
67919
- "rawdescription": "\n\nWhen clicking the caret button, to expand item's children. Used in mobile view.\n",
67920
- "description": "<p>When clicking the caret button, to expand item&#39;s children. Used in mobile view.</p>\n",
68015
+ "rawdescription": "\n\nWhen clicking the caret button, to expand/collapse item's children.\nUsed on both desktop (click-to-open/close affordance) and mobile view.\n\nThe caret is a toggle control, not a navigation control: prevent its default\naction and stop propagation so that, when the item also has an href/routerLink,\nclicking the caret toggles the submenu instead of navigating the link.\n",
68016
+ "description": "<p>When clicking the caret button, to expand/collapse item&#39;s children.\nUsed on both desktop (click-to-open/close affordance) and mobile view.</p>\n<p>The caret is a toggle control, not a navigation control: prevent its default\naction and stop propagation so that, when the item also has an href/routerLink,\nclicking the caret toggles the submenu instead of navigating the link.</p>\n",
67921
68017
  "jsdoctags": [
67922
68018
  {
67923
68019
  "name": {
67924
- "pos": 8798,
67925
- "end": 8801,
68020
+ "pos": 9145,
68021
+ "end": 9148,
67926
68022
  "kind": 80,
67927
68023
  "id": 0,
67928
68024
  "flags": 16842752,
@@ -67935,8 +68031,8 @@
67935
68031
  "deprecated": false,
67936
68032
  "deprecationMessage": "",
67937
68033
  "tagName": {
67938
- "pos": 8792,
67939
- "end": 8797,
68034
+ "pos": 9139,
68035
+ "end": 9144,
67940
68036
  "kind": 80,
67941
68037
  "id": 0,
67942
68038
  "flags": 16842752,
@@ -67963,7 +68059,7 @@
67963
68059
  "optional": false,
67964
68060
  "returnType": "void",
67965
68061
  "typeParameters": [],
67966
- "line": 231,
68062
+ "line": 219,
67967
68063
  "deprecated": false,
67968
68064
  "deprecationMessage": "",
67969
68065
  "rawdescription": "\n",
@@ -67998,7 +68094,7 @@
67998
68094
  "optional": false,
67999
68095
  "returnType": "void",
68000
68096
  "typeParameters": [],
68001
- "line": 227,
68097
+ "line": 215,
68002
68098
  "deprecated": false,
68003
68099
  "deprecationMessage": "",
68004
68100
  "rawdescription": "\n",
@@ -68033,7 +68129,7 @@
68033
68129
  "optional": false,
68034
68130
  "returnType": "void",
68035
68131
  "typeParameters": [],
68036
- "line": 217,
68132
+ "line": 205,
68037
68133
  "deprecated": false,
68038
68134
  "deprecationMessage": "",
68039
68135
  "rawdescription": "\n",
@@ -68068,7 +68164,7 @@
68068
68164
  "optional": false,
68069
68165
  "returnType": "void",
68070
68166
  "typeParameters": [],
68071
- "line": 222,
68167
+ "line": 210,
68072
68168
  "deprecated": false,
68073
68169
  "deprecationMessage": "",
68074
68170
  "rawdescription": "\n",
@@ -68103,7 +68199,7 @@
68103
68199
  "optional": false,
68104
68200
  "returnType": "void",
68105
68201
  "typeParameters": [],
68106
- "line": 330,
68202
+ "line": 328,
68107
68203
  "deprecated": false,
68108
68204
  "deprecationMessage": "",
68109
68205
  "rawdescription": "\n\nManages the keyboard navigation when the current element is the <a> tag of the menu item.\n",
@@ -68111,8 +68207,8 @@
68111
68207
  "jsdoctags": [
68112
68208
  {
68113
68209
  "name": {
68114
- "pos": 11645,
68115
- "end": 11648,
68210
+ "pos": 12243,
68211
+ "end": 12246,
68116
68212
  "kind": 80,
68117
68213
  "id": 0,
68118
68214
  "flags": 16842752,
@@ -68125,8 +68221,8 @@
68125
68221
  "deprecated": false,
68126
68222
  "deprecationMessage": "",
68127
68223
  "tagName": {
68128
- "pos": 11639,
68129
- "end": 11644,
68224
+ "pos": 12237,
68225
+ "end": 12242,
68130
68226
  "kind": 80,
68131
68227
  "id": 0,
68132
68228
  "flags": 16842752,
@@ -68205,7 +68301,7 @@
68205
68301
  "description": "<p>This component represents a root menu item. Adds styling and functionality to menu item.</p>\n",
68206
68302
  "rawdescription": "\n\nThis component represents a root menu item. Adds styling and functionality to menu item.\n",
68207
68303
  "type": "component",
68208
- "sourceCode": "import { NgTemplateOutlet } from '@angular/common';\nimport {\n AfterContentInit,\n afterNextRender,\n Component,\n ElementRef,\n forwardRef,\n inject,\n OnDestroy,\n Renderer2,\n ViewEncapsulation,\n DOCUMENT,\n booleanAttribute,\n computed,\n input,\n linkedSignal,\n output,\n signal,\n contentChild,\n viewChild,\n} from '@angular/core';\nimport { outputToObservable } from '@angular/core/rxjs-interop';\nimport { Params, QueryParamsHandling, RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EuiAppShellService } from '@eui/core';\nimport { EclButtonComponent, EUI_ECL_BUTTON } from '@eui/ecl/components/ecl-button';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { ECLBaseDirective, KeyCode } from '@eui/ecl/core';\nimport { Subject } from 'rxjs';\nimport { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';\nimport { EclMenuMegaComponent } from '../ecl-menu-mega';\nimport {\n EclMenuItemCaretEvent,\n EclMenuItemFocusEvent,\n EclMenuItemHoverEvent,\n EclMenuItemHoverOutEvent,\n EclMenuItemKeydownEvent,\n EclMenuItemSelectEvent,\n} from '../events';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\n\nconst enum ColTrigger {\n COL1_TRIGGER = 5,\n COL2_TRIGGER = 9,\n COL3_TRIGGER = 13,\n}\n\n/**\n * This component represents a root menu item. Adds styling and functionality to menu item.\n */\n@Component({\n selector: 'ecl-menu-item',\n templateUrl: './ecl-menu-item.component.html',\n imports: [NgTemplateOutlet, RouterLink, RouterLinkWithHref, ...EUI_ECL_BUTTON, ...EUI_ECL_ICON, ...EUI_ECL_LINK],\n encapsulation: ViewEncapsulation.None,\n host: {\n role: 'listitem',\n '[class]': 'cssClasses()',\n '[class.ecl-menu__item--has-children]': 'hasEclChildrenDataAttribute',\n '[attr.aria-haspopup]': 'hasEclChildrenDataAttribute',\n '[attr.data-expanded]': 'isDataExpanded',\n '(mouseenter)': 'onItemMouseEnter($event)',\n '(mouseleave)': 'onItemMouseLeave($event)',\n },\n})\nexport class EclMenuItemComponent extends ECLBaseDirective implements EclMenuItem, AfterContentInit, OnDestroy {\n el = inject(ElementRef);\n\n readonly id = input<string>(undefined);\n readonly label = input<string>(undefined);\n /**\n * When in mobile view, use this property to set aria-label for the toggle items button (to expand item's children).\n */\n readonly toggleAriaLabel = input<string>(undefined);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly routerLink = input<string | any[]>(null);\n readonly queryParams = input<Params | null>(null);\n readonly queryParamsHandling = input<QueryParamsHandling>(null);\n readonly href = input('');\n readonly target = input('');\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n /**\n * Custom attributes, to be added to the <a> tag of the menu item.\n */\n readonly linkExtraAttributes = input<Record<string, string>>(undefined);\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemCaret = output<EclMenuItemCaretEvent>();\n /**\n * Event that is emited on clicking the caret button to expand item's children in mobile mode.\n */\n readonly menuItemHover = output<EclMenuItemHoverEvent>();\n readonly menuItemHoverOut = output<EclMenuItemHoverOutEvent>();\n readonly menuMegaItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemFocus = output<EclMenuItemFocusEvent>();\n readonly menuItemKeydown = output<EclMenuItemKeydownEvent>();\n /**\n * This event is emited when the keyboard nav reaches the last item in mobile view.\n * Then the next focused element should be the 'close' btn.\n */\n readonly menuMegaLastItemTab = output<void>();\n\n readonly isExpanded = signal(false);\n readonly isHovered = signal(false);\n /**\n * Used when menu has overlay, to show if the item is currently displayed.\n */\n readonly isVisible = signal(true);\n readonly isMobileBreakpoint = signal(false);\n\n readonly submenu = contentChild(forwardRef(() => EclMenuMegaComponent));\n\n /**\n * Set to 'true', when item has more than 13 chidren and the overlay needs to use the full available space.\n */\n readonly isFull = computed<boolean>(() => this.hasChildren && this.submenu().eclMenuMegaItems().length >= ColTrigger.COL3_TRIGGER);\n\n /**\n * The default role of the item is 'listitem' and should not be changed.\n */\n protected readonly cssClasses = computed<string>(() =>\n [\n super.getCssClasses('ecl-menu__item'),\n this.isCurrent() ? 'ecl-menu__item--current' : '',\n this.isExpanded() ? 'ecl-menu__item--expanded' : '',\n this.getClassDependingChildrenCnt(),\n ]\n .join(' ')\n .trim());\n\n /**\n * The <a> element of the menu item, when there are no children.\n */\n protected readonly menuLink = viewChild<ElementRef<HTMLAnchorElement>>('menuLink');\n /**\n * The <button> element of the menu item, when item has children.\n */\n protected readonly menuButton = viewChild(EclButtonComponent);\n\n private readonly doc = inject(DOCUMENT);\n private readonly window = this.doc.defaultView; // Window | null\n private destroy$: Subject<boolean> = new Subject<boolean>();\n private renderer = inject(Renderer2);\n private appShell = inject(EuiAppShellService);\n\n constructor() {\n super();\n afterNextRender(() => {\n this.observeBreakpointChanges();\n this.setExtraAttributesForLink(this.menuLink()?.nativeElement, this.linkExtraAttributes());\n });\n }\n\n get hasEclChildrenDataAttribute(): boolean {\n return this.hasChildren || undefined;\n }\n\n get isDataExpanded(): boolean {\n return this.hasChildren ? this.isHovered() : null;\n }\n\n /**\n * 'aria-current' is set to 'page' only when this item itself is the current page, and not when it is\n * marked current merely because one of its children is the active item (in that case the child link\n * carries 'aria-current=\"page\"'). Derived from live state so it stays correct whether 'isCurrent' is set\n * at render time or updated on selection.\n */\n get ariaCurrent(): string | null {\n return this.isCurrent() && !this.hasCurrentChild ? 'page' : null;\n }\n\n /**\n * Whether any child (sub-item) is the current item.\n */\n get hasCurrentChild(): boolean {\n return this.children.some((child) => child.isCurrent());\n }\n\n get hasChildren(): boolean {\n return !!this.submenu();\n }\n\n get children(): EclMenuItem[] {\n return this.hasChildren ? this.submenu().eclMenuMegaItems() : [];\n }\n\n ngAfterContentInit(): void {\n // Subscibe to submenu component events.\n const submenu = this.submenu();\n if (submenu) {\n outputToObservable(submenu.menuItemSelect).pipe(takeUntil(this.destroy$)).subscribe((it: EclMenuItemSelectEvent) => {\n this.menuMegaItemSelect.emit(new EclMenuItemSelectEvent(it.menuItem, it.domEvent));\n });\n outputToObservable(submenu.menuMegaItemParentFocus).pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (!this.isMobileBreakpoint()) {\n this.onFocusFromSubmenu();\n }\n });\n outputToObservable(submenu.menuMegaLastItemTab).pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (this.isMobileBreakpoint()) {\n this.menuMegaLastItemTab.emit();\n }\n });\n\n if (this.hasSubmenuAnySelections()) {\n this.isCurrent.set(true);\n }\n }\n }\n\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n }\n\n onItemMouseEnter(evt: MouseEvent): void {\n if (this.isMobileBreakpoint()) return;\n this.menuItemHover.emit(new EclMenuItemHoverEvent(this, evt));\n }\n\n onItemMouseLeave(evt: MouseEvent): void {\n if (this.isMobileBreakpoint()) return;\n this.menuItemHoverOut.emit(new EclMenuItemHoverOutEvent(this, evt));\n }\n\n onItemFocus(evt: FocusEvent): void {\n this.menuItemFocus.emit(new EclMenuItemFocusEvent(this, evt));\n }\n\n onItemClick(evt: Event): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(this, evt));\n\n if (!this.href()) {\n evt.stopPropagation();\n }\n }\n\n /**\n * When clicking the caret button, to expand item's children. Used in mobile view.\n * @param evt\n */\n onItemCaretClick(evt: MouseEvent): void {\n this.menuItemCaret.emit(new EclMenuItemCaretEvent(this, evt));\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from the submenu.\n */\n onFocusFromSubmenu(): void {\n this.onButtonFocus();\n this.isHovered.set(false);\n this.isExpanded.set(false);\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n * the next root menu item.\n */\n onFocusFromNext(): void {\n if (this.menuButton()) {\n this.onButtonFocus();\n } else {\n this.onLinkFocus();\n }\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n * the previous root menu item.\n */\n onFocusFromPrev(): void {\n this.onLinkFocus();\n }\n\n /**\n * Manages the keyboard navigation when the current element is the button of the menu item.\n * @param evt\n */\n onButtonKeydown(evt: Event): void {\n switch ((evt as KeyboardEvent).key) {\n case KeyCode.ARROW_DOWN: {\n evt.preventDefault();\n if (this.isHovered()) {\n this.selectFirstMegaMenuItem();\n } else {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n break;\n }\n case KeyCode.ARROW_UP: {\n evt.preventDefault();\n this.onLinkFocus();\n break;\n }\n case KeyCode.ARROW_RIGHT: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ARROW_LEFT: {\n evt.preventDefault();\n this.onLinkFocus();\n break;\n }\n case KeyCode.TAB: {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ENTER: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ESCAPE: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n default: {\n break;\n }\n }\n }\n\n /**\n * Manages the keyboard navigation when the current element is the <a> tag of the menu item.\n * @param evt\n */\n onLinkKeydown(evt: Event): void {\n switch ((evt as KeyboardEvent).key) {\n case KeyCode.ARROW_DOWN: {\n evt.preventDefault();\n this.onLinkNextFocus(evt);\n break;\n }\n case KeyCode.ARROW_UP: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ARROW_RIGHT: {\n evt.preventDefault();\n this.onLinkNextFocus(evt);\n break;\n }\n case KeyCode.ARROW_LEFT: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ENTER: {\n this.onItemClick(evt);\n break;\n }\n case KeyCode.ESCAPE: {\n evt.preventDefault();\n this.onLinkEscape(evt);\n break;\n }\n default: {\n break;\n }\n }\n }\n\n /**\n * This method calculates height and width of an item, depending the maximum of lines(rows) allowed.\n * Depends on the 'maxLines' property of the parent menu component.\n * @param maxLines - property that sets the maximum possible lines in a menu item.\n * @param isMobileBreakpoint\n * @returns\n */\n checkMenuItem(maxLines: number, isMobileBreakpoint: boolean): void {\n if (maxLines < 1) return;\n\n const currentItem = this.el.nativeElement;\n const currentLink = this.menuLink().nativeElement;\n if (isMobileBreakpoint) {\n this.renderer.setStyle(currentLink, 'width', '100%');\n return;\n }\n this.renderer.setStyle(currentItem, 'alignItems', 'flex-start');\n const linkStyle = this.window.getComputedStyle(currentLink);\n\n const maxHeight = this.getMenuItemLinkMaxHeight(linkStyle, maxLines);\n\n if (currentLink.offsetHeight > maxHeight) {\n this.changeMenuItemLinkWidth(currentLink, maxLines, linkStyle);\n }\n this.renderer.removeStyle(currentItem, 'alignItems');\n }\n\n private onLinkEscape(evt: Event): void {\n const menuButton = this.menuButton();\n if (menuButton) {\n menuButton.el.nativeElement.focus();\n evt.stopPropagation();\n } else {\n this.menuLink().nativeElement.blur();\n }\n }\n\n private selectFirstMegaMenuItem(): void {\n this.submenu().selectFirstSubItem();\n }\n\n private onLinkFocus(): void {\n this.menuLink().nativeElement.focus();\n }\n\n private onLinkNextFocus(evt: Event): void {\n if (this.menuButton()) {\n this.onButtonFocus();\n } else {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n }\n\n private onButtonFocus(): void {\n this.menuButton().el.nativeElement.focus();\n }\n\n private getMenuItemLinkMaxHeight(linkStyle: CSSStyleDeclaration, maxLines: number): number {\n return parseInt(linkStyle.lineHeight, 10) * maxLines + parseInt(linkStyle.paddingTop, 10) + parseInt(linkStyle.paddingBottom, 10);\n }\n\n /**\n * Calculates width of a menu item, depending on maximum lines of text allowed.\n * @param currentLink\n * @param maxLines\n * @param linkStyle\n * @returns\n */\n private changeMenuItemLinkWidth(currentLink: HTMLAnchorElement, maxLines: number, linkStyle: CSSStyleDeclaration): void {\n this.renderer.setStyle(currentLink, 'white-space', 'nowrap');\n const linkWidth = currentLink.offsetWidth;\n if (maxLines === 1) {\n this.renderer.setStyle(currentLink, 'width', `${linkWidth + parseInt(linkStyle.paddingLeft, 10) + parseInt(linkStyle.paddingRight, 10)}px`);\n this.renderer.removeStyle(currentLink, 'white-space');\n return;\n }\n const textSize = currentLink.text.length;\n const letterWidth = Math.ceil(linkWidth / textSize);\n const wordsArray = currentLink.text.trim().split(' ');\n const goalWidth = Math.ceil(linkWidth / maxLines);\n const firstWord = wordsArray[0];\n let cnt = 1;\n let wordWidth = Math.ceil((firstWord.length + 1) * letterWidth);\n while (wordWidth < goalWidth) {\n wordWidth += Math.ceil((wordsArray[cnt].length + 1) * letterWidth);\n cnt++;\n }\n this.renderer.setStyle(currentLink, 'width', `${wordWidth + parseInt(linkStyle.paddingLeft, 10) + parseInt(linkStyle.paddingRight, 10)}px`);\n this.renderer.removeStyle(currentLink, 'white-space');\n }\n\n private getClassDependingChildrenCnt(): string {\n if (this.hasChildren) {\n if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL1_TRIGGER) {\n return 'ecl-menu__item--col1';\n } else if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL2_TRIGGER) {\n return 'ecl-menu__item--col2';\n } else if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL3_TRIGGER) {\n return 'ecl-menu__item--col3';\n } else {\n return 'ecl-menu__item--full';\n }\n }\n return '';\n }\n\n private hasSubmenuAnySelections(): boolean {\n const submenu = this.submenu();\n return submenu && !!submenu.eclMenuMegaItems().find((it) => it.isCurrent());\n }\n\n private setExtraAttributesForLink(link: HTMLElement, linkExtraAttributes: Record<string, string>): void {\n if (linkExtraAttributes) {\n Object.keys(linkExtraAttributes).forEach((key) => {\n if (key !== 'href' && key !== 'routerLink') {\n this.renderer.setAttribute(link, key, linkExtraAttributes[key]);\n }\n });\n }\n }\n\n private observeBreakpointChanges(): void {\n this.appShell.getState<any>('breakpoints')\n .pipe(\n map(bp => !!(bp?.isMobile || bp?.isTablet || bp?.isLtLargeTablet)), // xs/sm/md/lg (< 1140px)\n distinctUntilChanged(),\n takeUntil(this.destroy$),\n )\n .subscribe((isMobile) => {\n this.isMobileBreakpoint.set(isMobile);\n });\n }\n}\n",
68304
+ "sourceCode": "import { NgTemplateOutlet } from '@angular/common';\nimport {\n AfterContentInit,\n afterNextRender,\n Component,\n ElementRef,\n forwardRef,\n inject,\n OnDestroy,\n Renderer2,\n ViewEncapsulation,\n DOCUMENT,\n booleanAttribute,\n computed,\n input,\n linkedSignal,\n output,\n signal,\n contentChild,\n viewChild,\n} from '@angular/core';\nimport { outputToObservable } from '@angular/core/rxjs-interop';\nimport { Params, QueryParamsHandling, RouterLink, RouterLinkWithHref } from '@angular/router';\nimport { EuiAppShellService } from '@eui/core';\nimport { EclButtonComponent, EUI_ECL_BUTTON } from '@eui/ecl/components/ecl-button';\nimport { EUI_ECL_ICON } from '@eui/ecl/components/ecl-icon';\nimport { EUI_ECL_LINK } from '@eui/ecl/components/ecl-link';\nimport { ECLBaseDirective, KeyCode } from '@eui/ecl/core';\nimport { Subject } from 'rxjs';\nimport { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';\nimport { EclMenuMegaComponent } from '../ecl-menu-mega';\nimport {\n EclMenuItemCaretEvent,\n EclMenuItemFocusEvent,\n EclMenuItemHoverEvent,\n EclMenuItemHoverOutEvent,\n EclMenuItemKeydownEvent,\n EclMenuItemSelectEvent,\n} from '../events';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\n\nconst enum ColTrigger {\n COL1_TRIGGER = 5,\n COL2_TRIGGER = 9,\n COL3_TRIGGER = 13,\n}\n\n/**\n * This component represents a root menu item. Adds styling and functionality to menu item.\n */\n@Component({\n selector: 'ecl-menu-item',\n templateUrl: './ecl-menu-item.component.html',\n imports: [NgTemplateOutlet, RouterLink, RouterLinkWithHref, ...EUI_ECL_BUTTON, ...EUI_ECL_ICON, ...EUI_ECL_LINK],\n encapsulation: ViewEncapsulation.None,\n host: {\n role: 'listitem',\n '[class]': 'cssClasses()',\n '[class.ecl-menu__item--has-children]': 'hasEclChildrenDataAttribute()',\n '[attr.aria-haspopup]': 'hasEclChildrenDataAttribute()',\n '[attr.data-expanded]': 'isDataExpanded()',\n '(mouseenter)': 'onItemMouseEnter($event)',\n '(mouseleave)': 'onItemMouseLeave($event)',\n },\n})\nexport class EclMenuItemComponent extends ECLBaseDirective implements EclMenuItem, AfterContentInit, OnDestroy {\n el = inject(ElementRef);\n\n readonly id = input<string>(undefined);\n readonly label = input<string>(undefined);\n /**\n * When in mobile view, use this property to set aria-label for the toggle items button (to expand item's children).\n */\n readonly toggleAriaLabel = input<string>(undefined);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly routerLink = input<string | any[]>(null);\n readonly queryParams = input<Params | null>(null);\n readonly queryParamsHandling = input<QueryParamsHandling>(null);\n readonly href = input('');\n readonly target = input('');\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n /**\n * Custom attributes, to be added to the <a> tag of the menu item.\n */\n readonly linkExtraAttributes = input<Record<string, string>>(undefined);\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemCaret = output<EclMenuItemCaretEvent>();\n /**\n * Event that is emited on clicking the caret button to expand item's children in mobile mode.\n */\n readonly menuItemHover = output<EclMenuItemHoverEvent>();\n readonly menuItemHoverOut = output<EclMenuItemHoverOutEvent>();\n readonly menuMegaItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemFocus = output<EclMenuItemFocusEvent>();\n readonly menuItemKeydown = output<EclMenuItemKeydownEvent>();\n /**\n * This event is emited when the keyboard nav reaches the last item in mobile view.\n * Then the next focused element should be the 'close' btn.\n */\n readonly menuMegaLastItemTab = output<void>();\n\n readonly isExpanded = signal(false);\n readonly isHovered = signal(false);\n /**\n * Used when menu has overlay, to show if the item is currently displayed.\n */\n readonly isVisible = signal(true);\n readonly isMobileBreakpoint = signal(false);\n\n readonly submenu = contentChild(forwardRef(() => EclMenuMegaComponent));\n\n /**\n * Set to 'true', when item has more than 13 chidren and the overlay needs to use the full available space.\n */\n readonly isFull = computed<boolean>(() => this.hasChildren() && this.submenu().eclMenuMegaItems().length >= ColTrigger.COL3_TRIGGER);\n\n /**\n * The default role of the item is 'listitem' and should not be changed.\n */\n protected readonly cssClasses = computed<string>(() =>\n [\n super.getCssClasses('ecl-menu__item'),\n this.isCurrent() ? 'ecl-menu__item--current' : '',\n this.isExpanded() ? 'ecl-menu__item--expanded' : '',\n this.getClassDependingChildrenCnt(),\n ]\n .join(' ')\n .trim());\n\n /**\n * The <a> element of the menu item, when there are no children.\n */\n protected readonly menuLink = viewChild<ElementRef<HTMLAnchorElement>>('menuLink');\n /**\n * The <button> element of the menu item, when item has children.\n */\n protected readonly menuButton = viewChild(EclButtonComponent);\n\n private readonly doc = inject(DOCUMENT);\n private readonly window = this.doc.defaultView; // Window | null\n private destroy$: Subject<boolean> = new Subject<boolean>();\n private renderer = inject(Renderer2);\n private appShell = inject(EuiAppShellService);\n\n constructor() {\n super();\n afterNextRender(() => {\n this.observeBreakpointChanges();\n this.setExtraAttributesForLink(this.menuLink()?.nativeElement, this.linkExtraAttributes());\n });\n }\n\n readonly hasEclChildrenDataAttribute = computed<boolean | undefined>(() => this.hasChildren() || undefined);\n\n readonly isDataExpanded = computed<boolean | null>(() => this.hasChildren() ? this.isHovered() : null);\n\n /**\n * 'aria-current' is set to 'page' only when this item itself is the current page, and not when it is\n * marked current merely because one of its children is the active item (in that case the child link\n * carries 'aria-current=\"page\"'). Derived from live state so it stays correct whether 'isCurrent' is set\n * at render time or updated on selection.\n */\n readonly ariaCurrent = computed<string | null>(() => this.isCurrent() && !this.hasCurrentChild() ? 'page' : null);\n\n /**\n * Whether any child (sub-item) is the current item.\n */\n readonly hasCurrentChild = computed<boolean>(() => this.children().some((child) => child.isCurrent()));\n\n readonly hasChildren = computed<boolean>(() => !!this.submenu());\n\n readonly children = computed<EclMenuItem[]>(() => this.hasChildren() ? this.submenu().eclMenuMegaItems() : []);\n\n ngAfterContentInit(): void {\n // Subscibe to submenu component events.\n const submenu = this.submenu();\n if (submenu) {\n outputToObservable(submenu.menuItemSelect).pipe(takeUntil(this.destroy$)).subscribe((it: EclMenuItemSelectEvent) => {\n this.menuMegaItemSelect.emit(new EclMenuItemSelectEvent(it.menuItem, it.domEvent));\n });\n outputToObservable(submenu.menuMegaItemParentFocus).pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (!this.isMobileBreakpoint()) {\n this.onFocusFromSubmenu();\n }\n });\n outputToObservable(submenu.menuMegaLastItemTab).pipe(takeUntil(this.destroy$)).subscribe(() => {\n if (this.isMobileBreakpoint()) {\n this.menuMegaLastItemTab.emit();\n }\n });\n\n if (this.hasSubmenuAnySelections()) {\n this.isCurrent.set(true);\n }\n }\n }\n\n ngOnDestroy(): void {\n this.destroy$.next(true);\n this.destroy$.unsubscribe();\n }\n\n onItemMouseEnter(evt: MouseEvent): void {\n if (this.isMobileBreakpoint()) return;\n this.menuItemHover.emit(new EclMenuItemHoverEvent(this, evt));\n }\n\n onItemMouseLeave(evt: MouseEvent): void {\n if (this.isMobileBreakpoint()) return;\n this.menuItemHoverOut.emit(new EclMenuItemHoverOutEvent(this, evt));\n }\n\n onItemFocus(evt: FocusEvent): void {\n this.menuItemFocus.emit(new EclMenuItemFocusEvent(this, evt));\n }\n\n onItemClick(evt: Event): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(this, evt));\n\n if (!this.href()) {\n evt.stopPropagation();\n }\n }\n\n /**\n * When clicking the caret button, to expand/collapse item's children.\n * Used on both desktop (click-to-open/close affordance) and mobile view.\n *\n * The caret is a toggle control, not a navigation control: prevent its default\n * action and stop propagation so that, when the item also has an href/routerLink,\n * clicking the caret toggles the submenu instead of navigating the link.\n * @param evt\n */\n onItemCaretClick(evt: MouseEvent): void {\n evt.preventDefault();\n evt.stopPropagation();\n this.menuItemCaret.emit(new EclMenuItemCaretEvent(this, evt));\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from the submenu.\n */\n onFocusFromSubmenu(): void {\n this.onButtonFocus();\n this.isHovered.set(false);\n this.isExpanded.set(false);\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n * the next root menu item.\n */\n onFocusFromNext(): void {\n if (this.menuButton()) {\n this.onButtonFocus();\n } else {\n this.onLinkFocus();\n }\n }\n\n /**\n * Manages the state when the item is focused, when navigating with keyboard and the navigation is coming from\n * the previous root menu item.\n */\n onFocusFromPrev(): void {\n this.onLinkFocus();\n }\n\n /**\n * Manages the keyboard navigation when the current element is the button of the menu item.\n * @param evt\n */\n onButtonKeydown(evt: Event): void {\n switch ((evt as KeyboardEvent).key) {\n case KeyCode.ARROW_DOWN: {\n evt.preventDefault();\n if (this.isHovered()) {\n this.selectFirstMegaMenuItem();\n } else {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n break;\n }\n case KeyCode.ARROW_UP: {\n evt.preventDefault();\n this.onLinkFocus();\n break;\n }\n case KeyCode.ARROW_RIGHT: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ARROW_LEFT: {\n evt.preventDefault();\n this.onLinkFocus();\n break;\n }\n case KeyCode.TAB: {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ENTER:\n case KeyCode.SPACE: {\n // Space must behave like Enter: activate the caret/submenu toggle.\n // preventDefault stops the page from scrolling on Space.\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ESCAPE: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n default: {\n break;\n }\n }\n }\n\n /**\n * Manages the keyboard navigation when the current element is the <a> tag of the menu item.\n * @param evt\n */\n onLinkKeydown(evt: Event): void {\n switch ((evt as KeyboardEvent).key) {\n case KeyCode.ARROW_DOWN: {\n evt.preventDefault();\n this.onLinkNextFocus(evt);\n break;\n }\n case KeyCode.ARROW_UP: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ARROW_RIGHT: {\n evt.preventDefault();\n this.onLinkNextFocus(evt);\n break;\n }\n case KeyCode.ARROW_LEFT: {\n evt.preventDefault();\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n break;\n }\n case KeyCode.ENTER: {\n this.onItemClick(evt);\n break;\n }\n case KeyCode.ESCAPE: {\n evt.preventDefault();\n this.onLinkEscape(evt);\n break;\n }\n default: {\n break;\n }\n }\n }\n\n /**\n * This method calculates height and width of an item, depending the maximum of lines(rows) allowed.\n * Depends on the 'maxLines' property of the parent menu component.\n * @param maxLines - property that sets the maximum possible lines in a menu item.\n * @param isMobileBreakpoint\n * @returns\n */\n checkMenuItem(maxLines: number, isMobileBreakpoint: boolean): void {\n if (maxLines < 1) return;\n\n const currentItem = this.el.nativeElement;\n const currentLink = this.menuLink().nativeElement;\n if (isMobileBreakpoint) {\n this.renderer.setStyle(currentLink, 'width', '100%');\n return;\n }\n this.renderer.setStyle(currentItem, 'alignItems', 'flex-start');\n const linkStyle = this.window.getComputedStyle(currentLink);\n\n const maxHeight = this.getMenuItemLinkMaxHeight(linkStyle, maxLines);\n\n if (currentLink.offsetHeight > maxHeight) {\n this.changeMenuItemLinkWidth(currentLink, maxLines, linkStyle);\n }\n this.renderer.removeStyle(currentItem, 'alignItems');\n }\n\n private onLinkEscape(evt: Event): void {\n const menuButton = this.menuButton();\n if (menuButton) {\n menuButton.el.nativeElement.focus();\n evt.stopPropagation();\n } else {\n this.menuLink().nativeElement.blur();\n }\n }\n\n private selectFirstMegaMenuItem(): void {\n this.submenu().selectFirstSubItem();\n }\n\n private onLinkFocus(): void {\n this.menuLink().nativeElement.focus();\n }\n\n private onLinkNextFocus(evt: Event): void {\n if (this.menuButton()) {\n this.onButtonFocus();\n } else {\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n }\n\n private onButtonFocus(): void {\n this.menuButton().el.nativeElement.focus();\n }\n\n private getMenuItemLinkMaxHeight(linkStyle: CSSStyleDeclaration, maxLines: number): number {\n return parseInt(linkStyle.lineHeight, 10) * maxLines + parseInt(linkStyle.paddingTop, 10) + parseInt(linkStyle.paddingBottom, 10);\n }\n\n /**\n * Calculates width of a menu item, depending on maximum lines of text allowed.\n * @param currentLink\n * @param maxLines\n * @param linkStyle\n * @returns\n */\n private changeMenuItemLinkWidth(currentLink: HTMLAnchorElement, maxLines: number, linkStyle: CSSStyleDeclaration): void {\n this.renderer.setStyle(currentLink, 'white-space', 'nowrap');\n const linkWidth = currentLink.offsetWidth;\n if (maxLines === 1) {\n this.renderer.setStyle(currentLink, 'width', `${linkWidth + parseInt(linkStyle.paddingLeft, 10) + parseInt(linkStyle.paddingRight, 10)}px`);\n this.renderer.removeStyle(currentLink, 'white-space');\n return;\n }\n const textSize = currentLink.text.length;\n const letterWidth = Math.ceil(linkWidth / textSize);\n const wordsArray = currentLink.text.trim().split(' ');\n const goalWidth = Math.ceil(linkWidth / maxLines);\n const firstWord = wordsArray[0];\n let cnt = 1;\n let wordWidth = Math.ceil((firstWord.length + 1) * letterWidth);\n while (wordWidth < goalWidth) {\n wordWidth += Math.ceil((wordsArray[cnt].length + 1) * letterWidth);\n cnt++;\n }\n this.renderer.setStyle(currentLink, 'width', `${wordWidth + parseInt(linkStyle.paddingLeft, 10) + parseInt(linkStyle.paddingRight, 10)}px`);\n this.renderer.removeStyle(currentLink, 'white-space');\n }\n\n private getClassDependingChildrenCnt(): string {\n if (this.hasChildren()) {\n if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL1_TRIGGER) {\n return 'ecl-menu__item--col1';\n } else if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL2_TRIGGER) {\n return 'ecl-menu__item--col2';\n } else if (this.submenu().eclMenuMegaItems().length < ColTrigger.COL3_TRIGGER) {\n return 'ecl-menu__item--col3';\n } else {\n return 'ecl-menu__item--full';\n }\n }\n return '';\n }\n\n private hasSubmenuAnySelections(): boolean {\n const submenu = this.submenu();\n return submenu && !!submenu.eclMenuMegaItems().find((it) => it.isCurrent());\n }\n\n private setExtraAttributesForLink(link: HTMLElement, linkExtraAttributes: Record<string, string>): void {\n if (linkExtraAttributes) {\n Object.keys(linkExtraAttributes).forEach((key) => {\n if (key !== 'href' && key !== 'routerLink') {\n this.renderer.setAttribute(link, key, linkExtraAttributes[key]);\n }\n });\n }\n }\n\n private observeBreakpointChanges(): void {\n this.appShell.getState<any>('breakpoints')\n .pipe(\n map(bp => !!(bp?.isMobile || bp?.isTablet || bp?.isLtLargeTablet)), // xs/sm/md/lg (< 1140px)\n distinctUntilChanged(),\n takeUntil(this.destroy$),\n )\n .subscribe((isMobile) => {\n this.isMobileBreakpoint.set(isMobile);\n });\n }\n}\n",
68209
68305
  "assetsDirs": [],
68210
68306
  "styleUrlsData": "",
68211
68307
  "stylesData": "",
@@ -68238,74 +68334,6 @@
68238
68334
  "AfterContentInit",
68239
68335
  "OnDestroy"
68240
68336
  ],
68241
- "accessors": {
68242
- "hasEclChildrenDataAttribute": {
68243
- "name": "hasEclChildrenDataAttribute",
68244
- "getSignature": {
68245
- "name": "hasEclChildrenDataAttribute",
68246
- "type": "boolean",
68247
- "returnType": "boolean",
68248
- "line": 155,
68249
- "rawdescription": "\n",
68250
- "description": ""
68251
- }
68252
- },
68253
- "isDataExpanded": {
68254
- "name": "isDataExpanded",
68255
- "getSignature": {
68256
- "name": "isDataExpanded",
68257
- "type": "boolean",
68258
- "returnType": "boolean",
68259
- "line": 159,
68260
- "rawdescription": "\n",
68261
- "description": ""
68262
- }
68263
- },
68264
- "ariaCurrent": {
68265
- "name": "ariaCurrent",
68266
- "getSignature": {
68267
- "name": "ariaCurrent",
68268
- "type": "unknown",
68269
- "returnType": "string | null",
68270
- "line": 169,
68271
- "rawdescription": "\n\n'aria-current' is set to 'page' only when this item itself is the current page, and not when it is\nmarked current merely because one of its children is the active item (in that case the child link\ncarries 'aria-current=\"page\"'). Derived from live state so it stays correct whether 'isCurrent' is set\nat render time or updated on selection.\n",
68272
- "description": "<p>&#39;aria-current&#39; is set to &#39;page&#39; only when this item itself is the current page, and not when it is\nmarked current merely because one of its children is the active item (in that case the child link\ncarries &#39;aria-current=&quot;page&quot;&#39;). Derived from live state so it stays correct whether &#39;isCurrent&#39; is set\nat render time or updated on selection.</p>\n"
68273
- }
68274
- },
68275
- "hasCurrentChild": {
68276
- "name": "hasCurrentChild",
68277
- "getSignature": {
68278
- "name": "hasCurrentChild",
68279
- "type": "boolean",
68280
- "returnType": "boolean",
68281
- "line": 176,
68282
- "rawdescription": "\n\nWhether any child (sub-item) is the current item.\n",
68283
- "description": "<p>Whether any child (sub-item) is the current item.</p>\n"
68284
- }
68285
- },
68286
- "hasChildren": {
68287
- "name": "hasChildren",
68288
- "getSignature": {
68289
- "name": "hasChildren",
68290
- "type": "boolean",
68291
- "returnType": "boolean",
68292
- "line": 180,
68293
- "rawdescription": "\n",
68294
- "description": ""
68295
- }
68296
- },
68297
- "children": {
68298
- "name": "children",
68299
- "getSignature": {
68300
- "name": "children",
68301
- "type": "[]",
68302
- "returnType": "EclMenuItem[]",
68303
- "line": 184,
68304
- "rawdescription": "\n",
68305
- "description": ""
68306
- }
68307
- }
68308
- },
68309
68337
  "relationships": {
68310
68338
  "incoming": [],
68311
68339
  "outgoing": [
@@ -68351,7 +68379,7 @@
68351
68379
  }
68352
68380
  ]
68353
68381
  },
68354
- "templateData": "@if (href()) {\n <a eclLink\n #menuLink\n variant=\"standalone\"\n [href]=\"href()\"\n [target]=\"target()\"\n class=\"ecl-menu__link\"\n [class.ecl-menu__link--current]=\"isCurrent()\"\n [attr.aria-current]=\"ariaCurrent\"\n (focus)=\"onItemFocus($event)\"\n (click)=\"onItemClick($event)\"\n (keydown)=\"onLinkKeydown($event)\">\n {{ label() }}\n <ng-container *ngTemplateOutlet=\"content\"></ng-container>\n </a>\n} @else {\n <a eclLink\n #menuLink\n variant=\"standalone\"\n [attr.tabindex]=\"tabindex || 0\"\n [routerLink]=\"routerLink()\"\n [queryParams]=\"queryParams()\"\n [queryParamsHandling]=\"queryParamsHandling()\"\n class=\"ecl-menu__link\"\n [class.ecl-menu__link--current]=\"isCurrent()\"\n [attr.aria-current]=\"ariaCurrent\"\n (focus)=\"onItemFocus($event)\"\n (click)=\"onItemClick($event)\"\n (keydown)=\"onLinkKeydown($event)\">\n {{ label() }}\n <ng-container *ngTemplateOutlet=\"content\"></ng-container>\n </a>\n}\n\n@if (hasChildren) {\n @if (isMobileBreakpoint()) {\n <button\n eclButton\n variant=\"tertiary\"\n class=\"ecl-menu__button-caret ecl-button--neutral\"\n type=\"button\"\n [attr.aria-label]=\"toggleAriaLabel() || 'Access item children'\"\n (click)=\"onItemCaretClick($event)\"\n (keydown)=\"onButtonKeydown($event)\">\n <ecl-icon icon=\"corner-arrow\" size=\"m\" transform=\"rotate-180\" class=\"ecl-button__icon\"></ecl-icon>\n </button>\n } @else {\n <button\n eclButton\n variant=\"tertiary\"\n class=\"ecl-menu__button-caret ecl-button--neutral\"\n type=\"button\"\n [attr.aria-label]=\"toggleAriaLabel() || 'Access item children'\"\n [attr.data-expanded]=\"isDataExpanded\"\n (keydown)=\"onButtonKeydown($event)\">\n <ecl-icon icon=\"corner-arrow\" size=\"m\" transform=\"rotate-180\" class=\"ecl-button__icon\"></ecl-icon>\n </button>\n }\n}\n\n<ng-content select=\"ecl-menu-mega\"></ng-content>\n\n<ng-template #content>\n <ng-content></ng-content>\n</ng-template>\n"
68382
+ "templateData": "@if (href()) {\n <a eclLink\n #menuLink\n variant=\"standalone\"\n [href]=\"href()\"\n [target]=\"target()\"\n class=\"ecl-menu__link\"\n [class.ecl-menu__link--current]=\"isCurrent()\"\n [attr.aria-current]=\"ariaCurrent()\"\n (focus)=\"onItemFocus($event)\"\n (click)=\"onItemClick($event)\"\n (keydown)=\"onLinkKeydown($event)\">\n {{ label() }}\n <ng-container *ngTemplateOutlet=\"content\"></ng-container>\n </a>\n} @else {\n <a eclLink\n #menuLink\n variant=\"standalone\"\n [attr.tabindex]=\"tabindex || 0\"\n [routerLink]=\"routerLink()\"\n [queryParams]=\"queryParams()\"\n [queryParamsHandling]=\"queryParamsHandling()\"\n class=\"ecl-menu__link\"\n [class.ecl-menu__link--current]=\"isCurrent()\"\n [attr.aria-current]=\"ariaCurrent()\"\n (focus)=\"onItemFocus($event)\"\n (click)=\"onItemClick($event)\"\n (keydown)=\"onLinkKeydown($event)\">\n {{ label() }}\n <ng-container *ngTemplateOutlet=\"content\"></ng-container>\n </a>\n}\n\n@if (hasChildren()) {\n @if (isMobileBreakpoint()) {\n <button\n eclButton\n variant=\"tertiary\"\n class=\"ecl-menu__button-caret ecl-button--neutral\"\n type=\"button\"\n [attr.aria-label]=\"toggleAriaLabel() || 'Access item children'\"\n (click)=\"onItemCaretClick($event)\"\n (keydown)=\"onButtonKeydown($event)\">\n <ecl-icon icon=\"corner-arrow\" size=\"m\" transform=\"rotate-180\" class=\"ecl-button__icon\"></ecl-icon>\n </button>\n } @else {\n <button\n eclButton\n variant=\"tertiary\"\n class=\"ecl-menu__button-caret ecl-button--neutral\"\n type=\"button\"\n [attr.aria-label]=\"toggleAriaLabel() || 'Access item children'\"\n [attr.aria-expanded]=\"isDataExpanded()\"\n (click)=\"onItemCaretClick($event)\"\n (keydown)=\"onButtonKeydown($event)\">\n <ecl-icon icon=\"corner-arrow\" size=\"m\" transform=\"rotate-180\" class=\"ecl-button__icon\"></ecl-icon>\n </button>\n }\n}\n\n<ng-content select=\"ecl-menu-mega\"></ng-content>\n\n<ng-template #content>\n <ng-content></ng-content>\n</ng-template>\n"
68355
68383
  },
68356
68384
  {
68357
68385
  "name": "EclMenuMegaComponent",
@@ -68722,7 +68750,7 @@
68722
68750
  },
68723
68751
  {
68724
68752
  "name": "EclMenuMegaItemComponent",
68725
- "id": "component-EclMenuMegaItemComponent-3df0320be5992238018852526a4f2497a7e3936f85ded1e9bf1efd3d4106e56a80c3f20a9c73c54cc9746eaa89cbc0513729434525706a82e7a4f8278fbd6647",
68753
+ "id": "component-EclMenuMegaItemComponent-93753c7f17ccad475586c23c1ce5a01d59ea65cefabd1cb311b8a3104b837c2a4b9b99541a25adce9fbe8a61aa5cb41040fb9d43a15716e8f95f0055606abcc7",
68726
68754
  "file": "packages/ecl/components/ecl-menu/ecl-menu-mega/ecl-menu-mega-item.component.ts",
68727
68755
  "encapsulation": [],
68728
68756
  "entryComponents": [],
@@ -69029,10 +69057,10 @@
69029
69057
  {
69030
69058
  "name": "children",
69031
69059
  "coverageIgnore": false,
69032
- "defaultValue": "[]",
69060
+ "defaultValue": "signal<EclMenuItem[]>([])",
69033
69061
  "deprecated": false,
69034
69062
  "deprecationMessage": "",
69035
- "type": "EclMenuItem[]",
69063
+ "type": "unknown",
69036
69064
  "indexKey": "",
69037
69065
  "optional": false,
69038
69066
  "description": "",
@@ -69045,7 +69073,7 @@
69045
69073
  {
69046
69074
  "name": "hasChildren",
69047
69075
  "coverageIgnore": false,
69048
- "defaultValue": "false",
69076
+ "defaultValue": "signal(false)",
69049
69077
  "deprecated": false,
69050
69078
  "deprecationMessage": "",
69051
69079
  "type": "unknown",
@@ -69231,8 +69259,8 @@
69231
69259
  "jsdoctags": [
69232
69260
  {
69233
69261
  "name": {
69234
- "pos": 3480,
69235
- "end": 3483,
69262
+ "pos": 3496,
69263
+ "end": 3499,
69236
69264
  "kind": 80,
69237
69265
  "id": 0,
69238
69266
  "flags": 16842752,
@@ -69245,8 +69273,8 @@
69245
69273
  "deprecated": false,
69246
69274
  "deprecationMessage": "",
69247
69275
  "tagName": {
69248
- "pos": 3474,
69249
- "end": 3479,
69276
+ "pos": 3490,
69277
+ "end": 3495,
69250
69278
  "kind": 80,
69251
69279
  "id": 0,
69252
69280
  "flags": 16842752,
@@ -69348,7 +69376,7 @@
69348
69376
  "description": "<p>This component represents a sub-menu item, that is on the second level of the ecl-menu.\nAdds some custom styling and functionality.</p>\n",
69349
69377
  "rawdescription": "\n\nThis component represents a sub-menu item, that is on the second level of the ecl-menu.\nAdds some custom styling and functionality.\n",
69350
69378
  "type": "component",
69351
- "sourceCode": "import {\n AfterViewInit,\n Component,\n ElementRef,\n Renderer2,\n booleanAttribute,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n contentChildren,\n viewChild,\n} from '@angular/core';\nimport { Params, QueryParamsHandling, RouterLink } from '@angular/router';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { KeyCode } from '@eui/ecl/core';\nimport { EclLinkDirective } from '@eui/ecl/components/ecl-link';\nimport { EclIconComponent } from '@eui/ecl/components/ecl-icon';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\nimport { EclMenuItemKeydownEvent, EclMenuItemSelectEvent } from '../events/ecl-menu-item.event';\n\n/**\n * This component represents a sub-menu item, that is on the second level of the ecl-menu.\n * Adds some custom styling and functionality.\n */\n@Component({\n selector: 'ecl-menu-mega-item',\n templateUrl: './ecl-menu-mega-item.component.html',\n imports: [NgTemplateOutlet, RouterLink],\n styles: [\n `\n :host {\n display: list-item;\n }\n `,\n ],\n host: {\n 'class': 'ecl-menu__subitem',\n role: 'listitem',\n '[class.ecl-menu__subitem--current]': 'isCurrent()',\n '[class.ecl-menu__see-all]': 'isSeeAll()',\n '(click)': 'onHostClick($event)',\n },\n})\nexport class EclMenuMegaItemComponent extends ECLBaseDirective implements EclMenuItem, AfterViewInit {\n\n readonly id = input<string>(undefined);\n readonly label = input<string>(undefined);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly routerLink = input<string | any[]>(null);\n readonly queryParams = input<Params | null>(null);\n readonly queryParamsHandling = input<QueryParamsHandling>(null);\n readonly href = input('');\n readonly target = input('');\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n /**\n * An additional item, that represents a link.\n */\n readonly isSeeAll = input(false, { transform: booleanAttribute });\n readonly linkExtraAttributes = input<Record<string, string>>(undefined);\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemKeydown = output<EclMenuItemKeydownEvent>();\n\n readonly hasChildren = false;\n readonly isExpanded = signal(true);\n readonly children: EclMenuItem[] = [];\n\n /**\n * The actual link of the menu item.\n */\n protected readonly megaMenuLink = viewChild<ElementRef>('megaMenuLink');\n /**\n * If menu item includes any icon, added by the user (for example for 'external' link).\n */\n protected readonly iconComponents = contentChildren(forwardRef(() => EclIconComponent));\n /**\n * If menu item includes any links, added by the user.\n */\n protected readonly links = contentChildren(forwardRef(() => EclLinkDirective));\n\n private renderer = inject(Renderer2);\n\n ngAfterViewInit(): void {\n this.manageContentChildren();\n this.setExtraAttributesForLink(this.megaMenuLink()?.nativeElement, this.linkExtraAttributes());\n }\n\n /**\n * When the item is the current active item, the 'aria-current' attribute is set to 'page'.\n */\n get ariaCurrent(): string | null {\n return this.isCurrent() ? 'page' : null;\n }\n\n /**\n *\n * @param evt This event is emited, when the menu item is clicked.\n */\n onItemClick(evt: Event): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(this, evt as MouseEvent));\n\n if (!this.href()) {\n evt.preventDefault();\n }\n }\n\n /**\n * Focus should be set on the underlying <a> element.\n */\n onFocus(): void {\n this.megaMenuLink().nativeElement.focus();\n }\n\n onKeydown(evt: Event): void {\n if ((evt as KeyboardEvent).key === KeyCode.ENTER) {\n this.onItemClick(evt);\n }\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n\n protected onHostClick(evt: MouseEvent): void {\n evt.stopPropagation();\n }\n\n /**\n * Adds specific classes to content children (icons or links).\n */\n private manageContentChildren(): void {\n if (this.iconComponents()) {\n setTimeout(() => {\n this.iconComponents().forEach((icn) => {\n icn.baseDirective.class.set('ecl-menu__sublink-icon');\n });\n });\n }\n\n if (this.links()) {\n setTimeout(() => {\n this.links().forEach((icn) => {\n icn.class.set(icn.class() ? icn.class() + ' ecl-menu__sublink' : 'ecl-menu__sublink');\n });\n });\n }\n }\n\n private setExtraAttributesForLink(link: HTMLElement, linkExtraAttributes: Record<string, string>): void {\n if (linkExtraAttributes) {\n Object.keys(linkExtraAttributes).forEach((key) =>{\n if (key !== 'href' && key !== 'routerLink') {\n this.renderer.setAttribute(link, key, linkExtraAttributes[key]);\n }\n });\n }\n }\n}\n",
69379
+ "sourceCode": "import {\n AfterViewInit,\n Component,\n ElementRef,\n Renderer2,\n booleanAttribute,\n forwardRef,\n inject,\n input,\n linkedSignal,\n output,\n signal,\n contentChildren,\n viewChild,\n} from '@angular/core';\nimport { Params, QueryParamsHandling, RouterLink } from '@angular/router';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { ECLBaseDirective } from '@eui/ecl/core';\nimport { KeyCode } from '@eui/ecl/core';\nimport { EclLinkDirective } from '@eui/ecl/components/ecl-link';\nimport { EclIconComponent } from '@eui/ecl/components/ecl-icon';\nimport { EclMenuItem } from '../models/ecl-menu-item.model';\nimport { EclMenuItemKeydownEvent, EclMenuItemSelectEvent } from '../events/ecl-menu-item.event';\n\n/**\n * This component represents a sub-menu item, that is on the second level of the ecl-menu.\n * Adds some custom styling and functionality.\n */\n@Component({\n selector: 'ecl-menu-mega-item',\n templateUrl: './ecl-menu-mega-item.component.html',\n imports: [NgTemplateOutlet, RouterLink],\n styles: [\n `\n :host {\n display: list-item;\n }\n `,\n ],\n host: {\n 'class': 'ecl-menu__subitem',\n role: 'listitem',\n '[class.ecl-menu__subitem--current]': 'isCurrent()',\n '[class.ecl-menu__see-all]': 'isSeeAll()',\n '(click)': 'onHostClick($event)',\n },\n})\nexport class EclMenuMegaItemComponent extends ECLBaseDirective implements EclMenuItem, AfterViewInit {\n\n readonly id = input<string>(undefined);\n readonly label = input<string>(undefined);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly routerLink = input<string | any[]>(null);\n readonly queryParams = input<Params | null>(null);\n readonly queryParamsHandling = input<QueryParamsHandling>(null);\n readonly href = input('');\n readonly target = input('');\n readonly isCurrentInput = input(false, { alias: 'isCurrent', transform: booleanAttribute });\n readonly isCurrent = linkedSignal(() => this.isCurrentInput());\n /**\n * An additional item, that represents a link.\n */\n readonly isSeeAll = input(false, { transform: booleanAttribute });\n readonly linkExtraAttributes = input<Record<string, string>>(undefined);\n\n readonly menuItemSelect = output<EclMenuItemSelectEvent>();\n readonly menuItemKeydown = output<EclMenuItemKeydownEvent>();\n\n readonly hasChildren = signal(false);\n readonly isExpanded = signal(true);\n readonly children = signal<EclMenuItem[]>([]);\n\n /**\n * The actual link of the menu item.\n */\n protected readonly megaMenuLink = viewChild<ElementRef>('megaMenuLink');\n /**\n * If menu item includes any icon, added by the user (for example for 'external' link).\n */\n protected readonly iconComponents = contentChildren(forwardRef(() => EclIconComponent));\n /**\n * If menu item includes any links, added by the user.\n */\n protected readonly links = contentChildren(forwardRef(() => EclLinkDirective));\n\n private renderer = inject(Renderer2);\n\n ngAfterViewInit(): void {\n this.manageContentChildren();\n this.setExtraAttributesForLink(this.megaMenuLink()?.nativeElement, this.linkExtraAttributes());\n }\n\n /**\n * When the item is the current active item, the 'aria-current' attribute is set to 'page'.\n */\n get ariaCurrent(): string | null {\n return this.isCurrent() ? 'page' : null;\n }\n\n /**\n *\n * @param evt This event is emited, when the menu item is clicked.\n */\n onItemClick(evt: Event): void {\n this.menuItemSelect.emit(new EclMenuItemSelectEvent(this, evt as MouseEvent));\n\n if (!this.href()) {\n evt.preventDefault();\n }\n }\n\n /**\n * Focus should be set on the underlying <a> element.\n */\n onFocus(): void {\n this.megaMenuLink().nativeElement.focus();\n }\n\n onKeydown(evt: Event): void {\n if ((evt as KeyboardEvent).key === KeyCode.ENTER) {\n this.onItemClick(evt);\n }\n this.menuItemKeydown.emit(new EclMenuItemKeydownEvent(this, evt as KeyboardEvent));\n }\n\n protected onHostClick(evt: MouseEvent): void {\n evt.stopPropagation();\n }\n\n /**\n * Adds specific classes to content children (icons or links).\n */\n private manageContentChildren(): void {\n if (this.iconComponents()) {\n setTimeout(() => {\n this.iconComponents().forEach((icn) => {\n icn.baseDirective.class.set('ecl-menu__sublink-icon');\n });\n });\n }\n\n if (this.links()) {\n setTimeout(() => {\n this.links().forEach((icn) => {\n icn.class.set(icn.class() ? icn.class() + ' ecl-menu__sublink' : 'ecl-menu__sublink');\n });\n });\n }\n }\n\n private setExtraAttributesForLink(link: HTMLElement, linkExtraAttributes: Record<string, string>): void {\n if (linkExtraAttributes) {\n Object.keys(linkExtraAttributes).forEach((key) =>{\n if (key !== 'href' && key !== 'routerLink') {\n this.renderer.setAttribute(link, key, linkExtraAttributes[key]);\n }\n });\n }\n }\n}\n",
69352
69380
  "assetsDirs": [],
69353
69381
  "styleUrlsData": "",
69354
69382
  "stylesData": "\n :host {\n display: list-item;\n }\n \n",