@ngrdt/menu 0.0.1

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.
@@ -0,0 +1,1214 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, DestroyRef, Renderer2, ChangeDetectorRef, Component, ChangeDetectionStrategy, ViewChildren, ViewChild, Input, HostBinding, HostListener, EnvironmentInjector, booleanAttribute, Directive, ViewEncapsulation, numberAttribute, NgModule } from '@angular/core';
3
+ import { RdtStringUtils, KB_CODE, RdtObjectUtils } from '@ngrdt/utils';
4
+ import * as i1 from '@angular/common';
5
+ import { DOCUMENT, CommonModule } from '@angular/common';
6
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
+ import * as i2 from '@angular/router';
8
+ import { Router, RouterModule } from '@angular/router';
9
+ import { delay, of, map, first, Subscription, fromEvent, throttleTime, animationFrameScheduler } from 'rxjs';
10
+ import * as i3 from '@ngrdt/router';
11
+ import { RdtRouterService, RdtAnyRouteActiveDirective } from '@ngrdt/router';
12
+ import * as i4 from '@ngrdt/shortcuts';
13
+ import { RdtShortcutService, RdtShortcut, RdtKeyListenerDirective } from '@ngrdt/shortcuts';
14
+ import { RDT_BUTTON_BASE_PROVIDER, RdtButtonBase } from '@ngrdt/button';
15
+
16
+ function parseMenuItems(items, injector, prefix = '') {
17
+ return items
18
+ .map((item) => {
19
+ let newPrefix;
20
+ if (item.dataTestId) {
21
+ newPrefix = item.dataTestId;
22
+ }
23
+ else if (prefix) {
24
+ newPrefix = RdtStringUtils.getDataTestId(item.label, prefix);
25
+ }
26
+ else {
27
+ newPrefix = RdtStringUtils.getDataTestId(item.label);
28
+ }
29
+ const newDataTestId = RdtStringUtils.getDataTestId(newPrefix, null, 'menu-item');
30
+ const res = {
31
+ label: item.label,
32
+ icon: item.icon,
33
+ shortcut: item.shortcut,
34
+ command: item.command,
35
+ visible: item.visible,
36
+ queryParams: item.queryParams,
37
+ requiredParent: item.requiredParent,
38
+ dataTestId: newDataTestId,
39
+ };
40
+ if (item.items) {
41
+ const childrenRes = res;
42
+ const parsedChildren = parseMenuItems(item.items, injector, newPrefix);
43
+ if (parsedChildren.length > 0) {
44
+ childrenRes.items = parsedChildren;
45
+ }
46
+ return childrenRes;
47
+ }
48
+ else if (item.routerLink) {
49
+ const linkRes = res;
50
+ linkRes.target = item.target ?? '_self';
51
+ if (item.routerLink) {
52
+ linkRes.routerLink = [item.routerLink.createAbsoluteUrl()];
53
+ if (!res.visible && item.routerLink.hasCanBeEnteredGuard) {
54
+ const canBeEntered = item.routerLink.canBeEntered.bind(item.routerLink);
55
+ linkRes.visible = () => canBeEntered(injector);
56
+ }
57
+ }
58
+ return linkRes;
59
+ }
60
+ else {
61
+ const linkRes = res;
62
+ if (item.externalLink) {
63
+ if (item.queryParams) {
64
+ linkRes.externalLink = RdtStringUtils.appendQueryParams(item.externalLink, item.queryParams);
65
+ }
66
+ else {
67
+ linkRes.externalLink = item.externalLink;
68
+ }
69
+ }
70
+ linkRes.target = item.target ?? '_blank';
71
+ return linkRes;
72
+ }
73
+ })
74
+ .filter((item) => !item.visible || item.visible())
75
+ .filter((item) => item.externalLink ||
76
+ item['routerLink'] ||
77
+ item.command ||
78
+ (item['items']?.length ?? 0) > 0);
79
+ }
80
+
81
+ var RdtMenuShortcutMode;
82
+ (function (RdtMenuShortcutMode) {
83
+ RdtMenuShortcutMode["FOCUS_ITEM"] = "focus-item";
84
+ RdtMenuShortcutMode["OPEN_SUBMENU"] = "open-submenu";
85
+ })(RdtMenuShortcutMode || (RdtMenuShortcutMode = {}));
86
+ const RDT_MENU_MARGIN_TOP_PROPERTY_NAME = '--rdt-menu-margin-top';
87
+ const RDT_MENU_MARGIN_BOTTOM_PROPERTY_NAME = '--rdt-menu-margin-bottom';
88
+ const RDT_MENU_MARGIN_LEFT_PROPERTY_NAME = '--rdt-menu-margin-left';
89
+ const RDT_MENU_MARGIN_RIGHT_PROPERTY_NAME = '--rdt-menu-margin-right';
90
+ const DEFAULT_MENU_HORIZONTAL_DIR = 'left';
91
+ const DEFAULT_MENU_VERTICAL_DIR = 'down';
92
+ const RDT_MENU_HORIZONTAL_DIR_PROVIDER = new InjectionToken('RDT_MENU_HORIZONTAL_DIR');
93
+ const RDT_MENU_VERTICAL_DIR_PROVIDER = new InjectionToken('RDT_MENU_VERTICAL_DIR');
94
+
95
+ const INVISIBLE_CLASS = 'invisible';
96
+ const FOCUS_VISIBLE = 'focus-visible';
97
+ function getChildRoutesMapRec(item, map) {
98
+ let childRoutes;
99
+ if (menuItemHasChildren(item)) {
100
+ childRoutes = item.items.map((child) => getChildRoutesMapRec(child, map));
101
+ }
102
+ else {
103
+ childRoutes = [];
104
+ }
105
+ let res;
106
+ if (menuItemHasRoute(item) && item.routerLink) {
107
+ res = [item.routerLink].concat(...childRoutes);
108
+ }
109
+ else {
110
+ res = [].concat(...childRoutes);
111
+ }
112
+ map.set(item, res);
113
+ return res;
114
+ }
115
+ function getChildRoutesMap(items) {
116
+ const map = new Map();
117
+ items.forEach((item) => getChildRoutesMapRec(item, map));
118
+ return map;
119
+ }
120
+ function findNextItemWithPrefix(items, itemIndex, prefix) {
121
+ const len = items.length;
122
+ prefix = prefix.toLocaleLowerCase();
123
+ for (let i = (itemIndex + 1) % len; i !== itemIndex; i = (i + 1) % len) {
124
+ const label = items[i].label.toLocaleLowerCase();
125
+ if (label.startsWith(prefix)) {
126
+ return i;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ function menuItemHasChildren(item) {
132
+ return 'items' in item;
133
+ }
134
+ function menuItemHasRoute(item) {
135
+ return 'routerLink' in item;
136
+ }
137
+ function getMenuItemsShortcutsRec(item, currentPath, res) {
138
+ const path = [...currentPath, item];
139
+ if (item.shortcut) {
140
+ res.push({
141
+ path: path,
142
+ shortcut: item.shortcut,
143
+ });
144
+ }
145
+ if (menuItemHasChildren(item)) {
146
+ item.items.forEach((child) => getMenuItemsShortcutsRec(child, path, res));
147
+ }
148
+ }
149
+ function getMenuItemsShortcuts(items) {
150
+ const res = [];
151
+ const initialPath = [];
152
+ items.forEach((item) => getMenuItemsShortcutsRec(item, initialPath, res));
153
+ return res;
154
+ }
155
+ function areHorDirsEqual(a, b) {
156
+ if (!a) {
157
+ return !b;
158
+ }
159
+ if (!b) {
160
+ return !a;
161
+ }
162
+ return a.dir === b.dir && a.left === b.left;
163
+ }
164
+ function areVertDirsEqual(a, b) {
165
+ if (!a) {
166
+ return !b;
167
+ }
168
+ if (!b) {
169
+ return !a;
170
+ }
171
+ return a.dir === b.dir && a.top === b.top;
172
+ }
173
+
174
+ // TODO: Split positioning and shortcut/focus management in separate classes
175
+ class RdtMenuOverlayComponent {
176
+ elRef = inject((ElementRef));
177
+ destroyRef = inject(DestroyRef);
178
+ renderer = inject(Renderer2);
179
+ topLevelMenu = inject(RdtMenuBaseComponent);
180
+ parentMenu = inject(RdtMenuOverlayComponent, {
181
+ optional: true,
182
+ skipSelf: true,
183
+ });
184
+ cd = inject(ChangeDetectorRef);
185
+ router = inject(Router);
186
+ document = inject(DOCUMENT);
187
+ shortcutService = inject(RdtShortcutService);
188
+ children;
189
+ focusableElements;
190
+ menuItemContainer;
191
+ item;
192
+ level;
193
+ preferredHorizontalDir = DEFAULT_MENU_HORIZONTAL_DIR;
194
+ preferredVerticalDir = DEFAULT_MENU_VERTICAL_DIR;
195
+ expanded = false;
196
+ autofocusItem = null;
197
+ // Element used to relatively position menu,
198
+ // typically parent element.
199
+ // If not specified, parent element is used.
200
+ anchorElement;
201
+ roleAttr = 'menu';
202
+ tabindexAttr = '-1';
203
+ get zIndex() {
204
+ return this.level + 10;
205
+ }
206
+ get isRootLevel() {
207
+ return this.level === 0;
208
+ }
209
+ // Bounding box of this excluding any extra offset.
210
+ _box;
211
+ get box() {
212
+ return this._box;
213
+ }
214
+ // Bounding box of parent element.
215
+ _anchorBox;
216
+ _containerPadding = { top: 0, bottom: 0 };
217
+ // Recalculate child components only after they are initialized.
218
+ _viewWasInit = false;
219
+ // Currently applied vertical and horizontal directions.
220
+ verticalDir = null;
221
+ horizontalDir = null;
222
+ expandedChild = null;
223
+ autofocusSubmenuItem = null;
224
+ keyActions = {
225
+ [KB_CODE.ARROW.UP]: (index) => this.focusPrevItem(index),
226
+ [KB_CODE.ARROW.DOWN]: (index) => this.focusNextItem(index),
227
+ [KB_CODE.ARROW.RIGHT]: (index) => this.openSubmenu(index, 'first'),
228
+ [KB_CODE.ARROW.LEFT]: () => this.closeSelf(),
229
+ [KB_CODE.HOME]: () => this.focusItem(0),
230
+ [KB_CODE.END]: () => this.focusItem(this.item.items.length - 1),
231
+ [KB_CODE.ESCAPE]: () => this.closeSelf(),
232
+ [KB_CODE.ENTER]: (index) => this.invokeItemClickByIndex(index),
233
+ [KB_CODE.SPACEBAR]: (index) => this.invokeItemClickByIndex(index),
234
+ };
235
+ ngOnInit() {
236
+ this.setClasses();
237
+ this.listenTabKeyPress();
238
+ }
239
+ ngOnChanges(changes) {
240
+ const recalcTriggers = [
241
+ 'preferredHorizontalDir',
242
+ 'preferredVerticalDir',
243
+ 'expanded',
244
+ ];
245
+ if (recalcTriggers.some((key) => key in changes) && this._viewWasInit) {
246
+ this.recalculatePosition(this.preferredHorizontalDir, this.preferredVerticalDir);
247
+ }
248
+ if (!this.expanded) {
249
+ this.expandedChild = null;
250
+ }
251
+ if ('expanded' in changes && this.expanded) {
252
+ // Wait for Angular to set .expanded class,
253
+ // because invisible elements cannot be focused.
254
+ setTimeout(() => {
255
+ switch (this.autofocusItem) {
256
+ case null:
257
+ this.focusItem(0, false);
258
+ break;
259
+ case 'first':
260
+ this.focusItem(0, true);
261
+ break;
262
+ case 'last':
263
+ this.focusItem(this.item.items.length - 1, true);
264
+ }
265
+ });
266
+ }
267
+ }
268
+ ngAfterViewInit() {
269
+ this.measureContainerPadding();
270
+ this.recalculatePosition(this.preferredHorizontalDir, this.preferredVerticalDir);
271
+ this._viewWasInit = true;
272
+ }
273
+ getMinOffsetLeft() {
274
+ return this.topLevelMenu.bodyMargin.left;
275
+ }
276
+ getMaxOffsetLeft() {
277
+ return (this.topLevelMenu.clientSize.width -
278
+ this._box.width -
279
+ this.topLevelMenu.bodyMargin.right);
280
+ }
281
+ getMinOffsetTop() {
282
+ return this.topLevelMenu.bodyMargin.top;
283
+ }
284
+ getMaxOffsetTop() {
285
+ return (this.topLevelMenu.clientSize.height -
286
+ this._box.height -
287
+ this.topLevelMenu.bodyMargin.bottom);
288
+ }
289
+ recalculatePosition(preferredHorizontalDir, preferredVerticalDir) {
290
+ // Make menu invisible so the menu does not move around in case of slow computer.
291
+ this.measureBoundingRect();
292
+ if (!this._box || !this._anchorBox) {
293
+ return;
294
+ }
295
+ const newHorDir = this.getRealHorizontalDir(preferredHorizontalDir);
296
+ const newVertDir = this.getRealVerticalDir(preferredVerticalDir);
297
+ // Horizontal direction
298
+ if (newHorDir.dir === 'right') {
299
+ newHorDir.left += this._anchorBox.left;
300
+ if (!this.isRootLevel) {
301
+ newHorDir.left += this._anchorBox.width;
302
+ }
303
+ }
304
+ else if (newHorDir.dir === 'left') {
305
+ newHorDir.left += this._anchorBox.left - this._box.width;
306
+ if (this.isRootLevel) {
307
+ newHorDir.left += this._anchorBox.width;
308
+ }
309
+ }
310
+ // Restrictions from left menu and right menu
311
+ newHorDir.left = Math.min(Math.max(this.getMinOffsetLeft(), newHorDir.left), this.getMaxOffsetLeft());
312
+ // Vertical direction
313
+ if (newVertDir.dir === 'down') {
314
+ newVertDir.top = this._anchorBox.top;
315
+ if (this.isRootLevel) {
316
+ newVertDir.top += this._anchorBox.height;
317
+ }
318
+ else {
319
+ newVertDir.top -= this._containerPadding.top;
320
+ }
321
+ }
322
+ else if (newVertDir.dir === 'up') {
323
+ newVertDir.top = this._anchorBox.top - this._box.height;
324
+ if (!this.isRootLevel) {
325
+ newVertDir.top += this._anchorBox.height + this._containerPadding.top;
326
+ }
327
+ }
328
+ // Restrictions from header and footer
329
+ newVertDir.top = Math.min(Math.max(this.getMinOffsetTop(), newVertDir.top), this.getMaxOffsetTop());
330
+ if (!areHorDirsEqual(this.horizontalDir, newHorDir) ||
331
+ !areVertDirsEqual(this.verticalDir, newVertDir)) {
332
+ this.horizontalDir = newHorDir;
333
+ this.verticalDir = newVertDir;
334
+ this.setClasses();
335
+ this.measureBoundingRect();
336
+ }
337
+ this.recalculateChildren();
338
+ }
339
+ onItemClick(item) {
340
+ const hasChildren = menuItemHasChildren(item);
341
+ if (hasChildren && this.expandedChild !== item) {
342
+ this.autofocusSubmenuItem = null;
343
+ this.expandedChild = item;
344
+ }
345
+ else {
346
+ this.expandedChild = null;
347
+ }
348
+ // Could also listen to router events, but might be triggered by
349
+ // child router navigation.
350
+ if (!hasChildren) {
351
+ this.topLevelMenu.closeSubmenus();
352
+ }
353
+ if (typeof item.command === 'function') {
354
+ item.command();
355
+ }
356
+ }
357
+ invokeItemClick(item) {
358
+ if (menuItemHasRoute(item) && item.routerLink) {
359
+ this.router.navigate(item.routerLink);
360
+ }
361
+ this.onItemClick(item);
362
+ this.autofocusSubmenuItem = 'first';
363
+ }
364
+ getChildRoutes(item) {
365
+ return this.topLevelMenu.getChildRoutes(item);
366
+ }
367
+ closeSubmenus(focusExpanded = false) {
368
+ if (focusExpanded && this.expandedChild) {
369
+ this.focusItem(this.expandedChild);
370
+ }
371
+ this.expandedChild = null;
372
+ this.cd.markForCheck();
373
+ }
374
+ focusItemRecursively(path) {
375
+ const children = [...path];
376
+ const thisItem = children.shift();
377
+ this.autofocusSubmenuItem = null;
378
+ this.autofocusItem = null;
379
+ if (children.length > 0) {
380
+ this.expandAndGetChild(thisItem)
381
+ .pipe(delay(1))
382
+ .subscribe((overlay) => {
383
+ overlay.focusItemRecursively(children);
384
+ });
385
+ }
386
+ else {
387
+ // First focus the correct item because submenu might have focus on it.
388
+ // Removing focused element from DOM results in window:focusout event
389
+ // and that closes all menus
390
+ this.focusItem(thisItem);
391
+ // Now close submenus and therefore remove them from DOM
392
+ this.closeSubmenus();
393
+ // Wait until they are removed and focus the correct item again
394
+ setTimeout(() => this.focusItem(thisItem), 1);
395
+ }
396
+ }
397
+ expandAndGetChild(item) {
398
+ if (this.item.items.indexOf(item) < 0) {
399
+ throw new Error('Attempting to expand item that is not child item of this.item');
400
+ }
401
+ this.expandedChild = item;
402
+ this.cd.markForCheck();
403
+ const child = this.children.find((child) => child.item === item);
404
+ if (child) {
405
+ return of(child);
406
+ }
407
+ else {
408
+ return this.children.changes.pipe(map(() => this.children.find((child) => child.item === item)), first(RdtObjectUtils.notNullGuard));
409
+ }
410
+ }
411
+ onKeyDown(itemIndex, event) {
412
+ const hasAction = event.code in this.keyActions;
413
+ const isLetter = RdtStringUtils.isAlphabetCharacter(event.key) ||
414
+ RdtStringUtils.isNumericCharacter(event.key);
415
+ if (hasAction || isLetter) {
416
+ event.preventDefault();
417
+ }
418
+ if (hasAction) {
419
+ event.stopPropagation();
420
+ }
421
+ if (hasAction) {
422
+ this.keyActions[event.code](itemIndex);
423
+ }
424
+ else if (isLetter) {
425
+ const next = findNextItemWithPrefix(this.item.items, itemIndex, event.key);
426
+ if (next !== null) {
427
+ this.focusItem(next);
428
+ }
429
+ }
430
+ }
431
+ onTabKeyPress(event) {
432
+ // Do not prevent default to correctly switch to previous element
433
+ event.originalEvent.stopPropagation();
434
+ // Wait for document.activeElement to switch to new element
435
+ setTimeout(() => {
436
+ if (!this.elRef.nativeElement.contains(this.document.activeElement)) {
437
+ this.closeSelf(false);
438
+ }
439
+ }, 1);
440
+ }
441
+ invokeItemClickByIndex(itemIndex) {
442
+ const item = this.item.items[itemIndex];
443
+ if (!item) {
444
+ return;
445
+ }
446
+ this.invokeItemClick(item);
447
+ }
448
+ focusItem(item, visibleFocus = true) {
449
+ let itemIndex;
450
+ if (typeof item === 'number') {
451
+ itemIndex = item;
452
+ }
453
+ else {
454
+ itemIndex = this.item.items.indexOf(item);
455
+ }
456
+ if (itemIndex >= 0 && itemIndex < this.focusableElements.length) {
457
+ this.blurAllFocusable();
458
+ this.focusFocusable(itemIndex, visibleFocus);
459
+ }
460
+ }
461
+ blurAllFocusable() {
462
+ this.focusableElements.forEach((el) => this.renderer.removeClass(el.nativeElement, FOCUS_VISIBLE));
463
+ }
464
+ focusFocusable(itemIndex, visibleFocus) {
465
+ const elRef = this.focusableElements.get(itemIndex);
466
+ if (elRef) {
467
+ if (visibleFocus) {
468
+ this.renderer.addClass(elRef.nativeElement, FOCUS_VISIBLE);
469
+ }
470
+ elRef.nativeElement.focus();
471
+ }
472
+ }
473
+ focusNextItem(itemIndex) {
474
+ const next = (itemIndex + 1) % this.item.items.length;
475
+ this.focusItem(next);
476
+ }
477
+ focusPrevItem(itemIndex) {
478
+ const prev = (itemIndex - 1 + this.item.items.length) % this.item.items.length;
479
+ this.focusItem(prev);
480
+ }
481
+ openSubmenu(itemIndex, visibleFocus) {
482
+ if (itemIndex < this.item.items.length &&
483
+ menuItemHasChildren(this.item.items[itemIndex])) {
484
+ this.expandedChild = this.item.items[itemIndex];
485
+ this.autofocusSubmenuItem = visibleFocus;
486
+ }
487
+ }
488
+ closeSelf(focusExpanded = true) {
489
+ if (this.parentMenu) {
490
+ this.parentMenu.closeSubmenus(focusExpanded);
491
+ }
492
+ else {
493
+ this.topLevelMenu.closeSubmenus(focusExpanded);
494
+ }
495
+ }
496
+ checkActiveElement(event) {
497
+ if (this.topLevelMenu.closeOnFocusOut) {
498
+ const thisEl = this.elRef.nativeElement;
499
+ const target = event.relatedTarget;
500
+ if (!(target instanceof HTMLElement) || !thisEl.contains(target)) {
501
+ this.expandedChild = null;
502
+ }
503
+ }
504
+ }
505
+ // Calculates direction of this menu and offset in case
506
+ // menu won't fit in left nor right of parent menu.
507
+ getRealHorizontalDir(preferredHorizontalDir) {
508
+ let left;
509
+ let right;
510
+ // This menu is directly under menubar
511
+ if (this.level === 0) {
512
+ left = this._anchorBox.right - this._box.width;
513
+ right =
514
+ this.topLevelMenu.clientSize.width -
515
+ (this._anchorBox.left + this._box.width);
516
+ }
517
+ else {
518
+ // Menu is nested
519
+ right =
520
+ this.topLevelMenu.clientSize.width -
521
+ (this._anchorBox.right + this._box.width);
522
+ left = this._anchorBox.left - this._box.width;
523
+ }
524
+ left -= this.topLevelMenu.bodyMargin.left;
525
+ right -= this.topLevelMenu.bodyMargin.right;
526
+ if (preferredHorizontalDir === 'right') {
527
+ // We use preferred if possible.
528
+ if (right >= 0) {
529
+ return { dir: 'right', left: 0 };
530
+ // Then the other one.
531
+ }
532
+ else if (left >= 0) {
533
+ return { dir: 'left', left: 0 };
534
+ // Edge case if neither direction can be used (ultra narrow screen).
535
+ // Choose direction that covers least of parent menu.
536
+ }
537
+ else {
538
+ return this.getNarrowScreenHorizontalDir(left, right);
539
+ }
540
+ }
541
+ else {
542
+ if (left >= 0) {
543
+ return { dir: 'left', left: 0 };
544
+ }
545
+ else if (right >= 0) {
546
+ return { dir: 'right', left: 0 };
547
+ }
548
+ else {
549
+ return this.getNarrowScreenHorizontalDir(left, right);
550
+ }
551
+ }
552
+ }
553
+ // Finds out where to put menu in case screen is too narrow for two menus
554
+ // to fit next to each other. Minimizes area of parent menu covered by this.
555
+ getNarrowScreenHorizontalDir(left, right) {
556
+ const screenStart = this.topLevelMenu.bodyMargin.left;
557
+ const screenEnd = this.topLevelMenu.clientSize.width - this.topLevelMenu.bodyMargin.right;
558
+ const screenCenter = (screenStart - screenEnd) / 2;
559
+ const leftStart = Math.max(this._anchorBox.left, screenStart);
560
+ const leftEnd = Math.min(this._anchorBox.left + this._anchorBox.width, screenCenter);
561
+ const rightStart = Math.max(screenCenter, leftEnd);
562
+ const rightEnd = Math.min(this._anchorBox.left + this._anchorBox.width, screenEnd);
563
+ if (leftEnd - leftStart > rightEnd - rightStart) {
564
+ return { dir: 'right', left: right };
565
+ }
566
+ else {
567
+ return { dir: 'left', left: -left };
568
+ }
569
+ }
570
+ getRealVerticalDir(preferredVerticalDir) {
571
+ let bottom;
572
+ let top;
573
+ if (this.level === 0) {
574
+ bottom =
575
+ this.topLevelMenu.clientSize.height +
576
+ this._anchorBox.bottom -
577
+ this._box.height;
578
+ top = this._anchorBox.top - this._box.height;
579
+ }
580
+ else {
581
+ bottom =
582
+ this.topLevelMenu.clientSize.height -
583
+ this._anchorBox.top -
584
+ this._box.height;
585
+ top = this._anchorBox.bottom - this._box.height;
586
+ }
587
+ top -= this.topLevelMenu.bodyMargin.top;
588
+ bottom -= this.topLevelMenu.bodyMargin.bottom;
589
+ if (preferredVerticalDir === 'down') {
590
+ if (bottom >= 0) {
591
+ return { dir: 'down', top: 0 };
592
+ }
593
+ else if (top >= 0) {
594
+ return { dir: 'up', top: 0 };
595
+ }
596
+ else {
597
+ return { dir: preferredVerticalDir, top: 0 };
598
+ }
599
+ }
600
+ else {
601
+ if (top >= 0) {
602
+ return { dir: 'up', top: 0 };
603
+ }
604
+ else if (bottom >= 0) {
605
+ return { dir: 'down', top: 0 };
606
+ }
607
+ else {
608
+ return { dir: preferredVerticalDir, top: 0 };
609
+ }
610
+ }
611
+ }
612
+ // Measures bounding rect of this and offset container
613
+ // Should always be called after parent menu has already been measured.
614
+ measureBoundingRect() {
615
+ this._box = this.elRef.nativeElement.getBoundingClientRect();
616
+ const anchor = this.anchorElement ?? this.elRef.nativeElement.parentElement;
617
+ this._anchorBox = anchor.getBoundingClientRect();
618
+ }
619
+ measureContainerPadding() {
620
+ const computedStyles = getComputedStyle(this.menuItemContainer.nativeElement);
621
+ const paddingTop = parseFloat(computedStyles.paddingTop);
622
+ const paddingBottom = parseFloat(computedStyles.paddingBottom);
623
+ this._containerPadding = { top: paddingTop, bottom: paddingBottom };
624
+ }
625
+ recalculateChildren() {
626
+ if (this.expanded && this.horizontalDir && this.verticalDir) {
627
+ this.children.forEach((child) => child.recalculatePosition(this.horizontalDir.dir, this.verticalDir.dir));
628
+ }
629
+ }
630
+ setClasses() {
631
+ if (this.verticalDir) {
632
+ if (this.verticalDir.dir === 'up') {
633
+ this.setClass('rdt-overlay-up');
634
+ this.removeClass('rdt-overlay-down');
635
+ }
636
+ else {
637
+ this.removeClass('rdt-overlay-up');
638
+ this.setClass('rdt-overlay-down');
639
+ }
640
+ this.setOffset('top', this.verticalDir.top);
641
+ }
642
+ if (this.horizontalDir) {
643
+ if (this.horizontalDir.dir === 'left') {
644
+ this.setClass('rdt-overlay-left');
645
+ this.removeClass('rdt-overlay-right');
646
+ }
647
+ else {
648
+ this.removeClass('rdt-overlay-left');
649
+ this.setClass('rdt-overlay-right');
650
+ }
651
+ this.setOffset('left', this.horizontalDir.left);
652
+ }
653
+ if (this.isRootLevel) {
654
+ this.setClass('rdt-menu-root');
655
+ this.removeClass('rdt-menu-sub');
656
+ }
657
+ else {
658
+ this.removeClass('rdt-menu-root');
659
+ this.setClass('rdt-menu-sub');
660
+ }
661
+ }
662
+ setClass(className) {
663
+ this.renderer.addClass(this.elRef.nativeElement, className);
664
+ }
665
+ removeClass(className) {
666
+ this.renderer.removeClass(this.elRef.nativeElement, className);
667
+ }
668
+ setOffset(direction, value) {
669
+ this.renderer.setStyle(this.elRef.nativeElement, direction, `${value}px`);
670
+ }
671
+ listenTabKeyPress() {
672
+ const shiftTab = RdtShortcut.createUnsafe('shift', 'tab');
673
+ const tab = RdtShortcut.createUnsafe('tab');
674
+ this.shortcutService
675
+ .listen([tab, shiftTab], {
676
+ element: this.elRef.nativeElement,
677
+ preventDefault: false,
678
+ })
679
+ .pipe(takeUntilDestroyed(this.destroyRef))
680
+ .subscribe((event) => this.onTabKeyPress(event));
681
+ }
682
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
683
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: RdtMenuOverlayComponent, selector: "rdt-menu-overlay", inputs: { item: "item", level: "level", preferredHorizontalDir: "preferredHorizontalDir", preferredVerticalDir: "preferredVerticalDir", expanded: "expanded", autofocusItem: "autofocusItem", anchorElement: "anchorElement" }, host: { listeners: { "window:focusout": "checkActiveElement($event)" }, properties: { "class.expanded": "this.expanded", "attr.role": "this.roleAttr", "attr.tabindex": "this.tabindexAttr", "style.z-index": "this.zIndex" } }, viewQueries: [{ propertyName: "menuItemContainer", first: true, predicate: ["menuItemContainer"], descendants: true }, { propertyName: "children", predicate: RdtMenuOverlayComponent, descendants: true }, { propertyName: "focusableElements", predicate: ["focusableItem"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ul class=\"menu-item-container\" role=\"presentation\" #menuItemContainer>\r\n <li\r\n *ngFor=\"let item of item.items; let i = index\"\r\n role=\"presentation\"\r\n class=\"menu-item\"\r\n (rdtKeyListener)=\"onKeyDown(i, $event)\"\r\n rdtAnyRouteActive=\"menu-item-route-active\"\r\n [watchedRoutes]=\"getChildRoutes(item)\"\r\n #anchorEl\r\n >\r\n <ng-container *ngIf=\"!item.routerLink && !item.externalLink\">\r\n <button\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [attr.aria-haspopup]=\"item.items ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item === expandedChild\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n {{ item.label }}\r\n\r\n <div class=\"menu-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO:\r\n <vnsh-icon\r\n *ngIf=\"item.items\"\r\n name=\"chevron_right\"\r\n class=\"menu-item-icon\"\r\n />\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-item-icon\"\r\n />\r\n -->\r\n <span class=\"menu-item-icon\" *ngIf=\"item.items\">&gt;</span>\r\n </div>\r\n </button>\r\n </ng-container>\r\n\r\n <ng-template #linkBody let-item>\r\n {{ item.label }}\r\n\r\n <div class=\"menu-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO:\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-item-icon\"\r\n />\r\n --></div>\r\n </ng-template>\r\n\r\n <ng-container *ngIf=\"item.routerLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [routerLink]=\"item.routerLink\"\r\n [queryParams]=\"item.queryParams\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"item.externalLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [href]=\"item.externalLink\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Child menus exist only if this menu is expanded. -->\r\n <!-- Only one invisible level is always attached to DOM, the rest does not exist. -->\r\n <rdt-menu-overlay\r\n *ngIf=\"item.items && expanded\"\r\n [item]=\"$any(item)\"\r\n [expanded]=\"item === expandedChild\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"horizontalDir?.dir!\"\r\n [preferredVerticalDir]=\"verticalDir?.dir!\"\r\n [level]=\"level + 1\"\r\n [anchorElement]=\"anchorEl\"\r\n />\r\n </li>\r\n</ul>\r\n", styles: [":host{visibility:hidden;position:fixed;min-width:var(--vnsh-menu-min-width);box-shadow:var(--vnsh-menu-box-shadow);border-radius:var(--vnsh-menu-border-radius);max-height:calc(100vh - var(--vnsh-menu-margin-top, 0px) - var(--vnsh-menu-margin-bottom, 0px));overflow-y:auto}:host.expanded{visibility:visible}.menu-item-container{list-style-type:none;padding:var(--vnsh-menu-padding-horizontal-padding) 0;border-radius:var(--vnsh-menu-border-radius);background-color:var(--vnsh-menu-item-background)}.menu-item{cursor:pointer;position:relative;box-sizing:border-box}.menu-item.menu-item-route-active>.menu-item-content{background-color:var(--vnsh-menu-item-background-route-active)}.menu-item-content{display:flex;align-items:center;padding:var(--vnsh-menu-item-padding);width:100%;background-color:var(--vnsh-menu-item-background);color:var(--vnsh-menu-item-text-color)}.menu-item-content:hover{background-color:var(--vnsh-menu-item-background-hover)}.menu-item-content.focus-visible:focus,.menu-item-content:focus-visible{background-color:var(--vnsh-menu-item-background-focus);outline:none}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i3.RdtAnyRouteActiveDirective, selector: "[rdtAnyRouteActive]", inputs: ["anyRouteActive", "watchedRoutes", "anyRouteActiveOptions", "ariaCurrentWhenActive"] }, { kind: "directive", type: i4.RdtKeyListenerDirective, selector: "[rdtKeyListener]", outputs: ["rdtKeyListener"] }, { kind: "component", type: RdtMenuOverlayComponent, selector: "rdt-menu-overlay", inputs: ["item", "level", "preferredHorizontalDir", "preferredVerticalDir", "expanded", "autofocusItem", "anchorElement"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
684
+ }
685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuOverlayComponent, decorators: [{
686
+ type: Component,
687
+ args: [{ selector: 'rdt-menu-overlay', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ul class=\"menu-item-container\" role=\"presentation\" #menuItemContainer>\r\n <li\r\n *ngFor=\"let item of item.items; let i = index\"\r\n role=\"presentation\"\r\n class=\"menu-item\"\r\n (rdtKeyListener)=\"onKeyDown(i, $event)\"\r\n rdtAnyRouteActive=\"menu-item-route-active\"\r\n [watchedRoutes]=\"getChildRoutes(item)\"\r\n #anchorEl\r\n >\r\n <ng-container *ngIf=\"!item.routerLink && !item.externalLink\">\r\n <button\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [attr.aria-haspopup]=\"item.items ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item === expandedChild\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n {{ item.label }}\r\n\r\n <div class=\"menu-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO:\r\n <vnsh-icon\r\n *ngIf=\"item.items\"\r\n name=\"chevron_right\"\r\n class=\"menu-item-icon\"\r\n />\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-item-icon\"\r\n />\r\n -->\r\n <span class=\"menu-item-icon\" *ngIf=\"item.items\">&gt;</span>\r\n </div>\r\n </button>\r\n </ng-container>\r\n\r\n <ng-template #linkBody let-item>\r\n {{ item.label }}\r\n\r\n <div class=\"menu-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO:\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-item-icon\"\r\n />\r\n --></div>\r\n </ng-template>\r\n\r\n <ng-container *ngIf=\"item.routerLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [routerLink]=\"item.routerLink\"\r\n [queryParams]=\"item.queryParams\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"item.externalLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [href]=\"item.externalLink\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Child menus exist only if this menu is expanded. -->\r\n <!-- Only one invisible level is always attached to DOM, the rest does not exist. -->\r\n <rdt-menu-overlay\r\n *ngIf=\"item.items && expanded\"\r\n [item]=\"$any(item)\"\r\n [expanded]=\"item === expandedChild\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"horizontalDir?.dir!\"\r\n [preferredVerticalDir]=\"verticalDir?.dir!\"\r\n [level]=\"level + 1\"\r\n [anchorElement]=\"anchorEl\"\r\n />\r\n </li>\r\n</ul>\r\n", styles: [":host{visibility:hidden;position:fixed;min-width:var(--vnsh-menu-min-width);box-shadow:var(--vnsh-menu-box-shadow);border-radius:var(--vnsh-menu-border-radius);max-height:calc(100vh - var(--vnsh-menu-margin-top, 0px) - var(--vnsh-menu-margin-bottom, 0px));overflow-y:auto}:host.expanded{visibility:visible}.menu-item-container{list-style-type:none;padding:var(--vnsh-menu-padding-horizontal-padding) 0;border-radius:var(--vnsh-menu-border-radius);background-color:var(--vnsh-menu-item-background)}.menu-item{cursor:pointer;position:relative;box-sizing:border-box}.menu-item.menu-item-route-active>.menu-item-content{background-color:var(--vnsh-menu-item-background-route-active)}.menu-item-content{display:flex;align-items:center;padding:var(--vnsh-menu-item-padding);width:100%;background-color:var(--vnsh-menu-item-background);color:var(--vnsh-menu-item-text-color)}.menu-item-content:hover{background-color:var(--vnsh-menu-item-background-hover)}.menu-item-content.focus-visible:focus,.menu-item-content:focus-visible{background-color:var(--vnsh-menu-item-background-focus);outline:none}\n"] }]
688
+ }], propDecorators: { children: [{
689
+ type: ViewChildren,
690
+ args: [RdtMenuOverlayComponent]
691
+ }], focusableElements: [{
692
+ type: ViewChildren,
693
+ args: ['focusableItem']
694
+ }], menuItemContainer: [{
695
+ type: ViewChild,
696
+ args: ['menuItemContainer']
697
+ }], item: [{
698
+ type: Input
699
+ }], level: [{
700
+ type: Input
701
+ }], preferredHorizontalDir: [{
702
+ type: Input
703
+ }], preferredVerticalDir: [{
704
+ type: Input
705
+ }], expanded: [{
706
+ type: HostBinding,
707
+ args: ['class.expanded']
708
+ }, {
709
+ type: Input
710
+ }], autofocusItem: [{
711
+ type: Input
712
+ }], anchorElement: [{
713
+ type: Input
714
+ }], roleAttr: [{
715
+ type: HostBinding,
716
+ args: ['attr.role']
717
+ }], tabindexAttr: [{
718
+ type: HostBinding,
719
+ args: ['attr.tabindex']
720
+ }], zIndex: [{
721
+ type: HostBinding,
722
+ args: ['style.z-index']
723
+ }], checkActiveElement: [{
724
+ type: HostListener,
725
+ args: ['window:focusout', ['$event']]
726
+ }] } });
727
+
728
+ class RdtMenuBaseComponent {
729
+ preferredVerticalDir = DEFAULT_MENU_VERTICAL_DIR;
730
+ preferredHorizontalDir = DEFAULT_MENU_HORIZONTAL_DIR;
731
+ // Mode only matters if item has shortcut and only children, no command nor routerLink
732
+ // FOCUS_ITEM will put focus on item with shortcut
733
+ // OPEN_SUBMENU will open its submenu and put focus on the first item in it
734
+ // If item has command or routerLink, it will always be activated and everything closed
735
+ shortcutMode = RdtMenuShortcutMode.OPEN_SUBMENU;
736
+ closeOnFocusOut = true;
737
+ cd = inject(ChangeDetectorRef);
738
+ destroyRef = inject(DestroyRef);
739
+ rdtRouter = inject(RdtRouterService);
740
+ elRef = inject(ElementRef);
741
+ document = inject(DOCUMENT);
742
+ renderer = inject(Renderer2);
743
+ router = inject(Router);
744
+ shortcutService = inject(RdtShortcutService);
745
+ injector = inject(EnvironmentInjector);
746
+ children;
747
+ focusableElements;
748
+ classes = 'rdt-menu-base';
749
+ expanded = null;
750
+ autofocusSubmenuItem = null;
751
+ parsedItems;
752
+ get clientSize() {
753
+ return this._clientSize;
754
+ }
755
+ _clientSize;
756
+ get bodyMargin() {
757
+ return this._bodyMargin;
758
+ }
759
+ _bodyMargin;
760
+ allParsedItems;
761
+ childRoutesMap = new Map();
762
+ shortcutSub = new Subscription();
763
+ shortcutMap = {};
764
+ keyActions = {
765
+ [KB_CODE.ARROW.LEFT]: (index) => this.focusPrevItem(index),
766
+ [KB_CODE.ARROW.RIGHT]: (index) => this.focusNextItem(index),
767
+ [KB_CODE.ARROW.DOWN]: (index) => this.openSubmenu(index, 'first'),
768
+ [KB_CODE.ARROW.UP]: (index) => this.openSubmenu(index, 'last'),
769
+ [KB_CODE.HOME]: () => this.focusItem(0),
770
+ [KB_CODE.END]: () => this.focusItem(this.parsedItems.length - 1),
771
+ [KB_CODE.ENTER]: (index) => this.invokeItemClickByIndex(index),
772
+ [KB_CODE.SPACEBAR]: (index) => this.invokeItemClickByIndex(index),
773
+ };
774
+ // Returns all routes asociated with item or its subitems.
775
+ getChildRoutes(item) {
776
+ return this.childRoutesMap.get(item) ?? [];
777
+ }
778
+ ngOnInit() {
779
+ this.childRoutesMap = getChildRoutesMap(this.parsedItems);
780
+ this.rdtRouter.navigationEnd$
781
+ .pipe(takeUntilDestroyed(this.destroyRef))
782
+ .subscribe((ev) => this.filterItems());
783
+ this.measure();
784
+ }
785
+ ngOnChanges(changes) {
786
+ if (this.children) {
787
+ this.recalculateChildren();
788
+ }
789
+ if ('items' in changes) {
790
+ this.childRoutesMap = getChildRoutesMap(this.parsedItems);
791
+ this.listenShortcuts();
792
+ }
793
+ }
794
+ ngAfterViewInit() {
795
+ this.listenWindowResize();
796
+ // Not using timeout results in horizontal overflow on html element
797
+ setTimeout(() => {
798
+ this.measure();
799
+ this.recalculateChildren();
800
+ }, 50);
801
+ }
802
+ recalculateChildren() {
803
+ this.children.forEach((child) => {
804
+ child.recalculatePosition(this.preferredHorizontalDir, this.preferredVerticalDir);
805
+ });
806
+ }
807
+ closeSubmenus(focusExpanded = false) {
808
+ if (focusExpanded && this.expanded) {
809
+ this.focusItem(this.expanded);
810
+ }
811
+ this.expanded = null;
812
+ this.cd.markForCheck();
813
+ }
814
+ onItemClick(item) {
815
+ if (menuItemHasChildren(item) && this.expanded !== item) {
816
+ this.autofocusSubmenuItem = null;
817
+ this.expanded = item;
818
+ }
819
+ else {
820
+ this.expanded = null;
821
+ }
822
+ if (typeof item.command === 'function') {
823
+ item.command();
824
+ }
825
+ }
826
+ onKeyDown(itemIndex, event) {
827
+ const hasAction = event.code in this.keyActions;
828
+ const isLetter = RdtStringUtils.isAlphabetCharacter(event.key) ||
829
+ RdtStringUtils.isNumericCharacter(event.key);
830
+ if (hasAction || isLetter) {
831
+ event.preventDefault();
832
+ }
833
+ if (hasAction) {
834
+ event.stopPropagation();
835
+ }
836
+ if (hasAction) {
837
+ this.keyActions[event.code](itemIndex);
838
+ }
839
+ else if (isLetter) {
840
+ const next = findNextItemWithPrefix(this.parsedItems, itemIndex, event.key);
841
+ if (next !== null) {
842
+ this.focusItem(next);
843
+ }
844
+ }
845
+ }
846
+ invokeItemClick(item) {
847
+ if (menuItemHasRoute(item) && item.routerLink) {
848
+ this.router.navigate(item.routerLink);
849
+ }
850
+ this.onItemClick(item);
851
+ this.autofocusSubmenuItem = 'first';
852
+ }
853
+ filterItems() {
854
+ const current = this.rdtRouter.parseAbsoluteUrl();
855
+ if (!current) {
856
+ this.parsedItems = this.allParsedItems;
857
+ }
858
+ else {
859
+ this.parsedItems = this.filterRec(this.allParsedItems, current);
860
+ }
861
+ this.cd.markForCheck();
862
+ }
863
+ filterRec(items, current) {
864
+ let currentParent = current.route;
865
+ const currentParams = current.params;
866
+ const filtered = items.filter((item) => {
867
+ if (!item.requiredParent) {
868
+ return true;
869
+ }
870
+ const required = item.requiredParent;
871
+ while (currentParent.absolutePath !== required.absolutePath) {
872
+ currentParent = currentParent.parent;
873
+ if (!currentParent) {
874
+ return false;
875
+ }
876
+ }
877
+ let requiredParent = required;
878
+ do {
879
+ const path = requiredParent.absolutePath;
880
+ if (path in currentParams) {
881
+ for (const key in requiredParent.staticParams) {
882
+ if (currentParams[path][key] !== requiredParent.staticParams[key]) {
883
+ return false;
884
+ }
885
+ }
886
+ }
887
+ requiredParent = requiredParent.parent;
888
+ } while (requiredParent);
889
+ return true;
890
+ });
891
+ return filtered.map((item) => {
892
+ if (item.items) {
893
+ return {
894
+ ...item,
895
+ items: this.filterRec(item.items, current),
896
+ };
897
+ }
898
+ else {
899
+ return item;
900
+ }
901
+ });
902
+ }
903
+ invokeItemClickByIndex(itemIndex) {
904
+ const item = this.parsedItems[itemIndex];
905
+ if (!item) {
906
+ return;
907
+ }
908
+ this.invokeItemClick(item);
909
+ }
910
+ openSubmenu(itemIndex, visibleFocus) {
911
+ if (itemIndex < this.parsedItems.length &&
912
+ menuItemHasChildren(this.parsedItems[itemIndex])) {
913
+ const item = this.parsedItems[itemIndex];
914
+ this.autofocusSubmenuItem = visibleFocus;
915
+ this.expanded = item;
916
+ }
917
+ }
918
+ focusItem(item) {
919
+ let itemIndex;
920
+ if (typeof item === 'number') {
921
+ itemIndex = item;
922
+ }
923
+ else {
924
+ itemIndex = this.parsedItems.indexOf(item);
925
+ }
926
+ if (itemIndex >= 0 && itemIndex < this.focusableElements.length) {
927
+ this.blurAllFocusable();
928
+ this.focusFocusable(itemIndex);
929
+ }
930
+ }
931
+ focusNextItem(itemIndex) {
932
+ const next = (itemIndex + 1) % this.parsedItems.length;
933
+ this.focusItem(next);
934
+ }
935
+ focusPrevItem(itemIndex) {
936
+ const prev = (itemIndex - 1 + this.parsedItems.length) % this.parsedItems.length;
937
+ this.focusItem(prev);
938
+ }
939
+ blurAllFocusable() {
940
+ this.focusableElements.forEach((el) => this.renderer.removeClass(el.nativeElement, FOCUS_VISIBLE));
941
+ }
942
+ focusFocusable(itemIndex) {
943
+ const elRef = this.focusableElements.get(itemIndex);
944
+ if (elRef) {
945
+ this.renderer.addClass(elRef.nativeElement, FOCUS_VISIBLE);
946
+ elRef.nativeElement.focus();
947
+ }
948
+ }
949
+ // Closes menu if user navigates outside it using Tab.
950
+ checkActiveElement(event) {
951
+ if (this.closeOnFocusOut) {
952
+ const thisEl = this.elRef.nativeElement;
953
+ const target = event.relatedTarget;
954
+ if (!(target instanceof HTMLElement) || !thisEl.contains(target)) {
955
+ this.expanded = null;
956
+ }
957
+ }
958
+ }
959
+ onDocumentClick(event) {
960
+ const target = event.target;
961
+ const elRef = this.elRef.nativeElement;
962
+ if (!elRef.contains(target)) {
963
+ this.closeSubmenus();
964
+ }
965
+ }
966
+ measure() {
967
+ this.measureClientSize();
968
+ this.readMargin();
969
+ }
970
+ readMargin() {
971
+ const computedStyle = getComputedStyle(this.elRef.nativeElement);
972
+ const topVal = computedStyle.getPropertyValue(RDT_MENU_MARGIN_TOP_PROPERTY_NAME);
973
+ const bottomVal = computedStyle.getPropertyValue(RDT_MENU_MARGIN_BOTTOM_PROPERTY_NAME);
974
+ const leftVal = computedStyle.getPropertyValue(RDT_MENU_MARGIN_LEFT_PROPERTY_NAME);
975
+ const rightVal = computedStyle.getPropertyValue(RDT_MENU_MARGIN_RIGHT_PROPERTY_NAME);
976
+ this._bodyMargin = {
977
+ top: parseInt(topVal) || 0,
978
+ bottom: parseInt(bottomVal) || 0,
979
+ left: parseInt(leftVal) || 0,
980
+ right: parseInt(rightVal) || 0,
981
+ };
982
+ }
983
+ measureClientSize() {
984
+ this._clientSize = {
985
+ width: this.document.body.clientWidth,
986
+ height: this.document.body.clientHeight,
987
+ };
988
+ }
989
+ listenWindowResize() {
990
+ fromEvent(window, 'resize')
991
+ .pipe(throttleTime(0, animationFrameScheduler, { trailing: true }), takeUntilDestroyed(this.destroyRef))
992
+ .subscribe(() => {
993
+ this.measure();
994
+ this.recalculateChildren();
995
+ });
996
+ }
997
+ listenShortcuts() {
998
+ const itemsWithShortcuts = getMenuItemsShortcuts(this.parsedItems);
999
+ const shortcuts = itemsWithShortcuts.map((it) => it.shortcut);
1000
+ this.shortcutMap = this.getShortcutMap(itemsWithShortcuts);
1001
+ this.shortcutSub.unsubscribe();
1002
+ this.shortcutSub = this.shortcutService
1003
+ .listen(shortcuts)
1004
+ .pipe(takeUntilDestroyed(this.destroyRef))
1005
+ .subscribe((evt) => this.onShortcut(evt.shortcut));
1006
+ }
1007
+ getShortcutMap(itemsWithShortcuts) {
1008
+ const shortcutMap = {};
1009
+ itemsWithShortcuts.forEach((item) => {
1010
+ shortcutMap[item.shortcut.hotkeysValue] = item.path;
1011
+ });
1012
+ return shortcutMap;
1013
+ }
1014
+ onShortcut(shortcut) {
1015
+ const path = this.shortcutMap[shortcut.hotkeysValue];
1016
+ if (!path) {
1017
+ console.warn(`Menu shortcut ${shortcut.hotkeysValue} was detected, but there is no path associated with it.`, this.shortcutMap);
1018
+ }
1019
+ const last = path[path.length - 1];
1020
+ const hasRoute = menuItemHasRoute(last);
1021
+ const hasCommand = typeof last.command === 'function';
1022
+ if (hasRoute || hasCommand) {
1023
+ this.closeSubmenus(false);
1024
+ if (hasCommand) {
1025
+ last.command();
1026
+ }
1027
+ if (hasRoute) {
1028
+ this.router.navigate(last.routerLink);
1029
+ }
1030
+ }
1031
+ else {
1032
+ this.activateItemRecursively(path);
1033
+ }
1034
+ }
1035
+ activateItemRecursively(path) {
1036
+ const children = [...path];
1037
+ if (this.shortcutMode === RdtMenuShortcutMode.OPEN_SUBMENU) {
1038
+ const last = children[children.length - 1];
1039
+ if (menuItemHasChildren(last) && last.items[0]) {
1040
+ children.push(last.items[0]);
1041
+ }
1042
+ }
1043
+ const thisItem = children.shift();
1044
+ if (children.length > 0) {
1045
+ this.expanded = thisItem;
1046
+ const overlay = this.children.find((child) => child.item === thisItem);
1047
+ overlay.focusItemRecursively(children);
1048
+ }
1049
+ else {
1050
+ this.expanded = null;
1051
+ this.focusItem(thisItem);
1052
+ }
1053
+ }
1054
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1055
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.2.7", type: RdtMenuBaseComponent, inputs: { preferredVerticalDir: "preferredVerticalDir", preferredHorizontalDir: "preferredHorizontalDir", shortcutMode: "shortcutMode", closeOnFocusOut: ["closeOnFocusOut", "closeOnFocusOut", booleanAttribute] }, host: { listeners: { "window:focusout": "checkActiveElement($event)", "document:click": "onDocumentClick($event)" }, properties: { "class": "this.classes" } }, viewQueries: [{ propertyName: "children", predicate: RdtMenuOverlayComponent, descendants: true }, { propertyName: "focusableElements", predicate: ["focusableItem"], descendants: true }], usesOnChanges: true, ngImport: i0 });
1056
+ }
1057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuBaseComponent, decorators: [{
1058
+ type: Directive
1059
+ }], propDecorators: { preferredVerticalDir: [{
1060
+ type: Input
1061
+ }], preferredHorizontalDir: [{
1062
+ type: Input
1063
+ }], shortcutMode: [{
1064
+ type: Input
1065
+ }], closeOnFocusOut: [{
1066
+ type: Input,
1067
+ args: [{ transform: booleanAttribute }]
1068
+ }], children: [{
1069
+ type: ViewChildren,
1070
+ args: [RdtMenuOverlayComponent]
1071
+ }], focusableElements: [{
1072
+ type: ViewChildren,
1073
+ args: ['focusableItem']
1074
+ }], classes: [{
1075
+ type: HostBinding,
1076
+ args: ['class']
1077
+ }], checkActiveElement: [{
1078
+ type: HostListener,
1079
+ args: ['window:focusout', ['$event']]
1080
+ }], onDocumentClick: [{
1081
+ type: HostListener,
1082
+ args: ['document:click', ['$event']]
1083
+ }] } });
1084
+
1085
+ class RdtMenuComponent extends RdtMenuBaseComponent {
1086
+ buttonClass = inject(RDT_BUTTON_BASE_PROVIDER);
1087
+ dataTestId = '';
1088
+ get item() {
1089
+ return this._item;
1090
+ }
1091
+ set item(value) {
1092
+ this._item = value;
1093
+ this.allParsedItems = parseMenuItems([value], this.injector);
1094
+ this.filterItems();
1095
+ }
1096
+ _item;
1097
+ button;
1098
+ get parsedItem() {
1099
+ return this.parsedItems[0];
1100
+ }
1101
+ get buttonInputs() {
1102
+ return {
1103
+ 'attr.role': 'menuitem',
1104
+ 'attr.aria-haspopup': 'menu',
1105
+ 'attr.aria-expanded': this.expanded === this.parsedItem,
1106
+ tabIndex: 0,
1107
+ dataTestId: this.dataTestId,
1108
+ text: this.parsedItem.label,
1109
+ icon: this.parsedItem.icon,
1110
+ };
1111
+ }
1112
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1113
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: RdtMenuComponent, selector: "rdt-menu", inputs: { dataTestId: "dataTestId", item: "item" }, providers: [
1114
+ {
1115
+ provide: RdtMenuBaseComponent,
1116
+ useExisting: RdtMenuComponent,
1117
+ },
1118
+ ], viewQueries: [{ propertyName: "button", first: true, predicate: RdtButtonBase, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<ng-container\r\n #focusableItem\r\n *ngComponentOutlet=\"buttonClass; inputs: buttonInputs\"\r\n/>\r\n\r\n<rdt-menu-overlay\r\n *ngIf=\"parsedItem\"\r\n [item]=\"$any(parsedItem)\"\r\n [expanded]=\"expanded === parsedItem\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n/>\r\n<!-- TODO: Fix\r\n<rdt-menu-overlay\r\n *ngIf=\"parsedItem\"\r\n [anchorElement]=\"focusableItem.elRef.nativeElement\"\r\n [item]=\"$any(parsedItem)\"\r\n [expanded]=\"expanded === parsedItem\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n/>\r\n-->\r\n", styles: ["rdt-menu{display:block;position:relative}\n"], dependencies: [{ kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: RdtMenuOverlayComponent, selector: "rdt-menu-overlay", inputs: ["item", "level", "preferredHorizontalDir", "preferredVerticalDir", "expanded", "autofocusItem", "anchorElement"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1119
+ }
1120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuComponent, decorators: [{
1121
+ type: Component,
1122
+ args: [{ selector: 'rdt-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
1123
+ {
1124
+ provide: RdtMenuBaseComponent,
1125
+ useExisting: RdtMenuComponent,
1126
+ },
1127
+ ], template: "<ng-container\r\n #focusableItem\r\n *ngComponentOutlet=\"buttonClass; inputs: buttonInputs\"\r\n/>\r\n\r\n<rdt-menu-overlay\r\n *ngIf=\"parsedItem\"\r\n [item]=\"$any(parsedItem)\"\r\n [expanded]=\"expanded === parsedItem\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n/>\r\n<!-- TODO: Fix\r\n<rdt-menu-overlay\r\n *ngIf=\"parsedItem\"\r\n [anchorElement]=\"focusableItem.elRef.nativeElement\"\r\n [item]=\"$any(parsedItem)\"\r\n [expanded]=\"expanded === parsedItem\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n/>\r\n-->\r\n", styles: ["rdt-menu{display:block;position:relative}\n"] }]
1128
+ }], propDecorators: { dataTestId: [{
1129
+ type: Input
1130
+ }], item: [{
1131
+ type: Input,
1132
+ args: [{ required: true }]
1133
+ }], button: [{
1134
+ type: ViewChild,
1135
+ args: [RdtButtonBase, { static: true }]
1136
+ }] } });
1137
+
1138
+ class RdtMenuBarComponent extends RdtMenuBaseComponent {
1139
+ get items() {
1140
+ return this._items;
1141
+ }
1142
+ set items(value) {
1143
+ this._items = value;
1144
+ this.allParsedItems = parseMenuItems(value, this.injector);
1145
+ this.filterItems();
1146
+ }
1147
+ _items;
1148
+ headerHeight = 0;
1149
+ footerHeight = 0;
1150
+ roleAttr = 'menubar';
1151
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuBarComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1152
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "18.2.7", type: RdtMenuBarComponent, selector: "rdt-menu-bar", inputs: { items: "items", headerHeight: ["headerHeight", "headerHeight", numberAttribute], footerHeight: ["footerHeight", "footerHeight", numberAttribute] }, host: { properties: { "attr.role": "this.roleAttr" } }, providers: [
1153
+ {
1154
+ provide: RdtMenuBaseComponent,
1155
+ useExisting: RdtMenuBarComponent,
1156
+ },
1157
+ ], usesInheritance: true, ngImport: i0, template: "<ul class=\"menu-bar-item-container\" role=\"presentation\">\r\n <li\r\n *ngFor=\"let item of parsedItems; let i = index\"\r\n class=\"menu-bar-item\"\r\n role=\"presentation\"\r\n rdtAnyRouteActive=\"menu-bar-item-route-active\"\r\n [watchedRoutes]=\"getChildRoutes(item)\"\r\n (rdtKeyListener)=\"onKeyDown(i, $event)\"\r\n #anchorEl\r\n >\r\n <ng-container *ngIf=\"!item.routerLink && !item.externalLink\">\r\n <button\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [attr.aria-haspopup]=\"item.items ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item === expanded\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n {{ item.label }}\r\n\r\n <div class=\"menu-bar-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO\r\n <vnsh-icon\r\n *ngIf=\"item.items\"\r\n name=\"expand_more\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n -->\r\n <span class=\"menu-bar-item-icon\" *ngIf=\"item.items\">v</span>\r\n </div>\r\n </button>\r\n </ng-container>\r\n\r\n <ng-template #linkBody let-item>\r\n {{ item.label }}\r\n\r\n <div class=\"menu-bar-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!--\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n --></div>\r\n </ng-template>\r\n\r\n <ng-container *ngIf=\"item.routerLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [routerLink]=\"item.routerLink\"\r\n [target]=\"item.target!\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"item.externalLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [href]=\"item.externalLink\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <rdt-menu-overlay\r\n *ngIf=\"item.items\"\r\n [item]=\"$any(item)\"\r\n [expanded]=\"item === expanded\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n [anchorElement]=\"anchorEl\"\r\n />\r\n </li>\r\n</ul>\r\n", styles: [":root{--rdt-menu-min-width: 12rem;--rdt-menu-border-radius: 5px;--rdt-menu-padding-horizontal-padding: .75rem;--rdt-menu-box-shadow: 0px 5px 6px -3px rgba(0, 0, 0, .2), 0px 9px 12px 1px rgba(0, 0, 0, .14), 0px 3px 16px 2px rgba(0, 0, 0, .12);--rdt-menu-bar-item-background: transparent;--rdt-menu-bar-item-background-expanded: rgb(64, 116, 188);--rdt-menu-bar-item-background-route-active: var( --rdt-menu-bar-item-background-expanded );--rdt-menu-bar-item-background-hover: rgb(64, 116, 188);--rdt-menu-bar-item-background-focus: rgb(64, 116, 188);--rdt-menu-bar-item-background-disabled: black;--rdt-menu-bar-item-padding: .5rem;--rdt-menu-bar-item-border-radius: 5px;--rdt-menu-item-background: var(--white);--rdt-menu-item-background-hover: rgba(var(--primary-blue-rgb), .08);--rdt-menu-item-background-expanded: rgba(var(--primary-blue-rgb), .16);--rdt-menu-item-background-route-active: var( --rdt-menu-item-background-expanded );--rdt-menu-item-background-focus: var(--rdt-menu-item-background-expanded);--rdt-menu-item-background-disabled: black;--rdt-menu-item-text-color: var(--black);--rdt-menu-item-padding: .5rem 1rem;--rdt-menu-margin-top: var(--dp3-header-height);--rdt-menu-margin-bottom: var(--dp3-footer-height)}a.menu-bar-item-content,a.menu-item-content,button.menu-bar-item-content,button.menu-item-content{white-space:nowrap;appearance:none;border:none;text-decoration:none}.menu-item-right-content,.menu-bar-item-right-content{margin-left:auto;display:flex;align-items:center}.menu-item-shortcut,.menu-bar-item-shortcut{margin-left:2rem}.menu-item-icon,.menu-bar-item-icon{margin-left:.5rem;font-size:1.2rem}.dp3-menu-base ul{margin-bottom:0}rdt-menu-bar{-webkit-user-select:none;user-select:none;pointer-events:none;padding:var(--sub-menu-padding)}.menu-bar-item-container{display:flex;list-style-type:none;margin-bottom:0;padding-left:0}.menu-bar-item{cursor:pointer;pointer-events:all;color:var(--white);margin:0 .5rem;position:relative;display:flex;align-items:center;justify-content:center}.menu-bar-item.menu-bar-item-route-active .menu-bar-item-content{background-color:var(--rdt-menu-bar-item-background-route-active)}.menu-bar-item[aria-expanded=true] .menu-bar-item-content{background-color:var(--rdt-menu-bar-item-background-expanded)}.menu-bar-item-content{display:flex;align-items:center;padding:var(--rdt-menu-bar-item-padding);color:inherit!important;border-radius:var(--rdt-menu-bar-item-border-radius);overflow:hidden;background-color:var(--rdt-menu-bar-item-background)}.menu-bar-item-content:hover{background-color:var(--rdt-menu-bar-item-background-hover)}.menu-bar-item-content.focus-visible:focus,.menu-bar-item-content:focus-visible{outline:none;background-color:var(--rdt-menu-bar-item-background-focus)}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i3.RdtAnyRouteActiveDirective, selector: "[rdtAnyRouteActive]", inputs: ["anyRouteActive", "watchedRoutes", "anyRouteActiveOptions", "ariaCurrentWhenActive"] }, { kind: "directive", type: i4.RdtKeyListenerDirective, selector: "[rdtKeyListener]", outputs: ["rdtKeyListener"] }, { kind: "component", type: RdtMenuOverlayComponent, selector: "rdt-menu-overlay", inputs: ["item", "level", "preferredHorizontalDir", "preferredVerticalDir", "expanded", "autofocusItem", "anchorElement"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1158
+ }
1159
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuBarComponent, decorators: [{
1160
+ type: Component,
1161
+ args: [{ selector: 'rdt-menu-bar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
1162
+ {
1163
+ provide: RdtMenuBaseComponent,
1164
+ useExisting: RdtMenuBarComponent,
1165
+ },
1166
+ ], template: "<ul class=\"menu-bar-item-container\" role=\"presentation\">\r\n <li\r\n *ngFor=\"let item of parsedItems; let i = index\"\r\n class=\"menu-bar-item\"\r\n role=\"presentation\"\r\n rdtAnyRouteActive=\"menu-bar-item-route-active\"\r\n [watchedRoutes]=\"getChildRoutes(item)\"\r\n (rdtKeyListener)=\"onKeyDown(i, $event)\"\r\n #anchorEl\r\n >\r\n <ng-container *ngIf=\"!item.routerLink && !item.externalLink\">\r\n <button\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [attr.aria-haspopup]=\"item.items ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item === expanded\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n {{ item.label }}\r\n\r\n <div class=\"menu-bar-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!-- TODO\r\n <vnsh-icon\r\n *ngIf=\"item.items\"\r\n name=\"expand_more\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n -->\r\n <span class=\"menu-bar-item-icon\" *ngIf=\"item.items\">v</span>\r\n </div>\r\n </button>\r\n </ng-container>\r\n\r\n <ng-template #linkBody let-item>\r\n {{ item.label }}\r\n\r\n <div class=\"menu-bar-item-right-content\">\r\n <span *ngIf=\"item.shortcut\" class=\"menu-item-shortcut\">\r\n {{ item.shortcut.label }}\r\n </span>\r\n <!--\r\n <vnsh-icon\r\n *ngIf=\"item.icon\"\r\n [name]=\"item.icon\"\r\n class=\"menu-bar-item-icon\"\r\n inverted\r\n />\r\n --></div>\r\n </ng-template>\r\n\r\n <ng-container *ngIf=\"item.routerLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [routerLink]=\"item.routerLink\"\r\n [target]=\"item.target!\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"item.externalLink\">\r\n <a\r\n #focusableItem\r\n class=\"menu-bar-item-content\"\r\n role=\"menuitem\"\r\n (click)=\"onItemClick(item)\"\r\n [href]=\"item.externalLink\"\r\n [target]=\"item.target\"\r\n [attr.tabindex]=\"0\"\r\n [attr.data-testid]=\"item.dataTestId\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"linkBody; context: { $implicit: item }\"\r\n />\r\n </a>\r\n </ng-container>\r\n\r\n <rdt-menu-overlay\r\n *ngIf=\"item.items\"\r\n [item]=\"$any(item)\"\r\n [expanded]=\"item === expanded\"\r\n [autofocusItem]=\"autofocusSubmenuItem\"\r\n [preferredHorizontalDir]=\"preferredHorizontalDir\"\r\n [preferredVerticalDir]=\"preferredVerticalDir\"\r\n [level]=\"0\"\r\n [anchorElement]=\"anchorEl\"\r\n />\r\n </li>\r\n</ul>\r\n", styles: [":root{--rdt-menu-min-width: 12rem;--rdt-menu-border-radius: 5px;--rdt-menu-padding-horizontal-padding: .75rem;--rdt-menu-box-shadow: 0px 5px 6px -3px rgba(0, 0, 0, .2), 0px 9px 12px 1px rgba(0, 0, 0, .14), 0px 3px 16px 2px rgba(0, 0, 0, .12);--rdt-menu-bar-item-background: transparent;--rdt-menu-bar-item-background-expanded: rgb(64, 116, 188);--rdt-menu-bar-item-background-route-active: var( --rdt-menu-bar-item-background-expanded );--rdt-menu-bar-item-background-hover: rgb(64, 116, 188);--rdt-menu-bar-item-background-focus: rgb(64, 116, 188);--rdt-menu-bar-item-background-disabled: black;--rdt-menu-bar-item-padding: .5rem;--rdt-menu-bar-item-border-radius: 5px;--rdt-menu-item-background: var(--white);--rdt-menu-item-background-hover: rgba(var(--primary-blue-rgb), .08);--rdt-menu-item-background-expanded: rgba(var(--primary-blue-rgb), .16);--rdt-menu-item-background-route-active: var( --rdt-menu-item-background-expanded );--rdt-menu-item-background-focus: var(--rdt-menu-item-background-expanded);--rdt-menu-item-background-disabled: black;--rdt-menu-item-text-color: var(--black);--rdt-menu-item-padding: .5rem 1rem;--rdt-menu-margin-top: var(--dp3-header-height);--rdt-menu-margin-bottom: var(--dp3-footer-height)}a.menu-bar-item-content,a.menu-item-content,button.menu-bar-item-content,button.menu-item-content{white-space:nowrap;appearance:none;border:none;text-decoration:none}.menu-item-right-content,.menu-bar-item-right-content{margin-left:auto;display:flex;align-items:center}.menu-item-shortcut,.menu-bar-item-shortcut{margin-left:2rem}.menu-item-icon,.menu-bar-item-icon{margin-left:.5rem;font-size:1.2rem}.dp3-menu-base ul{margin-bottom:0}rdt-menu-bar{-webkit-user-select:none;user-select:none;pointer-events:none;padding:var(--sub-menu-padding)}.menu-bar-item-container{display:flex;list-style-type:none;margin-bottom:0;padding-left:0}.menu-bar-item{cursor:pointer;pointer-events:all;color:var(--white);margin:0 .5rem;position:relative;display:flex;align-items:center;justify-content:center}.menu-bar-item.menu-bar-item-route-active .menu-bar-item-content{background-color:var(--rdt-menu-bar-item-background-route-active)}.menu-bar-item[aria-expanded=true] .menu-bar-item-content{background-color:var(--rdt-menu-bar-item-background-expanded)}.menu-bar-item-content{display:flex;align-items:center;padding:var(--rdt-menu-bar-item-padding);color:inherit!important;border-radius:var(--rdt-menu-bar-item-border-radius);overflow:hidden;background-color:var(--rdt-menu-bar-item-background)}.menu-bar-item-content:hover{background-color:var(--rdt-menu-bar-item-background-hover)}.menu-bar-item-content.focus-visible:focus,.menu-bar-item-content:focus-visible{outline:none;background-color:var(--rdt-menu-bar-item-background-focus)}\n"] }]
1167
+ }], propDecorators: { items: [{
1168
+ type: Input
1169
+ }], headerHeight: [{
1170
+ type: Input,
1171
+ args: [{ transform: numberAttribute }]
1172
+ }], footerHeight: [{
1173
+ type: Input,
1174
+ args: [{ transform: numberAttribute }]
1175
+ }], roleAttr: [{
1176
+ type: HostBinding,
1177
+ args: ['attr.role']
1178
+ }] } });
1179
+
1180
+ class RdtMenuModule {
1181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1182
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuModule, declarations: [RdtMenuOverlayComponent,
1183
+ RdtMenuComponent,
1184
+ RdtMenuBarComponent], imports: [CommonModule,
1185
+ RouterModule,
1186
+ RdtAnyRouteActiveDirective,
1187
+ RdtKeyListenerDirective], exports: [RdtMenuComponent, RdtMenuBarComponent] });
1188
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuModule, imports: [CommonModule,
1189
+ RouterModule] });
1190
+ }
1191
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: RdtMenuModule, decorators: [{
1192
+ type: NgModule,
1193
+ args: [{
1194
+ imports: [
1195
+ CommonModule,
1196
+ RouterModule,
1197
+ RdtAnyRouteActiveDirective,
1198
+ RdtKeyListenerDirective,
1199
+ ],
1200
+ declarations: [
1201
+ RdtMenuOverlayComponent,
1202
+ RdtMenuComponent,
1203
+ RdtMenuBarComponent,
1204
+ ],
1205
+ exports: [RdtMenuComponent, RdtMenuBarComponent],
1206
+ }]
1207
+ }] });
1208
+
1209
+ /**
1210
+ * Generated bundle index. Do not edit.
1211
+ */
1212
+
1213
+ export { DEFAULT_MENU_HORIZONTAL_DIR, DEFAULT_MENU_VERTICAL_DIR, RDT_MENU_HORIZONTAL_DIR_PROVIDER, RDT_MENU_MARGIN_BOTTOM_PROPERTY_NAME, RDT_MENU_MARGIN_LEFT_PROPERTY_NAME, RDT_MENU_MARGIN_RIGHT_PROPERTY_NAME, RDT_MENU_MARGIN_TOP_PROPERTY_NAME, RDT_MENU_VERTICAL_DIR_PROVIDER, RdtMenuBarComponent, RdtMenuComponent, RdtMenuModule, RdtMenuShortcutMode };
1214
+ //# sourceMappingURL=ngrdt-menu.mjs.map