@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.
- package/esm2020/datatable/datatable-action-menu/datatable-action-menu.component.mjs +1 -1
- package/esm2020/datatable/datatable-column-preferences-button/datatable-column-preferences-button.component.mjs +1 -1
- package/esm2020/datatable/datatable-export-button/datatable-export-button.component.mjs +1 -1
- package/esm2020/datatable-dynamic/datatable-dynamic-action-menu/datatable-dynamic-action-menu.component.mjs +1 -1
- package/esm2020/framework/schema-form-controls/schema-form-submit-split/schema-form-submit-split.component.mjs +1 -1
- package/esm2020/framework/top-bar/top-bar.component.mjs +1 -1
- package/esm2020/google-maps/google-maps/google-maps.component.mjs +32 -8
- package/esm2020/google-maps/google-maps-feature-helpers.mjs +13 -1
- package/esm2020/google-maps/google-maps.module.mjs +4 -2
- package/esm2020/google-maps/google-maps.service.mjs +96 -25
- package/esm2020/menu/menu-item.component.mjs +43 -15
- package/esm2020/menu/menu-toggle.directive.mjs +202 -43
- package/esm2020/menu/menu.component.mjs +37 -14
- package/esm2020/utils/geo-json/close-polygons.mjs +38 -0
- package/esm2020/utils/public-api.mjs +2 -1
- package/esm2020/widget/widget/widget.component.mjs +54 -33
- package/fesm2015/theseam-ui-common-datatable-dynamic.mjs +1 -1
- package/fesm2015/theseam-ui-common-datatable-dynamic.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-datatable.mjs +3 -3
- package/fesm2015/theseam-ui-common-datatable.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-framework.mjs +2 -2
- package/fesm2015/theseam-ui-common-framework.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-google-maps.mjs +138 -28
- package/fesm2015/theseam-ui-common-google-maps.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-menu.mjs +442 -228
- package/fesm2015/theseam-ui-common-menu.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-utils.mjs +39 -1
- package/fesm2015/theseam-ui-common-utils.mjs.map +1 -1
- package/fesm2015/theseam-ui-common-widget.mjs +53 -32
- package/fesm2015/theseam-ui-common-widget.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-datatable-dynamic.mjs +1 -1
- package/fesm2020/theseam-ui-common-datatable-dynamic.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-datatable.mjs +3 -3
- package/fesm2020/theseam-ui-common-datatable.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-framework.mjs +2 -2
- package/fesm2020/theseam-ui-common-framework.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-google-maps.mjs +141 -32
- package/fesm2020/theseam-ui-common-google-maps.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-menu.mjs +432 -225
- package/fesm2020/theseam-ui-common-menu.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-utils.mjs +39 -1
- package/fesm2020/theseam-ui-common-utils.mjs.map +1 -1
- package/fesm2020/theseam-ui-common-widget.mjs +53 -32
- package/fesm2020/theseam-ui-common-widget.mjs.map +1 -1
- package/google-maps/google-maps/google-maps.component.d.ts +8 -1
- package/google-maps/google-maps-feature-helpers.d.ts +3 -0
- package/google-maps/google-maps.module.d.ts +1 -1
- package/google-maps/google-maps.service.d.ts +7 -0
- package/menu/menu-item.component.d.ts +20 -9
- package/menu/menu-toggle.directive.d.ts +34 -11
- package/menu/menu.component.d.ts +16 -2
- package/package.json +1 -1
- package/utils/geo-json/close-polygons.d.ts +9 -0
- package/utils/public-api.d.ts +1 -0
- package/widget/widget/widget.component.d.ts +10 -11
- 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,
|
|
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,
|
|
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 {
|
|
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,
|
|
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,
|
|
99
|
+
constructor(_elementRef, _document, _focusMonitor, _changeDetectorRef, _parentMenu) {
|
|
98
100
|
super();
|
|
99
101
|
this._elementRef = _elementRef;
|
|
100
|
-
this.
|
|
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
|
-
|
|
111
|
-
|
|
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.
|
|
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
|
|
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
|
|
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() {
|
|
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
|
-
|
|
236
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
// this._setMenuElevation()
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
423
|
-
const
|
|
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: {
|
|
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 () {
|
|
446
|
-
|
|
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; }
|