@theseam/ui-common 0.4.1 → 0.4.2

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 (56) hide show
  1. package/esm2020/datatable/datatable-action-menu/datatable-action-menu.component.mjs +1 -1
  2. package/esm2020/datatable/datatable-column-preferences-button/datatable-column-preferences-button.component.mjs +1 -1
  3. package/esm2020/datatable/datatable-export-button/datatable-export-button.component.mjs +1 -1
  4. package/esm2020/datatable-dynamic/datatable-dynamic-action-menu/datatable-dynamic-action-menu.component.mjs +1 -1
  5. package/esm2020/framework/schema-form-controls/schema-form-submit-split/schema-form-submit-split.component.mjs +1 -1
  6. package/esm2020/framework/top-bar/top-bar.component.mjs +1 -1
  7. package/esm2020/google-maps/google-maps/google-maps.component.mjs +32 -8
  8. package/esm2020/google-maps/google-maps-feature-helpers.mjs +13 -1
  9. package/esm2020/google-maps/google-maps.module.mjs +4 -2
  10. package/esm2020/google-maps/google-maps.service.mjs +96 -25
  11. package/esm2020/menu/menu-item.component.mjs +43 -15
  12. package/esm2020/menu/menu-toggle.directive.mjs +202 -43
  13. package/esm2020/menu/menu.component.mjs +37 -14
  14. package/esm2020/utils/geo-json/close-polygons.mjs +38 -0
  15. package/esm2020/utils/public-api.mjs +2 -1
  16. package/esm2020/widget/widget/widget.component.mjs +54 -33
  17. package/fesm2015/theseam-ui-common-datatable-dynamic.mjs +1 -1
  18. package/fesm2015/theseam-ui-common-datatable-dynamic.mjs.map +1 -1
  19. package/fesm2015/theseam-ui-common-datatable.mjs +3 -3
  20. package/fesm2015/theseam-ui-common-datatable.mjs.map +1 -1
  21. package/fesm2015/theseam-ui-common-framework.mjs +2 -2
  22. package/fesm2015/theseam-ui-common-framework.mjs.map +1 -1
  23. package/fesm2015/theseam-ui-common-google-maps.mjs +138 -28
  24. package/fesm2015/theseam-ui-common-google-maps.mjs.map +1 -1
  25. package/fesm2015/theseam-ui-common-menu.mjs +442 -228
  26. package/fesm2015/theseam-ui-common-menu.mjs.map +1 -1
  27. package/fesm2015/theseam-ui-common-utils.mjs +39 -1
  28. package/fesm2015/theseam-ui-common-utils.mjs.map +1 -1
  29. package/fesm2015/theseam-ui-common-widget.mjs +53 -32
  30. package/fesm2015/theseam-ui-common-widget.mjs.map +1 -1
  31. package/fesm2020/theseam-ui-common-datatable-dynamic.mjs +1 -1
  32. package/fesm2020/theseam-ui-common-datatable-dynamic.mjs.map +1 -1
  33. package/fesm2020/theseam-ui-common-datatable.mjs +3 -3
  34. package/fesm2020/theseam-ui-common-datatable.mjs.map +1 -1
  35. package/fesm2020/theseam-ui-common-framework.mjs +2 -2
  36. package/fesm2020/theseam-ui-common-framework.mjs.map +1 -1
  37. package/fesm2020/theseam-ui-common-google-maps.mjs +141 -32
  38. package/fesm2020/theseam-ui-common-google-maps.mjs.map +1 -1
  39. package/fesm2020/theseam-ui-common-menu.mjs +432 -225
  40. package/fesm2020/theseam-ui-common-menu.mjs.map +1 -1
  41. package/fesm2020/theseam-ui-common-utils.mjs +39 -1
  42. package/fesm2020/theseam-ui-common-utils.mjs.map +1 -1
  43. package/fesm2020/theseam-ui-common-widget.mjs +53 -32
  44. package/fesm2020/theseam-ui-common-widget.mjs.map +1 -1
  45. package/google-maps/google-maps/google-maps.component.d.ts +8 -1
  46. package/google-maps/google-maps-feature-helpers.d.ts +3 -0
  47. package/google-maps/google-maps.module.d.ts +1 -1
  48. package/google-maps/google-maps.service.d.ts +7 -0
  49. package/menu/menu-item.component.d.ts +20 -9
  50. package/menu/menu-toggle.directive.d.ts +34 -11
  51. package/menu/menu.component.d.ts +16 -2
  52. package/package.json +1 -1
  53. package/utils/geo-json/close-polygons.d.ts +9 -0
  54. package/utils/public-api.d.ts +1 -0
  55. package/widget/widget/widget.component.d.ts +10 -11
  56. package/widget/widget/widget.component.scss +12 -1
@@ -1,21 +1,23 @@
1
1
  import { animation, style, group, animate, query, useAnimation, trigger, transition } from '@angular/animations';
2
2
  import * as i0 from '@angular/core';
3
- import { Component, ChangeDetectionStrategy, InjectionToken, Inject, Optional, Input, HostListener, Directive, forwardRef, EventEmitter, TemplateRef, ViewChild, Output, HostBinding, NgModule } from '@angular/core';
3
+ import { Component, ChangeDetectionStrategy, InjectionToken, Inject, Optional, Input, HostListener, forwardRef, EventEmitter, TemplateRef, ViewChild, Output, inject, ChangeDetectorRef, Directive, Self, HostBinding, NgModule } from '@angular/core';
4
4
  import * as i2 from '@angular/common';
5
5
  import { DOCUMENT, CommonModule } from '@angular/common';
6
- import { Subject, Subscription, of, merge, BehaviorSubject, fromEvent } from 'rxjs';
6
+ import { Subject, BehaviorSubject, Subscription, fromEvent, of, merge, filter, delay, asapScheduler, take, takeUntil as takeUntil$1 } from 'rxjs';
7
+ import { faCaretRight } from '@fortawesome/free-solid-svg-icons';
7
8
  import { mixinDisabled } from '@theseam/ui-common/core';
8
9
  import * as i1 from '@angular/cdk/a11y';
9
- import { isFakeMousedownFromScreenReader, FocusKeyManager } from '@angular/cdk/a11y';
10
+ import { FocusKeyManager, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y';
10
11
  import * as i3 from '@theseam/ui-common/icon';
11
12
  import { TheSeamIconModule } from '@theseam/ui-common/icon';
12
- import { UP_ARROW, DOWN_ARROW, ESCAPE, END, hasModifierKey, HOME } from '@angular/cdk/keycodes';
13
+ import { UP_ARROW, DOWN_ARROW, END, hasModifierKey, HOME, RIGHT_ARROW, LEFT_ARROW, ESCAPE, ENTER, SPACE } from '@angular/cdk/keycodes';
13
14
  import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
14
15
  import { TemplatePortal } from '@angular/cdk/portal';
15
- import * as i1$1 from '@angular/cdk/overlay';
16
- import { OverlayModule } from '@angular/cdk/overlay';
17
16
  import { coerceNumberProperty } from '@angular/cdk/coercion';
18
17
  import { map, switchMap, startWith, distinctUntilChanged, takeUntil } from 'rxjs/operators';
18
+ import * as i1$1 from '@angular/cdk/overlay';
19
+ import { OverlayModule } from '@angular/cdk/overlay';
20
+ import * as i4 from '@angular/cdk/bidi';
19
21
 
20
22
  const menuDropdownPanelSlideIn = animation([
21
23
  style({
@@ -94,25 +96,25 @@ class TheSeamMenuItemBase {
94
96
  }
95
97
  const _seamMenuItemMixinBase = mixinDisabled(TheSeamMenuItemBase);
96
98
  class MenuItemComponent extends _seamMenuItemMixinBase {
97
- constructor(_elementRef, document, _focusMonitor, _parentMenu) {
99
+ constructor(_elementRef, _document, _focusMonitor, _changeDetectorRef, _parentMenu) {
98
100
  super();
99
101
  this._elementRef = _elementRef;
100
- this.document = document;
102
+ this._document = _document;
101
103
  this._focusMonitor = _focusMonitor;
104
+ this._changeDetectorRef = _changeDetectorRef;
102
105
  this._parentMenu = _parentMenu;
103
106
  /** ARIA role for the menu item. */
104
107
  this.role = 'menuitem';
108
+ this.sublevelIcon = faCaretRight;
105
109
  this.badgeTheme = 'danger';
106
110
  /** Stream that emits when the menu item is hovered. */
107
111
  this._hovered = new Subject();
112
+ /** Stream that emits when the menu item is focused. */
113
+ this._focused = new Subject();
108
114
  /** Whether the menu item is highlighted. */
109
115
  this._highlighted = false;
110
- if (_focusMonitor) {
111
- // Start monitoring the element so it gets the appropriate focused classes. We want
112
- // to show the focus style for menu items only when the focus was not caused by a
113
- // mouse or touch interaction.
114
- _focusMonitor.monitor(this._elementRef, false);
115
- }
116
+ /** Whether the menu item acts as a trigger for a sub-menu. */
117
+ this._triggersSubmenu = false;
116
118
  // console.log(this._parentMenu)
117
119
  if (_parentMenu && _parentMenu.addItem) {
118
120
  _parentMenu.addItem(this);
@@ -126,6 +128,15 @@ class MenuItemComponent extends _seamMenuItemMixinBase {
126
128
  this._parentMenu.removeItem(this);
127
129
  }
128
130
  this._hovered.complete();
131
+ this._focused.complete();
132
+ }
133
+ ngAfterViewInit() {
134
+ if (this._focusMonitor) {
135
+ // Start monitoring the element, so it gets the appropriate focused classes. We want
136
+ // to show the focus style for menu items only when the focus was not caused by a
137
+ // mouse or touch interaction.
138
+ this._focusMonitor.monitor(this._elementRef, false);
139
+ }
129
140
  }
130
141
  /** Focuses the menu item. */
131
142
  focus(origin = 'program') {
@@ -135,6 +146,7 @@ class MenuItemComponent extends _seamMenuItemMixinBase {
135
146
  else {
136
147
  this._getHostElement().focus();
137
148
  }
149
+ this._focused.next(this);
138
150
  }
139
151
  /** Used to set the `tabindex`. */
140
152
  _getTabIndex() {
@@ -158,7 +170,7 @@ class MenuItemComponent extends _seamMenuItemMixinBase {
158
170
  /** Gets the label to be used when determining whether the option should be focused. */
159
171
  getLabel() {
160
172
  const element = this._elementRef.nativeElement;
161
- const textNodeType = this.document ? this.document.TEXT_NODE : 3;
173
+ const textNodeType = this._document ? this._document.TEXT_NODE : 3;
162
174
  let output = '';
163
175
  if (element.childNodes) {
164
176
  const length = element.childNodes.length;
@@ -173,23 +185,38 @@ class MenuItemComponent extends _seamMenuItemMixinBase {
173
185
  }
174
186
  return output.trim();
175
187
  }
188
+ _setHighlighted(isHighlighted) {
189
+ var _a;
190
+ this._highlighted = isHighlighted;
191
+ (_a = this._changeDetectorRef) === null || _a === void 0 ? void 0 : _a.markForCheck();
192
+ }
193
+ _setTriggersSubmenu(triggersSubmenu) {
194
+ var _a;
195
+ this._triggersSubmenu = triggersSubmenu;
196
+ (_a = this._changeDetectorRef) === null || _a === void 0 ? void 0 : _a.markForCheck();
197
+ }
198
+ _hasFocus() {
199
+ return this._document && this._document.activeElement === this._getHostElement();
200
+ }
176
201
  }
177
- MenuItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuItemComponent, deps: [{ token: i0.ElementRef }, { token: DOCUMENT }, { token: i1.FocusMonitor }, { token: THESEAM_MENU_PANEL, optional: true }], target: i0.ɵɵFactoryTarget.Component });
178
- MenuItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuItemComponent, selector: "[seamMenuItem]", inputs: { disabled: "disabled", role: "role", icon: "icon", iconClass: "iconClass", badgeText: "badgeText", badgeTheme: "badgeTheme" }, host: { listeners: { "click": "_checkDisabled($event)", "mouseenter": "_handleMouseEnter()" }, properties: { "attr.role": "role", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled.toString()", "attr.disabled": "disabled || null" }, classAttribute: "dropdown-item" }, exportAs: ["seamMenuItem"], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex flex-row\">\n <seam-icon *ngIf=\"icon\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pr-2\"\n [icon]=\"icon\"\n [iconClass]=\"iconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n <!-- <span class=\"text-nowrap\">\n <ng-content></ng-content>\n </span> -->\n <div class=\"text-truncate\">\n <ng-content></ng-content>\n </div>\n\n <div *ngIf=\"badgeText\">\n <span class=\"badge badge-pill{{ badgeTheme ? ' badge-' + badgeTheme : '' }} position-relative\"\n style=\"top: -8px\"\n >{{ badgeText }}</span>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.IconComponent, selector: "seam-icon", inputs: ["grayscaleOnDisable", "disabled", "iconClass", "icon", "size", "showDefaultOnError", "defaultIcon", "iconType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
202
+ MenuItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuItemComponent, deps: [{ token: i0.ElementRef }, { token: DOCUMENT }, { token: i1.FocusMonitor }, { token: i0.ChangeDetectorRef }, { token: THESEAM_MENU_PANEL, optional: true }], target: i0.ɵɵFactoryTarget.Component });
203
+ MenuItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuItemComponent, selector: "[seamMenuItem]", inputs: { disabled: "disabled", role: "role", icon: "icon", iconClass: "iconClass", sublevelIcon: "sublevelIcon", subLevelIconClass: "subLevelIconClass", badgeText: "badgeText", badgeTheme: "badgeTheme" }, host: { listeners: { "click": "_checkDisabled($event)", "mouseenter": "_handleMouseEnter()" }, properties: { "attr.role": "role", "class.seam-menu-item-highlighted": "_highlighted", "class.seam-menu-item-submenu-trigger": "_triggersSubmenu", "attr.tabindex": "_getTabIndex()", "attr.aria-disabled": "disabled.toString()", "attr.disabled": "disabled || null" }, classAttribute: "seam-menu-item dropdown-item" }, exportAs: ["seamMenuItem"], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex flex-row\">\n <seam-icon *ngIf=\"icon\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pr-2\"\n [icon]=\"icon\"\n [iconClass]=\"iconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n <div class=\"flex-fill\">\n <div class=\"text-truncate\">\n <ng-content></ng-content>\n </div>\n\n <div *ngIf=\"badgeText\">\n <span class=\"badge badge-pill{{ badgeTheme ? ' badge-' + badgeTheme : '' }} position-relative\"\n style=\"top: -8px\"\n >{{ badgeText }}</span>\n </div>\n </div>\n <seam-icon *ngIf=\"_triggersSubmenu\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pl-2\"\n [icon]=\"sublevelIcon\"\n [iconClass]=\"subLevelIconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n</div>\n", styles: [":host.cdk-mouse-focused:not(:hover){background:transparent}:host[aria-expanded]{color:#16181b;text-decoration:none;background-color:#e9ecef}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.IconComponent, selector: "seam-icon", inputs: ["grayscaleOnDisable", "disabled", "iconClass", "icon", "size", "showDefaultOnError", "defaultIcon", "iconType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
179
204
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuItemComponent, decorators: [{
180
205
  type: Component,
181
206
  args: [{ selector: '[seamMenuItem]', exportAs: 'seamMenuItem', inputs: ['disabled'], host: {
182
207
  '[attr.role]': 'role',
183
- 'class': 'dropdown-item',
208
+ 'class': 'seam-menu-item dropdown-item',
209
+ '[class.seam-menu-item-highlighted]': '_highlighted',
210
+ '[class.seam-menu-item-submenu-trigger]': '_triggersSubmenu',
184
211
  '[attr.tabindex]': '_getTabIndex()',
185
212
  '[attr.aria-disabled]': 'disabled.toString()',
186
213
  '[attr.disabled]': 'disabled || null',
187
- }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"d-flex flex-row\">\n <seam-icon *ngIf=\"icon\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pr-2\"\n [icon]=\"icon\"\n [iconClass]=\"iconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n <!-- <span class=\"text-nowrap\">\n <ng-content></ng-content>\n </span> -->\n <div class=\"text-truncate\">\n <ng-content></ng-content>\n </div>\n\n <div *ngIf=\"badgeText\">\n <span class=\"badge badge-pill{{ badgeTheme ? ' badge-' + badgeTheme : '' }} position-relative\"\n style=\"top: -8px\"\n >{{ badgeText }}</span>\n </div>\n</div>\n" }]
214
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"d-flex flex-row\">\n <seam-icon *ngIf=\"icon\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pr-2\"\n [icon]=\"icon\"\n [iconClass]=\"iconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n <div class=\"flex-fill\">\n <div class=\"text-truncate\">\n <ng-content></ng-content>\n </div>\n\n <div *ngIf=\"badgeText\">\n <span class=\"badge badge-pill{{ badgeTheme ? ' badge-' + badgeTheme : '' }} position-relative\"\n style=\"top: -8px\"\n >{{ badgeText }}</span>\n </div>\n </div>\n <seam-icon *ngIf=\"_triggersSubmenu\"\n style=\"width: 30px; height: 20px; flex: 0 0 auto;\"\n class=\"pl-2\"\n [icon]=\"sublevelIcon\"\n [iconClass]=\"subLevelIconClass\"\n iconType=\"image-fill\">\n </seam-icon>\n</div>\n", styles: [":host.cdk-mouse-focused:not(:hover){background:transparent}:host[aria-expanded]{color:#16181b;text-decoration:none;background-color:#e9ecef}\n"] }]
188
215
  }], ctorParameters: function () {
189
216
  return [{ type: i0.ElementRef }, { type: undefined, decorators: [{
190
217
  type: Inject,
191
218
  args: [DOCUMENT]
192
- }] }, { type: i1.FocusMonitor }, { type: undefined, decorators: [{
219
+ }] }, { type: i1.FocusMonitor }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
193
220
  type: Inject,
194
221
  args: [THESEAM_MENU_PANEL]
195
222
  }, {
@@ -201,6 +228,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
201
228
  type: Input
202
229
  }], iconClass: [{
203
230
  type: Input
231
+ }], sublevelIcon: [{
232
+ type: Input
233
+ }], subLevelIconClass: [{
234
+ type: Input
204
235
  }], badgeText: [{
205
236
  type: Input
206
237
  }], badgeTheme: [{
@@ -213,9 +244,225 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
213
244
  args: ['mouseenter']
214
245
  }] } });
215
246
 
247
+ let menuPanelUid = 0;
248
+ const LIB_MENU = {
249
+ provide: THESEAM_MENU_PANEL,
250
+ // tslint:disable-next-line:no-use-before-declare
251
+ useExisting: forwardRef(() => MenuComponent)
252
+ };
253
+ class MenuComponent {
254
+ /**
255
+ * Defines a width for a menu that will scale down if the window innerWidth is
256
+ * smaller than the value.
257
+ */
258
+ get baseWidth() { return this._baseWidth.value; }
259
+ set baseWidth(value) {
260
+ const _val = coerceNumberProperty(value, null);
261
+ if (_val !== this._baseWidth.value) {
262
+ this._baseWidth.next(_val);
263
+ }
264
+ }
265
+ constructor() {
266
+ this._ngUnsubscribe = new Subject();
267
+ this.panelId = `menu-panel-${menuPanelUid++}`;
268
+ this._footer = new BehaviorSubject(undefined);
269
+ this.hasFooter$ = this._footer.pipe(map(v => v !== null && v !== undefined));
270
+ this._header = new BehaviorSubject(undefined);
271
+ this.hasHeader$ = this._header.pipe(map(v => v !== null && v !== undefined));
272
+ /** Menu items inside the current menu. */
273
+ this._items = [];
274
+ /** Emits whenever the amount of menu items changes. */
275
+ this._itemChanges = new Subject();
276
+ /** Subscription to tab events on the menu panel */
277
+ this._tabSubscription = Subscription.EMPTY;
278
+ /** Emits whenever an animation on the menu completes. */
279
+ this._animationDone = new Subject();
280
+ /** Whether the menu is animating. */
281
+ this._isAnimating = false;
282
+ this.closed = new EventEmitter();
283
+ this._baseWidth = new BehaviorSubject(null);
284
+ this.animationType = 'slide';
285
+ this._menuWidth$ = this._baseWidth.pipe(switchMap(baseWidth => {
286
+ if (baseWidth) {
287
+ return fromEvent(window, 'resize').pipe(startWith(undefined), map(() => window.innerWidth < baseWidth ? `${window.innerWidth}px` : `${baseWidth}px`));
288
+ }
289
+ return of(undefined);
290
+ }), distinctUntilChanged(), takeUntil(this._ngUnsubscribe));
291
+ }
292
+ ngOnDestroy() {
293
+ this._tabSubscription.unsubscribe();
294
+ this.closed.complete();
295
+ this._ngUnsubscribe.next(undefined);
296
+ this._ngUnsubscribe.complete();
297
+ }
298
+ ngAfterContentInit() {
299
+ this._keyManager = new FocusKeyManager(this._items).withWrap().withTypeAhead();
300
+ this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));
301
+ }
302
+ /** Stream that emits whenever the hovered menu item changes. */
303
+ _hovered() {
304
+ return this._itemChanges.pipe(startWith(this._items), switchMap(items => merge(...items.map(item => item._hovered))));
305
+ }
306
+ /** Handle a keyboard event from the menu, delegating to the appropriate action. */
307
+ _handleKeydown(event) {
308
+ // tslint:disable-next-line:deprecation
309
+ const keyCode = event.keyCode;
310
+ const manager = this._keyManager;
311
+ switch (keyCode) {
312
+ case ESCAPE:
313
+ if (!hasModifierKey(event)) {
314
+ event.preventDefault();
315
+ this.closed.emit('keydown');
316
+ }
317
+ break;
318
+ case LEFT_ARROW:
319
+ if (this.parentMenu && this.direction === 'ltr') {
320
+ this.closed.emit('keydown');
321
+ }
322
+ break;
323
+ case RIGHT_ARROW:
324
+ if (this.parentMenu && this.direction === 'rtl') {
325
+ this.closed.emit('keydown');
326
+ }
327
+ break;
328
+ case HOME:
329
+ case END:
330
+ if (!hasModifierKey(event)) {
331
+ keyCode === HOME ? manager === null || manager === void 0 ? void 0 : manager.setFirstItemActive() : manager === null || manager === void 0 ? void 0 : manager.setLastItemActive();
332
+ event.preventDefault();
333
+ }
334
+ break;
335
+ default:
336
+ if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
337
+ manager === null || manager === void 0 ? void 0 : manager.setFocusOrigin('keyboard');
338
+ }
339
+ manager === null || manager === void 0 ? void 0 : manager.onKeydown(event);
340
+ }
341
+ }
342
+ /**
343
+ * Focus the first item in the menu.
344
+ * @param origin Action from which the focus originated. Used to set the correct styling.
345
+ */
346
+ focusFirstItem(origin = 'program') {
347
+ var _a;
348
+ (_a = this._keyManager) === null || _a === void 0 ? void 0 : _a.setFocusOrigin(origin).setFirstItemActive();
349
+ }
350
+ /**
351
+ * Resets the active item in the menu. This is used when the menu is opened, allowing
352
+ * the user to start from the first option when pressing the down arrow.
353
+ */
354
+ resetActiveItem() {
355
+ var _a;
356
+ (_a = this._keyManager) === null || _a === void 0 ? void 0 : _a.setActiveItem(-1);
357
+ }
358
+ /** Registers a menu item with the menu. */
359
+ addItem(item) {
360
+ // We register the items through this method, rather than picking them up through
361
+ // `ContentChildren`, because we need the items to be picked up by their closest
362
+ // `seam-menu` ancestor. If we used `@ContentChildren(MenuItemComponent, {descendants: true})`,
363
+ // all descendant items will bleed into the top-level menu in the case where the consumer
364
+ // has `seam-menu` instances nested inside each other.
365
+ if (this._items.indexOf(item) === -1) {
366
+ this._items.push(item);
367
+ this._itemChanges.next(this._items);
368
+ }
369
+ }
370
+ /** Removes an item from the menu. */
371
+ removeItem(item) {
372
+ const index = this._items.indexOf(item);
373
+ if (this._items.indexOf(item) > -1) {
374
+ this._items.splice(index, 1);
375
+ this._itemChanges.next(this._items);
376
+ }
377
+ }
378
+ /** Sets the footer component. */
379
+ setFooter(footer) {
380
+ this._footer.next(footer);
381
+ }
382
+ /** Sets the header component. */
383
+ setHeader(header) {
384
+ this._header.next(header);
385
+ }
386
+ _dropdownMenuClick(event) {
387
+ // This is needed, because some menu's will get stuck open if the component
388
+ // managing the menu is destroyed before the menu finishes its cleanup. I
389
+ // may look for a fix to that eventually.
390
+ this.closed.emit('click');
391
+ }
392
+ /** Callback that is invoked when the panel animation completes. */
393
+ _onAnimationDone(event) {
394
+ this._animationDone.next(event);
395
+ this._isAnimating = false;
396
+ }
397
+ _onAnimationStart(event) {
398
+ var _a;
399
+ this._isAnimating = true;
400
+ // Scroll the content element to the top as soon as the animation starts. This is necessary,
401
+ // because we move focus to the first item while it's still being animated, which can throw
402
+ // the browser off when it determines the scroll position. Alternatively we can move focus
403
+ // when the animation is done, however moving focus asynchronously will interrupt screen
404
+ // readers which are in the process of reading out the menu already. We take the `element`
405
+ // from the `event` since we can't use a `ViewChild` to access the pane.
406
+ if (event.toState === 'enter' && ((_a = this._keyManager) === null || _a === void 0 ? void 0 : _a.activeItemIndex) === 0) {
407
+ event.element.scrollTop = 0;
408
+ }
409
+ }
410
+ }
411
+ MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
412
+ MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuComponent, selector: "seam-menu", inputs: { menuClass: "menuClass", baseWidth: "baseWidth", animationType: "animationType" }, outputs: { closed: "closed" }, providers: [LIB_MENU], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], exportAs: ["seamMenu"], ngImport: i0, template: "<ng-template>\n <div class=\"seam-menu-container\"\n @slideDown\n (@slideDown.start)=\"_onAnimationStart($event)\"\n (@slideDown.done)=\"_onAnimationDone($event)\"\n [class.seam-menu-anim--slide]=\"animationType==='slide'\"\n [class.seam-menu-anim--fade]=\"animationType==='fade'\"\n [id]=\"panelId\">\n <div class=\"dropdown-menu show position-static{{ menuClass ? ' ' + menuClass : '' }}\"\n [style.width]=\"_menuWidth$ | async\"\n [class.pt-0]=\"hasHeader$ | async\"\n [class.pb-0]=\"hasFooter$ | async\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_dropdownMenuClick($event)\"\n tabindex=\"-1\"\n role=\"menu\"\n >\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".seam-menu-container.seam-menu-anim--slide{overflow:hidden}.seam-menu-container.ng-animating,.dropdown-menu.ng-animating{-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}\n"], dependencies: [{ kind: "pipe", type: i2.AsyncPipe, name: "async" }], animations: [
413
+ trigger('slideDown', [
414
+ transition(':enter', useAnimation(menuDropdownPanelIn)),
415
+ transition(':leave', useAnimation(menuDropdownPanelOut)),
416
+ ])
417
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush });
418
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, decorators: [{
419
+ type: Component,
420
+ args: [{ selector: 'seam-menu', providers: [LIB_MENU], animations: [
421
+ trigger('slideDown', [
422
+ transition(':enter', useAnimation(menuDropdownPanelIn)),
423
+ transition(':leave', useAnimation(menuDropdownPanelOut)),
424
+ ])
425
+ ], changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'seamMenu', template: "<ng-template>\n <div class=\"seam-menu-container\"\n @slideDown\n (@slideDown.start)=\"_onAnimationStart($event)\"\n (@slideDown.done)=\"_onAnimationDone($event)\"\n [class.seam-menu-anim--slide]=\"animationType==='slide'\"\n [class.seam-menu-anim--fade]=\"animationType==='fade'\"\n [id]=\"panelId\">\n <div class=\"dropdown-menu show position-static{{ menuClass ? ' ' + menuClass : '' }}\"\n [style.width]=\"_menuWidth$ | async\"\n [class.pt-0]=\"hasHeader$ | async\"\n [class.pb-0]=\"hasFooter$ | async\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_dropdownMenuClick($event)\"\n tabindex=\"-1\"\n role=\"menu\"\n >\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".seam-menu-container.seam-menu-anim--slide{overflow:hidden}.seam-menu-container.ng-animating,.dropdown-menu.ng-animating{-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}\n"] }]
426
+ }], ctorParameters: function () { return []; }, propDecorators: { templateRef: [{
427
+ type: ViewChild,
428
+ args: [TemplateRef]
429
+ }], closed: [{
430
+ type: Output
431
+ }], menuClass: [{
432
+ type: Input
433
+ }], baseWidth: [{
434
+ type: Input
435
+ }], animationType: [{
436
+ type: Input
437
+ }] } });
438
+
216
439
  /** Options for binding a passive event listener. */
217
440
  const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
218
441
  class MenuToggleDirective {
442
+ get menu() { return this._menu; }
443
+ set menu(menu) {
444
+ var _a;
445
+ if (menu === this._menu) {
446
+ return;
447
+ }
448
+ this._menu = menu;
449
+ this._menuClosedSubscription.unsubscribe();
450
+ if (menu) {
451
+ if (menu === this._parentMenuComponent && (typeof ngDevMode === 'undefined' || ngDevMode)) {
452
+ throw Error(`seamMenuToggle: menu cannot contain its own trigger. Assign a menu that is ` +
453
+ `not a parent of the trigger or move the trigger outside of the menu.`);
454
+ }
455
+ this._menuClosedSubscription = menu.closed.subscribe((reason) => {
456
+ // this._destroyMenu(reason)
457
+ this.closeMenu();
458
+ // If a click closed the menu, we should close the entire chain of nested menus.
459
+ if ((reason === 'click' || reason === 'tab') && this._parentMenuComponent) {
460
+ this._parentMenuComponent.closed.emit(reason);
461
+ }
462
+ });
463
+ }
464
+ (_a = this._menuItemInstance) === null || _a === void 0 ? void 0 : _a._setTriggersSubmenu(this.triggersSubmenu());
465
+ }
219
466
  set positions(val) {
220
467
  var _a;
221
468
  this._positions = val;
@@ -223,7 +470,40 @@ class MenuToggleDirective {
223
470
  (_a = this._overlayRef) === null || _a === void 0 ? void 0 : _a.updatePositionStrategy(this.getOverlayPosition(this._elementRef.nativeElement));
224
471
  }
225
472
  }
226
- get positions() { return this._positions; }
473
+ get positions() {
474
+ const positions = this._positions;
475
+ if (this.triggersSubmenu()) {
476
+ return [
477
+ {
478
+ originX: 'start',
479
+ originY: 'top',
480
+ overlayX: 'end',
481
+ overlayY: 'top',
482
+ offsetY: -11,
483
+ },
484
+ {
485
+ originX: 'end',
486
+ originY: 'top',
487
+ overlayX: 'start',
488
+ overlayY: 'top',
489
+ offsetY: -11,
490
+ },
491
+ {
492
+ originX: 'start',
493
+ originY: 'bottom',
494
+ overlayX: 'end',
495
+ overlayY: 'top',
496
+ },
497
+ {
498
+ originX: 'start',
499
+ originY: 'top',
500
+ overlayX: 'start',
501
+ overlayY: 'bottom',
502
+ },
503
+ ];
504
+ }
505
+ return positions;
506
+ }
227
507
  _onMouseDown(event) {
228
508
  if (!isFakeMousedownFromScreenReader(event)) {
229
509
  // Since right or middle button clicks won't trigger the `click` event,
@@ -232,42 +512,64 @@ class MenuToggleDirective {
232
512
  // Since clicking on the trigger won't close the menu if it opens a sub-menu,
233
513
  // we should prevent focus from moving onto it via click to avoid the
234
514
  // highlight from lingering on the menu item.
235
- // if (this.triggersSubmenu()) {
236
- // event.preventDefault();
237
- // }
515
+ if (this.triggersSubmenu()) {
516
+ event.preventDefault();
517
+ }
238
518
  }
239
519
  }
240
520
  _onKeydown(event) {
241
521
  var _a;
242
522
  this._openedBy = null;
523
+ // console.log('keydown', event)
243
524
  // tslint:disable-next-line:deprecation
244
525
  const keyCode = event.keyCode;
245
526
  if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
246
527
  if (this.menuOpen()) {
247
- (_a = this.seamMenuToggle) === null || _a === void 0 ? void 0 : _a.focusFirstItem(this._openedBy || 'program');
528
+ (_a = this.menu) === null || _a === void 0 ? void 0 : _a.focusFirstItem(this._openedBy || 'program');
248
529
  }
249
530
  }
531
+ else if (keyCode === ESCAPE) {
532
+ this.closeMenu();
533
+ }
534
+ // Pressing enter on the trigger will trigger the click handler later.
535
+ if (keyCode === ENTER || keyCode === SPACE) {
536
+ this._openedBy = 'keyboard';
537
+ }
538
+ if (this.triggersSubmenu() &&
539
+ ((keyCode === RIGHT_ARROW && this.dir === 'ltr') ||
540
+ (keyCode === LEFT_ARROW && this.dir === 'rtl'))) {
541
+ this._openedBy = 'keyboard';
542
+ this.openMenu();
543
+ }
250
544
  }
251
545
  _onClick(event) {
252
- this.toggle();
546
+ if (this.triggersSubmenu()) {
547
+ // Stop event propagation to avoid closing the parent menu.
548
+ event.stopPropagation();
549
+ this.openMenu();
550
+ }
551
+ else {
552
+ this.toggle();
553
+ }
253
554
  }
254
555
  _onDocumentKeydown(event) {
255
556
  if (event.keyCode === ESCAPE) {
256
557
  this.closeMenu();
257
558
  }
258
559
  }
259
- // @HostListener('document:mousedown', [ '$event' ])
260
- // _onDocumentMouseDown(event: any) {
261
- // console.log('outside click')
262
- // }
263
- constructor(_elementRef, _viewContainerRef, _overlay, _focusMonitor) {
560
+ constructor(_elementRef, _viewContainerRef, _overlay, _focusMonitor, _parentMenu, _menuItemInstance, _dir) {
264
561
  this._elementRef = _elementRef;
265
562
  this._viewContainerRef = _viewContainerRef;
266
563
  this._overlay = _overlay;
267
564
  this._focusMonitor = _focusMonitor;
565
+ this._parentMenu = _parentMenu;
566
+ this._menuItemInstance = _menuItemInstance;
567
+ this._dir = _dir;
268
568
  this._active = false;
269
569
  this._menuClosedSubscription = Subscription.EMPTY;
270
570
  this._closingActionsSubscription = Subscription.EMPTY;
571
+ this._hoverSubscription = Subscription.EMPTY;
572
+ this._changeDetectorRef = inject(ChangeDetectorRef);
271
573
  this.restoreFocus = true;
272
574
  // Tracking input type is necessary so it's possible to only auto-focus
273
575
  // the first item of the list when the menu is opened via the keyboard
@@ -298,11 +600,16 @@ class MenuToggleDirective {
298
600
  overlayY: 'bottom',
299
601
  },
300
602
  ];
603
+ /** Event emitted when the associated menu is opened. */
604
+ this.menuOpened = new EventEmitter();
605
+ /** Event emitted when the associated menu is opened. */
606
+ this.menuClosed = new EventEmitter();
301
607
  /**
302
608
  * Handles touch start events on the trigger.
303
609
  * Needs to be an arrow function so we can easily use addEventListener and removeEventListener.
304
610
  */
305
611
  this._handleTouchStart = () => this._openedBy = 'touch';
612
+ this._parentMenuComponent = this._parentMenu instanceof MenuComponent ? this._parentMenu : undefined;
306
613
  this._elementRef.nativeElement.addEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
307
614
  }
308
615
  ngOnDestroy() {
@@ -310,6 +617,10 @@ class MenuToggleDirective {
310
617
  this._elementRef.nativeElement.removeEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
311
618
  this._menuClosedSubscription.unsubscribe();
312
619
  this._closingActionsSubscription.unsubscribe();
620
+ this._hoverSubscription.unsubscribe();
621
+ }
622
+ ngAfterContentInit() {
623
+ this._handleHover();
313
624
  }
314
625
  toggle() {
315
626
  if (this._active) {
@@ -320,23 +631,23 @@ class MenuToggleDirective {
320
631
  }
321
632
  }
322
633
  openMenu() {
323
- if (this._active || !this.seamMenuToggle) {
634
+ if (this._active || !this.menu) {
324
635
  return;
325
636
  }
326
637
  this._active = true;
327
638
  this._overlayRef = this._overlay.create({
328
- hasBackdrop: true,
329
- backdropClass: 'transparent',
639
+ hasBackdrop: !this._parentMenuComponent,
640
+ backdropClass: 'cdk-overlay-transparent-backdrop',
330
641
  positionStrategy: this.getOverlayPosition(this._elementRef.nativeElement),
331
642
  });
332
- const tpl = this.seamMenuToggle.templateRef;
643
+ const tpl = this.menu.templateRef;
333
644
  if (!tpl) {
334
645
  throw Error(`Menu template not found.`);
335
646
  }
336
647
  this._overlayRef.attach(new TemplatePortal(tpl, this._viewContainerRef));
337
648
  this._closingActionsSubscription = this._menuClosingActions().subscribe(() => this.closeMenu());
338
- this._initMenu();
339
- this._menuClosedSubscription = this.seamMenuToggle.closed.subscribe(v => {
649
+ this._initMenu(this.menu);
650
+ this._menuClosedSubscription = this.menu.closed.subscribe(v => {
340
651
  // console.log('closed', v)
341
652
  this.closeMenu();
342
653
  });
@@ -344,11 +655,23 @@ class MenuToggleDirective {
344
655
  // console.log('backdropClick', v)
345
656
  // })
346
657
  }
658
+ /** The text direction of the containing app. */
659
+ get dir() {
660
+ return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
661
+ }
662
+ /** Whether the menu triggers a sub-menu or a top-level one. */
663
+ triggersSubmenu() {
664
+ return !!(this._menuItemInstance && this._parentMenuComponent && this.menu);
665
+ }
347
666
  closeMenu() {
348
- var _a, _b;
667
+ var _a, _b, _c;
349
668
  if (!this._active) {
350
669
  return;
351
670
  }
671
+ let emitCloseEvent = false;
672
+ if (this.menuOpen()) {
673
+ emitCloseEvent = true;
674
+ }
352
675
  if ((_a = this._overlayRef) === null || _a === void 0 ? void 0 : _a.hasAttached()) {
353
676
  (_b = this._overlayRef) === null || _b === void 0 ? void 0 : _b.detach();
354
677
  }
@@ -356,6 +679,9 @@ class MenuToggleDirective {
356
679
  this._elementRef.nativeElement.removeEventListener('touchstart', this._handleTouchStart, passiveEventListenerOptions);
357
680
  this._menuClosedSubscription.unsubscribe();
358
681
  this._closingActionsSubscription.unsubscribe();
682
+ if (emitCloseEvent) {
683
+ (_c = this.menu) === null || _c === void 0 ? void 0 : _c.closed.emit();
684
+ }
359
685
  this._active = false;
360
686
  }
361
687
  menuOpen() {
@@ -386,19 +712,19 @@ class MenuToggleDirective {
386
712
  * This method sets the menu state to open and focuses the first item if
387
713
  * the menu was opened via the keyboard.
388
714
  */
389
- _initMenu() {
390
- // this.seamMenuToggle.parentMenu = this.triggersSubmenu() ? this._parentMenu : undefined
391
- // this.seamMenuToggle.direction = this.dir
392
- // this._setMenuElevation()
393
- // this._setIsMenuOpen(true)
394
- // this.seamMenuToggle.focusFirstItem(this._openedBy || 'program')
715
+ _initMenu(menu) {
716
+ menu.parentMenu = this.triggersSubmenu() ? this._parentMenu : undefined;
717
+ menu.direction = this.dir;
718
+ // this._setMenuElevation(menu)
719
+ menu.focusFirstItem(this._openedBy || 'program');
720
+ this._setIsMenuOpen(true);
395
721
  }
396
722
  /**
397
723
  * This method resets the menu when it's closed, most importantly restoring
398
724
  * focus to the menu trigger if the menu was opened via the keyboard.
399
725
  */
400
726
  _resetMenu() {
401
- // this._setIsMenuOpen(false)
727
+ this._setIsMenuOpen(false);
402
728
  // We should reset focus if the user is navigating using a keyboard or
403
729
  // if we have a top-level trigger which might cause focus to be lost
404
730
  // when clicking on the backdrop.
@@ -408,44 +734,100 @@ class MenuToggleDirective {
408
734
  // `keyboard` so we don't have to specify which one it is.
409
735
  this.focus();
410
736
  }
411
- // else if (!this.triggersSubmenu()) {
412
- // this.focus(this._openedBy)
413
- // }
737
+ else if (!this.triggersSubmenu()) {
738
+ this.focus(this._openedBy);
739
+ }
414
740
  }
415
741
  this._openedBy = null;
416
742
  }
743
+ // set state rather than toggle to support triggers sharing a menu
744
+ _setIsMenuOpen(isOpen) {
745
+ var _a;
746
+ if (isOpen !== this.menuOpen()) {
747
+ // this._menuOpen = isOpen
748
+ this.menuOpen() ? this.menuOpened.emit() : this.menuClosed.emit();
749
+ if (this.triggersSubmenu()) {
750
+ (_a = this._menuItemInstance) === null || _a === void 0 ? void 0 : _a._setHighlighted(isOpen);
751
+ }
752
+ this._changeDetectorRef.markForCheck();
753
+ }
754
+ }
417
755
  /** Returns a stream that emits whenever an action that should close the menu occurs. */
418
756
  _menuClosingActions() {
419
757
  var _a, _b, _c, _d;
420
758
  const backdrop = (_b = (_a = this._overlayRef) === null || _a === void 0 ? void 0 : _a.backdropClick()) !== null && _b !== void 0 ? _b : of();
421
759
  const detachments = (_d = (_c = this._overlayRef) === null || _c === void 0 ? void 0 : _c.detachments()) !== null && _d !== void 0 ? _d : of();
422
- // const parentClose = this._parentMenu ? this._parentMenu.closed : of()
423
- const parentClose = of();
424
- // const hover = this._parentMenu ? this._parentMenu._hovered().pipe(
425
- // filter(active => active !== this._menuItemInstance),
426
- // filter(() => this._menuOpen)
427
- // ) : of()
428
- const hover = of();
760
+ const parentClose = this._parentMenu ? this._parentMenu.closed : of();
761
+ const hover = this._parentMenuComponent ? this._parentMenuComponent._hovered().pipe(filter(active => active !== this._menuItemInstance), filter(() => this.menuOpen())) : of();
429
762
  return merge(backdrop, parentClose, hover, detachments);
430
763
  }
764
+ /** Handles the cases where the user hovers over the trigger. */
765
+ _handleHover() {
766
+ // Subscribe to changes in the hovered item in order to toggle the panel.
767
+ if (!this.triggersSubmenu() || !this._parentMenuComponent) {
768
+ return;
769
+ }
770
+ this._hoverSubscription = this._parentMenuComponent
771
+ ._hovered()
772
+ // Since we might have multiple competing triggers for the same menu (e.g. a sub-menu
773
+ // with different data and triggers), we have to delay it by a tick to ensure that
774
+ // it won't be closed immediately after it is opened.
775
+ .pipe(filter(active => active === this._menuItemInstance && !active.disabled), delay(0, asapScheduler))
776
+ .subscribe(() => {
777
+ this._openedBy = 'mouse';
778
+ // If the same menu is used between multiple triggers, it might still be animating
779
+ // while the new trigger tries to re-open it. Wait for the animation to finish
780
+ // before doing so. Also interrupt if the user moves to another item.
781
+ if (this.menu instanceof MenuComponent && this.menu._isAnimating) {
782
+ // We need the `delay(0)` here in order to avoid
783
+ // 'changed after checked' errors in some cases. See #12194.
784
+ this.menu._animationDone
785
+ .pipe(take(1), delay(0, asapScheduler), takeUntil$1(this._parentMenuComponent._hovered()))
786
+ .subscribe(() => this.openMenu());
787
+ }
788
+ else {
789
+ this.openMenu();
790
+ }
791
+ });
792
+ }
431
793
  }
432
- MenuToggleDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuToggleDirective, deps: [{ token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: i1$1.Overlay }, { token: i1.FocusMonitor }], target: i0.ɵɵFactoryTarget.Directive });
433
- MenuToggleDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: MenuToggleDirective, selector: "[seamMenuToggle]", inputs: { seamMenuToggle: "seamMenuToggle", positions: "positions" }, host: { attributes: { "aria-haspopup": "true" }, listeners: { "mousedown": "_onMouseDown($event)", "keydown": "_onKeydown($event)", "click": "_onClick($event)", "document:keydown": "_onDocumentKeydown($event)" }, properties: { "attr.aria-expanded": "menuOpen() || null" } }, exportAs: ["seamMenuToggle"], ngImport: i0 });
794
+ MenuToggleDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuToggleDirective, deps: [{ token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: i1$1.Overlay }, { token: i1.FocusMonitor }, { token: THESEAM_MENU_PANEL, optional: true }, { token: MenuItemComponent, optional: true, self: true }, { token: i4.Directionality, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
795
+ MenuToggleDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: MenuToggleDirective, selector: "[seamMenuToggle]", inputs: { menu: ["seamMenuToggle", "menu"], positions: "positions" }, outputs: { menuOpened: "menuOpened", menuClosed: "menuClosed" }, host: { attributes: { "aria-haspopup": "true" }, listeners: { "mousedown": "_onMouseDown($event)", "keydown": "_onKeydown($event)", "click": "_onClick($event)", "document:keydown": "_onDocumentKeydown($event)" }, properties: { "attr.aria-expanded": "menuOpen() || null", "attr.aria-controls": "menuOpen() ? menu.panelId : null" }, classAttribute: "seam-menu-toggle" }, exportAs: ["seamMenuToggle"], ngImport: i0 });
434
796
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuToggleDirective, decorators: [{
435
797
  type: Directive,
436
798
  args: [{
437
799
  selector: '[seamMenuToggle]',
438
800
  // tslint:disable-next-line:use-host-property-decorator
439
801
  host: {
802
+ 'class': 'seam-menu-toggle',
440
803
  'aria-haspopup': 'true',
441
- '[attr.aria-expanded]': 'menuOpen() || null'
804
+ '[attr.aria-expanded]': 'menuOpen() || null',
805
+ '[attr.aria-controls]': 'menuOpen() ? menu.panelId : null',
442
806
  },
443
807
  exportAs: 'seamMenuToggle'
444
808
  }]
445
- }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: i1$1.Overlay }, { type: i1.FocusMonitor }]; }, propDecorators: { seamMenuToggle: [{
446
- type: Input
809
+ }], ctorParameters: function () {
810
+ return [{ type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: i1$1.Overlay }, { type: i1.FocusMonitor }, { type: undefined, decorators: [{
811
+ type: Inject,
812
+ args: [THESEAM_MENU_PANEL]
813
+ }, {
814
+ type: Optional
815
+ }] }, { type: MenuItemComponent, decorators: [{
816
+ type: Optional
817
+ }, {
818
+ type: Self
819
+ }] }, { type: i4.Directionality, decorators: [{
820
+ type: Optional
821
+ }] }];
822
+ }, propDecorators: { menu: [{
823
+ type: Input,
824
+ args: ['seamMenuToggle']
447
825
  }], positions: [{
448
826
  type: Input
827
+ }], menuOpened: [{
828
+ type: Output
829
+ }], menuClosed: [{
830
+ type: Output
449
831
  }], _onMouseDown: [{
450
832
  type: HostListener,
451
833
  args: ['mousedown', ['$event']]
@@ -460,174 +842,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
460
842
  args: ['document:keydown', ['$event']]
461
843
  }] } });
462
844
 
463
- const LIB_MENU = {
464
- provide: THESEAM_MENU_PANEL,
465
- // tslint:disable-next-line:no-use-before-declare
466
- useExisting: forwardRef(() => MenuComponent)
467
- };
468
- class MenuComponent {
469
- /**
470
- * Defines a width for a menu that will scale down if the window innerWidth is
471
- * smaller than the value.
472
- */
473
- get baseWidth() { return this._baseWidth.value; }
474
- set baseWidth(value) {
475
- const _val = coerceNumberProperty(value, null);
476
- if (_val !== this._baseWidth.value) {
477
- this._baseWidth.next(_val);
478
- }
479
- }
480
- constructor() {
481
- this._ngUnsubscribe = new Subject();
482
- this._footer = new BehaviorSubject(undefined);
483
- this.hasFooter$ = this._footer.pipe(map(v => v !== null && v !== undefined));
484
- this._header = new BehaviorSubject(undefined);
485
- this.hasHeader$ = this._header.pipe(map(v => v !== null && v !== undefined));
486
- /** Menu items inside the current menu. */
487
- this._items = [];
488
- /** Emits whenever the amount of menu items changes. */
489
- this._itemChanges = new Subject();
490
- /** Subscription to tab events on the menu panel */
491
- this._tabSubscription = Subscription.EMPTY;
492
- this.closed = new EventEmitter();
493
- this._baseWidth = new BehaviorSubject(null);
494
- this.animationType = 'slide';
495
- this._menuWidth$ = this._baseWidth.pipe(switchMap(baseWidth => {
496
- if (baseWidth) {
497
- return fromEvent(window, 'resize').pipe(startWith(undefined), map(() => window.innerWidth < baseWidth ? `${window.innerWidth}px` : `${baseWidth}px`));
498
- }
499
- return of(undefined);
500
- }), distinctUntilChanged(), takeUntil(this._ngUnsubscribe));
501
- }
502
- ngOnDestroy() {
503
- this._tabSubscription.unsubscribe();
504
- this.closed.complete();
505
- this._ngUnsubscribe.next(undefined);
506
- this._ngUnsubscribe.complete();
507
- }
508
- ngAfterContentInit() {
509
- this._keyManager = new FocusKeyManager(this._items).withWrap().withTypeAhead();
510
- this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.closed.emit('tab'));
511
- }
512
- /** Stream that emits whenever the hovered menu item changes. */
513
- _hovered() {
514
- return this._itemChanges.pipe(startWith(this._items), switchMap(items => merge(...items.map(item => item._hovered))));
515
- }
516
- /** Handle a keyboard event from the menu, delegating to the appropriate action. */
517
- _handleKeydown(event) {
518
- // tslint:disable-next-line:deprecation
519
- const keyCode = event.keyCode;
520
- const manager = this._keyManager;
521
- switch (keyCode) {
522
- case ESCAPE:
523
- if (!hasModifierKey(event)) {
524
- event.preventDefault();
525
- this.closed.emit('keydown');
526
- }
527
- break;
528
- // case LEFT_ARROW:
529
- // if (this.parentMenu && this.direction === 'ltr') {
530
- // this.closed.emit('keydown')
531
- // }
532
- // break
533
- // case RIGHT_ARROW:
534
- // if (this.parentMenu && this.direction === 'rtl') {
535
- // this.closed.emit('keydown')
536
- // }
537
- // break
538
- case HOME:
539
- case END:
540
- if (!hasModifierKey(event)) {
541
- keyCode === HOME ? manager === null || manager === void 0 ? void 0 : manager.setFirstItemActive() : manager === null || manager === void 0 ? void 0 : manager.setLastItemActive();
542
- event.preventDefault();
543
- }
544
- break;
545
- default:
546
- if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
547
- manager === null || manager === void 0 ? void 0 : manager.setFocusOrigin('keyboard');
548
- }
549
- manager === null || manager === void 0 ? void 0 : manager.onKeydown(event);
550
- }
551
- }
552
- /**
553
- * Focus the first item in the menu.
554
- * @param origin Action from which the focus originated. Used to set the correct styling.
555
- */
556
- focusFirstItem(origin = 'program') {
557
- var _a;
558
- (_a = this._keyManager) === null || _a === void 0 ? void 0 : _a.setFocusOrigin(origin).setFirstItemActive();
559
- }
560
- /**
561
- * Resets the active item in the menu. This is used when the menu is opened, allowing
562
- * the user to start from the first option when pressing the down arrow.
563
- */
564
- resetActiveItem() {
565
- var _a;
566
- (_a = this._keyManager) === null || _a === void 0 ? void 0 : _a.setActiveItem(-1);
567
- }
568
- /** Registers a menu item with the menu. */
569
- addItem(item) {
570
- // We register the items through this method, rather than picking them up through
571
- // `ContentChildren`, because we need the items to be picked up by their closest
572
- // `seam-menu` ancestor. If we used `@ContentChildren(MenuItemComponent, {descendants: true})`,
573
- // all descendant items will bleed into the top-level menu in the case where the consumer
574
- // has `seam-menu` instances nested inside each other.
575
- if (this._items.indexOf(item) === -1) {
576
- this._items.push(item);
577
- this._itemChanges.next(this._items);
578
- }
579
- }
580
- /** Removes an item from the menu. */
581
- removeItem(item) {
582
- const index = this._items.indexOf(item);
583
- if (this._items.indexOf(item) > -1) {
584
- this._items.splice(index, 1);
585
- this._itemChanges.next(this._items);
586
- }
587
- }
588
- /** Sets the footer component. */
589
- setFooter(footer) {
590
- this._footer.next(footer);
591
- }
592
- /** Sets the header component. */
593
- setHeader(header) {
594
- this._header.next(header);
595
- }
596
- _dropdownMenuClick(event) {
597
- // This is needed, because some menu's will get stuck open if the component
598
- // managing the menu is destroyed before the menu finishes its cleanup. I
599
- // may look for a fix to that eventually.
600
- this.closed.emit('click');
601
- }
602
- }
603
- MenuComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
604
- MenuComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: MenuComponent, selector: "seam-menu", inputs: { menuClass: "menuClass", baseWidth: "baseWidth", animationType: "animationType" }, outputs: { closed: "closed" }, providers: [LIB_MENU], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], exportAs: ["seamMenu"], ngImport: i0, template: "<ng-template>\n <div class=\"seam-menu-container\" @slideDown\n [class.seam-menu-anim--slide]=\"animationType==='slide'\"\n [class.seam-menu-anim--fade]=\"animationType==='fade'\">\n <div class=\"dropdown-menu show position-static{{ menuClass ? ' ' + menuClass : '' }}\"\n [style.width]=\"_menuWidth$ | async\"\n [class.pt-0]=\"hasHeader$ | async\"\n [class.pb-0]=\"hasFooter$ | async\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_dropdownMenuClick($event)\"\n tabindex=\"-1\"\n role=\"menu\"\n >\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".seam-menu-container.seam-menu-anim--slide{overflow:hidden}.seam-menu-container.ng-animating,.dropdown-menu.ng-animating{-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}\n"], dependencies: [{ kind: "pipe", type: i2.AsyncPipe, name: "async" }], animations: [
605
- trigger('slideDown', [
606
- transition(':enter', useAnimation(menuDropdownPanelIn)),
607
- transition(':leave', useAnimation(menuDropdownPanelOut)),
608
- ])
609
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush });
610
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: MenuComponent, decorators: [{
611
- type: Component,
612
- args: [{ selector: 'seam-menu', providers: [LIB_MENU], animations: [
613
- trigger('slideDown', [
614
- transition(':enter', useAnimation(menuDropdownPanelIn)),
615
- transition(':leave', useAnimation(menuDropdownPanelOut)),
616
- ])
617
- ], changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'seamMenu', template: "<ng-template>\n <div class=\"seam-menu-container\" @slideDown\n [class.seam-menu-anim--slide]=\"animationType==='slide'\"\n [class.seam-menu-anim--fade]=\"animationType==='fade'\">\n <div class=\"dropdown-menu show position-static{{ menuClass ? ' ' + menuClass : '' }}\"\n [style.width]=\"_menuWidth$ | async\"\n [class.pt-0]=\"hasHeader$ | async\"\n [class.pb-0]=\"hasFooter$ | async\"\n (keydown)=\"_handleKeydown($event)\"\n (click)=\"_dropdownMenuClick($event)\"\n tabindex=\"-1\"\n role=\"menu\"\n >\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".seam-menu-container.seam-menu-anim--slide{overflow:hidden}.seam-menu-container.ng-animating,.dropdown-menu.ng-animating{-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}\n"] }]
618
- }], ctorParameters: function () { return []; }, propDecorators: { templateRef: [{
619
- type: ViewChild,
620
- args: [TemplateRef]
621
- }], closed: [{
622
- type: Output
623
- }], menuClass: [{
624
- type: Input
625
- }], baseWidth: [{
626
- type: Input
627
- }], animationType: [{
628
- type: Input
629
- }] } });
630
-
631
845
  // TODO: Split up the button and anchor classes.
632
846
  class MenuFooterActionComponent {
633
847
  get _attrType() { return this.type; }