@radix-ng/primitives 0.51.0 → 1.0.0-beta.0
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/fesm2022/radix-ng-primitives-accordion.mjs +105 -38
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +221 -129
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-arrow.mjs +20 -4
- package/fesm2022/radix-ng-primitives-arrow.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-aspect-ratio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-avatar.mjs +54 -61
- package/fesm2022/radix-ng-primitives-avatar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-button.mjs +123 -0
- package/fesm2022/radix-ng-primitives-button.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +378 -54
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs +182 -81
- package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collection.mjs +40 -57
- package/fesm2022/radix-ng-primitives-collection.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs +140 -424
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +735 -744
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs +1 -0
- package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +51 -45
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +655 -327
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +70 -46
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +1059 -0
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +363 -0
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-fieldset.mjs +79 -0
- package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +23 -8
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +172 -0
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-label.mjs +6 -6
- package/fesm2022/radix-ng-primitives-label.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +1480 -344
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +290 -162
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs +271 -0
- package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1052 -1553
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +1102 -367
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-pagination.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +978 -989
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +91 -41
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +34 -10
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +134 -246
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +997 -0
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-progress.mjs +223 -84
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +191 -51
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +96 -50
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +791 -509
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-separator.mjs +12 -35
- package/fesm2022/radix-ng-primitives-separator.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +969 -717
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +15 -19
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +125 -113
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +381 -108
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +55 -46
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +121 -247
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle.mjs +98 -61
- package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +303 -92
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +690 -1071
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs +25 -66
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs.map +1 -1
- package/meter/README.md +3 -0
- package/navigation-menu/README.md +2 -1
- package/package.json +31 -18
- package/portal/README.md +2 -0
- package/preview-card/README.md +3 -0
- package/schematics/collection.json +1 -0
- package/schematics/ng-add/index.d.ts +3 -2
- package/schematics/ng-add/index.js +62 -31
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/package-config.d.ts +4 -2
- package/schematics/ng-add/package-config.js +10 -2
- package/schematics/ng-add/package-config.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +3 -0
- package/schematics/ng-add/schema.js +3 -0
- package/schematics/ng-add/schema.js.map +1 -0
- package/schematics/ng-add/schema.json +14 -0
- package/select/README.md +2 -0
- package/types/radix-ng-primitives-accordion.d.ts +48 -14
- package/types/radix-ng-primitives-alert-dialog.d.ts +95 -38
- package/types/radix-ng-primitives-arrow.d.ts +1 -1
- package/types/radix-ng-primitives-aspect-ratio.d.ts +1 -1
- package/types/radix-ng-primitives-avatar.d.ts +7 -11
- package/types/radix-ng-primitives-button.d.ts +73 -0
- package/types/radix-ng-primitives-calendar.d.ts +1 -2
- package/types/radix-ng-primitives-checkbox.d.ts +201 -32
- package/types/radix-ng-primitives-collapsible.d.ts +112 -39
- package/types/radix-ng-primitives-collection.d.ts +38 -34
- package/types/radix-ng-primitives-config.d.ts +1 -1
- package/types/radix-ng-primitives-context-menu.d.ts +60 -116
- package/types/radix-ng-primitives-core.d.ts +307 -236
- package/types/radix-ng-primitives-cropper.d.ts +2 -2
- package/types/radix-ng-primitives-date-field.d.ts +38 -23
- package/types/radix-ng-primitives-dialog.d.ts +282 -165
- package/types/radix-ng-primitives-dismissable-layer.d.ts +15 -7
- package/types/radix-ng-primitives-drawer.d.ts +448 -0
- package/types/radix-ng-primitives-editable.d.ts +1 -1
- package/types/radix-ng-primitives-field.d.ts +373 -0
- package/types/radix-ng-primitives-fieldset.d.ts +48 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +13 -5
- package/types/radix-ng-primitives-input.d.ts +87 -0
- package/types/radix-ng-primitives-label.d.ts +0 -1
- package/types/radix-ng-primitives-menu.d.ts +572 -99
- package/types/radix-ng-primitives-menubar.d.ts +60 -50
- package/types/radix-ng-primitives-meter.d.ts +193 -0
- package/types/radix-ng-primitives-navigation-menu.d.ts +422 -340
- package/types/radix-ng-primitives-number-field.d.ts +405 -145
- package/types/radix-ng-primitives-pagination.d.ts +2 -2
- package/types/radix-ng-primitives-popover.d.ts +365 -351
- package/types/radix-ng-primitives-popper.d.ts +49 -9
- package/types/radix-ng-primitives-portal.d.ts +14 -6
- package/types/radix-ng-primitives-presence.d.ts +28 -76
- package/types/radix-ng-primitives-preview-card.d.ts +359 -0
- package/types/radix-ng-primitives-progress.d.ts +174 -48
- package/types/radix-ng-primitives-radio.d.ts +55 -25
- package/types/radix-ng-primitives-roving-focus.d.ts +30 -21
- package/types/radix-ng-primitives-select.d.ts +475 -177
- package/types/radix-ng-primitives-separator.d.ts +7 -32
- package/types/radix-ng-primitives-slider.d.ts +315 -201
- package/types/radix-ng-primitives-stepper.d.ts +5 -7
- package/types/radix-ng-primitives-switch.d.ts +86 -71
- package/types/radix-ng-primitives-tabs.d.ts +213 -79
- package/types/radix-ng-primitives-time-field.d.ts +42 -27
- package/types/radix-ng-primitives-toggle-group.d.ts +85 -164
- package/types/radix-ng-primitives-toggle.d.ts +43 -53
- package/types/radix-ng-primitives-toolbar.d.ts +163 -38
- package/types/radix-ng-primitives-tooltip.d.ts +347 -384
- package/types/radix-ng-primitives-visually-hidden.d.ts +19 -19
- package/dropdown-menu/README.md +0 -1
- package/fesm2022/radix-ng-primitives-dropdown-menu.mjs +0 -581
- package/fesm2022/radix-ng-primitives-dropdown-menu.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-hover-card.mjs +0 -1238
- package/fesm2022/radix-ng-primitives-hover-card.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-select2.mjs +0 -897
- package/fesm2022/radix-ng-primitives-select2.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-tooltip2.mjs +0 -735
- package/fesm2022/radix-ng-primitives-tooltip2.mjs.map +0 -1
- package/hover-card/README.md +0 -3
- package/select2/README.md +0 -3
- package/tooltip2/README.md +0 -3
- package/types/radix-ng-primitives-dropdown-menu.d.ts +0 -171
- package/types/radix-ng-primitives-hover-card.d.ts +0 -471
- package/types/radix-ng-primitives-select2.d.ts +0 -511
- package/types/radix-ng-primitives-tooltip2.d.ts +0 -325
|
@@ -1,248 +1,105 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
2
|
+
import { inject, computed, Directive, input, signal, TemplateRef, booleanAttribute, effect, untracked, ElementRef, output, DestroyRef, numberAttribute, model, ViewContainerRef, Renderer2, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@radix-ng/primitives/popper';
|
|
4
|
+
import { RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentConfig, RdxPopper } from '@radix-ng/primitives/popper';
|
|
5
|
+
import { createContext, ENTER, SPACE, ARROW_DOWN, ARROW_UP, HOME, END, useGraceArea, useTransitionStatus, injectDocument, ARROW_LEFT, ARROW_RIGHT, getMaxTransitionDuration } from '@radix-ng/primitives/core';
|
|
6
|
+
import * as i1$1 from '@radix-ng/primitives/roving-focus';
|
|
7
|
+
import { RdxRovingFocusGroupDirective, RdxRovingFocusItemDirective } from '@radix-ng/primitives/roving-focus';
|
|
8
|
+
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
|
|
9
|
+
import * as i2 from '@radix-ng/primitives/dismissable-layer';
|
|
10
|
+
import { RdxDismissableLayer, RdxDismissableLayersContextToken } from '@radix-ng/primitives/dismissable-layer';
|
|
11
|
+
import * as i1$2 from '@radix-ng/primitives/portal';
|
|
12
|
+
import { RdxPortal } from '@radix-ng/primitives/portal';
|
|
13
|
+
import * as i1$3 from '@radix-ng/primitives/presence';
|
|
14
|
+
import { provideRdxPresenceContext, RdxPresenceDirective } from '@radix-ng/primitives/presence';
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onFocus(event) {
|
|
19
|
-
const focusEvent = event;
|
|
20
|
-
const prevFocusedElement = focusEvent.relatedTarget;
|
|
21
|
-
const wasTriggerFocused = prevFocusedElement === this.triggerElement;
|
|
22
|
-
const wasFocusFromContent = this.contentElement ? this.contentElement.contains(prevFocusedElement) : false;
|
|
23
|
-
if (wasTriggerFocused || !wasFocusFromContent) {
|
|
24
|
-
this.proxyFocus.emit(wasTriggerFocused ? 'start' : 'end');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuFocusProxyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
28
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuFocusProxyComponent, isStandalone: true, selector: "rdx-navigation-menu-focus-proxy", inputs: { triggerElement: "triggerElement", contentElement: "contentElement" }, outputs: { proxyFocus: "proxyFocus" }, ngImport: i0, template: `
|
|
29
|
-
<span
|
|
30
|
-
[attr.tabindex]="0"
|
|
31
|
-
[attr.aria-hidden]="true"
|
|
32
|
-
(focus)="onFocus($event)"
|
|
33
|
-
rdxVisuallyHidden
|
|
34
|
-
feature="focusable"
|
|
35
|
-
></span>
|
|
36
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RdxVisuallyHiddenDirective, selector: "[rdxVisuallyHidden]", inputs: ["feature"] }] }); }
|
|
37
|
-
}
|
|
38
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuFocusProxyComponent, decorators: [{
|
|
39
|
-
type: Component,
|
|
40
|
-
args: [{
|
|
41
|
-
selector: 'rdx-navigation-menu-focus-proxy',
|
|
42
|
-
template: `
|
|
43
|
-
<span
|
|
44
|
-
[attr.tabindex]="0"
|
|
45
|
-
[attr.aria-hidden]="true"
|
|
46
|
-
(focus)="onFocus($event)"
|
|
47
|
-
rdxVisuallyHidden
|
|
48
|
-
feature="focusable"
|
|
49
|
-
></span>
|
|
50
|
-
`,
|
|
51
|
-
imports: [RdxVisuallyHiddenDirective]
|
|
52
|
-
}]
|
|
53
|
-
}], propDecorators: { triggerElement: [{
|
|
54
|
-
type: Input
|
|
55
|
-
}], contentElement: [{
|
|
56
|
-
type: Input
|
|
57
|
-
}], proxyFocus: [{
|
|
58
|
-
type: Output
|
|
59
|
-
}] } });
|
|
60
|
-
class RdxNavigationMenuAriaOwnsComponent {
|
|
16
|
+
const [injectNavigationMenuRootContext, provideNavigationMenuRootContext] = createContext('RdxNavigationMenuRootContext');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An optional arrow element pointing toward the active trigger.
|
|
20
|
+
*/
|
|
21
|
+
class RdxNavigationMenuArrow {
|
|
61
22
|
constructor() {
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
24
|
+
this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
|
|
25
|
+
this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
26
|
+
this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
27
|
+
this.uncentered = computed(() => this.wrapper?.arrowUncentered() ?? false, ...(ngDevMode ? [{ debugName: "uncentered" }] : /* istanbul ignore next */ []));
|
|
28
|
+
}
|
|
29
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuArrow, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
30
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuArrow, isStandalone: true, selector: "[rdxNavigationMenuArrow]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-side": "side()", "attr.data-align": "align()", "attr.data-uncentered": "uncentered() ? \"\" : undefined" } }, hostDirectives: [{ directive: i1.RdxPopperArrow }], ngImport: i0 }); }
|
|
68
31
|
}
|
|
69
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
70
|
-
type:
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuArrow, decorators: [{
|
|
33
|
+
type: Directive,
|
|
71
34
|
args: [{
|
|
72
|
-
selector: '
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
35
|
+
selector: '[rdxNavigationMenuArrow]',
|
|
36
|
+
hostDirectives: [RdxPopperArrow],
|
|
37
|
+
host: {
|
|
38
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
39
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
40
|
+
'[attr.data-side]': 'side()',
|
|
41
|
+
'[attr.data-align]': 'align()',
|
|
42
|
+
'[attr.data-uncentered]': 'uncentered() ? "" : undefined'
|
|
43
|
+
}
|
|
77
44
|
}]
|
|
78
|
-
}]
|
|
79
|
-
type: Input
|
|
80
|
-
}] } });
|
|
81
|
-
|
|
82
|
-
const RDX_NAVIGATION_MENU_TOKEN = new InjectionToken('RdxNavigationMenuToken');
|
|
83
|
-
function injectNavigationMenu() {
|
|
84
|
-
return inject(RDX_NAVIGATION_MENU_TOKEN);
|
|
85
|
-
}
|
|
86
|
-
function isRootNavigationMenu(context) {
|
|
87
|
-
return context.isRootMenu;
|
|
88
|
-
}
|
|
89
|
-
function provideNavigationMenuContext(provider) {
|
|
90
|
-
return {
|
|
91
|
-
provide: RDX_NAVIGATION_MENU_TOKEN,
|
|
92
|
-
useExisting: provider
|
|
93
|
-
};
|
|
94
|
-
}
|
|
45
|
+
}] });
|
|
95
46
|
|
|
96
|
-
var RdxNavigationMenuAnimationStatus;
|
|
97
|
-
(function (RdxNavigationMenuAnimationStatus) {
|
|
98
|
-
RdxNavigationMenuAnimationStatus["OPEN_STARTED"] = "open_started";
|
|
99
|
-
RdxNavigationMenuAnimationStatus["OPEN_ENDED"] = "open_ended";
|
|
100
|
-
RdxNavigationMenuAnimationStatus["CLOSED_STARTED"] = "closed_started";
|
|
101
|
-
RdxNavigationMenuAnimationStatus["CLOSED_ENDED"] = "closed_ended";
|
|
102
|
-
})(RdxNavigationMenuAnimationStatus || (RdxNavigationMenuAnimationStatus = {}));
|
|
103
47
|
/**
|
|
104
|
-
*
|
|
48
|
+
* An optional backdrop rendered behind the popup.
|
|
105
49
|
*/
|
|
106
|
-
class
|
|
107
|
-
|
|
108
|
-
|
|
50
|
+
class RdxNavigationMenuBackdrop {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
109
53
|
}
|
|
54
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
55
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuBackdrop, isStandalone: true, selector: "[rdxNavigationMenuBackdrop]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"" } }, ngImport: i0 }); }
|
|
110
56
|
}
|
|
57
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuBackdrop, decorators: [{
|
|
58
|
+
type: Directive,
|
|
59
|
+
args: [{
|
|
60
|
+
selector: '[rdxNavigationMenuBackdrop]',
|
|
61
|
+
host: {
|
|
62
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
63
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
64
|
+
'[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
|
|
65
|
+
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
|
|
66
|
+
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
|
|
67
|
+
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
|
|
68
|
+
}
|
|
69
|
+
}]
|
|
70
|
+
}] });
|
|
111
71
|
|
|
112
|
-
const ROOT_CONTENT_DISMISS$1 = 'navigationMenu.rootContentDismiss';
|
|
113
72
|
/**
|
|
114
|
-
* Generate a unique
|
|
73
|
+
* Generate a short unique id segment.
|
|
115
74
|
*/
|
|
116
75
|
function generateId() {
|
|
117
76
|
return Math.random().toString(36).substring(2, 11);
|
|
118
77
|
}
|
|
119
78
|
/**
|
|
120
|
-
*
|
|
121
|
-
*/
|
|
122
|
-
function getOpenStateLabel(open) {
|
|
123
|
-
return open ? 'open' : 'closed';
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Create a trigger ID from base ID and value
|
|
127
|
-
*/
|
|
128
|
-
function makeTriggerId(baseId, value) {
|
|
129
|
-
return `${baseId}-trigger-${value}`;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Create a content ID from base ID and value
|
|
133
|
-
*/
|
|
134
|
-
function makeContentId(baseId, value) {
|
|
135
|
-
return `${baseId}-content-${value}`;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Get the motion attribute for animations
|
|
139
|
-
*/
|
|
140
|
-
function getMotionAttribute(currentValue, previousValue, itemValue, itemValues, dir) {
|
|
141
|
-
// reverse values in RTL
|
|
142
|
-
const values = dir === 'rtl' ? [...itemValues].reverse() : itemValues;
|
|
143
|
-
const currentIndex = currentValue !== null ? values.indexOf(currentValue) : -1;
|
|
144
|
-
const prevIndex = previousValue !== null ? values.indexOf(previousValue) : -1;
|
|
145
|
-
const isSelected = itemValue === currentValue;
|
|
146
|
-
const wasSelected = itemValue === previousValue && previousValue !== null;
|
|
147
|
-
// Preserve motion attribute for items not directly involved in the transition
|
|
148
|
-
// (This matches React's behaviour, using a ref/signal might be needed
|
|
149
|
-
// in the component using this function to fully replicate React's prevMotionAttributeRef)
|
|
150
|
-
// For now, returning null if not involved, as per the original code's intent here.
|
|
151
|
-
if (!isSelected && !wasSelected) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
// handle transitions between items
|
|
155
|
-
if (currentIndex !== -1 && prevIndex !== -1) {
|
|
156
|
-
// if moving to this item (isSelected)
|
|
157
|
-
if (isSelected) {
|
|
158
|
-
return currentIndex > prevIndex ? 'from-end' : 'from-start';
|
|
159
|
-
}
|
|
160
|
-
// if moving away from this item (wasSelected)
|
|
161
|
-
if (wasSelected) {
|
|
162
|
-
return currentIndex > prevIndex ? 'to-start' : 'to-end';
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// handle initial open (prevIndex is -1, currentIndex is valid)
|
|
166
|
-
if (isSelected && prevIndex === -1) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
// handle closing entirely (currentIndex is -1, prevIndex is valid)
|
|
170
|
-
if (wasSelected && currentIndex === -1) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
// fallback if none of the above conditions met (should ideally not happen with clear states)
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Focus the first element in a list of candidates
|
|
178
|
-
* @param candidates Array of elements that can receive focus
|
|
179
|
-
* @param preventScroll Whether to prevent scrolling when focusing
|
|
180
|
-
* @param activateKeyboardNav Whether to dispatch a dummy keydown event to activate keyboard navigation handlers
|
|
181
|
-
* @returns Whether focus was successfully moved
|
|
182
|
-
*/
|
|
183
|
-
function focusFirst(candidates, preventScroll = false, activateKeyboardNav = true) {
|
|
184
|
-
const prevFocusedElement = document.activeElement;
|
|
185
|
-
// sort candidates by tabindex to ensure proper order
|
|
186
|
-
const sortedCandidates = [...candidates].sort((a, b) => {
|
|
187
|
-
const aIndex = a.tabIndex || 0;
|
|
188
|
-
const bIndex = b.tabIndex || 0;
|
|
189
|
-
return aIndex - bIndex;
|
|
190
|
-
});
|
|
191
|
-
const success = sortedCandidates.some((candidate) => {
|
|
192
|
-
// if focus is already where we want it, do nothing
|
|
193
|
-
if (candidate === prevFocusedElement)
|
|
194
|
-
return true;
|
|
195
|
-
try {
|
|
196
|
-
candidate.focus({ preventScroll });
|
|
197
|
-
return document.activeElement !== prevFocusedElement;
|
|
198
|
-
}
|
|
199
|
-
catch (e) {
|
|
200
|
-
console.error('Error focusing element:', e);
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
// if focus was moved successfully and we want to activate keyboard navigation,
|
|
205
|
-
// dispatch a dummy keypress to ensure keyboard handlers are activated
|
|
206
|
-
if (success && activateKeyboardNav && document.activeElement !== prevFocusedElement) {
|
|
207
|
-
try {
|
|
208
|
-
// dispatch a no-op keydown event to activate any keyboard handlers
|
|
209
|
-
document.activeElement?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
210
|
-
bubbles: true,
|
|
211
|
-
cancelable: true,
|
|
212
|
-
key: 'Tab',
|
|
213
|
-
code: 'Tab'
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
catch (e) {
|
|
217
|
-
console.error('Error dispatching keyboard event:', e);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return success;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Get all tabbable candidates in a container
|
|
79
|
+
* Collect the tabbable elements inside a container, in DOM order, skipping hidden ones.
|
|
224
80
|
*/
|
|
225
81
|
function getTabbableCandidates(container) {
|
|
226
|
-
if (!container || !container.querySelectorAll)
|
|
82
|
+
if (!container || !container.querySelectorAll) {
|
|
227
83
|
return [];
|
|
84
|
+
}
|
|
228
85
|
const TABBABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), ' +
|
|
229
86
|
'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), ' +
|
|
230
87
|
'[contenteditable="true"]:not([tabindex="-1"])';
|
|
231
|
-
// use querySelector for better browser support
|
|
232
88
|
const elements = Array.from(container.querySelectorAll(TABBABLE_SELECTOR));
|
|
233
|
-
// filter out elements that are hidden, have display:none, etc.
|
|
234
89
|
return elements.filter((element) => {
|
|
235
|
-
if (element.tabIndex < 0)
|
|
90
|
+
if (element.tabIndex < 0) {
|
|
236
91
|
return false;
|
|
237
|
-
|
|
92
|
+
}
|
|
93
|
+
if (element.hasAttribute('disabled')) {
|
|
238
94
|
return false;
|
|
239
|
-
|
|
95
|
+
}
|
|
96
|
+
if (element.getAttribute('aria-hidden') === 'true') {
|
|
240
97
|
return false;
|
|
241
|
-
|
|
98
|
+
}
|
|
242
99
|
let current = element;
|
|
243
100
|
while (current) {
|
|
244
101
|
const style = window.getComputedStyle(current);
|
|
245
|
-
if (style.display === 'none' || style.visibility === 'hidden'
|
|
102
|
+
if (style.display === 'none' || style.visibility === 'hidden') {
|
|
246
103
|
return false;
|
|
247
104
|
}
|
|
248
105
|
current = current.parentElement;
|
|
@@ -251,1547 +108,1189 @@ function getTabbableCandidates(container) {
|
|
|
251
108
|
});
|
|
252
109
|
}
|
|
253
110
|
/**
|
|
254
|
-
*
|
|
111
|
+
* Focus the first focusable candidate, returning whether focus moved.
|
|
255
112
|
*/
|
|
256
|
-
function
|
|
257
|
-
const
|
|
258
|
-
candidates.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
candidate.
|
|
113
|
+
function focusFirst(candidates, preventScroll = false) {
|
|
114
|
+
const previouslyFocused = document.activeElement;
|
|
115
|
+
return candidates.some((candidate) => {
|
|
116
|
+
if (candidate === previouslyFocused) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
candidate.focus({ preventScroll });
|
|
120
|
+
return document.activeElement !== previouslyFocused;
|
|
263
121
|
});
|
|
264
|
-
// Return restore function
|
|
265
|
-
return () => {
|
|
266
|
-
candidates.forEach((candidate) => {
|
|
267
|
-
const originalValue = originalValues.get(candidate);
|
|
268
|
-
if (originalValue == null) {
|
|
269
|
-
candidate.removeAttribute('tabindex');
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
candidate.setAttribute('tabindex', originalValue);
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
};
|
|
276
122
|
}
|
|
277
123
|
/**
|
|
278
|
-
*
|
|
124
|
+
* Derive a slide direction (e.g. `"left"`, `"right up"`) from the relative position of two triggers.
|
|
125
|
+
* Used by the viewport to animate content as the active item changes.
|
|
126
|
+
*/
|
|
127
|
+
function getActivationDirection(previous, next) {
|
|
128
|
+
const previousCenter = getCenter(previous.getBoundingClientRect());
|
|
129
|
+
const nextCenter = getCenter(next.getBoundingClientRect());
|
|
130
|
+
const directions = [];
|
|
131
|
+
if (nextCenter.x < previousCenter.x) {
|
|
132
|
+
directions.push('left');
|
|
133
|
+
}
|
|
134
|
+
else if (nextCenter.x > previousCenter.x) {
|
|
135
|
+
directions.push('right');
|
|
136
|
+
}
|
|
137
|
+
if (nextCenter.y < previousCenter.y) {
|
|
138
|
+
directions.push('up');
|
|
139
|
+
}
|
|
140
|
+
else if (nextCenter.y > previousCenter.y) {
|
|
141
|
+
directions.push('down');
|
|
142
|
+
}
|
|
143
|
+
return directions.join(' ') || undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Remove `id` from an element and all its descendants (used when cloning content for an exit
|
|
147
|
+
* animation, to avoid duplicate ids in the document).
|
|
279
148
|
*/
|
|
280
|
-
function
|
|
281
|
-
|
|
149
|
+
function removeIds(element) {
|
|
150
|
+
element.removeAttribute('id');
|
|
151
|
+
element.querySelectorAll('[id]').forEach((child) => child.removeAttribute('id'));
|
|
152
|
+
}
|
|
153
|
+
function getCenter(rect) {
|
|
154
|
+
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
282
155
|
}
|
|
283
156
|
|
|
284
|
-
|
|
157
|
+
/**
|
|
158
|
+
* A single navigation menu item. Holds a trigger + content pair, or a standalone link.
|
|
159
|
+
*/
|
|
160
|
+
class RdxNavigationMenuItem {
|
|
285
161
|
constructor() {
|
|
286
|
-
this.
|
|
287
|
-
this.context = injectNavigationMenu();
|
|
288
|
-
this.value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
162
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
289
163
|
/**
|
|
290
|
-
*
|
|
164
|
+
* A unique value that identifies the item. Falls back to a generated id.
|
|
291
165
|
*/
|
|
292
|
-
this.
|
|
166
|
+
this.value = input('', { ...(ngDevMode ? { debugName: "value" } : /* istanbul ignore next */ {}), transform: (value) => value || `item-${generateId()}` });
|
|
167
|
+
/** The trigger element, set by the trigger directive. */
|
|
293
168
|
this.triggerRef = signal(null, ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
|
|
294
|
-
|
|
295
|
-
this.
|
|
296
|
-
this.
|
|
297
|
-
this.
|
|
298
|
-
}
|
|
299
|
-
get restoreContentTabOrderRef() {
|
|
300
|
-
return this._restoreContentTabOrderRef;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Handle keyboard entry into content from trigger
|
|
304
|
-
*/
|
|
305
|
-
onEntryKeyDown() {
|
|
306
|
-
// Check if we're using a viewport in a root menu
|
|
307
|
-
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
|
308
|
-
const viewport = this.context.viewport();
|
|
309
|
-
if (viewport) {
|
|
310
|
-
// find tabbable elements in the viewport
|
|
311
|
-
const candidates = getTabbableCandidates(viewport);
|
|
312
|
-
if (candidates.length) {
|
|
313
|
-
this.ensureTabOrder();
|
|
314
|
-
// focus the first element
|
|
315
|
-
focusFirst(candidates);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// fallback to content if no viewport or no tabbable elements in viewport
|
|
321
|
-
if (this.contentRef()) {
|
|
322
|
-
// restore tab order if needed
|
|
323
|
-
const restoreFn = this._restoreContentTabOrderRef();
|
|
324
|
-
if (restoreFn)
|
|
325
|
-
restoreFn();
|
|
326
|
-
// find and focus first tabbable element
|
|
327
|
-
const candidates = getTabbableCandidates(this.contentRef());
|
|
328
|
-
if (candidates.length) {
|
|
329
|
-
focusFirst(candidates);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
focus() {
|
|
334
|
-
this.triggerOrLink()?.focus();
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Ensure elements are in the tab order by restoring any previously removed tabindex values
|
|
338
|
-
*/
|
|
339
|
-
ensureTabOrder() {
|
|
340
|
-
const restoreFn = this._restoreContentTabOrderRef();
|
|
341
|
-
if (restoreFn) {
|
|
342
|
-
restoreFn();
|
|
343
|
-
this._restoreContentTabOrderRef.set(null);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Handle focus coming from the focus proxy element
|
|
348
|
-
* @param side Which side the focus is coming from (start = from trigger, end = from after content)
|
|
349
|
-
*/
|
|
350
|
-
onFocusProxyEnter(side = 'start') {
|
|
351
|
-
// check for viewport first
|
|
352
|
-
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
|
353
|
-
const viewport = this.context.viewport();
|
|
354
|
-
if (viewport) {
|
|
355
|
-
const candidates = getTabbableCandidates(viewport);
|
|
356
|
-
if (candidates.length) {
|
|
357
|
-
this.ensureTabOrder();
|
|
358
|
-
// focus first or last element depending on direction
|
|
359
|
-
focusFirst(side === 'start' ? candidates : [...candidates].reverse());
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// fallback to content
|
|
365
|
-
if (this.contentRef()) {
|
|
366
|
-
// restore tab order if needed
|
|
367
|
-
const restoreFn = this._restoreContentTabOrderRef();
|
|
368
|
-
if (restoreFn)
|
|
369
|
-
restoreFn();
|
|
370
|
-
// find and focus appropriate element based on direction
|
|
371
|
-
const candidates = getTabbableCandidates(this.contentRef());
|
|
372
|
-
if (candidates.length) {
|
|
373
|
-
// Focus first or last element depending on which direction we're coming from
|
|
374
|
-
focusFirst(side === 'start' ? candidates : [...candidates].reverse());
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Handle focus moving outside of the content
|
|
380
|
-
* Remove elements from tab order when not focused
|
|
381
|
-
*/
|
|
382
|
-
onContentFocusOutside() {
|
|
383
|
-
// get all tabbable elements from both viewport and content
|
|
384
|
-
let allCandidates = [];
|
|
385
|
-
// check viewport first
|
|
386
|
-
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
|
387
|
-
const viewport = this.context.viewport();
|
|
388
|
-
if (viewport) {
|
|
389
|
-
allCandidates = getTabbableCandidates(viewport);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// ... also check direct content
|
|
393
|
-
if (this.contentRef()) {
|
|
394
|
-
const contentCandidates = getTabbableCandidates(this.contentRef());
|
|
395
|
-
allCandidates = [...allCandidates, ...contentCandidates];
|
|
396
|
-
}
|
|
397
|
-
// remove from tab order and store restore function
|
|
398
|
-
if (allCandidates.length) {
|
|
399
|
-
this._restoreContentTabOrderRef.set(removeFromTabOrder(allCandidates));
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Handle content being closed from root menu
|
|
404
|
-
*/
|
|
405
|
-
onRootContentClose() {
|
|
406
|
-
this.onContentFocusOutside();
|
|
169
|
+
/** Whether this item is the currently open one. */
|
|
170
|
+
this.isOpen = computed(() => this.rootContext.value() === this.value(), ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
171
|
+
this.triggerId = computed(() => this.rootContext.triggerId(this.value()), ...(ngDevMode ? [{ debugName: "triggerId" }] : /* istanbul ignore next */ []));
|
|
172
|
+
this.contentId = computed(() => this.rootContext.contentId(this.value()), ...(ngDevMode ? [{ debugName: "contentId" }] : /* istanbul ignore next */ []));
|
|
407
173
|
}
|
|
408
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
409
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.
|
|
174
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
175
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuItem, isStandalone: true, selector: "[rdxNavigationMenuItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["rdxNavigationMenuItem"], ngImport: i0 }); }
|
|
410
176
|
}
|
|
411
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
177
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuItem, decorators: [{
|
|
412
178
|
type: Directive,
|
|
413
179
|
args: [{
|
|
414
180
|
selector: '[rdxNavigationMenuItem]',
|
|
415
|
-
host: {
|
|
416
|
-
'[attr.value]': 'value()'
|
|
417
|
-
},
|
|
418
181
|
exportAs: 'rdxNavigationMenuItem'
|
|
419
182
|
}]
|
|
420
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }]
|
|
183
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
421
184
|
|
|
422
|
-
|
|
185
|
+
/**
|
|
186
|
+
* The content shown when its item is open. Used as a structural directive; its template is rendered
|
|
187
|
+
* into the shared {@link RdxNavigationMenuViewport}.
|
|
188
|
+
*
|
|
189
|
+
* ```html
|
|
190
|
+
* <ng-container *rdxNavigationMenuContent>…</ng-container>
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
class RdxNavigationMenuContent {
|
|
423
194
|
constructor() {
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
426
|
-
this.
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
195
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
196
|
+
this.item = inject(RdxNavigationMenuItem);
|
|
197
|
+
this.templateRef = inject(TemplateRef);
|
|
198
|
+
/**
|
|
199
|
+
* Required by the structural directive syntax; the value is unused.
|
|
200
|
+
*/
|
|
201
|
+
this.rdxNavigationMenuContent = input(false, { ...(ngDevMode ? { debugName: "rdxNavigationMenuContent" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
430
202
|
/**
|
|
431
|
-
*
|
|
432
|
-
* Useful for animations or SEO.
|
|
433
|
-
* @default false
|
|
203
|
+
* Keep the content mounted in the viewport even when its item is closed.
|
|
434
204
|
*/
|
|
435
205
|
this.forceMount = input(false, { ...(ngDevMode ? { debugName: "forceMount" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// register template with viewport in root menu via context
|
|
449
|
-
if (isRootNavigationMenu(this.context) && this.context.onViewportContentChange) {
|
|
450
|
-
this.context.onViewportContentChange(this.item.value(), {
|
|
451
|
-
ref: this.elementRef,
|
|
452
|
-
templateRef: this.template,
|
|
453
|
-
forceMount: this.forceMount(),
|
|
454
|
-
value: this.item.value(),
|
|
455
|
-
getMotionAttribute: this.getMotionAttribute.bind(this),
|
|
456
|
-
additionalAttrs: {
|
|
457
|
-
id: this.contentId,
|
|
458
|
-
'aria-labelledby': this.triggerId,
|
|
459
|
-
role: 'menu'
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
// add Escape key handler
|
|
464
|
-
this.escapeHandler = (event) => {
|
|
465
|
-
if (event.key === ESCAPE && this.context.value() === this.item.value()) {
|
|
466
|
-
// mark that this close was triggered by Escape
|
|
467
|
-
this.item.wasEscapeCloseRef.set(true);
|
|
468
|
-
// close the content
|
|
469
|
-
if (this.context.onItemDismiss) {
|
|
470
|
-
this.context.onItemDismiss();
|
|
471
|
-
}
|
|
472
|
-
// refocus the trigger
|
|
473
|
-
setTimeout(() => {
|
|
474
|
-
const trigger = this.item.triggerRef();
|
|
475
|
-
if (trigger)
|
|
476
|
-
trigger.focus();
|
|
477
|
-
}, 0);
|
|
478
|
-
event.preventDefault();
|
|
479
|
-
event.stopPropagation();
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
this.ngZone.runOutsideAngular(() => {
|
|
483
|
-
if (this.escapeHandler) {
|
|
484
|
-
this.document.addEventListener('keydown', this.escapeHandler);
|
|
485
|
-
}
|
|
206
|
+
effect((onCleanup) => {
|
|
207
|
+
const value = this.item.value();
|
|
208
|
+
// Register untracked so reading/writing the root's `contents` map inside registerContent
|
|
209
|
+
// doesn't make this effect re-run when other items register.
|
|
210
|
+
const unregister = untracked(() => this.rootContext.registerContent({
|
|
211
|
+
value,
|
|
212
|
+
contentId: this.rootContext.contentId(value),
|
|
213
|
+
triggerId: this.rootContext.triggerId(value),
|
|
214
|
+
templateRef: this.templateRef,
|
|
215
|
+
forceMount: this.forceMount
|
|
216
|
+
}));
|
|
217
|
+
onCleanup(unregister);
|
|
486
218
|
});
|
|
487
219
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
// unregister from viewport
|
|
491
|
-
if (isRootNavigationMenu(this.context) && this.context.onViewportContentRemove) {
|
|
492
|
-
this.context.onViewportContentRemove(this.item.value());
|
|
493
|
-
}
|
|
494
|
-
// remove escape key handler
|
|
495
|
-
if (this.escapeHandler) {
|
|
496
|
-
this.document.removeEventListener('keydown', this.escapeHandler);
|
|
497
|
-
this.escapeHandler = null;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
/** @ignore - Compute motion attribute for animations */
|
|
501
|
-
getMotionAttribute() {
|
|
502
|
-
if (!isRootNavigationMenu(this.context))
|
|
503
|
-
return null;
|
|
504
|
-
const itemValues = Array.from(this.context.viewportContent?.() ?? new Map()).map(([value]) => value);
|
|
505
|
-
return getMotionAttribute(this.context.value(), this.context.previousValue(), this.item.value(), itemValues, this.context.dir);
|
|
506
|
-
}
|
|
507
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
508
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuContentDirective, isStandalone: true, selector: "[rdxNavigationMenuContent]", inputs: { rdxNavigationMenuContent: { classPropertyName: "rdxNavigationMenuContent", publicName: "rdxNavigationMenuContent", isSignal: false, isRequired: false, transformFunction: booleanAttribute }, forceMount: { classPropertyName: "forceMount", publicName: "forceMount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
220
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuContent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
221
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuContent, isStandalone: true, selector: "[rdxNavigationMenuContent]", inputs: { rdxNavigationMenuContent: { classPropertyName: "rdxNavigationMenuContent", publicName: "rdxNavigationMenuContent", isSignal: true, isRequired: false, transformFunction: null }, forceMount: { classPropertyName: "forceMount", publicName: "forceMount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
509
222
|
}
|
|
510
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
223
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuContent, decorators: [{
|
|
511
224
|
type: Directive,
|
|
512
225
|
args: [{
|
|
513
226
|
selector: '[rdxNavigationMenuContent]'
|
|
514
227
|
}]
|
|
515
|
-
}], propDecorators: { rdxNavigationMenuContent: [{
|
|
516
|
-
type: Input,
|
|
517
|
-
args: [{ transform: booleanAttribute }]
|
|
518
|
-
}], forceMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceMount", required: false }] }] } });
|
|
228
|
+
}], ctorParameters: () => [], propDecorators: { rdxNavigationMenuContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdxNavigationMenuContent", required: false }] }], forceMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceMount", required: false }] }] } });
|
|
519
229
|
|
|
520
|
-
|
|
230
|
+
/**
|
|
231
|
+
* A visual indicator (e.g. a caret) rendered inside a trigger. Exposes the open state so the icon
|
|
232
|
+
* can rotate when its item opens.
|
|
233
|
+
*/
|
|
234
|
+
class RdxNavigationMenuIcon {
|
|
521
235
|
constructor() {
|
|
522
|
-
this.
|
|
523
|
-
this.
|
|
524
|
-
this.renderer = inject(Renderer2);
|
|
525
|
-
/**
|
|
526
|
-
* Used to keep the indicator rendered and available in the DOM, even when hidden.
|
|
527
|
-
* Useful for animations.
|
|
528
|
-
* @default false
|
|
529
|
-
*/
|
|
530
|
-
this.forceMount = input(false, { ...(ngDevMode ? { debugName: "forceMount" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
531
|
-
/** @ignore */
|
|
532
|
-
this._position = signal(null, ...(ngDevMode ? [{ debugName: "_position" }] : /* istanbul ignore next */ []));
|
|
533
|
-
/** @ignore */
|
|
534
|
-
this._activeTrigger = signal(null, ...(ngDevMode ? [{ debugName: "_activeTrigger" }] : /* istanbul ignore next */ []));
|
|
535
|
-
/** @ignore */
|
|
536
|
-
this._resizeObserver = new ResizeObserver(() => this.updatePosition());
|
|
537
|
-
this.isVisible = computed(() => Boolean(this.context.value() || this.forceMount()), ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
|
|
538
|
-
// set up effect for tracking active trigger and position
|
|
539
|
-
effect(() => {
|
|
540
|
-
// this effect runs when the current value changes
|
|
541
|
-
const value = this.context.value();
|
|
542
|
-
untracked(() => {
|
|
543
|
-
if (value && isRootNavigationMenu(this.context)) {
|
|
544
|
-
this.findAndSetActiveTrigger();
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
// initialize observers for position tracking
|
|
549
|
-
runInInjectionContext(this.context, () => {
|
|
550
|
-
if (isRootNavigationMenu(this.context) && this.context.indicatorTrack) {
|
|
551
|
-
const track = this.context.indicatorTrack();
|
|
552
|
-
if (track) {
|
|
553
|
-
// observe size changes on the track
|
|
554
|
-
this._resizeObserver.observe(track);
|
|
555
|
-
}
|
|
556
|
-
// initial position update if menu is open
|
|
557
|
-
if (this.context.value()) {
|
|
558
|
-
setTimeout(() => this.findAndSetActiveTrigger(), 0);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
/** @ignore */
|
|
564
|
-
ngOnDestroy() {
|
|
565
|
-
this._resizeObserver.disconnect();
|
|
566
|
-
}
|
|
567
|
-
/** @ignore */
|
|
568
|
-
findAndSetActiveTrigger() {
|
|
569
|
-
if (!isRootNavigationMenu(this.context) || !this.context.indicatorTrack)
|
|
570
|
-
return;
|
|
571
|
-
const track = this.context.indicatorTrack();
|
|
572
|
-
if (!track)
|
|
573
|
-
return;
|
|
574
|
-
// find all triggers within the track
|
|
575
|
-
const triggers = Array.from(track.querySelectorAll('[rdxNavigationMenuTrigger]'));
|
|
576
|
-
// find the active trigger based on the current menu value
|
|
577
|
-
const activeTrigger = triggers.find((trigger) => {
|
|
578
|
-
const item = trigger.closest('[rdxNavigationMenuItem]');
|
|
579
|
-
if (!item)
|
|
580
|
-
return false;
|
|
581
|
-
const value = item.getAttribute('value');
|
|
582
|
-
return value === this.context.value();
|
|
583
|
-
});
|
|
584
|
-
if (activeTrigger && activeTrigger !== this._activeTrigger()) {
|
|
585
|
-
this._activeTrigger.set(activeTrigger);
|
|
586
|
-
this.updatePosition();
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
/** @ignore */
|
|
590
|
-
updatePosition() {
|
|
591
|
-
const trigger = this._activeTrigger();
|
|
592
|
-
if (!trigger)
|
|
593
|
-
return;
|
|
594
|
-
const isHorizontal = this.context.orientation === 'horizontal';
|
|
595
|
-
// calculate new position
|
|
596
|
-
const newPosition = {
|
|
597
|
-
size: isHorizontal ? trigger.offsetWidth : trigger.offsetHeight,
|
|
598
|
-
offset: isHorizontal ? trigger.offsetLeft : trigger.offsetTop
|
|
599
|
-
};
|
|
600
|
-
// only update if position has changed
|
|
601
|
-
if (JSON.stringify(newPosition) !== JSON.stringify(this._position())) {
|
|
602
|
-
this._position.set(newPosition);
|
|
603
|
-
// apply position styles
|
|
604
|
-
const styles = isHorizontal
|
|
605
|
-
? {
|
|
606
|
-
position: 'absolute',
|
|
607
|
-
left: '0',
|
|
608
|
-
width: `${newPosition.size}px`,
|
|
609
|
-
transform: `translateX(${newPosition.offset}px)`
|
|
610
|
-
}
|
|
611
|
-
: {
|
|
612
|
-
position: 'absolute',
|
|
613
|
-
top: '0',
|
|
614
|
-
height: `${newPosition.size}px`,
|
|
615
|
-
transform: `translateY(${newPosition.offset}px)`
|
|
616
|
-
};
|
|
617
|
-
Object.entries(styles).forEach(([key, value]) => {
|
|
618
|
-
this.renderer.setStyle(this.elementRef.nativeElement, key, value);
|
|
619
|
-
});
|
|
620
|
-
}
|
|
236
|
+
this.item = inject(RdxNavigationMenuItem);
|
|
237
|
+
this.open = computed(() => this.item.isOpen(), ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
621
238
|
}
|
|
622
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
623
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
239
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
240
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuIcon, isStandalone: true, selector: "[rdxNavigationMenuIcon]", host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "open() ? \"open\" : \"closed\"" } }, ngImport: i0 }); }
|
|
624
241
|
}
|
|
625
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
242
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuIcon, decorators: [{
|
|
626
243
|
type: Directive,
|
|
627
244
|
args: [{
|
|
628
|
-
selector: '[
|
|
245
|
+
selector: '[rdxNavigationMenuIcon]',
|
|
629
246
|
host: {
|
|
630
|
-
'
|
|
631
|
-
'[attr.data-
|
|
632
|
-
'[style.display]': 'isVisible() ? null : "none"',
|
|
633
|
-
'aria-hidden': 'true'
|
|
247
|
+
'aria-hidden': 'true',
|
|
248
|
+
'[attr.data-state]': 'open() ? "open" : "closed"'
|
|
634
249
|
}
|
|
635
250
|
}]
|
|
636
|
-
}]
|
|
251
|
+
}] });
|
|
637
252
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
253
|
+
/**
|
|
254
|
+
* A navigation link. Closes the menu on selection unless prevented.
|
|
255
|
+
*
|
|
256
|
+
* Used both as a top-level menubar item and inside content. It is a plain tabbable anchor (not part
|
|
257
|
+
* of the menubar's arrow-key roving), matching Base UI.
|
|
258
|
+
*/
|
|
259
|
+
class RdxNavigationMenuLink {
|
|
641
260
|
constructor() {
|
|
642
|
-
|
|
643
|
-
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
|
644
|
-
this.uniqueId = generateId();
|
|
645
|
-
this.active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
646
|
-
this.onSelect = input(...(ngDevMode ? [undefined, { debugName: "onSelect" }] : /* istanbul ignore next */ []));
|
|
261
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
647
262
|
this.elementRef = inject(ElementRef);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Whether the link represents the current page.
|
|
265
|
+
*/
|
|
266
|
+
this.active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
267
|
+
/**
|
|
268
|
+
* Whether selecting the link should close the menu.
|
|
269
|
+
*/
|
|
270
|
+
this.closeOnClick = input(true, { ...(ngDevMode ? { debugName: "closeOnClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
271
|
+
/**
|
|
272
|
+
* Emits when the link is selected. Call `preventDefault()` to keep the menu open.
|
|
273
|
+
*/
|
|
274
|
+
this.onSelect = output();
|
|
654
275
|
}
|
|
655
276
|
onClick(event) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const linkSelectEvent = new CustomEvent(LINK_SELECT, {
|
|
660
|
-
bubbles: true,
|
|
661
|
-
cancelable: true
|
|
662
|
-
});
|
|
663
|
-
// add one-time listener for onSelect handler
|
|
664
|
-
const onSelect = this.onSelect();
|
|
665
|
-
if (onSelect) {
|
|
666
|
-
target.addEventListener(LINK_SELECT, onSelect, { once: true });
|
|
667
|
-
}
|
|
668
|
-
// dispatch event
|
|
669
|
-
target.dispatchEvent(linkSelectEvent);
|
|
670
|
-
// if not prevented and not meta key, dismiss content
|
|
671
|
-
if (!linkSelectEvent.defaultPrevented && !mouseEvent.metaKey) {
|
|
672
|
-
const dismissEvent = new CustomEvent(ROOT_CONTENT_DISMISS, {
|
|
673
|
-
bubbles: true,
|
|
674
|
-
cancelable: true
|
|
675
|
-
});
|
|
676
|
-
target.dispatchEvent(dismissEvent);
|
|
277
|
+
this.onSelect.emit(event);
|
|
278
|
+
if (this.closeOnClick() && !event.defaultPrevented) {
|
|
279
|
+
this.rootContext.close('link-select', event);
|
|
677
280
|
}
|
|
678
281
|
}
|
|
679
282
|
onKeydown(event) {
|
|
680
|
-
|
|
681
|
-
// activate link on Enter or Space
|
|
682
|
-
if (keyEvent.key === ENTER || keyEvent.key === SPACE) {
|
|
683
|
-
// prevent default behavior like scrolling (Space) or form submission (Enter) BEFORE simulating the click.
|
|
283
|
+
if (event.key === ENTER || event.key === SPACE) {
|
|
684
284
|
event.preventDefault();
|
|
685
|
-
|
|
686
|
-
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
687
|
-
this.elementRef.nativeElement.dispatchEvent(clickEvent);
|
|
688
|
-
return;
|
|
285
|
+
this.elementRef.nativeElement.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
689
286
|
}
|
|
690
287
|
}
|
|
691
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
692
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type:
|
|
288
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuLink, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
289
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuLink, isStandalone: true, selector: "[rdxNavigationMenuLink]", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "attr.data-active": "active() ? \"\" : undefined", "attr.aria-current": "active() ? \"page\" : undefined" } }, ngImport: i0 }); }
|
|
693
290
|
}
|
|
694
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuLink, decorators: [{
|
|
695
292
|
type: Directive,
|
|
696
293
|
args: [{
|
|
697
294
|
selector: '[rdxNavigationMenuLink]',
|
|
698
|
-
hostDirectives: [{ directive: RdxRovingFocusItemDirective, inputs: ['focusable'] }],
|
|
699
295
|
host: {
|
|
700
296
|
'[attr.data-active]': 'active() ? "" : undefined',
|
|
701
297
|
'[attr.aria-current]': 'active() ? "page" : undefined',
|
|
702
298
|
'(click)': 'onClick($event)',
|
|
703
299
|
'(keydown)': 'onKeydown($event)'
|
|
704
|
-
}
|
|
705
|
-
providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuLinkDirective }]
|
|
300
|
+
}
|
|
706
301
|
}]
|
|
707
|
-
}], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }],
|
|
302
|
+
}], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
|
|
708
303
|
|
|
709
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Contains the navigation menu items. Renders as a menubar with roving keyboard focus.
|
|
306
|
+
*/
|
|
307
|
+
class RdxNavigationMenuList {
|
|
710
308
|
constructor() {
|
|
711
|
-
this.
|
|
712
|
-
this.elementRef = inject((ElementRef));
|
|
713
|
-
this.renderer = inject(Renderer2);
|
|
309
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
714
310
|
this.rovingFocusGroup = inject(RdxRovingFocusGroupDirective, { self: true });
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* @ignore
|
|
723
|
-
*/
|
|
724
|
-
ngAfterContentInit() {
|
|
725
|
-
const items = this.items();
|
|
726
|
-
this.keyManager = new FocusKeyManager(items);
|
|
727
|
-
if (this.context.orientation === 'horizontal') {
|
|
728
|
-
this.keyManager.withHorizontalOrientation(this.context.dir || 'ltr');
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
this.keyManager.withVerticalOrientation();
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* @ignore
|
|
736
|
-
*/
|
|
737
|
-
ngAfterViewInit() {
|
|
738
|
-
this.rovingFocusGroup.setOrientation(this.context.orientation);
|
|
739
|
-
this.rovingFocusGroup.setDir(this.context.dir);
|
|
740
|
-
// looping typically only applies to the root menu bar
|
|
741
|
-
if (isRootNavigationMenu(this.context)) {
|
|
742
|
-
this.rovingFocusGroup.setLoop(this.context.loop ?? false);
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
this.rovingFocusGroup.setLoop(false);
|
|
746
|
-
}
|
|
747
|
-
if (isRootNavigationMenu(this.context) && this.context.onIndicatorTrackChange) {
|
|
748
|
-
const listElement = this.elementRef.nativeElement;
|
|
749
|
-
const parent = listElement.parentNode;
|
|
750
|
-
// ensure parent exists and list hasn't already been wrapped
|
|
751
|
-
if (parent && !listElement.parentElement?.hasAttribute('data-radix-navigation-menu-list-wrapper')) {
|
|
752
|
-
// create a wrapper div with relative positioning
|
|
753
|
-
const wrapper = this.renderer.createElement('div');
|
|
754
|
-
this.renderer.setAttribute(wrapper, 'data-radix-navigation-menu-list-wrapper', ''); // Add marker
|
|
755
|
-
this.renderer.setStyle(wrapper, 'position', 'relative');
|
|
756
|
-
// insert the wrapper before the list element in the parent
|
|
757
|
-
this.renderer.insertBefore(parent, wrapper, listElement);
|
|
758
|
-
// move the list element inside the new wrapper
|
|
759
|
-
this.renderer.appendChild(wrapper, listElement);
|
|
760
|
-
// register the wrapper element as the track for the indicator positioning
|
|
761
|
-
this.context.onIndicatorTrackChange(wrapper);
|
|
762
|
-
}
|
|
763
|
-
else if (listElement.parentElement?.hasAttribute('data-radix-navigation-menu-list-wrapper')) {
|
|
764
|
-
// if wrapper somehow already exists, ensure context has the correct reference
|
|
765
|
-
this.context.onIndicatorTrackChange(listElement.parentElement);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
311
|
+
effect(() => {
|
|
312
|
+
this.rovingFocusGroup.setOrientation(this.rootContext.orientation());
|
|
313
|
+
this.rovingFocusGroup.setDir(this.rootContext.dir());
|
|
314
|
+
this.rovingFocusGroup.setLoop(this.rootContext.loop());
|
|
315
|
+
});
|
|
768
316
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
onKeydown(event) {
|
|
773
|
-
const keyEvent = event;
|
|
774
|
-
if (!this.keyManager.activeItem) {
|
|
775
|
-
this.keyManager.setFirstItemActive();
|
|
776
|
-
}
|
|
777
|
-
if (keyEvent.key === TAB && keyEvent.shiftKey) {
|
|
778
|
-
if (this.keyManager.activeItemIndex === 0)
|
|
779
|
-
return;
|
|
780
|
-
this.keyManager.setPreviousItemActive();
|
|
781
|
-
event.preventDefault();
|
|
782
|
-
}
|
|
783
|
-
else if (keyEvent.key === TAB) {
|
|
784
|
-
const items = this.items();
|
|
785
|
-
if (this.keyManager.activeItemIndex === items.length - 1) {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
this.keyManager.setNextItemActive();
|
|
789
|
-
event.preventDefault();
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
this.keyManager.onKeydown(keyEvent);
|
|
317
|
+
onPointerLeave(event) {
|
|
318
|
+
if (event.pointerType === 'touch') {
|
|
319
|
+
return;
|
|
793
320
|
}
|
|
321
|
+
this.rootContext.closeOnHover();
|
|
794
322
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
*/
|
|
798
|
-
setActiveItem(item) {
|
|
799
|
-
this.keyManager.setActiveItem(item);
|
|
800
|
-
}
|
|
801
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
802
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.9", type: RdxNavigationMenuListDirective, isStandalone: true, selector: "[rdxNavigationMenuList]", host: { attributes: { "role": "menubar" }, listeners: { "keydown": "onKeydown($event)" } }, queries: [{ propertyName: "items", predicate: i0.forwardRef(() => RdxNavigationMenuItemDirective), descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.RdxRovingFocusGroupDirective }], ngImport: i0 }); }
|
|
323
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
324
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuList, isStandalone: true, selector: "[rdxNavigationMenuList]", host: { attributes: { "role": "menubar" }, listeners: { "pointerleave": "onPointerLeave($event)" }, properties: { "attr.data-orientation": "rootContext.orientation()" } }, hostDirectives: [{ directive: i1$1.RdxRovingFocusGroupDirective }], ngImport: i0 }); }
|
|
803
325
|
}
|
|
804
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
326
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuList, decorators: [{
|
|
805
327
|
type: Directive,
|
|
806
328
|
args: [{
|
|
807
329
|
selector: '[rdxNavigationMenuList]',
|
|
808
330
|
hostDirectives: [RdxRovingFocusGroupDirective],
|
|
809
331
|
host: {
|
|
810
332
|
role: 'menubar',
|
|
811
|
-
'
|
|
333
|
+
'[attr.data-orientation]': 'rootContext.orientation()',
|
|
334
|
+
'(pointerleave)': 'onPointerLeave($event)'
|
|
812
335
|
}
|
|
813
336
|
}]
|
|
814
|
-
}],
|
|
337
|
+
}], ctorParameters: () => [] });
|
|
815
338
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
RdxNavigationMenuAction["CLOSE"] = "close";
|
|
821
|
-
})(RdxNavigationMenuAction || (RdxNavigationMenuAction = {}));
|
|
822
|
-
class RdxNavigationMenuDirective {
|
|
823
|
-
// State
|
|
824
|
-
#value;
|
|
825
|
-
#previousValue;
|
|
826
|
-
#indicatorTrack;
|
|
827
|
-
#viewport;
|
|
828
|
-
#viewportContent;
|
|
829
|
-
#rootNavigationMenu;
|
|
830
|
-
#userDismissedByClick;
|
|
831
|
-
#isOpenDelayed;
|
|
832
|
-
// pointer tracking
|
|
833
|
-
#isPointerOverContent;
|
|
834
|
-
#isPointerOverTrigger;
|
|
339
|
+
/**
|
|
340
|
+
* The shared container for the active item's content.
|
|
341
|
+
*/
|
|
342
|
+
class RdxNavigationMenuPopup {
|
|
835
343
|
constructor() {
|
|
344
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
345
|
+
this.dismissableLayer = inject(RdxDismissableLayer);
|
|
346
|
+
this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
|
|
347
|
+
this.layersContext = inject(RdxDismissableLayersContextToken);
|
|
836
348
|
this.elementRef = inject(ElementRef);
|
|
837
|
-
this.
|
|
838
|
-
this.
|
|
839
|
-
|
|
840
|
-
this
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this
|
|
845
|
-
this
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
this.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
this.
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
this.clickIgnoreDuration = 250;
|
|
865
|
-
this.delayDuration = 200;
|
|
866
|
-
this.skipDelayDuration = 300;
|
|
867
|
-
this.loop = false;
|
|
868
|
-
this.cssAnimation = false;
|
|
869
|
-
this.cssOpeningAnimation = false;
|
|
870
|
-
this.cssClosingAnimation = false;
|
|
871
|
-
this.isRootMenu = true;
|
|
872
|
-
this.cssAnimationStatus = signal(null, ...(ngDevMode ? [{ debugName: "cssAnimationStatus" }] : /* istanbul ignore next */ []));
|
|
873
|
-
// exposed state as functions for the token
|
|
874
|
-
this.value = () => this.#value();
|
|
875
|
-
this.previousValue = () => this.#previousValue();
|
|
876
|
-
this.rootNavigationMenu = () => this.#rootNavigationMenu();
|
|
877
|
-
this.indicatorTrack = () => this.#indicatorTrack();
|
|
878
|
-
this.viewport = () => this.#viewport();
|
|
879
|
-
this.viewportContent = () => this.#viewportContent();
|
|
880
|
-
// exposed pointer state
|
|
881
|
-
this.setTriggerPointerState = (isOver) => this.#isPointerOverTrigger.set(isOver);
|
|
882
|
-
this.setContentPointerState = (isOver) => this.#isPointerOverContent.set(isOver);
|
|
883
|
-
this.isPointerInSystem = () => this.#isPointerOverContent() || this.#isPointerOverTrigger();
|
|
884
|
-
// exposed animation state
|
|
885
|
-
this.getCssAnimation = () => this.cssAnimation;
|
|
886
|
-
this.getCssOpeningAnimation = () => this.cssOpeningAnimation;
|
|
887
|
-
this.getCssClosingAnimation = () => this.cssClosingAnimation;
|
|
349
|
+
this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
350
|
+
this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
351
|
+
/** Names the menu after the active trigger so the `role="menu"` element has an accessible name. */
|
|
352
|
+
this.labelledBy = computed(() => {
|
|
353
|
+
const value = this.rootContext.value() ?? this.rootContext.previousValue();
|
|
354
|
+
return value ? this.rootContext.triggerId(value) : undefined;
|
|
355
|
+
}, ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
356
|
+
this.dismissReason = 'none';
|
|
357
|
+
this.dismissEvent = new Event('navigation-menu.dismiss');
|
|
358
|
+
/**
|
|
359
|
+
* Event handler called when the escape key is down. Can be prevented.
|
|
360
|
+
*/
|
|
361
|
+
this.escapeKeyDown = outputFromObservable(outputToObservable(this.dismissableLayer.escapeKeyDown));
|
|
362
|
+
/**
|
|
363
|
+
* Event handler called when a pointerdown event happens outside the popup. Can be prevented.
|
|
364
|
+
*/
|
|
365
|
+
this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
|
|
366
|
+
/**
|
|
367
|
+
* Event handler called when focus moves outside the popup. Can be prevented.
|
|
368
|
+
*/
|
|
369
|
+
this.focusOutside = outputFromObservable(outputToObservable(this.dismissableLayer.focusOutside));
|
|
370
|
+
const destroyRef = inject(DestroyRef);
|
|
371
|
+
const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.elementRef.nativeElement);
|
|
372
|
+
destroyRef.onDestroy(unregisterTransitionElement);
|
|
373
|
+
// Register the triggers as dismissable-layer branches so a pointer-down or (async) focus move
|
|
374
|
+
// onto a trigger counts as "inside" — otherwise focusing a sibling trigger to switch items,
|
|
375
|
+
// or returning focus to the trigger, would dismiss the menu. See dismissable-layer gotcha.
|
|
888
376
|
effect(() => {
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
// menu is closed, start skip delay timer
|
|
898
|
-
this.window.clearTimeout(this.skipDelayTimerRef);
|
|
899
|
-
this.skipDelayTimerRef = this.window.setTimeout(() => {
|
|
900
|
-
this.#isOpenDelayed.set(true);
|
|
901
|
-
}, this.skipDelayDuration);
|
|
902
|
-
}
|
|
377
|
+
const triggers = this.rootContext.triggers();
|
|
378
|
+
untracked(() => this.layersContext.branches.update((branches) => {
|
|
379
|
+
const next = new Set(branches);
|
|
380
|
+
triggers.forEach((trigger) => next.add(trigger));
|
|
381
|
+
return [...next];
|
|
382
|
+
}));
|
|
903
383
|
});
|
|
904
|
-
|
|
905
|
-
.
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
384
|
+
destroyRef.onDestroy(() => {
|
|
385
|
+
const triggers = this.rootContext.triggers();
|
|
386
|
+
this.layersContext.branches.update((branches) => branches.filter((el) => !triggers.includes(el)));
|
|
387
|
+
});
|
|
388
|
+
this.dismissableLayer.escapeKeyDown.subscribe((event) => {
|
|
389
|
+
this.dismissReason = 'escape-key';
|
|
390
|
+
this.dismissEvent = event;
|
|
391
|
+
});
|
|
392
|
+
this.dismissableLayer.pointerDownOutside.subscribe((event) => {
|
|
393
|
+
this.dismissReason = 'outside-press';
|
|
394
|
+
this.dismissEvent = event;
|
|
395
|
+
});
|
|
396
|
+
this.dismissableLayer.focusOutside.subscribe((event) => {
|
|
397
|
+
this.dismissReason = 'focus-out';
|
|
398
|
+
this.dismissEvent = event;
|
|
399
|
+
});
|
|
400
|
+
this.dismissableLayer.dismiss.subscribe(() => {
|
|
401
|
+
const reason = this.dismissReason;
|
|
402
|
+
const event = this.dismissEvent;
|
|
403
|
+
this.dismissReason = 'none';
|
|
404
|
+
this.dismissEvent = new Event('navigation-menu.dismiss');
|
|
405
|
+
this.rootContext.close(reason, event);
|
|
406
|
+
// Return focus to the trigger after an Escape dismissal.
|
|
407
|
+
if (reason === 'escape-key') {
|
|
408
|
+
this.rootContext.trigger()?.focus();
|
|
922
409
|
}
|
|
923
|
-
})
|
|
924
|
-
.subscribe();
|
|
925
|
-
// set up document mouseleave handler to close menu when mouse leaves window
|
|
926
|
-
this.documentMouseLeaveHandler = () => this.handleClose();
|
|
927
|
-
this.document.addEventListener('mouseleave', this.documentMouseLeaveHandler);
|
|
410
|
+
});
|
|
928
411
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
this.window.clearTimeout(this.skipDelayTimerRef);
|
|
933
|
-
this.window.clearTimeout(this.recentlyActivatedTimerRef);
|
|
934
|
-
// clean up document event listener
|
|
935
|
-
if (this.documentMouseLeaveHandler) {
|
|
936
|
-
document.removeEventListener('mouseleave', this.documentMouseLeaveHandler);
|
|
412
|
+
onPointerLeave(event) {
|
|
413
|
+
if (event.pointerType === 'touch') {
|
|
414
|
+
return;
|
|
937
415
|
}
|
|
416
|
+
this.rootContext.closeOnHover();
|
|
938
417
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
// skip opening if user explicitly dismissed this menu
|
|
947
|
-
if (this.#userDismissedByClick() && itemValue === this.#previousValue()) {
|
|
418
|
+
/**
|
|
419
|
+
* Keyboard navigation inside the open panel: Down/Up move between the panel's focusable items in
|
|
420
|
+
* DOM order, Home/End jump to the first/last, and Up from the first item returns focus to the
|
|
421
|
+
* trigger. (Tab keeps working natively; Escape is handled by the dismissable layer.)
|
|
422
|
+
*/
|
|
423
|
+
onKeydown(event) {
|
|
424
|
+
if (event.key !== ARROW_DOWN && event.key !== ARROW_UP && event.key !== HOME && event.key !== END) {
|
|
948
425
|
return;
|
|
949
426
|
}
|
|
950
|
-
this
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
427
|
+
// If the key originates from a nested navigation menu rendered inside this popup, let that
|
|
428
|
+
// menu's own roving group / popup handle it — otherwise both react and focus jumps/skips.
|
|
429
|
+
const nestedRoot = event.target.closest('[rdxNavigationMenuRoot]');
|
|
430
|
+
if (nestedRoot && this.elementRef.nativeElement.contains(nestedRoot)) {
|
|
431
|
+
return;
|
|
954
432
|
}
|
|
955
|
-
|
|
956
|
-
|
|
433
|
+
const candidates = getTabbableCandidates(this.elementRef.nativeElement);
|
|
434
|
+
if (candidates.length === 0) {
|
|
435
|
+
return;
|
|
957
436
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
}
|
|
963
|
-
onContentEnter() {
|
|
964
|
-
this.window.clearTimeout(this.closeTimerRef);
|
|
965
|
-
}
|
|
966
|
-
onContentLeave() {
|
|
967
|
-
this.startCloseTimer();
|
|
968
|
-
}
|
|
969
|
-
handleClose() {
|
|
970
|
-
this.actionSubject$.next({ action: RdxNavigationMenuAction.CLOSE });
|
|
971
|
-
}
|
|
972
|
-
onItemSelect(itemValue) {
|
|
973
|
-
const wasOpen = this.#value() === itemValue;
|
|
974
|
-
// if this item just opened and the click would close it,
|
|
975
|
-
// ignore the click during the brief protection window
|
|
976
|
-
if (this.recentlyActivatedItem() === itemValue && wasOpen) {
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
const currentIndex = candidates.indexOf(document.activeElement);
|
|
439
|
+
if (event.key === HOME) {
|
|
440
|
+
focusFirst([candidates[0]]);
|
|
977
441
|
return;
|
|
978
442
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
this.#userDismissedByClick.set(true);
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
985
|
-
this.#userDismissedByClick.set(false);
|
|
986
|
-
}
|
|
987
|
-
this.setValue(newValue);
|
|
988
|
-
}
|
|
989
|
-
onItemDismiss() {
|
|
990
|
-
this.setValue('');
|
|
991
|
-
}
|
|
992
|
-
onViewportContentChange(contentValue, contentData) {
|
|
993
|
-
const newMap = new Map(this.#viewportContent());
|
|
994
|
-
newMap.set(contentValue, contentData);
|
|
995
|
-
this.#viewportContent.set(newMap);
|
|
996
|
-
}
|
|
997
|
-
onViewportContentRemove(contentValue) {
|
|
998
|
-
const newMap = new Map(this.#viewportContent());
|
|
999
|
-
if (newMap.has(contentValue)) {
|
|
1000
|
-
newMap.delete(contentValue);
|
|
1001
|
-
this.#viewportContent.set(newMap);
|
|
443
|
+
if (event.key === END) {
|
|
444
|
+
focusFirst([candidates[candidates.length - 1]]);
|
|
445
|
+
return;
|
|
1002
446
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
this.window.clearTimeout(this.recentlyActivatedTimerRef);
|
|
1008
|
-
this.recentlyActivatedItem.set(value);
|
|
1009
|
-
this.recentlyActivatedTimerRef = this.window.setTimeout(() => {
|
|
1010
|
-
this.recentlyActivatedItem.set(null);
|
|
1011
|
-
}, this.clickIgnoreDuration);
|
|
447
|
+
if (event.key === ARROW_DOWN) {
|
|
448
|
+
const next = currentIndex < candidates.length - 1 ? currentIndex + 1 : 0;
|
|
449
|
+
focusFirst([candidates[next]]);
|
|
450
|
+
return;
|
|
1012
451
|
}
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
startCloseTimer() {
|
|
1018
|
-
this.window.clearTimeout(this.closeTimerRef);
|
|
1019
|
-
this.closeTimerRef = this.window.setTimeout(() => {
|
|
1020
|
-
// only close if not hovering over any part of the system
|
|
1021
|
-
if (!this.isPointerInSystem()) {
|
|
1022
|
-
this.setValue('');
|
|
1023
|
-
}
|
|
1024
|
-
}, 150);
|
|
1025
|
-
}
|
|
1026
|
-
handleOpen(itemValue) {
|
|
1027
|
-
this.window.clearTimeout(this.closeTimerRef);
|
|
1028
|
-
this.setValue(itemValue);
|
|
1029
|
-
}
|
|
1030
|
-
handleDelayedOpen(itemValue) {
|
|
1031
|
-
const isOpenItem = this.#value() === itemValue;
|
|
1032
|
-
if (isOpenItem) {
|
|
1033
|
-
// if the item is already open, clear close timer
|
|
1034
|
-
this.window.clearTimeout(this.closeTimerRef);
|
|
452
|
+
// ArrowUp: from the first item, return focus to the trigger; otherwise move to the previous.
|
|
453
|
+
if (currentIndex <= 0) {
|
|
454
|
+
this.rootContext.trigger()?.focus();
|
|
1035
455
|
}
|
|
1036
456
|
else {
|
|
1037
|
-
|
|
1038
|
-
this.openTimerRef = this.window.setTimeout(() => {
|
|
1039
|
-
this.window.clearTimeout(this.closeTimerRef);
|
|
1040
|
-
this.setValue(itemValue);
|
|
1041
|
-
}, this.delayDuration);
|
|
457
|
+
focusFirst([candidates[currentIndex - 1]]);
|
|
1042
458
|
}
|
|
1043
459
|
}
|
|
1044
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1045
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
460
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
461
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPopup, isStandalone: true, selector: "[rdxNavigationMenuPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside" }, host: { attributes: { "role": "menu", "tabindex": "-1" }, listeners: { "pointerenter": "rootContext.cancelHoverClose()", "pointerleave": "onPointerLeave($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-labelledby": "labelledBy()", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-side": "side()", "attr.data-align": "align()" } }, hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxDismissableLayer }], ngImport: i0 }); }
|
|
1046
462
|
}
|
|
1047
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, decorators: [{
|
|
1048
464
|
type: Directive,
|
|
1049
465
|
args: [{
|
|
1050
|
-
selector: '[
|
|
1051
|
-
|
|
466
|
+
selector: '[rdxNavigationMenuPopup]',
|
|
467
|
+
hostDirectives: [RdxPopperContent, RdxDismissableLayer],
|
|
1052
468
|
host: {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
'aria-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
469
|
+
role: 'menu',
|
|
470
|
+
tabindex: '-1',
|
|
471
|
+
'[attr.aria-labelledby]': 'labelledBy()',
|
|
472
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
473
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
474
|
+
'[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
|
|
475
|
+
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
|
|
476
|
+
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
|
|
477
|
+
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
478
|
+
'[attr.data-side]': 'side()',
|
|
479
|
+
'[attr.data-align]': 'align()',
|
|
480
|
+
'(pointerenter)': 'rootContext.cancelHoverClose()',
|
|
481
|
+
'(pointerleave)': 'onPointerLeave($event)',
|
|
482
|
+
'(keydown)': 'onKeydown($event)'
|
|
483
|
+
}
|
|
1059
484
|
}]
|
|
1060
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
1061
|
-
type: Input
|
|
1062
|
-
}], dir: [{
|
|
1063
|
-
type: Input
|
|
1064
|
-
}], clickIgnoreDuration: [{
|
|
1065
|
-
type: Input,
|
|
1066
|
-
args: [{ transform: numberAttribute }]
|
|
1067
|
-
}], delayDuration: [{
|
|
1068
|
-
type: Input,
|
|
1069
|
-
args: [{ transform: numberAttribute }]
|
|
1070
|
-
}], skipDelayDuration: [{
|
|
1071
|
-
type: Input,
|
|
1072
|
-
args: [{ transform: numberAttribute }]
|
|
1073
|
-
}], loop: [{
|
|
1074
|
-
type: Input,
|
|
1075
|
-
args: [{ transform: booleanAttribute }]
|
|
1076
|
-
}], cssAnimation: [{
|
|
1077
|
-
type: Input,
|
|
1078
|
-
args: [{ transform: booleanAttribute }]
|
|
1079
|
-
}], cssOpeningAnimation: [{
|
|
1080
|
-
type: Input,
|
|
1081
|
-
args: [{ transform: booleanAttribute }]
|
|
1082
|
-
}], cssClosingAnimation: [{
|
|
1083
|
-
type: Input,
|
|
1084
|
-
args: [{ transform: booleanAttribute }]
|
|
1085
|
-
}] } });
|
|
485
|
+
}], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], focusOutside: [{ type: i0.Output, args: ["focusOutside"] }] } });
|
|
1086
486
|
|
|
1087
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Moves the navigation menu popup to a different part of the DOM (by default `document.body`).
|
|
489
|
+
*/
|
|
490
|
+
class RdxNavigationMenuPortal {
|
|
1088
491
|
constructor() {
|
|
1089
|
-
this.
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
this.
|
|
1094
|
-
this.isRootMenu = false;
|
|
1095
|
-
this.parent = inject(RdxNavigationMenuDirective, { optional: true });
|
|
1096
|
-
}
|
|
1097
|
-
set defaultValue(val) {
|
|
1098
|
-
if (val)
|
|
1099
|
-
this.value.set(val);
|
|
1100
|
-
}
|
|
1101
|
-
get dir() {
|
|
1102
|
-
if (!this.parent) {
|
|
1103
|
-
return 'ltr';
|
|
1104
|
-
}
|
|
1105
|
-
return this.parent.dir || 'ltr';
|
|
1106
|
-
}
|
|
1107
|
-
get rootNavigationMenu() {
|
|
1108
|
-
return this.parent?.rootNavigationMenu() || null;
|
|
1109
|
-
}
|
|
1110
|
-
onTriggerEnter(itemValue) {
|
|
1111
|
-
this.setValue(itemValue);
|
|
1112
|
-
}
|
|
1113
|
-
onItemSelect(itemValue) {
|
|
1114
|
-
this.setValue(itemValue);
|
|
1115
|
-
}
|
|
1116
|
-
onItemDismiss() {
|
|
1117
|
-
this.setValue('');
|
|
1118
|
-
}
|
|
1119
|
-
setValue(value) {
|
|
1120
|
-
this.previousValue.set(this.value());
|
|
1121
|
-
this.value.set(value);
|
|
1122
|
-
this.valueChange.emit(value);
|
|
492
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
493
|
+
/**
|
|
494
|
+
* Optional container to portal the popup into. Defaults to `document.body`.
|
|
495
|
+
*/
|
|
496
|
+
this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
|
|
1123
497
|
}
|
|
1124
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1125
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type:
|
|
498
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
499
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuPortal, isStandalone: true, selector: "[rdxNavigationMenuPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"" } }, hostDirectives: [{ directive: i1$2.RdxPortal, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
1126
500
|
}
|
|
1127
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
501
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, decorators: [{
|
|
1128
502
|
type: Directive,
|
|
1129
503
|
args: [{
|
|
1130
|
-
selector: '[
|
|
1131
|
-
|
|
504
|
+
selector: '[rdxNavigationMenuPortal]',
|
|
505
|
+
hostDirectives: [
|
|
506
|
+
{
|
|
507
|
+
directive: RdxPortal,
|
|
508
|
+
inputs: ['container']
|
|
509
|
+
}
|
|
510
|
+
],
|
|
1132
511
|
host: {
|
|
1133
|
-
'[attr.data-
|
|
512
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
513
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
514
|
+
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
|
|
1134
515
|
}
|
|
1135
516
|
}]
|
|
1136
|
-
}], propDecorators: {
|
|
1137
|
-
|
|
1138
|
-
|
|
517
|
+
}], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Mounts the popup while the menu is open and waits for CSS exit keyframes before unmounting.
|
|
521
|
+
*
|
|
522
|
+
* ```html
|
|
523
|
+
* <ng-template rdxNavigationMenuPortalPresence>…</ng-template>
|
|
524
|
+
* ```
|
|
525
|
+
*/
|
|
526
|
+
class RdxNavigationMenuPortalPresence {
|
|
527
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortalPresence, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
528
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPortalPresence, isStandalone: true, selector: "ng-template[rdxNavigationMenuPortalPresence]", providers: [
|
|
529
|
+
provideRdxPresenceContext(() => {
|
|
530
|
+
const context = injectNavigationMenuRootContext();
|
|
531
|
+
return { present: context.isOpen };
|
|
532
|
+
})
|
|
533
|
+
], hostDirectives: [{ directive: i1$3.RdxPresenceDirective }], ngImport: i0 }); }
|
|
534
|
+
}
|
|
535
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortalPresence, decorators: [{
|
|
536
|
+
type: Directive,
|
|
537
|
+
args: [{
|
|
538
|
+
selector: 'ng-template[rdxNavigationMenuPortalPresence]',
|
|
539
|
+
hostDirectives: [RdxPresenceDirective],
|
|
540
|
+
providers: [
|
|
541
|
+
provideRdxPresenceContext(() => {
|
|
542
|
+
const context = injectNavigationMenuRootContext();
|
|
543
|
+
return { present: context.isOpen };
|
|
544
|
+
})
|
|
545
|
+
]
|
|
546
|
+
}]
|
|
547
|
+
}] });
|
|
1139
548
|
|
|
1140
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Positions the shared popup against the active trigger.
|
|
551
|
+
*/
|
|
552
|
+
class RdxNavigationMenuPositioner {
|
|
1141
553
|
constructor() {
|
|
1142
|
-
|
|
1143
|
-
this.
|
|
1144
|
-
this.item = inject(RdxNavigationMenuItemDirective);
|
|
1145
|
-
this.list = inject(RdxNavigationMenuListDirective);
|
|
1146
|
-
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
|
554
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
555
|
+
this.wrapper = inject(RdxPopperContentWrapper);
|
|
1147
556
|
this.elementRef = inject(ElementRef);
|
|
1148
|
-
this.
|
|
1149
|
-
this.
|
|
1150
|
-
this.
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
this.
|
|
1159
|
-
|
|
557
|
+
this.triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
|
|
558
|
+
this.containerEl = signal(this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
|
|
559
|
+
this.graceArea = useGraceArea(this.triggerEl, this.containerEl);
|
|
560
|
+
/**
|
|
561
|
+
* An element to position the popup against. Defaults to the active trigger.
|
|
562
|
+
*/
|
|
563
|
+
this.anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
|
|
564
|
+
/**
|
|
565
|
+
* The preferred side of the trigger to render against when open.
|
|
566
|
+
*/
|
|
567
|
+
this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
568
|
+
/**
|
|
569
|
+
* Distance between the trigger and the popup in pixels.
|
|
570
|
+
*/
|
|
571
|
+
this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
572
|
+
/**
|
|
573
|
+
* How to align the popup relative to the specified side.
|
|
574
|
+
*/
|
|
575
|
+
this.align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
576
|
+
/**
|
|
577
|
+
* An offset in pixels from the `start` or `end` alignment options.
|
|
578
|
+
*/
|
|
579
|
+
this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
580
|
+
/**
|
|
581
|
+
* Minimum distance to maintain between the arrow and the edges of the popup.
|
|
582
|
+
*/
|
|
583
|
+
this.arrowPadding = input(5, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
584
|
+
/**
|
|
585
|
+
* Whether to override side and alignment preferences to prevent collisions.
|
|
586
|
+
*/
|
|
587
|
+
this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
588
|
+
/**
|
|
589
|
+
* The element used as the collision boundary.
|
|
590
|
+
*/
|
|
591
|
+
this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
|
|
592
|
+
/**
|
|
593
|
+
* Distance in pixels from the boundary edges where collision detection should occur.
|
|
594
|
+
*/
|
|
595
|
+
this.collisionPadding = input(5, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
|
|
596
|
+
/**
|
|
597
|
+
* The sticky behavior on the alignment axis.
|
|
598
|
+
*/
|
|
599
|
+
this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
600
|
+
/**
|
|
601
|
+
* Whether to hide the popup when the trigger becomes fully occluded.
|
|
602
|
+
*/
|
|
603
|
+
this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
604
|
+
/**
|
|
605
|
+
* The CSS position strategy used by Floating UI.
|
|
606
|
+
*/
|
|
607
|
+
this.positionStrategy = input('fixed', ...(ngDevMode ? [{ debugName: "positionStrategy" }] : /* istanbul ignore next */ []));
|
|
608
|
+
/**
|
|
609
|
+
* Whether to update position on every animation frame.
|
|
610
|
+
*/
|
|
611
|
+
this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
|
|
612
|
+
effect(() => this.triggerEl.set(this.rootContext.trigger() ?? null));
|
|
613
|
+
// Keep the menu open while the pointer travels from the trigger to the popup; close once it
|
|
614
|
+
// leaves the grace area between them.
|
|
615
|
+
this.graceArea.onPointerExit(() => this.rootContext.closeOnHover());
|
|
616
|
+
}
|
|
617
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
618
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuPositioner, isStandalone: true, selector: "[rdxNavigationMenuPositioner]", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, positionStrategy: { classPropertyName: "positionStrategy", publicName: "positionStrategy", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-anchor-hidden": "wrapper.anchorHidden() ? \"\" : undefined", "attr.data-align": "wrapper.placedAlign()", "attr.data-side": "wrapper.placedSide()", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "style": "{\n '--anchor-width': 'var(--radix-popper-anchor-width)',\n '--anchor-height': 'var(--radix-popper-anchor-height)',\n '--available-width': 'var(--radix-popper-available-width)',\n '--available-height': 'var(--radix-popper-available-height)',\n '--positioner-width': 'var(--radix-popper-content-wrapper-width)',\n '--positioner-height': 'var(--radix-popper-content-wrapper-height)',\n '--transform-origin': 'var(--radix-popper-transform-origin)'\n }" } }, providers: [
|
|
619
|
+
provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
|
|
620
|
+
], hostDirectives: [{ directive: i1.RdxPopperContentWrapper, inputs: ["anchor", "anchor", "side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "positionStrategy", "positionStrategy", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
|
|
621
|
+
}
|
|
622
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPositioner, decorators: [{
|
|
623
|
+
type: Directive,
|
|
624
|
+
args: [{
|
|
625
|
+
selector: '[rdxNavigationMenuPositioner]',
|
|
626
|
+
providers: [
|
|
627
|
+
provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
|
|
628
|
+
],
|
|
629
|
+
hostDirectives: [
|
|
630
|
+
{
|
|
631
|
+
directive: RdxPopperContentWrapper,
|
|
632
|
+
inputs: [
|
|
633
|
+
'anchor',
|
|
634
|
+
'side',
|
|
635
|
+
'sideOffset',
|
|
636
|
+
'align',
|
|
637
|
+
'alignOffset',
|
|
638
|
+
'arrowPadding',
|
|
639
|
+
'avoidCollisions',
|
|
640
|
+
'collisionBoundary',
|
|
641
|
+
'collisionPadding',
|
|
642
|
+
'sticky',
|
|
643
|
+
'hideWhenDetached',
|
|
644
|
+
'positionStrategy',
|
|
645
|
+
'updatePositionStrategy'
|
|
646
|
+
]
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
host: {
|
|
650
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
651
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
652
|
+
'[attr.data-anchor-hidden]': 'wrapper.anchorHidden() ? "" : undefined',
|
|
653
|
+
'[attr.data-align]': 'wrapper.placedAlign()',
|
|
654
|
+
'[attr.data-side]': 'wrapper.placedSide()',
|
|
655
|
+
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
|
|
656
|
+
'[style]': `{
|
|
657
|
+
'--anchor-width': 'var(--radix-popper-anchor-width)',
|
|
658
|
+
'--anchor-height': 'var(--radix-popper-anchor-height)',
|
|
659
|
+
'--available-width': 'var(--radix-popper-available-width)',
|
|
660
|
+
'--available-height': 'var(--radix-popper-available-height)',
|
|
661
|
+
'--positioner-width': 'var(--radix-popper-content-wrapper-width)',
|
|
662
|
+
'--positioner-height': 'var(--radix-popper-content-wrapper-height)',
|
|
663
|
+
'--transform-origin': 'var(--radix-popper-transform-origin)'
|
|
664
|
+
}`
|
|
665
|
+
}
|
|
666
|
+
}]
|
|
667
|
+
}], ctorParameters: () => [], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], positionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionStrategy", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }] } });
|
|
668
|
+
|
|
669
|
+
const context = () => contextFor(inject(RdxNavigationMenuRoot));
|
|
670
|
+
/**
|
|
671
|
+
* Groups all parts of the navigation menu.
|
|
672
|
+
*
|
|
673
|
+
* Holds the shared open state: `value` identifies the currently open item, and the menu is open
|
|
674
|
+
* whenever `value` is non-null. A single popup (Portal → Positioner → Popup → Viewport) is shared
|
|
675
|
+
* between every item and anchored to the active trigger.
|
|
676
|
+
*/
|
|
677
|
+
class RdxNavigationMenuRoot {
|
|
678
|
+
constructor() {
|
|
679
|
+
this.popper = inject(RdxPopper);
|
|
680
|
+
this.destroyRef = inject(DestroyRef);
|
|
681
|
+
this.parentRoot = inject(RdxNavigationMenuRoot, { optional: true, skipSelf: true });
|
|
682
|
+
/** Whether this root is nested inside another navigation menu's content. */
|
|
683
|
+
this.nested = !!this.parentRoot;
|
|
684
|
+
this.baseId = `rdx-nav-menu-${generateId()}`;
|
|
685
|
+
/**
|
|
686
|
+
* The value of the navigation menu item that should be currently open.
|
|
687
|
+
*/
|
|
688
|
+
this.value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
689
|
+
/**
|
|
690
|
+
* The uncontrolled value of the item that should be initially open.
|
|
691
|
+
*/
|
|
692
|
+
this.defaultValue = input(null, ...(ngDevMode ? [{ debugName: "defaultValue" }] : /* istanbul ignore next */ []));
|
|
693
|
+
/**
|
|
694
|
+
* The orientation of the navigation menu.
|
|
695
|
+
*/
|
|
696
|
+
this.orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
697
|
+
/**
|
|
698
|
+
* The reading direction of the navigation menu.
|
|
699
|
+
*/
|
|
700
|
+
this.dir = input('ltr', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
701
|
+
/**
|
|
702
|
+
* Whether keyboard navigation loops from the last item back to the first and vice versa.
|
|
703
|
+
*/
|
|
704
|
+
this.loop = input(false, { ...(ngDevMode ? { debugName: "loop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
705
|
+
/**
|
|
706
|
+
* How long to wait before opening the menu on hover, in milliseconds.
|
|
707
|
+
*/
|
|
708
|
+
this.delay = input(50, { ...(ngDevMode ? { debugName: "delay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
709
|
+
/**
|
|
710
|
+
* How long to wait before closing the menu after the pointer leaves, in milliseconds.
|
|
711
|
+
*/
|
|
712
|
+
this.closeDelay = input(50, { ...(ngDevMode ? { debugName: "closeDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
713
|
+
/**
|
|
714
|
+
* Emits when the open item changes.
|
|
715
|
+
*/
|
|
716
|
+
this.onValueChange = output();
|
|
717
|
+
/**
|
|
718
|
+
* Emits whenever the menu opens or closes.
|
|
719
|
+
*/
|
|
720
|
+
this.onOpenChange = output();
|
|
721
|
+
/**
|
|
722
|
+
* Emits after any enter/exit transition completes.
|
|
723
|
+
*/
|
|
724
|
+
this.onOpenChangeComplete = output();
|
|
725
|
+
this.hasAppliedDefaultValue = false;
|
|
726
|
+
this.transition = useTransitionStatus((open) => {
|
|
727
|
+
this.instant.set(false);
|
|
728
|
+
this.onOpenChangeComplete.emit(open);
|
|
729
|
+
});
|
|
730
|
+
this.instant = signal(false, ...(ngDevMode ? [{ debugName: "instant" }] : /* istanbul ignore next */ []));
|
|
731
|
+
this.transitionStatus = this.transition.status;
|
|
732
|
+
this.previousValue = signal(null, ...(ngDevMode ? [{ debugName: "previousValue" }] : /* istanbul ignore next */ []));
|
|
733
|
+
this.isOpen = computed(() => this.value() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
734
|
+
this.trigger = signal(undefined, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
735
|
+
this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
|
|
736
|
+
this.contents = signal(new Map(), ...(ngDevMode ? [{ debugName: "contents" }] : /* istanbul ignore next */ []));
|
|
737
|
+
this.activeContent = computed(() => {
|
|
738
|
+
const value = this.value() ?? this.previousValue();
|
|
739
|
+
return value ? this.contents().get(value) : undefined;
|
|
740
|
+
}, ...(ngDevMode ? [{ debugName: "activeContent" }] : /* istanbul ignore next */ []));
|
|
741
|
+
this.registeredTriggers = new Map();
|
|
742
|
+
this.viewportTriggerChange = new Set();
|
|
743
|
+
let previousOpen = this.isOpen();
|
|
1160
744
|
effect(() => {
|
|
1161
|
-
this.
|
|
745
|
+
const defaultValue = this.defaultValue();
|
|
746
|
+
if (!this.hasAppliedDefaultValue && defaultValue !== null) {
|
|
747
|
+
this.hasAppliedDefaultValue = true;
|
|
748
|
+
this.value.set(defaultValue);
|
|
749
|
+
}
|
|
1162
750
|
});
|
|
1163
751
|
effect(() => {
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
752
|
+
const open = this.isOpen();
|
|
753
|
+
if (open !== previousOpen) {
|
|
754
|
+
previousOpen = open;
|
|
755
|
+
untracked(() => this.transition.start(open));
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
// Anchor the shared popper to the active trigger.
|
|
759
|
+
effect(() => this.popper.anchorOverride.set(this.trigger()));
|
|
760
|
+
this.destroyRef.onDestroy(() => {
|
|
761
|
+
this.clearHoverTimers();
|
|
762
|
+
if (this.instantFrame !== undefined) {
|
|
763
|
+
cancelAnimationFrame(this.instantFrame);
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
contentId(value) {
|
|
768
|
+
return `${this.baseId}-content-${value}`;
|
|
769
|
+
}
|
|
770
|
+
triggerId(value) {
|
|
771
|
+
return `${this.baseId}-trigger-${value}`;
|
|
772
|
+
}
|
|
773
|
+
setValue(value, reason = 'none', event = new Event('navigation-menu.value-change')) {
|
|
774
|
+
const previous = this.value();
|
|
775
|
+
if (previous === value) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const previousTrigger = this.trigger();
|
|
779
|
+
const nextTrigger = value ? this.registeredTriggers.get(value) : undefined;
|
|
780
|
+
const changedTriggerWhileOpen = previous !== null && value !== null && previousTrigger !== nextTrigger;
|
|
781
|
+
this.instant.set(changedTriggerWhileOpen || reason === 'trigger-focus');
|
|
782
|
+
if (changedTriggerWhileOpen) {
|
|
783
|
+
this.scheduleInstantReset();
|
|
784
|
+
if (previousTrigger && nextTrigger) {
|
|
785
|
+
this.viewportTriggerChange.forEach((notify) => notify(previousTrigger, nextTrigger));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (nextTrigger) {
|
|
789
|
+
this.trigger.set(nextTrigger);
|
|
790
|
+
}
|
|
791
|
+
this.previousValue.set(previous);
|
|
792
|
+
this.value.set(value);
|
|
793
|
+
this.onValueChange.emit(value);
|
|
794
|
+
this.onOpenChange.emit({ value, open: value !== null, reason, event });
|
|
795
|
+
}
|
|
796
|
+
open(value, trigger, reason = 'none', event) {
|
|
797
|
+
this.clearHoverTimers();
|
|
798
|
+
// Register the anchor in case this value hasn't been seen yet, but DON'T set `this.trigger`
|
|
799
|
+
// here: setValue must still read the *previous* trigger to detect a trigger switch and drive
|
|
800
|
+
// the viewport morph. It sets `this.trigger` from the registry after that comparison.
|
|
801
|
+
if (!this.registeredTriggers.has(value)) {
|
|
802
|
+
this.registeredTriggers.set(value, trigger);
|
|
803
|
+
}
|
|
804
|
+
this.setValue(value, reason, event);
|
|
805
|
+
}
|
|
806
|
+
close(reason = 'none', event) {
|
|
807
|
+
this.clearHoverTimers();
|
|
808
|
+
if (!this.isOpen()) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
this.instant.set(reason !== 'none' && reason !== 'trigger-hover' && reason !== 'list-leave');
|
|
812
|
+
this.setValue(null, reason, event);
|
|
813
|
+
}
|
|
814
|
+
toggle(value, trigger, event) {
|
|
815
|
+
this.clearHoverTimers();
|
|
816
|
+
if (this.value() === value) {
|
|
817
|
+
this.close('trigger-press', event);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
this.open(value, trigger, 'trigger-press', event);
|
|
821
|
+
}
|
|
822
|
+
openOnHover(value, trigger, event) {
|
|
823
|
+
this.clearHoverTimers();
|
|
824
|
+
// Switching between already-open items happens instantly.
|
|
825
|
+
if (this.isOpen()) {
|
|
826
|
+
this.open(value, trigger, 'trigger-hover', event);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.openTimer = setTimeout(() => this.open(value, trigger, 'trigger-hover', event), this.delay());
|
|
830
|
+
}
|
|
831
|
+
closeOnHover() {
|
|
832
|
+
this.clearOpenTimer();
|
|
833
|
+
this.clearCloseTimer();
|
|
834
|
+
this.closeTimer = setTimeout(() => this.close('list-leave', new Event('navigation-menu.hover-close')), this.closeDelay());
|
|
835
|
+
}
|
|
836
|
+
cancelHoverOpen() {
|
|
837
|
+
this.clearOpenTimer();
|
|
838
|
+
}
|
|
839
|
+
cancelHoverClose() {
|
|
840
|
+
this.clearCloseTimer();
|
|
841
|
+
}
|
|
842
|
+
registerTrigger(value, trigger) {
|
|
843
|
+
this.registeredTriggers.set(value, trigger);
|
|
844
|
+
this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
|
|
845
|
+
if (this.value() === value) {
|
|
846
|
+
this.trigger.set(trigger);
|
|
847
|
+
}
|
|
848
|
+
return () => {
|
|
849
|
+
if (this.registeredTriggers.get(value) === trigger) {
|
|
850
|
+
this.registeredTriggers.delete(value);
|
|
851
|
+
}
|
|
852
|
+
this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
|
|
853
|
+
if (this.destroyRef.destroyed || this.value() !== value) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
// Defer the close: when an item's `value` changes, the trigger's registration effect
|
|
857
|
+
// unregisters the old value and synchronously re-registers the same element under the new
|
|
858
|
+
// value. Closing immediately would collapse the menu mid-rename, so only close if the
|
|
859
|
+
// element is truly gone (not re-registered under any value) on the next microtask.
|
|
860
|
+
queueMicrotask(() => {
|
|
861
|
+
if (this.destroyRef.destroyed || this.value() !== value) {
|
|
862
|
+
return;
|
|
1169
863
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
this.item.onRootContentClose();
|
|
1174
|
-
}
|
|
1175
|
-
this.hasPointerMoveOpened = false;
|
|
864
|
+
const stillRegistered = [...this.registeredTriggers.values()].includes(trigger);
|
|
865
|
+
if (!stillRegistered) {
|
|
866
|
+
this.close();
|
|
1176
867
|
}
|
|
1177
868
|
});
|
|
1178
|
-
}
|
|
869
|
+
};
|
|
1179
870
|
}
|
|
1180
|
-
|
|
1181
|
-
this.
|
|
1182
|
-
|
|
1183
|
-
|
|
871
|
+
registerContent(entry) {
|
|
872
|
+
this.contents.update((contents) => new Map(contents).set(entry.value, entry));
|
|
873
|
+
return () => {
|
|
874
|
+
this.contents.update((contents) => {
|
|
875
|
+
if (contents.get(entry.value) !== entry) {
|
|
876
|
+
return contents;
|
|
877
|
+
}
|
|
878
|
+
const next = new Map(contents);
|
|
879
|
+
next.delete(entry.value);
|
|
880
|
+
return next;
|
|
881
|
+
});
|
|
882
|
+
};
|
|
1184
883
|
}
|
|
1185
|
-
|
|
1186
|
-
this.
|
|
884
|
+
registerTransitionElement(element) {
|
|
885
|
+
return this.transition.registerElement(element);
|
|
1187
886
|
}
|
|
1188
|
-
|
|
1189
|
-
this.
|
|
887
|
+
registerViewport(onTriggerChange) {
|
|
888
|
+
this.viewportTriggerChange.add(onTriggerChange);
|
|
889
|
+
return () => this.viewportTriggerChange.delete(onTriggerChange);
|
|
1190
890
|
}
|
|
1191
|
-
|
|
1192
|
-
if (this.
|
|
1193
|
-
|
|
891
|
+
scheduleInstantReset() {
|
|
892
|
+
if (this.instantFrame !== undefined) {
|
|
893
|
+
cancelAnimationFrame(this.instantFrame);
|
|
1194
894
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
this.item.onFocusProxyEnter(direction);
|
|
895
|
+
this.instantFrame = requestAnimationFrame(() => {
|
|
896
|
+
this.instantFrame = undefined;
|
|
897
|
+
if (!this.destroyRef.destroyed && this.isOpen()) {
|
|
898
|
+
this.instant.set(false);
|
|
899
|
+
}
|
|
1201
900
|
});
|
|
1202
|
-
// store reference in item directive
|
|
1203
|
-
this.item.focusProxyRef.set(this.focusProxyRef.location.nativeElement);
|
|
1204
|
-
// only add aria-owns component if using viewport
|
|
1205
|
-
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
|
1206
|
-
this.ariaOwnsRef = this.viewContainerRef.createComponent(RdxNavigationMenuAriaOwnsComponent);
|
|
1207
|
-
this.ariaOwnsRef.instance.contentId = this.contentId;
|
|
1208
|
-
}
|
|
1209
901
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
902
|
+
clearHoverTimers() {
|
|
903
|
+
this.clearOpenTimer();
|
|
904
|
+
this.clearCloseTimer();
|
|
905
|
+
}
|
|
906
|
+
clearOpenTimer() {
|
|
907
|
+
if (this.openTimer !== undefined) {
|
|
908
|
+
clearTimeout(this.openTimer);
|
|
909
|
+
this.openTimer = undefined;
|
|
1215
910
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
911
|
+
}
|
|
912
|
+
clearCloseTimer() {
|
|
913
|
+
if (this.closeTimer !== undefined) {
|
|
914
|
+
clearTimeout(this.closeTimer);
|
|
915
|
+
this.closeTimer = undefined;
|
|
1219
916
|
}
|
|
1220
917
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
918
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
919
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuRoot, isStandalone: true, selector: "[rdxNavigationMenuRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, host: { attributes: { "role": "navigation", "aria-label": "Main" }, properties: { "attr.data-orientation": "orientation()", "attr.dir": "dir()" } }, providers: [provideNavigationMenuRootContext(context)], exportAs: ["rdxNavigationMenuRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
|
|
920
|
+
}
|
|
921
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuRoot, decorators: [{
|
|
922
|
+
type: Directive,
|
|
923
|
+
args: [{
|
|
924
|
+
selector: '[rdxNavigationMenuRoot]',
|
|
925
|
+
exportAs: 'rdxNavigationMenuRoot',
|
|
926
|
+
providers: [provideNavigationMenuRootContext(context)],
|
|
927
|
+
hostDirectives: [RdxPopper],
|
|
928
|
+
host: {
|
|
929
|
+
role: 'navigation',
|
|
930
|
+
'aria-label': 'Main',
|
|
931
|
+
'[attr.data-orientation]': 'orientation()',
|
|
932
|
+
'[attr.dir]': 'dir()'
|
|
933
|
+
}
|
|
934
|
+
}]
|
|
935
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
936
|
+
function contextFor(root) {
|
|
937
|
+
return {
|
|
938
|
+
nested: root.nested,
|
|
939
|
+
baseId: root.baseId,
|
|
940
|
+
orientation: root.orientation,
|
|
941
|
+
dir: root.dir,
|
|
942
|
+
loop: root.loop,
|
|
943
|
+
value: root.value,
|
|
944
|
+
previousValue: root.previousValue.asReadonly(),
|
|
945
|
+
isOpen: root.isOpen,
|
|
946
|
+
instant: root.instant.asReadonly(),
|
|
947
|
+
transitionStatus: root.transitionStatus,
|
|
948
|
+
trigger: root.trigger.asReadonly(),
|
|
949
|
+
triggers: root.triggers.asReadonly(),
|
|
950
|
+
activeContent: root.activeContent,
|
|
951
|
+
contentId: (value) => root.contentId(value),
|
|
952
|
+
triggerId: (value) => root.triggerId(value),
|
|
953
|
+
setValue: (value, reason, event) => root.setValue(value, reason, event),
|
|
954
|
+
open: (value, trigger, reason, event) => root.open(value, trigger, reason, event),
|
|
955
|
+
close: (reason, event) => root.close(reason, event),
|
|
956
|
+
toggle: (value, trigger, event) => root.toggle(value, trigger, event),
|
|
957
|
+
openOnHover: (value, trigger, event) => root.openOnHover(value, trigger, event),
|
|
958
|
+
closeOnHover: () => root.closeOnHover(),
|
|
959
|
+
cancelHoverOpen: () => root.cancelHoverOpen(),
|
|
960
|
+
cancelHoverClose: () => root.cancelHoverClose(),
|
|
961
|
+
registerTrigger: (value, trigger) => root.registerTrigger(value, trigger),
|
|
962
|
+
registerContent: (entry) => root.registerContent(entry),
|
|
963
|
+
registerTransitionElement: (element) => root.registerTransitionElement(element),
|
|
964
|
+
registerViewport: (onTriggerChange) => root.registerViewport(onTriggerChange)
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* A button that opens its item's content in the shared popup.
|
|
970
|
+
*/
|
|
971
|
+
class RdxNavigationMenuTrigger {
|
|
972
|
+
constructor() {
|
|
973
|
+
this.item = inject(RdxNavigationMenuItem);
|
|
974
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
975
|
+
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
|
976
|
+
this.elementRef = inject(ElementRef);
|
|
977
|
+
this.document = injectDocument();
|
|
978
|
+
/**
|
|
979
|
+
* Whether the trigger should ignore user interaction.
|
|
980
|
+
*/
|
|
981
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
982
|
+
/**
|
|
983
|
+
* Whether the content should also open when the trigger is hovered.
|
|
984
|
+
*/
|
|
985
|
+
this.openOnHover = input(true, { ...(ngDevMode ? { debugName: "openOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
986
|
+
this.open = computed(() => this.item.isOpen(), ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
987
|
+
// Host element is available in the constructor; the trigger ref does not depend on inputs.
|
|
988
|
+
this.item.triggerRef.set(this.elementRef.nativeElement);
|
|
989
|
+
effect(() => this.rovingFocusItem.setFocusable(!this.disabled()));
|
|
990
|
+
// `value` is an input on the item, so read it in an effect (kept in sync if it ever changes).
|
|
991
|
+
effect(() => this.rovingFocusItem.setTabStopId(this.item.value()));
|
|
992
|
+
effect((onCleanup) => {
|
|
993
|
+
const value = this.item.value();
|
|
994
|
+
const element = this.elementRef.nativeElement;
|
|
995
|
+
// Register untracked: registerTrigger reads root state internally, and we must not make
|
|
996
|
+
// this effect re-run (and tear down the registration) when the open value changes.
|
|
997
|
+
onCleanup(untracked(() => this.rootContext.registerTrigger(value, element)));
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
onClick(event) {
|
|
1001
|
+
if (this.disabled()) {
|
|
1224
1002
|
return;
|
|
1225
|
-
this.wasClickClose = false; // Reset click close flag on enter
|
|
1226
|
-
this.item.wasEscapeCloseRef.set(false); // Reset escape flag
|
|
1227
|
-
this.context.setTriggerPointerState?.(true); // Update context state
|
|
1228
|
-
// if the menu isn't already open for this item, trigger the enter logic
|
|
1229
|
-
if (!this.open()) {
|
|
1230
|
-
if (this.openOnHover() || this.context.value() !== '') {
|
|
1231
|
-
this.context.onTriggerEnter?.(this.item.value());
|
|
1232
|
-
}
|
|
1233
1003
|
}
|
|
1004
|
+
this.rootContext.toggle(this.item.value(), this.elementRef.nativeElement, event);
|
|
1234
1005
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
// ignore if not a mouse event, disabled, closed by click/escape, or already opened by this move
|
|
1238
|
-
if (pointerEvent.pointerType !== 'mouse' ||
|
|
1239
|
-
this.disabled() ||
|
|
1240
|
-
this.wasClickClose ||
|
|
1241
|
-
this.item.wasEscapeCloseRef() ||
|
|
1242
|
-
this.hasPointerMoveOpened ||
|
|
1243
|
-
!isRootNavigationMenu(this.context)) {
|
|
1006
|
+
onPointerEnter(event) {
|
|
1007
|
+
if (this.disabled() || event.pointerType === 'touch' || !this.openOnHover()) {
|
|
1244
1008
|
return;
|
|
1245
1009
|
}
|
|
1246
|
-
this.
|
|
1010
|
+
this.rootContext.cancelHoverClose();
|
|
1011
|
+
this.rootContext.openOnHover(this.item.value(), this.elementRef.nativeElement, event);
|
|
1247
1012
|
}
|
|
1248
1013
|
onPointerLeave(event) {
|
|
1249
|
-
|
|
1250
|
-
// ignore if not a mouse event or disabled
|
|
1251
|
-
if (pointerEvent.pointerType !== 'mouse' || this.disabled() || !isRootNavigationMenu(this.context)) {
|
|
1014
|
+
if (event.pointerType === 'touch' || !this.openOnHover()) {
|
|
1252
1015
|
return;
|
|
1253
1016
|
}
|
|
1254
|
-
this.
|
|
1255
|
-
this.context.onTriggerLeave?.(); // Trigger leave logic (handles delays)
|
|
1256
|
-
this.hasPointerMoveOpened = false; // Reset flag
|
|
1257
|
-
// reset user dismissal flag if pointer leaves the whole system (trigger + content)
|
|
1258
|
-
if (this.context.resetUserDismissed) {
|
|
1259
|
-
// relay slightly to allow pointer movement to content area without resetting dismissal state
|
|
1260
|
-
setTimeout(() => {
|
|
1261
|
-
if (!this.context.isPointerInSystem?.()) {
|
|
1262
|
-
this.context.resetUserDismissed?.();
|
|
1263
|
-
}
|
|
1264
|
-
}, 50); // small delay for tolerance
|
|
1265
|
-
}
|
|
1017
|
+
this.rootContext.cancelHoverOpen();
|
|
1266
1018
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* Open-follows-focus: while the menu is already open, moving keyboard focus (arrow keys via
|
|
1021
|
+
* roving) to another trigger switches the shared popup to that item — matching Base UI, so the
|
|
1022
|
+
* open menu visibly responds to arrow-key navigation. Focus never *opens* a closed menu.
|
|
1023
|
+
*/
|
|
1024
|
+
onFocus() {
|
|
1025
|
+
if (this.disabled() || !this.rootContext.isOpen() || this.open()) {
|
|
1269
1026
|
return;
|
|
1270
|
-
// manually set the `KeyManager` active item to this trigger
|
|
1271
|
-
this.list.setActiveItem(this.item);
|
|
1272
|
-
if (this.context.onItemSelect) {
|
|
1273
|
-
this.context.onItemSelect(this.item.value());
|
|
1274
|
-
// track if this click action resulted in closing the menu
|
|
1275
|
-
this.wasClickClose = !this.open();
|
|
1276
|
-
// reset escape flag if menu was opened by click
|
|
1277
|
-
if (this.open()) {
|
|
1278
|
-
this.item.wasEscapeCloseRef.set(false);
|
|
1279
|
-
}
|
|
1280
1027
|
}
|
|
1028
|
+
this.rootContext.open(this.item.value(), this.elementRef.nativeElement, 'trigger-focus');
|
|
1281
1029
|
}
|
|
1282
1030
|
onKeydown(event) {
|
|
1283
|
-
|
|
1284
|
-
if (this.disabled())
|
|
1285
|
-
return;
|
|
1286
|
-
if (keyEvent.key === ENTER || keyEvent.key === SPACE) {
|
|
1287
|
-
event.preventDefault(); // prevent default button behavior
|
|
1288
|
-
this.onClick();
|
|
1289
|
-
// if menu was opened by this keypress, move focus into the content
|
|
1290
|
-
if (this.open()) {
|
|
1291
|
-
// defer focus slightly to ensure content is ready
|
|
1292
|
-
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
|
1293
|
-
}
|
|
1031
|
+
if (this.disabled()) {
|
|
1294
1032
|
return;
|
|
1295
1033
|
}
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
if (keyEvent.key === ARROW_DOWN) {
|
|
1301
|
-
event.preventDefault();
|
|
1302
|
-
}
|
|
1303
|
-
// if the menu is open, focus into the content
|
|
1034
|
+
const entryKey = this.entryKey();
|
|
1035
|
+
if (event.key === ENTER || event.key === SPACE) {
|
|
1036
|
+
event.preventDefault();
|
|
1037
|
+
this.rootContext.toggle(this.item.value(), this.elementRef.nativeElement, event);
|
|
1304
1038
|
if (this.open()) {
|
|
1305
|
-
|
|
1306
|
-
// needed to ensure that the `keyManager` on the list directive does not activate
|
|
1307
|
-
// any focus updates, shifting focus to the subsequent focusable list item
|
|
1308
|
-
event.stopImmediatePropagation();
|
|
1309
|
-
}
|
|
1310
|
-
// direct focus handling for viewport case
|
|
1311
|
-
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
|
1312
|
-
// get the viewport element
|
|
1313
|
-
const viewport = this.context.viewport();
|
|
1314
|
-
if (viewport) {
|
|
1315
|
-
// find all tabbable elements in the viewport
|
|
1316
|
-
const tabbables = getTabbableCandidates(viewport);
|
|
1317
|
-
if (tabbables.length > 0) {
|
|
1318
|
-
// focus the first tabbable element directly
|
|
1319
|
-
setTimeout(() => {
|
|
1320
|
-
tabbables[0].focus();
|
|
1321
|
-
}, 0);
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
// fallback to the standard entry key down approach
|
|
1327
|
-
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
|
1328
|
-
return;
|
|
1039
|
+
this.focusContent();
|
|
1329
1040
|
}
|
|
1330
|
-
// if not open but in horizontal orientation, emulate right key navigation
|
|
1331
|
-
if (isHorizontal) {
|
|
1332
|
-
const nextEvent = new KeyboardEvent('keydown', {
|
|
1333
|
-
key: isRTL ? ARROW_LEFT : ARROW_RIGHT,
|
|
1334
|
-
bubbles: true
|
|
1335
|
-
});
|
|
1336
|
-
this.elementRef.nativeElement.dispatchEvent(nextEvent);
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
// handle ArrowUp in horizontal orientation
|
|
1341
|
-
if (isHorizontal && keyEvent.key === ARROW_UP) {
|
|
1342
|
-
event.preventDefault();
|
|
1343
|
-
// emulate a left key press to move to the previous item
|
|
1344
|
-
const nextEvent = new KeyboardEvent('keydown', {
|
|
1345
|
-
key: isRTL ? ARROW_RIGHT : ARROW_LEFT,
|
|
1346
|
-
bubbles: true
|
|
1347
|
-
});
|
|
1348
|
-
this.elementRef.nativeElement.dispatchEvent(nextEvent);
|
|
1349
1041
|
return;
|
|
1350
1042
|
}
|
|
1351
|
-
|
|
1352
|
-
const verticalEntryKey = isRTL ? ARROW_LEFT : ARROW_RIGHT;
|
|
1353
|
-
const entryKey = isHorizontal ? ARROW_DOWN : verticalEntryKey;
|
|
1354
|
-
if (this.item.contentRef() && keyEvent.key === entryKey && keyEvent.key !== ARROW_DOWN) {
|
|
1355
|
-
// Skip if it's ArrowDown as we already handled it above
|
|
1043
|
+
if (event.key === entryKey) {
|
|
1356
1044
|
event.preventDefault();
|
|
1357
1045
|
if (!this.open()) {
|
|
1358
|
-
|
|
1359
|
-
this.context.onItemSelect?.(this.item.value());
|
|
1360
|
-
// defer focus movement into content until after state update and render
|
|
1361
|
-
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
|
1046
|
+
this.rootContext.open(this.item.value(), this.elementRef.nativeElement, 'trigger-press', event);
|
|
1362
1047
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1048
|
+
this.focusContent();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/** The key that moves focus from the trigger into the open content, based on orientation/dir. */
|
|
1052
|
+
entryKey() {
|
|
1053
|
+
if (this.rootContext.orientation() === 'horizontal') {
|
|
1054
|
+
return ARROW_DOWN;
|
|
1055
|
+
}
|
|
1056
|
+
return this.rootContext.dir() === 'rtl' ? ARROW_LEFT : ARROW_RIGHT;
|
|
1057
|
+
}
|
|
1058
|
+
focusContent() {
|
|
1059
|
+
// Content is rendered into the shared popup, which mounts through Presence + Portal and is
|
|
1060
|
+
// then positioned asynchronously by floating-ui. Until it is positioned the popper keeps it
|
|
1061
|
+
// `visibility: hidden`, so its tabbables aren't focusable yet. Poll across animation frames
|
|
1062
|
+
// until focus actually lands inside the panel (don't give up just because the element exists).
|
|
1063
|
+
const contentId = this.item.contentId();
|
|
1064
|
+
let attempts = 0;
|
|
1065
|
+
const tryFocus = () => {
|
|
1066
|
+
const content = this.document.getElementById(contentId);
|
|
1067
|
+
const candidates = content ? getTabbableCandidates(content) : [];
|
|
1068
|
+
if (candidates.length && focusFirst(candidates)) {
|
|
1069
|
+
return;
|
|
1366
1070
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1071
|
+
if (attempts++ < 15) {
|
|
1072
|
+
requestAnimationFrame(tryFocus);
|
|
1073
|
+
}
|
|
1074
|
+
else if (content) {
|
|
1075
|
+
// Fallback: no tabbable content — focus the panel container itself.
|
|
1076
|
+
content.focus();
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
requestAnimationFrame(tryFocus);
|
|
1369
1080
|
}
|
|
1370
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1371
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type:
|
|
1081
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1082
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuTrigger, isStandalone: true, selector: "button[rdxNavigationMenuTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "onClick($event)", "pointerenter": "onPointerEnter($event)", "pointerleave": "onPointerLeave($event)", "keydown": "onKeydown($event)", "focus": "onFocus()" }, properties: { "id": "item.triggerId()", "attr.aria-controls": "item.contentId()", "attr.aria-expanded": "open()", "attr.aria-haspopup": "\"menu\"", "attr.data-state": "open() ? \"open\" : \"closed\"", "attr.data-popup-open": "open() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined", "attr.disabled": "disabled() ? \"\" : undefined" } }, hostDirectives: [{ directive: i1$1.RdxRovingFocusItemDirective }], ngImport: i0 }); }
|
|
1372
1083
|
}
|
|
1373
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1084
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuTrigger, decorators: [{
|
|
1374
1085
|
type: Directive,
|
|
1375
1086
|
args: [{
|
|
1376
|
-
selector: '[rdxNavigationMenuTrigger]',
|
|
1087
|
+
selector: 'button[rdxNavigationMenuTrigger]',
|
|
1377
1088
|
hostDirectives: [RdxRovingFocusItemDirective],
|
|
1378
1089
|
host: {
|
|
1379
|
-
|
|
1090
|
+
type: 'button',
|
|
1091
|
+
'[id]': 'item.triggerId()',
|
|
1092
|
+
'[attr.aria-controls]': 'item.contentId()',
|
|
1093
|
+
'[attr.aria-expanded]': 'open()',
|
|
1094
|
+
'[attr.aria-haspopup]': '"menu"',
|
|
1380
1095
|
'[attr.data-state]': 'open() ? "open" : "closed"',
|
|
1381
|
-
'[attr.data-
|
|
1096
|
+
'[attr.data-popup-open]': 'open() ? "" : undefined',
|
|
1382
1097
|
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
1383
1098
|
'[attr.disabled]': 'disabled() ? "" : undefined',
|
|
1384
|
-
'
|
|
1385
|
-
'
|
|
1386
|
-
'[attr.aria-haspopup]': '"menu"',
|
|
1387
|
-
'(pointerenter)': 'onPointerEnter()',
|
|
1388
|
-
'(pointermove)': 'onPointerMove($event)',
|
|
1099
|
+
'(click)': 'onClick($event)',
|
|
1100
|
+
'(pointerenter)': 'onPointerEnter($event)',
|
|
1389
1101
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
1390
|
-
'(click)': 'onClick()',
|
|
1391
1102
|
'(keydown)': 'onKeydown($event)',
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuTriggerDirective }]
|
|
1103
|
+
'(focus)': 'onFocus()'
|
|
1104
|
+
}
|
|
1395
1105
|
}]
|
|
1396
1106
|
}], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], openOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnHover", required: false }] }] } });
|
|
1397
1107
|
|
|
1398
|
-
|
|
1108
|
+
/**
|
|
1109
|
+
* Clips and animates the active item's content. A single viewport is shared between all items; when
|
|
1110
|
+
* the active item changes the outgoing content is retained as `data-previous` until its CSS
|
|
1111
|
+
* animation/transition completes, and the new content is marked `data-current`.
|
|
1112
|
+
*/
|
|
1113
|
+
class RdxNavigationMenuViewport {
|
|
1399
1114
|
constructor() {
|
|
1400
|
-
this.
|
|
1401
|
-
this.document = injectDocument();
|
|
1402
|
-
this.window = injectWindow();
|
|
1115
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
1403
1116
|
this.elementRef = inject(ElementRef);
|
|
1404
1117
|
this.viewContainerRef = inject(ViewContainerRef);
|
|
1405
1118
|
this.renderer = inject(Renderer2);
|
|
1406
|
-
this.zone = inject(NgZone);
|
|
1407
|
-
this.destroyRef = inject(DestroyRef);
|
|
1408
1119
|
/**
|
|
1409
|
-
*
|
|
1410
|
-
* Useful for animations.
|
|
1411
|
-
* @default false
|
|
1120
|
+
* Keep the viewport mounted even when the menu is closed.
|
|
1412
1121
|
*/
|
|
1413
1122
|
this.forceMount = input(false, { ...(ngDevMode ? { debugName: "forceMount" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1414
|
-
this.
|
|
1415
|
-
this.
|
|
1416
|
-
this.
|
|
1417
|
-
this.
|
|
1418
|
-
this.
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
if (!isRootNavigationMenu(this.context))
|
|
1425
|
-
return false;
|
|
1426
|
-
return Boolean(this.context.value() || this.forceMount());
|
|
1427
|
-
}, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
1428
|
-
this.dataState = computed(() => getOpenStateLabel(this.isOpen()), ...(ngDevMode ? [{ debugName: "dataState" }] : /* istanbul ignore next */ []));
|
|
1429
|
-
this.viewportSize = computed(() => this._viewportSize(), ...(ngDevMode ? [{ debugName: "viewportSize" }] : /* istanbul ignore next */ []));
|
|
1430
|
-
this._resizeObserver = new ResizeObserver(() => this.updateSize());
|
|
1431
|
-
this.setupViewportEffect();
|
|
1432
|
-
}
|
|
1433
|
-
ngOnInit() {
|
|
1434
|
-
if (isRootNavigationMenu(this.context) && this.context.onViewportChange) {
|
|
1435
|
-
this.context.onViewportChange(this.elementRef.nativeElement);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
ngOnDestroy() {
|
|
1439
|
-
this._resizeObserver.disconnect();
|
|
1440
|
-
// clean up any remaining nodes/views/subscriptions
|
|
1441
|
-
this._contentNodes().forEach((node) => this.cleanupAfterLeave(node));
|
|
1442
|
-
if (isRootNavigationMenu(this.context) && this.context.onViewportChange) {
|
|
1443
|
-
this.context.onViewportChange(null);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
onKeydown(event) {
|
|
1447
|
-
const keyEvent = event;
|
|
1448
|
-
if (!this.isOpen())
|
|
1449
|
-
return;
|
|
1450
|
-
const tabbableElements = getTabbableCandidates(this.elementRef.nativeElement);
|
|
1451
|
-
if (!tabbableElements.length)
|
|
1452
|
-
return;
|
|
1453
|
-
const activeElement = this.document.activeElement;
|
|
1454
|
-
const currentIndex = tabbableElements.findIndex((el) => el === activeElement);
|
|
1455
|
-
if (keyEvent.key === ARROW_DOWN) {
|
|
1456
|
-
event.preventDefault();
|
|
1457
|
-
const nextIndex = currentIndex >= 0 && currentIndex < tabbableElements.length - 1 ? currentIndex + 1 : 0;
|
|
1458
|
-
tabbableElements[nextIndex]?.focus();
|
|
1459
|
-
}
|
|
1460
|
-
else if (keyEvent.key === ARROW_UP) {
|
|
1461
|
-
event.preventDefault();
|
|
1462
|
-
const prevIndex = currentIndex > 0 ? currentIndex - 1 : tabbableElements.length - 1;
|
|
1463
|
-
tabbableElements[prevIndex]?.focus();
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
onPointerEnter() {
|
|
1467
|
-
if (isRootNavigationMenu(this.context) && this.context.onContentEnter) {
|
|
1468
|
-
this.context.onContentEnter();
|
|
1469
|
-
}
|
|
1470
|
-
if (isRootNavigationMenu(this.context) && this.context.setContentPointerState) {
|
|
1471
|
-
this.context.setContentPointerState(true);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
onPointerLeave() {
|
|
1475
|
-
if (isRootNavigationMenu(this.context) && this.context.onContentLeave) {
|
|
1476
|
-
this.context.onContentLeave();
|
|
1477
|
-
}
|
|
1478
|
-
if (isRootNavigationMenu(this.context) && this.context.setContentPointerState) {
|
|
1479
|
-
this.context.setContentPointerState(false);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
setupViewportEffect() {
|
|
1123
|
+
this.activationDirection = signal(undefined, ...(ngDevMode ? [{ debugName: "activationDirection" }] : /* istanbul ignore next */ []));
|
|
1124
|
+
this.transitioning = signal(false, ...(ngDevMode ? [{ debugName: "transitioning" }] : /* istanbul ignore next */ []));
|
|
1125
|
+
this.size = signal(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1126
|
+
this.current = null;
|
|
1127
|
+
this.previousElement = null;
|
|
1128
|
+
this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => this.measure()) : null;
|
|
1129
|
+
this.activeContent = computed(() => this.rootContext.activeContent(), ...(ngDevMode ? [{ debugName: "activeContent" }] : /* istanbul ignore next */ []));
|
|
1130
|
+
const unregister = this.rootContext.registerViewport((previous, next) => {
|
|
1131
|
+
this.pendingDirection = getActivationDirection(previous, next);
|
|
1132
|
+
});
|
|
1483
1133
|
effect(() => {
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const currentNodesMap = this._contentNodes();
|
|
1494
|
-
let enteringNode = null;
|
|
1495
|
-
let leavingNode = this._leavingContentNode(); // get potentially already leaving node
|
|
1496
|
-
// 1. Identify Entering Node
|
|
1497
|
-
if (currentActiveValue && allContentData.has(currentActiveValue)) {
|
|
1498
|
-
enteringNode = this.getOrCreateContentNode(currentActiveValue);
|
|
1499
|
-
}
|
|
1500
|
-
// 2. Identify Leaving Node
|
|
1501
|
-
const nodeThatWasActive = previousActiveValue ? currentNodesMap.get(previousActiveValue) : null;
|
|
1502
|
-
// if there was a previously active node, it's different from the entering one,
|
|
1503
|
-
// and it's not already leaving, mark it for removal.
|
|
1504
|
-
if (nodeThatWasActive && nodeThatWasActive !== enteringNode && nodeThatWasActive !== leavingNode) {
|
|
1505
|
-
// if another node was already leaving, force complete its transition
|
|
1506
|
-
if (leavingNode) {
|
|
1507
|
-
this.forceCompleteLeaveTransition(leavingNode);
|
|
1508
|
-
}
|
|
1509
|
-
leavingNode = nodeThatWasActive;
|
|
1510
|
-
this._leavingContentNode.set(leavingNode);
|
|
1511
|
-
}
|
|
1512
|
-
// 3. Handle Entering Node
|
|
1513
|
-
if (enteringNode) {
|
|
1514
|
-
// cancel any pending leave transition for this node if it was leaving
|
|
1515
|
-
if (enteringNode === leavingNode) {
|
|
1516
|
-
this.cancelLeaveTransition(enteringNode);
|
|
1517
|
-
leavingNode = null;
|
|
1518
|
-
this._leavingContentNode.set(null);
|
|
1519
|
-
}
|
|
1520
|
-
// ensure it's in the DOM and set state to open
|
|
1521
|
-
this.addNodeToDOM(enteringNode);
|
|
1522
|
-
this.setNodeState(enteringNode, 'open'); // Triggers enter animation via data-state
|
|
1523
|
-
this._activeContentNode.set(enteringNode);
|
|
1524
|
-
this.updateSize(); // Update size based on the entering node
|
|
1525
|
-
}
|
|
1526
|
-
else {
|
|
1527
|
-
// no node entering, clear active node state
|
|
1528
|
-
this._activeContentNode.set(null);
|
|
1529
|
-
}
|
|
1530
|
-
// 4. Handle Leaving Node
|
|
1531
|
-
if (leavingNode) {
|
|
1532
|
-
if (forceMount) {
|
|
1533
|
-
// if forceMount, just mark as closed, don't trigger removal animation
|
|
1534
|
-
this.setNodeState(leavingNode, 'closed');
|
|
1535
|
-
this._leavingContentNode.set(null); // No longer considered "leaving"
|
|
1536
|
-
}
|
|
1537
|
-
else {
|
|
1538
|
-
// start the leave transition (usePresence handles DOM removal)
|
|
1539
|
-
this.startLeaveTransition(leavingNode);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
});
|
|
1134
|
+
const entry = this.activeContent();
|
|
1135
|
+
untracked(() => this.render(entry));
|
|
1136
|
+
});
|
|
1137
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1138
|
+
unregister();
|
|
1139
|
+
this.resizeObserver?.disconnect();
|
|
1140
|
+
this.clearCleanupTimer();
|
|
1141
|
+
this.removePrevious();
|
|
1142
|
+
this.current?.view.destroy();
|
|
1543
1143
|
});
|
|
1544
1144
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const existingNode = this._contentNodes().get(contentValue);
|
|
1548
|
-
if (existingNode && !existingNode.embeddedView.destroyed) {
|
|
1549
|
-
return existingNode;
|
|
1550
|
-
}
|
|
1551
|
-
// create if doesn't exist or view was destroyed
|
|
1552
|
-
if (!isRootNavigationMenu(this.context) || !this.context.viewportContent)
|
|
1553
|
-
return null;
|
|
1554
|
-
const allContentData = this.context.viewportContent();
|
|
1555
|
-
const contentData = allContentData.get(contentValue);
|
|
1556
|
-
const templateRef = contentData?.templateRef;
|
|
1557
|
-
if (!templateRef) {
|
|
1558
|
-
console.error(`No templateRef found for content value: ${contentValue}`);
|
|
1559
|
-
return null;
|
|
1560
|
-
}
|
|
1561
|
-
try {
|
|
1562
|
-
const embeddedView = this.viewContainerRef.createEmbeddedView(templateRef);
|
|
1563
|
-
const container = this.renderer.createElement('div');
|
|
1564
|
-
this.renderer.setAttribute(container, 'class', 'NavigationMenuContentWrapper');
|
|
1565
|
-
this.renderer.setAttribute(container, 'data-content-value', contentValue);
|
|
1566
|
-
embeddedView.rootNodes.forEach((node) => this.renderer.appendChild(container, node));
|
|
1567
|
-
const newNode = {
|
|
1568
|
-
embeddedView,
|
|
1569
|
-
element: container,
|
|
1570
|
-
contentValue,
|
|
1571
|
-
state: 'closed'
|
|
1572
|
-
};
|
|
1573
|
-
const newMap = new Map(this._contentNodes());
|
|
1574
|
-
newMap.set(contentValue, newNode);
|
|
1575
|
-
this._contentNodes.set(newMap);
|
|
1576
|
-
return newNode;
|
|
1577
|
-
}
|
|
1578
|
-
catch (error) {
|
|
1579
|
-
console.error(`Error creating content node for ${contentValue}:`, error);
|
|
1580
|
-
return null;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
// adds node element to viewport DOM if not already present
|
|
1584
|
-
addNodeToDOM(node) {
|
|
1585
|
-
if (!this.elementRef.nativeElement.contains(node.element)) {
|
|
1586
|
-
this.renderer.appendChild(this.elementRef.nativeElement, node.element);
|
|
1587
|
-
// observe size only when added to DOM
|
|
1588
|
-
this._resizeObserver.observe(node.element);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
// removes node element from viewport DOM
|
|
1592
|
-
removeNodeFromDOM(node) {
|
|
1593
|
-
if (this.elementRef.nativeElement.contains(node.element)) {
|
|
1594
|
-
this._resizeObserver.unobserve(node.element); // stop observing before removal
|
|
1595
|
-
this.renderer.removeChild(this.elementRef.nativeElement, node.element);
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
// updates the data-state and motion attributes
|
|
1599
|
-
setNodeState(node, state) {
|
|
1600
|
-
if (node.state === state)
|
|
1601
|
-
return; // avoid redundant updates
|
|
1602
|
-
node.state = state;
|
|
1603
|
-
this.renderer.setAttribute(node.element, 'data-state', state);
|
|
1604
|
-
// apply motion attribute based on context
|
|
1605
|
-
if (isRootNavigationMenu(this.context) && this.context.viewportContent) {
|
|
1606
|
-
const contentData = this.context.viewportContent().get(node.contentValue);
|
|
1607
|
-
if (contentData?.getMotionAttribute) {
|
|
1608
|
-
// get motion based on current state transition
|
|
1609
|
-
const motionAttr = contentData.getMotionAttribute();
|
|
1610
|
-
if (motionAttr) {
|
|
1611
|
-
this.renderer.setAttribute(node.element, 'data-motion', motionAttr);
|
|
1612
|
-
}
|
|
1613
|
-
else {
|
|
1614
|
-
this.renderer.removeAttribute(node.element, 'data-motion');
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
else {
|
|
1618
|
-
this.renderer.removeAttribute(node.element, 'data-motion');
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
// apply A11y attributes (might only be needed on open?)
|
|
1622
|
-
if (state === 'open') {
|
|
1623
|
-
this.applyA11yAttributes(node);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
// apply A11y attributes to the first child element
|
|
1627
|
-
applyA11yAttributes(node) {
|
|
1628
|
-
if (!isRootNavigationMenu(this.context) || !this.context.viewportContent)
|
|
1145
|
+
render(entry) {
|
|
1146
|
+
if (!entry) {
|
|
1629
1147
|
return;
|
|
1630
|
-
const contentData = this.context.viewportContent().get(node.contentValue);
|
|
1631
|
-
if (contentData?.additionalAttrs && node.embeddedView.rootNodes.length > 0) {
|
|
1632
|
-
const firstRootNode = node.embeddedView.rootNodes[0];
|
|
1633
|
-
if (firstRootNode) {
|
|
1634
|
-
Object.entries(contentData.additionalAttrs).forEach(([attr, value]) => {
|
|
1635
|
-
this.renderer.setAttribute(firstRootNode, attr, value);
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
1148
|
}
|
|
1639
|
-
|
|
1640
|
-
startLeaveTransition(node) {
|
|
1641
|
-
// ensure node exists and isn't already leaving with an active subscription
|
|
1642
|
-
if (!node || node.transitionSubscription) {
|
|
1643
|
-
node.transitionSubscription?.unsubscribe();
|
|
1149
|
+
if (this.current?.value === entry.value) {
|
|
1644
1150
|
return;
|
|
1645
1151
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1152
|
+
// Snapshot the outgoing content so it can animate out as `data-previous`.
|
|
1153
|
+
if (this.current) {
|
|
1154
|
+
this.resizeObserver?.unobserve(this.current.measureTarget);
|
|
1155
|
+
this.startLeave(this.current.element);
|
|
1156
|
+
this.current.view.destroy();
|
|
1157
|
+
}
|
|
1158
|
+
const view = this.viewContainerRef.createEmbeddedView(entry.templateRef);
|
|
1159
|
+
view.detectChanges();
|
|
1160
|
+
const element = this.renderer.createElement('div');
|
|
1161
|
+
this.renderer.setAttribute(element, 'data-current', '');
|
|
1162
|
+
this.renderer.setAttribute(element, 'id', entry.contentId);
|
|
1163
|
+
this.renderer.setAttribute(element, 'aria-labelledby', entry.triggerId);
|
|
1164
|
+
// Make the panel a valid focus target for keyboard entry when it has no tabbable content.
|
|
1165
|
+
this.renderer.setAttribute(element, 'tabindex', '-1');
|
|
1166
|
+
view.rootNodes.forEach((node) => this.renderer.appendChild(element, node));
|
|
1167
|
+
this.renderer.appendChild(this.elementRef.nativeElement, element);
|
|
1168
|
+
// Measure the content's own root element, not the wrapper: the wrapper is `display: block`
|
|
1169
|
+
// and stretches to the viewport, so measuring it would feed the viewport its own width back
|
|
1170
|
+
// and the popup would balloon to fill the page. The content root sizes to its content.
|
|
1171
|
+
const measureTarget = element.firstElementChild ?? element;
|
|
1172
|
+
this.current = { value: entry.value, view, element, measureTarget };
|
|
1173
|
+
this.resizeObserver?.observe(measureTarget);
|
|
1174
|
+
this.measure();
|
|
1175
|
+
}
|
|
1176
|
+
startLeave(element) {
|
|
1177
|
+
this.removePrevious();
|
|
1178
|
+
const previous = element.cloneNode(true);
|
|
1179
|
+
previous.removeAttribute('data-current');
|
|
1180
|
+
previous.setAttribute('data-previous', '');
|
|
1181
|
+
previous.setAttribute('aria-hidden', 'true');
|
|
1182
|
+
previous.setAttribute('inert', '');
|
|
1183
|
+
removeIds(previous);
|
|
1184
|
+
this.activationDirection.set(this.pendingDirection);
|
|
1185
|
+
this.pendingDirection = undefined;
|
|
1186
|
+
this.previousElement = previous;
|
|
1187
|
+
this.transitioning.set(true);
|
|
1188
|
+
this.renderer.appendChild(this.elementRef.nativeElement, previous);
|
|
1189
|
+
const onEnd = () => this.removePrevious();
|
|
1190
|
+
previous.addEventListener('animationend', onEnd, { once: true });
|
|
1191
|
+
previous.addEventListener('transitionend', onEnd, { once: true });
|
|
1192
|
+
const duration = getMaxTransitionDuration(previous);
|
|
1193
|
+
this.cleanupTimer = setTimeout(onEnd, duration > 0 ? duration + 50 : 0);
|
|
1194
|
+
}
|
|
1195
|
+
removePrevious() {
|
|
1196
|
+
this.clearCleanupTimer();
|
|
1197
|
+
this.previousElement?.remove();
|
|
1198
|
+
this.previousElement = null;
|
|
1199
|
+
this.transitioning.set(false);
|
|
1200
|
+
}
|
|
1201
|
+
clearCleanupTimer() {
|
|
1202
|
+
if (this.cleanupTimer !== undefined) {
|
|
1203
|
+
clearTimeout(this.cleanupTimer);
|
|
1204
|
+
this.cleanupTimer = undefined;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
measure() {
|
|
1208
|
+
const target = this.current?.measureTarget;
|
|
1209
|
+
if (!target || !target.isConnected) {
|
|
1210
|
+
return;
|
|
1682
1211
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
node.transitionSubscription?.unsubscribe();
|
|
1688
|
-
node.transitionSubscription = null;
|
|
1212
|
+
const width = Math.ceil(target.offsetWidth);
|
|
1213
|
+
const height = Math.ceil(target.offsetHeight);
|
|
1214
|
+
if (width === 0 && height === 0) {
|
|
1215
|
+
return;
|
|
1689
1216
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
* @param node The node that is leaving
|
|
1694
|
-
*/
|
|
1695
|
-
cancelLeaveTransition(node) {
|
|
1696
|
-
node.transitionSubscription?.unsubscribe();
|
|
1697
|
-
node.transitionSubscription = null;
|
|
1698
|
-
}
|
|
1699
|
-
/**
|
|
1700
|
-
* Force completes a leave transition (e.g., if another leave starts)
|
|
1701
|
-
* @param node The node that is leaving
|
|
1702
|
-
*/
|
|
1703
|
-
forceCompleteLeaveTransition(node) {
|
|
1704
|
-
if (node && node.transitionSubscription) {
|
|
1705
|
-
node.transitionSubscription.unsubscribe();
|
|
1706
|
-
// perform cleanup immediately
|
|
1707
|
-
this.cleanupAfterLeave(node);
|
|
1217
|
+
const size = this.size();
|
|
1218
|
+
if (!size || size.width !== width || size.height !== height) {
|
|
1219
|
+
this.size.set({ width, height });
|
|
1708
1220
|
}
|
|
1709
1221
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
if (!activeNode || !activeNode.isConnected)
|
|
1713
|
-
return;
|
|
1714
|
-
const firstChild = activeNode.firstChild;
|
|
1715
|
-
if (!firstChild)
|
|
1716
|
-
return;
|
|
1717
|
-
this.window.requestAnimationFrame(() => {
|
|
1718
|
-
// keep rAF here for measurement stability
|
|
1719
|
-
activeNode.getBoundingClientRect(); // force layout
|
|
1720
|
-
const width = Math.ceil(firstChild.offsetWidth);
|
|
1721
|
-
const height = Math.ceil(firstChild.offsetHeight);
|
|
1722
|
-
if (width !== 0 || height !== 0) {
|
|
1723
|
-
const currentSize = this._viewportSize();
|
|
1724
|
-
if (!currentSize || currentSize.width !== width || currentSize.height !== height) {
|
|
1725
|
-
this._viewportSize.set({ width, height });
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
else if (this._viewportSize() !== null) {
|
|
1729
|
-
this._viewportSize.set(null);
|
|
1730
|
-
}
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuViewportDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1734
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuViewportDirective, isStandalone: true, selector: "[rdxNavigationMenuViewport]", inputs: { forceMount: { classPropertyName: "forceMount", publicName: "forceMount", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)", "pointerenter": "onPointerEnter()", "pointerleave": "onPointerLeave()" }, properties: { "attr.data-state": "dataState()", "attr.data-orientation": "context.orientation", "style.--radix-navigation-menu-viewport-width.px": "viewportSize()?.width", "style.--radix-navigation-menu-viewport-height.px": "viewportSize()?.height" } }, ngImport: i0 }); }
|
|
1222
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1223
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuViewport, isStandalone: true, selector: "[rdxNavigationMenuViewport]", inputs: { forceMount: { classPropertyName: "forceMount", publicName: "forceMount", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-orientation": "rootContext.orientation()", "attr.data-activation-direction": "activationDirection()", "attr.data-transitioning": "transitioning() ? \"\" : undefined", "style.--popup-width.px": "size()?.width", "style.--popup-height.px": "size()?.height" } }, ngImport: i0 }); }
|
|
1735
1224
|
}
|
|
1736
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1225
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuViewport, decorators: [{
|
|
1737
1226
|
type: Directive,
|
|
1738
1227
|
args: [{
|
|
1739
1228
|
selector: '[rdxNavigationMenuViewport]',
|
|
1740
1229
|
host: {
|
|
1741
|
-
'[attr.data-
|
|
1742
|
-
'[attr.data-
|
|
1743
|
-
'[
|
|
1744
|
-
'[
|
|
1745
|
-
'
|
|
1746
|
-
'
|
|
1747
|
-
'
|
|
1230
|
+
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
1231
|
+
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
1232
|
+
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
1233
|
+
'[attr.data-orientation]': 'rootContext.orientation()',
|
|
1234
|
+
'[attr.data-activation-direction]': 'activationDirection()',
|
|
1235
|
+
'[attr.data-transitioning]': 'transitioning() ? "" : undefined',
|
|
1236
|
+
'[style.--popup-width.px]': 'size()?.width',
|
|
1237
|
+
'[style.--popup-height.px]': 'size()?.height'
|
|
1748
1238
|
}
|
|
1749
1239
|
}]
|
|
1750
1240
|
}], ctorParameters: () => [], propDecorators: { forceMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceMount", required: false }] }] } });
|
|
1751
1241
|
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1242
|
+
const navigationMenuImports = [
|
|
1243
|
+
RdxNavigationMenuRoot,
|
|
1244
|
+
RdxNavigationMenuList,
|
|
1245
|
+
RdxNavigationMenuItem,
|
|
1246
|
+
RdxNavigationMenuTrigger,
|
|
1247
|
+
RdxNavigationMenuIcon,
|
|
1248
|
+
RdxNavigationMenuContent,
|
|
1249
|
+
RdxNavigationMenuLink,
|
|
1250
|
+
RdxNavigationMenuPortal,
|
|
1251
|
+
RdxNavigationMenuPortalPresence,
|
|
1252
|
+
RdxNavigationMenuBackdrop,
|
|
1253
|
+
RdxNavigationMenuPositioner,
|
|
1254
|
+
RdxNavigationMenuPopup,
|
|
1255
|
+
RdxNavigationMenuArrow,
|
|
1256
|
+
RdxNavigationMenuViewport
|
|
1764
1257
|
];
|
|
1765
1258
|
class RdxNavigationMenuModule {
|
|
1766
1259
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1767
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuModule, imports: [
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1260
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuModule, imports: [RdxNavigationMenuRoot,
|
|
1261
|
+
RdxNavigationMenuList,
|
|
1262
|
+
RdxNavigationMenuItem,
|
|
1263
|
+
RdxNavigationMenuTrigger,
|
|
1264
|
+
RdxNavigationMenuIcon,
|
|
1265
|
+
RdxNavigationMenuContent,
|
|
1266
|
+
RdxNavigationMenuLink,
|
|
1267
|
+
RdxNavigationMenuPortal,
|
|
1268
|
+
RdxNavigationMenuPortalPresence,
|
|
1269
|
+
RdxNavigationMenuBackdrop,
|
|
1270
|
+
RdxNavigationMenuPositioner,
|
|
1271
|
+
RdxNavigationMenuPopup,
|
|
1272
|
+
RdxNavigationMenuArrow,
|
|
1273
|
+
RdxNavigationMenuViewport], exports: [RdxNavigationMenuRoot,
|
|
1274
|
+
RdxNavigationMenuList,
|
|
1275
|
+
RdxNavigationMenuItem,
|
|
1276
|
+
RdxNavigationMenuTrigger,
|
|
1277
|
+
RdxNavigationMenuIcon,
|
|
1278
|
+
RdxNavigationMenuContent,
|
|
1279
|
+
RdxNavigationMenuLink,
|
|
1280
|
+
RdxNavigationMenuPortal,
|
|
1281
|
+
RdxNavigationMenuPortalPresence,
|
|
1282
|
+
RdxNavigationMenuBackdrop,
|
|
1283
|
+
RdxNavigationMenuPositioner,
|
|
1284
|
+
RdxNavigationMenuPopup,
|
|
1285
|
+
RdxNavigationMenuArrow,
|
|
1286
|
+
RdxNavigationMenuViewport] }); }
|
|
1788
1287
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuModule }); }
|
|
1789
1288
|
}
|
|
1790
1289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuModule, decorators: [{
|
|
1791
1290
|
type: NgModule,
|
|
1792
1291
|
args: [{
|
|
1793
|
-
imports: [...
|
|
1794
|
-
exports: [...
|
|
1292
|
+
imports: [...navigationMenuImports],
|
|
1293
|
+
exports: [...navigationMenuImports]
|
|
1795
1294
|
}]
|
|
1796
1295
|
}] });
|
|
1797
1296
|
|
|
@@ -1799,5 +1298,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1799
1298
|
* Generated bundle index. Do not edit.
|
|
1800
1299
|
*/
|
|
1801
1300
|
|
|
1802
|
-
export {
|
|
1301
|
+
export { RdxNavigationMenuArrow, RdxNavigationMenuBackdrop, RdxNavigationMenuContent, RdxNavigationMenuIcon, RdxNavigationMenuItem, RdxNavigationMenuLink, RdxNavigationMenuList, RdxNavigationMenuModule, RdxNavigationMenuPopup, RdxNavigationMenuPortal, RdxNavigationMenuPortalPresence, RdxNavigationMenuPositioner, RdxNavigationMenuRoot, RdxNavigationMenuTrigger, RdxNavigationMenuViewport, injectNavigationMenuRootContext, navigationMenuImports, provideNavigationMenuRootContext };
|
|
1803
1302
|
//# sourceMappingURL=radix-ng-primitives-navigation-menu.mjs.map
|