@spartan-ng/brain 0.0.1-alpha.536 → 0.0.1-alpha.538

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 (35) hide show
  1. package/{hover-card/lib/createHoverObservable.d.ts → core/helpers/create-hover-observable.d.ts} +4 -1
  2. package/core/helpers/wait-for-element-animations.d.ts +5 -0
  3. package/core/index.d.ts +3 -0
  4. package/fesm2022/spartan-ng-brain-core.mjs +96 -15
  5. package/fesm2022/spartan-ng-brain-core.mjs.map +1 -1
  6. package/fesm2022/spartan-ng-brain-hover-card.mjs +7 -24
  7. package/fesm2022/spartan-ng-brain-hover-card.mjs.map +1 -1
  8. package/fesm2022/spartan-ng-brain-input-otp.mjs +5 -7
  9. package/fesm2022/spartan-ng-brain-input-otp.mjs.map +1 -1
  10. package/fesm2022/spartan-ng-brain-navigation-menu.mjs +681 -0
  11. package/fesm2022/spartan-ng-brain-navigation-menu.mjs.map +1 -0
  12. package/fesm2022/spartan-ng-brain-radio-group.mjs +2 -2
  13. package/fesm2022/spartan-ng-brain-radio-group.mjs.map +1 -1
  14. package/fesm2022/spartan-ng-brain-tooltip.mjs +2 -47
  15. package/fesm2022/spartan-ng-brain-tooltip.mjs.map +1 -1
  16. package/hlm-tailwind-preset.css +7 -7
  17. package/hover-card/index.d.ts +0 -1
  18. package/input-otp/lib/brn-input-otp.d.ts +1 -3
  19. package/navigation-menu/README.md +3 -0
  20. package/navigation-menu/index.d.ts +13 -0
  21. package/navigation-menu/lib/brn-navigation-menu-content.d.ts +22 -0
  22. package/navigation-menu/lib/brn-navigation-menu-content.service.d.ts +44 -0
  23. package/navigation-menu/lib/brn-navigation-menu-item-focusable.token.d.ts +4 -0
  24. package/navigation-menu/lib/brn-navigation-menu-item.d.ts +23 -0
  25. package/navigation-menu/lib/brn-navigation-menu-item.token.d.ts +5 -0
  26. package/navigation-menu/lib/brn-navigation-menu-link.d.ts +22 -0
  27. package/navigation-menu/lib/brn-navigation-menu-list.d.ts +7 -0
  28. package/navigation-menu/lib/brn-navigation-menu-trigger.d.ts +44 -0
  29. package/navigation-menu/lib/brn-navigation-menu.d.ts +61 -0
  30. package/navigation-menu/lib/brn-navigation-menu.token.d.ts +5 -0
  31. package/navigation-menu/lib/brn-parent-nav-menu.token.d.ts +8 -0
  32. package/package.json +5 -1
  33. package/radio-group/index.d.ts +1 -0
  34. package/radio-group/lib/brn-radio-group.d.ts +1 -1
  35. /package/{tooltip/lib → core/helpers}/computed-previous.d.ts +0 -0
@@ -0,0 +1,681 @@
1
+ import { FocusKeyManager } from '@angular/cdk/a11y';
2
+ import { Directionality } from '@angular/cdk/bidi';
3
+ import * as i0 from '@angular/core';
4
+ import { inject, NgZone, Renderer2, signal, Injectable, InjectionToken, ElementRef, input, computed, contentChild, Directive, booleanAttribute, model, contentChildren, effect, TemplateRef, untracked, PLATFORM_ID, ViewContainerRef } from '@angular/core';
5
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
6
+ import { createHoverObservable, waitForElementAnimations, computedPrevious, isElement } from '@spartan-ng/brain/core';
7
+ import { Subject, BehaviorSubject, of, fromEvent, merge, combineLatest } from 'rxjs';
8
+ import { switchMap, share, takeUntil, map, pairwise, filter, startWith, debounceTime, distinctUntilChanged, delay, tap } from 'rxjs/operators';
9
+ import * as i1 from '@spartan-ng/brain/button';
10
+ import { BrnButton } from '@spartan-ng/brain/button';
11
+ import { hasModifierKey } from '@angular/cdk/keycodes';
12
+ import { Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
13
+ import { TemplatePortal } from '@angular/cdk/portal';
14
+ import { isPlatformBrowser } from '@angular/common';
15
+
16
+ const horizontalPositions = [
17
+ {
18
+ originX: 'start',
19
+ originY: 'bottom',
20
+ overlayX: 'start',
21
+ overlayY: 'top',
22
+ },
23
+ ];
24
+ const verticalPositions = [
25
+ {
26
+ originX: 'end',
27
+ originY: 'top',
28
+ overlayX: 'start',
29
+ overlayY: 'top',
30
+ },
31
+ {
32
+ originX: 'start',
33
+ originY: 'top',
34
+ overlayX: 'end',
35
+ overlayY: 'top',
36
+ },
37
+ ];
38
+ class BrnNavigationMenuContentService {
39
+ static _id = 0;
40
+ _overlay = inject(Overlay);
41
+ _zone = inject(NgZone);
42
+ _psBuilder = inject(OverlayPositionBuilder);
43
+ _renderer = inject(Renderer2);
44
+ _content = signal(null);
45
+ id = `brn-navigation-menu-content-${++BrnNavigationMenuContentService._id}`;
46
+ _shouldDetach = false;
47
+ _config = {};
48
+ _overlayRef;
49
+ _positionStrategy;
50
+ _destroyed$ = new Subject();
51
+ _contentEl = signal(undefined);
52
+ contentEl = this._contentEl.asReadonly();
53
+ _overlayHoveredObservables$ = new BehaviorSubject(undefined);
54
+ _overlayShiftTabObservables$ = new BehaviorSubject(undefined);
55
+ _overlayEscapeObservables$ = new BehaviorSubject(undefined);
56
+ hovered$ = this._overlayHoveredObservables$.pipe(switchMap((overlayHoveredObservable) => (overlayHoveredObservable !== undefined ? overlayHoveredObservable : of())), share());
57
+ _shiftTabPressed$ = this._overlayShiftTabObservables$.pipe(switchMap((contentFocused$) => (contentFocused$ !== undefined ? contentFocused$ : of())));
58
+ escapePressed$ = this._overlayEscapeObservables$.pipe(switchMap((contentFocused$) => (contentFocused$ !== undefined ? contentFocused$ : of())));
59
+ constructor() {
60
+ this._shiftTabPressed$.pipe(takeUntil(this._destroyed$)).subscribe((e) => {
61
+ if (this._config.attachTo?.nativeElement) {
62
+ e.preventDefault();
63
+ this._config.attachTo.nativeElement.focus();
64
+ }
65
+ });
66
+ }
67
+ setConfig(config) {
68
+ this._config = config;
69
+ if (config.attachTo) {
70
+ const positions = this._getPositions(config.orientation);
71
+ this._positionStrategy = this._buildPositionStrategy(config.attachTo, positions);
72
+ this._config = {
73
+ ...this._config,
74
+ positionStrategy: this._positionStrategy,
75
+ scrollStrategy: this._overlay.scrollStrategies.reposition(),
76
+ };
77
+ }
78
+ this._overlayRef = this._overlay.create(this._config);
79
+ }
80
+ updateOrientation(orientation) {
81
+ if (!this._config.attachTo)
82
+ return;
83
+ const positions = this._getPositions(orientation);
84
+ this._positionStrategy = this._buildPositionStrategy(this._config.attachTo, positions);
85
+ this._config = {
86
+ ...this._config,
87
+ positionStrategy: this._positionStrategy,
88
+ scrollStrategy: this._overlay.scrollStrategies.reposition(),
89
+ };
90
+ this._overlayRef?.updatePositionStrategy(this._positionStrategy);
91
+ }
92
+ updateDirection(dir) {
93
+ this._overlayRef?.setDirection(dir);
94
+ }
95
+ setContent(value, vcr) {
96
+ this._content.set(new TemplatePortal(value, vcr));
97
+ if (!this._overlayRef) {
98
+ this._overlayRef = this._overlay.create(this._config);
99
+ }
100
+ }
101
+ show() {
102
+ const content = this._content();
103
+ if (!content || !this._overlayRef)
104
+ return;
105
+ this._shouldDetach = false;
106
+ this._overlayRef?.detach();
107
+ const embededViewRef = this._overlayRef?.attach(content);
108
+ this._destroyed$ = new Subject();
109
+ const contentEl = embededViewRef.rootNodes[0];
110
+ const attachToId = this._config.attachTo?.nativeElement.id;
111
+ this._renderer.setAttribute(contentEl, 'id', this.id);
112
+ if (attachToId !== undefined) {
113
+ this._renderer.setAttribute(contentEl, 'aria-labelledby', attachToId);
114
+ }
115
+ this._contentEl.set(contentEl);
116
+ this._overlayHoveredObservables$.next(createHoverObservable(this._overlayRef.hostElement, this._zone, this._destroyed$).pipe(map((e) => e.hover)));
117
+ this._overlayShiftTabObservables$.next(fromEvent(contentEl, 'keydown').pipe(switchMap((e) => (e.key === 'Tab' && e.shiftKey && e.target === this.contentEl() ? of(e) : of())), takeUntil(this._destroyed$)));
118
+ this._overlayEscapeObservables$.next(fromEvent(contentEl, 'keydown').pipe(switchMap((e) => (e.key === 'Escape' && !hasModifierKey(e) ? of(e) : of())), takeUntil(this._destroyed$)));
119
+ }
120
+ async hide() {
121
+ const contentEl = this._contentEl();
122
+ if (!contentEl)
123
+ return;
124
+ this._shouldDetach = true;
125
+ if (!this._hasAnimation()) {
126
+ this._detach();
127
+ }
128
+ await waitForElementAnimations(contentEl);
129
+ this._detach();
130
+ }
131
+ _detach = () => {
132
+ if (!this._shouldDetach)
133
+ return;
134
+ this._overlayRef?.detach();
135
+ this._contentEl.set(undefined);
136
+ this._destroyed$.next();
137
+ this._destroyed$.complete();
138
+ this._destroyed$ = new Subject();
139
+ };
140
+ _hasAnimation() {
141
+ const contentEl = this.contentEl();
142
+ if (!contentEl)
143
+ return;
144
+ return getComputedStyle(contentEl).animationName !== 'none';
145
+ }
146
+ _getPositions(orientation) {
147
+ return orientation === 'vertical' ? verticalPositions : horizontalPositions;
148
+ }
149
+ _buildPositionStrategy(attachTo, positions) {
150
+ return this._psBuilder
151
+ .flexibleConnectedTo(attachTo)
152
+ .withPositions(positions)
153
+ .withDefaultOffsetY(0)
154
+ .withDefaultOffsetX(0)
155
+ .withPush(false);
156
+ }
157
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuContentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
158
+ /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuContentService });
159
+ }
160
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuContentService, decorators: [{
161
+ type: Injectable
162
+ }], ctorParameters: () => [] });
163
+
164
+ const BrnNavigationMenuFocusable = new InjectionToken('BrnNavigationMenuFocusable');
165
+ function provideBrnNavigationMenuFocusable(focusable) {
166
+ return { provide: BrnNavigationMenuFocusable, useExisting: focusable };
167
+ }
168
+
169
+ const BrnNavigationMenuItemToken = new InjectionToken('BrnNavigationMenuItemToken');
170
+ function injectBrnNavigationMenuItem() {
171
+ return inject(BrnNavigationMenuItemToken);
172
+ }
173
+ function provideBrnNavigationMenuItem(navigationMenuItem) {
174
+ return { provide: BrnNavigationMenuItemToken, useExisting: navigationMenuItem };
175
+ }
176
+
177
+ const BrnNavigationMenuToken = new InjectionToken('BrnNavigationMenuToken');
178
+ function injectBrnNavigationMenu() {
179
+ return inject(BrnNavigationMenuToken);
180
+ }
181
+ function provideBrnNavigationMenu(navigationMenu) {
182
+ return { provide: BrnNavigationMenuToken, useExisting: navigationMenu };
183
+ }
184
+
185
+ class BrnNavigationMenuItem {
186
+ static _id = 0;
187
+ _navigationMenu = injectBrnNavigationMenu();
188
+ _contentService = inject(BrnNavigationMenuContentService);
189
+ el = inject(ElementRef);
190
+ navMenuElRef = this._navigationMenu.el;
191
+ /** The id of the navigation menu item */
192
+ id = input(`brn-navigation-menu-item-${++BrnNavigationMenuItem._id}`);
193
+ isActive = computed(() => this.id() === this._navigationMenu.value());
194
+ wasActive = computed(() => this.id() === this._navigationMenu.previousValue());
195
+ state = computed(() => (this.isActive() ? 'open' : 'closed'));
196
+ contentTemplate = signal(null);
197
+ contentHovered$ = this._contentService.hovered$;
198
+ subNavVisible$ = new Subject();
199
+ focusable = contentChild.required(BrnNavigationMenuFocusable);
200
+ _triggerOrLinkBtn = contentChild.required(BrnNavigationMenuFocusable, { read: BrnButton });
201
+ disabled = computed(() => this._triggerOrLinkBtn().disabled());
202
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
203
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.8", type: BrnNavigationMenuItem, isStandalone: true, selector: "li[brnNavigationMenuItem]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "navigation-menu-item" }, properties: { "id": "id()", "attr.data-disabled": "disabled() || null" } }, providers: [provideBrnNavigationMenuItem(BrnNavigationMenuItem), BrnNavigationMenuContentService], queries: [{ propertyName: "focusable", first: true, predicate: BrnNavigationMenuFocusable, descendants: true, isSignal: true }, { propertyName: "_triggerOrLinkBtn", first: true, predicate: BrnNavigationMenuFocusable, descendants: true, read: BrnButton, isSignal: true }], ngImport: i0 });
204
+ }
205
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuItem, decorators: [{
206
+ type: Directive,
207
+ args: [{
208
+ selector: 'li[brnNavigationMenuItem]',
209
+ host: {
210
+ '[id]': 'id()',
211
+ '[attr.data-disabled]': 'disabled() || null',
212
+ 'data-slot': 'navigation-menu-item',
213
+ },
214
+ providers: [provideBrnNavigationMenuItem(BrnNavigationMenuItem), BrnNavigationMenuContentService],
215
+ }]
216
+ }] });
217
+
218
+ class BrnNavigationMenuLink {
219
+ _navigationMenu = injectBrnNavigationMenu();
220
+ _navigationMenuItem = injectBrnNavigationMenuItem();
221
+ _el = inject(ElementRef);
222
+ // Returns false if this BrnNavigationMenuLink is used "standalone", meaning it doesn't have a parent BrnNavigationMenuItem element.
223
+ _hasParentNavMenuItem = this._el.nativeElement.parentElement === this._navigationMenuItem.el.nativeElement;
224
+ /**
225
+ * Used to identify the link as the currently active page.
226
+ */
227
+ active = input(undefined, { transform: booleanAttribute });
228
+ _isActive = computed(() => {
229
+ const active = this.active();
230
+ if (active !== undefined)
231
+ return active;
232
+ if (!this._hasParentNavMenuItem)
233
+ return false;
234
+ return this._navigationMenuItem.isActive();
235
+ });
236
+ get disabled() {
237
+ return this._navigationMenuItem.disabled();
238
+ }
239
+ focus(_origin) {
240
+ if (!this._hasParentNavMenuItem || this._navigationMenuItem.disabled())
241
+ return;
242
+ this._el.nativeElement.focus();
243
+ }
244
+ handleFocus() {
245
+ if (!this._hasParentNavMenuItem)
246
+ return;
247
+ this._navigationMenu.setActiveItem(this);
248
+ }
249
+ onClick() {
250
+ if (!this._hasParentNavMenuItem)
251
+ return;
252
+ this._navigationMenu.value.set(this._navigationMenuItem.id());
253
+ }
254
+ activate() {
255
+ if (!this._hasParentNavMenuItem)
256
+ return;
257
+ this._navigationMenu.value.set(this._navigationMenuItem.id());
258
+ }
259
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
260
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.8", type: BrnNavigationMenuLink, isStandalone: true, selector: "a[brnNavigationMenuLink]", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "navigation-menu-link" }, listeners: { "click": "onClick()", "mouseenter": "activate()", "focus": "handleFocus()" }, properties: { "attr.data-active": "_isActive() ? \"\" : undefined", "attr.aria-current": "_isActive() ? \"page\" : undefined" } }, providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuLink)], hostDirectives: [{ directive: i1.BrnButton, inputs: ["disabled", "disabled"] }], ngImport: i0 });
261
+ }
262
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuLink, decorators: [{
263
+ type: Directive,
264
+ args: [{
265
+ selector: 'a[brnNavigationMenuLink]',
266
+ host: {
267
+ '(click)': 'onClick()',
268
+ '(mouseenter)': 'activate()',
269
+ '(focus)': 'handleFocus()',
270
+ '[attr.data-active]': '_isActive() ? "" : undefined',
271
+ '[attr.aria-current]': '_isActive() ? "page" : undefined',
272
+ 'data-slot': 'navigation-menu-link',
273
+ },
274
+ hostDirectives: [
275
+ {
276
+ directive: BrnButton,
277
+ inputs: ['disabled'],
278
+ },
279
+ ],
280
+ providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuLink)],
281
+ }]
282
+ }] });
283
+
284
+ const BrnParentNavMenu = new InjectionToken('BrnParentNavMenu');
285
+ function injectBrnParentNavMenu() {
286
+ return inject(BrnParentNavMenu, { optional: true });
287
+ }
288
+ function provideBrnParentNavMenu(parentNavMenu) {
289
+ return { provide: BrnParentNavMenu, useFactory: parentNavMenu };
290
+ }
291
+
292
+ function areArraysSameByElRef(arr1, arr2) {
293
+ if (arr1.length !== arr2.length)
294
+ return false;
295
+ return arr1.every((item, index) => item === arr2[index]);
296
+ }
297
+ class BrnNavigationMenu {
298
+ _directionality = inject(Directionality);
299
+ _zone = inject(NgZone);
300
+ _destroy$ = new Subject();
301
+ el = inject(ElementRef);
302
+ parentNavMenu = injectBrnParentNavMenu();
303
+ /**
304
+ * The controlled value of the menu item to activate.
305
+ */
306
+ value = model();
307
+ /**
308
+ * The duration from when the mouse enters a trigger until the content opens.
309
+ */
310
+ delayDuration = input(200);
311
+ /**
312
+ * How much time a user has to enter another trigger without incurring a delay again.
313
+ */
314
+ skipDelayDuration = input(300);
315
+ /**
316
+ * The reading direction of the menu when applicable.
317
+ */
318
+ dir = input();
319
+ /**
320
+ * The orientation of the menu.
321
+ */
322
+ orientation = input('horizontal');
323
+ _isOpenDelayed = signal(true);
324
+ isOpenDelayed = this._isOpenDelayed.asReadonly();
325
+ _skipDelayTimerRef;
326
+ _navAndSubnavMenuItems = contentChildren(BrnNavigationMenuItem, { descendants: true });
327
+ menuItems = computed(() => this._navAndSubnavMenuItems().filter((mi) => mi.navMenuElRef === this.el), {
328
+ equal: areArraysSameByElRef,
329
+ });
330
+ menuItemIds = computed(() => this.menuItems().map((mi) => mi.id()));
331
+ _triggersAndLinks = computed(() => this.menuItems().map((mi) => mi.focusable()));
332
+ _linkMenuItems = computed(() => this.menuItems().filter((i) => i.focusable() instanceof BrnNavigationMenuLink));
333
+ _keyManager = computed(() => {
334
+ return new FocusKeyManager(this._triggersAndLinks())
335
+ .withHorizontalOrientation(this._dir())
336
+ .withHomeAndEnd()
337
+ .withPageUpDown()
338
+ .withWrap()
339
+ .skipPredicate((e) => !!e.disabled);
340
+ });
341
+ _reset$ = toObservable(this.value).pipe(pairwise(), filter(([prev, curr]) => curr === undefined && curr !== prev), map(() => undefined));
342
+ _hovered$ = merge(createHoverObservable(this.el.nativeElement, this._zone, this._destroy$).pipe(map((e) => e.hover)), this._reset$).pipe(startWith(undefined));
343
+ _contentHovered$ = merge(toObservable(this._navAndSubnavMenuItems).pipe(switchMap((menuItems) => merge(...menuItems.map((mi) => mi.contentHovered$)))), this._reset$).pipe(startWith(undefined));
344
+ previousValue = computedPrevious(this.value);
345
+ _dir$ = toObservable(this.dir);
346
+ /**
347
+ * The reading direction of the menu when applicable.
348
+ * If input is not passed, inherits globally from Directionality or assumes LTR (left-to-right) reading mode.
349
+ */
350
+ _dir = toSignal(combineLatest([
351
+ this._dir$.pipe(startWith(undefined)),
352
+ this._directionality.change.pipe(startWith(undefined)),
353
+ of('ltr'),
354
+ ]).pipe(map(([dir, dirChange, fallback]) => dir ?? dirChange ?? fallback)), { requireSync: true });
355
+ context = computed(() => ({ orientation: this.orientation(), dir: this._dir() }));
356
+ constructor() {
357
+ effect(() => {
358
+ const isOpen = this.value() !== undefined;
359
+ const hasSkipDelayDuration = this.skipDelayDuration() > 0;
360
+ if (isOpen) {
361
+ clearTimeout(this._skipDelayTimerRef);
362
+ if (hasSkipDelayDuration)
363
+ this._isOpenDelayed.set(false);
364
+ }
365
+ else {
366
+ clearTimeout(this._skipDelayTimerRef);
367
+ this._skipDelayTimerRef = setTimeout(() => {
368
+ this._isOpenDelayed.set(true);
369
+ }, this.skipDelayDuration());
370
+ }
371
+ });
372
+ combineLatest([this._hovered$, this._contentHovered$])
373
+ .pipe(debounceTime(0), filter(([hovered, contentHovered]) => !(hovered === undefined && contentHovered === undefined)), takeUntil(this._destroy$))
374
+ .subscribe(([hovered, contentHovered]) => {
375
+ if (!hovered && !contentHovered) {
376
+ this.value.set(undefined);
377
+ }
378
+ });
379
+ }
380
+ isLink(id) {
381
+ return !!this._linkMenuItems().find((item) => item.id() === id);
382
+ }
383
+ setActiveItem(item) {
384
+ this._keyManager().setActiveItem(item);
385
+ }
386
+ handleKeydown(event) {
387
+ this._keyManager().onKeydown(event);
388
+ }
389
+ ngOnDestroy() {
390
+ this._destroy$.next();
391
+ this._destroy$.complete();
392
+ clearTimeout(this._skipDelayTimerRef);
393
+ }
394
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
395
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.8", type: BrnNavigationMenu, isStandalone: true, selector: "nav[brnNavigationMenu]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, delayDuration: { classPropertyName: "delayDuration", publicName: "delayDuration", isSignal: true, isRequired: false, transformFunction: null }, skipDelayDuration: { classPropertyName: "skipDelayDuration", publicName: "skipDelayDuration", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "aria-label": "Main", "data-slot": "navigation-menu" }, listeners: { "keydown": "handleKeydown($event)" }, properties: { "attr.data-orientation": "orientation()", "attr.dir": "_dir()" } }, providers: [provideBrnNavigationMenu(BrnNavigationMenu)], queries: [{ propertyName: "_navAndSubnavMenuItems", predicate: BrnNavigationMenuItem, descendants: true, isSignal: true }], ngImport: i0 });
396
+ }
397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenu, decorators: [{
398
+ type: Directive,
399
+ args: [{
400
+ selector: 'nav[brnNavigationMenu]',
401
+ host: {
402
+ '(keydown)': 'handleKeydown($event)',
403
+ '[attr.data-orientation]': 'orientation()',
404
+ '[attr.dir]': '_dir()',
405
+ 'aria-label': 'Main',
406
+ 'data-slot': 'navigation-menu',
407
+ },
408
+ providers: [provideBrnNavigationMenu(BrnNavigationMenu)],
409
+ }]
410
+ }], ctorParameters: () => [] });
411
+
412
+ class BrnNavigationMenuContent {
413
+ _navigationMenu = injectBrnNavigationMenu();
414
+ _navigationMenuItem = injectBrnNavigationMenuItem();
415
+ _contentService = inject(BrnNavigationMenuContentService);
416
+ _tpl = inject(TemplateRef);
417
+ _renderer = inject(Renderer2);
418
+ _subNavContext = inject(BrnParentNavMenu);
419
+ _navMenuValue = this._navigationMenu.value;
420
+ _prevNavMenuValue = this._navigationMenu.previousValue;
421
+ _id = this._navigationMenuItem.id;
422
+ _isActive = this._navigationMenuItem.isActive;
423
+ _wasActive = this._navigationMenuItem.wasActive;
424
+ _state = this._navigationMenuItem.state;
425
+ _contentEl = this._contentService.contentEl;
426
+ _menuItemsIds = this._navigationMenu.menuItemIds;
427
+ _orientation = computed(() => this._navigationMenu.context().orientation);
428
+ _dir = computed(() => this._navigationMenu.context().dir);
429
+ constructor() {
430
+ if (!this._tpl)
431
+ return;
432
+ this._navigationMenuItem.contentTemplate.set(this._tpl);
433
+ this._navigationMenuItem.subNavVisible$.next(this._subNavContext.subNavVisible$);
434
+ effect(() => {
435
+ const el = this._contentEl();
436
+ if (el) {
437
+ this._renderer.setAttribute(el, 'tabindex', '0');
438
+ this._renderer.setAttribute(el, 'data-slot', 'navigation-menu-content');
439
+ }
440
+ });
441
+ effect(() => {
442
+ const el = this._contentEl();
443
+ if (el) {
444
+ this._renderer.setAttribute(el, 'data-state', this._state());
445
+ }
446
+ });
447
+ effect(() => {
448
+ const el = this._contentEl();
449
+ if (el) {
450
+ this._renderer.setAttribute(el, 'data-orientation', this._orientation());
451
+ }
452
+ });
453
+ effect(() => {
454
+ const isActive = this._isActive();
455
+ const wasActive = this._wasActive();
456
+ const dir = this._dir();
457
+ const orientation = this._orientation();
458
+ const el = untracked(this._contentEl);
459
+ if (!el)
460
+ return;
461
+ const id = this._id();
462
+ const menuItemsIds = dir === 'rtl' && orientation === 'horizontal' ? this._menuItemsIds().slice().reverse() : this._menuItemsIds();
463
+ if (isActive) {
464
+ const prevNavMenuValue = this._prevNavMenuValue();
465
+ const isPrevLink = untracked(() => this._navigationMenu.isLink(prevNavMenuValue));
466
+ if (prevNavMenuValue && !isPrevLink) {
467
+ const motion = menuItemsIds.indexOf(id) > menuItemsIds.indexOf(prevNavMenuValue) ? 'from-end' : 'from-start';
468
+ this._renderer.setAttribute(el, 'data-motion', motion);
469
+ }
470
+ }
471
+ else if (wasActive) {
472
+ const navMenuValue = this._navMenuValue();
473
+ const isLink = untracked(() => this._navigationMenu.isLink(navMenuValue));
474
+ if (!navMenuValue || isLink)
475
+ return;
476
+ const motion = menuItemsIds.indexOf(id) > menuItemsIds.indexOf(navMenuValue) ? 'to-end' : 'to-start';
477
+ this._renderer.setAttribute(el, 'data-motion', motion);
478
+ }
479
+ });
480
+ }
481
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
482
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: BrnNavigationMenuContent, isStandalone: true, selector: "[brnNavigationMenuContent]", providers: [provideBrnParentNavMenu((() => ({ subNavVisible$: new Subject() })))], ngImport: i0 });
483
+ }
484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuContent, decorators: [{
485
+ type: Directive,
486
+ args: [{
487
+ selector: '[brnNavigationMenuContent]',
488
+ providers: [provideBrnParentNavMenu((() => ({ subNavVisible$: new Subject() })))],
489
+ }]
490
+ }], ctorParameters: () => [] });
491
+
492
+ class BrnNavigationMenuList {
493
+ _navigationMenu = injectBrnNavigationMenu();
494
+ _orientation = computed(() => this._navigationMenu.context().orientation);
495
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuList, deps: [], target: i0.ɵɵFactoryTarget.Directive });
496
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: BrnNavigationMenuList, isStandalone: true, selector: "ul[brnNavigationMenuList]", host: { attributes: { "data-slot": "navigation-menu-list" }, properties: { "attr.data-orientation": "_orientation()" } }, ngImport: i0 });
497
+ }
498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuList, decorators: [{
499
+ type: Directive,
500
+ args: [{
501
+ selector: 'ul[brnNavigationMenuList]',
502
+ host: {
503
+ '[attr.data-orientation]': '_orientation()',
504
+ 'data-slot': 'navigation-menu-list',
505
+ },
506
+ }]
507
+ }] });
508
+
509
+ class BrnNavigationMenuTrigger {
510
+ static _id = 0;
511
+ _isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
512
+ _navigationMenu = injectBrnNavigationMenu();
513
+ _navigationMenuItem = injectBrnNavigationMenuItem();
514
+ _destroy$ = new Subject();
515
+ _vcr = inject(ViewContainerRef);
516
+ _zone = inject(NgZone);
517
+ _el = inject(ElementRef);
518
+ _contentService = inject(BrnNavigationMenuContentService);
519
+ _id = `brn-navigation-menu-trigger-${++BrnNavigationMenuTrigger._id}`;
520
+ _parentNavMenu = this._navigationMenu.parentNavMenu;
521
+ _isActive = this._navigationMenuItem.isActive;
522
+ _contentId = this._contentService.id;
523
+ _state = this._navigationMenuItem.state;
524
+ _dir = computed(() => this._navigationMenu.context().dir);
525
+ _orientation = computed(() => this._navigationMenu.context().orientation);
526
+ _isOpenDelayed = this._navigationMenu.isOpenDelayed;
527
+ _delayDuration = this._navigationMenu.delayDuration;
528
+ _contentTemplate = this._navigationMenuItem.contentTemplate;
529
+ _isSubNavVisible = toSignal(this._navigationMenuItem.subNavVisible$.pipe(switchMap((c) => c)), {
530
+ initialValue: false,
531
+ });
532
+ _isActive$ = toObservable(this._navigationMenuItem.isActive).pipe(map((value) => ({ type: 'set', visible: value })));
533
+ _clicked$ = fromEvent(this._el.nativeElement, 'click').pipe(map(() => !this._navigationMenuItem.isActive()), map((value) => ({ type: 'click', visible: value })));
534
+ _hovered$ = merge(createHoverObservable(this._el.nativeElement, this._zone, this._destroy$), this._contentService.hovered$.pipe(map((v) => ({ hover: v, relatedTarget: null })))).pipe(
535
+ // Hover event is NOT allowed when a sub-navigation is currently visible, AND the current hover event is false.
536
+ filter((e) => !(this._isSubNavVisible() && !e.hover)), map((e) => ({ type: 'hover', visible: e.hover, relatedTarget: e.relatedTarget })));
537
+ _showing$ = merge(this._isActive$, this._clicked$, this._hovered$).pipe(debounceTime(0), distinctUntilChanged((prev, curr) => prev.visible === curr.visible), switchMap((ev) => {
538
+ const shouldDelay = ev.visible && ev.type !== 'click' && this._isOpenDelayed();
539
+ return of(ev).pipe(delay(shouldDelay ? this._delayDuration() : 0));
540
+ }),
541
+ // Deactivate needs to be called if the menu item content is hidden with a user click OR
542
+ // If nav item is hovered out to a disabled sibling nav item
543
+ tap((ev) => {
544
+ if (ev.visible) {
545
+ this._activate();
546
+ }
547
+ else {
548
+ const shouldDeactivate = (ev.type === 'click' || !this._isHoverOnSibling(ev)) && this._navigationMenuItem.isActive();
549
+ if (shouldDeactivate) {
550
+ this._deactivate();
551
+ }
552
+ }
553
+ }), share(), takeUntil(this._destroy$));
554
+ get disabled() {
555
+ return this._navigationMenuItem.disabled();
556
+ }
557
+ constructor() {
558
+ effect(() => {
559
+ const value = this._contentTemplate();
560
+ untracked(() => {
561
+ if (value) {
562
+ this._contentService.setContent(value, this._vcr);
563
+ }
564
+ });
565
+ });
566
+ effect(() => {
567
+ const orientation = this._orientation();
568
+ untracked(() => {
569
+ this._contentService.updateOrientation(orientation);
570
+ });
571
+ });
572
+ effect(() => {
573
+ const dir = this._dir();
574
+ untracked(() => {
575
+ this._contentService.updateDirection(dir);
576
+ });
577
+ });
578
+ }
579
+ ngOnInit() {
580
+ this._contentService.setConfig({ attachTo: this._el, direction: this._dir(), orientation: this._orientation() });
581
+ this._showing$.pipe(takeUntil(this._destroy$)).subscribe((ev) => {
582
+ if (this._parentNavMenu) {
583
+ this._parentNavMenu.subNavVisible$.next(ev.visible);
584
+ }
585
+ if (ev.visible) {
586
+ if (this._isBrowser) {
587
+ this._contentService.show();
588
+ }
589
+ }
590
+ else {
591
+ this._contentService.hide();
592
+ }
593
+ });
594
+ this._contentService.escapePressed$.pipe(takeUntil(this._destroy$)).subscribe((e) => {
595
+ e.preventDefault();
596
+ this._el.nativeElement.focus();
597
+ this._deactivate();
598
+ });
599
+ }
600
+ ngOnDestroy() {
601
+ this._destroy$.next();
602
+ this._destroy$.complete();
603
+ }
604
+ focus(_origin) {
605
+ if (this._navigationMenuItem.disabled())
606
+ return;
607
+ this._el.nativeElement.focus();
608
+ }
609
+ handleFocus() {
610
+ this._navigationMenu.setActiveItem(this);
611
+ }
612
+ onTab(e) {
613
+ const contentEl = this._contentService.contentEl();
614
+ if (contentEl && !hasModifierKey(e)) {
615
+ e.preventDefault();
616
+ contentEl.focus();
617
+ }
618
+ }
619
+ onEscape(e) {
620
+ e.preventDefault();
621
+ this._deactivate();
622
+ }
623
+ _activate() {
624
+ this._navigationMenu.value.set(this._navigationMenuItem.id());
625
+ }
626
+ _deactivate() {
627
+ this._navigationMenu.value.set(undefined);
628
+ }
629
+ _isHoverOnSibling(ev) {
630
+ if (ev.type !== 'hover' || !isElement(ev.relatedTarget))
631
+ return false;
632
+ const menuItem = this._isMenuItemOrChild(ev.relatedTarget);
633
+ return !!menuItem && !menuItem.disabled();
634
+ }
635
+ _isMenuItemOrChild(node) {
636
+ return this._navigationMenu
637
+ .menuItems()
638
+ .find((ref) => ref.el.nativeElement === node || ref.el.nativeElement.contains(node));
639
+ }
640
+ /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
641
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: BrnNavigationMenuTrigger, isStandalone: true, selector: "button[brnNavigationMenuTrigger]", host: { attributes: { "data-slot": "navigation-menu-trigger" }, listeners: { "keydown.escape": "onEscape($event)", "keydown.tab": "onTab($event)", "focus": "handleFocus()" }, properties: { "id": "_id", "attr.data-state": "_state()", "attr.aria-expanded": "_isActive()", "attr.aria-controls": "_contentId" } }, providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuTrigger)], hostDirectives: [{ directive: i1.BrnButton, inputs: ["disabled", "disabled"] }], ngImport: i0 });
642
+ }
643
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: BrnNavigationMenuTrigger, decorators: [{
644
+ type: Directive,
645
+ args: [{
646
+ selector: 'button[brnNavigationMenuTrigger]',
647
+ host: {
648
+ '(keydown.escape)': 'onEscape($event)',
649
+ '(keydown.tab)': 'onTab($event)',
650
+ '(focus)': 'handleFocus()',
651
+ '[id]': '_id',
652
+ '[attr.data-state]': '_state()',
653
+ '[attr.aria-expanded]': '_isActive()',
654
+ '[attr.aria-controls]': '_contentId',
655
+ 'data-slot': 'navigation-menu-trigger',
656
+ },
657
+ hostDirectives: [
658
+ {
659
+ directive: BrnButton,
660
+ inputs: ['disabled'],
661
+ },
662
+ ],
663
+ providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuTrigger)],
664
+ }]
665
+ }], ctorParameters: () => [] });
666
+
667
+ const BrnNavigationMenuImports = [
668
+ BrnNavigationMenu,
669
+ BrnNavigationMenuItem,
670
+ BrnNavigationMenuList,
671
+ BrnNavigationMenuTrigger,
672
+ BrnNavigationMenuContent,
673
+ BrnNavigationMenuLink,
674
+ ];
675
+
676
+ /**
677
+ * Generated bundle index. Do not edit.
678
+ */
679
+
680
+ export { BrnNavigationMenu, BrnNavigationMenuContent, BrnNavigationMenuImports, BrnNavigationMenuItem, BrnNavigationMenuLink, BrnNavigationMenuList, BrnNavigationMenuTrigger };
681
+ //# sourceMappingURL=spartan-ng-brain-navigation-menu.mjs.map